目录

常规命令

String字符串类型

BIT位图

【使用bit命令实现签到功能】

List有序链表

Hash哈希

Set无序集合

【使用无序集合统计用户的留存数】

Sorted Set有序集合

【使用有序集合实现排行榜业务】

关于Key的命令

eval命令:在redis中使用Lua脚本

​编辑

【redis实现秒杀】

【redis做分布式锁】

【解决redis超时问题】

如何使用redis做消息队列?

【方法1:使用List结构】

【方法2:使用Streams结构】

​编辑

GEO命令:位置LBS查询

scan命令

Redis事务的应用场景

需求:提供最新评论列表

(1)使用list实现(有问题)

(2)使用Sorted Set实现(没有问题)

需求:统计网页的 UV(基数统计)

方法1:使用Set 类型,默认支持去重

方法2:使用Hash类型

方法3:使用HyperLogLog基数统计


常规命令

详细使用请点击:

redis笔记02-字符串相关命令_浮.尘的博客-CSDN博客

redis笔记03-链表和哈希_浮.尘的博客-CSDN博客

redis笔记04-无序集合和有序集合_redis 有序集合和无序集合_浮.尘的博客-CSDN博客

String字符串类型

set:给一个 key 赋值的。
get:用来获取一个 key 的 value。
append:使用 append 命令时,如果 key 已经存在,则直接在对应的 value 后追加值,否则就创建新的键值对。
decr:可以实现对 value 的减 1 操作(前提是 value 是一个数字),如果 value 不是数字,会报错,如果value 不存在,则会给一个默认的值为 0,在默认值的基础上减一。
decrby:和 decr 类似,但是可以自己设置步长,该命令第二个参数就是步长。
incr:给某一个 key 的 value 自增。
incrby:给某一个 key 的 value 自增,同时还可以设置步长。
incrbyfloat:和 incrby 类似,但是自增的步长可以设置为浮点数。
getrange:用来返回 key 对应的 value 的子串,这个命令第二个和第三个参数就是截取的起始和终止位置,其中,-1 表示最后一个字符串,-2 表示倒数第二个字符串,以此类推….。注意:原key的值不发生变化。
getset:获取并更新某一个 key。
mget 和 mset:批量获取和批量存储。
ttl:查看 key 的有效期
setex:在给 key 设置 value 的同时,还设置过期时间。
psetex: 和 setex 类似,只不过这里的时间单位是毫秒。
setnx:默认情况下, set 命令会覆盖已经存在的 key,setnx 则不会。
msetnx:批量设置,全部成功才会写入成功,否则失败。
setrange:覆盖一个已经存在的 key 的value的指定下标开始之后的部分,超出部分会用0补齐:
strlen:查看字符串长度。

BIT位图

getbit:key 对应的 value 在 offset 处的 bit 值。
setbit:修改 key 对应的 value 在 offset 处的 bit 值
bitcount:统计二进制数据中 1 的个数。

【使用bit命令实现签到功能】

用户一年的签到记录,如果用 string 类型来存储,那么就需要存储 365 个 key/value,操作起来麻烦。通过位图(Bitmap)可以有效的简化这个操作,它的统计很简单:01111000111,每天的记录占一个位,365 天就是 365 个位,大概 46 个字节,这样可以有效的节省存储空间。如果有一天想要统计用户一共签到了多少天,统计 1 的个数即可。
对于位图的操作,可以操作对应的字符串(get/set),也可以直接操作位(getbit/setbit),因为 Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。 
需求:假设要统计 ID 3000 的用户在 2020 年 8 月份的签到情况:

127.0.0.1:6379> SETBIT uid:sign:3000:202008 2 1  //记录用户8月3号已签到(8月1号用0表示)
127.0.0.1:6379> GETBIT uid:sign:3000:202008 2  //检查用户8月3号是否已签到:返回1
127.0.0.1:6379> SETBIT uid:sign:3000:202008 5 1 //记录用户8月6号已签到:返回0
127.0.0.1:6379> SETBIT uid:sign:3000:202008 6 1 //记录用户8月7号已签到:返回0
127.0.0.1:6379> BITCOUNT uid:sign:3000:202008 //统计用户8月份的签到次数:返回3

如果只需要统计数据的二值状态,例如商品有没有、用户在不在等,就可以使用 Bitmap,因为它只用一个 bit 位就能表示 0 或 1。在记录海量数据时,Bitmap 能够有效 地节省内存空间。

List有序链表

