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();
//如果当前时间小于上一次生成的时间戳,说明系统时钟回退过就抛出异常
if (timestamp < lastTimestamp) {
throw new BusinessionException(“回拨的时间为:”+lastTimestamp - timestamp);
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
} else { //时间戳改变,毫秒内序列重置
sequence = 0L;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift) // 计算时间戳
| (datacenterId << datacenterIdShift) // 计算数据中心
| (workerId << workerIdShift) // 计算机器ID
| sequence; // 序列号
}
/**
*获得新的时间戳
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = getCurrentTime();
// 若是当前时间等于上一次的1时间就一直阻塞,知道获取到最新的时间(回拨后的时间)
while (timestamp <= lastTimestamp) {
timestamp = getCurrentTime();
}
return timestamp;
}
/**
* 获取当前时间
* @return 当前时间(毫秒)
*/
protected long getCurrentTime() {
return System.currentTimeMillis();
}
为了给面试官留下个好印象,这下也写上了注解,免得他又说我,敲完我又把电脑移回给他,他快速的看了看,点了点头,嘴角露出思思的笑意。
面试官:嗯,你的底子还算比价扎实,面试之前早有准备吧,看了很多的面试资料。
我心想怎么是面试之前准备呢?我是一直再准备,从工作到现在都在总结自己的知识点,形成自己的知识体系,为了迎合他,也只能说是。
我:嗯嗯,是的,准备了很久,算是比较充分。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
最后
推荐一些系统学习的途径和方法。
每个Web开发人员必备,很权威很齐全的Web开发文档。作为学习辞典使用,可以查询到每个概念、方法、属性的详细解释,注意使用英文关键字搜索。里面的一些 HTML,CSS,HTTP 技术教程也相当不错。
CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
HTML 和 CSS:
mg-6kykMh3w-1712064345626)]
最后
推荐一些系统学习的途径和方法。
每个Web开发人员必备,很权威很齐全的Web开发文档。作为学习辞典使用,可以查询到每个概念、方法、属性的详细解释,注意使用英文关键字搜索。里面的一些 HTML,CSS,HTTP 技术教程也相当不错。
CodeChina开源项目:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
HTML 和 CSS: