继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Redis 数据结构与内存管理策略(上)

慕虎7371278
关注TA
已关注
手记 1125
粉丝 201
获赞 871

Redis 数据类型特点与使用场景

redis 为我们提供了 5 种数据类型,基本上我们使用频率最高的就是 string ,而对其他四种数据类型使用的频次稍弱于 string 。

一方面是由于 string 使用起来比较简单,可以方便存储复杂大对象,使用场景比较多。还有一个原因就是由于 redis expire time 只能设置在 key 上,像 listhashsetzset 属于集合类型,会管理一组 item,我们无法在这些集合的 item 上设置过期时间,所以使用 expire time 来处理集合的 cache 失效会变得稍微复杂些。但是 string 使用 expire time 来管理过期策略会比较简单,因为它包含的项少。这里说的集合是宽泛的类似集合。

导致我们习惯性的使用 string 而忽视其他四种数据类型的另一个深层次原因,大多是由于我们对另外四种数据类型的使用和原理不是太了解。这个时候往往会忽视在特定场景下使用某种数据类型可能会比 string 性能高出很多,比如使用 hash 结构来提高某个实体的某个项的修改等。

这里我们不打算罗列这 5 种数据类型的使用方法,这些资料网上有很多。我们主要讨论这 5 种数据类型的功能特点,这些特点分别适合用于处理哪些现实的业务场景,最重要的是我们如何组合性的使用这 5 种数据类型来解决复杂的 cache 问题。

String、List、Hash、Set、Zset

String

string 是 redis 提供的字符串类型。可以针对 string 类型独立设置 expire time 。通常用来存储长字符串数据,比如,某个对象的 json 字符串。

string 类型我们在使用上最巧妙的是可以动态拼接 key。通常我们可以将一组 id 放在 set 里,然后动态查找 string 还是否存在,如果不存在说明已经过期或者由于数据修改主动 delete 了,需要再做一次 cache 数据 load 。

虽然 set 无法设置 item 的过期时间,但是我们可以将 set item 与 string key 关联来达到相同的效果。

webp

上图中的左边是一个 key 为 set:order:ids 的 set 集合,它可能是一个全量集合,也可能是某个查询条件获取出来的一个集合。

有时候复杂点的场景需要多个 set 集合来支撑计算,在 redis 服务器 里可能会有很多类似这样的集合。

这些集合我们可以称为 功能数据,这些数据是用来辅助 cache 计算的,当进行各种集合运算之后会得出当前查询需要返回的子集,最后我们才会去获取某个订单真正的数据。

这些 string:order:{orderId} 字符串 key 并不一定是为了服务一种场景,而是整个系统最底层的数据,各种场景最后都需要获取这些数据。那些 set 集合可以认为是查询条件数据,用来辅助查询条件的计算。

redis 为我们提供了 TYPE 命令来查看某个 key 的数据类型,如:string 类型:

SETstring:order:100order-100TYPEstring:order:100string

List

list 在提高 throughput 的场景中非常适用,因为它特有的 LPUSHRPUSHLPOPRPOP 功能可以无缝的支持生产者、消费者架构模式。

这非常适合实现类似 Java Concurrency Fork/Join 框架中的 work-stealing 算法 (工作窃取) 。

java fork/join 框架使用并行来提高性能,但是会带来由于并发 take task 带来的 race condition (竞态条件) 问题,所以采用 work-stealing 算法 来解决由于竞争问题带来的性能损耗。

webp

上图中模拟了一个典型的支付 callback 峰值场景。在峰值出现的地方一般我们都会使用加 buffer 的方式来加快请求处理速度,这样才能提高并发处理能力,提高 throughput 。

支付 gateway 收到 callback 之后不做任何处理直接交给 分发器 。分发器 是一个无状态的 cluster ,每个 node 通过向 注册中心 pull handler queue list,也就是获取下游处理器注册到注册中心里的消息通道。

每一个分发器 node 会维护一个本地 queue list ,然后顺序推送消息到这些 queue list 即可。这里会有点小问题,就是 支付 gateway 调用分发器的时候是如何做 load balance ,如果不是平均负载可能会有某个 queue list 高出其他 queue list 。

而分发器不需要做 soft load balance ,因为哪怕某个 queue list 比其他 queue list 多也无所谓,因为下游 message handler 会根据 work-stealing 算法来窃取其他消费慢的 queue list 。

redis list 的 LPUSHRPUSHLPOPRPOP 特性确实可以在很多场景下提高这种横向扩展计算能力。

Hash

hash 数据类型很明显是基于 hash 算法的,对于项的查找时间复杂度是 O(1) 的,在极端情况下可能出现项 hash 冲突问题,redis 内部是使用链表加 key 判断来解决的。具体 redis 内部的数据结构我们在后面有介绍,这里就不展开了。

hash 数据类型的特点通常可以用来解决带有映射关系,同时又需要对某些项进行更新或者删除等操作。如果不是某个项需要维护,那么一般可以通过使用 string 来解决。

如果有需要对某个字段进行修改,使用 string 很明显是会多出很多开销,需要读取出来反序列化成对象然后操作,然后再序列化写回 redis ,这中间可能还有并发问题。

那我们可以使用 redis hash 提供的实体属性 hash 存储特性,我们可以认为 hash value 是一个 hash table ,实体的每一个属性都是通过 hash 得到属性的最终数据索引。

webp

上图使用 hash 数据类型来记录页面的 a/b metrics ,左边的是首页 index 的各个区域的统计,右边是营销 marketing 的各个区域统计。

在程序里我们可以很方便的使用 redis 的 atomic 特性对 hash 某个项进行累加操作。

HMSEThash:mall:page:ab:metrics:indextopbanner10leftbanner5rightbanner8bottombanner20productmore10topshopping8OK

HGETALLhash:mall:page:ab:metrics:index1)"topbanner"2)"10"3)"leftbanner"4)"5"5)"rightbanner"6)"8"7)"bottombanner"8)"20"9)"productmore"10)"10"11)"topshopping"12)"8"

HINCRBYhash:mall:page:ab:metrics:indextopbanner1(integer)11

使用 redis hash increment 进行原子增加操作。HINCRBY 命令可以原子增加任何给定的整数,也可以通过 HINCRBYFLOAT 来原子增加浮点类型数据。

Set

set 集合数据类型可以支持集合运算,不能存储重复数据。

set 最大的特点就是集合的计算能力,inter 交集union 并集diff 差集,这些特点可以用来做高性能的交叉计算或者剔除数据。

set 集合在使用场景上还是比较多和自由的。举个简单的例子,在应用系统中比较常见的就是商品、活动类场景。用一个 set 缓存有效商品集合,再用一个 set 缓存活动商品集合。如果商品出现上下架操作只需要维护有效商品 set ,每次获取活动商品的时候需要过滤下是否有下架商品,如果有就需要从活动商品中剔除。

当然,下架的时候可以直接删除缓存的活动商品,但是活动是从 marketing 系统中 load 出来的,就算我将 cache 里的活动商品删除,当下次再从 marketing系统中 load 活动商品时候还是会有下架商品。当然这只是举例,一个场景有不同的实现方法。

webp



作者:java菜
链接:https://www.jianshu.com/p/7a7df6c0f8e8


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP