为什么要用Redis/为什么要用缓存?

  1. 高性能
  • Redis/缓存中的数据是放在内存中的,读取速度比读取数据库快,因此将高频数据放在Redis中,可以提高数据读取效率。
  1. 高并发
  • MySQL之类的数据库QPS(每秒查询次数)大约1W,但是使用Redis缓存之后可以很容易达到10W+,这还只是单机Redis的情况,采用集群的化更高,可以满足高并发事件。

Redis可以用来做什么?

  1. 缓存
  • 可以将热点数据存放在Redis中,这样请求时就可以直接从Redis中读取,因为Redis中的数据是存放在内存中的,因此读取速度快,可以用来做缓存,降低数据库压力。
  1. 消息队列
  • Redis的list数据结构可以作为一个简单的消息队列使用。
  • Redis5.0中新增的Stream数据结构也可以作为消息队列,类似Kafka,有主题和消费组的概念,支持持久化和ACK机制。
  1. 限流
  • 通过Redis + Lua脚本可以实现限流
  1. 分布式锁
  • 采用Redis扩展-Redission来实现
  1. 复杂业务场景
  • 采用Redis的bitmap数据结构统计活跃用户
  • 采用sorted set维护排行榜

Redis常见数据结构&使用场景

String

  1. 常用命令
    • set key val
    • get key
    • exists key
    • strlen key
    • del key
127.0.0.1:6379> set key value #设置 key-value 类型的值
OK
127.0.0.1:6379> get key # 根据 key 获得对应的 value
"value"
127.0.0.1:6379> exists key  # 判断某个 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。
(integer) 5
127.0.0.1:6379> del key # 删除某个 key 对应的值
(integer) 1
127.0.0.1:6379> get key
(nil)
  • mset key1 val1 key2 val2
  • mget key1 key2
OK
127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
1) "value1"
2) "value2"
  • expire key time
  • setex key time val
  • ttl key
127.0.0.1:6379> expire key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56
  • set key val[val == 整数long / 双精度浮点数double,即使是字符串只要能被解释为整数都可以对其进行增、减操作,空串或者不存在的key会当成0处理]
  • incr key[自增1]
  • get key
  • decr key[自减1]
  • incrby key amount[在原有值上加上long,long值可以为负数]
  • decrby key amount[原有值 - long]
  • incrbyfloat key amount[原有值 + double]
  • 无 decrbyfloat key amount 命令
OK
127.0.0.1:6379> incr number # 将 key 中储存的数字值增一
(integer) 2
127.0.0.1:6379> get number
"2"
127.0.0.1:6379> decr number # 将 key 中储存的数字值减一
(integer) 1
127.0.0.1:6379> get number
"1"
  1. 使用场景
    • 当做计数器使用
      • 获取用户访问次数
      • 热点文章的点赞转发数量

list

  1. 常用命令
    • lpush list val1 [val2 ...]
    • lpop list
    • lindex list index
    • lrange list startIndex[从0开始] endIndex[-1表示所有元素] --[可以实现分页查询]
    • llen
    • ltrim list startIndex endIndex
    • rpush list val1 [val2 ...]
    • rpop list
# lpush & rpush,返回当前列表的长度
127.0.0.1:6379> lpush list xx
(integer)1
127.0.0.1:6379> lrange list 0 -1
1)"xx"
127.0.0.1:6379> rpush list yy
(integer)2
127.0.0.1:6379> lindex list 1
"yy"

# lpop & rpop,移除并返回弹出的值
127.0.0.1:6379> rpop list 
"yy"
127.0.0.1:6379> lpop list
"xx"
127.0.0.1:6379> lrange list 0 -1
(empty list or set) # 目前为空列表
127.0.0.1:6379> llen list
(integer)0

# ltrim: 只保留 [startIndex, endIndex] 闭区间的元素
127.0.0.1:6379> rpush list aa
(integer) 1
127.0.0.1:6379> rpush list bb
(integer) 2
127.0.0.1:6379> rpush list cc
(integer) 3
127.0.0.1:6379> rpush list dd
(integer) 4
127.0.0.1:6379> rpush list ee
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "aa"
2) "bb"
3) "cc"
4) "dd"
5) "ee"
127.0.0.1:6379> ltrim list 2 -1
OK
127.0.0.1:6379> lrange list 0 -1
1) "cc"
2) "dd"
3) "ee"

阻塞式弹出命令 & 列表间移动命令

  • blpop key1 [key2 ...] timeout
  • brpop key1 [key2 ...] timeout
  • rpoplpush keyFrom keyTo
  • brpoplpush keyFrom keyTo timeout
127.0.0.1:6379> rpush yh aa bb cc dd ee
(integer) 5
127.0.0.1:6379> lrange yh 0 -1
1) "aa"
2) "bb"
3) "cc"
4) "dd"
5) "ee"
127.0.0.1:6379> rpush bx mm nn xx yy zz
(integer) 5
127.0.0.1:6379> lrange bx 0 -1
1) "mm"
2) "nn"
3) "xx"
4) "yy"
5) "zz"

