redis深度历险学习笔记

redis深度历险这本书已经看完一遍了,但是记性不太好,看完就忘,所以准备写一个笔记,以后看笔记就好了。

./redis-server

./redis-cli -h ip -p port

目录

第一章 基础和应用篇

1.1 5种基础结构

1.1.1 string

1.1.2 list

1.1.3 hash

1.1.4 set

1.1.5 zset

1.2 分布式锁

1.3 延时队列



第一章 基础和应用篇

1.1 5种基础结构

1.1.1 string

字符串string是Redis最简单的数据结构,内部是一个字符数组。Redis所有的数据结构的key都是字符串,差异就是value的结构不一样。字符串长度小于1M时,成倍扩容,多于1M时一次多扩1M,字符串最大长度512M。

实操一下: 

127.0.0.1:6379> set today 1204
OK
127.0.0.1:6379> get today
"1204"

set redis基本命令之一,在redis放置一个值,语法为 set key value,上面的代码today就是key,1204就是value。

批量操作:

127.0.0.1:6379> mset name1 Kobe name2 James name3 Jordan
OK
127.0.0.1:6379> mget name1 name2 name3
1) "Kobe"
2) "James"
3) "Jordan"

设置过期时间:

127.0.0.1:6379> expire today 5
(integer) 1
127.0.0.1:6379> get today
(nil)

5s之后获取设置过的值,已经获取不到了。

setex 相当于set+expire

127.0.0.1:6379> setex today 5 1204
OK
127.0.0.1:6379> get today
(nil)

setnx 如果不存在就执行set操作,第二次执行setnx时,因为已经有值为today的key了,所以创建失败。

127.0.0.1:6379> setnx today 1204
(integer) 1
127.0.0.1:6379> setnx today 1205
(integer) 0

计数

127.0.0.1:6379> set age 31
OK
127.0.0.1:6379> incr age
(integer) 32
127.0.0.1:6379> incrby age 5
(integer) 37
127.0.0.1:6379> incrby age -5
(integer) 32
127.0.0.1:6379> set max 9223372036854775807
OK
127.0.0.1:6379> incrby max
(error) ERR wrong number of arguments for 'incrby' command

超出最大值会报错。

1.1.2 list

Redis里面的list相当于java的linkedlist,并且是双向链表,同时支持前向遍历和后向遍历,我(bu)的(zhi)理(dao)解(zhe)是(me)既(li)是(jie)栈(you)又(mei)是(you)队(mao)列(bing)。

右进左出

127.0.0.1:6379> rpush best java python go
(integer) 3
127.0.0.1:6379> llen best
(integer) 3
127.0.0.1:6379> lpop best
"java"
127.0.0.1:6379> lpop best
"python"
127.0.0.1:6379> lpop best
"go"
127.0.0.1:6379> lpop best
(nil)

右进右出

127.0.0.1:6379> rpush best java python go
(integer) 3
127.0.0.1:6379> rpop best
"go"
127.0.0.1:6379> rpop best
"python"
127.0.0.1:6379> rpop best
"java"
127.0.0.1:6379> rpop best
(nil)

lindex 复杂度O(n)慎用

lrange best 0 -1复杂度O(n)慎用, -1表示为倒数第一个元素

ltrim best 1 -1 相当于保留区间内的元素(闭区间),区间外的元素都不要了。

127.0.0.1:6379> rpush best java python go
(integer) 3
127.0.0.1:6379> lindex best 1
"python"
127.0.0.1:6379> lrange best 0 -1
1) "java"
2) "python"
3) "go"
127.0.0.1:6379> ltrim best 1 -1
OK
127.0.0.1:6379> lrange best 0 -1
1) "python"
2) "go"
127.0.0.1:6379> ltrim best 1 0
OK
127.0.0.1:6379> llen best
(integer) 0

1.1.3 hash

Redis里面的hash相当于java里面的HashMap,如下图所示,它是无序字典,内部存储了很多键值对,结构是数组加链表。

 hash既然是数组就会涉及到一个扩容的问题,rehash是一个很耗时的过程,redis优化了这一过程。渐进式rehash会同时保留两个hash结构,查询时会同时查询两个hash结构,后续通过定时任务和hash操作指令,一点一点地搬迁,搬迁完成之后就会使用新的hash结构。

如下图所示,数组hash碰撞时,碰撞的元素就会以链表的形式存储。

 hset key field value [field value ...]

hlen key 

hget key field

hset 相同key相同field 即为更新返回0

