您的位置:首页 > 教育 > 培训 > 如何做跨境电商_精品一卡2卡三卡4卡分类_汕头百度网站排名_优化模型的推广

如何做跨境电商_精品一卡2卡三卡4卡分类_汕头百度网站排名_优化模型的推广

2025/3/10 16:03:35 来源:https://blog.csdn.net/qq_43515464/article/details/146129856  浏览:    关键词:如何做跨境电商_精品一卡2卡三卡4卡分类_汕头百度网站排名_优化模型的推广
如何做跨境电商_精品一卡2卡三卡4卡分类_汕头百度网站排名_优化模型的推广
分布式id基本上都基于号段模式或者雪花算法模式实现,但是各有优缺点,需要根据业务情况下确定具体使用方案

UUID

UUID (Universally Unique ldentifier),通用唯一识别码。UUID是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。UUID由以下几部分的组合

  1. 当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
  2. 时钟序列。
  3. 全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。UUID 是由一组32位数的16进制数字所构成,以连字号分隔的五组来显示
        System.out.println(UUID.randomUUID().version()); // 版本为4UUID uuid = UUID.randomUUID();System.out.println(uuid.toString());System.out.println(UUID.fromString(uuid.toString()));

优点

  1. 简单、代码方便
  2. 性能好,全球唯

缺点

  1. 没有排序,无法保证趋势递增
  2. 使用字符存储,查询效率低
  3. 占用空间大,传输效率低

自增ID

Oracle,DB2,达梦,TD等分布式数据库有自增id,可以实现对应需求。

如果不是分布式数据库,表数据量大了,需要分库分表,这时这些数据库的自增id就不符合业务需求了,可以维护一张id自增表来满足业务需求

数据库A,数据库B,id自增表(由数据库A或者B来维护)

  1. 根据不同的业务创建不同的自增表
  2. 插入数据前查询下个id
  3. 查询到的id就为数据库A或者B对应业务id

缺点

  1. 需求根据业务情况维护多张id自增表(维护一张全局的也行)
  2. 如果id自增表挂了就影响业务了(数据库都挂了,还要啥业务,赶紧让数据库恢复才是王道)
  3. 并发高了,查询id自增表的次数就多了,对性能有一定要求(数据库这点qps都hold不住,要它有何用?)

数据库多主模式

还是分库分表的情况下,但是这次是id自增表由多个数据库维护,比如订单id自增表,数据库A和B都去维护

数据库A,数据库B,数据库A的id自增表、数据库B的id自增表

  1. 根据不同的业务创建不同的自增表。数据库A,B都创建id自增表

  2. 数据库A的id自增步长为1,数据库B的id自增步长为2,步长可以通过下面的方式确认

    有几台数据库,步长就为几,假如3台,那么对应的id结果如下

    A 1 4 7

    B 2 5 8

    C 3 6 9

  3. 插入数据前查询下个id

  4. 查询到的id就为应业务id

缺点

  1. 需求根据业务情况维护多张id自增表(维护一张全局的也行)
  2. 并发高了,查询id自增表的次数就多了,对性能有一定要求(数据库这点qps都hold不住,要它有何用?)

号段模式

假如有服务A,B,C。通过乐观锁的方式去查询,更新号段表。

比如支付业务:服务A申请到的ID范围是1-1000,B申请到的范围是1001-2000,C申请到的是2001-3000。将对应id保存在自己服务的内存中,使用时自己在范围内自增,使用完后,重新申请。这样就减少了访问数据库的频次(数据库这点qps都hold不住,要它有何用?)

  1. 数据库维护一张或多张号段表(可以根据不同的业务类型来分)
  2. 各个服务通过乐观锁的方式去查询,更新号段表,将对应自己的号段保存的到自己的内存
  3. 各个服务在自己的号段范围内进行业务操作
  4. 号段用完重新申请(防止高并发号段用得太快,可以提前申请,或者定时任务申请)

为什么使用乐观锁?

防止A,B,C三个服务并发修改同一条数据

CREATE TABLE id_generator(id int(10) NOT NULL,max_id bigint(20) NOT NULL COMMENT '当前最大id',step int(20) NOT NULL COMMENT '号段的步长',type int(20) NOT NULL COMMENT '业务类型',version int(20) NOT NULL COMMENT '版本号',PRIMARY KEY(id)
)
idmax_idsteptypeversion
120001000cust_pay2
220001000user_order2
340001000Shopping_Cart2
type:不同的业务类型
step:号段步长
max_id:当前最大号段长度
version:乐观锁

缺点

  1. 其中一个服务宕了,重启需要重新申请id,导致id不连续。(保存到其它中间件内存可以避免,但是都用中间件了,为啥不直接用redis)
  2. 号段连续可能导致业务交易情况泄露

Redis

基本逻辑就是使用redis自己incr命令来实现。单度自增1满足不了具体的业务需求的,key可以考虑 ”时间戳:业务类型:自增ID“

ID生成的持久化问题,如果Redis宕机了怎么进行恢复?

同时开启RDB与AOF

当个节点宕机怎么办?

当然针对故障问题我们可以通过Redis集群来处理,比如我们有三个Redis的Master节点。可以初始化每台Redis的值分别是1, 2, 3,然后分别把分布式ID的KEY用Hash Tags固定每一个master节点,步长就是master节点的个数。

各个Redis生成的ID为:
A: 1, 4, 7
B: 2, 5, 8
C: 3, 6, 9

缺点

  1. 集群节点确定是后,后面调整不是很友好

雪花算法

41位时间戳:精确到毫秒级,记录ID生成的时间。(Timestamp)
10位机器ID:用于标识不同机器或节点,可自定义分配。(Worker ID) && (Datacenter ID)
12位序列号:解决同一毫秒内生成多个ID的冲突问题。(Sequence Number)
符号位(1 bit):固定为0,保证生成的ID始终为正数。
时间戳(41 bits):表示自定义的纪元(epoch)以来的时间差,通常选择一个特定的时间点作为起始点(如Twitter使用的纪元是2010年11月4日)。这个时间戳可以精确到毫秒级别。最大可表示的时间范围约为 2^41 / (1000 * 60 * 60 * 24 * 365) ≈ 69 年。
数据中心ID(5 bits):用于区分不同的数据中心,最多可以支持 2^5 = 32 个数据中心。
工作节点ID(5 bits):用于标识同一数据中心内的不同工作节点或服务实例,最多可以支持 2^5 = 32 个工作节点。
序列号(12 bits):在同一毫秒内生成的多个ID可以通过序列号来区分,最大值为 2^12 = 4096。如果在某一毫秒内生成的ID数量超过4096,则会等待下一毫秒继续生成。

0-00000000-00000000-00000000-00000000-00000000-00000000-0000000

雪花算法的工作流程

  1. 获取当前时间戳:从系统时钟中获取当前的时间戳,并减去预设的纪元时间,得到相对于纪元的相对时间戳。
  2. 计算序列号:在同一毫秒内生成多个ID时,使用递增的序列号进行区分。当序列号达到最大值(4096)时,需要等待下一毫秒才能继续生成新的ID。
  3. 组合ID:将上述各部分按顺序拼接成一个64位的整数ID。
//要想实现雪花算法,就按照上面要求写,不会的话,直接git上面cv//https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java 下面这段代码,从这个git复制的
//https://cloud.tencent.cn/developer/article/2330461  cv这个的也一样public class SnowFlake {/*** 起始的时间戳* LocalDateTime localDateTime = LocalDateTime.of(2025, 3, 7, 0, 0, 0);* long time = localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli(); 通过这个方法可以得到指定时间的毫秒数*/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 SnowFlake(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;}/*** 产生下一个ID** @return*///这里使用同步锁,可以参考LongAdder的add源码,使用乐观锁加标志位的方式,提高效率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 {//不同毫秒内,序列号置为0sequence = 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();}public static void main(String[] args) {SnowFlake snowFlake = new SnowFlake(2, 3);long id = snowFlake.nextId();System.out.println(id);System.out.println(Long.toBinaryString(id));//1095512168495067136//111100110100000010101000001101010010010001000011000000000000//        for (int i = 0; i < (1 << 12); i++) {
//            System.out.println(snowFlake.nextId());
//        }}
}

为什么有使用年限的限制

因为表示时间年限的位数只有41位,所以可以表示最大的位2的41次方左右

同一毫秒达到最大的,序列号自增怎么办

表示同一毫秒的自增id位12位,也就是说1毫秒内可以获取2的12次方左右的自增个数id。获取超过了这个数,直接等待1毫秒,重新获取这一毫秒的自增id

时针回拨问题

时间回拨就是服务器时间比实际实际过快,或者过慢。需要将时间调整到正常时间。过慢直接调整到正常时间就行。对雪花算法没有什么影响。但是过快的话,需要将时间调回去,雪花算法极有可能会生成重复的id,雪花算法是根据时间来进行判断的,这段时间已经使用了,所以再次使用就会生成重复的id。

  1. 判断回拨时间是否小于100ms,小于100ms,直接循环等待100ms
  2. 如果大于1秒。可以考虑,当前分布式id服务直接下线
  3. 检测时间大于500ms且小于1000ms,可以考虑是其它分布式id服务获取
  4. 大于100ms且小于500ms。可以一开始就当前时间戳(currStmp)500毫秒后的最大的自增id,这样一来每次都获取的是500毫秒后的最大的自增id

个人觉得发现时间快了,不把时间调回去,也是一种解决方案。但是为了防止,服务器自动校准时间,还是要做下冗余方案。

生产环境一般怎么部署

多部署几台分布式id服务,一般用nacos来做注册中心,这样可以实现手动上下线。保存分布式id服务的工作节点和数据中心可以使用redis,zookeeper,db等来做持久化处理,这样分布式id服务重启后的工作节点和数据中心就不会丢失或者出现重复

缺点

  1. 有使用年限的限制(这影响不大)
  2. 有时间回拨问题(可以解决问题不大)

百度uid-generator

https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md 中文文档地址

UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。 在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。

依赖版本:Java8及以上版本, MySQL(内置WorkerID分配器, 启动阶段通过DB进行分配; 如自定义实现, 则DB非必选依赖)

美团Leaf

https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.md
美团的分布式id有两种模式:
1.号段模式
2.雪花算法https://tech.meituan.com/2017/04/21/mt-leaf.html  美团对应分布式id的介绍

滴滴Tinyid

https://github.com/didi/tinyid/wiki

Tinyid是用Java开发的一款分布式id生成系统,基于数据库号段算法实现,关于这个算法可以参考美团leaf或者tinyid原理介绍。Tinyid扩展了leaf-segment算法,支持了多db(master),同时提供了java-client(sdk)使id生成本地化,获得了更好的性能与可用性。Tinyid在滴滴客服部门使用,均通过tinyid-client方式接入,每天生成亿级别的id。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com