分布式ID生成策略,我和面试官掰扯了一个小时

2.png

我:在设计的初始阶段可以设计一个有初始值字段,并有步长字段的表,当每次要申请批量ID的时候,就可以去该表中申请,每次申请后「初始值=上一次的初始值+步长」。

我:这样就能保持初始值是每一个申请的ID的最大值,避免了ID的重复,并且每次都会有ID使用,一次就会生成一批的id来使用,这样访问数据库的次数大大减少。

我:但是这一种方案依旧有自己的缺点,依然不能抗真正意义上的高并发。

UUID生成

我:第四种方式是使用「UUID生成」的方式生成分布式ID,UUID的核心思想是使用「机器的网卡、当地时间、一个随机数」来生成UUID。

我:使用UUID的方式只需要调用UUID.randomUUID().toString()就可以生成,这种方式方便简单,本地生成,不会消耗网络。

我:当时简单的东西,出现的问题就会越多,不利于存储,16字节128位,通常是以36位长度的字符串表示,很多的场景都不适合。

我:并且UUID生成的无序的字符串,查询效率低下,没有实际的业务含义,不具备自增特性,所以都不会使用UUID作为分布式ID来使用。

面试官:恩额,那你知道生成UUID的方式有几种吗?不知道没关系,这个只是作为一个扩展。

我:这个我只知道可以通过「当前的时间戳及机器mac地址」来生成,可以确保生成的UUID全球唯一,其它的没有了解过。

面试官:嗯嗯,没关系的。

Redis的方式

我:为了解决上面纯关系型数据库生成分布式ID无法抗高并发的问题,可以使用Redis的方式来生成分布式ID。

我:Redis本身有incr和increby 这样自增的命令,保证原子性,生成的ID也是有序的。

我:Redis基于内存操作,性能高效,不依赖于数据库,数据天然有序,利于分页和排序。

我:但是这个方案也会有自己的缺点,因为增加了中间件,需要自己编码实现工作量增大,增加复杂度。

我:使用Redis的方式还要考虑持久化,Redis的持久化有两种「RDB和AOF」,「RDB是以快照的形式进行持久化,会丢失上一次快照至此时间的数据」。

我:「AOF可以设置一秒持久化一次,丢失的数据是秒内的」,也会存在可能上一次自增后的秒内的ID没有持久化的问题。

我:但是这种方法相对于上面的关系型数据库生成分布式ID的方法而言,已经优越了许多。

我:若是数据量比较大的话,重启Redis的时间也会比较长,可以采用Redis的集群方式。

面试官:你能手写一下Redis的生成分布式ID的工具类代码吗?

我奔溃了,我最怕手写了,因为工具类这种东西,基本就是项目开始的时候写一次,后面对后市重复使用,记不住,还要手写,这也太难为我怕虎了吧。

我:手写应该不行,因为有些API记不住,工具类基本就是项目开始的时候写一些,后续都没有去看过了,没有专门去记它。

我:我可以使用您的电脑吗?使用电脑应该可以敲出这些工具类。

面试官:可以的,这边电脑给你,你在这个测试项目下吧。

我:好的,谢谢。

时间流逝中…

大概敲了几分钟,废了九牛二虎之力,终于敲出来了,有好多API记不住,只能慢慢的找了,写了主要两种方式来生成分布式ID。

第一种是使用RedisAtomicLong 原子类使用CAS操作来生成ID。

@Service

public class RedisSequenceFactory {

@Autowired

RedisTemplate<String, String> redisTemplate;

public void setSeq(String key, int value, Date expireTime) {

RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());

counter.set(value);

counter.expireAt(expireTime);

}

public void setSeq(String key, int value, long timeout, TimeUnit unit) {

RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());

counter.set(value);

counter.expire(timeout, unit);

}

public long generate(String key) {

RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());

return counter.incrementAndGet();

}

public long incr(String key, Date expireTime) {

RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());

counter.expireAt(expireTime);

return counter.incrementAndGet();

}

public long incr(String key, int increment) {

RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());

return counter.addAndGet(increment);

}

public long incr(String key, int increment, Date expireTime) {

RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());

counter.expireAt(expireTime);

return counter.addAndGet(increment);

}

}

第二种是使用redisTemplate.opsForHash()和结合UUID的方式来生成生成ID。

public Long getSeq(String key,String hashKey,Long delta) throws BusinessException{

try {

if (null == delta) {

delta=1L;

}

return redisTemplate.opsForHash().increment(key, hashKey, delta);

} catch (Exception e) {  // 若是redis宕机就采用uuid的方式

int first = new Random(10).nextInt(8) + 1;

int randNo=UUID.randomUUID().toString().hashCode();

if (randNo < 0) {

randNo=-randNo;

}

return Long.valueOf(first + String.format(“d”, randNo));

}

}

