Redis分布式ID

Redis分布式ID

分布式ID应用场景

1. 自增流水号的的生成

2. 数据库分表之后ID的生成

3. 日切自增序列号

......

分布式ID的特性

1.全局唯一

2.支持高并发

3.高可靠

4.容错单点故障

5.高性能

6.可排序

使用redis做分布式ID需要考虑的问题

1. ID生成的持久化,如果redis宕机了之后怎么进行恢复

2. 如何保证KEY能够分不到每一台机器

集群模式下的redis自增ID

可以使用Redis集群来获取更高的吞吐量,容错单点故障,高并发,假如一个集群中有3个master节点。可以初始化每台Redis的值分别是1,2,3,然后分别把分布式ID的KEY用Hash Tags固定每一个master节点,步长就是master节点的个数。各个Redis生成的ID为:

A:1,4,7

B:2,5,8

C:3,6,9

优点:

不依赖于数据库,灵活方便,且性能优于数据库。

数字ID天然排序,对分页或者需要排序的结果很有帮助。

使用Redis集群也可以防止单点故障的问题。

缺点:

如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。

在开始时,程序实例负载到哪个redis实例一旦确定好,未来很难做修改。

实现代码

如果各位读者有更好的实现方法或者发现我的实现有问题请留言

/**
 * @author zcx
 * @description
 * redis集群方式的ID生成器
 * 实例化类之前需要先调用静态方法设置ID生成的节点信息,这个只要在程序启动的时候设置即可
 * 调用ID生成方法之前需要先初始化,在实例创建好之后初始化一次即可
 * redis中如果某一个master挂掉之后重新选举了master之后由于数据可能没有完全同步,有可能导致出现部分ID重复的情况,
 * 出现这种问题只需要重试就可以了,轮询的时候会跳过这个异常的节点,返回的异常ID直接丢弃,使用下一个ID
 * @date 2019/12/5
 */
public class ClusterRedisIdGenerator implements RedisIdGenerator{

    private JedisCluster jedisCluster;
    /**
     * 用于生成ID的key
     */
    private String key;

    /**
     * ID生成的节点的信息列表
     */
    private static List idNodeList = null;

    private AtomicInteger roundRobin = new AtomicInteger(0);

    public ClusterRedisIdGenerator(JedisCluster jedisCluster,String key){
        if(idNodeList == null || idNodeList.isEmpty()){
            throw new IllegalArgumentException("没有找到用于生成ID的key配置信息,请调用setRedisIdNodeInfo方法设置");
        }
        this.jedisCluster = jedisCluster;
        this.key = key;
    }

    /**
     * 节点信息,这个在程序启动的时候进行初始化
     * key1:initValue1
     * key2:initValue2
     * key3:initValue3
     * @param idNodeInfo
     */
    public static void setRedisIdNodeInfo(List idNodeInfo){
        if(idNodeInfo == null || idNodeInfo.isEmpty()){
            throw new NullPointerException("idNodeInfo is null");
        }
        idNodeList = idNodeInfo.stream().map( e -> {
            String [] idNodeInfos = e.split("\\:");
            return new IdNode("{"+idNodeInfos[0]+"}",Integer.valueOf(idNodeInfos[1]),idNodeInfo.size());
        }).collect(Collectors.toList());
    }

    /**
     * 用于生成ID的redis集群节点信息
     * hashTagsKey:key的Hash Tags,用这个做key的前缀计算slot的时候只使用hash tags,需要预先根据集群进行设置
     * step:每个节点自增步长
     * initValue:每个节点中的初始值
     */
    static class IdNode{
        private String hashTagsKey;
        private int step;
        private int initValue;

        public IdNode(String hashTagsKey,int initValue,int step){
            this.hashTagsKey = hashTagsKey;
            this.step = step;
            this.initValue = initValue;
        }

    }


    /**
     * 使用轮询每个redis节点的方式保证顺序生成,
     * 如果其中有一个节点挂掉了,那么生成的ID可能存在不连续的情况,不连续的段将会逐渐后移,异常由调用方自己处理
     * @return
     */
    @Override
    public long getId() {
        if(StringUtils.isBlank(key)){
            throw new IllegalArgumentException("分布式ID的key不能为空");
        }
        IdNode idNode = roundRobinNodeKeyPrefix();
        StringBuilder keyBuilder = new StringBuilder(idNode.hashTagsKey);
        keyBuilder.append(":").append(key);
        return jedisCluster.incrBy(keyBuilder.toString(),idNode.step);
    }

    /**
     * 初始化值,已经初始化之后不会重复初始化
     * @return
     */
    @Override
    public void init() {
        for(IdNode idNode : idNodeList){
            StringBuilder keyBuilder = new StringBuilder(idNode.hashTagsKey);
            keyBuilder.append(":").append(key);
            String key = keyBuilder.toString();
            //如果已经进行过初始化不会重复初始化
            if(StringUtils.isBlank(jedisCluster.get(key))){
                jedisCluster.set(key,String.valueOf(idNode.initValue));
            }
        }
    }


    /**
     * 轮询获取节点,保证顺序,生成ID的顺序
     * @return
     */
    private IdNode roundRobinNodeKeyPrefix(){
        if(Integer.MAX_VALUE == roundRobin.get()){
            //如果已经到整形的最大值了,就还原为和这个最大值具有相同效果的值,这样可以避免整形溢出
            roundRobin.compareAndSet(Integer.MAX_VALUE,Integer.MAX_VALUE % idNodeList.size());
        }
        int index = roundRobin.getAndIncrement() % idNodeList.size();
        return idNodeList.get(index);
    }
}复制代码

调用方式

@Test
public void test(){
   List list = Collections.synchronizedList(new ArrayList<>());
        ClusterRedisIdGenerator.setRedisIdNodeInfo(redisProperties.getDistributedidIdInfo());
        ClusterRedisIdGenerator clusterRedisIdGenerator = new ClusterRedisIdGenerator(jedisCluster,"locktest4");
        clusterRedisIdGenerator.init();
        CountDownLatch countDownLatch = new CountDownLatch(3);
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for(int i=0;i<3;i++){
            executorService.execute(()->{
                for(int j=0;j<100;j++){
                    list.add(clusterRedisIdGenerator.getId());
                }
                countDownLatch.countDown();
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        list.sort((e1,e2) -> e1.compareTo(e2));
        list.forEach(System.out::println);
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值