ZCache使用场景

String应用场景

场景一: Key-value键值对

使用String字符串来作为key-value键值对来使用,是String类型最广泛的应用场景。

场景二:可复位的计数器

组合使用GETSET和INCR ,可实现一个有原子性复位操作的计数器。例如每次当某个事件发生时,进程可能对一个名为mycount的key调用INCR操作,通常我们还要在一个原子时间内同时完成获得计数器的值和将计数器值复位为 0 两个操作。

场景三:限速器

限速器是特殊化的计数器,它用于限制一个操作可以被执行的速率(rate)。

限速器的典型用法是限制公开 API 的请求次数,以下是一个限速器实现示例,它将 API 的最大请求数限制在每个 IP 地址每秒钟十个之内:

FUNCTION LIMIT_API_CALL(ip):
value = INCR(ip)               # 计数器递增
IF value == 1 THEN
    EXPIRE(ip,1)                # 设置计数器生存周期为1秒钟
END
IF value > 10 THEN         # 超出使用次数限制
    ERROR "too many requests per second"
ELSE
    PERFORM_API_CALL()
END

这个实现每秒钟为每个 IP 地址使用一个不同的计数器,并用 EXPIRE 命令设置生存时间(这样 Redis 就会负责自动删除过期的计数器)。

场景四: 简单的锁实现

使用命令如下命令可以实现一种简单的锁

客户端执行以上的命令:

– 如果服务器返回 OK ,那么这个客户端获得锁。业务处理完毕后使用del删除锁。

– 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。

【设置的过期时间到达之后,锁将自动释放。以便客户端异常时锁能自动恢复状态】

可以通过以下修改,让这个锁实现更健壮(可以防止持有过期锁的客户端误删现有锁的情况出现):

– 不使用固定的字符串作为键的值,而是设置一个长随机字符串,作为口令串。

– 不使用DEL命令来释放锁,而是发送一个Lua脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。

if redis.call(“get”,KEYS[1]) == ARGV[1]  # 口令串验证
then
    return redis.call\("del",KEYS\[1\]\)
else
    return 0
end

场景五:用户上线次数统计

Bitmap 对于一些特定类型的计算非常有效。

假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 SETBIT 和 BITCOUNT 来实现。

比如说,每当用户在某一天上线的时候,我们就使用 SETBIT ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的为设置为 1 。

– 例如今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。

– 当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。

场景六:时间序列(Time series)

使用APPEND命令可以为一系列定长数据提供一种紧凑的表示方式,通常称之为时间序列(实际就是作为数组来使用)。每当一个新数据到达的时候,执行以下命令:

然后可以通过以下的方式访问时间序列的各项属性:

– 使用STRLEN获得时间序列中数据的数量

– 使用GETRANGE可以对时间序列进行随机访问。

– 使用SETRANGE可以用于覆盖或修改已存在的的时间序列。

注: 这种方式实现的时间序列的唯一缺陷是只能增长时间序列,而不能对时间序列进行缩短,但是总得来说,这个方式的储存还是可以节省下大量的空间。

List应用场景

场景一: 任务队列系统

任务派发者使用LPUSH向列表头插入数据;而任务执行者可以使用BRPOP阻塞接收任务进行处理。

场景二:保留最新的N条记录

例如保留最新的100条日志记录,每次将最新日志newest_log放到log列表中,然后使用LTRIM只保留最新的100项:

LPUSH log newest_log # 写入log列表

LTRIM log 0 99 # 保留最新的100项,因为平均情况下,每次只有一个元素被删除,所以效率会很高

场景三:循环任务列表

通过使用相同的key作为RPOPLPUSH命令的两个参数,客户端可以用一个接一个地获取列表元素的方式,取得列表的所有元素。

这个模式使得我们可以很容易实现这样一类系统:有N个客户端,需要循环处理一些任务。使用这个模式的客户端是易于扩展且安全的,因为就算接收到元素的客户端失败,元素还是保存在列表里面,不会丢失,等到下个迭代来临的时候,别的客户端又可以继续处理这些元素了。

场景四: 事件提醒

有时候为了等待一个新元素到达,需要使用轮询的方式对数据进行查询。

另一种更好的方式是,使用系统提供的阻塞原语,在新元素到达时立即进行处理,而新元素还没到达时,就一直阻塞住,避免轮询占用资源。

Set应用场景

场景一: 过滤功能