我把电脑移回给面试官,他很快的扫了一下我的代码,说了一句。

面试官:小伙子,不写注释哦,这个习惯不好哦。

我:哦哦,谢谢提醒,不好意思,下次我会注意的。

雪花算法

我:第六种方式是「雪花算法」,也是现在市面上比较流行的生成分布式ID的方法。

说着说着,我知道画图又是必不可少的了,于是在桌子上有画了起来,面试官好奇的看看我,知道了我在干啥,又耐心的等了等。

我:他是采用64bit作为ID生成类型,并且将64bit划分为,如下图的几段。

我顺手把我画的图递给他看了看,接着对着这个图进行解释。

3.png

我:第一位作为标识位,因为Java中long类型的时代符号的,因为ID位正数,所以第一位位0。

我:接着的41bit是时间戳,毫秒级位单位,注意这里的时间戳并不是指当前时间的时间戳,而是值之间差(「当前时间-开始时间」)。

我:这里的开始时间一般是指ID生成器的开始时间,是由我们程序自己指定的。

我:接着后面的10bit:包括5位的「数据中心标识ID(datacenterId)和5位的机器标识ID(workerId)」,可以最多标识1024个节点(1<<10=1024)。

我:最的12位是序列号,12位的计数顺序支持每个节点每毫秒差生4096序列号(1<<12=4096)。

我:雪花算法使用数据中心ID和机器ID作为标识,不会产生ID的重复,并且是在本地生成,不会消耗网络,效率高,有数据显示,每秒能生成26万个ID。

我:但是雪花算法也是又自己的缺点,因为雪花算法的计算依赖于时间,若是系统时间回拨,就会产生重复ID的情况。

面试官:那对于时间回拨产生重复ID的情况,你有什么比较好的解决方案吗?

我:在雪花算法的实现中,若是其前置的时间等于当前的时间,就抛出异常,也可以关闭掉时间回拨。

我:对于回拨时间比较短的,可以等待回拨时间过后再生成ID。

面试官:你可以帮我敲一个雪花算法吗?我这键盘给你。

我:……

我:好的。

时间流逝中…

过了几分钟时间,也总算是把雪花算法给敲出来了,真正要老命,面个试怎么就那么难呢?

/**

* 雪花算法

* @author:黎杜

*/

