服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

服务器之家 - 数据库 - Redis - redis分布式ID解决方案示例详解

redis分布式ID解决方案示例详解

2023-03-09 16:23德哥很ok Redis

这篇文章主要为大家介绍了redis分布式ID解决方案示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

常用的分布式ID解决方案

在分布式系统中,生成全局唯一ID是非常重要的,因为在分布式系统中,多个节点同时生成ID可能会导致ID冲突。

下面介绍几种常用的分布式ID解决方案。

redis分布式ID解决方案示例详解

UUID

UUID(通用唯一标识符)是由128位数字组成的标识符,它可以保证在全球范围内的唯一性,因为其生成算法基于时间戳、节点ID等因素。UUID可以使用Java自带的UUID类来生成,如下所示:

?
1
2
3
4
5
6
7
8
javaCopy code
import java.util.UUID;
public class UuidGenerator {
    public static void main(String[] args) {
        UUID uuid = UUID.randomUUID();
        System.out.println(uuid.toString());
    }
}

UUID的优点是简单易用,无需额外的配置和管理,可以直接使用Java自带的UUID类生成。但是UUID长度较长(128位),不太适合作为数据库表的主键,且不易于排序和索引。

Snowflake

Snowflake是Twitter开源的一种分布式ID生成算法,它可以生成64位的唯一ID,其中包含了时间戳、数据中心ID和机器ID等信息。Snowflake算法的Java代码如下所示:

Snowflake算法的Java代码:

?
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
javaCopy code
public class SnowflakeGenerator {
    private final static long START_STMP = 1480166465631L;
    private final static long SEQUENCE_BIT = 12;
    private final static long MACHINE_BIT = 5;
    private final static long DATACENTER_BIT = 5;
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
    private long datacenterId;
    private long machineId;
    private long sequence = 0L;
    private long lastStmp = -1L;
    public SnowflakeGenerator(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }
        if (currStmp == lastStmp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            sequence = 0L;
        }
        lastStmp = currStmp;
        return (currStmp - START_STMP) << TIMESTMP_LEFT
                | datacenterId << DATACENTER_LEFT
                | machineId << MACHINE_LEFT
                | sequence;
    }
    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }
    private long getNewstmp() {
        return System.currentTimeMillis();
    }
}

Snowflake算法的优点是生成ID的性能高,且ID长度较短(64位),可以作为数据库表的主键,且便于排序和索引。但是需要注意,如果集群中的节点数超过了机器ID所占的位数,或者集群规模很大,时间戳位数不够用,那么就需要考虑其他的分布式ID生成算法。

Leaf

Leaf是美团点评开源的一种分布式ID生成算法,它可以生成全局唯一的64位ID。Leaf算法的Java代码如下所示:

Leaf算法的Java代码:

?
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
javaCopy code
public class LeafGenerator {
    private static final Logger logger = LoggerFactory.getLogger(LeafGenerator.class);
    private static final String WORKER_ID_KEY = "leaf.worker.id";
    private static final String PORT_KEY = "leaf.port";
    private static final int DEFAULT_PORT = 8080;
    private static final int DEFAULT_WORKER_ID = 0;
    private static final int WORKER_ID_BITS = 10;
    private static final int SEQUENCE_BITS = 12;
    private static final int MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1;
    private static final int MAX_SEQUENCE = (1 << SEQUENCE_BITS) - 1;
    private static final long EPOCH = 1514736000000L;
    private final SnowflakeIdWorker idWorker;
    public LeafGenerator() {
        int workerId = SystemPropertyUtil.getInt(WORKER_ID_KEY, DEFAULT_WORKER_ID);
        int port = SystemPropertyUtil.getInt(PORT_KEY, DEFAULT_PORT);
        this.idWorker = new SnowflakeIdWorker(workerId, port);
        logger.info("Initialized LeafGenerator with workerId={}, port={}", workerId, port);
    }
    public long nextId() {
        return idWorker.nextId();
    }
    private static class SnowflakeIdWorker {
        private final long workerId;
        private final long port;
        private long sequence = 0L;
        private long lastTimestamp = -1L;
        SnowflakeIdWorker(long workerId, long port) {
            if (workerId < 0 || workerId > MAX_WORKER_ID) {
                throw new IllegalArgumentException(String.format("workerId must be between %d and %d", 0, MAX_WORKER_ID));
            }
            this.workerId = workerId;
            this.port = port;
        }
        synchronized long nextId() {
            long timestamp = System.currentTimeMillis();
            if (timestamp < lastTimestamp) {
                throw new RuntimeException("Clock moved backwards. Refusing to generate id");
            }
            if (timestamp == lastTimestamp) {
                sequence = (sequence + 1) & MAX_SEQUENCE;
                if (sequence == 0L) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }
            lastTimestamp = timestamp;
            return ((timestamp - EPOCH) << (WORKER_ID_BITS + SEQUENCE_BITS))
                    | (workerId << SEQUENCE_BITS)
                    | sequence;
        }
        private long tilNextMillis(long lastTimestamp) {
            long timestamp = System.currentTimeMillis();
            while (timestamp <= lastTimestamp) {
                timestamp = System.currentTimeMillis();
            }
            return timestamp;
        }
    }
}

