分布式缓存-Redis

2021/10/19 19:13:13

本文主要是介绍分布式缓存-Redis,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.产品简介:

tair 是淘宝自己开发的一个分布式 key/value 存储引擎。 tair 分为持久化和非持久化两种使用方式。 非持久化的 tair可以看成是一个分布式缓存.。持久化的 tair 将数据存放于磁盘中。 为了解决磁盘损坏导致数据丢失。tair 可以配置数据的备份数目。tair 自动将一份数据的不同备份放到不同的主机上, 当有主机发生异常, 无法正常提供服务的时候, 其于的备份会继续提供服务。

2.tair的总体架构

tair 作为一个分布式系统, 是由一个中心控制节点和一系列的服务节点组成。 我们称中心控制节点为config server。服务节点是data server. config server 负责管理所有的data server, 维护data server的状态信息。data server 对外提供各种数据服务, 并以心跳的形式将自身状况汇报给config server。config server是控制点,而且是单点。 所有的 data server 地位都是等价的。
在这里插入图片描述

3.tair 的负载均衡算法

tair 的分布采用的是一致性哈希算法, 对于所有的key, 分到N个桶中, 桶是负载均衡和数据迁移的基本单位.。config server根据一定的策略把每个桶指派到不同的data server上。 因为数据按照key做hash算法, 所以可以认为每个桶中的数据基本是平衡的。保证了桶分布的均衡性, 就保证了数据分布的均衡性。

4.增加或者减少data server的时候

当有某台data server故障不可用的时候, config server负责重新计算桶在data server上的分布表, 将原来由故障机器服务的桶的访问重新指派到其它的data server中。这个时候, 可能会发生数据的迁移. 比如原来由data server A负责的桶, 在新表中需要由 B负责. 而B上并没有该桶的数据,那么就将数据迁移到B上来。 同时config server会发现哪些桶的备份数目减少了, 然后根据负载情况在负载较低的data server上增加这些桶的备份.。当系统增加data server的时候, config server根据负载, 协调data server将他们控制的部分桶迁移到新的data server上.。迁移完成后调整路由。 如果系统中出现减少了某些data server 同时增加另外的一些data server。处理原理同上.。每次路由的变更, config server都会将新的配置信息推给data server.。在客户端访问data server的时候, 会发送客户端缓存的路由表的版本号,如果data server发现客户端的版本号过旧, 则会通知客户端去config server取一次新的路由表。 如果客户端访问某台data server 发生了不可达的情况(该 data server可能宕机了), 客户端会主动去config server取新的路由表。
在这里插入图片描述

5.发生迁移的时候data server如何对外提供服务

当迁移发生的时候, 举个例子, 假设data server A 要把 桶 3,4,5 迁移给data server B. 因为迁移完成前,客户端的路由表没有变化, 客户端对 3, 4, 5 的访问请求都会路由到A. 现在假设 3还没迁移, 4 正在迁移中, 5已经迁移完成.那么如果是对3的访问, 则跟以前一样. 如果是对5的访问, 则A会把该请求转发给B,并且将B的返回结果返回给客户,如果是对4的访问, 在A处理, 同时如果是对4的修改操作, 则会记录修改log.当桶4迁移完成的时候, 还要把log发送到B,在B上应用这些log. 最终A B上对于桶4来说, 数据完全一致才是真正的迁移完成. 如果是因为某data server宕机而引发的迁移, 客户端会收到一张中间临时状态的分配表. 这张表中, 把宕机的data server所负责的桶临时指派给有其备份data server来处理. 这个时候, 服务是可用的, 但是负载可能不均衡. 当迁移完成之后,才能重新达到一个新的负载均衡的状态.
在这里插入图片描述

6.桶在data server上分布时候的策略

