缓存组件选型(下)

Posted 飘花醉人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了缓存组件选型(下)相关的知识,希望对你有一定的参考价值。

 周一至周五不定时推送技术干货!精品技术文章准时送上!

前言回顾

首先给亲爱的读者说明一下原因,缓存组件选型我也没有想到会涉及这么多的内容,由于内容是原创,所以花了很多的时间书写,总结,反复的琢磨,希望尽可能把我自己想要表达的意思给大家讲解清楚。上篇文章说了缓存组件CDN缓存,代理层缓存的讲解,那么本篇文章继续上篇文章,继续分享,缓存的下篇,本地缓存组件,其实缓存组件也是比较多的,那么就针对使用比较多的本地缓存组件来解释和说明

本地缓存篇章
缓存组件选型(下)



缓存组件选型(下)
1.Ehcache

        Ehcache缓存是java进程内部的一个缓存框架,具有灵活性强,轻小等特点,在hibernate中是默认提供的缓存框架。由于ehcahe在发展了几年之后,现在可以说是相当的稳定,在很多流行的应用都提供了对ehcahe的支持,现在可能大家需要抛出一个问题了,那么就是ehcache由于是本地缓存,那么容量一定是一个问题,而且在不同的应用之间,缓存的一致性也是一个问题?所以在ehcache在要求一致性场景相对较为严格的时候不建议使用ehcache,至于大家关心容量的问题,其实我也关心,官方自然也考虑到了,ehcache支持两种默认的存储方式,一种是全部基于内存的模式,另外一种可以存在磁盘,所以在有些场景之下,容量方面提供了一些解决方案,ehcahe同时提供了LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多,灵活的使用方式。有一个特点比较好的是Ehcache提供了监听器模式,比如在缓存管理器监听器,缓存监听器,方便在缓存相关的操作行为的时候实现广播和特定的功能需要以及满足更多的场景。在分布式方面,ehcache也有自己的解决方案,可以通过 RMI、可插入 API 等方式进行分布式缓存,但是自始至终ehcahe的定位为本地缓存,所以在分布式方面存在天生的脑残,稳定性,一致性一直存在问题,尽管目前支持5种集群的方式(Terracotta , RMI, JMS, JGroups, EhcacheServer),但是建议读者还是不要使用,如果硬要使用一些分布式缓存可以考虑一些缓存中间件,分布式缓存等,下面就针对目前ehcache比较流行的集群模式进行分析:

        RMI模式主要有一个优点是不需要引入其他的jar包,java天生都内部支持RMI的方式,只是有一点需要注意的是RMI要求的key和value都必须经过序列化协议。RMI配置集群的方式,重要的一点是各个node之间都是对等关系的。

        RMI配置集群有两种方式,一种是配置集群的时候需要手动指定其余节点的信息,这个说实话比较麻烦,上下线节点信息都需要更改,人工操作容易出错。就是node1的配置里面需要配置node2的节点信息,除了不配置自己的节点的当前信息,都需要配置其余的节点的信息,不过官方还提供了另外一种机制,就是自动发现机制,利用多播的方式实现集群中的节点的发现,这种方式呢就很简单和灵活,能够避免人工手动配置出错。

        JGroup的模式,JGoup的模式呢配置文件极其的复杂,相对RMI的方式Jgroup提供了基于TCP的单播模式和UDP的广播模式,同时依赖第三发的包。在笔者的经验而讲在设置组播的时候由于某些电脑启用了IPV6,此时可能会出错,需要自己启用IPV4。

        与前面介绍的两种集群方案不同的是, EhCache Server 是一个独立的缓存服务器,其内部使用 EhCache 做为缓存系统,可利用前面提到的两种方式进行内部集群。对外提供编程语言无关的基于 HTTP 的 RESTful 或者是 SOAP 的数据缓存操作接口。

架构图如下所示:

缓存组件选型(下)

        最后需要有一点强调说明的是ehcache是支持元素级别的缓存和淘汰算法的,在应用层使用ehcache的时候将会得新应手。Spring的框架和注解包都支持的。生态圈还是较为广泛的。我们在生产中也较多的利用ehcache做本地缓存,鉴于ehcache有堆内存,堆外存,分布式存储,磁盘存储等功能,所以我们需要合理的选择一个适宜的场景进行ehcache的应用。