127.0.0.1:6379> hset friends "Kobe" "LA"
(integer) 1
127.0.0.1:6379> hset friends "Yao" "Huston"
(integer) 1
127.0.0.1:6379> hset friends "Wade" "Miami"
(integer) 1
127.0.0.1:6379> hgetall friends
1) "Kobe"
2) "LA"
3) "Yao"
4) "Huston"
5) "Wade"
6) "Miami"
127.0.0.1:6379> hlen friends
(integer) 3
127.0.0.1:6379> hget friends Kobe
"LA"
127.0.0.1:6379> hset friends Wade Chicago
(integer) 0
127.0.0.1:6379> hget friends Wade
"Chicago"
127.0.0.1:6379> hmset team "YAO" Rocket "Kobe" Lakers
OK

1.1.4 set

set相当于 Java中的HashSet,它内部的键值是无需的,唯一的。和HashSet一样内部维护了一个map,value为空。

set可以维护秒杀活动中奖用户,因为set具有唯一性,可以保证同一用户不会中奖两次。

sadd key value[key value ...] 向set中添加元素

smembers key 获取set中元素

sismember key value 判断set中是否包含某元素

scard key 返回set中元素个数

spop key [count] 弹出set中count个元素(默认一个)

127.0.0.1:6379> sadd languages Java
(integer) 1
127.0.0.1:6379> sadd languages c
(integer) 1
127.0.0.1:6379> smembers languages
1) "c"
2) "Java"
127.0.0.1:6379> sadd languages c
(integer) 0
127.0.0.1:6379> sadd languages c++ c#
(integer) 2
127.0.0.1:6379> sismember languages c
(integer) 1
127.0.0.1:6379> scard languages 
(integer) 4
127.0.0.1:6379> spop languages
"c"
127.0.0.1:6379> spop languages 2
1) "Java"
2) "c#"
127.0.0.1:6379> smembers languages
1) "c++"

1.1.5 zset

zset类似于SortedSet,内部实现使用跳跃链表。

zset可以存储学生成绩,value是学生id,score是学生成绩,按score排序可以得到学生成绩名次。

zadd key score member向zset中添加元素

zrange key min max 按score排序给定排名列出

zrevrange key min maxn 逆序列出

zcard key 获取zset count

zscore key key 获取zset score

zrank key member 获取排名

zrem key member 移除元素

zrangebyscore key min max 根据分值区间遍历zset(-inf 负无穷大∞,withscores可有可无)

127.0.0.1:6379> zadd phones 1.0 iphone1
(integer) 1
127.0.0.1:6379> zadd phones 2.0 iphone2
(integer) 1
127.0.0.1:6379> zadd phones 4.0 iphone4
(integer) 1
127.0.0.1:6379> zrange phones 0 -1
1) "iphone1"
2) "iphone2"
3) "iphone4"
127.0.0.1:6379> zrevrange phones 0 -1
1) "iphone4"
2) "iphone2"
3) "iphone1"
127.0.0.1:6379> zcard phones
(integer) 3
127.0.0.1:6379> zscore phones iphone4
"4"
127.0.0.1:6379> zadd phones 4.9 iphone5
(integer) 1
127.0.0.1:6379> zscore phones iphone5
"4.9000000000000004"
127.0.0.1:6379> zrank phones iphone4
(integer) 2
127.0.0.1:6379> zrem phones iphone2
(integer) 1
127.0.0.1:6379> zrange phones 0 -1
1) "iphone1"
2) "iphone4"
3) "iphone5"
127.0.0.1:6379> zrangebyscore phones 0 4.1
1) "iphone1"
2) "iphone4"
127.0.0.1:6379> zrangebyscore phones -inf 4.1
1) "iphone1"
2) "iphone4"
127.0.0.1:6379> zrangebyscore phones -inf 4.1 withscores
1) "iphone1"
2) "1"
3) "iphone4"
4) "4"

zset和set的区别是zset有序,既然有序就涉及到插入的问题,需要找到插入的位置,如果一个个遍历的话复杂度为O(n),这样的话效率很低,于是跳跃链表应运而生。如下图,跳跃链表是分层的元素添加进zset的时候落到第一层L0时,概率为50%,兼任L1时概率为25%,兼任L2时概率为12.5%,最多可以兼任到L31,概率为多少呢,各位客官可以算一下哈

1.2 分布式锁

这个我们平时项目中用的比较多,下面详细来聊一下。

我们可以用setnx的方式来添加一个key,添加成功说明抢锁成功,否则失败。

127.0.0.1:6379> setnx lock_do_sth true
(integer) 1
127.0.0.1:6379> setnx lock_do_sth true
(integer) 0