# rpoplpush: 从 keyFrom 弹出最右端元素,并压入 keyTo 最左端,并向用户返回这个元素
127.0.0.1:6379> rpoplpush yh bx
"ee" # 将 yh 最右端元素 ee 压入 bx 最左端
127.0.0.1:6379> lrange yh 0 -1
1) "aa"
2) "bb"
3) "cc"
4) "dd"
127.0.0.1:6379> lrange bx 0 -1
1) "ee"
2) "mm"
3) "nn"
4) "xx"
5) "yy"
6) "zz"

# brpoplpush: 同 rpoplpush,若 keyFrom 为空,则在 timeout 秒内阻塞并等待可弹出的元素出现
127.0.0.1:6379> brpoplpush yh bx 2
"dd"
127.0.0.1:6379> lrange yh 0 -1
1) "aa"
2) "bb"
3) "cc"
127.0.0.1:6379> lrange bx 0 -1
1) "dd"
2) "ee"
3) "mm"
4) "nn"
5) "xx"
6) "yy"
7) "zz"

# blpop: 从第一个非空列表开始每次弹出最左端元素,或在 timeout 秒内阻塞并等待可弹出的元素出现
127.0.0.1:6379> blpop bx yh 1
1) "bx"
2) "dd" # bx 列表非空,从它开始弹栈
127.0.0.1:6379> blpop bx yh 1
1) "bx"
2) "ee"
127.0.0.1:6379> blpop bx yh 1
1) "bx"
2) "mm"
127.0.0.1:6379> blpop bx yh 1
1) "bx"
2) "nn"
127.0.0.1:6379> blpop bx yh 1
1) "bx"
2) "xx"
127.0.0.1:6379> blpop bx yh 1
1) "bx"
2) "yy"
127.0.0.1:6379> blpop bx yh 1
1) "bx"
2) "zz" # 此时 bx 列表已空,yh 非空,下一次弹 yh 中的元素
127.0.0.1:6379> blpop bx yh 1
1) "yh"
2) "aa" # yh 列表弹栈
127.0.0.1:6379> lrange yh 0 -1
1) "bb"
2) "cc"
127.0.0.1:6379> lrange bx 0 -1
(empty list or set)

# brpop
127.0.0.1:6379> brpop bx yh 1
1) "yh"
2) "cc"
127.0.0.1:6379> brpop bx yh 1
1) "yh"
2) "bb"
127.0.0.1:6379> lrange yh 0 -1
(empty list or set)
127.0.0.1:6379> brpop bx yh 1
(nil)
(1.02s) # yh & bx 均空,等待时间内也没有元素可弹出

【注1】由于没有 rrange 和 rlen 命令,因此不论是实现队列还是栈,最好是使用 rpush 从右侧压栈,再通过 lpop 左侧弹栈实现队列,或者 rpop 右侧弹栈实现栈。

  1. 使用场景
    • 发布与订阅
    • 消息传递
    • 任务队列
    • 慢查询

hash

  1. 常用命令
    • hmset object key1 val1 key2 val2 ...
    • hexists object key1/key2/...
    • hget object key1/key2/...
    • hgetall object
    • hkeys object
    • hvals object
    • hset object key1/key2/... newval1/newval2/...
127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24"
OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value中指定的字段是否存在。
(integer) 1
127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。
"guide"
127.0.0.1:6379> hget userInfoKey age
"24"
127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
1) "name"
2) "description"
3) "age"
127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
1) "guide"
2) "dev"
3) "24"
127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某个字段对应的值
127.0.0.1:6379> hget userInfoKey name
"GuideGeGe"
  1. 使用场景
    • 存储对象数据,例如JSONObject

set

  1. 常用命令
    • sadd set val1 [val2 ...]
    • spop set [count] --[由于set存储不连续,所以不确定下一个弹出来的是什么]
    • smembers myset
    • sismember set val1/val2/...
    • sinsterstore newset set1 [set2 set3 ...] - - [求 set1 (和 set2 ... )交集存入 newset 中]
    • sunion new set set1 [set2 set3 ...]
(integer) 2
127.0.0.1:6379> sadd mySet value1 # 不允许有重复元素
(integer) 0
127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
1) "value1"
2) "value2"
127.0.0.1:6379> scard mySet # 查看 set 的长度
(integer) 2
127.0.0.1:6379> sismember mySet value1 # 检查某个元素是否存在set 中,只能接收单个元素
(integer) 1
127.0.0.1:6379> sadd mySet2 value2 value3
(integer) 2
127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交集并存放在 mySet3 中
(integer) 1
127.0.0.1:6379> smembers mySet3
1) "value2"
  1. 使用场景
    • 存放数据不能重复
    • 求交并集

