集美阅读大全是一个以文章句子为主题的在线阅读网站。内含有各种经典好文章,爱情美文,诗歌散文,情感句子说说,范文资料等。读好文章,尽在集美阅读大全!!!
当前位置:集美阅读大全 >杂文 > 正文

【redis】redis基础命令,分布式锁,缓存问题学习大集合


写在前面

Redis是一个高速的内存数据库,它的应用十分广泛,可以说是服务端必学必精的东西。然而,学以致用,无用则无为。学了的东西必须反复的去用,去实践,方能有真知。这篇文章记录了我在redis学习过程中的笔记、理解和实践,仅供参考。

本章介绍redis基础中的基础,常用命令的使用和效果。

如果你已经很厉害了,不需要看基础命令,你可以跳转:

【redis】redis应用场景,缓存的各种问题解析

【redis】分布式锁实现,与分布式定时任务

string

string类型是redis中最常见的类型了,通过简单的set、get命令就可以对这个数据结构做增删操作,应该也是redis最大众的类型之一,存json、存自增数值、甚至缓存图片。 string的底层是redis作者自定义的一个叫SDS的struct。长下面这样:

redis是使用c语言实现的

typedef char *sds;  // 省略  struct __attribute__ ((__packed__)) sdshdr64 {      uint64_t len; /* used */      uint64_t alloc; /* excluding the header and null terminator */      unsigned char flags; /* 3 lsb of type, 5 unused bits */      char buf[];  };

 

  • len 记录了字符串的长度
  • alloc 表示字符串的最大容量(不包含最后多余的那个字节)。
  • flags 总是占用一个字节。其中的最低3个bit用来表示header的类型。源码中的多个header是用来节省内存空间的。

这里有一个疑问,为什么作者要自定义一个sds而不是直接用c语言的字符串呢?

  1. 时间复杂度要求 redis的数据结构设计总是基于最优复杂度方案的,对每一个点的时间、空间复杂度要求非常高,这一点c语言的string就已经不满足需求了,因为c自带的字符串并不会记录自身长度信息,所以每次获取字符串长度的时间复杂度都是o(n),所以redis作者设计SDS时,有一个len字段,记录了字符串的长度,这样每次获取长度时的时间复杂度就是O(1)了。

  2. 缓冲区溢出问题 其实也是c语言不记录本身长度带来的问题,当拼接字符串的时候,例如 hello + world 因为c不记录长度,所以在拼接字符的时候需要手动为hello分配五个内存空间,然后才能+world,如果忘记分配内存,那么就会产生缓冲区溢出,而redis的解决方案是在SDS中分别记录len和alloc,表示当前字符串长度和最大容量,这样当进行字符串拼接的时候api直接去判断最大容量是否满足,满足就直接插入,不满足则对 char * 做一次扩容,然后插入,减少了人为出错的概率,并且可以对alloc适当的进行空间预先分配,减少扩容次数,例如在创建字符串hello时,完全可以将alloc长度设置10,这样在加入world时直接放进去就ok了。

  3. 实现了c语言字符串的识别特性,复用了c语言自带的字符串函数 传统的c语言使用的是n+1的char数组来表示长度n的字符串的,然后在n长度最后加上一个 , 所以redis的sds在设计的时候也加上了这个,这样可以复用部分c语言字符串的函数。

  4. 二进制安全 c字符串中的字符必须符合某种编码,比如 ASCII 并且除了字符串的末尾之外,字符串里面不能包含空字符(这里空字符指的是空()不是空格、换行之类的字符),主要是不能存储二进制的图片、视频、压缩文件等内容,而我们知道redis是可以用来缓存图片二进制数据的。因为redis记录了字符长度。c没有记录长度的时候遇到就认为读到字符结尾了。

可以看出,c语言中字符串没有记录长度是一个比较麻烦的事儿,如果没有记录长度就必须用占位符确定字符末尾,导致二进制不安全。如果没有记录长度就必须每次统计长度,导致时间复杂度陡增。如果没有记录长度在分割字符串、拼接字符串时麻烦也不少。所以—总的来说,在设计字符串的时候,不要忘了记录长度。