执行完之后删除即可

127.0.0.1:6379> del lock_do_sth true
(integer) 1

看到这里各位看官也许会有疑问,如果抢到该锁的代码没有执行完就异常了,这个key就永远删除不掉了,好办,我们来加个过期时间。

127.0.0.1:6379> setnx lock_do_sth true
(integer) 1
127.0.0.1:6379> expire lock_do_sth 5
(integer) 1
127.0.0.1:6379> del lock_do_sth true
(integer) 1

这样还是有问题,如果setnex和expire之间出问题了呢,毕竟这两个命令不是原子的,有人说用事务可以保证原子性,也不行,为啥呢,expire依赖于setnx的执行结果,通俗的讲就是没抢到锁加过期时间干啥,事务里面又没有if-else的逻辑,要么都执行要么都不执行。

为了解决这个问题2.8加入了set命令的扩展参数,可以setnx和expire命令一起执行。

127.0.0.1:6379> set lock_do_sth true ex 5 nx
OK

这样解决了设置过期时间的问题,但是不能解决超时的问题。比如线程1抢到了锁,设置了超时时间,时间到了,线程1活没干完,还在执行,线程2来了,一看抢到锁了,完了,现在出现了两个问题:问题1,线程1和线程2同时在干一件事,;问题2,线程1执行完了把锁删了,结果锁已经不是线程1那把锁了,而是线程2加的锁,线程3又进来了。。。那咋办,好办,使用redission,redission加锁之后会启用一个定时任务10s执行一次,如果发现还持有锁,那么续期过期时间,默认过期时间30s,此机制被称为:“看门狗”;另外对value进行随机,以防止删除别人的锁。Lua脚本如下:

if redis.call("get",KEYS[1]) == ARGV[1] then

          redis.call("set",KEYS[1],ex=3000)  

else

          getDLock();//重新获取锁    

还有一个问题就是可重入性,比如你写了一段代码,需要递归调用,每次调用都会获取锁,那么加完锁之后再次进行加锁,这个锁就是可重入的。Java可以使用ThreadLocal维护锁的重入次数。

1.3 延时队列

为什要用延时队列呢,比如:

打车时,如果规定时间内没有车主接单,平台就会取消你的订单;

订外卖时,商家没有接单,订单就会自动取消;

网购时,超过时间没有付款,平台就会关闭订单;

这样做有什么好处呢,你可以不用一直打车等不到回复,可以点别人家的外卖,可以把商品释放库存卖给别人。

队列也可以缓解服务器压力,程序之间解藕。

那么运用哪些数据结构可以实现队列呢,没错,就是list和zset,这两个都可以,不同的是zset有一个score属性,可以使用zrangebyscore命令排序,优先处理。

1.4 位图

在我们平时开发中,我们可能会存一些boolean类型的数据,比如打卡,今天打卡就是true,没打就是false,一年365天,就是365条记录,如果上亿用户呢,存储量是不是特别大,哎,位图的优势就体现出来了,用一个能容纳365个位的字符,是多少个字节呢,1byte=8bit,365/8=46,这样就大大节省了空间。位图最小单位是比特,每个比特非0即1,只有两个取值,也就是2进制,如下图所示:

通过python获取hello的二进制值

>>>bin(ord('h'))
'0b1101000'
>>>bin(ord('e'))
'0b1100101'
>>>bin(ord('l'))
'0b1101100'
>>>bin(ord('o'))
'0b1101111'

 返回的位数组和实际是反着的,实际上h只有1,2,4位为1,其他位为0,接下来redis实操一下:

127.0.0.1:6379> setbit s 1 1
(integer) 0
127.0.0.1:6379> setbit s 2 1
(integer) 0
127.0.0.1:6379> setbit s 4 1
(integer) 0
127.0.0.1:6379> get s
"h"

再举个🌰:

字符串a的ascII的值是97对吧,用二进制表示是01100001,b是01100010,对吧,加1就好了,向前进1位,那要怎么操作呢,把第六位0变1,第七位1变0,是不是就a变成b啦

127.0.0.1:6379> setbit andy 6 1
(integer) 0
127.0.0.1:6379> setbit andy 7 0
(integer) 1
127.0.0.1:6379> get andy
"b"

bigcount可以返回位图中1的个数,bitpos可以返回制定范围内出现的第一个0或1

127.0.0.1:6379> bitpos andy 1 0 7
(integer) 1
127.0.0.1:6379> bitcount andy
(integer) 3

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值