memcache:

是一种高性能、分布式对象缓存系统,最初设计于缓解动态网站数据库加载数据的延迟性,你可以把它想象成一个大的内存HashTable,就是一个key-value键值缓存。C语言所编写,依赖于最近版本的GCC和libevent。GCC是它的编译器,同时基于libevent做socket io。在安装memcache时保证你的系统同时具备有这两个环境。支持多个cpu同时工作,在memcache安装文件下有个叫threads.txt中特别说明,By default, memcached is compiled as a single-threaded application.默认是单线程编译安装,如果你需要多线程则需要修改./configure --enable-threads,为了支持多核系统,前提是你的系统必须具有多线程工作模式。开启多线程工作的线程数默认是4,如果线程数超过cpu数容易发生操作死锁的概率。结合自己业务模式选择才能做到物尽其用。

本身没有内置分布式功能,只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储【当客户端向Memcached集群发送数据之前,首先会通过内置的分布式算法计算出该条数据的目标节点,然后数据会直接发送到该节点上存储。但客户端查询数据时,同样要计算出查询数据所在的节点,然后直接向该节点发送查询请求以获取数据】。无法实现使用多台Memcache服务器来存储不同的数据,最大程度的使用相同的资源;无法同步数据,容易造成单点故障。

memagent代理实现集群)通过Magent缓存代理,防止单点现象,缓存代理也可以做备份,通过客户端连接到缓存代理服务器,缓存代理服务器连接缓存连接服务器,缓存代理服务器可以连接多台Memcached机器可以将每台Memcached机器进行数据同步。如果其中一台缓存服务器down机,系统依然可以继续工作,如果其中一台Memcached机器down掉,数据不会丢失并且可以保证数据的完整性。

Redis:

支持持久化【数据swap时会阻塞,设置I/O线程池的大小】;丰富的数据类型结构存储;单线程网络IO;数据存储即时申请内存【不会预分配内存池,不会自动剔除数据】;提供事务操作;消息队列【不支持持久化,消费方连接闪断或重连,之间过来的消息会全部丢失】;集群方式【没有中心节点,具有线性可伸缩的功能】

Redis cluster配置参考:

在每台服务器上执行如下操作:

#cd /opt/redis-3.0.2【必须先安装gcc、make】

#make

#make install【将生成的可执行程序复制到/usr/local/bin目录中】

#vim redis.conf

port 6379

bind ip【本机ip,其他节点可以访问】

daemonize yes【可保留】

cluster-enabled yes

cluster-config-file nodes-6379.conf

cluster-node-timeout 15000

appendonly yes【可保留】

#cp redis.conf redis1.conf

#vim redis1.conf【修改port: 6380, cluster-config-file nodes-6380.conf】

#redis-server redis.conf &

#redis-server redis1.conf &

#redis-cli -h 10.30.17.15 -p 6379【测试】

安装ruby 及 rubygems

#apt-get install ruby rubygems

安装ruby 的redis 接口支持包

#gem install redis

建立集群

#/opt/redis-3.0.2/src/redis-trib.rb create --replicas 1 10.30.17.85:6379 10.30.17.85:6380 10.30.17.94:6379 10.30.17.94:6380 10.30.17.15:6379 10.30.17.15:6380

测试

#redis-cli -p 6379【exit退出】

>cluster info

>set hello tang

>keys *

>set user_id 1234

>get user_id

在另一台机器上执行#redis-cli -p 6380

>keys *

>set user_id 1234【error,需#redis-cli -c -p 6380】

在另一台机器上执行#redis-cli -p 6379

>keys *【empty】

>set user_id 1234【error,需#redis-cli -c -p 6379】

Ehcache【java进程中的缓存系统】:

ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis

补刀:ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。

一旦将应用部署在集群环境中,每一个节点维护各自的缓存数据,当某个节点对缓存数据进行更新,这些更新的数据无法在其它节点中共享,这不仅会降低节点运行的效率,而且会导致数据不同步的情况发生,所以就需要用到 EhCache 的集群解决方案。EhCache 从 1.7 版本开始,支持五种集群方案,分别是:

  1. Terracotta
  2. RMI
  3. JMS
  4. JGroups
  5. EhCache Server【它以两种形式出现:适合大多数Web容器的WAR以及独立的服务器。Cache Server有两种类型的API:面向资源的RESTful以及SOAP。这两种API都支持任何编程语言。最大的Ehcache单实例在内存中可以缓存20GB。最大的磁盘可以缓存100GB。】

Ehcache可以对页面、对象、数据进行缓存

ehcache-core-2.5.2.jar 主要针对对象、数据缓存

ehcache-web-2.0.4.jar 主要针对页面缓存

页面缓存主要用Filter过滤器对请求的url进行过滤,如果该url在缓存中出现。那么页面数据就从缓存对象中获取,并以gzip压缩后返回。其速度是没有压缩缓存时速度的3-5倍,效率相当之高!其中页面缓存的过滤器有CachingFilter,一般要扩展filter或是自定义Filter都继承该CachingFilter。CachingFilter功能可以对HTTP响应的内容进行缓存。这种方式缓存数据的粒度比较粗,例如缓存整张页面。它的优点是使用简单、效率高,缺点是不够灵活,可重用程度不高。EHCache使用SimplePageCachingFilter类实现Filter缓存。该类继承自CachingFilter,有默认产生cache key的calculateKey()方法,该方法使用HTTP请求的URI和查询条件来组成key。也可以自己实现一个Filter,同样继承CachingFilter类,然后覆写calculateKey()方法,生成自定义的key。CachingFilter输出的数据会根据浏览器发送的Accept-Encoding头信息进行Gzip压缩。