set命令

  • set [key] [value]

set一个key的value值,这个值可以是任意字符串。例如:

set redis:demo helloRedis  > OK  get redis:demo  > "helloRedis"

 

  • set [key] [value] [NX] [EX|PX]

set还可以指定另外两个参数 [NX] 表示 SET if Not eXists , 指定这个参数就是告诉redis,如果key不存在才set。 [EX|PX] 这个参数表示超时时间,ex表示秒数,px表示毫秒数,一般redis通用的表示时间单位是 秒

set redis:demo:nxex helloRedis NX EX 20  > OK  set redis:demo:nxex hellostring NX EX 20  > (nil) // 设置失败

 

这里有一个值得注意的点是,set nx是跟普通的set互通的 ,什么意思呢? 就是:

set redis:demo:nxex a  > OK  set redis:demo:nxex b NX EX 20  > (nil) // 普通的set在第二次设置nx的时候依然会设置失败  del redis:demo:nxex  > OK  set redis:demo:nxex a NX EX 20  > OK  set redis:demo:nxex b  > OK // 就算是nx设置的值,在普通set下依然会成功覆盖,并且丢失nx和ex的作用

 

  • mset [key] [value] [key] [value] …

批量设置key value,可以批量设置一堆key,并且它是原子的,也就是这些key要么全部成功,要么全部失败.

请注意,mset是不可以指定过期时间和nx的,如果你希望批量设置key并且有过期时间,那么你最好自己写lua脚本来解决

mset a 1 b 2 c 3 NX EX 20  > (error) ERR wrong number of arguments for MSET

 

  • getset [key] [value]

set之前先get,返回set之前的值

set redis:getset:demo hello  > ok  getset redis:getset:demo world  > "hello"  get redis:getset:demo  > "world"

 

ps这个命令一般用来检查set之前的值是否正常 注意这个也不能加nx和ex等属性

get 命令

  • get [key]

获取一个字符串类型的key的值,如果键 key 不存在, 那么返回特殊值 nil ; 否则, 返回键 key 的值。

set redis:get:demo hello  > ok  get redis:get:demo  > "hello"  del redis:get:demo  > (integer) 1  get redis:get:demo  > (nil)

 

  • strlen [key]

获取key字符串的长度

set redis:get:demo hello  > ok  strlen redis:get:demo  >  (integer) 5

 

  • mget [key] [key] …

批量获取key的值,返回一个list结构

mset a 1 b 2  > ok  mget a b  >  (1) "1" (2) "2"

 

操作命令

  • append [key] [value]

这个命令就是用来拼接字符串的

set redis:append:demo hello  > ok  append redis:append:demo world  >  (integer) 10 // 返回了append之后的字符串的总长度,也就是上面说的sds中的len字段,这时候这个key的free也已经被扩容  get redis:append:demo  > hello world

 

注意,当key不存在,append命令依然会成功,并且会当作key是一个字符串来拼接

integer

在redis中的integer类型是存储为字符串对象,通过编码的不同来表示不同的类型

set redis:int:demo 1  > OK  type redis:int:demo  > string // type依然是string  object encoding redis:int:demo  > "int" // 但是编码现在是int

 

这里也有一个注意的点,就是redis是不支持任意小数点的,例如你set a 0.5会被存储为embstr编码,这时候对它使用incr和decr会报错

  • incr [key]

将key自增1

set redis:int:demo 1  > OK  incr redis:int:demo  > (integer) 2  set redis:int:demo 0.5  > OK  incr redis:int:demo  > (error) ERR value is not an integer or out of range

 

  • decr [key]

将key自减1 是可以减到负数的

set redis:int:demo 1  > OK  decr redis:int:demo  > (integer) 0  decr redis:int:demo  > (integer) -1

 

  • incrby [key] [integer]

将key自增指定的数字

set redis:int:demo 1  > OK  incrby redis:int:demo 2  > (integer) 3

 

  • decrby [key] [integer]

将key自减指定的数字

