背景

当时我们正在做一个游戏项目,游戏项目相比于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介绍[转]的更多相关文章

  1. [转] Redis系统性介绍

    Redis系统性介绍 http://blog.nosqlfan.com/html/3139.html?ref=rediszt 虽然Redis已经很火了,相信还是有很多同学对Redis只是有所听闻或者了 ...

  2. Redis安装介绍

    Redis安装介绍 一.Linux版本及配置 1.  Linux版本:Red Hat Enterprise Linux 6虚拟机 2.  配置: 内存:1G:CPU:1核:硬盘:20G 二.Redis ...

  3. Redis全面介绍

    最近重新认识了一下Redis,借着这个机会,也整理一篇算是比较详尽和全面的文章吧.   缓存 缓存就是数据交换的缓冲区(称作Cache)——摘自百度百科.无论是在计算机硬件体系结构还是软件体系结构中, ...

  4. NoSQL数据库之Redis数据库:Redis的介绍与安装部署

     NoSQL(NoSQL = Not Only SQL),它指的是非关系型的数据库.随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的w ...

  5. 4 Redis 配置文件介绍

    2016-12-22 14:28:39 该系列文章链接NoSQL 数据库简介Redis的安装及及一些杂项基础知识Redis 的常用五大数据类型(key,string,hash,list,set,zse ...

  6. 第五章· Redis主从复制介绍

    一.Redis主从复制 二.Redis主从复制工作机制 一.Redis主从复制 Redis复制功能简单介绍 1)使用异步复制.2)一个主服务器可以有多个从服务器.3)从服务器也可以有自己的从服务器.4 ...

  7. redis cluster介绍

    讲解分布式数据存储的核心算法,数据分布的算法 hash算法 -> 一致性hash算法(memcached) -> redis cluster,hash slot算法 一.概述 1.我们的m ...

  8. Python 基于python操纵redis入门介绍

    基于python操纵redis入门介绍 by:授客  QQ:1033553122 测试环境 redis-3.0.7 CentOS 6.5-x86_64 python 3.3.2 基于Python操作R ...

  9. Python redis 简单介绍

    Python redis 简单介绍 1.安装 终端输入: pip(or)pip3.6 install redis 安装成功 2.哈哈,发现我并没有redis服务可以访问,所以到这里,在本机安装了red ...

  10. Redis数据库介绍

    引言 redis是一个开源的.使用C语言编写的.支持网络交互的.可基于内存也可持久化的Key-Value数据库. redis数据结构 redis是一种高级的key:value存储系统,其中value支 ...

随机推荐

  1. 26个Jquery使用小技巧(转)

    下面列出了一些Jquery使用技巧.比如有禁止右键点击.隐藏搜索文本框文字.在新窗口中打开链接.检测浏览器.预加载图片.页面样式切换.所有列等 高.动态控制页面字体大小.获得鼠标指针的X值Y值.验证元 ...

  2. AJax知识介绍

    参考:http://www.runoob.com/ajax/ajax-asp-php.html

  3. 2018SDIBT_国庆个人第四场

    A - A  这题很巧妙啊,前两天刚好做过,而且就在博客里  Little C Loves 3 I time limit per test 1 second memory limit per test ...

  4. 解决 'Could not convert variant of type (NULL) into type (String)

    写存储过程中有不允许为空的字段,在客户端转化取数时显示 Could not convert variant of type (NULL) into type (String) 可以在存储过程中使用is ...

  5. Spring STS Call Hierarchy 查找不到被调用的信息

    今天使用Spring的STS的时候,发现Call  Hierarchy无法使用,很奇怪,发现问题出现在同一个工作区间里,如果工作区间不在此工作区间,发现还是可以找到被调用的信息的.当时在网上找也没找到 ...

  6. javap——查看class文件的方法

    有时候为了研究Javac的原理,要去看看class文件的内容是如何组织的,这时候很有必要查看class文件.方法有很多种,这里推荐使用JDK自带的javap工具. 首先建立如下源码: public c ...

  7. Linux性能测试分析命令_iostat

    iostat用于输出CPU和磁盘I/O相关的统计信息 iostat语法 用法:iostat [ 选项 ] [ <时间间隔> [ <次数> ]] 常用选项说明: -c:只显示系统 ...

  8. RxJava2.0学习笔记2 2018年7月3日 周二

    摘记: 1.map -- 转换  有些服务端的接口设计,会在返回的数据外层包裹一些额外信息,这些信息对于调试很有用,但本地显示是用不到的.使用 map() 可以把外层的格式剥掉,只留下本地会用到的核心 ...

  9. CSS强制换行和禁止换行代码

    一.强制换行      1.word-break: break-all;       只对英文起作用,以字母作为换行依据.      2.word-wrap: break-word;   只对英文起作 ...

  10. Microsoft® SQL Server® 2012 功能包

    Microsoft® SQL Server® 2012 功能包 http://www.microsoft.com/zh-cn/download/details.aspx?id=29065 Micros ...