开发者学堂课程【阿里云原生内存数据库 Tair 课程:Tair 生态,开源 module的使用】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/1198/detail/18206
Tair 生态,开源 module的使用
内容介绍
一、Tair 生态简介
二、TairStack 最佳实践
三、RedisShake 使用方法
今天的课程主要分为三个部分。首先先对生态的做一个整体的概论简介,让大家对Tair 有一个基本的认识,包括 Tair 现在开源的项目以及参与的开源项目,让大家对Tair生态有一个大体轮廓的认知。通过前面的课程大家应该也都了解,Tair 是兼容Redis的,Tair 的生态涉及 Redis 生态的各个领域,比如 Tair 与 Redis 社区之间就有着非常紧密的合作,还有很多其他的开源项目,例如 Stack 方面、模块、周边工具等。
第二部分就是 TairStack,目前也是 TairStack 生态的核心项目,也就是基于 Redis 扩展的各个模块系统,本小节会重点介绍已经开源的三个模块:TairString 、TairHash 、TairZset ,并且也会结合一些案例和最佳实践来充分理解谋划的使用方法,适合的场景。第三部分就是生态工具 RedisShake 。RedisShake 是一款用来做radius之间数据不同步的迁移工具,已经在 GitHub 上开源,而且也有非常多的用户在使用,并且有不少其他影厂商的也在使用。这次会介绍其中的原理,还有一些基本的使用方法。
一、Tair 生态简介
Tair 生态包括了整个 Redis 的生态,从 Redis引擎本身,再到周边的客户端 sdk,然后再到 module,还有固定工具等。Tair 团队从15年开始就深度的参与到 Redis 社区的开发中,累计贡献已经超过200个 patch ,比如说像 patch two 、 patch three,还有这次7.0的 module part 等,Tair 对 Redis 的贡献也得到了社区的充分认可。目前在社区有 Core member 1名,还有两名 contributor,社区未来的发展还有制定都有很强的影响力。在sdk方面,同时也在积极的跟进,因为业务需要使用 Tair Redis,肯定是需要客户端进行连接的,而且客户端的生态其实非常的丰富而且繁杂。也经常能遇到各种各样奇怪的客户端的问题。例如配置错误啊、网络错误,甚至还有的客户端对于 Redis 的协议解析都有错误。遇到这些问题的时候,一方面在帮用户解决问题,而且同时也会积极的跟社区沟通去做一些改进。目前在 sdk 的生态里面,也在 Tair 团队也有在 Redis 社区有一名 reviewer,还有一名 Lettuce contributor。Lettuce 客户端相对来说是比较新的,有很多不错的功能与理念,但是也有不少的问题,比如说在断线重连这部分做的就不够好,目前正在与社区共同的改进开发当中。当然还有很多其他的客户端,例如 php.net 等,同时也会积极的推进这部分开源客户端的发展,不光要给用户提供一个稳定的引擎端的服务,同时也要给用户提供稳定的客户端的服务。然后就是的 TairStack ,也是我们基于 Redis module 系统开发的众多的数据结构模块之一。在 Redis 出来之前的用户使用比较多的缓存系统是memery cash,当初 Redis 打扮 memery cash 就靠丰富的数据结构,memery cash一开始只支持简单的 Stack,Redis 自带的 haxisach 等丰富的数据结构,表达能力更强,使用也就更方便,业务就不用再做这种复杂的编辑码工作,就直接可以去使用表达能力更强的数据结构来实现自己的业务,也就更贴近用户,使用的场景也更多。所以 TairStack 的核心思路是开发新的数据结构来拓展Redis的使用场景,而且开源了其中用户返回反馈不错的,而且使用较多的一些模块,后面也会对这些已经开源的模块进行介绍。
最后是 RedisShake,这是一款用来进行 Redis 数据迁移的工具,主要应用在用户上云、机房之间的数据同步,数据搬迁等各种场景。在 top 上开源之后也收到2400多个 star ,还是比较火的,而且现在也不断断有新的需求。基本可以说现在国内做Redis 数据迁移的话,是一个实时的标准工具。
二、TairStack 最佳实践
介绍完 Tair 的整个生态,接下来再介绍 TairStack 的最佳实践。
1、TairStack 概览
先了解一下 TairStack 现有的模块类型,整个 TairStack 的家族。最上面的像对Redis 原生数据结构的拓展,例如 TairString 、TairHash、 TairZset,还有应用很广泛的布隆过滤器,jason Doc 及最新推出的全文搜索引擎 TairSearch 等。而且同时提供了配套的 sdk,像 go、JAVA、Python 等这些主流的语言都是支持的,而且云上数据库周边的系统例如数据管理、数据传输、审计日志及数据备份等也是全都支持的。对于这么多的模块,也不是凭空去创造的,也都是有基于用户的这些需求来实现的。Redis 的原作者 anti rice 也说过就是 Redis 使用在哪些产品当中并不是他决定的,就是用户来决定的,非常的接地气。所以就是开发了模块系统,让更多的开发者能够基于来定制化的进行需求的开发,这也极大的促进了 Redis 和 Tair 的发展,还是要非常感谢 anti rice 的大局观。
2、TairStack 开源模块
那么这些模块就共同组成了 TairStack,并且 Tair 在云上的云原生数据库也是完全支持 TairStack。也有很多的用户都在使用,同时做了一个统计,就是目前 TairStack中各个模块的使用情况,可以看到 Tair string 的使用量还是很大的,还有其他的像 TairHash、Tairbloom 以及 TairZset 的这些使用比例也是很高的。也就说明了有很多的使用场景,其实也就开源了其中的一部分就是用户基础不错,而且应用场景比较广的模块,目前是三个,分别是 TairString 、TairHash、 TairZset。分别是对 Redis 原生的 String、Hash,还有 Zset 的增强。对于源代码,在 github 上都是可以下载编译进行使用。
3、TairString 使用案例
首先来看一下 TairString 的案例。在介绍 TairString 具体的命令之前,先看一下一个关于并发更新的问题。Redis 本身是一个单线程运行的一个模式,命令的执行都是原始的,本身没有说锁的问题,但是业务的客户端需要连接过来的话,通常是有会有多个连接去访问 Redis 的。如果说业务执行逻辑中有需要执行多个命令的话,那么就没有办法保证业务的逻辑的原执性。这幅图里是一个典型的并发导致的资源竞争的问题,比如说最一开始这个 key 只是 hello,在T1的时刻,应用一去读取这个 key,那么读到的只是 hello。然后在本地更新为 word,但是还没有写回到Redis的时候,此时应用二也进行读取,读到的值也是 hello。在T3的时刻,将key 改成了 word。在T4的时刻,应用二却又把 word 改成了 universe。那么最终的值就是 universe,但是应用一仍然认为它还是 word,所以后续的操作就可能会出现问题。想要解决此问题,就需要保证访问和更新的原则性,那么 TairString 就可以做到这一点。实现方式也很容易理解,就是在 TairString 原生运行上面增加了vision 版本号的信息,那么每个key 其实都是有对应的 version 用于说明当前的版本,然后读取的时候就可以获取到值与版本两个信息,然后在更新的时候,再带上一个之前获取到的版本信息去做一个 set 操作。TairString 在 server 端就会进行校验,如果给定的版本号跟当前的版本号相同,那么就会更新成功,而且版本号加一,如果给定的版本号不同,那就会失败,而且返回 vision is stale 这个错误。客户端就知道数据已经被其他人更新过了,需要再重新来进行一轮的获取、更新及读写的操作。那么左边是TairString支持的命令,除了增强版本之外和原生的 string 操作的方法是基本一致的,但是也要注意,两者不是相同的数据构命令,是不能进行混用的。EXSET 不能同时操作同一个 key 的,EXSET 就是一个更新的操作,value这个参数后面跟的就是版本号。如果是创建一个新的 key 的话,那么版本默认是1,这个时候创建新 key 的时候,版本 value 这个参数其实是无效的。就是因为默认就是1,1后面跟10也是1。若需要创建新 key 并且指定版本的话,需要用abs 这个参数来设置绝对版本,abs 这个参数还要向 exset、value 这两个特殊参数的这种命令的参数其实就像金手指一样,就是允许强制更改版本号。这个一般只有在一些特殊的场景才会使用到。除了 exset 这种之外,dream 也支持 exset 那个命令,也就是说,只是计数器就是在版本号相同的时候才会对计数器进行加和减的操作,还有一个 by fold,也就是浮点数据技术器也都是类似的。其他还有就是 exget和 exset 的结合,在一些简单场景的话,就可以减少网络的交互,一个操作就可以覆盖 exget 与 exset 的两个操作。有了 TairString 这些特性之后,是用来时间的管所就很方便了,接下来看一下代码。
代码是一个 while 循环,首先用 exget 来获取 key 的值和版本,然后准备好要进行更新的数据,再使用 exset 尝试进行更新,里面就会带上之前获取的版本号。如果说 exset 返回 ok,那么数据就会更新成功,然后跳出循环,如果返回的是vision style,那么说明期间就有其他人更新了数据版本,版本已经不对了,需要尝试重新再来跑一遍这个流程,整个过程其实可以看到,其实还是很容易去实现的。此时其实就可以有很多的应用场景了,举一个简单的例子,就例如抢购系统,以一个计数器来代表库存,那么exget可以对比库存和版本判断,如果说库存大于0,那么就说明现在还有库存,就可以在本地减1,然后用 exset 去更新,或者直接去调用 EXINCRBY 减库存。如果返回 ok,那么就可以告诉用户更新成功了,但是如果返回的错误的话,那么就返回 vision is stale ,则说明其他人需要减库存,那么这个时候就要跳回到 while 这个要循环里面继续循环。TairString 的应用还是很广泛的,抢购系统以及乐观锁其实都是很简单的例子,大家课后可以再去探索一些更多的业务场景。
4、TairHash 使用案例
接下来再介绍一下 TairHash,TairHash 最主要的特点就是支持 field 的级别的过期时间设置,熟悉Redis的同学应该都知道是支持 key 级别的过期时间的,就是原生的 Hash 也同样支持整个 key 的设置过期。例如 tpl 是1秒的话,有一个Hash表里面可能存了4个、5个或者100个 field,那么整个 Hash 表都会被删除。但是如果想对Hash表内各个费用设置不同的过期时间的话,原生的 Hash 就不支持了,但是TairHash就可以做到这一点,而且还支持 field 的级别的版本号设置,功能还是非常强大的。命令的格式的也很多,与原生的 Hash 是基本一致的,只是增加了版本号以及过期时间,这里右边的列出来一些新增的与过期时间相关的命令,比如EXHPEXPIRE,意思就是说给 airHash 指定的 field 设置过期时间,EXHPTPL 就是获取指定的费用的国际时间,这里要说一下就是 TairHash 虽然支持版本号,但是并不是强制的,也就是说不带版本号也是完全可以操作的,也可以就是进行更新,这样的话就可以降低使用成本。就是不使用版本号仅使用 field 级别的过期时间也是可以的。因为主要的特定还是 field 级别的过期时间。
TairHash 的一个典型使用案例就是新型用户登录态系统,从最简单的用户登录态系统进行介绍,使用原生的 String 数据结构就可以实现。Key 作为用户名,value 作为存储用户信息,用户登录的 hash 信息,同时设置过期时间来管理登录周期。现在很多都会有比较多的登陆设备,比如像手机、电脑还有 iPad 等,这种情况 new String也是可以的,就是把key 做一下修改,比如说把用户名与设备信息拼接起来,这样也可以进行工作,但是一方面这样拼接的话,需要业务实现这个拼接逻辑不直观,而且又要进行重复的编解码工作,还有大量重复的用户名前缀浪费存储空间。若使用 TairHash 就可以很好的解决这个问题,key 是用户名,然后使用TairHash 的 field 的来记录不同的设备,比如说我有 phone、pc、pad 等的登录信息,分别存储的信息,还要再设置不同的 tpl 过期时间。这样的技术可观又高效,当然如果说在集合版本的话,还能有更多的用法,这里就不再展开学习讲解了。
5、TairZset 使用案例
最后介绍开源的最后一个模块原生的 TairZset ,是一个有序集合,在排序的场景中应用很广泛,但是原生的 Zset 只支持一个维度的 double 类型的分值排序,如果想做多位排序是比较困难的。就比如奖牌榜这个例子,有金银铜牌,那么金牌相同的时候,需要再去比银牌的个数,如果银牌个数相同的话,还要再去比铜牌的数量,这种三个维度的排序,原生的 Zset 就很难支持了。
当然也有人说使用 double 进行分段,然后拼接进行实现,但是又会引入一些新的问题。首先业务做这种分段拼接就很复杂,然后精度就会下降,因为 double 分段了,还有就是计数器的加减也没有办法进行使用。
TairZset 支持一种多维排序的集合,这个时候就很有用了,多个维度的分值,然后具体的格式介绍一下,就是多个维度的分值,用井号键来分隔,那么最大支持256个维度的 double 分值,而且也支持 EXINTERBY 这种可以对指定维度的分值做加减法的操作。而且目前也基本涵盖了所有原生 Zset 的命令,除了命令多了 EX 前缀,还有支持多维分值以外,格式上基本与原生的 Zset 是完全一致的。就是如果不带井号分割线的话,那么使用起来基本上就跟原生的 Zset 是一样的。然后右边的是一个典型的排行榜的一个代码实现,把各个参赛方的经营品牌的个数写入到TairZset 当中,然后就可以轻松的获取三维排序的排名。
6、TairStack 其他模块简介
当然 TairStack 原生态中还有不少其他的模块系统,比如 Redis Bloom 过滤器,还有 DOC 等这种就是 jason 的这种文档,TairStack 也有支持很多的这种功能的模块,而且功能其实是更丰富的,比如 TairDOC 就可以用来实现 jason 的存储,不仅能完全兼容 ReJson ,而且还支持 Json Path 和 Json Pointer 的功能。同时还提供XML/YAML 导出的功能。那么 Tair Bloom 也是完全兼容Redis Bloom 的,并且底层就是采用scaler Bloom来实现,支持动态的扩容,降低内存的消耗,并且在扩容期间也可以保持稳定的误判率。Tair Roaring 具有非常丰富的位运算功能,底层是采用两层索引下多种存储容器,同时可以兼顾性能以及空间的效率。TairStack 还有很多其他的模块,本次的课程时间有限,这些模块就不再展开学习讲述了,若有兴趣的话,可以在官方文档当中去学习参考。
三、RedisShake 使用方法
1、RedisShake 简介
RedisShake 其实用途是很明确的,就是把数据从一个 Redis 的实例迁移到另外一个 Redis 实例当中,而且单机与集群模式都是支持的,有两种模式,目的很明确而且看起来好像也不复杂,就是做数据搬迁,但是真正实现起来的时候并不简单,因为有各种复杂的场景,比如说有的用户只有 rdb 文件,连接不了实例,有的用户不仅希望做全量的数据迁移,还希望能有实时的增长数据同步,这些需求本身就很复杂,另外还有像账号的权限问题、断点重传怎么处理等。所以 RedisShake 在不断的开发迭代中,总结出了不少的通用的场景,目前抽象出五种同步的方法。
包括 decode、restore、dump、sync、rump 模式,支持从 RedisShake 本身或者说从 rdb 文件进行同步。比如说从自建 Redis 前移到云 Redis 或者从 Redis 文件导入到 Redis 的 rdb 文件导入数据到云 Redis,甚至也可以从其他友商,比如说AWS、腾讯等厂商把数据同步到阿里云 Redis 也是可以的。而且现在可以看到有不少其他的云厂商也把 RedisShake 作为数据迁移工具来交付给用户使用,但是这也很好。在迁移中,比如一个典型的问题就是大 key 的拆分,就是 Redis 原生的协议对于参数的限制是512M。例如存储一个 Hash ,单个的 value 是不能超过512M的,但是加起来却可以。比如说有个 Hash,每一次的 field 都只有几 kb。但有上千万上亿的这种,那么在打包成 rdb 之后,整个的 rdb 的存储就可能超过512M。此时把512兆rdb 直接往目的端同步的话,Redis 其实是不符合的,可能会超过512M。这个时候 RedisShake 就会对大 key 进行拆分。就是会解析这个 rdb 的格式,把它转成一条一条执行 Redis 的命令,例如 Hash 就会转成一条一条的 hset 进行数据同步,就可以防止大 key 导致的同步失败,那么 Shake 现在也是支持从二.×版本到最新的7.0版本,绝大部分的版本的数据同步,而且也支持刚才讲过的 TairString、TairHash 以及 exset 的数据同步。所以使用 TairShake 的开源模块也是可以用 Shake来进行同步数据的,大家不用担心。刚才讲解了 RedisShake 有五种工作模式,那么常用的有三种,这里重点介绍一下三种的原理,还有适用的场景。
2、RedisShake 常用同步模式
经常有用户来询问数据同步的时候到底应该使用哪种模式,今天从原理上讲解,让大家有一个清晰的认识。首先是 SYNC 模式,这个模式应该是需求最为广泛的一个同步模式,因为支持全量和实时的增量同步,所以它的工作原理最为复杂。在通过同盟链当中的 Redis 是伪装成一个 Shake 实例从源端去获取数据,然后再把数据同步给目的端的 Redis。伪装成 slave 其实是实现了一整套的 Redis 复制协议,对外呈现的就是就是一个会发送 SYNC 或者 PSYNC 命令给源端,此时源端收到之后就会当做一个 slave,首先就是打包当前的一个快照或者生成 rdb 文件,然后把 rdb 文件传输给 Shake ,然后把增量的数据缓存下来。发完 rdb 文件之后,才会把增量的数据再发给 Shake,而且后续的数据会一直不停的实时的发送给 Shake。Shake首先收到 rdb 文件进行解析,其中将一对一对的kb数据转成命令发给目标端,但是如果有大 key 的话,会直接拆分成具体的数据结构相关的命令。全量的过程需要Shake 来进行转换,全量的 rdb 文件全部同步完成之后,才会接触到增量的数据,也就是一条 Redis 的命令,然后再转发给目地端。用户在源端填写或者满足其他的一些条件之后,就可以完成整个的数据同步。其实 Redis的复制协议还是很复杂的,现在已经尽量简化了过程,那么 Shake 模式的工作原理,大家也应该知道优缺点。优点其实很明显,就是支持全量和实时增量的数据同步,可以最大化的满足数据一致性的要求。当然前提的条件是源端的实力要开放SYNC、PSYNC 高级别的复制权限命令。也收到过一些用户反馈,一WX就不支持 SYNC 和 PSYNC ,但是有时候就是把这些内部换成其他的命令了。RedisShake 目前也是支持按根据配置来把SYNC和PSYNC 这边是AWS的运营这种模式的。如果还有其他的完全不支持 SYNC和 PSYNC ,则需要有其他方法来实现数据同步,则开发了 RESTORE 还有 RUMP 模式。RESTORE 相对来说简单一点,就是直接读取本地的 rdb 文件,然后把 rdb 文件转成一条一条的 RESTORE 命令,然后发给目标实例。就是类似 SYNC 中的全量阶段,但是不需要连接原实例执行 SYNC 和 PSYNC 命令,此 rdb 其实就是一种离线的全量同步方式,那么使用成本也比较低,对原始率也没有影响,是比较适合这种仅需要做全量数据同步,并且也能够获取到 rdb 备份的用户。大部分的情况下,其实 rdb 还是都可以拿到的,但是也有一些情况拿不到备份,那这个时候又引入了一个新问题,就是拿不到的时候要怎么办?那么就开发 RUMP 模式派上用场,RUMP 模式相比 RESTORE 要稍微复杂一些,因为没有 rdb 文件,所以就需要连接到原实例上进行数据同步。具体的工作模式是 Shake会模拟生成 rdb 文件的整个过程,也就是不断的发送 SCAN 命令去扫描,扫描出来的 key ,针对扫描的 key 执行 DUMP 命令,获取到 rdb 格式,然后再使用命令发送给目标实例,并且一直重复这个过程,直到扫描完所有的数据。RUMP 模式主要是使用 SCAN 以及 DUMP 这两个命令,这两个命令目前来看的话,大部分场景都会支持的,但是要注意一点,就是 RUMP 模式只能做全量同步的,就是像 SYNC 后续的增量同步模式是不支持的,因为增量的数据只是在 Redis 原生的复制协议当中,而 RUMP 模式是是使用SCAN 以及 DUMP 这两个命令,是不在这个复制协议当中的。这三种模式,大家了解完之后可以根据各自的需求来进行实际的选择,这三种基本上可以满足大部分的数据同步的场景。
3、RedisShake 配置方法
最后再介绍一下 shake 的配置文件。有一些比较关键的配置项目,比如源端就是有三个比较关键的,像源端的类型就是 source type,是主从的还是集群的,就是standalone 还是 cluster。如果是集群 cluster 的话,会自动的获取源端集群的拓扑、源端的地址及密码。目标端同样也是这三个关键的配置目标的类型standalone,同样的如果是 cluster 的话,shake 也会自动获取目标的集群拓扑,而且兼容了 cluster 的 raw,cluster 集群的整个同步过程,用户是不需要关心两边的集群拓扑,shake 会自动的去做这些事情。选好配置文件之后,按照刚才所讲的几种工作模式就可以启动,就是 top 来指定工作模式,比如说 SYNC、RESTORE、RUMP 等,然后同步的过程就会记录在运行的日志当中。除了上面这些必须进行的技术配置之外,shake 还支持一些高级配置,比如 key exists,在同步过程中如果遇到重复的 key,是覆盖、终止同步还是忽略,一般来说需要业务保证目标实例是一个公实例,就是尽量避免这种冲突,但是也有一些特殊情况,比如说传输到一半,然后 RedisShake 中断了,则需要重新同步,此时就可以选择,rewrite 这种模式进行覆盖之后继续。还有就是像 list 的黑白名单来指定同步哪几个 db ,或者说有哪几个 db 是不同步的,有的时候也可能是在进行实例的拆分,就比如说把一个实例中的数据分别导入到不同的几个实例,比如现在有一个实例需要导入至三个实例里面,123库导入到实例1和456库导入到实例2,其他几个库导入至实例3中,这种时候按照 db 维度进行拆分就很有用。