zoukankan      html  css  js  c++  java
  • 分布式唯一ID系列(4)——Redis集群实现的分布式ID适合做分布式ID吗

    首先是项目地址:

    https://github.com/maqiankun/distributed-id-redis-generator

    关于Redis集群生成分布式ID,这里要先了解redis使用lua脚本的时候的EVAL,EVALSHA命令:

    https://www.runoob.com/redis/scripting-eval.html
    https://www.runoob.com/redis/scripting-evalsha.html

    讲解一下Redis实现分布式ID的原理,这里用java语言来讲解:

    这里的分布式id我们分成3部分组成:毫秒级时间,redis集群的第多少个节点,每一个redis节点在每一毫秒的自增序列值

    然后因为window是64位的,然后整数的时候第一位必须是0,所以最大的数值就是63位的111111111111111111111111111111111111111111111111111111111111111,这里呢,我们分出来41位作为毫秒,然后12位作为redis节点的数量,然后10位做成redis节点在每一毫秒的自增序列值

    41位的二进制11111111111111111111111111111111111111111转换成10进制的毫秒就是2199023255551,然后我们把 2199023255551转换成时间就是2039-09-07,也就是说可以用20年的
    然后12位作为redis节点,所以最多就是12位的111111111111,也就是最多可以支持4095个redis节点,
    然后10位的redis每一个节点自增序列值,,这里最多就是10位的1111111111,也就是说每一个redis节点可以每一毫秒可以最多生成1023个不重复id值

    然后我们使用java代码来讲解这个原理,下面的1565165536640L是一个毫秒值,然后我们的的redis节点设置成53,然后我们设置了两个不同的自增序列值,分别是1和1023,下面的结果展示的就是在1565165536640L这一毫秒里面,53号redis节点生成了两个不同的分布式id值

    package io.github.hengyunabc.redis;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    
    public class Test {
    
    	public static void main(String[] args) {
    		long buildId = buildId(1565165536640L, 53, 1);
    		System.out.println("分布式id是:"+buildId);
    		long buildIdLast = buildId(1565165536640L, 53, 1023);
    		System.out.println("分布式id是:"+buildIdLast);
    	}
    	
    	public static long buildId(long miliSecond, long shardId, long seq) {
    		return (miliSecond << (12 + 10)) + (shardId << 10) + seq;
    	}
    
    
    }
    public class Test {
    
    	public static void main(String[] args) {
    		long buildId = buildId(1565165536640L, 53, 1);
    		System.out.println("分布式id是:"+buildId);
    		long buildIdLast = buildId(1565165536640L, 53, 1023);
    		System.out.println("分布式id是:"+buildIdLast);
    	}
    	
    	public static long buildId(long miliSecond, long shardId, long seq) {
    		return (miliSecond << (12 + 10)) + (shardId << 10) + seq;
    	}
    
    
    }
    

    结果如下所示

    分布式id是:6564780070991352833
    分布式id是:6564780070991353855
    

    那么有人要说了,你这也不符合分布式id的设置啊,完全没有可读性啊,这里我们可以使用下面的方式来获取这个分布式id的生成毫秒时间值,

    package io.github.hengyunabc.redis;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class Test {
    
    	public static void main(String[] args) {
    		long buildId = buildId(1565165536640L, 53, 1);
    		parseId(buildId);
    		long buildIdLast = buildId(1565165536640L, 53, 1023);
    		parseId(buildIdLast);
    	}
    	
    	public static long buildId(long miliSecond, long shardId, long seq) {
    		return (miliSecond << (12 + 10)) + (shardId << 10) + seq;
    	}
    
    	public static void parseId(long id) {
    		long miliSecond = id >>> 22;
    		long shardId = (id & (0xFFF << 10)) >> 10;
    		System.err.println("分布式id-"+id+"生成的时间是:"+new SimpleDateFormat("yyyy-MM-dd").format(new Date(miliSecond)));
    		System.err.println("分布式id-"+id+"在第"+shardId+"号redis节点生成");
    	}
    
    }
    

    这样不就ok了,哈哈。

    分布式id-6564780070991352833生成的时间是:2019-08-07
    分布式id-6564780070991352833在第53号redis节点生成
    分布式id-6564780070991353855生成的时间是:2019-08-07
    分布式id-6564780070991353855在第53号redis节点生成
    

    实现集群版的redis的分布式id创建

    此时我的分布式redis集群的端口分别是6380,6381
    首先是生成Evalsha命令安全sha1 校验码,生成过程如下,
    首先是生成6380端口对应的安全sha1 校验码,首先进入到redis的bin目录里面,然后执行下面的命令下载lua脚本

    wget https://github.com/maqiankun/distributed-id-redis-generator/blob/master/redis-script-node1.lua
    

    然后执行下面的命令,生成6380端口对应的安全sha1 校验码,此时看到是be6d4e21e9113bf8af47ce72f3da18e00580d402

    ./redis-cli -p 6380 script load "$(cat redis-script-node1.lua)"
    

    首先是生成6381端口对应的安全sha1 校验码,首先进入到redis的bin目录里面,然后执行下面的命令下载lua脚本

    wget https://github.com/maqiankun/distributed-id-redis-generator/blob/master/redis-script-node2.lua
    


    然后执行下面的命令,生成6381端口对应的安全sha1 校验码,此时看到是97f65601d0aaf1a0574da69b1ff3092969c4310e

    ./redis-cli -p 6381 script load "$(cat redis-script-node2.lua)"
    

    然后我们就使用上面的sha1 校验码和下面的代码来生成分布式id

    项目图片如下

    IdGenerator类的代码如下所示

    
    package io.github.hengyunabc.redis;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.apache.commons.lang3.tuple.Pair;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.exceptions.JedisConnectionException;
    
    public class IdGenerator {
    	/**
    	 * JedisPool, luaSha
    	 */
    	List<Pair<JedisPool, String>> jedisPoolList;
    	int retryTimes;
    
    	int index = 0;
    
    	private IdGenerator(List<Pair<JedisPool, String>> jedisPoolList,
    			int retryTimes) {
    		this.jedisPoolList = jedisPoolList;
    		this.retryTimes = retryTimes;
    	}
    
    	static public IdGeneratorBuilder builder() {
    		return new IdGeneratorBuilder();
    	}
    
    	static class IdGeneratorBuilder {
    		List<Pair<JedisPool, String>> jedisPoolList = new ArrayList();
    		int retryTimes = 5;
    
    		public IdGeneratorBuilder addHost(String host, int port, String luaSha) {
    			jedisPoolList.add(Pair.of(new JedisPool(host, port), luaSha));
    			return this;
    		}
    
    		public IdGenerator build() {
    			return new IdGenerator(jedisPoolList, retryTimes);
    		}
    	}
    
    	public long next(String tab) {
    		for (int i = 0; i < retryTimes; ++i) {
    			Long id = innerNext(tab);
    			if (id != null) {
    				return id;
    			}
    		}
    		throw new RuntimeException("Can not generate id!");
    	}
    
    	Long innerNext(String tab) {
    		index++;
    		int i = index % jedisPoolList.size();
    		Pair<JedisPool, String> pair = jedisPoolList.get(i);
    		JedisPool jedisPool = pair.getLeft();
    
    		String luaSha = pair.getRight();
    		Jedis jedis = null;
    		try {
    			jedis = jedisPool.getResource();
    			List<Long> result = (List<Long>) jedis.evalsha(luaSha, 2, tab, ""
    					+ i);
    			long id = buildId(result.get(0), result.get(1), result.get(2),
    					result.get(3));
    			return id;
    		} catch (JedisConnectionException e) {
    			if (jedis != null) {
    				jedisPool.returnBrokenResource(jedis);
    			}
    		} finally {
    			if (jedis != null) {
    				jedisPool.returnResource(jedis);
    			}
    		}
    		return null;
    	}
    
    	public static long buildId(long second, long microSecond, long shardId,
    			long seq) {
    		long miliSecond = (second * 1000 + microSecond / 1000);
    		return (miliSecond << (12 + 10)) + (shardId << 10) + seq;
    	}
    
    	public static List<Long> parseId(long id) {
    		long miliSecond = id >>> 22;
    		long shardId = (id & (0xFFF << 10)) >> 10;
    
    		List<Long> re = new ArrayList<Long>(4);
    		re.add(miliSecond);
    		re.add(shardId);
    		return re;
    	}
    }
    

    Example的代码如下所示,下面的while循环的目的就是为了打印多个分布式id,下面的tab变量就是evalsha命令里面的参数,可以根据自己的需求来定义

    package io.github.hengyunabc.redis;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    
    public class Example {
    
    	public static void main(String[] args) {
    		String tab = "这个就是evalsha命令里面的参数,随便定义";
    
    		IdGenerator idGenerator = IdGenerator.builder()
    				.addHost("47.91.248.236", 6380, "be6d4e21e9113bf8af47ce72f3da18e00580d402")
    				.addHost("47.91.248.236", 6381, "97f65601d0aaf1a0574da69b1ff3092969c4310e")
    				.build();
    		int hello = 0;
            while (hello<3){
                long id = idGenerator.next(tab);
    
                System.out.println("分布式id值:" + id);
                List<Long> result = IdGenerator.parseId(id);
    
                System.out.println("分布式id生成的时间是:" + new SimpleDateFormat("yyyy-MM-dd").format(new Date(result.get(0))) );
                System.out.println("redis节点:" + result.get(1));
                hello++;
            }
    
    	}
    }
    

    此时打印结果如下所示

    分布式id值:6564819854640022531
    分布式id生成的时间是:2019-08-07
    redis节点:1
    分布式id值:6564819855189475330
    分布式id生成的时间是:2019-08-07
    redis节点:0
    分布式id值:6564819855361442819
    分布式id生成的时间是:2019-08-07
    redis节点:1
    

    到这里redis集群版的分布式id就算搞定了,完美؏؏☝ᖗ乛◡乛ᖘ☝؏؏

    Redis集群实现的分布式id是否适合做分布式id呢?

    我觉得Redis集群实现分布式ID是可以供我们开发中的基本使用的,但是我还是觉得它有下面的两个问题:

    1:这里我们可以给上一篇的数据库自增ID机制进行对比,其实Redis集群可以说是解决了数据库集群创建分布式ID的性能问题,但是Redis集群系统水平扩展还是比较困难,如果以后想对Redis集群增加Redis节点的话,还是会和数据库集群的节点扩展一样麻烦。
    2:还有就是如果你的项目里面没有使用Redis,那么你就要引入新的组件,这也是一个比较麻烦的问题。

    原文链接

    其他分布式ID系列快捷键:
    分布式ID系列(1)——为什么需要分布式ID以及分布式ID的业务需求
    分布式ID系列(2)——UUID适合做分布式ID吗
    分布式ID系列(3)——数据库自增ID机制适合做分布式ID吗
    分布式ID系列(4)——Redis集群实现的分布式ID适合做分布式ID吗
    分布式ID系列(5)——Twitter的雪法算法Snowflake适合做分布式ID吗

  • 相关阅读:
    了解 NoSQL 的必读资料
    关于什么时候用assert(断言)的思考
    这次见到了一些大侠
    NetBeans 时事通讯(刊号 # 87 Jan 12, 2010)
    动态链接库dll,静态链接库lib, 导入库lib
    新女性十得 写得了代码,查得出异常
    记录系统乱谈
    新女性十得 写得了代码,查得出异常
    fullpage.js禁止滚动
    RunningMapReduceExampleTFIDF hadoopclusternet This document describes how to run the TFIDF MapReduce example against ascii books. This project is for those who wants to experiment hadoop as a skunkworks in a small cluster (110 nodes) Google Pro
  • 原文地址:https://www.cnblogs.com/itqiankun/p/11319994.html
Copyright © 2011-2022 走看看