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  是毫秒

命令源码分解
  1. void setCommand(redisClient *c) {
  2. int j;
  3. robj *expire = NULL;
  4. int unit = UNIT_SECONDS;
  5. int flags = REDIS_SET_NO_FLAGS;
  6. for (j = 3; j < c->argc; j++) {
  7. char *a = c->argv[j]->ptr;
  8. robj *next = (j == c->argc-1) ? NULL : c->argv[j+1];
  9. if ((a[0] == ‘n‘ || a[0] == ‘N‘) &&
  10. (a[1] == ‘x‘ || a[1] == ‘X‘) && a[2] == ‘\0‘) {
  11. flags |= REDIS_SET_NX;
  12. } else if ((a[0] == ‘x‘ || a[0] == ‘X‘) &&
  13. (a[1] == ‘x‘ || a[1] == ‘X‘) && a[2] == ‘\0‘) {
  14. flags |= REDIS_SET_XX;
  15. } else if ((a[0] == ‘e‘ || a[0] == ‘E‘) &&
  16. (a[1] == ‘x‘ || a[1] == ‘X‘) && a[2] == ‘\0‘ && next) {
  17. unit = UNIT_SECONDS;
  18. expire = next;
  19. j++;
  20. } else if ((a[0] == ‘p‘ || a[0] == ‘P‘) &&
  21. (a[1] == ‘x‘ || a[1] == ‘X‘) && a[2] == ‘\0‘ && next) {
  22. unit = UNIT_MILLISECONDS;
  23. expire = next;
  24. j++;
  25. } else {
  26. addReply(c,shared.syntaxerr);
  27. return;
  28. }
  29. }
  30. c->argv[2] = tryObjectEncoding(c->argv[2]);
  31. setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
  32. }

解析:line 8: a获取每个分割的命令参数的头指针  j从3开始, 如果命令如:

set key value 这种  中间的循环就不会做了, line 3-5变量的设置就是原始值

如果做了中间的循环:

首先由2种可能: ex 和Px 但是在写代码的时候  他就会注意到, 后面还会不会接其他的命令

最终就解析出来了   line33主要是防止Object Value是一个数字  看看能不能进行重新编码

Line 34:就调用通用的SetCommand。 这里实现了所有SET命令版本的入口

  1. void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
  2. long long milliseconds = 0; /* initialized to avoid any harmness warning */
  3. if (expire) {
  4. if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
  5. return;
  6. if (milliseconds <= 0) {
  7. addReplyError(c,"invalid expire time in SETEX");
  8. return;
  9. }
  10. if (unit == UNIT_SECONDS) milliseconds *= 1000;
  11. }
  12. if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
  13. (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL))
  14. {
  15. addReply(c, abort_reply ? abort_reply : shared.nullbulk);
  16. return;
  17. }
  18. setKey(c->db,key,val);
  19. server.dirty++;
  20. if (expire) setExpire(c->db,key,mstime()+milliseconds);
  21. 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命令  难度【*】

  1. int getGenericCommand(redisClient *c) {
  2. robj *o;
  3. if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
  4. return REDIS_OK;
  5. if (o->type != REDIS_STRING) {
  6. addReply(c,shared.wrongtypeerr);
  7. return REDIS_ERR;
  8. } else {
  9. addReplyBulk(c,o);
  10. return REDIS_OK;
  11. }
  12. }

get命名从Line 1 进入:  调用DB.C里的lookupKeyReadOrReply函数 如果不存在  直接返回OK。

如果不是REDIS_STRING类型: 则返回一个错误。

如果完全正确 Line 11:把结果返回给客户端

T_string.c  GETSET/INCR命令   难度【*】

这2个命令非常的实用

  1. void getsetCommand(redisClient *c) {
  2. if (getGenericCommand(c) == REDIS_ERR) return;
  3. c->argv[2] = tryObjectEncoding(c->argv[2]);
  4. setKey(c->db,c->argv[1],c->argv[2]);
  5. server.dirty++;
  6. }

这里可以看到 Line 2:  先调用get命令

然后line 4:进行setKey  覆盖掉以前的old key

这个命令可以完成那种复位计数器的功能:获得当前某个变量值 然后置空

INCR命令                                                                                                                                                   难度【**】

对key 存储的数字加1  当然他的key必须要存储的是数字哦

  1. void incrDecrCommand(redisClient *c, long long incr) {
  2. long long value, oldvalue;
  3. robj *o, *new;
  4. o = lookupKeyWrite(c->db,c->argv[1]);
  5. if (o != NULL && checkType(c,o,REDIS_STRING)) return;
  6. if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
  7. oldvalue = value;
  8. if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
  9. (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
  10. addReplyError(c,"increment or decrement would overflow");
  11. return;
  12. }
  13. value += incr;
  14. new = createStringObjectFromLongLong(value);
  15. if (o)
  16. dbOverwrite(c->db,c->argv[1],new);
  17. else
  18. dbAdd(c->db,c->argv[1],new);
  19. signalModifiedKey(c->db,c->argv[1]);
  20. server.dirty++;
  21. addReply(c,shared.colon);
  22. addReply(c,new);
  23. addReply(c,shared.crlf);
  24. }
  25. void incrCommand(redisClient *c) {
  26. incrDecrCommand(c,1);
  27. }

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

  1. void appendCommand(redisClient *c) {
  2. size_t totlen;
  3. robj *o, *append;
  4. o = lookupKeyWrite(c->db,c->argv[1]);
  5. if (o == NULL) {
  6. /* Create the key */
  7. c->argv[2] = tryObjectEncoding(c->argv[2]);
  8. dbAdd(c->db,c->argv[1],c->argv[2]);
  9. incrRefCount(c->argv[2]);
  10. totlen = stringObjectLen(c->argv[2]);
  11. } else {
  12. /* Key exists, check type */
  13. if (checkType(c,o,REDIS_STRING))
  14. return;
  15. /* "append" is an argument, so always an sds */
  16. append = c->argv[2];
  17. totlen = stringObjectLen(o)+sdslen(append->ptr);
  18. if (checkStringLength(c,totlen) != REDIS_OK)
  19. return;
  20. /* If the object is shared or encoded, we have to make a copy */
  21. if (o->refcount != 1 || o->encoding != REDIS_ENCODING_RAW) {
  22. robj *decoded = getDecodedObject(o);
  23. o = createStringObject(decoded->ptr, sdslen(decoded->ptr));
  24. decrRefCount(decoded);
  25. dbOverwrite(c->db,c->argv[1],o);
  26. }
  27. /* Append the value */
  28. o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
  29. totlen = sdslen(o->ptr);
  30. }
  31. signalModifiedKey(c->db,c->argv[1]);
  32. server.dirty++;
  33. addReplyLongLong(c,totlen);
  34. }

分2种: 如果没有含key 就直接加入

如果没有含key  则重新分配

line 32:sdscatlen() 将o->ptr的内容分配到append->ptr里。

T_string.c  MSET/MGET命令

简介:

一次性进行多次插入和取操作命令   是一个原子操作

void msetGenericCommand(redisClient *c, int nx) {

  1. int j, busykeys = 0;
  2. if ((c->argc % 2) == 0) {
  3. addReplyError(c,"wrong number of arguments for MSET");
  4. return;
  5. }
  6. /* Handle the NX flag. The MSETNX semantic is to return zero and don‘t
  7. * set nothing at all if at least one already key exists. */
  8. if (nx) {
  9. for (j = 1; j < c->argc; j += 2) {
  10. if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
  11. busykeys++;
  12. }
  13. }
  14. if (busykeys) {
  15. addReply(c, shared.czero);
  16. return;
  17. }
  18. }
  19. for (j = 1; j < c->argc; j += 2) {
  20. c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
  21. setKey(c->db,c->argv[j],c->argv[j+1]);
  22. }
  23. server.dirty += (c->argc-1)/2;
  24. addReply(c, nx ? shared.cone : shared.ok);
  25. }
  26. void msetCommand(redisClient *c) {
  27. msetGenericCommand(c,0);
  28. }

同样 入口是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操作  这个是实验室项目用的最多的,所以必须要很好的分析。

 

时间: 2024-10-05 23:28:42

redis 数据结构一 之t_string的相关文章

Redis 数据结构使用场景