通过集合的uniq特性,可使用SISMEMBER来检测元素是否存在于集合中,从而达到过滤检查的效果。

场景二:数据排重统计

例如想要知道某些特定的注册用户或IP地址,他们到底有多少访问了某篇文章,那么每次获得一次新的页面浏览时只需要这样做:

SADD page:day1:<page_id> <user_id>

通过SADD将用户信息加入到集合page:day1:<page\_id>中,此过程会自动进行排重处理;

通过SCARD可以方便获取访问该文章的用户数量;

而通过SISMEMBER 也可以检测出某个用户是否访问了该文章。

SortedSet应用场景

场景一: 排行榜系统

当需要用一系列整数进行排序,或者需要对数值进行一系列操作的情况都适用有序集合。

最常见的应用就是排行榜系统:以排行数值作为score,对象作为元素添加到有序集合中。可以使用ZRANK获取对象的排名;使用ZSCORE获取对象的分值;使用ZRANGE等获取Top排行。

场景二: 有序时间队列

当以时间作为有序集合的score,可以构建一个有序时间队列。

因为集合中天然的会按照时间进行排序,相比于链表,使用有序集合可以方便的按时间范围进行数据的获取、统计等操作。

场景三: 权重消息队列

使用有序集合也可以实现简单的权重消息队列。

例如普通消息的socre为1,重要消息的score为2,那么工作线程可以选择按score的倒序来获取队列任务,让重要的任务优先执行。

注:因为有序集合没有类似BPOP的阻塞功能,所以在作为消息队列使用时需要使用list的事件提醒方式

Hash应用场景

场景一: 存储JSON对象

将json对象的属性以域值对的形式存储于哈希表中,可以方便的只读取/更新对象的某些属性\(同时也支持对域的批量读取、写入操作,可以减少网络开销\),而不需要每次都存取整个对象内容;另外不同的应用模块可以只更新自己关心的属性而不会互相并发覆盖冲突。

场景二:创建索引

比如User对象,除了id有时还要按name来查询。可以有如下的数据记录:

(String) user:101 -> {“id”:101,”name”:”calvin”…}

(String) user:102 -> {“id”:102,”name”:”kevin”…}

(Hash) user:index-> “calvin”->101, “kevin” -> 102

用户名索引user:index使用哈希表存储;哈希的域存储name;而域的值即为id。

注:1. 也可以使用List、Set、Zset等来为字段建立索引

2.无论表数据还是索引都应该使用多键的组成方式,来保障扩展性

HyperLogLog应用场景

场景一:唯一计数近似统计

HyperLogLog就是为实现该类场景而存在,其标准误差率低于1%。

例如要统计每天拨打电话的用户数,当用户成功拨通电话时,则执行:

PFADD key_Date 主叫号码

当要统计该天拨打电话的用户数时,执行PFCOUNT key\_Date即可。

注:当然使用集合也可以用来实现该类场景,而且可以得到精确统计值,但是使用集合将带来大量的内存消耗。

Pub/Sub应用场景

场景一:通知栏推送

通知发布系统通过PUB/SUB功能实现,客户端订阅指定的频道,来及时展示通知信息。

场景二:聊天室功能

创建聊天室即是制定一个聊天室频道;而加入聊天室则是订阅该频道;

通过向聊天室频道发布信息,来推送给各个聊天室客户端。

场景三:参数类缓存数据本地更新

客户端启动单独的线程来更新本地缓存,其通过订阅参数类变更通知来感知参数的修改,然后修改本地缓存相应的参数项;

而对于修改分布式缓存中参数的客户端,在修改参数后,要主动发布一条更新消息(或定制一个修改参数并发布消息的lua脚本,客户端使用该脚本进行参数修改)。

Lua脚本应用场景

场景一:键值关联操作

例如基于key1的value生成key2的value:

首先使用redis.call获取KEYS\[1\]的值,然后使用该值构造生成KEY\[2\]的域信息。

注:这种关联操作,如果不使用Lua脚本,只能由客户端与缓存服务交互两次进行处理。

场景二:CAS类逻辑

例如当key1满足条件时,才处理key2

注:这种关联操作,如果不使用Lua脚本,只能由客户端与Redis交互多次进行处理,同时因为几次操作之间不是原子性的,所以多客户端情况下会有并发冲突,导致真正实现起来会更加复杂

results matching ""

    No results matching ""