一日一技:这个东西能给 Redis 插上火箭

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 一日一技:这个东西能给 Redis 插上火箭

摄影:产品经理黄金蘑菇

我们知道,用 Redis 的 Hash 可以实现一对多的映射,就像是 Python 的字典一样,例如:

但如果我们需要实现多对多怎么办?我举一个例子,我们要用 Redis 实现一个英语词典。有10000个单词,每个单词对应1-3个中文意思。例如:

  • resume: 重新开始;简历
  • close: 靠近;关闭
  • hello: 你好
  • address: 地址;致辞

如果这些词和中文是已经对应好的,那么显然我们直接用 Hash 就可以了,如下图所示:

eng_dict = {'resume': '重新开始;简历', 'close': '靠近;关闭', 'hello': '你好', 'address': '地址;致辞'}
client.hmset('eng_dict', eng_dict)

但是现在问题来了,如果我们要实现实时添加怎么办?例如一开始,close这个词只有一个意思:靠近,现在我要添加另一个意思,你觉得是否可以这样写:

new_chinese = '关闭'
chinese = client.hget('eng_dict', 'close')
if chinese:
    chinese = f'{chinese.decode()};{new_chinese}'
else:
    chinese = new_chinese
client.hmset('eng_dict', {'close': chinese})

这样写,在你一个人操作的时候,确实没有问题。但是,假如有两个人同时要修改这个词的中文意思怎么办?close这个词还有吝啬的的意思。如果两个人要添加这个词的中文意思,并且两个人的代码几乎同时运行到chinese = client.hget('eng_dict', 'close')这一行代码。此时,他们获取到的中文意思,都只有靠近这一个。但是甲先更新了关闭的意思,然后乙再更新了吝啬的的意思。此时就会导致甲的修改被覆盖。

为了解决这个问题,使用锁是一个思路。但今天我们不用锁,而是使用另一个方案。

在使用 Redis 的字符串时,我们可以使用 append 命令,原子性地在字符串末尾追加新的字符串,如下图所示:

但是,Hash 没有这个命令。如果你翻看redis-py这个库的官方文档,也许你会惊喜地发现,似乎使用Pipeline + Watch[1]可以实现你的需求:

先别高兴地太早,你仔细看一下watch命令监控的对象是什么。watch监控的是一个key,而不是 Hash 里面的field。但同一时间,可能会有其他人修改其他field。这就会导致 watch 总是失败。

在这种情况下,是时候使用 Redis 的内置 Lua 脚本了。你可以把一段 Lua 脚本发送到 Redis 中,它会被原子性地执行。

那么,如果使用redis-py这个库来执行 Lua 脚本呢?在官方文档上也给出了一个示例[2],如下图所示:

于是,我们可以仿照它的写法,来实现一个 Lua 版本的 Hash Append 命令:

import redis
client = redis.Redis()
def register_redis_lua():
    lua = '''
            local key = KEYS[1]
            local field = ARGV[1]
            local new_chinese = ARGV[2]
            local chinese_to_update = ""
            if redis.call('HEXISTS', key, field) == 1 then
                local chinese = redis.call('HGET', key, field)
                chinese_to_update = chinese .. ';' .. new_chinese
            else
                chinese_to_update = new_chinese
            end
            redis.call('HSET', key, field, chinese_to_update)
            '''
    lua_instance = client.register_script(lua)
    return lua_instance
automic_hash_append = register_redis_lua()
def hash_append(key, field, new_chinese):
    automic_hash_append(keys=[key], args=[field, new_chinese])

其中,我们调用register_lua方法,返回一个脚本实例,这个实例接收两个参数,keysargs,他们都是列表。这个脚本对象只需要注册一次,就可以在整个运行时持续使用。

我们来测试一下,首先,在 key 不存在的时候,它会把当前的值添加到 Hash 中:

现在已经close已经有一个中文意思了,我们再添加一个:

这样,就实现了 Hash 版本的 append 命令。

最后,我们简单讲讲涉及到的 Lua 命令。大部分命令大家看字面意思就能懂。只有一个chinese .. ';' .. new_chinese可能会让大家困惑一下。实际上,..在 Lua 里面就是用来连接两个字符串的符号,相当于 Python 中的+

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
5月前
|
消息中间件 NoSQL Java
腾讯音乐:说说Redis脑裂问题?
redis脑裂问题如何解决?
41 1
腾讯音乐:说说Redis脑裂问题?
|
NoSQL Redis
redis驯不好,骑你头上跑
redis驯不好,骑你头上跑
50 0
|
存储 缓存 NoSQL
走进 Redis,让你重新认识 redis。绝不是表面
说到Redis我们不禁的会联想到:缓存。提到缓存我们要聊的就有很多了。
|
缓存 NoSQL BI
路上,小胖问我:Redis 主从复制原理是怎样的?
路上,小胖问我:Redis 主从复制原理是怎样的?
路上,小胖问我:Redis 主从复制原理是怎样的?
|
NoSQL 网络协议 Java
|
缓存 NoSQL Redis