set redis:int:demo 1  > OK  decrby redis:int:demo 2  > (integer) -1

 

有趣的实验

用decrby减去-1会是加法的效果吗?

set redis:int:demo 1  > OK  decrby redis:int:demo -2  > (integer) 3

 

答案是会增加。

 

hash

hash从源码上看,底层在redis中其实叫dict(字典)

看一个插入函数

dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) // addraw  {      long index;      dictEntry *entry;      dictht *ht;        if (dictIsRehashing(d)) _dictRehashStep(d); // 判断是否正在rehash        /* Get the index of the new element, or -1 if       * the element already exists. */      if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1) // 通过hash算法,得到key的hash值,如果是-1则返回null          return NULL;        /* Allocate the memory and store the new entry.       * Insert the element in top, with the assumption that in a database       * system it is more likely that recently added entries are accessed       * more frequently. */       // 判断是否正在rehash 将元素插入到顶部      ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];       entry = zmalloc(sizeof(*entry));      entry->next = ht->table[index];      ht->table[index] = entry;      ht->used++;        /* Set the hash entry fields. */      dictSetKey(d, entry, key);      return entry;  }

 

字典和hash表的实现都大同小异 可以看到基本上原理是使用hash算法加桶(table),通过拉链法解决hash冲突,当每个槽位的平均容积大于1:1触发rehash等操作。

set命令

  • hset [key] [field] [value]

将哈希表 hash 中一个key的 field 的值设置为 value 。 如果给定的哈希表key并不存在, 那么一个新的哈希表key将被创建并执行 HSET 操作。 如果域 field 已经存在于哈希表中, 那么它的旧值将被新值 value 覆盖。

hset redis:hash:demo com redis  > (integer) 1  hset redis:hash:demo com java // 设置同样的field将更新field 但返回是0  > (integer) 0  hget redis:hash:demo com  > "java"

 

  • hmset [key] [field] [value] …

批量设置 hash 中一个key的field值为value 如果不存在,则新建再插入。

hmset redis:hash:demo com redis lan java  > OK // 这里就不再是返回integer了,而是返回了ok  hget redis:hash:demo com  > "redis"

 

  • hsetnx [key] [field] [value]

这个命令与string中的nx参数是一样的行为,即只有当field不存在key上时,field的设置才生效,否则set失败 特别注意,这里第二次nx设置时返回的既不是null也不是报错,而是返回了0,这里比较坑一点,所以要在hash中使用hsetnx,你可以尝试使用lua脚本实现

hsetnx redis:hash:demo com redis  > (integer) 1  hsetnx redis:hash:demo com java  > (integer) 0 // 既不是null也不是报错  hget redis:hash:demo com  > "redis" // 第二次设置未生效

 

get命令

  • hget [key] [field]

get一个key的field的值,key或field不存在时都返回为nil

hset redis:hash:demo com redis  > (integer) 1  hget redis:hash:demo com  > "redis"  hget redis:hash:demo empty  > (nil)

 

  • hmget [key] [field1] [field2] …

批量获取field的值,这个值返回的是一个list

hmset redis:hash:demo com redis lan java  > OK  hmget redis:hash:demo com lan  > 1) "redis"  > 2) "java"

 

  • hlen [key]…

获取key中的field数量

hmset redis:hash:demo com redis lan java  > OK  hlen redis:hash:demo  > (integer) 2

 

  • hkeys [key]

获取key中的所有field的key,返回的是一个list

hmset redis:hash:demo com redis lan java  > OK  hkeys redis:hash:demo  > 1) "com"  > 2) "lan"

 

  • hvals [key]

获取key中的所有field的value,返回的是一个list

hmset redis:hash:demo com redis lan java  > OK  hvals redis:hash:demo  > 1) "redis"  > 2) "java"

 

  • hgetall [key]

获取key中的所有的东西,返回的是一个list,按 field,value,field,value的顺序排列

hmset redis:hash:demo com redis lan java  > OK  hgetall redis:hash:demo  > 1) "com"  > 2) "redis"  > 3) "lan"  > 4) "java"

 

  • hexists [key] [field]