Leaf算法的特点是生成ID的速度比Snowflake算法略慢,但是可以支持更多的Worker节点。Leaf算法生成的ID由三部分组成,分别是时间戳、Worker ID和序列号,其中时间戳占用42位、Worker ID占用10位、序列号占用12位,总共64位。

以上是常见的分布式ID生成算法,当然还有其他的一些方案,如:MongoDB ID、UUID、Twitter Snowflake等。不同的方案适用于不同的业务场景,具体实现细节和性能表现也有所不同,需要根据实际情况选择合适的方案。

除了上述介绍的分布式ID生成算法,还有一些新的分布式ID生成方案不断涌现,例如Flicker的分布式ID生成算法,它使用了类似于Snowflake的思想,但是采用了不同的位数分配方式,相比Snowflake更加灵活,并且可以根据需要动态调整每个部分占用的位数。此外,Facebook还推出了ID Generation Service (IGS)方案,该方案将ID的生成和存储分离,提供了更加灵活和可扩展的方案,但是需要进行更加复杂的架构设计和实现。

针对不同的业务需求,可以设计多套分布式ID生成方案。下面是我个人的一些建议:

  • 基于数据库自增ID生成:使用数据库自增ID作为全局唯一ID,可以很好的保证ID的唯一性,并且实现简单,但是并发量较高时可能会导致性能瓶颈。因此,在高并发场景下不建议使用。
  • 基于UUID生成:使用UUID作为全局唯一ID,可以很好地保证ID的唯一性,但是ID长度较长(128位),不便于存储和传输,并且存在重复ID的概率非常小但不为0。因此,建议在分布式系统中使用时要考虑ID的长度和存储传输的成本。
  • 基于Redis生成:使用Redis的原子性操作,可以保证ID的唯一性,并且生成ID的速度非常快,可以适用于高并发场景。但是需要注意,如果Redis宕机或者性能不足,可能会影响ID的生成效率和可用性。
  • 基于ZooKeeper生成:使用ZooKeeper的序列号生成器,可以保证ID的唯一性,并且实现较为简单,但是需要引入额外的依赖和资源,并且可能会存在性能瓶颈。

选择适合自己业务场景的分布式ID生成方案,需要综合考虑ID的唯一性、生成速度、长度、存储成本、可扩展性、可用性等多个因素。同时需要注意,不同方案的实现细节和性能表现也有所不同,需要根据实际情况进行权衡和选择。

下面给出每种方案的详细代码demo:

基于数据库自增ID生成