程序提供了两种生成分配表的策略, 一种叫做负载均衡优先, 一种叫做位置安全优先;
负载优先策略:当采用负载优先策略的时候,config server会尽量的把桶均匀的分布到各个data server上. 尽量是指在不违背下面的原则的条件下尽量负载均衡. 1:每个桶必须有COPY_COUNT(可以设置值的大小)份数据 。2: 一个桶的各份数据不能在同一台主机上;
位置安全优先原则:在不违背上面两个原则的条件下,还需要满足位置安全条件, 然后再考虑负载均衡. 位置信息的获取是通过 _pos_mask(一个配置项) 计算得到.我们通过控制 _pos_mask(机架信息掩码. 程序使用这个值和由ip以及端口生成的64位的id做与操作, 得到的值就认为是位置信息) 来使得不同的机房具有不同的位置信息. 在位置安全优先的时候, 且一个桶的各份数据不能都位于相同的一个位置(不在同一个机房). 假如只有两个机房, 机房1中有100台data server,机房2中只有1台data server. 这个时候, 机房2中data server的压力必然会非常大. 当机房差异比率大于这个配置值(_build_diff_ratio)时, config server也不再build新表。
机房差异比率的计算:首先找到机器最多的机房, 假设data server数量是SA. 那么其余的data
server的数量记做SB. 则机房差异比率=|SA – SB|/SA. 假设只有两个机房A和B, 当差异比率小于
0.5的时候是可以做到各台data server负载都完全均衡的.假设A机房有机器6台,B有机器3台. 那么差异比率
6 – 3 / 6 = 0.5。这个时候如果进行扩容, 在机房A增加一台data server, 扩容后的差异比率 7 – 3 / 7 =
0.57。当只在机器数多的机房增加data server会扩大差异比率。如果我们的_build_diff_ratio配置值是0.5。那么进行这种扩容后, config server会拒绝再继续build新表.

7.Tair集群

一个Tair集群主要包括3个必选模块:configserver、dataserver和client,一个可选模块:invalidserver。通常情况下,一个集群中包含2台configserver及多台dataServer。两台configserver互为主备并通过维护和dataserver之间的心跳获知集群中存活可用的dataserver,构建数据在集群中的分布信息(对照表)。dataserver负责数据的存储,并按照configserver的指示完成数据的复制和迁移工作。client在启动的时候,从configserver获取数据分布信息,根据数据分布信息和相应的dataserver交互完成用户的请求。invalidserver主要负责对等集群的删除和隐藏操作,保证对等集群的数据一致。
从架构上看,configserver的角色类似于传统应用系统的中心节点,整个集群服务依赖于configserver的正常工作。但实际上相对来说,tair的configserver是非常轻量级的,当正在工作的服务器宕机的时候另外一台会在秒级别时间内自动接管。而且,如果出现两台服务器同时宕机的最恶劣情况,只要应用服务器没有新的变化,tair依然服务正常。而有了configserver这个中心节点,带来的好处就是应用在使用的时候只需要配置configserver的地址(现在可以直接配置Diamond key),而不需要知道内部节点的情况。
在这里插入图片描述

8.各个server的功能说明

8.1 ConfigServer的功能

  1. 通过维护和dataserver心跳来获知集群中存活节点的信息
  2. 根据存活节点的信息来构建数据在集群中的分布表。
  3. 提供数据分布表的查询服务。
  4. 调度dataserver之间的数据迁移、复制。

8.2 DataServer的功能

  1. 提供存储引擎
  2. 接受client的put/get/remove等操作
  3. 执行数据迁移,复制等
  4. 插件:在接受请求的时候处理一些自定义功能

8.3 InvalidServer的功能

  1. 接收来自client的invalid/hide等请求后,对属于同一组的集群(双机房独立集群部署方式)做 delete/hide操作,保证同一组集群的一致。
  2. 集群断网之后的,脏数据清理。

8.4 client的功能

  1. 在应用端提供访问Tair集群的接口。
  2. 更新并缓存数据分布表和invalidserver地址等。

9.存储引擎与应用场景

Tair经过这两年的发展演进,除了应用于cache缓存外,在存储(持久化)上支持的应用需求也越来越广泛。现在主要有mdb,rdb,ldb三种存储引擎。

9.1 mdb

