山河二月天 發表於 2023-6-17 00:00:00

大厂都在用EhCache,它到底比Redis强在哪里?

<p>

                <img title="大厂都在用EhCache,它到底比Redis强在哪里?" alt="大厂都在用EhCache,它到底比Redis强在哪里?" border="0" src="https://zhuji.jb51.net/uploads/img/202305/c2061d7bb9808965a43a520daefd3da6.jpg"></p>
        <h3>
                故事背景
        </h3>
        <p>
                随着硬件价格的走低,大家对硬件的依赖越来越高。甚至听说,代码不重要,不行就加机器呗。比如缓存的使用,通常有基于虚拟机内存、基于磁盘存储、基于中间件(Redis内存)等方式,我们都知道,最适合的才是最好的,但实践中,往往是动不动就直接上Redis。
        </p>
        <p>
                那么,Redis一定是最好的选择吗?单不说对内存的要求,从效率和性能上来说,也未必是最优的。所以,不同的场景使用不同的缓存策略才是高手应该追求的。
        </p>
        <p>
                这篇文章就带大家认识除Redis之外的另一种缓存框架:EhCache。之所以要写写,也是因为项目中运用了此框架,同时又遇到点问题,于是决定深入研究一下。研究之后,发现还真有点意思。
        </p>
        <h3>
                EhCache简介
        </h3>
        <p>
                EhCache是一个纯Java的进程内缓存框架,具有快速、精干的特点。注意的这里的关键字进程,基于进程的缓存直觉告诉我们效率肯定要高一些,因为它直接在进程之内进行操作,但不同应用之间缓存的共享可能就会有问题。
        </p>
        <p>
                EhCache是Hibernate中默认的CacheProvider,Spring Boot也对其进行了支持,Spring中提供的缓存抽象也支持对EhCache缓存框架的绑定,而且支持基于注解的方式来使用。因此,EhCache是一款被广泛使用的基于Java的高速缓存框架,使用起来也非常方便。
        </p>
        <p>
                EhCache提供了多种缓存策略,主要分为内存和磁盘两级,是一款面向通用缓存、Java EE和轻量级容器的缓存框架。
        </p>
        <p>
                <strong>EhCache的特点</strong>
        </p>
        <p>
                简单说一下该框架的特点:
        </p>
        <ul>
<li>
                        简单、快速,拥有多种缓存策略;
                </li>
                <li>
                        缓存数据有两级:内存和磁盘,无需担心容量问题;
                </li>
                <li>
                        缓存数据会在虚拟机重启的过程中写入磁盘;
                </li>
                <li>
                        可以通过RMI、可插入API等方式进行分布式缓存;
                </li>
                <li>
                        具有缓存和缓存管理器的侦听接口;
                </li>
                <li>
                        支持多缓存管理器实例,以及一个实例的多个缓存区域,并提供Hibernate的缓存实现。
                </li>
        </ul>
<p>
                EhCache可以单独使用,但通常会与Mybatis、Shiro等三方类库结合使用。本人项目中使用EhCache就是结合Shiro来使用的。
        </p>
        <p>
                除了优点,EhCache也还有一些缺点。比如,非常占用磁盘空间,这是因为DiskCache的算法简单,只是对元素直接追加存储。这样虽然可以提高效率,但在使用频繁的系统中,磁盘很快会满。
        </p>
        <p>
                另外就是不能保证数据安全,当然突然kill掉Java进程时,可能会产生冲突。EhCache解决冲突的方法是重建Cache,这对Cache数据需要保持时可能会产生影响。Cache只是简单的加速,不能保证数据的安全。
        </p>
        <p>
                <strong>EhCache与Redis</strong>
        </p>
        <p>
                EhCache直接在JVM中进行缓存,速度快,效率高。与Redis相比,操作简单、易用、高效,虽然EhCache也提供有缓存共享的方案,但对分布式集群的支持不太好,缓存共享实现麻烦。
        </p>
        <p>
                Redis是通过Socket访问到缓存服务,效率比EhCache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。
        </p>
        <p>
                所以,如果是单体应用,或对缓存访问要求很高,可考虑采用EhCache;如果是大型系统,存在缓存共享、分布式部署、缓存内容很大时,则建议采用Redis。
        </p>
        <p>
                <strong>EhCache架构图</strong>
        </p>
        <p>
                看一下EhCache的架构图,大概了解一下它由几部分组成。
        </p>
        <p>
                <img title="大厂都在用EhCache,它到底比Redis强在哪里?" alt="大厂都在用EhCache,它到底比Redis强在哪里?" border="0" src="https://zhuji.jb51.net/uploads/img/202305/e41da8f12333a4c125764631c89dc964.jpg"></p>
        <p>
                Ehcache-architecture
        </p>
        <p>
                Cache Replication部分提供了缓存复制的机制,用于分布式环境。EhCache最初是独立的本地缓存框架组件,在后期的发展中,结合Terracotta服务阵列模型,可以支持分布式缓存集群,主要有RMI、JGroups、JMS和Cache Server等传播方式进行节点间通信。
        </p>
        <p>
                In-process APIs则提供了基于JSR、JMX等标准的支持,能够较好的兼容和移植,同时对各类对象有较完善的监控管理机制。
        </p>
        <p>
                Network APIs则对外提供了基于RESTful API、JMS API、Cache Server等方式的支持。
        </p>
        <p>
                在使用过程中,需要关注的核心部分便是中间的Core部分了。它包含了核心的API和概念:
        </p>
        <ul>