在使用Gzip压缩时,需注意两个问题:

1. Filter在进行Gzip压缩时,采用系统默认编码,对于使用GBK编码的中文网页来说,需要将操作系统的语言设置为:zh_CN.GBK,否则会出现乱码的问题。

2. 默认情况下CachingFilter会根据浏览器发送的请求头部所包含的Accept-Encoding参数值来判断是否进行Gzip压缩。虽然IE6/7浏览器是支持Gzip压缩的,但是在发送请求的时候却不带该参数。为了对IE6/7也能进行Gzip压缩,可以通过继承CachingFilter,实现自己的Filter,然后在具体的实现中覆写方法acceptsGzipEncoding。

具体实现参考:

protected boolean acceptsGzipEncoding(HttpServletRequest request) {

boolean ie6 = headerContains(request, "User-Agent", "MSIE 6.0");

boolean ie7 = headerContains(request, "User-Agent", "MSIE 7.0");

return acceptsEncoding(request, "gzip") || ie6 || ie7;

}

对象缓存就是将查询的数据,添加到缓存中,下次再次查询的时候直接从缓存中获取,而不去数据库中查询。对象缓存一般是针对方法、类而来的,结合Spring的Aop对象、方法缓存就很简单。这里需要用到切面编程,用到了Spring的MethodInterceptor或是用@Aspect。这里的方法拦截器主要是对你要拦截的类的方法进行拦截,然后判断该方法的类路径+方法名称+参数值组合的cache key在缓存cache中是否存在。如果存在就从缓存中取出该对象,转换成我们要的返回类型。没有的话就把该方法返回的对象添加到缓存中即可。值得主意的是当前方法的参数和返回值的对象类型需要序列化。我们需要在src目录下添加applicationContext.xml完成对MethodCacheInterceptor拦截器的配置,该配置主意是注入我们的cache对象,哪个cache来管理对象缓存,然后哪些类、方法参与该拦截器的扫描。

Spring自身并没有实现缓存解决方案,但是对缓存管理功能提供了声明式的支持,能够与多种流行的缓存实现进行集成。

Spring Cache是作用在方法上的(不能理解为只注解在方法上),其核心思想是:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值存放在缓存中,等到下次利用同样的参数调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。所以在使用Spring Cache的时候我们要保证我们的缓存的方法对于相同的方法参数要有相同的返回结果。

SpringCache的支持有两种方式

基于注解驱动的缓存

基于XML配置声明的缓存

启用Spring对注解驱动缓存的支持,也是有两种方式的

Java配置(这个方法可以比较清晰的了解缓存管理器是如何声明的)

XML配置(这个方法比较方便简单,这里的XML配置不能跟上面的“基于XML配置声明的缓存”搞混,两者不是同一层概念)

这两种方式,Java配置方式是通过使用@EnableCaching启用注解驱动的缓存,XML配置方式是通过使用<cache:annotation-driven/>启用注解驱动的缓存。本质上来讲,这两种工作方式是相同的,它们都会创建一个切面(AOP)并出发Spring缓存注解的切点(pointcut)。

在这两个程序清单中,不仅仅启用了注解驱动的缓存,还声明了一个缓存管理器(cache manager)的bean。缓存管理器是Spring抽象的核心,它能够与多个流行的缓存实现进行集成。

数据缓存是在hibernate或mybatis中加入ehcache

配置ehcache.xml:

maxElementsInMemory:缓存中允许创建的最大对象数

eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。

timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是 0 就意味着元素可以停顿无穷长的时间。

timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。

overflowToDisk:内存不足时,是否启用磁盘缓存。

memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。

配置applicationContext.xml:

...

<!--  缓存  属性--> 

<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">   

         <property name="configLocation"  value="classpath:com/config/ehcache.xml"/>  

</bean>      

<!-- 默认是cacheManager --> 

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">   

         <property name="cacheManager"  ref="cacheManagerFactory"/>   

</bean>

<!-- 支持缓存注解 --> 

<cache:annotation-driven cache-manager="cacheManager" />

实现(常在@Service服务文件,缓存数据):

@Cacheable(value = "serviceCache", key="#id")【当调用其方法时,会从一个名叫 serviceCache 的缓存中查询,如果没有,则执行实际的方法(即查询数据库),并将执行的结果存入缓存中,否则返回缓存中的对象。serviceCache为ehcache.xml中定义的缓存名称】

@CacheEvict(value="serviceCache",allEntries=true)【标记要清空缓存的方法,allEntries 表示调用之后,清空缓存,默认false, 还有个beforeInvocation 属性,表示先清空缓存,再进行查询】

Google Guava

Google Guava工具包是一个非常方便易用的本地化缓存实现,基于LRU算法实现,支持多种缓存过期策略。Guava在每次访问缓存的时候判断cache数据是否过期,如果过期,这时才将其删除,并没有另起一个线程专门来删除过期数据。内部维护了2个队列accessQueue和writeQueue来记录缓存中数据访问和写入的顺序。访问缓存时,先用key计算出hash,从而找出所在的segment,然后再在segment中寻找具体数据,类似于使用ConcurrentHashMap数据结构来存放缓存数据。

Caffeine

Caffeine是基于Java8,对Guava缓存的重写版本,在Spring Boot 2.0中将取代Guava,基于LRU算法实现,支持多种缓存过期策略。Caffeine使用Disruptor框架的RingBuffer数据结构记录,RingBuffer是一个环形队列,并且是无锁的,利用的是缓存行的特性。Caffeine读比写的性能比Guava和EhCache要高很多。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