redis 数据结构一 之t_string
简介
REDIS有非常丰富的数据结构 以及建立在这数据结构上的操作,在源文件中主要集中在 T_hash.c /T_list.c /T_string.c/T_zset.c
可以说读懂了这4个源文件 大部分数据结构命令都比较清楚了。 先从T_string.c源文件开始读起:
T_string.c SET命令
命令简介
SET key value [EX seconds] [PX milliseconds] [NX|XX] 1)设置了Key Value的时间 有2种单位: 第一: EX 对应的是秒 第2: PX 是毫秒
命令源码分解
- void setCommand(redisClient *c) {
- int j;
- robj *expire = NULL;
- int unit = UNIT_SECONDS;
- int flags = REDIS_SET_NO_FLAGS;
- for (j = 3; j < c->argc; j++) {
- char *a = c->argv[j]->ptr;
- robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
- if ((a[0] == 'n' || a[0] == 'N') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
- flags |= REDIS_SET_NX;
- } else if ((a[0] == 'x' || a[0] == 'X') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
- flags |= REDIS_SET_XX;
- } else if ((a[0] == 'e' || a[0] == 'E') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
- unit = UNIT_SECONDS;
- expire = next;
- j++;
- } else if ((a[0] == 'p' || a[0] == 'P') &&
- (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
- unit = UNIT_MILLISECONDS;
- expire = next;
- j++;
- } else {
- addReply(c,shared.syntaxerr);
- return;
- }
- }
- c->argv[2] = tryObjectEncoding(c->argv[2]);
- setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
- }
解析:line 8: a获取每个分割的命令参数的头指针 j从3开始, 如果命令如:
set key value 这种 中间的循环就不会做了, line 3-5变量的设置就是原始值
如果做了中间的循环:
首先由2种可能: ex 和Px 但是在写代码的时候 他就会注意到, 后面还会不会接其他的命令
最终就解析出来了 line33主要是防止Object Value是一个数字 看看能不能进行重新编码
Line 34:就调用通用的SetCommand。 这里实现了所有SET命令版本的入口
- void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
- long long milliseconds = 0; /* initialized to avoid any harmness warning */
- if (expire) {
- if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
- return;
- if (milliseconds <= 0) {
- addReplyError(c,"invalid expire time in SETEX");
- return;
- }
- if (unit == UNIT_SECONDS) milliseconds *= 1000;
- }
- if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
- (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL))
- {
- addReply(c, abort_reply ? abort_reply : shared.nullbulk);
- return;
- }
- setKey(c->db,key,val);
- server.dirty++;
- if (expire) setExpire(c->db,key,mstime()+milliseconds);
- addReply(c, ok_reply ? ok_reply : shared.ok);
line 4到line 11 :是计算出需要多长时间
line 14- 15: lookupKeyWrite(c->db,key) != NULL) 查看是否有这个key 如果没有就写入
同样的:lookupKeyWrite(c->db,key) == NULL) 查看是否有这个key 如果有就写入
line 20: 调用db.c/setKey函数,就把相应的Key和value插入到dict[]中去了
line 21: dirty重新赋值
line22:调用db.c/setExpire函数 把时间键值插入到这个expire字典里了。 mstime()是计算出当前时间的长整形秒数。 至此 整个SET命令完成了~!
T_string.c GET命令 难度【*】
- int getGenericCommand(redisClient *c) {
- robj *o;
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
- return REDIS_OK;
- if (o->type != REDIS_STRING) {
- addReply(c,shared.wrongtypeerr);
- return REDIS_ERR;
- } else {
- addReplyBulk(c,o);
- return REDIS_OK;
- }
- }
get命名从Line 1 进入: 调用DB.C里的lookupKeyReadOrReply函数 如果不存在 直接返回OK。
如果不是REDIS_STRING类型: 则返回一个错误。
如果完全正确 Line 11:把结果返回给客户端
T_string.c GETSET/INCR命令 难度【*】
这2个命令非常的实用
- void getsetCommand(redisClient *c) {
- if (getGenericCommand(c) == REDIS_ERR) return;
- c->argv[2] = tryObjectEncoding(c->argv[2]);
- setKey(c->db,c->argv[1],c->argv[2]);
- server.dirty++;
- }
这里可以看到 Line 2: 先调用get命令
然后line 4:进行setKey 覆盖掉以前的old key
这个命令可以完成那种复位计数器的功能:获得当前某个变量值 然后置空
INCR命令 难度【**】
对key 存储的数字加1 当然他的key必须要存储的是数字哦
- void incrDecrCommand(redisClient *c, long long incr) {
- long long value, oldvalue;
- robj *o, *new;
- o = lookupKeyWrite(c->db,c->argv[1]);
- if (o != NULL && checkType(c,o,REDIS_STRING)) return;
- if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
- oldvalue = value;
- if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
- (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
- addReplyError(c,"increment or decrement would overflow");
- return;
- }
- value += incr;
- new = createStringObjectFromLongLong(value);
- if (o)
- dbOverwrite(c->db,c->argv[1],new);
- else
- dbAdd(c->db,c->argv[1],new);
- signalModifiedKey(c->db,c->argv[1]);
- server.dirty++;
- addReply(c,shared.colon);
- addReply(c,new);
- addReply(c,shared.crlf);
- }
- void incrCommand(redisClient *c) {
- incrDecrCommand(c,1);
- }
line28:命令入口 然后进入Line1:
line 5: 先找到key对应的value 先判断是不是STRING 是撤回, 然后判断能否转换成Longlong型数 如果能转换 就继续下面的工作: 如果递增 会导致数量越界 就返回。
修改了具体的数值
注意点: 如果o为NULL,则没有这个key 那么就会采用这个方案:dbAdd,如果含有 则改写
最后一个命令:line 21: 需要通知下 相关的被watch的Key, 如果被watched的key里有这个key 则那个key的相应标记位需要置为脏。 但不影响这里的操作
题外话 : 引入INCR的原因
试想 如果N客户端 都想对某个变量做自增的一个操作 如果没有INCR的话 只能是取回 在本地加一 然后在传上去 但是这样必须进行原子锁 所以是不行的 如果这样的事情交给服务器做,就可以避免这样的问题,对于客户端来讲只需要提供INCR命令,剩下的都是redis 进行流水线操作 就绝对能保持其自增效果 对于这个INCR在我们实验室的爬虫部分 关于多台主机爬取信息 怎么根据ID自增来插入表格 是一个非常好的解决方案~!
T_string.c APPEND命令 难度【**】
如果已经存在 就讲value加到key的末尾 如果不存在 就是简单的set key value
- void appendCommand(redisClient *c) {
- size_t totlen;
- robj *o, *append;
- o = lookupKeyWrite(c->db,c->argv[1]);
- if (o == NULL) {
- /* Create the key */
- c->argv[2] = tryObjectEncoding(c->argv[2]);
- dbAdd(c->db,c->argv[1],c->argv[2]);
- incrRefCount(c->argv[2]);
- totlen = stringObjectLen(c->argv[2]);
- } else {
- /* Key exists, check type */
- if (checkType(c,o,REDIS_STRING))
- return;
- /* "append" is an argument, so always an sds */
- append = c->argv[2];
- totlen = stringObjectLen(o)+sdslen(append->ptr);
- if (checkStringLength(c,totlen) != REDIS_OK)
- return;
- /* If the object is shared or encoded, we have to make a copy */
- if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
- robj *decoded = getDecodedObject(o);
- o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
- decrRefCount(decoded);
- dbOverwrite(c->db,c->argv[1],o);
- }
- /* Append the value */
- o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
- totlen = sdslen(o->ptr);
- }
- signalModifiedKey(c->db,c->argv[1]);
- server.dirty++;
- addReplyLongLong(c,totlen);
- }
分2种: 如果没有含key 就直接加入
如果没有含key 则重新分配
line 32:sdscatlen() 将o->ptr的内容分配到append->ptr里。
T_string.c MSET/MGET命令
简介:
一次性进行多次插入和取操作命令 是一个原子操作
void msetGenericCommand(redisClient *c, int nx) {
- int j, busykeys = 0;
- if ((c->argc % 2) == 0) {
- addReplyError(c,"wrong number of arguments for MSET");
- return;
- }
- /* Handle the NX flag. The MSETNX semantic is to return zero and don't
- * set nothing at all if at least one already key exists. */
- if (nx) {
- for (j = 1; j < c->argc; j += 2) {
- if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
- busykeys++;
- }
- }
- if (busykeys) {
- addReply(c, shared.czero);
- return;
- }
- }
- for (j = 1; j < c->argc; j += 2) {
- c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
- setKey(c->db,c->argv[j],c->argv[j+1]);
- }
- server.dirty += (c->argc-1)/2;
- addReply(c, nx ? shared.cone : shared.ok);
- }
- void msetCommand(redisClient *c) {
- msetGenericCommand(c,0);
- }
同样 入口是line 29 很简单:
进入line msetGenericCommand之后, Line 3 先看下是不是有参数错了, 如果所有参数和是个偶数 就证明错了
line 9-14: 如果是msetnx就是key不存在的时候能插入 如果存在不让插入 nx=1的话 就是调用msetnx命令
注意点:这里是原子操作,要么全面插入成功 要么全部失败,也就是说:如果中间有一个key存在 msetnx直接从17返回了
Line21-26:这里是真正的插入信息 每2个作为一组进行插入 然后就讲dirty更新
MGET命令基本上是一样的原理 再次忽略
t_string.c 基本上就是这些命令 ,但是奇怪的是getbit setbit的源码不在,这个bit 2进制数组需要在其他的文件中应该会出现,出现在其他源文件中放在其他文件中分析。
下篇预告: List操作 这个是实验室项目用的最多的,所以必须要很好的分析。
redis 数据结构一 之t_string的更多相关文章
- Redis 数据结构使用场景
转自http://get.ftqq.com/523.get 一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的 ...
- Redis数据结构
Redis数据结构 Redis数据结构详解(一) 前言 Redis和Memcached最大的区别,Redis 除啦支持数据持久化之外,还支持更多的数据类型而不仅仅是简单key-value结构的数据 ...
- Redis数据结构底层知识总结
Redis数据结构底层总结 本篇文章是基于作者黄建宏写的书Redis设计与实现而做的笔记 数据结构与对象 Redis中数据结构的底层实现包括以下对象: 对象 解释 简单动态字符串 字符串的底层实现 链 ...
- Redis 数据结构与内存管理策略(上)
Redis 数据结构与内存管理策略(上) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...
- Redis 数据结构与内存管理策略(下)
Redis 数据结构与内存管理策略(下) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...
- Redis数据结构之intset
本文及后续文章,Redis版本均是v3.2.8 上篇文章<Redis数据结构之robj>,我们说到redis object数据结构,其有5中数据类型:OBJ_STRING,OBJ_LIST ...
- Redis数据结构之robj
本文及后续文章,Redis版本均是v3.2.8 我们知道一个database内的这个映射关系是用一个dict来维护的.dict的key固定用一种数据结构来表达,这这数据结构就是动态字符串sds.而va ...
- Redis 数据结构之dict(2)
本文及后续文章,Redis版本均是v3.2.8 上篇文章<Redis 数据结构之dict>,我们对dict的结构有了大致的印象.此篇文章对dict是如何维护数据结构的做个详细的理解. 老规 ...
- Redis 数据结构之dict
上篇文章<Redis数据结构概述>中,了解了常用数据结构.我们知道Redis以高效的方式实现了多种数据结构,因此把Redis看做为数据结构服务器也未尝不可.研究Redis的数据结构和正确. ...
随机推荐
- @@identity的使用
-- ============================================= -- Author: Qiuhua,Huang -- Create date: 08/09/2012 ...
- PHPCMS开启伪静态和织梦开启伪静态的优缺点比较
PHPCMS和织梦CMS都是国内比较出名的PHP语言的CMS程序系统,他们拥有比较完善的网站内容管理功能,也比较注重网站优化方面的功能,深受很多网站建设者的喜爱. 这两套系统,都有启用伪静态的功能,在 ...
- Java设计模式 工厂模式概念解析
定义:工厂模式专门负责将大量有共同接口的类实例化.工厂模式可以动态决定将哪一个类实例化,而不必事先知道每次要实例化哪一个类. 三种形态: 简单工厂模式:又称静态工厂方法 工厂方法模式:又称多态性工厂模 ...
- 如何在脚本中获取进程ID(PID)
我想要知道运行中脚本子shell的进程id.我该如何在shell脚本中得到PID. 当我在执行shell脚本时,它会启动一个叫子shell的进程.作为主shell的子进程,子shell将shell脚本 ...
- PHP注册审核做法
1.注册页面 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www. ...
- gtp转换mbr
http://wenku.baidu.com/link?url=P_t0U8Q-LIUdxVGHBefipAvbV6fg3jnX8hc8ugaRoo5WWd8GJePO8sBbtLON15gvOZh4 ...
- keep the bar green to keep the code clean——Junit详解(一)
测试用例 单元测试时每个开发人员必需掌握的,是保证开发过程中代码的准确性,无误性,保证代码质量.敏捷开发模式是先根据用户需求写测试用例,考虑基本所有用户所需要的情况,再写实现方法.单元测试有很多种,当 ...
- hibernate的1+n
对数据库访问还是必须考虑性能问题的, 在设定了1 对多这种关系之后, 查询就会出现传说中的n +1 问题. 1 )1 对多,在1 方,查找得到了n 个对象, 那么又需要将n 个对象关联的集合取出,于是 ...
- 黑马程序员——C语言基础 流程控制 选择结构和循环结构
---恢复内容开始--- Java培训.Android培训.iOS培训..Net培训.期待与您交流! (以下内容是对黑马苹果入学视频的个人知识点总结) (一)流程控制 1> 顺序结构:默认的流程 ...
- js实现当前导航菜单高亮显示
为了增加用户体验度,增加网页的易用性和美观度,往往需要把当前导航菜单以特殊方式显示,通常是高亮显示或有不同于其它菜单的背景,有两种方法可以实现,第一种是用纯css来实现,二是用js辅助css来实现,两 ...