如何保证水平分表后的多张表中的id是全局唯一性?

雪花

简介

目前的服务基本都是分布式和微服务的形式,大量的数据也导致了分库分表的产生。对于横向分表,需要保证表中id的全局唯一性。

对于MySQL来说,一张表中的主键id一般采用自增方式,但是如果横向划分表,会在多张表中产生重复的id值。那么如何保证水平子表之后的多个表中的id是全局唯一的呢?

如果还是使用数据库主键自增形式,可以为不同的表初始化不同的初始值,然后按照指定的步长自增。比如有3张拆分表,初始主键值为1、2、3,自增步长为3。

当然也有人使用UUID作为主键,但是UUID生成无序字符串,不适合MySQL推荐使用增长的数值作为主键。

你也可以使用Redis的自增原子性来生成唯一的id,但是这个方法在业界很少使用。

当然还有其他的解决方案,不同的互联网公司都有自己的内部实现。 Snowflake算法是解决分布式id的高效方案之一,也被很多互联网公司推荐使用。

SnowFlake 雪花算法

SnowFlake中文意思是雪花,所以叫雪花算法。 Twitter 最初在内部使用它在分布式环境中生成唯一 ID。 2014年开源了scala语言版本。

雪花算法的原理是生成一个唯一的64位long类型的id。

最高1位固定值为0,因为生成的id是正整数,如果是1就是负数。

接下来的 41 位存储毫秒时间戳,2^41/(1000606024365)=69,可以使用大约 69 年。

接下来的10位存储机器码,包括5位datacenterId和5位workerId。最多可部署 2^10=1024 台机器。

最后 12 位存储序列号。相同的毫秒时间戳通过这个递增的序列号来区分。即对于同一台机器,在相同的毫秒时间戳下,可以生成2^12=4096个唯一ID。

Snowflake 算法可以部署为单独的服务,然后需要全局唯一 id 的系统可以请求 Snowflake 算法服务获取 id。

对于每一个Snowflake算法服务,需要先指定一个10位的机器码,可以根据自己的业务来设置。比如房间号+机器号,机器号+服务号,或者其他任何可以区分的10位整数值。

MybatisPlus实现的实际应用

依赖:

     	
            com.baomidou
            mybatis-plus-boot-starter
            3.3.1
        

yml 配置:

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    worker-id: ${random.int(1,31)}
    datacenter-id: ${random.int(1,31)}

测试实体:

@Data
@TableName("test_content")
public class TestContent {
    /**
     * ID

     */
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    /**
     * 数据内容
     */
    private String content;
    /**
     * 部门id
     */
    private Integer deptId;
}

测试控制层:

  @GetMapping("/test2")
    public String add() {
        TestContent testContent = new TestContent();
        testContent.setContent(new Random().nextInt() + "自定义添加内容");
        testContent.setDeptId(1);
        int insert = testContentService.getBaseMapper().insert(testContent);
        log.info("插入成功:{}", testContent.getId());
        return "插入成功";
    }

插入测试:

非ID字段需要id时可以使用idwork

        testContent.setId(IdWorker.getId());

源码分析:

IdWorker提供了获取id的基本方法,底层通过DefaultIdentifierGenerator生成Sequence类生成雪花id


public class IdWorker {

图片[1]-如何保证水平分表后的多张表中的id是全局唯一性?-唐朝资源网

private static IdentifierGenerator IDENTIFIER_GENERATOR = new DefaultIdentifierGenerator(); public static final DateTimeFormatter MILLISECOND = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); public IdWorker() { } public static long getId() { return getId(new Object()); } public static long getId(Object entity) { return IDENTIFIER_GENERATOR.nextId(entity).longValue(); } public static String getIdStr() { return getIdStr(new Object()); } public static String getIdStr(Object entity) { return IDENTIFIER_GENERATOR.nextId(entity).toString(); } public static String getMillisecond() { return LocalDateTime.now().format(MILLISECOND); } public static String getTimeId() { return getMillisecond() + getIdStr(); } public static void initSequence(long workerId, long dataCenterId) { IDENTIFIER_GENERATOR = new DefaultIdentifierGenerator(workerId, dataCenterId); } public static void setIdentifierGenerator(IdentifierGenerator identifierGenerator) { IDENTIFIER_GENERATOR = identifierGenerator; } public static String get32UUID() { ThreadLocalRandom random = ThreadLocalRandom.current(); return (new UUID(random.nextLong(), random.nextLong())).toString().replace("-", ""); } }

Sequence类:主要构造方法包含两个参数,类似于雪花算法的机器ID和服务ID。集群模式下最好不要重复,否则生成的ID可能会重复。这两个参数可以在YML文件中配置

public class Sequence {
    private static final Log logger = LogFactory.getLog(Sequence.class);
    private final long twepoch = 1288834974657L;
    private final long workerIdBits = 5L;
    private final long datacenterIdBits = 5L;
    private final long maxWorkerId = 31L;
    private final long maxDatacenterId = 31L;
    private final long sequenceBits = 12L;
    private final long workerIdShift = 12L;
    private final long datacenterIdShift = 17L;
    private final long timestampLeftShift = 22L;
    private final long sequenceMask = 4095L;
    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    public Sequence() {
        this.datacenterId = getDatacenterId(31L);
        this.workerId = getMaxWorkerId(this.datacenterId, 31L);
    }
    public Sequence(long workerId, long datacenterId) {
        Assert.isFalse(workerId > 31L || workerId  31L || datacenterId 
        StringBuilder mpid = new StringBuilder();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (StringUtils.isNotBlank(name)) {
            mpid.append(name.split("@")[0]);
        }
        return (long)(mpid.toString().hashCode() & 'uffff') % (maxWorkerId + 1L);
    }
    protected static long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                if (null != mac) {
                    id = (255L & (long)mac[mac.length - 1] | 65280L & (long)mac[mac.length - 2] <> 6;
                    id %= maxDatacenterId + 1L;
                }
            }
        } catch (Exception var7) {
            logger.warn(" getDatacenterId: " + var7.getMessage());
        }
        return id;
    }
    public synchronized long nextId() {
        long timestamp = this.timeGen();
        if (timestamp  5L) {
                throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));

图片[2]-如何保证水平分表后的多张表中的id是全局唯一性?-唐朝资源网

} try { this.wait(offset << 1); timestamp = this.timeGen(); if (timestamp < this.lastTimestamp) { throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset)); } } catch (Exception var6) { throw new RuntimeException(var6); } } if (this.lastTimestamp == timestamp) { this.sequence = this.sequence + 1L & 4095L; if (this.sequence == 0L) { timestamp = this.tilNextMillis(this.lastTimestamp); } } else { this.sequence = ThreadLocalRandom.current().nextLong(1L, 3L); } this.lastTimestamp = timestamp; return timestamp - 1288834974657L << 22 | this.datacenterId << 17 | this.workerId << 12 | this.sequence; } protected long tilNextMillis(long lastTimestamp) { long timestamp; for(timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) { } return timestamp; } protected long timeGen() { return SystemClock.now(); } }

© 版权声明
THE END
喜欢就支持一下吧
点赞245赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容