1. Infinispan是什么?
下述截自官方描述:
Infinispan是基于Apache 2.0协议的分布式键值存储系统,可以以普通java lib或者独立服务的方式提供服务,支持各种协议(Hot Rod, REST, Memcached and WebSockets)。
支持的高级特性包括:事务、事件通知、高级查询、分布式处理、off-heap及故障迁移。
2. 部署模式
Infinispan按部署模式分为嵌入式(Embedded)模式、Client-Server(C\S)模式。
2.1 嵌入式模式
嵌入式模式下,Infinispan实例和应用程序运行在相同的JVM中。如Infinispan运行在集群模式下,则每个应用程序中内嵌的Infinispan实例同时作为集群中的一个节点。
2.1.1 适用场景
应用程序为Java程序、且自身可长期、稳定运行。每个应用程序做为一个集群节点,故应用总数不宜过多。
此模式下,每当应用启动时都会涉及Infinispan实例启动、集群拓扑结构变更和state transfer
2.2 C\S模式
C\S模式下,Infinispan部署在多个独立的节点上提供缓存服务,应用程序通过客户端API访问节点并获存数据。目前,Infinispan支持的协议包括Memcached,HotRod,REST,WebSocket等。
对于集群模式的缓存实例,多个服务器节点组成一个数据网格,客户端可采用Infinispan支持的任意协议访问缓存服务。
Infinispan提供的HotRod Client除了做为基本的TCP连接客户端外,还实现了拓扑感知(topology-aware)和hash分布感知(hash-distribution-aware)功能来提升客户端的访问性能和可靠性。
2.2.1 拓扑感知
在C\S模式下,客户端连接到Infinispan服务器只需指定任意服务器的IP地址和端口号即可。连接成功后,服务器会将自身的拓扑信息发送给客户端,并且当拓扑结构发生变化时,新的拓扑信息也会同步到客户端,这就是拓扑感知。这使得当客户端连接的服务器发生异常时,客户端仍旧可以正常访问Infinispan服务。
2.2.2 Hash分布感知
Hash分布感知是指当需要操作某个数据时,客户端知道数据分布在哪几个节点上,并选择最合适的一个节点发送TCP请求。hash分布感知将节点选择落在客户端完成,减少了服务器的计算负荷,减少了服务器间的通信数量,提升了程序性能。
2.2.3 适用场景
- 缓存部署在独立节点上,其耗用CPU、内存等对应用程序自身运行不会带来影响。
- 只要遵守Infinispan支持的协议,客户端可运行在各种环境之下。另外,对于非Java应用只能采用此种方式。
- 适用于Java应用但自身并不需要长期、稳定运行(如运行一次,或存在频繁重启场景)等场景。
3. 缓存模式
Infinispan支持四种缓存模式:本地(Local)模式、复制(Replicated)模式、失效(Invalidation)模式、分布式(Distribution)模式。除本地模式外,其他三种模式均为集群模式缓存。
3.1 本地模式
本地模式下,数据缓存在本地内存,节点间数据不共享。Infinispan的cache对象继承自Java的ConcurrentMap,和直接采用map相比,其优势在于:
- 支持缓存数据持久化,支持mongodb、leveldb等多种数据库。Infinispan通过设置cache store仅将将上述数据库作为缓存数据的存储介质,并不涉及数据库本身的其他高级特性。
- 支持eviction策略,避免内存耗尽。
- 支持expiration策略,保证长期未被使用的数据被清除。
- 基于MVCC的并发处理策略,采用CAS和其他lock-free算法保证高性能。
*事务性保证。
3.2 失效模式
失效模式下,数据需持久化到数据库(或其他持久化设备)中,节点间并不共享任何数据。当发生数据更新时,更新数据库并通知其他节点该数据已过期。节点发现数据过期后,采用lazy策略从数据库更新至缓存。这样做的好处在于:
- 网络负荷最小化。和复制模式下更新数据相比,失效消息要小的多,有效降低了网络负荷。
- 延迟更新。节点发现数据失效后,无需立即更新数据,仅当需访问该数据时才执行更新。
- 支持同步、异步两种失效方式。
同步失效:发送失效通知,并等待所有节点响应(收到失效消息,并逐出过期数据)后返回。
异步失效:发送失效通知,广播至所有节点,并直接返回。 - 数据存在持久化需求,缓存作为持久数据的中间层。在读操作频繁的场景下,采用Infinispan,避免每次直接访问数据库,提高读取性能。
3.3 复制模式
复制模式是集群模式的一种,在该模式任何一个节点的数据变更将复制到其他所有节点上,这使得集群中任何一个节点都包含了完整的缓存数据。
和嵌入式模式组合时,所有数据均保存在应用程序本地,读操作仅需访问本地内存,性能最高。
和C\S模式组合时,因为数据都保存在远端,读写都需要进行一次远端访问,此时复制模式和分布式模式相比并无明显优势。
Infinispan设计了同步、异步两种方式用于将本节点的数据变更操作通知到其他节点,对于每种操作又支持TCP、UDP两种通信方式。采用UDP协议时,通过叠加JGroups的NAKACK2和UNICAST3保证传输的可靠性,具体可参见[JGroups支持]。
3.3.1 同步复制
执行写操作时,先在本地执行预提交(更新到MVCCEntry中),如本地节点是Primary节点,则向其他所有节点发起RPC调用并等待返回。如本节点不是primary节点,则向Primary节点发起RPC调用并等待结果返回。当RPC调用成功后,本地执行真正的提交动作。(Primary节点即根据一致性Hash算法计算出的第一个owner节点)。
如果在写值过程中发生拓扑结构变化或某个节点进入Suspect状态,此时Infinispan内部将发起重试,直到操作成功。
同步复制的优点在于强一致性,一旦写操作执行完毕,我们可以确定所有的远端节点都已更新。但缺点也很明显,写值动作相比异步模式要慢很多,它要等待所有的RPC调用。另外,随着节点增多,每次写操作需通知的节点增多。
按《Infinispan User Guide》的建议,对于复制模式在节点数量较少时(10个以下)时,采用TCP协议会获得更好的性能,但当节点数量增多时采用UDP会是更好的选择。
Infinispan只在拓扑结构发生变化或发生Suspect异常时,才会进行重试,而对于其他异常,如网络状况不佳导致的TimeOutException等不会重试,而是仅仅抛出异常。在这种情况下,不同节点节出现数据不一致,无法保证强一致性。关于Infinispan存在的全部一致性问题请参见[Infinispan一致性分析]。
3.3.2 异步复制
Infinispan提供了两种异步复制方法。
- 执行写操作时,先在本地执行预提交(更新到MVCCEntry中),如本地节点是Primary节点则向其他所有节点发起RPC调用并直接返回。如本节点不是primary节点,则向Primary节点发起RPC调用并直接返回,最后本地执行真正的提交动作。
- 执行写操作时,先在本地执行预提交(更新到MVCCEntry中),随后将写值命令插入到Replication Queue中,最后再本地执行真正的提交动作。Replication Queue根据设定的时间间隔(或达到最大数量时)将队列中的请求推送到所有节点。
采用Replication Queue方式的异步复制性能上更优,因为它会将发送的请求批量处理,减少了节点间的RPC请求数量和数据包处理数量。但数据延迟上会比第一种方式更高,因为前者采用立即发送RPC请求方式,而Replication Queue采用是周期轮询方式。
异步复制的优点在于优异的写值性能,一旦本地写值成功即认为操作成功。但数据一致性无法保证,异步写值失败时不会发起重试,仅将错误消息记入LOG中。
3.3.3 适用场景
在小规模集群中,嵌入式的复制模式效果最佳。在此种模式下,读操作只需访问本地内存,写操作也只需同步到有限的几个节点。
在小规模集群中,当无法采用嵌入式模式时,采用C\S的复制模式也是一种合理的选择。但此种模式下,节点数量建议控制在3个以下,当节点数量超过3个时,采用C\S的分布式模式更为合理。
当集群规模增大时,嵌入式的复制模式在读操作上依旧保持高性能,但写值由于同步节点增多导致性能下降明显。如果缓存单位时间内的写操作非常少可以考虑此类方式。
如对数据没有强一致性诉求,可考虑采用异步模式(建议采用Replication Queue方式)。另外,采用UDP协议也可以有效减少节点间发送的数据包数量。
3.4 分布式(Distributed)模式
分布模式是Infinispan支持的可扩展性最好的一种模式。在这种模式下,用户可以配置固定数量(numOwners)的副本数,而不需要复制数据到所有的节点上。复制有限个副本既可以控制开销,也可以带来数据访问性能和可靠性的提升。
在集群中,Infinispan通过一致性哈希(Consistent Hashing)算法来确定数据访问和存储的位置。使用一致性哈希是好处是:当现有节点失效或新节点加入时,系统不需要重新计算哈希来重新分配数据在集群中的存储位置。
和复制模式相比,优势如下:
- 可扩展性:对集群节点数量没有限制,可根据业务需要,灵活的增、删节点。
- 数据网格:假设集群中有三个节点A,B,C,内存大小为8G,复制模式下集群容量为8G。分布式模式下,如果每个条目保存在2个节点上,集群的容量为(8 G* 3) / 2 = 12G。
和复制模式一样,分布式模式也支持同步和异步两种模式
4. 并发处理
上节中提到Infinispan在写值时采用了先写入MVCCEntry的方式执行预提交,最后才执行真正的提交。MVCC(multi-versioned concurrency control)是多版本并发控制的简称,是目前流行的高性能并发处理机制,它有如下特点:
- 多个并发读操作和单个写操作可并发运行,且无需加锁。
- 不同key的写操作可并发运行,互不影响。
- 相同key的写操作排队运行,锁粒度低。
- write skew可以被检测并正确处理。
4.1 锁
4.1.1 锁类型
Infinispan支持两种类型的锁管理方式:为每个key维护一把锁或者使用分离锁(Lock striping)。
默认情况下,Infinispan采用的是为每个key维护一把锁的方式,这种方式锁粒度最小,并发性更高,但占用资源更多。
采用分离锁(Lock striping)时,为每个cache分配固定数量的lock,每个lock负责锁定一组entries(由key的hash code确定),这和ConcurrentHashMap类似。例如,ConcurrentHashMap的实现使用了一个包含16个锁的数组,每一个锁都守护HashMap的1/16。假设Hash值均匀分布,这将会把对于锁的请求减少到约为原来的1/16。这项技术使得ConcurrentHashMap能够支持16个的并发Writer。当多处理器系统的大负荷访问需要更好的并发性时,锁的数量还可以增加。
4.1.2 锁应用
在如下三种场景下需加锁保护:
- 非事务cache。写操作发送到key所属的primary节点,primary节点尝试获取锁,成功则通知其他节点更新数据,失败则抛出异常。
- 悲观事务cache。写操作或锁请求操作发送到primary节点,primary节点尝试获取锁,成功则继续运行,失败返回并回滚事务。可以有效地消除冲突,保证数据一致性,但是性能较低。
- 乐观事务cache。乐观事务直到事务预提交阶段才尝试获取锁,primary节点尝试获取锁,成功则执行write skew检查,如果write skew检查成功则执行事务操作,否则失败并回滚事务。这样的好处是减少占有锁的时间,提高系统的数据访问的吞吐量。该方法适用于多用户并发修改相同缓存数据概率较低的场景。
4.2 事务隔离
Infinispan提供两种级别的事务隔离机制:READ_COMMITTED(默认)和REPEATABLE_READ。
5 事件通知
5.1 Cache-level事件
Cache-level事件是指cache级别的事件通知,通过对指定cache添加listener的方式,应用可监听cache的数据变化。对于集群cache,通知事件类型包括键值的新增、修改、删除、过期。
集群模式下,嵌入式模式和C\S模式推送的事件类型相同,但处理方式上存在如下差别:
5.2 Cache manager-level事件
Cache manager level事件包括:cache start、cache stop、merged及viewchanged四种。并无特别之处,可参见Infinispan官网。
6. 高级用法
6.1 过滤器
Infinispan还支持自定义过滤器,特定读值接口允许指定过滤器并返回满足条件的一组数据。
嵌入式模式下,支持按key、value、metadata过滤,需实现KeyValueFilter接口。C\S式模式下,支持按key、value、metadata过滤,需实现KeyValueFilterConverterFactory接口,并且该实现类需提前部署到server上。
6.2 提高本地命中率
6.2.1 L1 caching
L1 caching是Infinispan自定义的本地一级缓存,它和本地其他缓存数据一起存放到ConcurrentMap中。在首次访问时获取远端数据并保存至L1 cache,当再次访问该数据时,直接访问本地L1 cache。
L1 caching中的数据在超过指定的超时时间(l1-lifespan)后自动清除,或者当远端数据发生变化是通知失效。
L1 caching的失效通知机制基本流程为:
- 当变更操作完成后,如果检测到当前cache启用了了caching,则发起失效通知。
- 首先检测变更key(s)的所有请求者,如果没有其他节点缓存了该key-value数据,则不发起通知。
- 将一个或多个key的变更请求打包成单个RPC请求,如配置为TCP协议则采用单播方式发送。如配置为UDP协议,则判定该key值的请求者是否超过设定阈值,如超过则采用组播方式,否则采用单播方式发送。
L1 caching维护了一个key到多个请求者的对应关系,每当其他节点请求本节点的某个key值,在映射表中增加一项。一旦数据发生变化,通过查找该结构以确定需推送失效信息的节点。Infinispan启动了一个定时任务来维护这组对应关系,时间间隔可配置,默认为10分钟,负责定时清理过期的对应关系。
如A节点访问了B节点”Hello”-“Value”数据,此时B节点将A节点添加到”Hello”键的请求者列表中。之后即便A节点永远不再访问”Hello”数据,在”Hello”数据发生变化时依旧会向A节点发送失效消息。直到B节点的定时任务触发将A节点从”Hello”的请求者列表中移除。
L1 caching适用于集群中某些节点存在频繁访问相同数据的场景,在这种场景下,将远端数据访问转换为一次本地内存访问,极大提高读操作性能。
6.2.2 Capacity factor
在分布式模式下,每个节点作为数据网格的一部分存储缓存数据。默认情况下,一致性hash选择将数据放到哪个节点时,采用的是“公平的”算法,其认定每个节点在存储能力上是一致的。
而在实际情况当中,各个节点的内存数据可能是不一致的,或者某个节点相比其他节点访问数据访问更加频繁。对于这种情况,我们希望一致性hash在做数据到节点映射时可以根据实际情况做相应的调整。
Infinispan提供了capacity factor来满足这种需求,默认情况下所有节点的capacity为1。如设置为2,则建议本地节点存储的键值数量是其他节点的两倍。注意,这里指明的是建议值,实际根据集群的运行情况向建议值靠近。
6.2.3 Key Affinity
这种技术指的是通过随机生成key的方式,可以指定该key的存放节点,以做到用户自主选择服务节点的目的。使用场景有限,切在数据迁移场景下,旧数据无法保证始终在指定的节点上。
7. 一致性分析
注:下述一致性分析默认基于Embedded模式,如C\S模式行为和Embedded模式不一致将在每节的末尾处指明。
7.1 复制(Replicate)模式
7.1.1 非事务模式
对于single key的写值操作,写值操作会发送到primary节点,由primary节点获取全局锁,并广播更新到所有其他节点(包括originator节点)。如果originator即primary节点,则省去发送到primary节点操作,直接获得全解锁并广播。在集群处于稳定状态时,数据保证一致性。
对于multi-key的写值操作,将不同的key发送到primary节点,在每个primary节点上单独处理写值操作。当key分散在不同的节点上时,操作并不保证原子性。
clear操作不获取全局锁,而是各自清理各自的本地内存,同样不保证原子性。
7.1.1.1 拓扑结构变更
新节点加入
新节点加入时,现有节点将复制所有数据到新节点上。
如果在复制过程中,新节点上发起KeyA的read操作,此时本地节点不存在KeyA数据,Infinispan将向其他所有节点发送read请求,并将最快返回的数据作为read的结果。这可能导致如下问题:
由于本地没有key的数据,多次发起远端请求时由于最快响应的节点不同导致数据不一致,但这只发生在state transfer过程中,一旦同步完毕,则不存在该问题。
发起者(originator)异常
节点A发起write请求后,该节点异常。
如果其他节点在处理write请求时,发生拓扑结构变化,将抛出OutdatedTopologyException,并且不会重试(因为重试之后发生在originator上)。同时,primary节点也将更新失败(因为primary节点一定是最后一个节点)。此时可能出现部分节点已更新,但部分节点更新失败问题,数据不一致。
C\S模式下,write请求的originator和primary为同一节点
新节点加入\移除
在写操作执行过程中,如果存在节点加入\移除,此时将产生OutdatedTopologyException或SuspectException异常,并由originator节点发起重试,以保证所有节点的数据一致性。
当originator同时是primary节点时,retry符合重试预期,但如果originator不是primary节点可能存在如下问题:
线程1、线程2同时执行put操作,线程3执行get操作。线程1首次put动作执行失败并准备重试,此时部分节点已经更新成功,而另外节点未更新,线程3节点已更新故读到v1。在线程1的put动作重试之前,线程2执行了put操作并更新成功,此时线程3的get操作将读到线程2更新的结果。随后线程1的put重试并成功,线程3又读到了线程1更新的结果,并且该结果作为集群中该key的最终结果。注意:C\S模式下,不存在该问题。
7.1.1.2 网络分区处理
Partition handlingdisabled
在此模式下,拆分的两个分区将做为两个集群独立运行。
两个分区独立运行,数据更新并不互通,数据不一致。
当分区合并时,infinispan并不尝试merge不同的数据。infinispan将最大的分区(节点数最多的)做为新集群的初始状态,另外分区的所有节点上的数据将被覆盖。这时,集群内数据保证一致,但数据可能并不是用户预期的数据,而是“过时”的数据。
Partition handling enabled
在分区前,集群中所有的节点视为稳定状态(stable topology)。如果出现至少一半的节点快速异常脱离本集群,此时该集群将被降级“Degraded”,降级的集群即不可读也不可写。Infinispan将集群降级是出于如下假设:异常脱离本集群的超过半数的节点可能组成了新的集群,并且该集群是可读写的。
合并时存在以下几种场景:
- 两个分区都被降级,合并后的分区足够恢复到avaliable状态,合并后的集群重新变为available。
- 两个分区都被降级,合并后的分区不足以恢复到available状态,合并后的集群仍旧处于降级状态。
- 一个分区为available状态,另外分区为degraded状态,合并后的集群变为available状态,并且degraded的分区数据被覆盖。
对降级分区的读、写操作均会产生AvailabilityException异常。
C\S模式下,推测所有客户端均连到相同的分区,待确认
7.1.2 超时异常
当RPC请求超过设定的replication超时时间依旧未返回时,将抛出TimeoutException。如NAKACK2协议采用的是消极确认重发机制,消息重发可能导致超过设定的replication timeout。
如果primary节点向其他节点发起的RPC请求抛出TimeoutException异常,此时primary节点更新失败。如果originator向primary节点发起的RPC请求超时,可能所有节点成功也可能部分成功。
获取锁也可能出现超时,出现在单个key的写操作时,写操作失败。如果是multi-key的写操作,其他primary节点的写操作依旧执行成功。
一般来说,当出现TimeoutException时,可以认定在部分节点上更新成功,另外节点上更新失败,数据不一致。
7.2 分布式(Distributed)模式
7.2.1 非事务模式
基本行为和复制模式下的类似,请参见12.1.1非事务模式。
7.2.1.1 读一致性
当一个节点试图读取本节点不存在的数据时,将向所有其他节点发送读请求,并以最快返回的结果为准。当读、写并发时可能出现如下问题:
在线程1的cache.put(key,v2)处理过程中,节点依次更新,线程2的读取行为可能由于读到了不同的节点而出现读到之前数据的问题。
7.2.1.2 拓扑结构变更
当originator发起一次读请求时,处理方式如下:
- 如果某个节点返回了正确的数据,读请求结束。
- 如果所有节点都没有响应,并且检测到拓扑结构变化,发起重试。
- 如果读请求返回的结果为null,并且当前正在做reblance处理,以新的WriteCH(写一致性hash)替代当前的ReadCH(读一致性hash),发起重试。
- 如果读请求返回结果为null,并且即没有拓扑结构变化,也没有reblance处理,则返回null。
其他特性和复制模式相同,请参见复制模式的拓扑结构变更一节。
7.2.1.3 网络分区处理
Partition handlingdisabled
基本处理和复制模式相同。
当集群被拆分成两个独立的分区时,每个分区独立的执行reblance,保证分区中每个key的owner数量和设定值相同。对于key值来说,如果分区中该key有n个owner,则复制的数量为numOwners-n,如果分区中该key没有owner,则该数据丢失。
当分区合并时,infinispan将topology id最大的分区做为新集群的基准。另外分区的所有节点上的数据将被覆盖。这时,集群内数据保证一致,但数据可能并不是用户预期的数据,而是“过时”的数据。另外,如果基准分区在split时丢失了某些key-value数据,而另外分区中持有这些key-vale,合并后这部分数据丢失。
Partition handling enabled
在分区前,集群中所有的节点视为稳定状态(stable topology),出现集群拆分时处理方式如下:
- 如果分区中包含的节点数量少于原集群数量的一半时,分区降级。
- 如果分区中至少有一个segment的全部owners丢失,分区降级。
- 如不是以上两种情况,分区视为正常集群,并启动reblance。
和复制模式不同,分布式模式下降级的分区处理方式如下: - 如果该分区包含了该key的所有owner,课执行正常的读写操作。
- 如果该分区仅包含了该key的部分owner或全部owner丢失,读写操作将抛出AvailabilityException异常。
其他处理方式和复制模式一致性。
超时异常?
7.3 小结
在Partition handling enabled的集群模式下,Infinispan负责保证系统的一致性。
对于当前版本的Infinispan来说,主要存在如下两种数据不一致场景:
- 发起者异常+拓扑结构更新。在infinispan中,拓扑结构引发的异常由发起者发起重试。如拓扑结构更新时,发起者已经异常,此时无法发起重试,可能存在部分节点更新成功而部分节点更新失败问题。
- 超时异常。当出现超时异常时,infinispan并不发起重试,而仅仅抛出异常,对于此种场景,集群中节点会出现数据不一致。
8. 总结
Infinispan采用Java开发,性能上和redis相当,但资源消耗和CPU耗费上高于redis。Infinispan支持各种Java类型存储,集成度更好。
可靠性上,redis和infinispan均不能保证100%的可靠性、数据一致性,相较来说,个人认为infinispan可靠性更高。
转载请注明:【随安居士】http://www.jianshu.com/p/d9e6912aaab1