判断key中的field是否存在, 返回integer,1表示存在 ,0 表示不存在

hmset redis:hash:demo com redis lan java  > OK  hexists redis:hash:demo com  > (integer) 1 // 1表示存在

 

操作命令

  • hincrby [key] [field] [integer]

与string的incrby表现一致,将key中的field自增一个integer值, 字符和带小数点不可用

hset redis:hash:demo inta 1  > (integer) 1  hincrby redis:hash:demo inta 2  > (integer) 3  // 同样的,可以给定一个负数,这样就变成自减了  hincrby redis:hash:demo inta -2  > (integer) 1

 

 

list

 

基础命令

  • lpush [key] [value1] [value2] …

将多个value插入一个key,这里注意lpush和rpush的区别,lpush是从list的左边插入数据,rpush则是从右边。

rpush redis:list:demo 1 2 3  > (integer) 3  // 使用lrange查找  lrange redis:list:demo 0 -1  > 1) "3"  > 2) "2"  > 3) "1"   // 这里对应的值是从左往右插入的

 

  • rpush [key] [value1] [value2] …

将多个value插入一个key,这里注意lpush和rpush的区别,lpush是从list的左边插入数据,rpush则是从右边。

lpush redis:list:demo 1 2 3  > (integer) 3  // 使用lrange查找  lrange redis:list:demo 0 -1  > 1) "1"  > 2) "2"  > 3) "3"   // 这里对应的值是从右往左插入的

 

注意lpush和rpush都是在key不存在的时候,自动创建一个类型list的key,而当这个key已存在但类型不是list时,命令报错

del redis:list:demo // 删掉确保不存在  > (integer) n  type redis:list:demo  > none  lpush redis:list:demo 1 2 3  > (integer) 3  type redis:list:demo  > list // 自动创建了key并且类型是list  set redis:string:demo hello  > OK  lpush redis:string:demo 1 2 3  > (error) WRONGTYPE Operation against a key holding the wrong kind of value // key已经存在了但不是list类型

 

  • lrange [key] [start] [end]

读取一个list,从start下标开始end下标结束,end可以设置为负数

lpush redis:list:demo 1 2 3  > (integer) 3  lrange redis:list:demo 0 1  > 1) 3  > 2) 2  lrange redis:list:demo 0 -1  > 1) "3"  > 2) "2"  > 3) "1"  lrange redis:list:demo 0 -2  > 1) "3"  > 2) "2"

 

  • lpushx [key] [value]

将单个value插入一个类型为list且必须存在的key 如果key不存在,返回0,并不会报错,lpushx是从list的左边插入数据,rpushx则是从右边。

lpushx redis:list:demo 4  > (integer) 4  lpushx empty:key 1  > (integer) 0

 

  • rpushx [key] [value]

将单个value插入一个类型为list且必须存在的key 如果key不存在,返回0,并不会报错,lpushx是从list的左边插入数据,rpushx则是从右边。

rpushx redis:list:demo 5  > (integer) 5  rpushx empty:key 1  > (integer) 0

 

  • rpoplpush [source list] [destination list]

rpoplpush一个命令同时有两个动作,而且是原子操作,有两个参数

  1. 将列表 source 中的最后一个元素(最右边的元素)弹出,并返回给客户端。
  2. 将 source 弹出的元素插入到列表 destination ,作为 destination 列表的的头元素(也就是最左边的元素)

简单来说就是从list:a取出一个元素丢到list:b

例如 list:a = 1 2 3 list:b = 4 5 6

执行rpoplpush a b 之后:

list:a = 1 2 list:b = 3 4 5 6

返回客户端被操作的数 3

rpush list:a 1 2 3  > (integer) 3  rpush list:b 4 5 6  > (integer) 3  rpoplpush list:a list:b  > "3" // 返回客户端被操作的数  // 查看执行后的情况  lrange list:a 0 -1  > 1) "1"  > 2) "2"  lrange list:b 0 -1  > 1) "3"  > 2) "4"  > 3) "5"  > 4) "6"

 

  • lindex [key] [index]