sorted set

  1. 常用命令
    • zadd zset score1 val1 score2 val2 ...
    • zcard zset
    • zscore zset val1/val2/...
    • zrange zset startIndex endIndex --[按照score值从小到大排列]
    • zrevrange zset startIndex endIndex -- [从大到小]
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多个元素
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3"
127.0.0.1:6379> zrange  myZset 0 -1 # 顺序输出某个范围区间的元素,0 -1 表示输出所有元素
1) "value3"
2) "value2"
3) "value1"
127.0.0.1:6379> zrange  myZset 0 1 # 顺序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value3"
2) "value2"
127.0.0.1:6379> zrevrange  myZset 0 1 # 逆序输出某个范围区间的元素,0 为 start  1 为 stop
1) "value1"
2) "value2"
  1. 使用场景
    • 礼物排行榜
    • 直播间排行榜
    • 弹幕消息

bitmap

  1. 常用指令
    • setbit key index val[0/1]
    • getbit key index
    • bitcount key
    • bitop operation newkey key1 key2 [key3 ...]
    • append key value
    • setrange key index value
    • getrange key index value
    • setbit key index value
    • getbit key index value
127.0.0.1:6379> setbit mykey 7 1
(integer) 0
127.0.0.1:6379> getbit mykey 7
(integer) 1
127.0.0.1:6379> setbit mykey 8 1
(integer) 0
# 通过 bitcount 统计被被设置为 1 的位的数量。
127.0.0.1:6379> bitcount mykey
(integer) 2
# bitop对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
# bitop 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数
# bitop operation destkey key [key ...]
127.0.0.1:6379> bitop and desk1 20210308 20210309

127.0.0.1:6379> set tmp 5
OK
# append: 将val添加到key末尾,返回当前字符串长度,key可以不存在默认为空串之后key便存在
127.0.0.1:6379> append tmp 1
(integer) 2
127.0.0.1:6379> get tmp
"51"
# setrange: 将从index开始的子串设置为val,index加上val长度可以超出原有val长度,返回当前字符串长度
127.0.0.1:6379> set tmp 123456
OK
127.0.0.1:6379> setrange tmp 0 yh
(integer) 6
127.0.0.1:6379> get tmp
"yh3456" # yh 替换了 12
127.0.0.1:6379> setrange tmp 8 bx
(integer) 10
127.0.0.1:6379> get tmp
"yh3456\x00\x00bx" # 将本来不存在的index从8开始替换为了 bx,tmp 长度变长
  1. 使用场景
    • 保持用户状态信息,例如是否登录,是否签到,是否点赞或者转发等
      • 获取/统计用户在线状态(例1)
      • 统计活跃用户(例2)
      • 用户行为分析,点赞或者转发了哪些内容(例3)
setbit (自定义)key (用户id)index 0/1
// 例 1:
127.0.0.1:6379> setbit online 2754179 1

setbit (日期)key (用户id)index 0/1
// 例 2:
127.0.0.1:6379> setbit 20211207 2754179 1
(integer) 0
127.0.0.1:6379> setbit 20211207 9643884 1
(integer) 0
127.0.0.1:6379> setbit 20211208 2754179 1
(integer) 0

# 统计21/12/07-21/12/08两天都在线用户数 -- [1]
127.0.0.1:6379> bitop and andbit 211207 211208
(integer) 1205486
127.0.0.1:6379> bitcount andbit
(integer) 1

# 统计21/12/07-21/12/08两天至少一天在线用户数 -- [2]
127.0.0.1:6379> bitop or orbit 211207 211208
(integer) 1205486
127.0.0.1:6379> bitcount orbit
(integer) 2

setbit (自定义)key (用户id)index 0/1
// 例 1:
127.0.0.1:6379> setbit article123456 2754179 1

Redis线程详解

  1. 单线程
    1. Redis单线程如何监听大量的客户端连接?
      • Redis采用 IO 多路复用 来监听大量客户端连接,从而使得 Redis 不需要创建多线程来监听大量连接,从而降低了资源消耗。
    2. Redis6.0 之前为什么不采用多线程?
      • 单线程编程容易,且易于维护
      • 多线程主要用于提高CPU利用率,而Redis的瓶颈不在于CPU,而在于内存和网络
      • 采用多线程存在死锁、上下文切换等问题,甚至影响性能

  2. 多线程
    1. Redis6.0 之后为什么引入多线程?
      • 用于提高网络IO读写性能
      • 虽然引入多线程用于网络数据IO读写,但执行指令还是单线程,因此不需要担心线程安全
      • 但是多线程默认禁用,如果需要开启多线程,则需要修改配置文件redis.conf,开启多线程和设置线程数
io-threads-do-reads yes
#官网建议4核的机器建议设置为2或3个线程,8核的建议设置为6个线程
io-threads 4

Redis为什么要设置过期时间?