定位于cache缓存。
支持k/v存取和prefix操作
特点:适用容量小,读写QPS高(万级别)的应用场景。由于是内存型产品,因此无法保证数据的安全性,对数据安全有要求的应用需在后端加持久化数据源(例如MySQL)。

9.2 rdb

定位于cache缓存,采用了redis的内存存储结构。
支持k/v,list,hash,set,sortedset等数据结构。
rdb的应用场景
适用于需要高速访问某些数据结构的应用,例如存储一个商品的多个属性(hashmap);队列(list)等。

9.3 ldb

定位于高性能存储,并可选择内嵌mdb cache加速,这种情况下cache与持久化存储的数据一致性由tair进行维护。
支持k/v,prefix等数据结构。今后将支持list,hash,set,sortedset等redis支持的数据结构。
ldb的应用场景
存储,里面可以细分如下场景:

  1. 持续大数据量的存入读取,类似淘宝交易快照。
  2. 高频度的更新读取,例如计数器,库存等。
  3. 离线大批量数据导入后做查询。也可以用作cache:数据量大,响应时间敏感度不高的cache需求可以采用。例如天猫实时推荐。

10.基本概念

10.1 configID

唯一标识一个tair集群,每个集群都有一个对应的configID,在当前的大部分应用情况下configID是存放在diamond中的,对应了该集群的configserver地址和groupname。业务在初始化tairclient的时候需要配置此ConfigID。

10.2 namespace

又称area, 是tair中分配给应用的一个内存或者持久化存储区域, 可以认为应用的数据存在自己的namespace中。同一集群(同一个configID)中namespace是唯一的。通过引入namespace,我们可以使用相同的key来存放数据,也就是key相同,但内容不会冲突。一个namespace下是如果存放相同的key,那么内容会受到影响,在简单K/V形式下会被覆盖,rdb等带有数据结构的存储引擎内容会根据不同的接口发生不同的变化。

10.3 quota配额

对应了每个namespace储存区的大小限制,超过配额后数据将面临最近最少使用(LRU)的淘汰。持久化引擎(ldb)本身没有配额,ldb由于自带了mdb cache,所以也可以设置cache的配额。超过配额后,在内置的mdb内部进行淘汰。

10.4 配额是怎样计算的

配额大小直接影响数据的命中率和资源利用效率,业务方需要给出一个合适的值,通常的计算方法是评估在保证一定命中率情况下所需要的记录条数,这样配额大小即为: 记录条数 * 平均单条记录大小。

10.5 expireTime:过期时间

expiredTime是指数据的过期时间,当超过过期时间之后,数据将对应用不可见,这个设置同样影响到应用的命中率和资源利用率。不同的存储引擎有不同的策略清理掉过期的数据。调用接口时,expiredTime单位是秒,可以是相对时间(比如:30s),也可以是绝对时间(比如:当天23时,转换成距1970-1-1 00:00:00的秒数)。
小于0,不更改之前的过期时间
如果不传或者传入0,则表示数据永不过期;
大于0小于当前时间戳是相对时间过期;
大于当前时间戳是绝对时间过期;

10.6 version

Tair中存储的每个数据都有版本号,版本号在每次更新后都会递增,相应的,在Tair put接口中也有此version参数,这个参数是为了解决并发更新同一个数据而设置的,类似于乐观锁。
很多情况下,更新数据是先get,修改get回来的数据,然后put回系统。如果有多个客户端get到同一份数据,都对其修改并保存,那么先保存的修改就会被后到达的修改覆盖,从而导致数据一致性问题。
比如系统中有一个值”1”,
现在A和B客户端同时都取到了这个值。之后A和B客户端都想改动这个值,假设A要改成12,B要改成13,如果不加控制的话,无论A和B谁先更新成功,它的更新都会被后到的更新覆盖。Tair引入的version机制避免了这样的问题。刚刚的例子中,假设A和B同时取到数据,当时版本号是10,A先更新,更新成功后,值为12,版本为11。当B更新的时候,由于其基于的版本号是10,此时服务器会拒绝更新,返回version error,从而避免A的更新被覆盖。B可以选择get新版本的value,然后在其基础上修改,也可以选择强行更新。