lpush:向key 的列表的头部插入所有指定的值。如果 key 不存在,那么在进行 push 操作前会创建一个空列表。 如果 key 对应的值不是一个 list 的话,那么会返回一个错误。
lpop:移除并返回列表的头元素。
blpop:移除并返回列表的头元素(阻塞式)
rpush:向key 的列表的尾部插入所有指定的值。
rpop:移除并返回列表的尾元素。
brpop:移除并返回列表的尾元素(阻塞式)
lrange:返回列表指定区间内的元素。[0 -1]表示获取所有元素。
lindex:返回列表中,下标为 index 的元素。不会移除。
ltrim:对一个列表进行修剪。

Hash哈希

hset:添加key和value。如果 key 和 field 相同,会覆盖掉已有的 value。
hget:获取值。
hmset:批量设置。
hmget:批量获取。
hdel:删除一个指定的 field
hsetnx:如果 key 和 field 相同,不会覆盖掉已有的 value。
hkeys:获取所有的 key
hvals:获取所有的 value
hgetall:同时获取所有的 key 和 value
hexists:返回 field 是否存在
hincrby:给指定的 value 增加一个整数数值。
hincrbyfloat:给指定的 value 增加一个浮点数数值。
hlen:返回 某一个 key 中 value 的数量
hstrlen:返回某一个 key 中的某一个 field 的字符串长度

Set无序集合

sadd:添加元素到一个 key 中
smembers:获取一个 key 下的所有元素
srem:移除指定的元素
sismemeber:返回某一个成员是否在集合中
scard:返回集合的数量
srandmember:随机返回一个元素
spop:随机返回并且出栈一个元素。
sdiff:返回两个集合的差集。
sdiffstore:返回两个集合的差集,计算出来的结果会保存在一个新的集合中。
sinter:返回两个集合的交集。
sinterstore:返回两个集合的交集,将计算出来的交集保存到一个新的集合中。
sunion:返回两个集合的并集。
sunionstore:返回两个集合的并集,将结果保存到新的集合中。
smove:把一个元素从一个集合移到另一个集合中去。

【使用无序集合统计用户的留存数】

需求:统计手机 App 每天的新增用户数和第二天的留存用户数:

分析:可以使用聚合统计(交集、差集、并集),用一个集合记录所有登录过 App 的用户 ID,同时用另一个集合记录每一天登录过 App 的用户 ID。然后再对这两个集合做聚合统计。可以直接使用 Set 类型,以日期作为key,里面存储所有登录过 App 的用户 ID。具体实现方式如下:

127.0.0.1:6379> sadd user:id:20200803 1 2 3 4 5 //8月3号第一天上线,生成的新的用户id
127.0.0.1:6379> SUNIONSTORE user:id user:id user:id:20200803 //求并集,保存到user:id中,就是8月3号的用户id
127.0.0.1:6379> SMEMBERS user:id // 1 2 3 4 5127.0.0.1:6379> sadd user:id:20200804 2 4 5 7 8 //8月4号登录过的uid
127.0.0.1:6379> SDIFFSTORE user:new user:id:20200804 user:id //求差集,得到的就是8月4日的新增用户
127.0.0.1:6379> SMEMBERS user:new // 7 8//求交集,得到的就是在8.3号登录,并且在8.4留存的uid
127.0.0.1:6379> SINTERSTORE user:id:rem user:id:20200803 user:id:20200804
127.0.0.1:6379> SMEMBERS user:id:rem //2 4 5

需要注意的风险点:Set 的差集、并集和交集 的计算复杂度较高,在数据量较大的情况下,直接执行这些计算会导致 Redis 实例阻塞。所以可以从主从集群中选择一个从库让它专门负责聚合计算,这样就可以规避阻塞主库实例和其他从库实例的风险了。

Sorted Set有序集合