这个命令简单实用,获取key的index下标的元素,不存在返回nil

lindex redis:list:demo 0  > "4"  lindex redis:list:demo 999  > (nil)

 

  • lset [key] [index] [value]

直接设置key的index的value

lset redis:list:demo 0 5  > OK  lindex redis:list:demo 0  > "5"

 

队列和栈

因为list提供的命令的便利性和多样性,可以实现很多种数据结构,用的最多的就是队列和栈两个地方了,通过不同的方法分支成各种不同类型的队列,例如双端队列,优先级队列等。

  • lpop [key] [timeout]

移除并返回列表 key 的左边第一个元素,当 key 不存在时,返回 nil。

del redis:list:demo  > (integer) n  lpush redis:list:demo 1 2 3  > (integer) 3  lpop redis:list:demo  > "3" // 从左边取出的数  lrange redis:list:demo 0 -1  > 1) "2" // 删掉了最左边的3  > 2) "1"

 

  • rpop [key][timeout]

移除并返回列表 key 的右边第一个元素,当 key 不存在时,返回 nil。

del redis:list:demo  > (integer) n  lpush redis:list:demo 1 2 3  > (integer) 3  rpop redis:list:demo  > "1" // 从右边取出的数  lrange redis:list:demo 0 -1  > 1) "3" // 删掉了最右边的1  > 2) "2"

 

  • blpop [key] [key …] [timeout]

阻塞式的lpop,它可以设置多个key和一个timeout,将在这多个key里面选择一个列表不为空的key,lpop一个值出来,timeout可以指定一个超时时间,超过将会断开链接。

什么是阻塞式呢?

就是说这个操作是需要等待的,可以理解为下面的伪代码:

while ((n = list.lpop()) != null) {      return n;  }

 

就是说如果list的lpop取出不为null时就立刻返回,否则就一直循环了。

如果timeout指定为0则表示没有超时时间,一直等待

下面的示例请打开两个终端窗口

// terminal a  lpush redis:list:demo 1 2 3  > (integer) 3  blpop redis:list:demo 0 // 0 表示一直等待  > "3"  blpop redis:list:demo 0 // 0 表示一直等待  > "2"  blpop redis:list:demo 0 // 0 表示一直等待  > "1"  lrange redis:list:demo 0 -1  > (nil) // 此时list已经空了  blpop redis:list:demo 0 // 会一直等待list有新的命令插入    // 等待terminal b    > 1) "redis:list:demo" 等待后返回的结果  > 2) "4"  > (18.83s)  // terminal b  lpush redis:list:demo 4  > (integer) 1 // terminal a 会获取到这个4

 

可以看到最后一步,当terminal a 最终等到terminal b,push了一个值之后,返回的数据与正常pop的数据不一样

  • brpop [key] [key …] [timeout]

参考blpop。基本行为一致,只是brpop是从list的右侧pop,而blpop是左侧

  • brpoplpush [source list] [destination list] [timeout]

brpoplpush 是 rpoplpush的阻塞版本,你可以直接参考上面rpoplpush命令的解释,只是rpop变成了brpop,多了等待这一步。

分割

  • ltrim [key] [start] [end]

对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

注意,这里不要搞反了,是将start和end中间的保留,删除其余的

del redis:demo:list  > (integer) n  lpush redis:demo:list 1 2 3 4  > (integer) 4  ltrim redis:demo:list 1 2 // 只需要1到2  > OK  lrange redis:demo:list 0 -1  > 1) "3"  > 2) "2"

 

  • lrem [key] [count] [value]

移除count个与value相等的元素。

del redis:demo:list  > (integer) n  lpush redis:demo:list 1 2 2 3 4  > (integer) 5  lrem redis:demo:list 1 2 // 移除1个2  > OK  lrange redis:demo:list 0 -1  > 1) "4"  > 2) "3"  > 3) "2"  > 4) "1"

 

tips,使用这个命令,你可以配合lua脚本做一个不重复的list, 就是每次在push一个value之前先lrem一下这个value