<li>
                        CacheManager:缓存管理器,可以通过单例或者多例的方式创建,也是Ehcache的入口类。
                </li>
                <li>
                        Cache:每个CacheManager可以管理多个Cache,每个Cache可以采用hash的方式管理多个Element。所有cache都实现了Ehcache接口;
                </li>
                <li>
                        Element:单条缓存数据的组成单位,用于存放真正缓存内容的。
                </li>
        </ul>
<p>
                三者的管理可以用下图表示:
        </p>
        <p>
                <img title="大厂都在用EhCache,它到底比Redis强在哪里?" alt="大厂都在用EhCache,它到底比Redis强在哪里?" border="0" src="https://zhuji.jb51.net/uploads/img/202305/a19e72ca0b38f66b7dc0e0042bccd584.jpg"></p>
        <p>
                CacheManager
        </p>
        <p>
                <strong>缓存过期策略</strong>
        </p>
        <p>
                在架构图中还可以看到Memory Store LRU、Memory Store LFU、Memory Store FIFO等内存存储算法。也就是当缓存占用空间接近临界值时,会采用上面的淘汰策略来清理掉一部分数据。
        </p>
        <p>
                EhCache提供了三种淘汰算法:
        </p>
        <ul>
<li>
                        FIFO:First In First Out,先进先出。判断被存储的时间,离目前最远的数据优先被淘汰。
                </li>
                <li>
                        LRU:Least Recently Used,最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
                </li>
                <li>
                        LFU:Least Frequently Used,最不经常使用。在一段时间内,数据被使用次数最少的,优先被淘汰。
                </li>
        </ul>
<p>
                Ehcache采用的是懒淘汰机制,每次往缓存放入数据时,都会存一个时间,在读取时要和设置的时间做TTL比较来判断是否过期。
        </p>
        <h3>
                EhCache实战解析
        </h3>
        <p>
                了解了上面的基础知识之后,来实验一下EhCache如何使用。其中EhCache2.x和EhCache3.x的使用差距较大。
        </p>
        <p>
                这里采用比较新的3.9.6版本,不同的版本在API的使用上会有所差异。
        </p>
        <p>
                <strong>基于API使用EhCache</strong>
        </p>
        <p>
                EhCache提供了基于API和xml两种形式创建CacheManger和Cache。先来看基于API的形式:
        </p>
        <p>
                在pom文件中引入EhCache依赖:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span><dependency></dependency></span></span>
                </li>
                <li>
                        <span><groupid>org.ehcache</groupid></span>
                </li>
                <li class="alt">
                        <span>ehcache</artifactid></span>
                </li>
                <li>
                        <span><version>3.9.6</version></span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
        </ol>