zadd:将指定的元素添加到有序集合中。
zscore:返回 member 的 score 值
zrange[withscores]:按照下标返回集合中的一组元素,从小到大。返回全部是 0 -1,闭合区间。
zrevrange[withscores]:按照下标返回一组元素,从大到小。。返回全部是 0 -1,闭合区间。
zrangebyscore[withscores]:按照 score 的范围从小到大返回元素。
zrevrangebyscore[withscores]:按照 score 的范围从大到小返回元素。
zrank:返回元素的排名(从小到大)
zrevrank:返回元素排名(从大到小)
zcard:返回元素个数
zcount:返回 score 在某一个区间内的元素,左右闭合区间。如果需要开区间,加上(括号。
zincrby:score增加指定的值。
zrem:弹出一个元素。
zlexcount:返回指定区间内的成员数量。
zrangebylex:返回指定区间内的成员。
zinter / zinterstore:给两个集合求交集,并且结果中的score相加。
zunion / zunionstore:给两个集合求并集,并且结果中的score相加。

【使用有序集合实现排行榜业务】

假设一个服务器存储了 10 万名玩家的数据,我们想给这个区(这台服务器)上的玩家做个全区的排名,该如何用 Redis 实现呢?

# 统计全部玩家的排行榜,其中 -1 代表的是全部的玩家数据,WITHSCORES代表的是输出排名的同时也输出分数
ZREVRANGE user_score 0 -1 WITHSCORES
# 统计前 10 名玩家:
ZREVRANGE user_score 0 9
# 查询玩家10001的分数,时间复杂度为O(1)
ZSCORE user_score 10001
# 查询玩家10001的排名,时间复杂度为O(log(N))
ZREVRANK user_score 10001
# 对玩家10001的分数减1,时间复杂度为O(log(N))
ZINCRBY user_score -1 10001
# 查询玩家10001前后5名玩家(当前玩家10001的排名是36)
ZREVRANGE user_score 31 41
# 删除玩家 10001,时间复杂度为O(log(N))
ZREM user_score 10001
# 把玩家10001的信息再增加回来,时间复杂度为O(log(N))
ZADD user_score 36 10001
【问题】有序集合与列表 都是有序的,都可以获得某一范围的元素,它俩有什么区别?
【回答】 它俩在数据结构上有很大的不同, 区别如下:
  • 列表 list 是通过双向链表实现的,在操作左右两侧的数据时会非常快,而对于中间的数据操作则相对较慢。有序集合采用 hash 表的结构来实现,读取排序在中间部分的数据也会很快。
  • 有序集合可以通过 score 来完成元素位置的调整,但如果想要对列表进行元素位置的调整则会比较麻烦。

关于Key的命令

del:删除一个 key,任何类型都可以。
dump:序列化给定的 key
exists:判断一个 key 是否存在
ttl:查看一个 key 的有效期
expire:给一个 key 设置有效期,如果 key 在过期之前被重新 set 了,则过期时间会失效。
persist:移除一个 key 的过期时间
keys *:查看所有的 key
pttl:和 ttl 一样,只不过这里返回的是毫秒

eval命令:在redis中使用Lua脚本

为了实现并发控制要求的临界区代码互斥执行,Redis 的原子操作采用了两种方法:

1. 把多个操作在 Redis 中实现成一个操作,也就是单命令操作:例如 Redis 提供了 INCR/DECR 命令,可以对数据进行增值 / 减值操作,而且就是单个命令操作,本身就具有互斥性。

2. 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。语法:EVAL script numkeys key [key ...] arg [arg ...]

script: lua脚本

numkeys: key的个数

key: redis中各种数据结构的替代符号

arg: 自定义参数

比如使用 eval "return {ARGV[1], ARGV[2]}" 0 cy 123,代表的是传入的 key 的个数为 0,后面有两个 arg,分别为 cy 和 123。在 Lua 脚本中,直接返回这两个参数ARGV[1], ARGV[2]

还可以在命令中调用 Lua 脚本,使用的命令格式:redis-cli --eval lua_file key1 key2 , arg1 arg2 arg3

比如上面实现排行的功能,可以这样实现:

* 通过 Lua 脚本创建 10 万名玩家的数据,文件名为 insert_user_scores.lua,
* 在调用的时候,通过ARGV[1]获取时间种子的参数,传入的KEYS[1]为user_score,也就是创建有序集合user_score。然后通过 num 来设置生成玩家的数量,通过user_id获取初始的user_id。最后调用如下命令完成玩家数据的创建,将其存储在了 Redis 的有序集合user_score中: redis-cli -h localhost -p 6379 --eval insert_user_scores.lua user_score , 30 100000 10000

使用示例:

127.0.0.1:6379> eval "return redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 name age zhangsan 18
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> get age
"18"127.0.0.1:6379> eval "return redis.call('hset',KEYS[1],ARGV[1],ARGV[2])" 1 foo abc 123
(integer) 1
127.0.0.1:6379> hgetall foo
1) "abc"
2) "123"

【redis实现秒杀】

一般数据库每秒只能支撑千级别的并发请求,而 Redis 的并发处理能力(每秒处理请求数)能达到万级别甚至更高。所以当有大量并发请求涌入秒杀系统时,就需要使用 Redis 先拦截大部分请求,避免大量请求直接发送给数据库,把数据库压垮。

