前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一日一技:这个东西能给 Redis 插上火箭

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

作者头像
青南
发布2020-05-14 17:31:56
5660
发布2020-05-14 17:31:56
举报
文章被收录于专栏:未闻Code未闻Code

摄影:产品经理

黄金蘑菇

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

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

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

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

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

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

代码语言:javascript
复制
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 命令:

代码语言:javascript
复制
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 中的+

参考资料

[1]Pipeline + Watch: https://github.com/andymccurdy/redis-py#pipelines [2]示例: https://github.com/andymccurdy/redis-py#lua-scripting

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 未闻Code 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 参考资料
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档