Tair rdb(redis存储引擎)实现介绍

摘要:
淘宝那岩曾经在淘宝核心系统团队博客上介绍过Tairldb的实现,本文将尝试着介绍rdb的实现。redis作为Tair的存储引擎接入,称为rdb。本文参考的源码为Tairrdb实现最新的release版(这里),对照的redis实现为2.4.10版。但在rdb中,剥离了redis的网络层,只保留了内部存储实现,而上层是由多个线程调用的,因此就涉及到加锁的逻辑。

淘宝那岩曾经在淘宝核心系统团队博客上介绍过Tair ldb的实现,本文将尝试着介绍rdb(redis存储引擎)的实现。

Tair是淘宝开源的分布式KV缓存系统,内部将功能模块化,抽离出底层存储细节,可以接入不同的存储引擎。redis是一个开源的、高效的key-value存储,提供了strings、hashs、lists、sets、sorted sets等多种高级数据结构。redis作为Tair的存储引擎接入,称为rdb。

本文参考的源码为Tair rdb实现最新的release版(这里),对照的redis实现为2.4.10版。

Tair首先是一个分布式的框架,有一系列策略满足CAP(数据备份,迁移复制等)。另外,还有针对应用场景的功能特性(namespace,数据过期时间,原子计数等)。接入redis时做了比较多的修改,下边逐一介绍。对redis内部实现感兴趣的读者,可以看这里(或这里)。

1.配置修改

为了达到配置统一管理的目的,tair rdb去掉了redis自带的配置文件。它将redis所需的配置统一放在tair配置文件的TAIRRDB_SECTION下边。当前保留的配置文件项有:

RDB_UNIT_NUM 一个命名空间下的单元划分,默认值是16,决定了并发粒度,下边会有介绍;

RDB_AREA_GROUP_NUM 单机存储引擎的命名空间范围,默认值是512,这个值决定了单机上最多可以有多少个命名空间同时存在(命名空间类似于db name,使用中可以映射到项目,比如可以在area 0中保存项目名到命名空间id的映射,后续就可以在特定的项目内查找key-value);

RDB_MAXMEMORY 单机上rdb存储引擎占用内存的上限值,达到上限后将依据maxmemory_policy的不同采取不同的行为;

RDB_MAXMEMORY_POLICY 达到内存上限后进行的操作,可以直接返回错误,也可以进行LRU,可以参考redis的设置;

RDB_MAXMEMORY_SAMPLES 与LRU实现相关的一个配置参数,可以参考redis的配置;

RDB_LIST(/HASH/ZSET/SET)_MAX_SIZE list、hash、sorted set、set数据结构value最大占用空间的大小,默认均是8k(感觉偏小了);

需要根据自身项目需求调整上述配置。

2.加锁的逻辑

redis的主逻辑是一个单进程、单线程的程序,其并发是通过操作系统结合事件驱动框架(ae)实现的。但在rdb中,剥离了redis的网络层,只保留了内部存储实现,而上层是由多个线程调用的,因此就涉及到加锁的逻辑。

我们先看一下tair的mdb存储引擎(类memcache存储引擎)中遇到的问题,下图是一张调用逻辑图:

Tair rdb(redis存储引擎)实现介绍第1张

所有的线程(无论是否同一个area,也不区分读写)都在争用同一把锁,存在严重的瓶颈(我们在生产环境中已经遇到了这个问题)。

rdb的调用逻辑与图中所示基本是一致的,但在bottleneck部分做了优化。rdb利用了redis可以同时存在多个db的特性,一次性提供了area_group_num*unit_num个db,所有的操作都局限在db内,将锁的粒度变成了以unit为单位,相当于将上图中的单一的蓝色框的部分分解成了大量的管道并行处理,同时,不同area之间实现互不干扰。以默认配置举例,其加锁的粒度由所有area争用一把锁,变成了一个area划分成16个unit,只有落在同一个unit中的查询才会竞争一把锁,大大的提高了并发度。

其加锁的index通过下述的宏来查找,其中_hashcode为key值的hash:

76 #define get_redis_db(_area, _hashcode) ((_area) * context->get_unit_num() + (_hashcode) % context->get_unit_num())

3.定时任务的逻辑

redis的定时任务是由事件驱动框架实现的,其逻辑在redis.c的serverCron函数中实现,可以参考这篇博客

rdb的定时任务改成使用后台线程的方式执行,同redis默认配置一样,每100ms执行一次。定时任务以unit为单位,执行定时任务前会获取对应unit的锁,其实现逻辑是redis定时任务的一个子集,去掉了和持久化相关的部分。

4.持久化的逻辑

Update:ForFunny(rdb作者)注没有使用2.4.x中的bio主要是原来是基于2.2做的rdb,改成bio的略显麻烦 中间尝试改过一次,需要涉及修改的东西比较多,所以先上2.2的方式。和redis不同的是这边不会使用子进程来说这些事情,而是线程来做的,主要是防止cow可能的内存上升。

rdb提供了dump和load的接口,采用后台线程的方式,实现类似于redis的snapshot操作,但比较奇怪的是,最新的release版本中,这部分代码都是被注释掉的,猜测或许是这部分代码的测试还不完善,也可能是因为rdb的定位就是提供丰富数据结构的cache服务,可靠性保证通过Tair中间件层提供的机制实现。由于没有持久化的逻辑,相应的代码中也去掉了redis精巧设计的bio。