缓存组件选型(下)
2.guava

    Guava是一种基于开源的Java库,其中包含谷歌正在由他们很多项目使用的很多核心库。这个库是为了方便编码,并减少编码错误。这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和验证的实用方法。   Guava Cache就是其中的一个组件,它是一个本地缓存,有以下优点:

    1)很好的封装了get、put操作,能够集成数据源。一般我们在业务中操作缓存都会操作缓存和数据源两部分。例如:put数据时,先插入DB再删除原来的缓存,get数据时,先查缓存,命中则返回,没有命中时需要查询DB,再把查询结果放入缓存中。Guava封装了这么多步骤,只需要调用一次get/put方法即可。

    2)它是线程安全的缓存,与ConcurrentMap相似,但前者增加了更多的元素失效策略,后者只能显示的移除元素。

     3)GuavaCache提供了三种基本的缓存回收方式:基于容量回收、定时回收和基于引用回收。定时回收有两种:按照写入时间,最早写入的最先回收;按照访问时间,最早访问的最早回收

    4)它可以监控加载/命中情况,这个功能对大家来说还是非常友好的,因为有时候我们需要知道目前的缓存命中情况,有时候方便或者有利于我们自己做热点缓存,缓存监控等比较高阶的一些缓存使用场景的功能。

    5)监听器,这个功能和ehcache的监听器比较类似,比如做缓存一致性,监控某个key被移除了之后,监控线程可以手动加载某些数据库生成的key以及value的值。

    但是guava也有不好的地方,也可以说是guava优势的地方,并不支持分布式,当然这个也是guava的定位吧,怎么呢,如果需要自己实现分布式也是可以的,但是这个没必要大费周章,因为这样会带来很多的数据一致性,可用性的问题,第二点guava在使用和实现单个key过期时间的时候实现并不是特别的友好,自己需要做很多的额外工作,也希望guava在这个方面提升一下api的易用性,不知道新版本中有没有优化这个api,总的来说呢,guava出自名家,还是有很多的优势的,平常我们自己在项目中使用的也是比较多的。



缓存组件选型(下)
3.Caffeine

        Caffeine是一种高性能的缓存库,几乎是基于Java 8的最佳缓存库。Caffeine提供了一个内存缓存受到谷歌guava启发的API。这些改进利用了我们设计Guava缓存和ConcurrentLinkedHashMap的经验。

Caffeine具有以下的优点:


  1. 可以自动加载数据到cache,这个操作方式可以是异步的操作,当然同时支持异步的刷新操作。

  2. 可以基于容量或者时间频率的回收和过期策略

  3. Key和value通过弱引用或者软引用包装,在gc的策略有一定的优势。

  4. 在key进行回收或者删除操作的时候有监听器,通知操作

  5. 在缓存访问具有统计功能

  6. 当然Caffeine在缓存的功能特性还有很多。

详情可以看官网,

https://github.com/ben-manes/caffeine



缓存组件选型(下)
4.自建缓存hashmap缓存,LinkedList缓存,堆外缓存等缓存组件

        当然如果你觉得开源的cache对你来说,不够透明,不够细节化,经过研究api不够使用,或者你认为太过重量级化的话,你可以选择自研缓存组件,当然我们做技术的其实还是多多的重复造轮子也不是什么坏事,因为这样我们能够更加的维护好自己核心组件,避免社区项目黄了。再比如有一些特殊的场景,你只需要存几个值,直接可以用hashmap就够了,那么也未尝不可,如果需要线程安全的话,可以使用ConcurrentHashMap构建自己的缓存组件,在淘汰机制上面可以自己实现对象来控制,给相应的key添加一个过期时间来处理,后台开启一个线程进行过期策略的处理。当然如果为了实现简单的淘汰算法的机制可以继承java的LinkedHashMap结构进行LRU和FIFO的淘汰算法进行过期key的处理和管理,这个也是很聪明的做法。堆外缓存呢,就是通过使用java堆意外的内存空间进行缓存的设计,java提供的API主要有两种,一种是java的NIO的bytebuffer和Unsafe,当然堆外内存的场景什么呢,我们在使用本地内存,会增加gc的负担,使用redis分布式缓存呢,会调用链路增长,速度比较慢,所以堆外存就应用而生了。

        

