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

Posted 伍Wu哈Ha

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
    

以上是关于Redis集群 Lua Java 随机编号 用户编号的主要内容,如果未能解决你的问题,请参考以下文章

redis lua脚本有啥用

redis集群lua脚本遇到的问题

lua编译为二进制方式

redis集群不支持lua脚本吗

lua连接redis集群

Redis Lua脚本的详细介绍以及使用入门