Redis 可以在秒杀场景的哪些环节发挥作用? 主要有三个操作:库存查验、库存扣减和订单处理。这个阶段中最大的并发压力都在库存查验操作上。一旦库存有余量,就立即在 Redis 中扣减库存。库存查验和库存扣减这两个操作需要保证原子性(decr命令或者Lua脚本)。

秒杀活动带来的请求流量巨大,因此需要把秒杀商品的库存信息用单独的实例保存,而不要和日常业务系统的数据保存在同一个实例上,这样可以避免干扰业务系统的正常运行。

【redis做分布式锁】

Redis 客户端可以通过加锁的方式来控制并发写操作对共享数据的修改,从而保证数据的正确性。但是Redis 属于分布式系统,当有多个客户端需要争抢锁时就必须要保证这把锁不能是某个客户端本地的锁,否则的话其它客户端是无法访问这把锁的,当然也就不能获取这把锁了。在分布式系统中,当有多个客户端需要获取锁时就需要分布式锁,此时锁是保存在一个共享存储系统中的,可以被多个客户端共享访问和获取。

在基于单个 Redis 实例实现分布式锁时,对于加锁操作,需要满足三个条件。

  • 加锁需要以原子操作的方式完成,所以,使用 SET 命令带上 NX 选项来实现加锁;
  • 锁变量需要设置过期时间,所以,我们在 SET 命令执行时加上 EX/PX 选项,设置其过期时间;
  • 锁变量的值需要能区分来自不同客户端的加锁操作,以免在释放锁时出现误释放操作,所以使用 SET 命令设置锁变量值时,每个客户端设置的值是一个唯一值,用于标识客户端。

setnx不支持设置过期时间,因此使用set命令包含nx参数(Redis在 2.6.12 版本后支持)。

//PHP code
$redis->set('key', 'value', ['nx', 'ex' => 10]) //表示当key不存在时, 设置key且过期时间为10秒.
$redis->set('key', 'value', ['nx', 'px' => 500]) //表示当key不存在时, 设置key且过期时间为500毫秒(即0.5秒).

Redis 也提供了 Redlock 算法,用来实现基于多个实例的分布式锁。这样一来,锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock 算法是实现高可靠分布式锁的一种有效解决方案,互斥性:在任何时候,只能有一个客户端能够持有锁。RedLock-红锁 - 简书

【解决redis超时问题】

如果要执行的业务非常耗时,可能会出现紊乱。

举个例子:第一个线程首先获取到锁,然后开始执行业务代码,但是业务代码比较耗时,执行了 8 秒,这样,会在第一个线程的任务还未执行成功锁就会被释放了,此时第二个线程会获取到锁开始执行,在第二个线程刚执行了 3 秒,第一个线程也执行完了,此时第一个线程会释放锁,但是注意,它释放的第二个线程的锁,释放之后,第三个线程进来。

对于这个问题,可以从两个角度入手:

  • 将锁的 value 设置为一个随机字符串,每次释放锁的时候,都去比较随机字符串是否一致,如果一致再去释放,否则不释放。(不具备原子性,不推荐)
  • 使用Lua 脚本:(1)使用方便,Redis 中内置了对 Lua 脚本的支持。(2)Lua 脚本可以在 Redis 服务端原子的执行多个 Redis 命令。(3)由于网络在很大程度上会影响到 Redis 性能,而使用 Lua 脚本可以让多个命令一次执行,可以有效解决网络给 Redis 带来的性能问题。 https://www.cnblogs.com/rcjtom/articles/8954398.html

如何使用redis做消息队列?

【方法1:使用List结构】

在客户端维护一个死循环来不停的从队列中读取消息并处理,如果队列中有消息则直接获取到,如果没有消息就会陷入死循环,直到下一次有消息进入,这种死循环会造成大量的资源浪费,这个时候可以使用 blpop/brpop阻塞式读取。

127.0.0.1:6379> lpush queue A B C D E // (integer) 5
127.0.0.1:6379> rpop queue // "A"
127.0.0.1:6379> rpop queue // "B"
127.0.0.1:6379> rpop queue // "C"127.0.0.1:6379> rpush queue2 aa bb cc // (integer) 3
127.0.0.1:6379> lpop queue2 // "aa"
127.0.0.1:6379> lpop queue2 // "bb"
127.0.0.1:6379> lpop queue2 // "cc"

为了保证宕机后消息不会丢失,List 类型提供了 BRPOPLPUSH 命令,作用是让消费者程序从 一个 List 中读取消息,同时把这个消息再插入到另一个 List(可以叫作备份 List)留存。这样一来,如果消费者程序读了消息但没能正常处理,等它重启后,就可以从备份 List 中重新读取消息并进行处理了。

