本文字数:2604

预计阅读时间:15分钟

01

引言

在现有分布式系统中,面对增长迅速的业务数据,id生成一直是非常重要的一环。而分布式系统的id生成方案需要满足几个重要特性:容错高可用、高性能高并发、全局唯一。

02

技术背景

我们系统中最开始使用的是通过数据库表生成对应的分布式id,数据库中存一张sequence表,只有一行数据,记录下一个id值。每次有新数据生成,都开启事务加锁查询当前值,然后再更新字段值加1。

经过测试,该方案每次生成分布式id的平均耗时为1.46毫秒,而跨机房获取id的平均耗时为5.32毫秒。

这种读数据库方案的弊端就很明显:

  1. 当qps过高时,数据库的压力就会大大增加;

  2. 跨机房读的耗时也会明显升高;

  3. 如果使用框架自带的逻辑,如Hibernate的strategy = GenerationType.TABLE策略,要求sequence表和数据表同数据源,所以当进行分库后,新库对id的获取就会很麻烦;

  4. 有很小的概率会在生成id读写数据库时,导致死锁,新增数据无法入库,我们就遇到过一次 - -!!!

当然这种方案有一个最大的好处:产生的id严格递增,对我们业务来说,这个特性非常重要。

03

现有分布式方案分析

分布式id生成这么重要,市面上当然有很多的解决方案。下边简单介绍一下几种常见的方案:

UUID

介绍:UUID是一个由32个十六进制数字组成,中间由横杠分割(例:372f3ba5-6359-4f1f-9184-a938a4908072),java中可以直接调用UUID.randomUUID()实现,也可以使用特定算法生成。

优点:实现简单,UUID的生成非常简单,不需要依赖于任何外部资源;实际应用中基本不会遇到重复的情况。

缺点:UUID长度较长,占用的存储空间较大,且可读性差;算法实现复杂,经测试存在效率问题。在数据库作为主键时,可能会影响写入性能;不是递增的。

雪花算法

介绍:SnowFlake是 Twitter 开源的分布式 id 生成算法,可以不用依赖任何第三方工具进行自增的数字类型的id生成;雪花算法的核心逻辑是使用一个 64 bit 的 long 型的数字作为全局唯一id。

雪花算法生成的唯一ID均为正数,所以这 64 个 bit 中,其中 1 个 bit 是不用的(第一个 bit 默认都是 0),然后用 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。

优点:实现简单,不依赖其他第三方库;高效,雪花算法能以极高的速度生成ID,每秒可生成数百万个,满足高并发场景的需求。

缺点:时间回拨问题;生成的id长度比较长;机器码不同,生成的id也不同,跟历史id相比变化比较大;生成的id趋势递增。

百度uid-generator框架

介绍:百度UidGenerator是基于snowflake算法思想实现的,但与原始算法不同的地方在于,UidGenerator支持自定义时间戳、工作机器id(workId)以及序列号等各个组成部分的位数,并且工作机器id采用用户自定义的生成策略。百度uid-generator有两种实现方式:DefaultUidGenerator和CachedUidGenerator。从性能和时间回拨问题考虑,一般都是考虑CachedUidGenerator类实现。

优点:性能好,每秒可生成数百万个id;简单易用,现成jar包直接接入,包括获取当前时间戳、数据中心id和机器id。支持多种部署方式,包括单机模式和分布式模式。

缺点:默认接入mybatis框架,否则需要自己重写dao层;生成的id趋势递增。

美团leaf框架

介绍:美团的leaf框架有两种模式。一种是雪花算法模式,也是基于雪花算法这里不再赘述。另一种模式是号段模式。号段模式:每次从数据库中取一个号段的id值,号段由step步长决定。然后把号段放在内存中。当号码使用到一定范围时,则更新到下一号段。

优点:每次取一个号段的id,大大减少了对数据库的读写,减轻了数据库压力;不同的机器存在不同的号段,放在内存中,速度快效率高,只需要考虑本机的线程安全问题。

缺点:如果有多台机器提供服务,那么每台机器生成的号段不同,只能保证趋势递增;如果有一台服务器,对于业务请求量巨大时,单台服务器可能会扛不住压力,服务器宕机就会使获取id服务不可用。

当然除了上述方案,还有其他的分布式id生成方案:比如zookeeper的顺序节点,滴滴的Tinyid框架,这里就不一一列举。

04

严格递增的分布式id生成方案

上述中的那些方案,可以看到除了使用zk的顺序节点,其他都是只能保证趋势递增,并不能保证严格递增(后请求的数据id,一定比先请求的数据id大)。对于我们业务来说,严格递增id非常有必要,而使用zk又需要维护一套高可用的zk集群。所以学习前人们的解决方案之后,诞生了我们自己的分布式id生成方案。

方案介绍

采用的是 数据库号段模式 加 缓存 加 监听 的方案,有两种id生成模式:

  1. 使用缓存生成;

  2. 使用数据库表生成。

具体工具可以自行选择。这里使用的是mysql + redis + nacos。

mysql中创建一张sequence表,主要字段:bizId: 业务表示,区分不同业务的id;maxId:目前号段最大的Id值;step:步长,每个号段包含的id个数。

