文章目录
- 一、事务
- 1. Redis事务相关命令:
- 2. 正常执行事务
- 3. Discard 事务
- 4. Command 命令错误(类似于java编译性错误)
- 5. 运行错误(类似于java的1/0的运行时异常)
- 6. watch
- 二、Pub/Sub 发布订阅
- 1. 订阅发布
- 2. 模式匹配订阅
- 3. 缺点
- 三、Pipeline 管道
- 四、EXPIRE Key seconds
- 1. Expire 特点:
- 2. Redis如何淘汰过期的 Keys
- 五、缓存回收策略
- 1. Maxmemory配置指令
- 2. 回收策略
- 3. 回收进程如何工作
- 4. 近似LRU算法
- 5. Java中的LRU实现方式
一、事务
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis 事务特点:
- 其中某条***命令错误***,执行后会返回错误信息,所有命令都不会执行
- 运行错误,在执行之前是没法发现的,如果出现该错误,其它命令依然会被执行
- Redis中,单条命令是原子性执行的,但事务不保证原子性,且 没有回滚。
- 批量操作在发送 EXEC 命令前被放入 队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
- 一但执行 EXEC 开启事务的执行后,无论事务使用执行成功, WARCH 对变量的监控都将被取消。所以当事务执行失败后,需重新执行WATCH 命令对变量进行监控,并开启新的事务进行操作。
- 如果你使用 WATCH 监视了一个带 过期时间 的键,那么即使这个键过期了,事务仍然可以正常执行。
1. Redis事务相关命令:
命令 | 描述 |
watch | WATCH key [key …] 监视一或多个key,WATCH 命令用于在事务 开始 之前监视任意数量的键,如果在事务 执行 之前,被监视的key被其他命令改动,则事务被打断(类似乐观锁) |
multi | 标记一个事务块的开始( queued )。 随后的指令将在执行EXEC时作为一个原子执行。它总是返回 |
exec | 行所有事务块的命令 ( 一旦执行exec后,之前加的监控锁都会被取消掉 ) |
discard | 取消事务,放弃事务块中的所有命令 |
unwatch | 取消watch对所有key的监控 |
2. 正常执行事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name1 v1
QUEUED
127.0.0.1:6379> set name2 v2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
127.0.0.1:6379>
3. Discard 事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name3 v3
QUEUED
127.0.0.1:6379> set name4 v4
QUEUED
127.0.0.1:6379> discard
OK
4. Command 命令错误(类似于java编译性错误)
则执行EXEC命令时,所有命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name3 v3
QUEUED
127.0.0.1:6379> set name4 v4
QUEUED
127.0.0.1:6379> get name1
QUEUED
127.0.0.1:6379> seet name5 v5
(error) ERR unknown command `seet`, with args beginning with: `name5`, `v5`,
# 执行事务时报错,所有命令未执行
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
5. 运行错误(类似于java的1/0的运行时异常)
则执行EXEC命令时,其他正确命令会被执行,错误命令抛出异常。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> get name1
QUEUED
127.0.0.1:6379> incr name1
QUEUED
127.0.0.1:6379> set name3 v3
QUEUED
127.0.0.1:6379> exec
1) "v1"
# 对一个字符串'V1'执行 incr,运行时错误,前后的命令正常被执行了
2) (error) ERR value is not an integer or out of range
3) OK
6. watch
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
1). 正常执行:
127.0.0.1:6379> watch name1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name3 v3
QUEUED
127.0.0.1:6379> set name4 v4
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
2). 非正常执行,在执行 exec 之前 watch 的值被修改了,执行失败:
127.0.0.1:6379> watch name1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name3 v3
QUEUED
127.0.0.1:6379> set name4 v4
QUEUED
127.0.0.1:6379> exec
# name1 已经被另一个链接修改了,执行失败。
(nil)
在上面事务执行 exec 之前开启另一个链接并修改name1
127.0.0.1:6379> set name1 v11
二、Pub/Sub 发布订阅
命令 | 描述 |
subscribe | SUBSCRIBE channel [channel …] 订阅一个或多个频道 |
unsubscribe | UNSUBSCRIBE [channel [channel …]] 退订频道,如果没有指定频道,则退订所有的频道 |
publish | PUBLISH channel message 给指定的频道发消息 |
psubscribe | PSUBSCRIBE pattern [pattern …] 订阅给定模式相匹配的所有频道 |
punsubscribe | PUNSUBSCRIBE [pattern [pattern …]]退订给定的模式,如果没有指定模式,则退订所有模式 |
1. 订阅发布
SUBSCRIBE channel [channel ...]
订阅,取消订阅和发布实现了发布/订阅消息范式,发送者(发布者)不是计划发送消息给特定的接收者(订阅者)。而是发布的消息分到不同的频道,不需要知道什么样的订阅者订阅。订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道什么样的发布者发布的。
1). 为了订阅 news 和 music,客户端发出一个订阅的频道名称:
127.0.0.1:6379> SUBSCRIBE news music
2). 再开一个客户端对这两个频道进行发布消息:
PUBLISH channel message
127.0.0.1:6379> publish news 'good news'
(integer) 1
127.0.0.1:6379> publish news 'play music'
(integer) 1
3). 订阅消息的客户端已经收到发布的消息
127.0.0.1:6379> SUBSCRIBE news music
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
1) "subscribe"
2) "music"
3) (integer) 2
1) "message"
2) "news"
3) "good news"
1) "message"
2) "news"
3) "play music"
4). 发布消息的客户端切换到其它db,并再次发布消息
127.0.0.1:6379> select 5
OK
127.0.0.1:6379[5]> publish news 'I am from db 5'
(integer) 1
5). 可以在订阅端看到同样收到了消息
127.0.0.1:6379> SUBSCRIBE news music
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "news"
3) (integer) 1
1) "subscribe"
2) "music"
3) (integer) 2
1) "message"
2) "news"
3) "good news"
1) "message"
2) "news"
3) "play music"
1) "message"
2) "news"
3) "I am from db 5"
发布/订阅与 key 所在空间没有关系,它不会受任何级别的干扰,包括不同数据库编码。 发布在db 10,订阅可以在db 1。 如果你需要区分某些频道,可以通过在频道名称前面加上所在环境的名称
2. 模式匹配订阅
PSUBSCRIBE pattern [pattern ...]
Redis 的Pub/Sub实现支持模式匹配。客户端可以订阅全风格的模式以便接收所有来自能匹配到给定模式的频道的消息。
1). psubscribe 匹配订阅 new?
, music*
127.0.0.1:6379> psubscribe new? music*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "new?"
3) (integer) 1
1) "psubscribe"
2) "music*"
3) (integer) 2
2). 发布消息的客户端分别对new1
, news-1
, music-001
三个频道各发一条消息
127.0.0.1:6379[5]> publish new1 'I am new1'
(integer) 1
127.0.0.1:6379[5]> publish news-1 'I am new-1'
(integer) 0
127.0.0.1:6379[5]> publish music-001 'all of me'
(integer) 1
3). 检查订阅端收到来自new1
, music-001
频道的消息
127.0.0.1:6379> psubscribe new? music*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "new?"
3) (integer) 1
1) "psubscribe"
2) "music*"
3) (integer) 2
1) "pmessage"
2) "new?"
3) "new1"
4) "I am new1"
1) "pmessage"
2) "music*"
3) "music-001"
4) "all of me"
3. 缺点
- 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
- 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。
三、Pipeline 管道
Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。
一个请求会分为以下步骤:
- 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
- 服务端处理命令,并将结果返回给客户端。
数据包从客户端发送到达服务器,并从服务器返回数据回复客户端。这个时间被称之为 RTT (Round Trip Time - 往返时间)。mget mset有效节约了RTT,但大部分命令(如hgetall,并没有mhgetall)不支持批量操作,需要消耗N次RTT ,这个时候需要pipeline来解决这个问题。
1). 未使用 pipeline 执行N条命令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5TUKI79X-1597586049553)(/Users/apple/Documents/md/Redis/四、Redis 发布订阅、Pipeline、事务.assets/redis request-7396823.png)]
2). 使用 pipeline 执行N条命令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DEJiKtaW-1597586049555)(/Users/apple/Documents/md/Redis/四、Redis 发布订阅、Pipeline、事务.assets/redis pipeline.png)]
3). 使用 pipeline 发送多条命令
[root@localhost ~]# (printf "set a aaaa\r\nset b bbbb\r\nset c cccc\r\n") | nc localhost 6379
+OK
+OK
+OK
[root@localhost ~]# (printf "get a\r\nget b\r\nget c\r\n") | nc localhost 6379
$4
aaaa
$4
bbbb
$4
cccc
四、EXPIRE Key seconds
1. Expire 特点:
- 为指定的
key
设置过期时间,以秒为单位。当对key重新set值的时候会清除了过期时间,但incr, lpush, hset, zrem 等对值操作的命令不会影响过时间。 - 使用 PERSIST 命令可以清除超时,使其变成一个永久的
key
。 - 如果
key
被 RENAME 命令修改,相关的超时时间会转移到新key
上面。 - EXPIRE一类命令能关联到一个有额外内存开销的key。当key执行过期操作时,Redis会确保按照规定时间删除他们。
- Keys的过期时间使用Unix时间戳存储(从Redis 2.6开始以毫秒为单位)。这意味着即使Redis实例不可用,时间也是一直在流逝的。例如设置了一个 key 的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后。
- 使用watch监控一个键,如果该键过期被清除,不会被认为该键被改变
- 返回1表示设置成功,0表示键不存在或设置失败
127.0.0.1:6379> expire name 10
2. Redis如何淘汰过期的 Keys
Redis keys过期有两种方式:被动和主动方式。
- 当一些客户端尝试访问它时,key会被发现并主动的过期。
当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。 - 具体就是Redis每秒10次做的事情:
1). 测试随机的20个keys进行相关过期检测。
2). 删除所有已经过期的keys。
2). 如果有多于25%的keys过期,重复步奏1.
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。
五、缓存回收策略
1. Maxmemory配置指令
通过redis.conf可以设置 maxmemory 的大小,或者之后使用CONFIG SET命令来进行运行时配置。
例如为了配置内存限制为1024mb,在 /etc/redis/6379.conf 文件中找到maxmemory,并设置内存:
maxmemory 1024mb
设置 maxmemory 为0代表没有内存限制。对于64位的系统这是个默认值,对于32位的系统默认内存限制为3GB。当指定的内存限制大小达到时,需要选择不同的行为,也就是回收策略。
2. 回收策略
- LRU: means Least Recently Used
- LFU: means Least Frequently Used
当指定的内存限制大小达到时,需要选择不同的行为,也就是策略。 Redis可以仅仅对命令返回错误,这将使得内存被使用得更多,或者回收一些旧的数据来使得添加数据时可以避免内存限制。
- volatile-lru -> 回收 最久 没有使用的键(近似 LRU),但仅限于在过期集合的键。
- allkeys-lru -> 所有键中回收 最久 使用的键(近似 LRU)。
- volatile-lfu -> 回收使用 频率最少 的键(近似 LFU),但仅限于在过期集合的键。
- allkeys-lfu -> 所有键中回收使用 频率最少 的键(近似 LFU)。
- volatile-random -> 回收 随机 的键,但仅限于在过期集合的键。
- allkeys-random -> 所有键中回收 随机 的键。
- volatile-ttl ->回收在过期集合的键,并且 优先回收存活时间(TTL)较短 的键。
- noeviction -> 直接返回错误,当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)。
在 /etc/redis/6379.conf 文件中找到maxmemory_policy,可以设置回收策略:
maxmemory_policy volatile-lru
3. 回收进程如何工作
回收进程如何工作的:
- 一个客户端运行了新的命令,添加了新的数据。
- Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
- 一个新的命令被执行,等等。
- 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
4. 近似LRU算法
Redis的LRU, LFU and minimal TTL 算法并非完整的实现。它会尝试运行一个近似LRU的算法,通过对少量keys进行取样,然后回收其中一个最好的key。默认是会检查5个 Keys,并且按设定好的回收策略回收其中的一个。
可以通过调整每次回收时检查的采样数量,以实现调整算法的精度。可以通过 /etc/redis/6379.conf 的配置调整:
maxmemory-samples 5
默认采样5个一般是最优的了;采样10个就会非常接近真识的LRU算法了,但需要消耗更多的CPU资源;采样3个会非常的快,但会降低精准度。
5. Java中的LRU实现方式
使用HashMap结合双向链表,HashMap的值是双向链表的节点,双向链表的节点也保存一份key value。
- 新增key value的时候首先在链表结尾添加Node节点,如果超过LRU设置的阈值就淘汰队头的节点并删除掉HashMap中对应的节点。
- 通过Map访问key对应的值,同时将访问的Node节点移动到队尾。
- 修改key对应的值的时候先修改对应的Node中的值,然后把Node节点移动队尾。