【方法2:使用Streams结构】

生产者消息发送很快,而消费者处理消息的速度比较慢,这就导致 List 中的消息越积越多,给 Redis 的内存带来很大压力。Redis 从 5.0 版本开始提供的 Streams 数据类型。和 List 相比,Streams 同样能够满足消息队列的三大需求。而且,它还支持消费组形式的消息读取。Streams 是 Redis 专门为消息队列设计的数据类型,它提供了丰富的消息队列操作命令。

XADD # 插入消息,保证有序,可以自动生成全局唯一 ID;
XREAD # 用于读取消息,可以按 ID 读取数据;
XREADGROUP # 按消费组形式读取消息;
XPENDING 和 XACK:XPENDING  # 用来查询每个消费组内所有消费者已读取但尚未确认的消息,而 XACK 命令用于向消息队列确认消息处理已完成
# 往名称为 mqstream 的消息队列中插入一条消息, 消息的键是 repo,值是 5。
# 其中,消息队列名称后面的*,表示让 Redis 为插入的数据自 动生成一个全局唯一的 ID
127.0.0.1:6379> XADD mqstream * repo 5
"1669348698878-0"  //这个是自动生成的全局唯一ID,表示在1669348698878(毫秒时间戳)生成的第0条消息
127.0.0.1:6379> XADD mqstream * repo 5
"1669348917303-0"
127.0.0.1:6379> XADD mqstream * repo 6
"1669348933066-0"
127.0.0.1:6379> XADD mqstream * repo 7
"1669348936627-0"# 使用 XREAD 命令从消息队列中读取。XREAD 在读取消息时,可以指定一个消息 ID,并从这个消息 ID 的下一条消息开始进行读取。
127.0.0.1:6379> XREAD BLOCK 100 STREAMS mqstream 1669348917303-0
1) 1) "mqstream"2) 1) 1) "1669348933066-0"2) 1) "repo"2) "6"2) 1) "1669348936627-0"2) 1) "repo"2) "7"# 也可以在调用 XRAED 时设定 block 配置项,实现类似于 BRPOP 的阻塞读 取操作。
127.0.0.1:6379> XREAD block 10000 streams mqstream $ #这里的"$"符号表示读取最新的消息,
(nil)
(10.08s)# 创建一个名为 group1 的消费组,这个消费组消费的消息队列是mqstream。
127.0.0.1:6379> XGROUP create mqstream group1 0
OK# 让group1消费组里的消费者consumer1从mqstream中读取所有消息,其中,命令最后的参数“>”,表示从第一条尚未被消费的消息开始读取。
127.0.0.1:6379> XREADGROUP group group1 consumer1 streams mqstream >
1) 1) "mqstream"2) 1) 1) "1669349181792-0"2) 1) "repo"2) "5"2) 1) "1669349184046-0"2) 1) "repo"2) "6"3) 1) "1669349185624-0"2) 1) "repo"2) "7"

参考资料:15 | 消息队列的考验:Redis有哪些解决方案?-极客时间

GEO命令:位置LBS查询

Redis3.2 开始提供了 GEO 模块。GEO 类型的底层数据结构就是用 Sorted Set (有序集合) 来实现的。Sorted Set 元素的权重分数是一个浮点数(float 类型),而一组经纬度包含的是经度和纬度两个值,要用到 GEO 类型中的 GeoHash算法,能够将二维的空间经纬度数据编码成一个一维字符串。

用了 GeoHash 编码后,原来无法用一个权重分数表示的一组经纬度(116.37,39.86)就可以用 1110011101 这一个值来表示,就可以保存为 Sorted Set 的权重分数了。

如何操作 GEO 类型(LBS应用,计算两个经纬度),比如常用的打车软件计算用户附近的车辆,以及外卖平台骑手距离自己的位置。

* GEOADD 命令:用于把一组经纬度信息和相对应的一个 ID 记录到 GEO 类型集合中;

* GEORADIUS 命令:会根据输入的经纬度位置,查找以这个经纬度为中心的一定范围内 的其他元素。