redis中也需要存储三个key:currentId:当前已经使用到的id值;maxId:当前号段的最大id值,step:步长。

nacos开关的作用:控制是否id生成模式,打开:使用redis生成模式,关闭:使用数据库表生成模式;同时监听nacos开关,控制生成模式自动切换。

实现流程:

项目启动时如果nacos开关打开,检测redis中是否存在当前业务id相关的key,如果没有则读取数据库加载到redis中(注意先更新到下一号段)

当有新的写入请求时:

  1. 首先判断 redis心跳检测正常 且 nacos开关打开,则是缓存生成模式:

    1. 直接读取redis中对应currentId,通过incr()方法获取到下一个id值;

    2. 此时检查id时否合理,合理阈值可以自行设置。如果不合理则调用 数据库同步redis流程(先把数据库更新到下一号段,然后再更新redis中的值:currentId = 原maxId + step*0.1, 新maxId = 原maxId + step);

    3. 生成id后,异步线程判断:当前id是否已经使用了当前号段的百分之30,如果超过则更新获取下一号段。

  2. 否则:使用数据库生成模式,直接读取数据库sequence表把maxId字段作为当前id使用,maxId字段先加锁查询,然后再更新加1。

当有特殊情况时:

  1. redis集群不可用,通过心跳任务检测出状态不对,则直接调用api关闭开关。关闭后就是使用数据库模式生成id,虽然耗时有所增加,但增加量不多且可以保证业务流程不阻塞;

  2. 处理好redis问题后,修改naocs开关,程序监听到开关打开事件,则从数据库模式改为缓存模式生成:先更新下一号段避免id重复,然后把新更新的值写入redis中,下次请求就继续开始使用redis生成id。

总结:

这个方案使用redis来生成Id,主要是因为redis作为强大的中间件基本所有项目都会用到,随处可见,不用再引入新的第三方依赖;其次redis的自增自带原子性,生成的id是严格递增。

并且redis可以很好的抗住高QPS请求,经测试id的获取绝大部分小于等于1毫秒。

为了防止redis集群抽疯不可用,准备了数据库生成方案:直接读取数据库中的sequence表,查询并更新maxId字段加1。这样可以保证业务的正常运行,耗时平均涨几毫秒,属于可接受范围。在使用数据模式生成期间,就可以着手处理redis集群的问题,处理完后通过监听开关打开事件,再重新切换到缓存生成模式,继续生成严格递增的分布式id。(为了防止重复,先更新数据库到下一号段,把新值更新到redis中)

附流程图:

05

结论

这个方案主要是通过redis的自增来高效生成严格递增的id,可以用其他中间件代替。这个方案重要的是不只依赖于redis,还要对redis不可用的情况进行兜底检测,形成一个自动切换的闭环。

经测试该方案性能,相比于之前直接查询更新数据库sequence表方案,同机房获取id性能提升接近10倍,跨机房获取id性能提升接近7倍。

同时该方案也解决了之前遇到过的数据库sequence表死锁,导致业务数据无法新增入库的问题。

当然如果业务需求并不要求id严格递增,那么上边介绍的优秀的框架都可以使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/95651.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/95651.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/95651.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】二叉树相关算法题

目录1、二叉树介绍【1】核心概念【2】关键特性2、算法题【1】二叉树的前序遍历【2】二叉树的后序遍历1、二叉树介绍 【1】核心概念 结构含义节点结构二叉树由节点组成, 每个节点包含一个数据元素和最多两个子节点:左子节点和右子节点根节点树的顶部节点…

Vulnhub Deathnote靶机复现攻略

一、靶机安装 下载地址:https://download.vulnhub.com/deathnote/Deathnote.ova 下载好后使用VB打开,配置如下 二、主机发现 使用相同连接方式的kali进行后续操作(172.16.2.7)根据mac地址进行确认。 nmap -sn 172.16.2.1/24 三、端口扫描 端口开放了…

DevEco Studio 6.0.0 元服务页面跳转失败

背景,我使用最新的编辑器DevEco Studio 6.0.0,编写一个元服务,发现使用跳转页面的时候失败了!然后查看官方文档,两种方式都测试了,发现都不行。 方法1:Navigation路由跳转无效,见官方…

docker重启或系统重启后harbor自动启动

docker重启或系统重启后harbor自动启动docker重启或系统重启后harbor自动启动方法 1:在 docker-compose.yml 中配置重启策略(推荐)方法 2:创建 Systemd 服务(更可靠)方法 3:使用 Docker 的 Rest…

OpenZeppelin Contracts 架构分层分析

OpenZeppelin Contracts 是一个面向以太坊(及兼容 EVM 的区块链)生态系统的​​模块化、安全性优先、标准兼容的智能合约库​​。其内部代码按照功能职责与抽象层级,可系统性地划分为多个逻辑层次。理解这些层次及其依赖关系,对于…

Java-JVM的内存模型

一.JVM内存模型JVM内存模型可以从进程生命周期和线程生命周期1.线程生命周期每个线程都会有自己各自一份数据,不会存在线程安全问题1.程序计数器指示当前线程执行的字节码指令的行号,以便线程执行时可以回到正确的位置2.虚拟机栈线程私有的,与…

