EhCache简单入门
一 介绍
EhCache 是一个纯Java
的进程内缓存框架
,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。它具有内存
和磁盘
存储,缓存加载器,缓存扩展,缓存异常处理程序,一个gzip缓存servlet过滤器,支持REST和SOAP api等特点。
特性
- 快速、简单
- 多种
缓存策略
- 缓存数据有两级:
内存和磁盘
,因此无需担心容量问题
- 缓存数据会在虚拟机
重启
的过程中写入磁盘
- 可以通过
RMI
、可插入API等方式进行分布式缓存
- 具有缓存和缓存管理器的侦听接口
- 支持
多
缓存管理器实例
,以及一个实例的多个缓存区域
- 提供
Hibernate
的缓存实现
集成
可以单独使用,一般在第三方库中被用到的比较多(如mybatis、shiro等)ehcache 对分布式支持不够好
,多个节点不能同步
,通常和redis一块使用
灵活性
ehcache具备对象api接口
和可序列化api接口
不能序列化的对象
可以使用出磁盘存储外ehcache
的所有功能
支持基于Cache和基于Element的过期策略,每个Cache的存活时间都是可以设置和控制的。
提供了LRU、LFU和FIFO缓存淘汰算法,Ehcache 1.2引入了最少使用和先进先出缓存淘汰算法,构成了完整的缓存淘汰算法。
提供内存和磁盘存储,Ehcache和大多数缓存解决方案一样,提供高性能的内存和磁盘存储。
动态、运行时缓存配置,存活时间、空闲时间、内存和磁盘存放缓存的最大数目都是可以在运行时修改的。
应用持久化
在vm重启
后,持久化到磁盘的存储可以复原数据
Ehache是第一个引入缓存数据持久化存储的开源java缓存框架,缓存的数据可以在机器重启后从磁盘上重新获得
根据需要将缓存刷到磁盘。将缓存条目刷到磁盘
的操作可以通过cache.fiush
方法执行,这大大方便了ehcache的使用
ehcache 和 redis 比较
- ehcache直接在jvm虚拟机中缓存,
速度快
,效率高;但是缓存共享麻烦
,集群分布式应用不方便。 - redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,
处理集群和分布式缓存方便,有成熟的方案。如果是单个应用
或者对缓存访问要求很高
的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大
的,建议用redis。
二 Hello World
依赖
<dependencies>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/ehcache"/>
<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<!-- helloworld缓存 -->
<cache name="HelloWorldCache"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="5"
timeToLiveSeconds="5"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
测试类
package com.zyc;
import org.junit.Test;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
public class Test1 {
@Test
public void test1() {
// 1. 创建缓存管理器
CacheManager cacheManager = CacheManager.create("./src/main/resources/ehcache.xml");
// 2. 获取缓存对象
Cache cache = cacheManager.getCache("HelloWorldCache");
// 3. 创建元素
Element element = new Element("key1", "value1");
// 4. 将元素添加到缓存
cache.put(element);
// 5. 获取缓存
Element value = cache.get("key1");
System.out.println(value);
System.out.println(value.getObjectValue());
// 6. 删除元素
cache.remove("key1");
Person p1 = new Person("小明",18,"杭州");
Element pelement = new Element("xm", p1);
cache.put(pelement);
Element pelement2 = cache.get("xm");
System.out.println(pelement2.getObjectValue());
System.out.println(cache.getSize());
// 7. 刷新缓存
cache.flush();
// 8. 关闭缓存管理器
cacheManager.shutdown();
}
}
三 配置文件说明
diskStore
- path :指定磁盘存储的位置
defaultCache
默认的缓存
cache
自定的缓存,当自定的配置不满足实际情况时可以通过自定义(可以包含多个cache节点
)
name
: 缓存的名称,可以通过指定名称
获取指定的某个Cache对象
maxElementsInMemory
:内存中允许存储的最大的元素个数
,0代表无限
个clearOnFlush
:内存数量最大时是否清除
。eternal
:设置缓存中对象是否为永久
的,如果是,超时设置将被忽略
,对象从不过期。根据存储数据的不同,例如一些静态不变的数据如省市区等可以设置为永不过时timeToIdleSeconds
: 设置对象在失效前
的允许闲置
时间(单位:秒)。仅当eternal=false
对象不是
永久有效时使用
,可选属性,默认值是0,也就是可闲置时间无穷大
。timeToLiveSeconds
:缓存数据的生存时间
(TTL),也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。(和上面的两者取最小值
)overflowToDisk
:内存不足时,是否启用磁盘
缓存。maxEntriesLocalDisk
:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。maxElementsOnDisk
:硬盘最大缓存个数。diskSpoolBufferSizeMB
:这个参数设置DiskStore(磁盘缓存
)的缓存区大小
。默认是30MB。每个Cache都应该有自己的一个缓冲区。diskPersistent
:是否在VM重启时存储硬盘的缓存数据
。默认值是false。
diskExpiryThreadIntervalSeconds
:磁盘失效线程
运行时间间隔,默认是120秒。memoryStoreEvictionPolicy
:当达到maxElementsInMemory
限制时,Ehcache将会根据指定的策略
去清理内存。默认策略是LRU(最近最少使用)
。你可以设置为FIFO(先进先出
)或是LFU(较少使用)
。这里比较遗憾,Ehcache并没有提供一个用户定制策略的接口,仅仅支持三种指定策略,感觉做的不够理想。
编程方式配置
Cache cache = manager.getCache("mycache");
CacheConfiguration config = cache.getCacheConfiguration();
config.setTimeToIdleSeconds(60);
config.setTimeToLiveSeconds(120);
config.setmaxEntriesLocalHeap(10000);
config.setmaxEntriesLocalDisk(1000000);
持久化配置
类必须实现序列化接口,不需要的属性用transient
x修饰
这种是所有数据都放到磁盘里去了
<!-- helloworld缓存 -->
<cache name="HelloWorldCache"
maxElementsInMemory="1" //设置成1,overflowToDisk设置成true,只要有一个缓存元素,就直接存到硬盘上去
eternal="false"
timeToIdleSeconds="50000"
timeToLiveSeconds="50000"
overflowToDisk="true"
diskPersistent="true" //设置成true表示缓存虚拟机重启期数据
memoryStoreEvictionPolicy="LRU"/>
自己决定说明时候持久化
测试得出以下两个方法在配置持久化环境的情况下
都会将内存中的数据放到磁盘上
cache.flush();
// 8. 关闭缓存管理器
cacheManager.shutdown()
自动持久化
想利用spring 的注解,不想手动shutdown ,因此web.xml 配置listener 监听,在销毁的时候进行shutdown,这里利用ehcache 的监听.
<!-- ehcache 磁盘缓存 监控,持久化恢复 -->
<listener>
<listener-class>net.sf.ehcache.constructs.web.ShutdownListener</listener-class>
</listener>
直接杀死线程,这肯定监听不到。
更多资料(未测试过):记一次,ehcache缓存到磁盘,再恢复的过程
spring持久化测试情况
通过注解调用发现,每次test结束后,都会自动持久化
@Test
public void testPersist(){
System.out.println(ehcacheService.getDataFromDB("tt1"));
System.out.println(ehcacheService.getDataFromDB("tt1"));
}
@Cacheable(value="HelloWorldCache", key="#key")
@Override
public String getDataFromDB(String key) {
System.out.println("从数据库中获取数据...");
return key + ":" + String.valueOf(Math.round(Math.random()*1000000));
}
四 一致性模型
说到一致性,数据库的一致性
是怎样的?不妨先来回顾一下数据库的几个隔离级别:
未提交读(Read Uncommitted
):在读数据时不会检查或使用任何锁。因此,在这种隔离级别中可能读取到没有提交的数据。会出现脏读、不可重复读、幻象读。
已提交读(Read Committed
):只读取提交的数据并等待其他事务释放排他锁。读数据的共享锁在读操作完成后立即释放。已提交读是数据库的默认隔离级别。会出现不可重复读、幻象读。
可重复读(Repeatable Read
):像已提交读级别那样读数据,但会保持共享锁直到事务结束。会出现幻象读。
可序列化(Serializable
):工作方式类似于可重复读。但它不仅会锁定受影响的数据,还会锁定这个范围,这就阻止了新数据插入查询所涉及的范围
。
模型分类
强一致性模型
:系统中的某个数据被成功更新(事务成功返回)后,后续任何对该数据的读取操作都得到到
更新后的值。这是传统关系数据库提供的一致性模型,也是关系数据库
深受人们喜爱的原因之一。强一致性模型下的性能消耗
通常是最大
的弱一致性模型
:系统中的某个数据被更新后,后续对该数据的读取操作得到的不一定
是更新后的值,这种情况下通常有个“不一致性时间窗口
”存在:即数据更新完成后在经过
这个时间窗口
,后续读取操作就能够得到更新后的值。最终一致性模型
:属于弱一致性的一种,即某个数据被更新后,如果该数据后续没有被再次更新,那么最终
(没有时间窗口)所有的读取操作都会返回更新后的值Bulk Load
:这种模型是基于批量加载数据到缓存
里面的场景而优化的,没有引入锁和常规的淘汰算法
这些降低性能的东西
,它和最终一致性模型很像,但是有批量、高速写和弱一致性保证
的机制。
最终一致性模型包含如下几个必要属性
读写一致
:某线程A,更新某条数据以后,后续的访问全部都能取得更新后的数据。会话内一致
:它本质上和上面那一条是一致的,某用户更改了数据,只要会话还存在,后续他取得的所有数据都必须是更改后的数据。单调读一致
:如果一个进程可以看到当前的值,那么后续的访问不能返回之前的值。单调写一致
:对同一进程内的写行为必须是保序的,否则,写完毕的结果就是不可预期的了。·
API
1、显式锁
(Explicit Locking ):如果我们本身就配置为强一致性
,那么自然所有的缓存操作都具备事务性质。而如果我们配置成最终一致性时,再在外部使用显式锁API,也可以达到事务
的效果。当然这样的锁可以控制得更细粒度,但是依然可能存在竞争和线程阻塞。
2、无锁可读取视图(UnlockedReadsView)
:一个允许脏读
的decorator,它只能用在强一致性的配置下
,它通过申请一个特殊的写锁来比完全的强一致性配置提升性能
。
举例如下,xml配置为强一致性
模型:
<cache name="myCache"
maxElementsInMemory="500"
eternal="false"
overflowToDisk="false"
<terracotta clustered="true" consistency="strong" />
</cache>
但是使用UnlockedReadsView:
Cache cache = cacheManager.getEhcache("myCache");
UnlockedReadsView unlockedReadsView = new UnlockedReadsView(cache, "myUnlockedCache"); //代码上设置
3、原子方法(Atomic methods
):方法执行是原子化
的,即CAS
操作(Compare and Swap)。CAS最终
也实现了强一致性的效果,但不同的是,它是采用乐观锁而不是悲观锁来实现的。在乐观锁机制下,更新的操作可能不成功,因为在这过程中可能会有其他线程对同一条数据进行变更,那么在失败后需要重新执行更新操作。现代的CPU都支持CAS原语了
。
cache.putIfAbsent(Element element);
cache.replace(Element oldOne, Element newOne);
cache.remove(Element);
五 Spring整合
spring注解
Spring对缓存的支持类似于对事务的支持。
首先使用注解标记方法
,相当于定义了切点
,然后使用Aop
技术在这个方法的调用前
、调用后
获取方法的入参和返回值,进而实现了缓存的逻辑
。
@Cacheable
表明所修饰的方法是可以缓存的:当第一次
调用这个方法时,它的结果会被缓存下来,在缓存的有效时间内,以后访问这个方法都直接返回缓存结果
,不再执行
方法中的代码段
。
- 这个注解可以用
condition
属性来设置条件,如果不满足条件,就不使用缓存能力,直接执行方法。 - 可以使用
key
属性来指定key的生成规则
。
参数
value
:缓存位置名称,不能为空,如果使用EHCache,就是ehcache.xml中声明的cache的name
, 指明将值缓存到哪个Cache中key
:缓存的key,默认为空
,既表示使用方法的参数类型
及参数值
作为key,支持SpEL
,如果要引用参数值
使用井号加参数名,如:#userId
,
一般来说,我们的更新操作只需要刷新缓存中某一个值,所以定义缓存的key值的方式就很重要,最好是能够唯一,因为这样可以准确的清除掉特定的缓存,而不会影响到其它缓存值 ,
本例子中使用实体加冒号再加ID组合成键的名称,如"user:1"、"order:223123"等condition
:触发条件,只有满足条件的情况才会加入缓存,默认为空
,既表示全部都加入缓存
,支持SpEL
// 将缓存保存到名称为UserCache中,键为"user:"字符串加上userId值,如 'user:1'
@Cacheable(value="UserCache", key="'user:' + #userId")
public User findById(String userId) {
return (User) new User("1", "mengdee");
}
// 将缓存保存进UserCache中,并当参数userId的长度小于12时才保存进缓存,默认使用参数值及类型作为缓存的key
// 保存缓存需要指定key,value, value的数据类型,不指定key默认和参数名一样如:"1"
@Cacheable(value="UserCache", condition="#userId.length() < 12")
public boolean isReserved(String userId) {
System.out.println("UserCache:"+userId);
return false;
}
@CachePut
与@Cacheable不同,@CachePut不仅会缓存方法的结果
,还会执行
方法的代码段。它支持的属性和用法都与@Cacheable
一致。一个缓存后就不执行代码了,一个还要执行)
@CacheEvict
与@Cacheable功能相反,@CacheEvict表明所修饰的方法是用来删除失效
或无用
的缓存数据。
参数
- value:缓存位置名称,不能为空,同上
- key:缓存的key,默认为空,同上
- condition:触发条件,只有满足条件的情况才会清除缓存,默认为空,支持SpEL
allEntries
:true表示清除value中的全部缓存
,默认为false
//清除掉UserCache中某个指定key的缓存
@CacheEvict(value="UserCache",key="'user:' + #userId")
public void removeUser(User user) {
System.out.println("UserCache"+user.getUserId());
}
//清除掉UserCache中全部的缓存
@CacheEvict(value="UserCache", allEntries=true)
public final void setReservedUsers(String[] reservedUsers) {
System.out.println("UserCache deleteall");
}
代码测试
目录结构
pom
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zyc</groupId>
<artifactId>ehcache1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>4.10</junit.version>
<spring.version>4.2.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<!-- springframework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.3</version>
</dependency>
</dependencies>
</project>
接口实现
package com.zyc;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.zyc.service.EhcacheService;
public class EhcacheServiceTest extends Test2 {
@Autowired
private EhcacheService ehcacheService;
/*
* 有效时间是5秒,第一次和第二次获取的值是一样的,因第三次是5秒之后所以会获取新的值
*/
@Test
public void testTimestamp() throws InterruptedException{
System.out.println("第一次调用:" + ehcacheService.getTimestamp("param"));
Thread.sleep(2000);
System.out.println("2秒之后调用:" + ehcacheService.getTimestamp("param"));
Thread.sleep(4000);
System.out.println("再过4秒之后调用:" + ehcacheService.getTimestamp("param"));
}
// 执行结果
// 第一次调用:1562460396352
// 2秒之后调用:1562460396352
// 再过4秒之后调用:1562460402359
@Test
public void testCache(){
String key = "zhangsan";
String value = ehcacheService.getDataFromDB(key); // 从数据库中获取数据...
ehcacheService.getDataFromDB(key); // 从缓存中获取数据,所以不执行该方法体
ehcacheService.removeDataAtDB(key); // 从数据库中删除数据
ehcacheService.getDataFromDB(key); // 从数据库中获取数据...(缓存数据删除了,所以要重新获取,执行方法体)
}
// 第二次调用已经用到了缓存
// 从数据库中获取数据...
// 从数据库中删除数据
// 从数据库中获取数据...
@Test
public void testPut(){
String key = "mengdee";
ehcacheService.refreshData(key); // 模拟从数据库中加载数据
String data = ehcacheService.getDataFromDB(key);//这个调用不会执行
System.out.println("data:" + data); // data:mengdee::103385
ehcacheService.refreshData(key); // 模拟从数据库中加载数据
String data2 = ehcacheService.getDataFromDB(key);
System.out.println("data2:" + data2); // data2:mengdee::180538
}
@Test
public void testFindById(){
ehcacheService.findById("1"); // 模拟从数据库中查询数据
ehcacheService.findById("1");
}
@Test
public void testIsReserved(){
ehcacheService.isReserved("123");
ehcacheService.isReserved("123");//会缓存
ehcacheService.isReserved("1234567890123");
ehcacheService.isReserved("1234567890123");//不会用到缓存
}
@Test
public void testRemoveUser(){
// 线添加到缓存
ehcacheService.findById("1");
// 再删除
ehcacheService.removeUser("1");
// 如果不存在会执行方法体
ehcacheService.findById("1");
}
@Test
public void testRemoveAllUser(){
ehcacheService.findById("1");
ehcacheService.findById("2");
ehcacheService.removeAllUser();
ehcacheService.findById("1");
ehcacheService.findById("2");
// 模拟从数据库中查询数据
// 模拟从数据库中查询数据
// UserCache delete all
// 模拟从数据库中查询数据
// 模拟从数据库中查询数据
}
}
参考
作者:guideEmotion
链接:https://www.jianshu.com/p/154c82073b07
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
EhCache简单入门的更多相关文章
- ehcache缓存入门学习
ehcache缓存入门学习 1,概念 特性 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认的CacheProvider. 主要的特性有:1. 快速2 ...
- 用IntelliJ IDEA创建Gradle项目简单入门
Gradle和Maven一样,是Java用得最多的构建工具之一,在Maven之前,解决jar包引用的问题真是令人抓狂,有了Maven后日子就好过起来了,而现在又有了Gradle,Maven有的功能它都 ...
- [原创]MYSQL的简单入门
MYSQL简单入门: 查询库名称:show databases; information_schema mysql test 2:创建库 create database 库名 DEFAULT CHAR ...
- Okio 1.9简单入门
Okio 1.9简单入门 Okio库是由square公司开发的,补充了java.io和java.nio的不足,更加方便,快速的访问.存储和处理你的数据.而OkHttp的底层也使用该库作为支持. 该库极 ...
- emacs最简单入门,只要10分钟
macs最简单入门,只要10分钟 windwiny @2013 无聊的时候又看到鼓吹emacs的文章,以前也有几次想尝试,结果都是玩不到10分钟就退出删除了. 这次硬着头皮,打开几篇文章都看完 ...
- 【java开发系列】—— spring简单入门示例
1 JDK安装 2 Struts2简单入门示例 前言 作为入门级的记录帖,没有过多的技术含量,简单的搭建配置框架而已.这次讲到spring,这个应该是SSH中的重量级框架,它主要包含两个内容:控制反转 ...
- Docker 简单入门
Docker 简单入门 http://blog.csdn.net/samxx8/article/details/38946737
- Springmvc整合tiles框架简单入门示例(maven)
Springmvc整合tiles框架简单入门示例(maven) 本教程基于Springmvc,spring mvc和maven怎么弄就不具体说了,这边就只简单说tiles框架的整合. 先贴上源码(免积 ...
- git简单入门
git简单入门 标签(空格分隔): git git是作为程序员必备的技能.在这里就不去介绍版本控制和git产生的历史了. 首先看看常用的git命令: git init git add git comm ...
随机推荐
- 【Golang】基于beego/orm实现相同表结构不同表名的分表方法实现
一.背景 在业务场景开发的过程中, 随着数据量的增加,相同表结构不同表名的分表策略是常用的方案选择之一.如下以golang做为后端业务开发,尝试修改beego的orm库做一个相同表结构不同表名的分表实 ...
- maven插件慢的解决方案
-DarchetypeCatalog=local 地址:https://www.cnblogs.com/del88/p/6286887.html
- 从记账软件看工具类APP的存量运营之道
随着移动互联网的发展,APP的种类越来越多,一些工具类 APP 增长乏力,难以实现长期增长.只有提高用户留存时间,实现流量变现,才能在激烈的市场竞争中持续发展. 工具类APP的特点: 替代性很强: 用 ...
- LOJ 2372 -「CEOI2002」臭虫集成电路公司(轮廓线 dp)
题面传送门 u1s1 似乎这题全网无一题解?那就由我来写篇题解造福人类罢(伦敦雾 首先看这数据范围,一脸状压.考虑到每一层的状态与上面两层有关,因此每层转移到下一层的有用信息只有两层,需要用三进制保存 ...
- Atcoder Regular Contest 096 C - Everything on It(组合数学)
Atcoder 题面传送门 & 洛谷题面传送门 简单题,由于这场 arc 的 F 是 jxd 作业而我不会做,所以只好来把这场的 E 水掉了. 我们记 \(f(i)\) 为钦定 \(i\) 个 ...
- ping 的原理
ping 的原理ping 程序是用来探测主机到主机之间是否可通信,如果不能ping到某台主机,表明不能和这台主机建立连接.ping 使用的是ICMP协议,它发送icmp回送请求消息给目的主机.ICMP ...
- 21-Add Two Numbers-Leetcode
You are given two linked lists representing two non-negative numbers. The digits are stored in rever ...
- 省时省心DTM,广告转化无难题
内容来源:华为开发者大会2021 HMS Core 6 App Services技术论坛,主题演讲<华为分析服务,助您打造数智化运营闭环方案>. 演讲嘉宾:华为消费者云服务 分析产品总监 ...
- 对Javascript中的对象Object改变内存及其变量改变的图解
Object 存储变量时,变量属性的内存改变图解 左边: 对象的内存 中间:变量属性的内存 右边:属性值的内存 [图一]创建一个对象,存obj1 变量--里面存age 属性和属性值--12. ...
- binlog真的是银弹吗?有些时候也让人头疼
大家好,我是架构摆渡人.这是实践经验系列的第三篇文章,这个系列会给大家分享很多在实际工作中有用的经验,如果有收获,还请分享给更多的朋友. binlog 用于记录用户对数据库操作的SQL语句信息,同时主 ...