?
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
javaCopy code
public class IdGenerator {
    private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test";
    private static final String JDBC_USER = "root";
    private static final String JDBC_PASSWORD = "password";
    public long generateId() {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
            pstmt = conn.prepareStatement("INSERT INTO id_generator (stub) VALUES (null)", Statement.RETURN_GENERATED_KEYS);
            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                return rs.getLong(1);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
                if (conn != null) {
                    conn.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return 0L;
    }
}

基于UUID生成

?
1
2
3
4
5
6
7
javaCopy code
import java.util.UUID;
public class IdGenerator {
    public String generateId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

基于Redis生成

?
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
javaCopy code
import redis.clients.jedis.Jedis;
public class IdGenerator {
    private static final String REDIS_HOST = "localhost";
    private static final int REDIS_PORT = 6379;
    private static final String REDIS_PASSWORD = "password";
    private static final int ID_GENERATOR_EXPIRE_SECONDS = 3600;
    private static final String ID_GENERATOR_KEY = "id_generator";
    public long generateId() {
        Jedis jedis = null;
        try {
            jedis = new Jedis(REDIS_HOST, REDIS_PORT);
            jedis.auth(REDIS_PASSWORD);
            long id = jedis.incr(ID_GENERATOR_KEY);
            jedis.expire(ID_GENERATOR_KEY, ID_GENERATOR_EXPIRE_SECONDS);
            return id;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return 0L;
    }
}

基于ZooKeeper生成

?
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
javaCopy code
import java.util.concurrent.CountDownLatch;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
public class IdGenerator implements Watcher {
    private static final String ZK_HOST = "localhost";
    private static final int ZK_PORT = 2181;
    private static final int SESSION_TIMEOUT = 5000;
    private static final String ID_GENERATOR_NODE = "/id_generator";
    private static final int ID_GENERATOR_EXPIRE_SECONDS = 3600;
    private long workerId = 0;
    public IdGenerator() {
        try {
            ZooKeeper zk = new ZooKeeper(ZK_HOST + ":" + ZK_PORT, SESSION_TIMEOUT, this);
            CountDownLatch latch = new CountDownLatch(1);
            latch.await();
            if (zk.exists(ID_GENERATOR_NODE, false) == null) {
                zk.create(ID_GENERATOR_NODE, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }
            workerId = zk.getChildren(ID_GENERATOR_NODE, false).size();
            zk.create(ID_GENERATOR_NODE + "/worker_" + workerId, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public long generateId() {
        ZooKeeper zk = null;
        try {
            zk = new ZooKeeper(ZK_HOST + ":" + ZK_PORT, SESSION_TIMEOUT, null);
            CountDownLatch latch = new CountDownLatch(1);
            latch.await();
            zk.create(ID_GENERATOR_NODE + "/id_", null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, (rc, path, ctx, name) -> {}, null);
            byte[] data = zk.getData(ID_GENERATOR_NODE + "/worker_" + workerId, false, null);
            long id = Long.parseLong(new String(data)) * 10000 + zk.getChildren(ID_GENERATOR_NODE, false).size();
            return id;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (zk != null) {
                try {
                    zk.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return 0L;
    }
    @Override
    public void process(WatchedEvent event) {
        if (event.getState() == Event.KeeperState.SyncConnected) {
            System.out.println("Connected to ZooKeeper");
            CountDownLatch latch = new CountDownLatch(1);
            latch.countDown();
        }
    }
}

注意,这里使用了ZooKeeper的临时节点来协调各个工作节点,如果一个工作节点挂掉了,它的临时节点也会被删除,这样可以保证每个工作节点获得的ID是唯一的。

以上就是各种分布式ID生成方案的详细代码demo,实际上,每种方案都有其优缺点,应根据具体业务场景和系统架构选择合适的方案。

以上就是redis分布式ID解决方案示例详解的详细内容,更多关于redis分布式ID的资料请关注服务器之家其它相关文章!

原文链接:https://juejin.cn/post/7207410405785993276

延伸 · 阅读

精彩推荐
  • RedisLinux服务器安装redis数据库图文教程

    Linux服务器安装redis数据库图文教程

    Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。这篇文章主要介绍...

    AlanLee(Java)5512019-11-12
  • RedisRedis列表类型的常用命令小结

    Redis列表类型的常用命令小结

    这篇文章给大家整理了在操作Redis列表类型中的常用命令,文章总结的很全面,对大家学习Redis具有一定的参考借鉴价值,下面来一起看看吧。 ...

    daisy3722019-10-30
  • Redis厉害了!原来Redis是这样集群的

    厉害了!原来Redis是这样集群的

    最近,各大互联网公司都开始了裁员,导致大量互联网从业人员又开始了艰辛的求职路。老手自不必说,但对于久在项目里但基本只是使用Redis的程序员们...

    今日头条5162020-06-08
  • RedisRedis SCAN命令详解

    Redis SCAN命令详解

    SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数,...

    废物大师兄10512022-07-27
  • RedisCentOS系统下Redis安装和自启动配置的步骤

    CentOS系统下Redis安装和自启动配置的步骤

    相信大家都知道Redis是一个C实现的基于内存、可持久化的键值对数据库,在分布式服务中常作为缓存服务。所以这篇文章将详细介绍在CentOS系统下如何从零...

    daisy2852019-10-30
  • RedisRedisDesktopManager无法远程连接Redis的完美解决方法

    RedisDesktopManager无法远程连接Redis的完美解决方法

    下载RedisDesktopManager客户端,输入服务器IP地址,端口(缺省值:6379);点击Test Connection按钮测试连接,连接失败,怎么回事呢?下面小编给大家带来了Re...

    何和远14512019-11-12
  • Redis基于Redis过期事件实现订单超时取消

    基于Redis过期事件实现订单超时取消

    这篇文章主要介绍了基于Redis过期事件实现订单超时取消,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋...

    WannaRunning12252021-08-03
  • Redis分布式爬虫处理Redis里的数据操作步骤

    分布式爬虫处理Redis里的数据操作步骤

    这篇文章主要介绍了分布式爬虫处理Redis里的数据操作步骤,数据分别存入mongodb和mysql数据库,具体内容详情及实例代码大家参考下本文 ...

    py哥3282019-11-12