10.7 如何获取到当前key的version

get接口返回的是DataEntry对象,该对象中包含get到的数据的版本号,可以通过getVersion()接口获得该版本号。在put时,将该版本号作为put的参数即可。 如果不考虑版本问题,则可设置version参数为0,系统将强行覆盖数据,即使版本不一致。

10.8 version是如何改变的

Version改变的逻辑如下:

  1. 如果put新数据且没有设置版本号,会自动将版本设置成1。
  2. 如果put是更新老数据且没有版本号,或者put传来的参数版本与当前版本一致,版本号自增1。
  3. 如果put是更新老数据且传来的参数版本与当前版本不一致,更新失败,返回VersionError。
  4. put时传入的version参数为0,则强制更新成功,版本号自增1。

10.9 version返回不一致的时候,该如何处理

如果更新所基于的version和系统中当前的版本不一致,则服务器会返回ResultCode.VERERROR。
这时你可以重新get数据,然后在新版本的数据上修改;或者设置version为0重新请求,以达到强制更新的效果,可以根据自身对数据一致性的要求,在这两种策略间进行选择。

10.10 version具体使用案例

如果应用有10个client会对key进行并发put,那么操作过程如下:

  1. get key。如果get key成功,则进入步骤2;如果数据不存在,则进入步骤3.
  2. 在调用put的时候将get key返回的verison重新传入put接口。服务端根据version是否匹配来返回client是否put成功。
  3. get key数据不存在,则新put数据。此时传入的version必须不是0和1,其他的值都可以(例如1000,要保证所有client是一套逻辑)。因为传入0,tair会认为强制覆盖;而传入1,第一个client写入会成功,但是新写入时服务端的version以0开始计数啊,所以此时version也是1,所以下一个到来的client写入也会成功,这样造成了冲突

10.11 version分布式锁

Tair中存在该key,则认为该key所代表的锁已被lock;不存在该key,则未加锁。操作过程和上面相似。业务方可以在put的时候增加expire,已避免该锁被长期锁住。
在选择这种策略的情况下需要考虑并处理Tair宕机带来的锁丢失的情况。
10.12 什么情况下需要使用version
业务对数据一致性有较高的要求,并且访问并发高,那么通过version可以避免数据的意外结果。
如果不关心并发,那么建议不传入version或者直接传0。

11.集群部署方式

11.1双机房单集群单份

双机房单集群单备份数是指,该Tair集群部署在两个机房中(也就是该Tair集群的机器分别在两个机房), 数据存储份数为1, 该类型集群部署示意图如下所示。数据服务器(Dataserver)分布在两个机房中,他们都属于同一集群。
在这里插入图片描述

优点:

  1. 服务器存在于双机房,任一机房故障保持可用。
  2. 单份数据,无论应用在哪个机房,看到的都是同一个数据。
    缺点:
  3. 应用服务器会跨机房访问。杭州的用户需要访问上海的机房,上海的用户需要访问杭州的机房,速度慢效率低。
  4. 当一边机房出现故障时,tair中的数据会失效一半(一半这个数值是按两边机房tair机器数相同估计的,如果不相同,则按对应比例估算),短期内DB会承受较多的访问。

11.2 双机房独立集群

双机房独立集群是指,在两个机房中同时部署2个独立的Tair集群,这两个集群没有直接关系,数据不同步,但解决了双机房单集群的问题。下图是一个双机房独立集部署示意图。
在这里插入图片描述

适用场景:

  1. 后端必须要有数据源,Tair集群本身不做同步。
  2. 对不同集群数据不要求强一致性,不同机房相同key的value不同,不同应用服务器不同地区的用户看到的数据可能不同。
    优点:
  3. 每个机房拥有独立Tair集群,应用在哪个机房就访问哪个机房的Tair集群,不会出现跨机房调用和流量。
  4. 单边机房故障,不会影响其他机房的访问。
    缺点:
  5. Tair mdb集群之间本身不会做数据同步,不保证多集群间一致性。

11.3 双机房单集群双份

