redis-storage介绍[转]
背景:
当时我们正在做一个游戏项目,游戏项目相比于web项目,更追求的是单机的性能,而我们对单个请求的处理时间有着bt级的需求(一个完整的api请求控制在10ms以内)。当时我们的数据层用的是ttserver,但他在我们之前项目中有一些比较不好处理的问题,所以我一直在寻找的替代方向,而这时redis的横空出世,给nosql世界带来了不小的震动,相比于memcache, 丰富的数据结构,给了很多人更换cache层的理由,而数据能落地,使之有成为数据库的可能。后来新浪的大面积使用,稳定性得到保障,我果断在新项目中使用了redis。
问题:
redis的一些丰富结构,特别适合游戏,我们当时用的非常爽。后来游戏也顺利上线了。性能也是非常好,但运营了将近一年后,我们发现了一个致命的问题,由于我们是偏社交游戏,没有分服,所有的用户都在一个服务器上,这里面有一个矛盾了:
1) 数据在无止境的增长 (对内存的需求越来越多)
2) 活跃用户( dau )基本稳定了 (热数据占比较小20%以内)
思考:
问题的根源在于redis提供的两种持久化机制,都只是起到备份作用,所有的数据都必需在内存中:
1) 数据落地只是备份
2) redis服务在重启之后,需要把备份数据一次性load回内存(数据量很大需要load时间很长)
思路:
用一个成熟的持久化的存储引擎来替代。落地的数据能直接对外提供服务。而只要保证把热数据留在内存中,冷数据在持久化的存储引擎中。这样就可以解决几个问题:
1 ) 对内存的需求基本只是热数据的需求
2 ) redis服务重启,不需要再load回内存,可以空重启
方案:
最后经过一翻寻找对比,确定了用leveldb (关于leveldb的介绍自行google), 理由:
1) 由google开源,而且有很多成熟应用,质量可靠。
2) leveldb性能好,特别写性能,几乎和读性能一致。
3) 提供c的api,方便直接嵌入到redis中
实现 :
1) leveldb的嵌入
封装一个方法,以便redis服务初始化的时候,把leveldb引擎载入
void ds_init() { if(!server.ds_open) { return ; } char *err = NULL;
server.ds_cache = leveldb_cache_create_lru(server.ds_lru_cache * 1048576); server.ds_options = leveldb_options_create();
server.policy = leveldb_filterpolicy_create_bloom(10);
//leveldb_options_set_comparator(server.ds_options, cmp); leveldb_options_set_filter_policy(server.ds_options, server.policy); leveldb_options_set_create_if_missing(server.ds_options, server.ds_create_if_missing); leveldb_options_set_error_if_exists(server.ds_options, server.ds_error_if_exists); leveldb_options_set_cache(server.ds_options, server.ds_cache); leveldb_options_set_info_log(server.ds_options, NULL); leveldb_options_set_write_buffer_size(server.ds_options, server.ds_write_buffer_size * 1048576); leveldb_options_set_paranoid_checks(server.ds_options, server.ds_paranoid_checks); leveldb_options_set_max_open_files(server.ds_options, server.ds_max_open_files); leveldb_options_set_block_size(server.ds_options, server.ds_block_size * 1024); leveldb_options_set_block_restart_interval(server.ds_options, server.ds_block_restart_interval); leveldb_options_set_compression(server.ds_options, leveldb_snappy_compression);
server.ds_db = leveldb_open(server.ds_options, server.ds_path, &err); if (err != NULL) { fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, err); leveldb_free(err); exit(1); }
server.woptions = leveldb_writeoptions_create(); server.roptions = leveldb_readoptions_create(); leveldb_readoptions_set_verify_checksums(server.roptions, 0); leveldb_readoptions_set_fill_cache(server.roptions, 1);
leveldb_writeoptions_set_sync(server.woptions, 0); }
我们在redis.c里的initServer方法最后调用ds_init()即可。这样我们就可以在redis内部对leveldb进行操作了。
2) 一个简单的读取流程 (rl_get命令)
当client连上redis的时候,他的标准读取流程是:先从redis读取, 如果redis没有,则到leveldb读取。代码示例:
static void rl_getCommand(redisClient *c, int set) { //从redis里取数据 robj *o;
if ((o = lookupKeyRead(c->db, c->argv[1])) == NULL) { //没有读取数据 ds_getCommand(c, set); //从leveldb读取 checkRlTTL(c->db, c->argv[1]); return; }
if (o->type == REDIS_STRING) { addReplyBulk(c, o); checkRlTTL(c->db, c->argv[1]); return; }
addReply(c, shared.nullbulk);
}
3) 一个简单的写入流程(rl_set)
当client连上redis的时候,他的标准写入流程是:先写到leveldb中,写成功了,再写到redis中, 代码示例:
void rl_set(redisClient *c) { if(!server.ds_open) { addReplyError(c,"REDIS_STORAGE CLOSED"); return; } char *key, *value; char *err = NULL;
key = (char *) c->argv[1]->ptr; value = (char *) c->argv[2]->ptr; leveldb_put(server.ds_db, server.woptions, key, sdslen((sds) key), value, sdslen((sds) value), &err); if (err != NULL) { //leveldb写入失败,直接返回错误 addReplyError(c, err); leveldb_free(err); return; } //addReply(c,shared.ok);
//存到redis setCommand(c); checkRlTTL(c->db, c->argv[1]);
}
4)各种组合:
=======string数据操作======
rl_get key (从redis或leveldb取值, 优先顺序:redis > leveldb)
rl_getset key (返回同rl_get, 当leveldb有值,redis无值时,会回写到redis)
rl_mget k1 k2 k3 (取redis和leveldb的并集,优先级:redis>leveldb)
rl_mgetset k1 k2 k3 (返回同rl_mget, 当leveldb有值,redis无值,会回写到redis)
rl_set key val (往redis和leveldb写值, 优先顺序:leveldb > redis, leveldb如果失败,将中断往redis写,返回错误)
rl_mset k1 v1 k2 v2 (往redis和leveldb批量写值, 优先顺序:leveldb > redis, leveldb如果失败,将中断往redis写,返回错误)
rl_del k1 k2 k3 (往redis和leveldb删值, 优先顺序:leveldb > redis) ========hash数据操作========
rl_hget key hk (从redis或leveldb取值, 优先顺序:redis > leveldb)
rl_hgetset key hk (返回同rl_hget, 当leveldb有值,redis无值时,会回写到redis)
rl_hmget key hk1 hk2 (往redis和leveldb批量写值,优先级:redis>leveldb)
rl_hmgetset k1 k2 k3 (返回同rl_hmget, 当leveldb有值,redis无值,会回写到redis)
rl_hset key hk hv (往redis和leveldb写值, 优先顺序:leveldb > redis, leveldb如果失败,将中断往redis写,返回错误)
rl_hmset key hk1 hv1 hk2 hv2 (取redis和leveldb的并集,优先级:redis>leveldb)
rl_hdel key hk1 hk2 hk3 (往redis和leveldb删值, 优先顺序:leveldb > redis)
冷数据自动淘汰:
到现在为止,还有个关键的功能没有提到,就是如果保证热数据在redis中,冷数据在leveldb中。给出的方案是:在往redis里写入数据的时候,强制设置一个过期时间 ,强制的过期时间通过全局的redis.conf里的 rl:ttl 来设置。
另一个问题:
项目到了后期,活跃用户大部分都是老用户,也就是所谓的热数据,所以新增了一个全局配置: rl:ttlcheck ,如果某个key在rl:ttlcheck 至 rl:ttl 这段时间内被读取,则把这个key自动续期一个 rl:ttl 周期。
代码示例: static void checkRlTTL(redisDb *db, robj *key) { if(server.rl_ttl) { //如果配置了。 if(server.rl_ttlcheck >= server.rl_ttl) { // return; } long long expire = getExpire(db,key); if(expire == -1 || expire-mstime() < server.rl_ttlcheck*1000) { //如果时间>rl:ttlcheck,则自动续期 expire = server.rl_ttl * 1000; setExpire(db, key, mstime()+expire); //强制设置过期时间。 } } }
示例: rl:ttl 60 rl:ttlcheck 40 代表: redis里的数据过期时间为60s, 如果一个key在创建的第40s ~ 60s 之前被读取到,则自动续期至 60s
redis-stroage 总结:
1) 可直接对外提供服务的持久化存储。
2) redis空重启
3) 冷数据自动淘汰,热数据自动续期,麻麻再也不用担心我的内存了。
4) 只做新增命令,完全兼容redis原有命令和主从机制
后记:
经过这一个改造之后,后面的几个项目都采用redis-storage做数据库,稳定使用超过一年了,在稳定性和内存的使用方便都达到了预期的效果。现项目已开源: https://github.com/shenzhe/redis-storage
redis-storage介绍[转]的更多相关文章
- [转] Redis系统性介绍
Redis系统性介绍 http://blog.nosqlfan.com/html/3139.html?ref=rediszt 虽然Redis已经很火了,相信还是有很多同学对Redis只是有所听闻或者了 ...
- Redis安装介绍
Redis安装介绍 一.Linux版本及配置 1. Linux版本:Red Hat Enterprise Linux 6虚拟机 2. 配置: 内存:1G:CPU:1核:硬盘:20G 二.Redis ...
- Redis全面介绍
最近重新认识了一下Redis,借着这个机会,也整理一篇算是比较详尽和全面的文章吧. 缓存 缓存就是数据交换的缓冲区(称作Cache)——摘自百度百科.无论是在计算机硬件体系结构还是软件体系结构中, ...
- NoSQL数据库之Redis数据库:Redis的介绍与安装部署
NoSQL(NoSQL = Not Only SQL),它指的是非关系型的数据库.随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的w ...
- 4 Redis 配置文件介绍
2016-12-22 14:28:39 该系列文章链接NoSQL 数据库简介Redis的安装及及一些杂项基础知识Redis 的常用五大数据类型(key,string,hash,list,set,zse ...
- 第五章· Redis主从复制介绍
一.Redis主从复制 二.Redis主从复制工作机制 一.Redis主从复制 Redis复制功能简单介绍 1)使用异步复制.2)一个主服务器可以有多个从服务器.3)从服务器也可以有自己的从服务器.4 ...
- redis cluster介绍
讲解分布式数据存储的核心算法,数据分布的算法 hash算法 -> 一致性hash算法(memcached) -> redis cluster,hash slot算法 一.概述 1.我们的m ...
- Python 基于python操纵redis入门介绍
基于python操纵redis入门介绍 by:授客 QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3.3.2 基于Python操作R ...
- Python redis 简单介绍
Python redis 简单介绍 1.安装 终端输入: pip(or)pip3.6 install redis 安装成功 2.哈哈,发现我并没有redis服务可以访问,所以到这里,在本机安装了red ...
- Redis数据库介绍
引言 redis是一个开源的.使用C语言编写的.支持网络交互的.可基于内存也可持久化的Key-Value数据库. redis数据结构 redis是一种高级的key:value存储系统,其中value支 ...
随机推荐
- 26个Jquery使用小技巧(转)
下面列出了一些Jquery使用技巧.比如有禁止右键点击.隐藏搜索文本框文字.在新窗口中打开链接.检测浏览器.预加载图片.页面样式切换.所有列等 高.动态控制页面字体大小.获得鼠标指针的X值Y值.验证元 ...
- AJax知识介绍
参考:http://www.runoob.com/ajax/ajax-asp-php.html
- 2018SDIBT_国庆个人第四场
A - A 这题很巧妙啊,前两天刚好做过,而且就在博客里 Little C Loves 3 I time limit per test 1 second memory limit per test ...
- 解决 'Could not convert variant of type (NULL) into type (String)
写存储过程中有不允许为空的字段,在客户端转化取数时显示 Could not convert variant of type (NULL) into type (String) 可以在存储过程中使用is ...
- Spring STS Call Hierarchy 查找不到被调用的信息
今天使用Spring的STS的时候,发现Call Hierarchy无法使用,很奇怪,发现问题出现在同一个工作区间里,如果工作区间不在此工作区间,发现还是可以找到被调用的信息的.当时在网上找也没找到 ...
- javap——查看class文件的方法
有时候为了研究Javac的原理,要去看看class文件的内容是如何组织的,这时候很有必要查看class文件.方法有很多种,这里推荐使用JDK自带的javap工具. 首先建立如下源码: public c ...
- Linux性能测试分析命令_iostat
iostat用于输出CPU和磁盘I/O相关的统计信息 iostat语法 用法:iostat [ 选项 ] [ <时间间隔> [ <次数> ]] 常用选项说明: -c:只显示系统 ...
- RxJava2.0学习笔记2 2018年7月3日 周二
摘记: 1.map -- 转换 有些服务端的接口设计,会在返回的数据外层包裹一些额外信息,这些信息对于调试很有用,但本地显示是用不到的.使用 map() 可以把外层的格式剥掉,只留下本地会用到的核心 ...
- CSS强制换行和禁止换行代码
一.强制换行 1.word-break: break-all; 只对英文起作用,以字母作为换行依据. 2.word-wrap: break-word; 只对英文起作 ...
- Microsoft® SQL Server® 2012 功能包
Microsoft® SQL Server® 2012 功能包 http://www.microsoft.com/zh-cn/download/details.aspx?id=29065 Micros ...