# 添加地址:
127.0.0.1:6379> GEOADD city 116.3980007200 39.9053908600 beijing
127.0.0.1:6379> GEOADD city 114.0592002900 22.5536230800 shenzhen# 查看两个地址之间的距离:
127.0.0.1:6379> GEODIST city beijing shenzhen km
"1942.5435"# 获取元素的位置:
127.0.0.1:6379> GEOPOS city beijing
1) 1) "116.39800339937210083"2) "39.90539144357683909"# 获取元素 hash 值:
127.0.0.1:6379> GEOHASH city beijing
1) "wx4g08w3y00"# 查找经纬度为中心附近多少公里的数据
127.0.0.1:6379> GEOADD cars:locations 116.034579 39.030452 33 //把ID为33的车辆当前经纬度位置存入GEO集合中
127.0.0.1:6379> GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10 //查找经纬度为中心5公里内的数据。返回 "33"# 说明:
# 1、可以使用 ASC 选项,让返回的车辆信息按照距离这个中心位置从近到远的方式来排序,以方便选择最近的车辆;
# 2、还可以使用 COUNT 选项,指定返回的车辆信息的数量。毕竟5公里范围内的车辆可能有很多。# 查看附近的人:
127.0.0.1:6379> GEORADIUSBYMEMBER city beijing 200 km count 3 asc
1) "beijing"# 以北京为中心,方圆 200km 以内的城市找出来 3 个,按照远近顺序排列,这个命令不会排除 北京。
# 当然,也可以根据经纬度来查询(将 member 换成对应的经纬度):
127.0.0.1:6379> GEORADIUS city 116.3980007200 39.9053908600 2000 km withdist withhash withcoord count 4 desc

参考资料:13 | GEO是什么?还可以定义新的数据类型吗?-极客时间

scan命令

从 Redis2.8 中开始,引入了 scan。

可以用 keys 来查询 key,在查询的过程中,可以使用通配符。keys 虽然用着还算方便,但是没有分页功能。同时因为 Redis 是单线程,所以 key 的执行会比较消耗时间,特别是当数据量大的时候,影响整个程序的运行。

为了解决 keys 存在的问题,从 Redis2.8 中开始,引入了 scan。scan 具备 keys 的功能,但是不会阻塞线程,而且可以控制每次返回的结果数。

scan 命令一共提供了三个参数,第一个 cursor,第二个参数是 key,第三个参数是 limit。cursor 实际上是指一维数组的位置索引,limit 则是遍历的一维数组个数(所以每次返回的数据大小可能不确定)。

示例:scan 0 match k8* count 1000

127.0.0.1:6379> mset name zhangsan age 18 sex nan hobby php time 20221123
OK
127.0.0.1:6379> keys *
1) "time"
2) "age"
3) "sex"
4) "hobby"
5) "name"127.0.0.1:6379> scan 0 match * count 1
1) "6"
2) 1) "age"2) "sex"127.0.0.1:6379> scan 6 match * count 1
1) "5"
2) 1) "time"127.0.0.1:6379> scan 5 match * count 1
1) "7"
2) 1) "hobby"127.0.0.1:6379> scan 7 match * count 1
1) "0"
2) 1) "name"

注意:scan 是一系列的指令,除了遍历所有的 key 之外,也可以遍历某一个类型的 key,对应的命令有:
* zscan-->zset
* hscan-->hash
* sscan-->set

Redis事务的应用场景

Redis 不支持事务的回滚机制(Rollback),Redis 是单线程程序,是串行化的方式,Redis 中的事务并不能算作原子性,但总是具有隔离性的,也就是说当前的事务可以不被其他事务打断。

1. MULTI:开启一个事务;

2. EXEC:事务执行,将一次性执行事务内的所有命令;

3. DISCARD:取消事务;

4. WATCH:监视一个或多个键,如果事务执行前某个键发生了改动,那么事务也会被打断;

5. UNWATCH:取消 WATCH 命令对所有键的监视。

经常使用 Redis 的 WATCH 和 MULTI 命令来处理共享资源的并发操作,比如秒杀、抢票等。实际上 WATCH+MULTI 实现的是乐观锁。可以使用 Redis 的 incr 和 decr 来实现键的原子性递增或递减。可以在redis中使用Lua脚本实现原子性。

Redis 中并没有提供回滚机制。虽然 Redis 提供了 DISCARD 命令,但是这个命令只能用来主动放弃事务执行,把暂存的命令队列清空,起不到回滚的效果。Redis 的事务机制可以保证一致性和隔离性,但是无法保证持久性。

127.0.0.1:6379> MSET zhangsan 100 lisi 200 //张三100元,李四200元 :OK
127.0.0.1:6379> MULTI //开启事务 : OK
127.0.0.1:6379(TX)> DECRBY zhangsan 10 //张三减10元 : QUEUED
127.0.0.1:6379(TX)> INCRBY lisi 10 //李四加10元 : QUEUED
127.0.0.1:6379(TX)> EXEC //提交事务
1) (integer) 90
2) (integer) 210
127.0.0.1:6379> MGET zhangsan lisi
1) "90"
2) "210"