双机房单集群双份,是指一个Tair集群部署在2个机房中,数据保存2份,并且同一数据的2个备份不会放在同一个数据服务器上。根据数据分布策略的不同,还可以将同一份数据的不同备份分布到不同的机房上。该类型的集群部署方式与双机房单集群单份数据的部署方式一样。其不同之处,数据保存份数不一样。该类型
集群部署方式示意图如下图所示,数据服务器分别部署在两个不同的机房里,所有的数据服务器都被相同的配置服务器管理,在逻辑上,他们构成一个独立的集群。
在这里插入图片描述

优点:

  1. 数据存放两份,数据安全性有一定保障。
  2. 当一边机房故障时,另外一边机房依然可以服务,并且数据不丢失。
    缺点:
  3. 需要使用双份数据,机房资源消耗是原来的两倍。

11.4 双机房主备集群

这种部署方式中,存在一个主集群和一个备份集群,分别在两个机房中。如下图所示,CM3中部署的是主集群,CM4中部署的是备份集群。那么,在正常情况下,用户只使用主集群,读写数据都与主集群交互。主备集群会自动同步数据(不需要业务去更新两边),保证两个机房数据的最终一致性。当一个机房发生故障后,备集群会自动切换成主集群,提供服务,保证系统可用性。
在这里插入图片描述

优点:

  1. 数据安全和服务可用性高。
  2. 无需考虑多集群间数据一致性的问题,没有跨机房访问问题。
    缺点:
    1)不同应用服务器不同地区的访问速度不一致。

12.接口说明

12.1 Map<String, TairResult> mget(List keys, Class clazz)

批量查询缓存
参数:
keys - 要获取的数据的key列表
clazz - 返回类型
返回:
如果成功,返回的数据对象为一个Map

12.2 TairResult get(String key, Class clazz)

指定类型查询缓存
参数:
key - 要获取的数据的key
clazz - 返回类型
返回:
如果成功,返回的数据对象为一个TairResult

12.3 TairResult get(String key)

查询缓存
参数:
key - 要获取的数据的key
返回:
如果成功,返回的数据对象为一个TairResult

12.4 put(String key, Serializable value, int version, int expireSeconds)

设置数据
参数:
key - 要获取的数据的key
value - 要设置数据的值
version - 版本号
expireSeconds - 过期时间
返回:
如果成功,返回ture

12.5 boolean invalid(String key)

失效缓存
参数:
key - 要失效数据的key
返回:
如果成功,返回ture

12.6 increment(String key, int value, int expireSeconds)

原子自增,注意不能和put混用
参数:
key - 要自增的key
value - 要自增的value
expireSeconds - 过期时间
返回:
如果成功,返回ture

12.7 decrement(String key, int value, int expireSeconds)

原子自减,注意不能和put混用
参数:
key - 要自减的key
value - 要自减的value
expireSeconds - 过期时间
返回:
如果成功,返回ture

12.8 Map<String, T> prefixGets(String primaryKey, List secondaryKeys, Class clazz)

批量查询缓存
参数:
primaryKey - 一级key
secondaryKeys - 二级key
clazz - 指定类型
返回:
如果成功,返回一个Map<String, T>

12.9 prefixPuts(String primaryKey, Map<String, ? extends Serializable> values, int expireSeconds)

批量更新缓存
primaryKey - 一级key
values - Map类型,key为二级key,value为 T
expireSeconds - 过期时间
返回:
如果成功,返回true

13.Tair总结

Tair是一个分布式的key/value存储系统,数据往往存储在多个数据节点上。客户端需要决定数据存储的具体节点,然后才能完成具体的操作。
Tair的客户端通过和configserver交互获取这部分信息。configserver会维护一张表,这张表包含hash值与存储其对应数据的节点的对照关系。客户端在启动时,需要先和configserver通信,获取这张对照表。
在获取到对照表后,客户端便可以开始提供服务。客户端会根据请求的key的hash值,查找对照表中负责该数据的数据节点,然后通过和数据节点通信完成用户的请求。



这篇关于分布式缓存-Redis的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程