雪花算法生成实例


一、集群高并发情况下如何保证分布式唯一全局id生成?


1.1 为什么需要分布式全局唯一ID以及分布式ID的业务需求

对分库分表后需要有一个唯一ID来标识一条数据或消息;
特别一点的如订单、骑手、优惠券也都需要有唯一ID做标识

1.2 ID生成规则部分硬性要求

① 全局唯一:不能出现重复的ID号,作为唯一标识的基本要求
② 趋势递增:MySQL的InnoDB引擎中使用的是聚簇索引,多数索引使用B+树的数据结构来存储索引数据,在主键选择上应尽量使用有序的主键保证写入性能
③ 单调递增:保证下一个ID一定大于上一个ID,新增数据对于索引结构的影响也最小
④ 信息安全:ID信息安全
⑤ 含时间戳:快速了解分布式id的生成时间

1.3 ID号生成系统的可用性要求

① 高可用
② 低延迟
③ 高QPS

二、一般通用方案


2.1 UUID

UUID:(Universally Unique Identifier)的标准型式包含32个16进制数字,以连字号分为五段,形式为8-4-4-4-12的36个字符,示例:abcd1234-ef56-2356-elid-23fges458965
性能非常高:本地生成,没有网络消耗
缺点:
无序,每一次UUID数据的插入都会对主键的B+树进行很大的修改,容易引起B+树索引的分裂

2.2 数据库自增主键

数据库自增主键:分布式里,数据库的自增ID机制的主要原理是:数据库自增ID和mysql数据库的replace into实现的(replace into首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入)
缺点:
系统水平扩展比较困难,比如定义好了步长和机器台数之后,如果要添加机器该怎么做?
每次获取ID都得读写一次数据库,非常影响性能,不符合分布式ID里面的延迟低和高QPS的规则(在高并发下,如果都去数据库里面获取id,那是非常影响性能的)

2.3 基于Redis生成全局id策略

基于Redis生成全局id策略:因为Redis是单线程的,天生保证原子性,可以使用原子操作INCR和INCRBY来实现
集群分布式
在Redis集群情况下,可以设置不同的增长步长,同事key一定要设置有效期
缺点:
配置麻烦,维护Redis集群

2.4 SnowFlake

Twitter的分布式自增ID算法
①ID能够按照时间有序生成
②生成id的结果是一个64bit大小的整数,为一个Long型(转换成字符串后长度最多19)
③分布式系统内不会产生ID碰撞(由datacenter和workId作区分)并且效率较高
在这里插入图片描述
号段解析:
1bit-不用
因为二进制中最高位是符号位,1表示负数,0表示正数
生成的id一般都是用整数,所以最高位固定为0

41bit-时间戳,毫秒级
-41位可以表示241-1 个数字,
-如果只用来标识正整数(计算机中正数包含0),可以表示的数值范围是:0至241-1,减1是因为可表示的数值范围是从0开始算的,而不是1(可以使用69年,到2039年)

10bit-工作机器id,用来记录工作机器id
-可以部署在210 = 1024个节点,包括5位datacenter和5位workId
-5位(bit)可以表示的最大正整数是25-1 = 31,即可以用0、1、2、3、…31这32个数字,来表示不同的datacenterId和workId

12bit-序列号,用来记录同毫秒内产生的不同id
-12位(bit)可以表示的最大正整数是212-1 = 4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号

三、Hutool工具包代码实现


Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
引入依赖:

 <!--包含雪花算法的Hutool工具包-->
 <dependency>
     <groupId>cn.hutool</groupId>
     <artifactId>hutool-all</artifactId>
     <version>5.4.4</version>
 </dependency>

在这里插入图片描述
Controller层:

/**
 * @Author: Ron
 * @Create: 2020 10:41
 */
@RestController
public class SnowFlakeController {
    @Autowired
    private OrderService orderService;

    @RequestMapping("/snowflake")
    public String index() {
        return orderService.getIDBySnowFlake();
    }
}

Service层:

/**
 * @Author: Ron
 * @Create: 2020 10:43
 */
public interface OrderService {
    String getIDBySnowFlake();
}

Impl:

/**
 * @Author: Ron
 * @Create: 2020 10:43
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private IdGeneratorSnowflake idGenerator;

    @Override
    public String getIDBySnowFlake() {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            threadPool.submit(() -> {
                System.out.println(idGenerator.snowflakeId());
            });
        }
        threadPool.shutdown();
        return "Hello SnowFlake!!!";
    }
}

Mapper层:

/**
 * @Author: Ron
 * @Create: 2020 10:46
 */
@Repository
@Slf4j
public class IdGeneratorSnowflake {
    private long workId = 0;
    private long datacenterId = 1;
    private Snowflake snowflake = IdUtil.createSnowflake(workId, 1);

    @PostConstruct
    public void init() {
        workId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
        log.info("当前机器id: {}", workId);
    }

    public synchronized long snowflakeId() {
        return snowflake.nextId();
    }

    // 范围为0~31
    public synchronized long snowflakeId(long workId, long datacenterId) {
        Snowflake snowflake = IdUtil.createSnowflake(workId, datacenterId);
        return snowflake.nextId();
    }

    public static void main(String[] args) {
        System.out.println(new IdGeneratorSnowflake().snowflakeId());
    }

}

启动类:

@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

}

application.yml配置文件

server:
  port: 8888

页面访问:
在这里插入图片描述
控制台打印结果:

在这里插入图片描述
缺点:
依赖机器时钟,如果机器时钟回拨,会导致重复ID生成
在单机上是递增的,但是由于设计到分布式环境,每台机器的时钟不可能完全同步,有时候会出现不是全局递增的情况(此缺点可以认为无所谓,一般分布式ID只要求趋势递增,并不会严格要求递增,90%的需求都只要求趋势递增)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值