Redis事务、PubSub、小对象压缩

同舟共济一一事务

Redis 事务的基本用法

multi 指示事务的开始, exec 指示事务的执行, discard 指示事务的丢弃.

所有的指令在 exec 之前不执行,而是缓存在服务器的一个事务队列中,服务器一旦收到 exec 指令,才开始执行整个事务队列,执行完毕后一次性返回所有指令的运行结果。因为 Redis 的单线程特性,它不用担心自己在执行队列的时被其他线程抢占。

原子性

事务的原子性是指事务要么全部成功, 要么全部失败,而 Redis 的事务根本不具备“原子性 ,而仅仅是满足了事务的“隔离性”中的串行化——当前执行的事务有着不被其他事务打断的权利。

discard (丢弃)

​ 用于丢弃事务缓存队列中的所有指令,在exec 执行之前。

优化

​ Redis 事务在发送每个指令到事务缓存队列时都要经过一次网络读写, 当一个事务内部的指令较多时,需要的网络 IO 时间也会线性增长, 所以通常Redis的客户端在执行事务时都会结合 pipeline 一起使用,这样可以将多次 IO 操作压缩为单次 IO 操作。

watch

​ Redis 存储了我们的账户余额数据,它是一个整数。现在有两个并发的客户端要对账户余额进行修改操作,要对余额乘以个倍数。可以通过 Redis 的分布式锁来避免并发问题冲突,分布式锁是一种悲观锁,Redis 提供了这种 watch 的机制,它就是一种乐观锁。

​ watch 会在开启事务之前盯住一个或多个关键变量,当事务执行时,服务器收到了 exec 指令顺序执行缓存的事务队列时, Redis 会检查关键变量自 watch 之后是否被修改了。如果关键变量被人改了, exec 指令就会返回 NULL 回复告知客户端事务执行失败,客户端一般会选择重试。

注意事项

Redis 禁止在 multi 和 exec 之间执行 watch 指令,而必须在 multi 之前盯住关键变量,否则会出错。

小道消息——PubSub

消息多播

​ 消息多播允许生产者只生产一次消息,由中间件负责将消息复制到多个消息队列,每个消息队列由相应的消费组进行消费,是分布式系统常用的一种解耦方式,用于将多个消费组的逻辑进行拆分。支持了消息多播,多个消费组的逻辑就可以放到不同的子系中。如果是普通的消息队列,就得将多个不同的消费组逻辑串接起来放在一个子系统中,进行连续消费。

PubSub

使用了PubSub模块来支持消息多播,全称是PublisherSubscriber(发布者/订阅者模式)。

PubSub 的消费者如果使用休眠的方式来轮询消息,也会遭遇消息处理不及时的问题。不过我们可以使用 listen 阻塞监听消息来进行处理,这点同 blpop 原理是一样的。

模式订阅

消费者订阅一个主题是必须明确指定主题的名称。如果我们想要订阅多个主题,那就 subscribe 多个名称。

subscribe key.a key.b key.c #同时订阅三个主题, 会有三条订阅成功反馈信息

publish key.a message

Redis 提供了模式订阅功能 Pattern Subscribe ,可以一次订阅多个主题,即使生产者新增加了同模式的主题,消费者可以立即收到消息。

psubscribe codehole . * #用模式匹配一次订阅多个主题,主题以 codehole 字符开头的消息都可以收到

消息结构

{’ pattern’ : None, ’ type ’:’subscribe ’,’ channel ’:’ codehole ’,’data ’: lL}

{’ pattern ’ : None, ’type ’:’message ’,'channel ’ : ’ codehole ’,’data ’ : java comes }

1 . data是消息的内容, 一个字符串。 2. channel 它表示当前订阅的主题名称。 3. type 表示消息的类型。如果是一个普通的消息,那么类型就是 message; 如果是控制消息,比如订阅指令的反馈,它的类型就是 subscribe :如果是模式订阅的反馈,它的类型就是 psubscribe ;此外还有取消订阅指令的反馈 unsubscribe 和 punsubscribe。4. pattern 表示当前消息是使用哪种模式订阅到的。如果是通过 subscribe 指令订阅的,那么这个字段就是空。

PubSub 的缺点

如果 Redis 停机重启, PubSub 的消息是不会持久化的,Redis 宕机就相当于一个消费者都没有,所有的消息会被直接丢弃。因此几乎找不到合适的应用场景。

开源节流一一小对象压缩

32bit VS 64bit

Redis 如果使用 32bit 进行编译,内部所有数据结构所使用的指针空间占用会少 一半,如果你的 Redis 使用内存不超过 4GB ,可以考虑使用 32bit 进行编译,能够节约大量内存。

小对象压缩存储(ziplist)

​ 如果 Redis 内部管理的集合数据结构很小,它会使用紧凑存储形式压缩存储。使用一维数组进行存储,需要查找时,因为元素少,进行遍历也很快,甚至可以比 HashMap 本身的查找还要快。Redis的 ziplist 个紧凑的字节数组结构。如果它存储的是 hash 结构,那么 key 和 value 会作为两个 entry被相邻存储。如果它存储的是 zset 结构,那么 value 和score 会作为两个 entry被相邻存储。

Redis 的 intset 是一个紧凑的整数数组结构,Redis 支持 set 集合动态从 uint16升级到 uint32 ,再升级到 uint64。如果 set 里存储的是字符串,那么 sadd 立即升级为 hashtable 结构。

Redis 规定小对象存储结构的限制条件

hash max-ziplist-entries 512  # hash 的元素个数超过 512 就必须用标准
结构存储
hash- max-ziplist-value 64  # hash 的任意元素的 key/value 的长度超过 64 就必须用标准结构存储
list-max-ziplist-entries 512  # list 的元素个数超过 512 就必须用标准结构存储
list-max-ziplist value 64     # list 的任意元素的长度超过 64 就必须用标准结构存储
zset-max-ziplist-entries 128  # zset 的元素个数超过 128 就必须用标准结构存储
zset-max- ziplist-value 64  # zset 的任意元素的长度超过 64 就必须用标准结构存储
set-max-intset-entries 512  # set 的整数元素个数超过 512 就必须用标准结构存储

内存回收机制

​ Redis 不一定将空闲内存立即归还给操作系统。 如果当前 Redis 内存有10GB ,当你删除了1GB 的key 后,再去观察内存发现变化不会太大。原因是操作系统是以页为单位来回收内存的,这个页上只要还有一个 key 在使用,那么它就不能被回收。 Redis 虽然删除了 1GB 的key ,但 这些 key 分散到了很多页面中,每个页面都还有其他 key 存在,这就导致了内存不会被立即回收。 如果你执行 flushdb 然后再观察内存,会发现内存确实被回收了 。因为所有的 key 都被干掉了,大部分之前使用的页面都完全干净了,就会立即被操作系统回收。 Redis 然无法保证立即回收已经删除的 key 的内存,但是它会重新使用那些尚未回收的空闲内存。

内存分配算法

Redis将内存分配的细节丢给了第三方内存分配库去实现。目前 Redis 可以使用 jemalloc ( facebook) 库采管理内存,也可以切换到 tcmalloc ( google )库。Redis 默认使用了 jemalloc,因为jemalloc 的性能相比 tcmalloc 要好。使用info memory 指令可以看到 Redis的 mem_allocator 使用了 jemalloc。

posted @ 2020-11-28 16:48  Anthony-bird  阅读(114)  评论(0编辑  收藏  举报