del redis:demo:list  > (integer) n  lrem redis:demo:list 1 a // 先检查删除  > (integer) 0  lpush redis:demo:list a // 再push  > (integer) 1

 

 …持续更新

 

github: https://github.com/294678380/redis-lerning

hash

您可能感兴趣的文章

  • redis是什么语言开发的
  • Redis进阶应用:Redis+Lua脚本实现复合操作
  • Redis进阶应用:Redis+Lua脚本实现复合操作 – 宜信技术学院的个人空间
  • 一文了解:Redis事务
  • 跟着大彬读源码 – Redis 7 – 对象编码之简单动态字符串
  • php join的用法
  • php的闭包是干嘛的
  • php中define的用法

未经允许不得转载:杂烩网 » 【redis】redis基础命令,分布式锁,缓存问题学习大集合

课后答案张九龄《望月怀远》阅读答案及全诗翻译赏析

望月怀远张九龄海上生明月,天涯共此时。情人怨遥夜,竟夕起相思。灭烛怜光满,披衣觉露滋。不堪盈手赠,还寝梦佳期。注释⑴怀远:怀念远方的亲人。⑵最前面两句:辽阔无边的大海上升起一轮明月,使人想起了远在天涯……
2023-11-22 04:53暂无评论阅读详情

课后答案王安石《次韵唐公三首其三旅思》阅读答案

次韵唐公三首其三旅思王安石此身南北老,愁见问征途。地大蟠三楚,天低入五湖。看云心共远,步月影同孤。慷慨秋风起,悲歌不为鲈②。注:①张壤,字唐公,北宋嘉佑六年契丹国母生辰使,王安石友人。②《晋书&mid……
2023-11-22 04:52暂无评论阅读详情

笔记心得各级干部学习执法为民心得体会

  “各级干部都要牢固树立全心全意为人民服务的思想和真心实意对人民负责的精神,做到心里装着群众,凡事想着群众,工作依靠群众,一切为了群众。要坚持权为民所用,情为民所系,利为民所谋,为群众诚……
2023-11-22 04:12暂无评论阅读详情

笔记心得寒假大学生社会实践心得体会

  自从走进了大学,就业问题就似乎总是围绕在我们的身边,成了说不完的话题。在现今社会,招聘会上的大字报都总写着“有经验者优先”,可还在校园里面的我们这班学子社会经验又会拥有多少……
2023-11-22 04:08暂无评论阅读详情

协议书济南市某美容院转让协议第2篇

  __________美容院根据中华人民共和国国务院劳动法规和________市私营企业劳动管理实施办法,结合本美容院经营的具体所需今制订此劳动合同书。  双……
2023-11-22 02:36暂无评论阅读详情

剧本劳模宣传短剧剧本《阿咪也想当劳模》

  1、机械厂门卫处,日,外。  清早,机械厂班长李玉伟开着别克赛欧小汽车驶进厂区,门卫室内的保安一边按开电动门,一边朝李玉伟摆手。  李玉伟:(摇下车窗,笑着打招呼)小秦,早。  保安小秦:(笑着)……
2023-11-22 02:11暂无评论阅读详情

教程灰雀说课稿

灰雀说课稿  灰雀说课稿(一):  《灰雀》说课稿  一、说教材  《灰雀》是义务教育课程标准实验教科书,小学语文第五册第二单元的一篇讲读课文。这篇课文记叙了列宁在莫斯科郊外养病期间爱护灰雀的故事。列……
2023-11-22 00:41暂无评论阅读详情

课件“吴隐之字处默,濮阳鄄城人”阅读答案及原文

吴隐之字处默,濮阳鄄城人。美姿容,善谈论,博涉文史,以儒雅标名。弱冠而介立,有清操,虽儋石无储,不取非其道。事母孝谨,及其执丧,哀毁过礼。与太常韩康伯邻居,康伯母,贤明妇人也,每闻隐之哭声,辍餐投箸,……
2023-11-22 00:38暂无评论阅读详情

标签