-----
缓存组件选型(下)
-----
     写了好久好久,那么接下来终于迎来了我们的分布式缓存组件的分享,分布式缓存中间件呢我们主要介绍以下几种,Memcached,redis,tair, Cassandra,相信这几个缓存中间件是大家用的最多的组件,那么我就一一的为大家介绍。
缓存组件选型(下)
分布式缓存篇
缓存组件选型(下)



缓存组件选型(下)
1. Memcached

    Memcached是一个自由开源的,高性能,分布式内存对象缓存系统。Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的一款软件。现在已成为mixi、hatena、Facebook、Vox、LiveJournal等众多服务中提高Web应用扩展性的重要因素。Memcached采用了单进程,单线程,异步 I/O ,基于事件 (event_based) 的服务方式,使用libevent的事件方式进行通知处理。libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能 封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能,为什么呢? 因为memcached使用自己的页块分配器和哈希表, 因此虚拟内存不会产生碎片并且虚拟内存分配的时间复杂度可以保证为O(1)。由于memcached使用这个libevent库,所以能在Linux、BSD、Solaris等操作系统上发挥其高性能,当然不仅仅因为这么一点,比如在一些数据结构上面的优秀设计等。Memcached是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者是页面渲染的结果。Memcached简洁而强大。它的简洁设计便于快速开发,减轻开发难度,解决了大数据量缓存的很多问题。它的API兼容大部分流行的开发语言。本质上,它是一个简洁的key-value存储系统。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

Memcached具有以下几种特点

①    协议简单

②    基于libevent事件模型

③    支持多种语言

④    仅仅支持key,value的存储方式和数据结构

⑤    不会造成内存碎片,但是为导致空间浪费

缓存组件选型(下)
题记:
缓存组件选型(下)

        这个内存碎片和空间浪费呢,主要是由于memcached的内存分配方式决定的,平常我们用的最多的是c中malloc和free进行内存的申请和释放,但是这样分配内存势必引起内存碎片或者内存的泄露,在高速缓存组件的设计的时候势必需要自己来管理内存和分配内存。在memcached的内存分配原则主要是申请一块大的内存,然后将大内存分为不同内存大小的slab Allocation空间,可以存储一定长度的key-value,在每一个slab的时候都会维护一个内存分配表,管理和记录slab中空闲内存和占用内存的情况,当然这种结构仅仅针对外部存储数据源的设计方式,自身内存仍然采用malloc和free的实现方式。



缓存组件选型(下)
2.redis

              Remote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets)与范围查询,bitmaps,hyperloglogs 和 地理空间(geospatial)索引半径查询。Redis 内置了 复制(replication),LUA脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence),并通过 Redis哨兵(Sentinel和自动分区(Cluster)提供高可用性(high availability)。

在新版本中还支持stream的数据结构在和kafka的结构思想很类似。在开源社区支持codis组件监控,很多其他公司也有自己实现的redis的集群方案,在容量和分布式化环节做了相当多的可拓展,官方的集群模式在3.0之后实现,目前cluster的集群档案相当多的公司抗住了生产的验证。持久化技术在rdb和aof方面有了和其他组件membercached本质的区别,在特定的场景还是显得非常的重要的,比如宕机了,我们可以迅速加载内存数据,快速恢复内存数据,以及在一些备份场景还是很有需要的。

Redis在读写性能上面具有很高的QPS,大家感兴趣可以参见之前写的一篇文章,关于redis的压测数据说明,redis的所有操作都是原子性的,并且支持弱的事务特性,在一些场景之下能够弥补关系型数据库的不足,同时由于redis支持更多的数据结构,应用场景也拓展了很多的领域,比如session管理,排行榜,消息队列,集合操作等场景。

目前redis集群方案主要有以下3种方案,我们来分析一下利弊

缓存组件选型(下)
 Master slave模式

Master slave模式主要是采用哨兵模式实现高可用,在一主多从的情况之下,可会实现读写分离,主写,从读,关于哨兵模式的原理这里简单的说一下:

    监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。

    提醒(Notification):当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。

        哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新Master。

每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown).若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡"(即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置。

哨兵模式的缺点也比较明显,容量受到一定的限制,单台机器容量满了之后很难进行拓展。