需求:提供最新评论列表

最新评论列表包含了所有评论中的最新留言,这就要求集合类型能对元素保序,也就是说,集合中的元素可以按序排列,这种对元素保序的集合类型叫作有序集合。List 和 Sorted Set 都属于有序集合。List 是按照元素进入 List 的顺序进行排序的,而 Sorted Set 可以根据元素插入的时间确定权重值,先插入的元素权重小,后插入的元素权重大。在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,建议优先考虑使用 Sorted Set。

(1)使用list实现(有问题)

每个商品对应一个 List,这个 List 包含了对这个商品的所有评论,而且会按照评论时间保存这些评论,每来一个新评论,就用 LPUSH 命令把它插入 List 的队头。List 是通过元素在 List 中的位置来排序的,当有一个新元素插入时,原先的元素在 List 中的位置都后移了一位,因此使用list存储就会有问题。(第二页的时候会把第一页最后一条评论再次取出来)。

127.0.0.1:6379> lpush product1 A B C D E F //A是最早评论,F是最新评论
127.0.0.1:6379> lrange product1 0 2 //第一页,获取前三条:F E D
127.0.0.1:6379> lrange product1 3 5 //第二页,获取后三条,正常情况下没有问题: C B A
127.0.0.1:6379> lpush product1 G  //假如此时新来一条评论:G,当前评论为:A B C D E F G
127.0.0.1:6379> lrange product1 3 5 //此时获取第二页,发现D又被取出来了:D C B

(2)使用Sorted Set实现(没有问题)

按评论时间的先后给每条评论设置一个权重值,然后再把评论保存到 Sorted Set 中。Sorted Set 的 ZRANGEBYSCORE 命令就可以按权重排序后返回元素。这样的话,即使集合中的元素频繁更新,Sorted Set 也能通过 ZRANGEBYSCORE 命令准确地获取到按 序排列的数据。假设一次取3条评论,那么 公式:ZRANGEBYSCORE comments (N-3+1)  N

127.0.0.1:6379> zadd product2 1 A 2 B 3 C 4 D 5 E 6 F //权重从1开始,分别生成6条评论,此时最新权重是6
127.0.0.1:6379> ZRANGEBYSCORE product2 4 6 //第一页,获取 (6-3+1) 到 6: D E F
127.0.0.1:6379> ZRANGEBYSCORE product2 1 3 //第二页,获取 ((6-3+1)-3) 到 (6-3): A B C
127.0.0.1:6379> zadd product2 7 G //此时新增一条评论G,权重是7
127.0.0.1:6379> ZRANGEBYSCORE product2 1 3 //此时获取第二页,还是((6-3+1)-3)到(6-3),不会有重复的评论:A B C

需求:统计网页的 UV(基数统计)

基数统计就是指统计一个集合中不重复的元素个数。网页 UV 的统计有个独特的地方,就是需要去重,一个用户一天内的多次访问只能算作一次。

方法1:使用Set 类型,默认支持去重

127.0.0.1:6379> SADD page1:uv user1 //有一个用户 user1 访问 page1:返回1
127.0.0.1:6379> SADD page1:uv user1 //同一个用户再次访问 page1:返回0
127.0.0.1:6379> SCARD page1:uv //统计次数,结果是1

方法2:使用Hash类型

如果一个page1的访问量特别大,达到千万,就会消耗很大的内存。因此也可以用 Hash 类型记录 UV。

127.0.0.1:6379> HSET page1:uv user1 1  //user1访问page1,记录数值1:返回1
127.0.0.1:6379> HSET page1:uv user1 1  //再次访问,同样记录数值1,会覆盖前值:返回0
127.0.0.1:6379> HLEN page1:uv  //使用hlen查看这个page1页面有多少人访问:返回1
127.0.0.1:6379> HGETALL page1:uv //返回user1:1

方法3:使用HyperLogLog基数统计

HyperLogLog 是一种用于统计基数的数据集合类型,当集合元素数量非常多时,它计算基数所需的空间总是固定的,而且还很小。在 Redis 中,每个 HyperLogLog 只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数。但是会存在一定误差。

127.0.0.1:6379> PFADD page1:uv user1 user2 user3 user4 user5 //向 HyperLogLog 中添加新元素,返回1
127.0.0.1:6379> PFCOUNT page1:uv //返回HyperLogLog的统计结果,返回5

最后用一张图总结一下Redis的各种数据类型的特点和应用场景:

Redis命令合集和设计场景相关推荐

  1. redis命令,开发规范以及应用场景

    Redis的实际操作的相关命令,不同版本之间命令存在差异. 全部命令请查看https://redis.io/commands/ 基础命令 连接客户端 #直接连接 > redis-cli> ...

  2. redis命令详解与使用场景举例——Server(服务器)

    BGREWRITEAOF 执行一个 AOF文件 重写操作.重写会创建一个当前 AOF 文件的体积优化版本. 即使 BGREWRITEAOF 执行失败,也不会有任何数据丢失,因为旧的 AOF 文件在 B ...

  3. Redis各特性的应用场景

    Redis的六种特性 l Strings l Hashs l Lists l Sets l Sorted Sets l Pub/Sub Redis各特性的应用场景 Strings Strings 数据 ...

  4. Redis各个数据类型的使用场景

    Redis各个数据类型的使用场景 Redis支持五种数据类型: string(字符串) hash(哈希) list(列表) set(集合) zset(sorted set:有序集合). Redis列表 ...

  5. 消息中间件----内存数据库 Redis7(第3章 Redis 命令)

    Redis 根据命令所操作对象的不同,可以分为三大类:对 Redis 进行基础性操作的命令, 对 Key 的操作命令,对 Value 的操作命令. 3.1 Redis 基本命令 首先通过 redis- ...

  6. “百变”Redis带你见识不同场景下的产品技术架构

    2018飞天技术汇24期-云数据库Redis产品发布会,由阿里云数据库技术组技术专家王欢.怀听.梁盼分别带来以"Redis全球多活产品"."Redis混合存储产品&quo ...

  7. 【2020尚硅谷Java大厂面试题第三季 04】Redis 9种数据类型使用场景,分布式锁演变步骤,lua脚本,redis事务,Redisson,Redis内存占用,删除策略,内存淘汰策略,手写LRU

    1.安装redis6.0.8 2023 02 02 为:redis-7.0.8.tar.gz 2.redis传统五大数据类型的落地应用 3.知道分布式锁吗?有哪些实现方案?你谈谈对redis分布式锁的 ...

  8. Redis深入学习(三)Redis数据类型选择和应用场景

    1. 简介 Redis是一个key-value的存储系统,它的key是字符串类型,value的类型有八种(steam类型是Redis5.0新增的一种).Redis种命令是忽略大小写的,key是不忽略大 ...

  9. redis中各种数据类型对应的jedis操作命令、redis命令大全

    一.常用数据类型简介: redis常用五种数据类型:string,hash,list,set,zset(sorted set). 1.String类型 String是最简单的类型,一个key对应一个v ...

最新文章

  1. 信用评分卡模型的理论准备
  2. 靠 GitHub 打赏谋生的程序员,他们是怎么做的?
  3. 33 | 关于 Linux 网络,你必须知道这些(上)
  4. 服务器节点信息管理,华为云管理节点服务器
  5. mysql 非最佳查询_Mysql 查询优化
  6. PaaS的发展将释放物联网开发效率 ——基于云架构的物联网云平台解决方案
  7. 面试常见的八股文记录
  8. 隐马尔可夫模型(HMM)推导详解
  9. 朋友们,想去一线大厂?卷起来...
  10. 工信部发布《2018中国区块链产业白皮书》:量子计算机将给密码体系带来重大安全威胁
  11. 伦敦金实时行情今日变化多少?
  12. 由 An Intriguing Failing of Convolutional Neural Networks and the CoordConv Solution 引起的思考
  13. javascript 实现在线多币种汇率实时换算
  14. ora-12505错误
  15. CSMA/CD协议 详解
  16. 关于手机端input获取焦点呼起键盘背景图片挤压解决方案
  17. weblogic wls-wsat组件远程命令执行(CVE-2017-3506)
  18. C语言 主动判别int型出界
  19. 家庭成员介绍html模板,幼儿园介绍家庭成员教案
  20. 秦智杂志秦智杂志社秦智编辑部2022年第12期目录

热门文章

  1. 计算机组成 阶码/阶符/尾数/尾符的简单认识
  2. 计算机中阶符,阶码,数符,尾数是什么?
  3. nvm安装与常用命令
  4. Virtual Serial Port Driver Pro 11 Crack
  5. Please move or remove them before you switch branches.Aborting
  6. oppo锁频段_OPPO 5G CPE Omni发布:支持世界所有5G和4G频段
  7. 解析人脸识别中cosface和arcface(insightface)的损失函数以及源码
  8. python实现BP算法
  9. 说说css中pt、px、em、rem都扮演了什么角色
  10. 智能工厂的与众不同之处到底在哪?