面试官:可以的,这边电脑给你,你在这个测试项目下吧。
我:好的,谢谢。
时间流逝中…
大概敲了几分钟,废了九牛二虎之力,终于敲出来了,有好多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划分为,如下图的几段。
我顺手把我画的图递给他看了看,接着对着这个图进行解释。
我:第一位作为标识位,因为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();
//如果当前时间小于上一次生成的时间戳,说明系统时钟回退过就抛出异常
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
JavaScript
-
js的基本类型有哪些?引用类型有哪些?null和undefined的区别。
-
如何判断一个变量是Array类型?如何判断一个变量是Number类型?(都不止一种)
-
Object是引用类型嘛?引用类型和基本类型有什么区别?哪个是存在堆哪一个是存在栈上面的?
-
JS常见的dom操作api
-
解释一下事件冒泡和事件捕获
-
事件委托(手写例子),事件冒泡和捕获,如何阻止冒泡?如何组织默认事件?
-
对闭包的理解?什么时候构成闭包?闭包的实现方法?闭包的优缺点?
-
this有哪些使用场景?跟C,Java中的this有什么区别?如何改变this的值?
-
call,apply,bind
-
显示原型和隐式原型,手绘原型链,原型链是什么?为什么要有原型链
-
创建对象的多种方式
-
实现继承的多种方式和优缺点
-
new 一个对象具体做了什么
-
手写Ajax,XMLHttpRequest
-
变量提升
-
举例说明一个匿名函数的典型用例
-
指出JS的宿主对象和原生对象的区别,为什么扩展JS内置对象不是好的做法?有哪些内置对象和内置函数?
-
attribute和property的区别
-
document load和document DOMContentLoaded两个事件的区别
-
JS代码调试
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
-
new 一个对象具体做了什么
-
手写Ajax,XMLHttpRequest
-
变量提升
-
举例说明一个匿名函数的典型用例
-
指出JS的宿主对象和原生对象的区别,为什么扩展JS内置对象不是好的做法?有哪些内置对象和内置函数?
-
attribute和property的区别
-
document load和document DOMContentLoaded两个事件的区别
-
JS代码调试
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-CPePwZ6s-1712747747203)]