基于Redis位图结构生成用户唯一短id

优雅实现短id生成,看这篇就够了!

业务背景:

        在互联网项目中常常需要用户短id,所谓用户短id即是提供用户使用的id,比方说用户与用户之间加好友所用的id,针对这种场景把数据库的user_id提供给用户貌似不太合理,20位的userId,即影响页面显示时的美感,又不方便用户记忆,所以需要一个”短id“来作为userId的副本存在。

        那怎么获取这个短id,怎么保证这个id的唯一性呢?短id作为用户属性之一必然会存在数据库中但为了确保id的唯一性貌似少不了循环比较,那有什么办法可以一个操作就可以实现"唯一"和生成id呢,看完本文保你通透。

一、核心lua脚本逻辑

        生成用户唯一短id的核心逻辑如下所示,重点关注主体逻辑,关于入参的特殊处理本文末尾会讲到(针对阿里云Redis集群的特殊性处理)

local random_start = KEYS[2]

random_start = string.gsub(random_start, "{user_default_id}", "")
//首先将标志位log的初始值设置为0
local log = '0'
//调用 Redis 的 bitpos 命令,查找键 KEYS[1] 中值为 0 的第一个位的位置
//第三个参数 random_start 是起始搜索的位置。如果找到了,将位置赋给 pos;如果没有找到(即返回 -1)
local pos = redis.call('bitpos', KEYS[1], 0, random_start)
//如果没找到,代表从random_start处开始找寻并没有找到
if pos == -1 then
    log = '-1'
    //在整个位图中再次调用 bitpos 查找值为 “0” 的位的位置,并将结果赋给 pos
    pos = redis.call('bitpos', KEYS[1], 0)
end
//如果依旧没有找到,代表在整个位图中都没有找到,将log赋值为-2以便程序中做判断
if pos == -1 then
    log = '-2'
else
    //如果找到了则将对应的位置的值设置为“1”
    redis.call('setbit', KEYS[1], pos, 1)
end

return log..'|'..pos;

二、代码逻辑

  2.1 初始化key的bit位

        由于上面lua脚本逻辑的目的就是找寻值为“0“的位,那么首先我们需要初始化用于生成短id的RedisKey的所有位,初始化Key代码如下:

redisTemplate.opsForValue().setBit("{your_redisKey}", 10000000, false);

        这里要注意一下,执行setBit命令时候,假如原本没有这个Key那会新建这个Key并且把0~10000000的所有位都置成指定的值(0/1)

  2.2 执行lua脚本

         这里需要提醒一下,为什么new SecureRandom().nextInt()的入参是1250000呢?因为在上面初始化bit位的时候我们初始化了0~10000000,而我们通过lua脚本去查询的位其实是字节的bit位,而1字节 = 8bit,也就是说1250000(字节)刚好占10000000个bit位

public Long getUserRandomId(long userId) {
    try {
        int random_start = new SecureRandom().nextInt(1250000);
        List<String> keys = Arrays.asList("{your_redisKey}", String.format("{your_redisKey}", random_start));
        //执行lua脚本
        Object execute = authRedisTemplate.execute(redisLuaScriptLoader.getByScriptName("account.lua"), keys);
        //由于我们的脚本返回的参数是log和pos拼接的,所以这里只要获取pos就可以了
        String s = execute.toString().split("\\|")[1];
        //由于lua脚本获取的是位,是一个七位的整数,所以这里补一位补成8位的短id
        int i1 = Integer.parseInt(s) + (random_key +1)* 10000000;
        return (long) i1;
    } catch (Exception e) {
        log.error("生成用户唯一短id错误", e);
        return -1L;
    }
}

三、踩坑

        现在回过头来看lua脚本的入参,可以看到random_start是通过Key的传递传进lua脚本的,然后在脚本中通过字符替换的方式解出random_start ,那为什么不通过args[]传参方式把random_start传入呢,注意此处有大坑因为阿里云Redis不支持args[]传参,细心的同学可能已经发现一个问题,这个Key为什么带的中括号{},因为在Redis集群环境下,是通过计算{}里面的字符得到的槽位,如果传入的两个Key的槽位不同会导致服务启动报错当然单节点的Redis无需考虑此问题

lua调用如下:

List<String> keys = Arrays.asList("{your_redisKey}", 
            String.format("{your_redisKey}", random_start));
//执行lua脚本
Object execute = authRedisTemplate.execute(redisLuaScriptLoader.getByScriptName("account.lua"), keys);

lua解参如下:

local random_start = KEYS[2]

random_start = string.gsub(random_start, "{user_default_id}", "")

        OK,本次分享到此就结束啦,如果大家有更优雅的方案也欢迎大家来评论区讨论

菜鸟日记持续更新... ...

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值