public class SnowflakeIdWorker {

/** 开始时间截 */

private final long twepoch = 1530051700000L;

/** 机器id的位数 */

private final long workerIdBits = 5L;

/** 数据标识id的位数 */

private final long datacenterIdBits = 5L;

/** 最大的机器id,结果是31 */

private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

/** 最大的数据标识id,结果是31 */

private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

/** 序列的位数 */

private final long sequenceBits = 12L;

/** 机器ID向左移12位 */

private final long workerIdShift = sequenceBits;

/** 数据标识id向左移17位 */

private final long datacenterIdShift = sequenceBits + workerIdBits;

/** 时间截向左移22位*/

private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

/** 生成序列的掩码 */

private final long sequenceMask = -1L ^ (-1L << sequenceBits);

/** 工作机器ID(0~31) */

private long workerId;

/** 数据中心ID(0~31) */

private long datacenterId;

/** 毫秒内序列(0~4095) */

private long sequence = 0L;

/** 上次生成ID的时间截 */

private long lastTimestamp = -1L;

/**

* 构造函数

* @param workerId 工作ID (0~31)

* @param datacenterId 数据中心ID (0~31)

*/

public SnowflakeIdWorker(long workerId, long datacenterId) {

if (workerId > maxWorkerId || workerId < 0) {

throw new IllegalArgumentException(String.format(“worker Id can’t be greater than %d or less than 0”, maxWorkerId));

}

if (datacenterId > maxDatacenterId || datacenterId < 0) {

throw new IllegalArgumentException(String.format(“datacenter Id can’t be greater than %d or less than 0”, maxDatacenterId));

}

this.workerId = workerId;

this.datacenterId = datacenterId;

}

/**

* 获得下一个ID (该方法是线程安全的)

* @return SnowflakeId

*/

public synchronized long nextId() {

long timestamp = getCurrentTime();

//如果当前时间小于上一次生成的时间戳,说明系统时钟回退过就抛出异常

if (timestamp < lastTimestamp) {

throw new BusinessionException(“回拨的时间为:”+lastTimestamp - timestamp);

}

//如果是同一时间生成的,则进行毫秒内序列

if (lastTimestamp == timestamp) {

sequence = (sequence + 1) & sequenceMask;

//毫秒内序列溢出

if (sequence == 0) {

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-gwOO8j0t-1715848199613)]

[外链图片转存中…(img-Fx860UYp-1715848199613)]

[外链图片转存中…(img-6OwdewMZ-1715848199614)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

<think>嗯,用户问的是如何拆除SATA百分表的表壳。首先,我需要确认用户指的是哪种类型的百分表。SATA可能是一个品牌,比如生产工具的那个SATA,也可能是其他类型的设备,但通常百分表指的是机械式百分表,用于测量工件的尺寸或形状误差。这类工具的表壳拆除可能需要一些特定的步骤,需要小心处理以免损坏内部结构。 接下来,我需要回忆一下百分表的结构。通常,百分表的外壳可能由前盖、后盖主体部分组成,可能用螺丝固定,或者有卡扣设计。如果用户要拆除表壳,可能是为了维修、更换电池(如果是电子式)或清洁内部零件。需要注意的是,百分表内部有精密的齿轮弹簧机构,拆卸时需非常谨慎,避免零件丢失或变形。 然后,我应该考虑可能的步骤。首先,确保工具齐全,比如小螺丝刀、镊子、防静电垫等。然后,检查表壳是否有可见的螺丝,通常在背面或侧面。如果有螺丝,用合适的螺丝刀拧下。如果没有螺丝,可能是卡扣式设计,需要用塑料撬棒轻轻撬开,避免划伤外壳。 另外,需要注意是否有隐藏的螺丝,比如在标签或橡胶垫下面。有些制造商可能会把螺丝藏在这样的地方,需要用户先移除这些覆盖物。拆下螺丝后,轻轻分离前后盖,注意可能有排线连接,比如电子百分表的显示屏,这时候需要断开排线,避免拉扯导致损坏。 拆开后,用户可能会看到内部结构,这时候需要记录每个零件的位置,或者拍照,以便后续组装。如果遇到阻力不要强行拆卸,可能还有未注意到的固定点。同时,提醒用户在操作前断开电源(如果是电子式),并避免触碰敏感部件如游丝或齿轮。 最后,组装时需要按照相反的顺序进行,确保所有零件正确归位,螺丝拧紧但不过度用力,避免损坏螺纹。如果过程中遇到困难,建议联系专业维修人员,以免造成不可逆的损坏。 可能用户遇到的问题包括:不确定螺丝位置,卡扣太紧不敢用力,或者拆开后不知道如何操作。需要给出具体的步骤,并强调谨慎操作的重要性。此外,不同型号的百分表可能有不同的拆卸方式,最好参考用户手册,但用户可能没有手册,所以需要通用步骤。 总结一下,步骤大致是:准备工具,检查螺丝或卡扣,小心分离外壳,注意内部连接,记录拆卸过程,组装时反向操作。同时提醒风险,建议非专业人士寻求帮助。</think>拆卸SATA百分表表壳需谨慎操作,避免损坏精密部件。以下是通用步骤(具体结构可能因型号而异): --- ### **工具准备** - 小号十字螺丝刀(PH00或PH0) - 塑料撬棒或薄片(避免金属划伤外壳) - 防静电手套/软布 - 镊子(处理小零件) --- ### **拆卸步骤** 1. **断电与检查** - 若为电子百分表,先取出电池。 - 观察表壳是否有螺丝孔、标签或橡胶垫遮挡的隐藏螺丝。 2. **移除可见螺丝** - 用螺丝刀拧下表壳背面/侧面的螺丝,分类保存避免丢失。 3. **分离卡扣式外壳** - 若无螺丝,用塑料撬棒沿表壳缝隙(通常在侧边或底部)轻压,逐步撬开卡扣。 - 避免强行掰扯,防止卡扣断裂。 4. **断开内部连接(电子表适用)** - 若表盘有排线连接主板,用镊子轻抬排线扣,缓慢拔出排线。 5. **取出机芯(如需进一步维修)** - 用镊子固定机芯支架,小心将其从外壳中托出,注意避让指针游丝。 --- ### **注意事项** - 全程轻拿轻放,避免震动导致齿轮错位。 - 拆卸时拍照记录零件位置,方便还原。 - 若表壳与玻璃表盘一体,不建议非专业人士继续拆解,需专用工具分离。 - 组装时按反向顺序操作,确保螺丝拧紧力度均匀。 --- ### **风险提示** - 自行拆解可能导致精度下降或失去保修资格。 - 若无经验,建议联系SATA官方售后或专业仪器维修机构。 (提示:部分老款机械百分表需专用工具调整游丝张力,非必要勿拆卸机芯内部。)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值