Highcharts Dashboards | 打造企业级数据仪表板:从图表到数据驾驶舱

企业日常决策、产品运营、业务监控,越来越依赖数据驱动。而仪表板(Dashboard)作为汇总展示多维度信息的“数据驾驶舱”,已成为企业可视化的核心场景之一。如果你正在寻找一款能够快速、灵活、安全构建仪表板的前端图表工具&#x…

基于Java的Markdown转Word工具(标题、段落、表格、Echarts图等)

项目源于我们开发的一款基于大模型的报告生成工具。由于需要将 Markdown 格式的内容导出为 Word 文档,而市面上缺乏合适的现成工具,所以决定自己开发一个Markdown转Word的工具。 🩷源码地址:daydayup-zyn/md2doc-plus &#x1f…

Unity:PlayerPrefs笔记

写在前面:写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。一、PlayerPrefs的基本方法1、存储相关PlayerPrefs的数据存储类似于键值对存储,一个键对应一个值。Unity…

SQL tutorials

SQL Literature SQL运行在资料库管理系统(Database Management System),如MySQL,Postgre SQL,Microsoft SQL Server, Oracle,etc。 SQL练习平台:https://sqliteviz.com/ EXAMPLE SQL…

MySQL快速恢复数据的N种方案完全教程

目录 1. 理解MySQL数据恢复的核心逻辑 1.1 数据丢失的常见场景 1.2 MySQL的“救命稻草”:关键文件和机制 2. 方案一:利用全量备份+binlog实现点对点恢复 2.1 准备工作 2.2 恢复步骤 2.3 实战案例 3. 方案二:利用InnoDB的崩溃恢复机制 3.1 崩溃恢复的原理 3.2 恢复步…

双屏加固笔记本电脑C156-2:坚固与高效的完美融合

在当今数字化时代,笔记本电脑已成为人们工作和生活中不可或缺的工具。然而,对于一些特殊行业和恶劣环境下的应用场景,普通笔记本电脑往往难以满足需求。此时,具备坚固耐用、高性能等特点的加固笔记本电脑应运而生。鲁成伟业的双屏…

Jenkins 环境部署

下载相关软件:Jenkins 的安装和设置 相关工具: Git : Git - Downloads java 17: Java Archive Downloads - Java SE 17.0.12 and earlier python : Download Python | Python.org jenkins、jenkins.war : Jenkins 的安装和设置 将所有软件安装后&am…

如何高效解决 Java 内存泄漏问题方法论

目录 一、系统化的诊断与优化方法论 二、获取内存快照:内存泄漏的第一步 (一)自动生成 Heap Dump (二)手动生成 Heap Dump 三、导入分析工具:MAT 和 JProfiler (一)MAT (Memory Analyzer Tool) (二)JProfiler (三)自身企业工具 四、深入分析:逐步排查内存…

HarmonyOS Camera Kit 全解析:从基础拍摄到跨设备协同的实战指南

在移动应用开发中,相机功能往往是提升用户体验的关键模块,但传统相机开发面临权限管理复杂、设备兼容性差、功能实现繁琐等痛点。HarmonyOS 作为面向全场景的分布式操作系统,其 Camera Kit(相机服务)通过统一的 API 接…

运用词向量模型分辨评论

代码实现:import jieba import pandas as pd hp pd.read_table(优质评价.txt,encodinggbk) cp pd.read_table(差评1.txt,encodinggbk) cp_segments [] contents cp.content.values.tolist() for content in contents:results jieba.lcut(content)if len(result…

基于Apache Flink的实时数据处理架构设计与高可用性实战经验分享

基于Apache Flink的实时数据处理架构设计与高可用性实战经验分享 一、业务场景描述 在现代电商平台中,实时用户行为数据(点击、浏览、购物车操作等)对业务决策、个性化推荐和风控都至关重要。我们需要搭建一个高吞吐、低延迟且具备高可用性的…

第二十四天:虚函数与纯虚函数

虚函数(Virtual Function) 定义:在基类中使用 virtual 关键字声明的成员函数,允许在派生类中被重新定义(覆盖,override)。其目的是实现多态性,即通过基类指针或引用调用函数时&#…

uniapp微信小程序-登录页面验证码的实现(springboot+vue前后端分离)EasyCaptcha验证码 超详细

一、项目技术栈登录页面暂时涉及到的技术栈如下:前端 Vue2 Element UI Axios,后端 Spring Boot 2 MyBatis MySQL Redis EasyCaptcha JWT Maven后端使用IntelliJ IDEA 2024.3.5 前端使用 HBuilder X 和 微信开发者工具二、实现功能及效果图过期管理验证码有…

【Java】HashMap的详细介绍

目录 一.HashMap 1.基本概念 2.底层数据结构: 3.HashCode和equals方法 为什么重写HashCode方法? 为什么重新equals方法? 4.put操作 1.初始化和数组检查 2.计算索引并检查桶是否为空 3.桶不为null,处理哈希冲突 4.判断链…