缓存组件选型(下)
Codis集群模式


Codis是一整套缓存解决方案,包含高可用、数据分片、监控、动态扩态 etc,动态分配,数据迁移等,尤其是他的动态扩容和数据迁移更加收开发者的追捧。

可以参见: https://github.com/CodisLabs/codis.

Codis的缺点也很明显:依赖过多的组件,明显的单点故障,在官方不支持集群方案的时候很多公司选择codis的方案,针对于单点故障的问题呢,目前官方已经提供了解决方案,就是部署多个codis-proxy通过哨兵模式和codis-dashboard进行切换,保证可用性。但是这样的集群架构proxy,意味着某些命令不支持,比如事务命令muti

集群架构图:

缓存组件选型(下)

这里补充一下codis的很重要的缺点。

    数据一致性难以保证

    高可用方案显得臃肿复杂

    就是codis不是redis亲生的,所有的更新和维护都比官网脱节了一个档次。

    依赖了过多的组件例如go,zk,依赖了过多的组件那么就需要考虑更多的物理资源,需要考虑更多复杂性的通信机制,在维护上面引入了更多的成本,难度系数增大。

注意一点:

TwemProxy 和codis的方案比较接近都是基于代理方式进行的,这里就不一一介绍了。


缓存组件选型(下)
Cluster集群模式

        Redis Cluster是一种服务器Sharding技术,3.0版本开始正式提供。Redis Cluster并没有使用一致性hash,而是采用slot(槽)的概念,一共分成16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。当客户端操作的key没有分配到该node上时,就像操作单一Redis实例一样,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node,这有点儿像浏览器页面的302 redirect跳转。Redis集群,要保证16384个槽对应的node都正常工作,如果某个node发生故障,那它负责的slots也就失效,整个集群将不能工作。为了增加集群的可访问性,官方推荐的方案是将node配置成主从结构,即一个master主节点,挂n个slave从节点。这时,如果主节点失效,Redis Cluster会根据选举算法从slave节点中选择一个上升为主节点,整个集群继续对外提供服务。

缓存组件选型(下)

特点:

    无中心架构,支持动态扩容,对业务透明

    具备Sentinel的监控和自动Failover能力

    客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

    高性能,客户端直连redis服务,免去了proxy代理的损耗

缺点是运维也很复杂,数据迁移需要人工干预,只能使用0号数据库,不支持批量操作,分布式逻辑和存储模块耦合等。



缓存组件选型(下)
  3.Tair方案


       tair 是淘宝的一个开源项目,它是一个分布式的key/value结构数据的解决方案。作为一个分布式系统,Tair由一个中心控制节点(config server)和一系列的服务节点(data server)组成,config server 负责管理所有的data server,并维护data server的状态信息;为了保证高可用(High Available),config server可通过hearbeat 以一主一备形式提供服务;data server 对外提供各种数据服务,并以心跳的形式将自身状况汇报给config server;所有的 data server 地位都是等价的。



缓存组件选型(下)

   

         Tair在存储引擎上面设计颇为复杂,不亏是出自阿里的作品,在cache上面支持持久化和非持久化两种策略,同时持久化的时候可以选择备份数量,备份目标地址等灵活的措施,用来保证高可用性。Tair的存储引擎有一个抽象层,只要满足存储引擎需要的接口,便可以很方便的替换Tair底层的存储引擎。比如你可以很方便的将bdb、tc、redis、leveldb甚至mysql作为Tair的存储引擎,而同时使用Tair的分布方式、同步等特性。
    a) mdb,场景定位于cache缓存,类似于memcache。支持k/v存取和prefix操作;
    b)rdb,场景定位于cache缓存,采用了redis的内存存储结构。支持k/v,list,hash,set,sortedset等数据结构;
    c)ldb,场景定位于高性能存储,采用了levelDB作为引擎,并可选择内嵌mdbcache加速,这种情况下cache与持久化存储的数据一致性由tair进行维护。支持k/v,prefix等数据结构。今后将支持list,hash,set,sortedset等redis支持的数据结构。
      tair 的分布采用的是一致性哈希算法,对于所有的key,分到Q个桶中,桶是负载均衡和数据迁移的基本单位。config server 根据一定的策略把每个桶指派到不同的data server上,因为数据按照key做hash算法,所以可以认为每个桶中的数据基本是平衡的,保证了桶分布的均衡性, 就保证了数据分布的均衡性。具体说,首先计算Hash(key),得到key所对应的bucket,然后再去config server查找该bucket对应的data server,再与相应的data server进行通信。也就是说,config server维护了一张由bucket映射到data server的对照表。Tair在数据一致性上面按照version机制处理并发,在容灾扩容,迁移方面都有相对而言比较完备的方案。综上所述,tair的分布式缓存系统是相当的复杂的,并且服务端采用的是c++编写,在高并发场景,一致性问题上面考虑到了更多的场景,相对其它的方案,比较完备,但是是阿里出品,也许有些担忧。在和redis做横向对比的时候呢,使用tair的案列并不是特别多,所以风险性就比较大而言。


       


