• 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    前缀
    在这里插入图片描述

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

    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"
    
    • 1
    • 2
    • 3
    • 4

    注意: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);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    测试方法

    
        @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));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    pom.xml

      		<dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
                <exclusions>
                    <exclusion>
                        <artifactId>lettuce-coreartifactId>
                        <groupId>io.lettucegroupId>
                    exclusion>
                exclusions>
            dependency>
            <dependency>
                <groupId>redis.clientsgroupId>
                <artifactId>jedisartifactId>
            dependency>
            <dependency>
                <groupId>org.apache.commonsgroupId>
                <artifactId>commons-pool2artifactId>
                <version>2.9.0version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    【mysql是怎样运行的】-客户端与服务器连接
    unity的ui跟随鼠标移动
    Prometheus基于Consul的 Redis 多实例监控方案
    java 实现链表
    C++宏函数和内联函数
    uboot移植-野火imx6ull
    Autowired注解与Resource注解的区别
    现在人都不练习打字了?爬取单词自制仙草打字练习
    Apache ECharts简介和相关操作
    最优乘车——最短路
  • 原文地址:https://blog.csdn.net/Peng_Hong_fu/article/details/127923116