1266 void* dump_db_thread(void*argv) {
1267pthread_detach(pthread_self());
1268     dumpThreadInfo* info = (dumpThreadInfo *)argv;
1269     //rdbSave(info->server, info->filename, info->area);
1270free_dump_thread_info(info);
1271     returnNULL;
1272 }
1274 void* load_db_thread(void*argv) {
1275pthread_detach(pthread_self());
1276     loadThreadInfo* info = (loadThreadInfo *)argv;
1277     //rdbLoad(info->server, info->filename, info->area);
1278free_load_thread_info(info);
1279     returnNULL;
1280 }

5.丰富的操作

与mdb相比,rdb从redis继承了丰富的操作,包括list、hash、sorted set、set(与mdb相比,list不再那么ugly)。

但由于分布式的关系,不同的set很有可能落在不同的redis db实例中,因此不再支持集合的交(SINTER/ZINTERSTORE) 并(SUNION)操作。

6.对版本的支持

Update:ForFunny(rdb作者)注关于version rdb不止有key有version的,db,value都是有的,现在主要要是hash结构的中的field的version来实现根据field删数据的功能和清清namespace的作用。

memcache中有版本的概念,tair中同样有版本的概念。我们看一下tair中引入版本的用途,其思路借鉴自Dynamo的vector clocks:

“Version支持

Tair中的每个数据都包含版本号,版本号在每次更新后都会递增。这个特性有助于防止由于数据的并发更新导致的问题。

比如,系统有一个value为“a,b,c”,A和B同时get到这个value。A执行操作,在后面添加一个d,value为“a,b,c,d”。B执行操作添加一个e,value为”a,b,c,e”。如果不加控制,无论A和B谁先更新成功,它的更新都会被后到的更新覆盖。

Tair无法解决这个问题,但是引入了version机制避免这样的问题。还是拿刚才的例子,A和B取到数据,假设版本号为10,A先更新,更新成功后,value为”a,b,c,d”,与此同时,版本号会变为11。当B更新时,由于其基于的版本号是10,服务器会拒绝更新,从而避免A的更新被覆盖。B可以选择get新版本的value,然后在其基础上修改,也可以选择强行更新。”

redis自身没有版本的概念,antirez曾就此进行过讨论,rdb将版本引入,同key存在一块,同时修改了存储部分的代码,加入了对版本的支持。

本文作者水平有限,而且更多的是从源码的角度去反推设计,因此对于不正确和不完善的地方,还望大家指出,多多讨论。

免责声明:文章转载自《Tair rdb(redis存储引擎)实现介绍》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用cover-view覆盖小程序原生组件,解决原生组件层级最高的问题(转)使用Cobbler批量部署Linux和Windows:Cobbler服务端部署(一)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Android学习——移植tr069程序到Android平台

原创作品,转载请注明出处,严禁非法转载。如有错误,请留言! email:40879506@qq.com 声明:本系列涉及的开源程序代码学习和研究,严禁用于商业目的。 如有任何问题,欢迎和我交流。(企鹅号:408797506)  淘宝店:https://shop484606081.taobao.com 本篇用到的代码下载路径:http://download....

jedis 连接 redis

一、连接单机版的 redis /** * 直接连接 redis * @throws Exception */ @Test public void test1() throws Exception { //创建一个 jedis 对象,参数:host、post Jedis jedis = new Jedis("192.168.25.128...

《深入理解Java内存模型》读书总结

概要 文章是《深入理解Java内容模型》读书笔记,该书总共包括了3部分的知识。 第1部分,基本概念 包括“并发、同步、主内存、本地内存、重排序、内存屏障、happens before规则、as-if-serial规则、数据依赖性、顺序一致性模型、JMM的含义和意义”。 第2部分,同步机制 该部分中就介绍了“同...

SpringBoot + Redis + Shiro 实现权限管理(转)

概述 本文基于网上整理,为了实现将Shiro框架的session存储到redis里面,进而实现基于Niginx负载均衡,多站点部署; maven下shiro依赖 <!-- shiro --> <dependency> <groupId>org.apache.shiro</gr...

SendMessageTimeout 的使用

在WINDOW编程中,发送消息的常用API有SendMessage,PostMessage,PostThreadMessage。 一般每个线程有两个队列:一个用来接收通过Send函数的消息,另外一个队列接收通过Post函数的消息。该两个函数的基本区别是:一个函数需要等待返回的,相当于函数调用,这个是SendMessage;另外一个是将消息放到对方的队列中,...

ABP module-zero +AdminLTE+Bootstrap Table+jQuery权限管理系统第十五节--缓存小结与ABP框架项目中 Redis Cache的实现

返回总目录:ABP+AdminLTE+Bootstrap Table权限管理系统一期 缓存 为什么要用缓存 为什么要用缓存呢,说缓存之前先说使用缓存的优点。 减少寄宿服务器的往返调用(round-trips)。 如果缓存在客户端或是代理,将减少对服务器的请求,减少带宽。 减少对数据库服务器的往返调用(round-trips)。 当内容缓存在web服务器,...