缓存组件选型(下)
4.  Cassandra

        

    Cassandra在国内资料少,用的也不多,在这里就简单的描述一下吧。

优点:

  1. 集群,集群意味着存储能力、负载能力的平行扩展,多节点提供快速故障转移,这是主要原因。

  2. 写入,Cassandra写入的能力还是不错,读性能正常水平,但是由于原因1,可以使用更多的设备来弥补。

  3. 单表海量数据的存储,Cassandra提供分区功能,类似在mysql中分表操作,减少了分表这一层逻辑,但是也带来了一些限制。

  4. CQL 类似 SQL,这里是和redis相比,Cassandra的数据操作更加可控,同时自动管理缓存,可以当做mysql + redis用,然而不需要写两套代码,数据可以设置生存时间,相比redis,数据的落地做的更好,更接近mysql这种关系数据库的数据保障。

  5. 客户端,Cassandra提供较完整的客户端,包括php、JAVA、PYTHON、C 等等,而且近一年来(2015)更新频繁,可以说在技术支持上提供了较好保障。

  6. 值得一提的是,Cassandra在PHP提供了异步编程模式,这使得较少涉及异步编程的PHP可以同时处理许多耗时的查询。

  7. 技术单一,你只需要一个Cassandra安装包,就可以完成集群架设,算是非常简单,这一点是相比HBase。

缺点:

 a) 在没有账号和密码的情况之下,链接比较慢几十ms

 b)在高级特性方面不支持跳页操作

 c)  数据一致性问题,在高并发的情况之下,默认采用的最终一致性

 d)  文档是英文,从另外一方面来说,文档比较缺失

 e) 性能方面比redis弱一些

 f) 在连接池方面可能需要提高和提升,或者独立开发。

总结:

在本地缓存方面呢,ehcache和guava做得比较成熟一些,Caffeine可以考虑尝试,但是我们需要明确一点的是,本地缓存,你的定位是什么?存取数据快,方便。

分布式缓存选型我个人觉得在中小型公司还是redis cluster方案比较靠谱点,主要原因如下:

    性能高,去中心化支持扩展。

    运维方面的数据迁移暂时业内也没有特别成熟的方案解决,这一点虽然codis做得比较好,但是迁移数据场景是否很多呢?codis在其他方面比较有劣势。

     redis cluster是redis官方提供,我们期待redis官方在后面能够完美支持

     Codis方案,依赖了过多组件,更新速度和官网的redis是否能够保持一致很有风险性。综上所述还是建议大家使用redis cluster的方案。

大型公司可以考虑阿里的tair的方案,不过维护和跟进社区是比较头疼的方案。本期的缓存组件选型就分析到这里,大家觉得有什么问题可以文章底部留言,作者看到后会第一时间给与回复。



缓存组件选型(下)

End


扫描下方二维码,可以和作者共同探讨技术架构,项目管理等互联网项目。


缓存组件选型(下)



一大批关于java架构高并发高性能的文章正在到来...


欢迎扫描下方二维码,持续关注:


      




以上是关于缓存组件选型(下)的主要内容,如果未能解决你的问题,请参考以下文章

Memcached入门到缓存组件的选型

高性能的本地缓存方案选型,看这篇就够了!

高性能的本地缓存方案选型,看这篇就够了!

阿里P8架构师谈:分布式缓存的应用场景选型比较问题和挑战

阿里P8架构师谈:分布式缓存的应用场景选型比较问题和挑战

高性能的那些事儿-缓存设计