大家好,我叫大鸡腿,大家可以关注下我,会持续更新技术文章还有人生感悟,感谢~
Leaf——美团点评分布式ID生成系统
优化
在文章中也讲到关于优化,有个双buffer的情况
下面是我关于双buffer的一个设计思路,然后生成id的话使用随机,(ps:我们后台是使用雪花算法生成,这里偷下懒)
代码
package com.example.demo;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created on 2021/3/5.
* 只能注入去使用
*
* @author yangsen
*/
@Component
public class MyIdUtils {
@PostConstruct
public void initClass(){
init();
}
private static Logger logger = LoggerFactory.getLogger(MyIdUtils.class);
private int bufferMaxSize = 30;
private List<Integer> buffer1 = new ArrayList<>();
private List<Integer> buffer2 = new ArrayList<>();
private AtomicLong number = new AtomicLong(0);
private AtomicInteger status = new AtomicInteger(0);
private MyIdUtils() {
}
public static void main(String[] args) {
MyIdUtils utils = new MyIdUtils();
utils.init();
for(int i = 0;i<50;i++){
new Thread(() -> {
logger.info(JSON.toJSONString(utils.getIdList(4)));
logger.info(JSON.toJSONString(utils.getIdList(4)));
logger.info(JSON.toJSONString(utils.getIdList(7)));
logger.info(JSON.toJSONString(utils.getIdList(9)));
}).start();
}
}
public List<Integer> getIdList(Integer size) {
//判断当前buffer1消耗了多少数量
synchronized(this){
if (number.intValue() % 2 == 0) {
//在第一个buffer
return getList(size, buffer1, buffer2);
} else {
return getList(size, buffer2, buffer1);
}
}
}
private List<Integer> getList(Integer size, List<Integer> firstList, List<Integer> lastList) {
while (status.get()!=0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
BigDecimal consumeSize = new BigDecimal(String.valueOf(size));
BigDecimal maxSize = new BigDecimal(String.valueOf(bufferMaxSize));
//这个100实际业务应该是qps的6倍
double level = maxSize.subtract(consumeSize).divide(maxSize, 3, RoundingMode.FLOOR).doubleValue();
logger.info("level:" + level + "," + firstList.size() + "," + lastList.size());
if (level <= 0.9 && lastList.size() == 0) {
//就是消耗超过10%的时候,初始化下一个buffer2
status.incrementAndGet();
new Thread(() -> {
logger.info("第二个buffer init...");
for (int i = 0; i < bufferMaxSize; i++) {
lastList.add(ThreadLocalRandom.current().nextInt(0, 1000));
}
status.set(0);
}).start();
while (status.get()!=0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (firstList.size() < size) {
logger.info("第二个buffer拿...");
//buffer1.size 一个很大的值,是平时qps的6倍。如果这时消耗值大于buffer1的数量,说明buffer1快没了
int lastSize = size - firstList.size();
List<Integer> list = lastList.subList(0, lastSize);
List<Integer> resultList = new ArrayList<>(firstList);
resultList.addAll(list);
logger.info("number:" + number.incrementAndGet());
//清空
lastList.removeAll(list);
firstList.removeAll(resultList);
return resultList;
}
logger.info("第一个buffer拿...");
List<Integer> list = new ArrayList<>(firstList.subList(0, size));
firstList.removeAll(list);
return list;
}
public void init() {
logger.info("第一个buffer init...");
for (int i = 0; i < bufferMaxSize; i++) {
buffer1.add(ThreadLocalRandom.current().nextInt(0, 1000));
}
}
}
思路
- 首先有两个buffer,buffer1,buffer2
- 一上来肯定先初始化buffer1,最大值在那篇文章也讲了一般是Qps的6倍数(保证吞吐性)
- 然后批量进行获取唯一id,首先从buffer1拿,这里不存在说一次性把整个buffer1拿完,像集群一起获取的话,这个buffer1的数量也是设计很大,取值的话就取完,然后buffer1剔除这些值。
- 如果说这时消耗的量已经占据整个buffer1数量的10%,会去初始化buffer2,来增加切换buffer的时候的柔顺。
- 细节,切换的时候就是buffer1不够去拿buufer2的时候,这里用了AtomicLong去控制一下具体路由到哪个buffer
- 然后有时多线程的话考虑到一个并发安全性,在getIdList的方法加了锁
- 以及在初始化buffer2的时候,加了个status,来控制等到buffer2初始化完才能取值。
- 一开始是buffer1,用完之后是buffer2,buffer2用完之后,把他们两调换下位置,循环往复
优化
- 随机数换成雪花算法
- 锁的那里需要再细化
- 等待buffer2初始化完成之后,sleep那里也需要再优化优化