Redis 数据结构使用场景 redis共有5种数据结构,每种的使用场景都是什么? 一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构,那每种数据结构的使用场景都是什么呢? String——字符串 Hash——字典 List——列表 Set——集合 Sorted Set——有序集合 下面我们就来简单说明一下它们各自

REdis数据结构服务器

Rdis和JQuery一样是纯粹为应用而产生的,这里记录的是在CentOS 5.7上学习入门文章: 1.Redis简介  Redis是一个key-value存储系统.和Memcached类似,但是解决了断电后数据完全丢失的情况,而且她支持更多无化的value类型,除了和string外,还支持lists(链表).sets(集合)和zsets(有序集合)几种数据类型.这些数据类型都支持push/pop.add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的. 2.Redis的

Redis数据结构之robj

本文及后续文章,Redis版本均是v3.2.8 我们知道一个database内的这个映射关系是用一个dict来维护的.dict的key固定用一种数据结构来表达,这这数据结构就是动态字符串sds.而value则比较复杂,为了在同一个dict内能够存储不同类型的value,这就需要一个通用的数据结构.针对不同的使用场景,这个通用的数据结构可以使用不同的数据结构实现,这样可以优化在不同场景下的效率.这个通用的数据结构就是robj(redisObject),也是本文主要探讨的redis中的对象是怎么实现

Redis 数据结构之dict(2)

本文及后续文章,Redis版本均是v3.2.8 上篇文章<Redis 数据结构之dict>,我们对dict的结构有了大致的印象.此篇文章对dict是如何维护数据结构的做个详细的理解. 老规矩还是打开Redis的源码,文件dict.c 一.dict数据结构的维护 1.dictCreate - 创建一个新的哈希表 /* Reset a hash table already initialized with ht_init(). * NOTE: This function should only b

Redis数据结构之intset

本文及后续文章,Redis版本均是v3.2.8 上篇文章<Redis数据结构之robj>,我们说到redis object数据结构,其有5中数据类型:OBJ_STRING,OBJ_LIST, OBJ_SET,OBJ_ZSET,OBJ_HASH.集合对象set有着广泛的实际业务应用场景,它包含的元素无序并且不能重复及集合间的交.并.差等基础的操作.本篇就来说说Redis暴露给我们使用的set集合对象的底层实现-intset. 其实,可以理解为有序整型集合 intset是一个由整数组成的有序集合,

读REDIS数据结构

一.DICT 主要有两个问题: 1.散列冲突,解决办法是拉链法 typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; } v; struct dictEntry *next; } dictEntry; next字段向后拉链 2.扩容时候的rehash,做类似于copy on write typedef struct dict { dictType *type; void *privd

Redis 数据结构与内存管理策略(下)

Redis 数据结构与内存管理策略(下) Redis 数据类型特点与使用场景 String.List.Hash.Set.Zset 案例:沪江团购系统大促 hot-top 接口 cache 设计 Redis 内存数据结构与编码 OBJECT encoding key.DEBUG OBJECT key 简单动态字符串(simple dynamic string) 链表(linked list) 字典(dict) 跳表(skip list) 整数集合(int set) 压缩表(zip list) Re

redis学习(二) redis数据结构介绍以及常用命令

redis数据结构介绍 我们已经知道redis是一个基于key-value数据存储的数据结构数据库,这里的key指的是string类型,而对应的value则可以是多样的数据结构.其中包括下面五种类型: 1.string 字符串 string字符串类型是redis最基础的数据存储类型.string是最基础的一种数据类型,其可以拓展为某种特定类型,例如普通文本,json字符串,二进制数据等等.就本质上来说,接下来要介绍的hash,list,set等其内部最基础的组成单位依然是string,只不过re

Redis学习系列六ZSet(有序列表)及Redis数据结构的过期

一.简介 ZSet可以说是Redis中最有趣的数据结构了,因为他兼具了Hash集合和Set的双重特性,也是用的最多的,保证了value值的唯一性的同时,,同时又保证了高性能,最主要的是还可以给每个Value设置Source(权重),那么我们就可以通过权重进行排序,这在业务上是非常常见的,比如很多地方需要,比如我们需要对所有用户的数学成绩进行排序.对英语等等地例子比比皆是,那么通过ZSet,你将会得到一个响应速度非常快的过程.下面会介绍. ZSet的内部原理是通过跳跃列表来实现的,这里还是不想说太