<p>
                创建并使用的代码如下:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span class="keyword">public</span><span> class EhCacheTest { </span></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>@Test </span>
                </li>
                <li>
                        <span><span class="keyword">public</span><span> void test() { </span></span>
                </li>
                <li class="alt">
                        <span>// 1、先创建一个CacheManagerBuilder; </span>
                </li>
                <li>
                        <span>// 2、使用CacheManagerBuilder创建一个预配置(pre-configured)缓存:第一个参数为别名,第二个参数用来配置Cache; </span>
                </li>
                <li class="alt">
                        <span>// 3、build方法构建并初始化;build中<span class="keyword">true</span><span>参数表示进行初始化。 </span></span>
                </li>
                <li>
                        <span>CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder() </span>
                </li>
                <li class="alt">
                        <span>.withCache(<span class="string">"preConfigured"</span><span>, </span></span>
                </li>
                <li>
                        <span>CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, </span>
                </li>
                <li class="alt">
                        <span>ResourcePoolsBuilder.heap(100)).build()) </span>
                </li>
                <li>
                        <span>.build(<span class="keyword">true</span><span>); </span></span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span>// 取回在设定的pre-configured,对于<span class="keyword">key</span><span>和value值类型,要求是类型安全的,否则将抛出ClassCastException异常。 </span></span>
                </li>
                <li class="alt">
                        <span>Cache<long string> preConfigured = cacheManager.getCache(<span class="string">"preConfigured"</span><span>, Long.class, String.class); </span></long></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>System.<span class="keyword">out</span><span>.println(</span><span class="string">"从缓存中获取key为preConfigured:"</span><span> + preConfigured); </span></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>// 根据需求,通过CacheManager创建出新的Cache。实例化和完整实例化的Cache将通过CacheManager.getCache API返回。 </span>
                </li>
                <li>
                        <span>Cache<long string> myCache = cacheManager.createCache(<span class="string">"myCache"</span><span>, </span></long></span>
                </li>
                <li class="alt">
                        <span>CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, </span>
                </li>
                <li>
                        <span>ResourcePoolsBuilder.heap(100)).build()); </span>
                </li>
                <li class="alt">
                        <span>// 使用put方法存储数据 </span>
                </li>
                <li>
                        <span>myCache.put(1L, <span class="string">"da one!"</span><span>); </span></span>
                </li>
                <li class="alt">
                        <span>// 使用get方法获取数据 </span>
                </li>
                <li>
                        <span>String value = myCache.get(1L); </span>
                </li>
                <li class="alt">
                        <span>System.<span class="keyword">out</span><span>.println(</span><span class="string">"从缓存中获取key为1L:"</span><span> + value); </span></span>
                </li>
                <li>
                        <span>// <span class="keyword">close</span><span>方法将释放CacheManager所管理的缓存资源 </span></span>
                </li>
                <li class="alt">
                        <span>cacheManager.<span class="keyword">close</span><span>(); </span></span>
                </li>
                <li>
                        <span>} </span>
                </li>
                <li class="alt">
                        <span>} </span>
                </li>
        </ol>
<p>
                上述代码基于API的形式演示了如何创建CacheManager及Cache,并对Cache进行设置和获取。
        </p>
        <p>
                <strong>基于XML使用EhCache</strong>
        </p>
        <p>
                依赖Jar包不变,在src/main/resources/目录下创建配置文件 ehcache.xml。
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span><config span></config></span>
                </span>
</li>
                <li>
                        <span>xmlns:xsi=<span class="string">'http://www.w3.org/2001/XMLSchema-instance'</span><span> </span></span>
                </li>
                <li class="alt">
                        <span>xmlns=<span class="string">'http://www.ehcache.org/v3'</span><span> </span></span>
                </li>
                <li>
                        <span>xsi:schemaLocation=<span class="string">"http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd"</span><span>&gt; </span></span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span><cache class="string" alias="&lt;span">"foo"</cache></span><span>&gt; </span>
                </li>
                <li class="alt">
                        <span>&lt;<span class="keyword">key</span><span>-type&gt;java.lang.String<span class="keyword">key</span><span>-type&gt; </span></span>
                </span>
</li>
                <li>
                        <span><value-type>java.lang.String</value-type></span>
                </li>
                <li class="alt">
                        <span><resources></resources></span>
                </li>
                <li>
                        <span><heap class="string" unit="&lt;span">"entries"</heap></span><span>&gt;20 </span>
                </li>
                <li class="alt">
                        <span><offheap class="string" unit="&lt;span">"MB"</offheap></span><span>&gt;10 </span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span><cache-template class="keyword">name</cache-template></span><span>=</span><span class="string">"myDefaults"</span><span>&gt; </span>
                </li>
                <li>
                        <span>&lt;<span class="keyword">key</span><span>-type&gt;java.lang.Long<span class="keyword">key</span><span>-type&gt; </span></span>
                </span>
</li>
                <li class="alt">
                        <span><value-type>java.lang.String</value-type></span>
                </li>
                <li>
                        <span><heap class="string" unit="&lt;span">"entries"</heap></span><span>&gt;200 </span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span><cache class="string" alias="&lt;span">"bar"</cache></span><span> uses-template=</span><span class="string">"myDefaults"</span><span>&gt; </span>
                </li>
                <li>
                        <span>&lt;<span class="keyword">key</span><span>-type&gt;java.lang.Number<span class="keyword">key</span><span>-type&gt; </span></span>
                </span>
</li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span><cache class="string" alias="&lt;span">"simpleCache"</cache></span><span> uses-template=</span><span class="string">"myDefaults"</span><span> /&gt; </span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
        </ol>
