Redis集群 Lua Java 随机编号 用户编号

用户id主键递增的情况下,要为用户生产随机的用户编号,效果如下:
在这里插入图片描述
这是一种伪随机的方案,前缀随机,后面的编号递增

reids脚本

local prefix=0
local exists = redis.call('EXISTS', KEYS[1])
if exists == 1 then
-- 随机出set的一个前缀
    prefix= redis.call('SRANDMEMBER', KEYS[1])
else
-- 初始化set KEYS[1] 默认1至9 10个前缀
    for i=1, 9 do redis.call('SADD', KEYS[1],i) end
    prefix = redis.call('SRANDMEMBER', KEYS[1])
end
-- 随机出set的前缀 对应的值递增
local currentNum  = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1])
if (currentNum > tonumber(ARGV[2])) then
  -- 有进位 前缀值加9,对应的旧前缀从set移除,增加新前缀
  local newPrefix = prefix + 9
  local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1])
  redis.call('srem', KEYS[1], prefix)
  redis.call('SADD', KEYS[1], newPrefix)
  return (newPrefix * tonumber(ARGV[2])) + newCurrentNum
else
  -- 无进位 直接返回
  return (prefix * tonumber(ARGV[2])) + currentNum
end

前缀
在这里插入图片描述

前缀对应的自增后的值
在这里插入图片描述

Redis客户端测试
把脚本的注释去掉

client.redis.rds.aliyuncs.com:1>eval "local prefix=0 local exists = redis.call('EXISTS', KEYS[1]) if exists == 1 then prefix= redis.call('SRANDMEMBER', KEYS[1]) else for i=1, 9 do redis.call('SADD', KEYS[1],i) end prefix = redis.call('SRANDMEMBER', KEYS[1]) end local currentNum = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1]) if (currentNum > tonumber(ARGV[2])) then local newPrefix = prefix + 9 local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1]) redis.call('srem', KEYS[1], prefix) redis.call('SADD', KEYS[1], newPrefix) return (newPrefix * tonumber(ARGV[2])) + newCurrentNum else return (prefix * tonumber(ARGV[2])) + currentNum end" 2 account_random_number_prefix_set account_random_number_hash 2332 10000
"ERR 'EVAL' command keys must in same slot"
client.redis.rds.aliyuncs.com:1>eval "local prefix=0 local exists = redis.call('EXISTS', KEYS[1]) if exists == 1 then prefix= redis.call('SRANDMEMBER', KEYS[1]) else for i=1, 9 do redis.call('SADD', KEYS[1],i) end prefix = redis.call('SRANDMEMBER', KEYS[1]) end local currentNum = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1]) if (currentNum > tonumber(ARGV[2])) then local newPrefix = prefix + 9 local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1]) redis.call('srem', KEYS[1], prefix) redis.call('SADD', KEYS[1], newPrefix) return (newPrefix * tonumber(ARGV[2])) + newCurrentNum else return (prefix * tonumber(ARGV[2])) + currentNum end" 2 {account_random_number}_prefix_set {account_random_number}_hash 2332 10000
"62332"

注意:Redis集群下,所有key必须带有 {xxx},并且xxx内容一致,保证key都分到同一个slot中,例如{account_random_number}_prefix_set替换account_random_number_prefix_set,account_random_number_hash 替换为{account_random_number}_hash,共同部分为{account_random_number}

你可以通过在key前面增加带有 {xxx} 的部分来使这些key被分配到同一个slot中去。因为对于头部带有 {xxx} 的key,redis服务就不会对整个key做hash,只会对 {xxx} 做hash。

Java 代码

	
    /**
     * 五位数最大值(不包含)
     */
    private static final Long FIVE_DIGITS_MAX = 100000L;
    /**
     * 用户随机ID 前缀
     * 首批前缀序号为
     * 递增规则如下:
     * 1 --10 --19 --28
     * 2 --11 --20 --29
     * 3
     * 4
     * 5
     * 6
     * 7
     * 8
     * 9 --18 --27 --36
     * 并且位数超过当前最大值 递增9为新前缀序号
     */
    String ACCOUNT_RANDOM_NUMBER_PREFIX_SET = "{account_random_number}_prefix_set";

    /**
     * 用户前缀下的自增number
     */
    String ACCOUNT_RANDOM_NUMBER_HASH = "{account_random_number}_hash";

    /**
     * lua脚本生成用户随机编号
     */
    public Long doCreateUserNumberLua() {
        /*
         * keys1 随机前缀set 默认1至9,超过
         * keys2 hash 存储前缀set对应的递增后的值
         * argv1 随机数
         * argv2 进位数 这里是5位 超过99999前缀set对应一个set进位
         * 例如:
         *  1.随机到前缀1 随机数为222 则生成的编号为 100222   hash存储前缀为1--》 222
         *  2.再次随机到前缀1 随机数为333 则生成的编号为 100555  hash存储前缀为1--》 222
         *  进位的情况
         *  3.再次随机到前缀1 随机数为555(hash存储前缀为1--》 99998)则删除前缀为1的set,生成1+9=10的前缀,生成的编号为 1000555
         */
        String script = "local prefix=0\n" +
                "local exists = redis.call('EXISTS', KEYS[1])\n" +
                "if exists == 1 then\n" +
                "    prefix= redis.call('SRANDMEMBER', KEYS[1])\n" +
                "else\n" +
                "    for i=1, 9 do redis.call('SADD', KEYS[1],i) end\n" +
                "    prefix = redis.call('SRANDMEMBER', KEYS[1])\n" +
                "end\n" +
                "local currentNum  = redis.call('hIncrBy', KEYS[2], prefix, ARGV[1])\n" +
                "if (currentNum > tonumber(ARGV[2])) then\n" +
                "  local newPrefix = prefix + 9\n" +
                "  local newCurrentNum = redis.call('hIncrBy', KEYS[2], newPrefix, ARGV[1])\n" +
                "  redis.call('srem', KEYS[1], prefix)\n" +
                "  redis.call('SADD', KEYS[1], newPrefix)\n" +
                "  return (newPrefix * tonumber(ARGV[2])) + newCurrentNum\n" +
                "else\n" +
                "  return (prefix * tonumber(ARGV[2])) + currentNum\n" +
                "end";
        return redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(ACCOUNT_RANDOM_NUMBER_PREFIX_SET, ACCOUNT_RANDOM_NUMBER_HASH), RandomUtil.randomInt(100, 500), FIVE_DIGITS_MAX);
    }

测试方法


    @Test
    public void initUserIdNumber() throws InterruptedException {
        CopyOnWriteArraySet<Long> set = new CopyOnWriteArraySet<>();
        CountDownLatch latch = new CountDownLatch(1000);
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                long lon = accountRegisterService.doCreateUserNumberLua();
                System.out.println("lon=" + lon);
                set.add(lon);
                latch.countDown();
            }).start();
        }
        latch.await();
        System.out.println("initUserIdNumber 完成");
        System.out.println(set.size());
        System.out.println(JSON.toJSONString(set));
    }

pom.xml

  		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>lettuce-core</artifactId>
                    <groupId>io.lettuce</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.9.0</version>
        </dependency>

yml配置文件

spring:
  redis:
    database: 2
    host: xxxx.rds.aliyuncs.com
    port: 6379
    password: xxxxx
    jedis:
      pool:
        min-idle: 0
        max-active: 8
        max-idle: 8
        max-wait: -1ms
    connect-timeout: 30000ms
@Configuration
public class RedisConfig {

    /**
     * redis template.
     *
     * @param factory factory
     * @return RedisTemplate
     */
    @Bean
    public StringRedisTemplate redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值