阿里P9架构师分享:通俗易懂Redis原理,都是你没看过的( 二 )


注:

  • cursor:游标
  • MATCH pattern:查询 Key 的条件
  • Count:返回的条数
SCAN 是一个基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程 。
SCAN 以 0 作为游标,开始一次新的迭代,直到命令返回游标 0 完成一次遍历 。
此命令并不保证每次执行都返回某个给定数量的元素,甚至会返回 0 个元素,但只要游标不是 0,程序都不会认为 SCAN 命令结束,但是返回的元素数量大概率符合 Count 参数 。另外,SCAN 支持模糊查询 。
例:
SCAN 0 MATCH test* COUNT 10 //每次返回10条以test为前缀的key 如何通过 Redis 实现分布式锁
分布式锁
分布式锁是控制分布式系统之间共同访问共享资源的一种锁的实现 。如果一个系统,或者不同系统的不同主机之间共享某个资源时,往往需要互斥,来排除干扰,满足数据一致性 。
分布式锁需要解决的问题如下:
  • 互斥性:任意时刻只有一个客户端获取到锁,不能有两个客户端同时获取到锁 。
  • 安全性:锁只能被持有该锁的客户端删除,不能由其他客户端删除 。
  • 死锁:获取锁的客户端因为某些原因而宕机继而无法释放锁,其他客户端再也无法获取锁而导致死锁,此时需要有特殊机制来避免死锁 。
  • 容错:当各个节点,如某个 Redis 节点宕机的时候,客户端仍然能够获取锁或释放锁 。
如何使用 Redis 实现分布式锁
使用 SETNX 实现,SETNX key value:如果 Key 不存在,则创建并赋值 。
该命令时间复杂度为 O(1),如果设置成功,则返回 1,否则返回 0 。
阿里P9架构师分享:通俗易懂Redis原理,都是你没看过的

文章插图
 
由于 SETNX 指令操作简单,且是原子性的,所以初期的时候经常被人们作为分布式锁,我们在应用的时候,可以在某个共享资源区之前先使用 SETNX 指令,查看是否设置成功 。
如果设置成功则说明前方没有客户端正在访问该资源,如果设置失败则说明有客户端正在访问该资源,那么当前客户端就需要等待 。
但是如果真的这么做,就会存在一个问题,因为 SETNX 是长久存在的,所以假设一个客户端正在访问资源,并且上锁,那么当这个客户端结束访问时,该锁依旧存在,后来者也无法成功获取锁,这个该如何解决呢?
由于 SETNX 并不支持传入 EXPIRE 参数,所以我们可以直接使用 EXPIRE 指令来对特定的 Key 来设置过期时间 。
用法:
EXPIRE key seconds
阿里P9架构师分享:通俗易懂Redis原理,都是你没看过的

文章插图
 
程序:
RedisService redisService = SpringUtils.getBean(RedisService.class); long status = redisService.setnx(key,"1"); if(status == 1){redisService.expire(key,expire);doOcuppiedWork(); } 这段程序存在的问题:假设程序运行到第二行出现异常,那么程序来不及设置过期时间就结束了,则 Key 会一直存在,等同于锁一直被持有无法释放 。
出现此问题的根本原因为:原子性得不到满足 。
解决:从 Redis 2.6.12 版本开始,我们就可以使用 Set 操作,将 SETNX 和 EXPIRE 融合在一起执行,具体做法如下:
  • EX second:设置键的过期时间为 Second 秒 。
  • PX millisecond:设置键的过期时间为 MilliSecond 毫秒 。
  • NX:只在键不存在时,才对键进行设置操作 。
  • XX:只在键已经存在时,才对键进行设置操作 。
SET KEY value [EX seconds] [PX milliseconds] [NX|XX] 注:SET 操作成功完成时才会返回 OK,否则返回 nil 。
有了 SET 我们就可以在程序中使用类似下面的代码实现分布式锁了:
RedisService redisService = SpringUtils.getBean(RedisService.class); String result = redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime); if("OK.equals(result)"){doOcuppiredWork(); } 如何实现异步队列
①使用 Redis 中的 List 作为队列
使用上文所说的 Redis 的数据结构中的 List 作为队列 Rpush 生产消息,LPOP 消费消息 。
阿里P9架构师分享:通俗易懂Redis原理,都是你没看过的

文章插图
 
此时我们可以看到,该队列是使用 Rpush 生产队列,使用 LPOP 消费队列 。
在这个生产者-消费者队列里,当 LPOP 没有消息时,证明该队列中没有元素,并且生产者还没有来得及生产新的数据 。
缺点:LPOP 不会等待队列中有值之后再消费,而是直接进行消费 。
弥补:可以通过在应用层引入 Sleep 机制去调用 LPOP 重试 。
②使用 BLPOP key [key…] timeout


推荐阅读