<p>
                <cache alias="simpleCache" uses-template="myDefaults"></cache></p>
        <p>
                3.x版本与2.x版本有所区别,在xml配置文件上非常明显。2.x中以ehcache元素为根节点,而3.x则以config为根节点。
        </p>
        <p>
                在上述xml中包含三部分:
        </p>
        <ul>
<li>
                        普通缓存cache-foo:别名为foo的缓存,缓存的Key-Value值类型均为String。如果没有指定,默认就是Object类型。
                </li>
                <li>
                        缓存模板cache-template:实现一个配置抽象,以便在未来可以进行扩展;
                </li>
                <li>
                        基于缓存模板的cache-bar:使用了cache-template模板myDefaults,并且覆盖了key-type类型,myDefaults的key-type是Long类型,覆盖后成了Number类型;
                </li>
        </ul>
<p>
                cache中其他属性及元素:
        </p>
        <ul>
<li>
                        name为名称;
                </li>
                <li>
                        alias为别名;
                </li>
                <li>
                        key-type为key的类型;
                </li>
                <li>
                        value-type为value的类型;
                </li>
                <li>
                        heap指定堆中可创建的实体格式,其中unit="entries",表示后面的20是实体;
                </li>
                <li>
                        offheap表示在开始淘汰过期缓存项之前,可以分配多达10M的堆内存;
                </li>
                <li>
                        uses-template表示使用模板的名称;
                </li>
        </ul>
<p>
                当然,也可以通过persistence元素来配置缓存的目录等。其他属性的使用,大家可以慢慢探索。
        </p>
        <h3>
                基于Spring Boot使用EhCache
        </h3>
        <p>
                前面已经提到,Spring对缓存进行了支持,Spring Boot也对缓存进行了自动配置的支持。下面就基于Spring Boot来完成EhCache的集成以及使用案例演示。
        </p>
        <p>
                在Spring Boot中引入对应的starter:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span><span class="comment">-- ehcache依赖--&gt;</span><span> </span></span>
                </span>
</li>
                <li>
                        <span><dependency></dependency></span>
                </li>
                <li class="alt">
                        <span><groupid>org.springframework.boot</groupid></span>
                </li>
                <li>
                        <span>spring-boot-starter-cache</artifactid></span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span><dependency></dependency></span>
                </li>
                <li class="alt">
                        <span><groupid>org.ehcache</groupid></span>
                </li>
                <li>
                        <span>ehcache</artifactid></span>
                </li>
                <li class="alt">
                        <span><version>3.9.6</version></span>
                </li>
                <li>
                        <span></span>
                </li>
        </ol>
<p>
                在application.properties中配置添加如下配置:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span>spring.cache.ehcache.config=ehcache.xml </span></span>
                </li>
        </ol>
<p>
                在Spring Boot启动类上添加@EnableCaching注解:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span>@EnableCaching </span></span>
                </li>
                <li>
                        <span>@SpringBootApplication </span>
                </li>
                <li class="alt">
                        <span>@MapperScan(<span class="string">"com.secbro.mapper"</span><span>) </span></span>
                </li>
                <li>
                        <span><span class="keyword">public</span><span> class SpringBootMainApplication { </span></span>
                </li>
                <li class="alt">
                        <span><span class="keyword">public</span><span> </span><span class="keyword">static</span><span> void main(String[] args) { </span></span>
                </li>
                <li>
                        <span>SpringApplication.run(SpringBootMainApplication.class, args); </span>
                </li>
                <li class="alt">
                        <span>} </span>
                </li>
                <li>
                        <span>} </span>
                </li>
        </ol>
<p>
                创建一个用户缓存的实体类Person:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span>@Data </span></span>
                </li>
                <li>
                        <span><span class="keyword">public</span><span> class Person { </span></span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span><span class="keyword">public</span><span> Person(</span><span class="keyword">int</span><span> id,String </span><span class="keyword">name</span><span>){ </span></span>
                </li>
                <li class="alt">
                        <span>this.id = id; </span>
                </li>
                <li>
                        <span>this.<span class="keyword">name</span><span> = </span><span class="keyword">name</span><span>; </span></span>
                </li>
                <li class="alt">
                        <span>} </span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>private <span class="keyword">int</span><span> id; </span></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>private String <span class="keyword">name</span><span>; </span></span>
                </li>
                <li>
                        <span>} </span>
                </li>
        </ol>
<p>
                对应的Service方法实现:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span class="keyword">public</span><span> interface PersonService { </span></span>
                </li>
                <li>
                        <span>Person getById(<span class="keyword">int</span><span> id); </span></span>
                </li>
                <li class="alt">
                        <span>} </span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>@Slf4j </span>
                </li>
                <li>
                        <span>@Service(<span class="string">"personService"</span><span>) </span></span>
                </li>
                <li class="alt">
                        <span><span class="keyword">public</span><span> class PersonServiceImpl implements PersonService { </span></span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>@Cacheable(value = <span class="string">"personCache"</span><span>, </span><span class="keyword">key</span><span> = </span><span class="string">"#id"</span><span>) </span></span>
                </li>
                <li>
                        <span>@Override </span>
                </li>
                <li class="alt">
                        <span><span class="keyword">public</span><span> Person getById(</span><span class="keyword">int</span><span> id) { </span></span>
                </li>
                <li>
                        <span>log.info(<span class="string">"查询id={}的用户"</span><span>, id); </span></span>
                </li>
                <li class="alt">
                        <span>if (id == 1) { </span>
                </li>
                <li>
                        <span><span class="keyword">return</span><span> new Person(1, </span><span class="string">"Tom"</span><span>); </span></span>
                </li>
                <li class="alt">
                        <span>} <span class="keyword">else</span><span> if (id == 2) { </span></span>
                </li>
                <li>
                        <span><span class="keyword">return</span><span> new Person(2, </span><span class="string">"Jim"</span><span>); </span></span>
                </li>
                <li class="alt">
                        <span>} </span>
                </li>
                <li>
                        <span><span class="keyword">return</span><span> new Person(3, </span><span class="string">"Other"</span><span>); </span></span>
                </li>
                <li class="alt">
                        <span>} </span>
                </li>
                <li>
                        <span>} </span>
                </li>
        </ol>
<p>
                通过Spring提供@Cacheable注解指定了缓存的名称为personCache,key为id。在方法内打印日志,如果调用到方法内,则会打印。
        </p>
        <p>
                编写单元测试类:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span>@Slf4j </span></span>
                </li>
                <li>
                        <span>@SpringBootTest </span>
                </li>
                <li class="alt">
                        <span>@TestMethodOrder(MethodOrderer.OrderAnnotation.class) </span>
                </li>
                <li>
                        <span>class PersonServiceTest { </span>
                </li>
                <li class="alt">
                        <span></span>
                </li>
                <li>
                        <span>@Resource </span>
                </li>
                <li class="alt">
                        <span>private PersonService personService; </span>
                </li>
                <li>
                        <span></span>
                </li>
                <li class="alt">
                        <span>@org.junit.jupiter.api.<span class="keyword">Order</span><span>(1) </span></span>
                </li>
                <li>
                        <span>@Test </span>
                </li>
                <li class="alt">
                        <span>void testCache() throws InterruptedException { </span>
                </li>
                <li>
                        <span>log.info(<span class="string">"第1次查询id=1的数据"</span><span>); </span></span>
                </li>
                <li class="alt">
                        <span>personService.getById(1); </span>
                </li>
                <li>
                        <span>log.info(<span class="string">"第2次查询id=1的数据"</span><span>); </span></span>
                </li>
                <li class="alt">
                        <span>personService.getById(1); </span>
                </li>
                <li>
                        <span>Thread.sleep(3000); </span>
                </li>
                <li class="alt">
                        <span>} </span>
                </li>
                <li>
                        <span>} </span>
                </li>
        </ol>
<p>
                两次调用对应的方法,打印日志如下:
        </p>
        <ol class="dp-sql">
<li class="alt">
                        <span><span>c.s.s.PersonServiceTest : 第1次查询id=1的数据 </span></span>
                </li>
                <li>
                        <span>c.s.s.i.PersonServiceImpl : 查询id=1的用户 </span>
                </li>
                <li class="alt">
                        <span>c.s.s.PersonServiceTest : 第2次查询id=1的数据 </span>
                </li>
        </ol>
<p>
                可以看到,第一进入方法内进行查询,第二次便走了缓存。
        </p>
        <p>
                关于Spring提供的cache注解的使用还有很多使用方法和场景,这里就不再展开了。
        </p>
        <h3>
                小结
        </h3>
        <p>
                因为工作恰好用到该技术,就钻研并写成文章带大家领略了EhCache的基本知识、技术架构、使用场景、API使用以及基于Spring Boot的集成。整体而言,算是入门级别的,大家可以在此基础上进一步学习扩展。至于EhCache对分布式的支持部分,本文并未涉及,主要原因是使用起来并没那么好用,如果感兴趣的话可自行研究。
        </p>
        <p>
                原文链接:https://mp.weixin.qq.com/s/dSs94d2iiAkrOlpkQI-Jag
        </p>
頁: [1]
查看完整版本: 大厂都在用EhCache,它到底比Redis强在哪里?