目录

  • SpringBoot实战:设备唯一ID生成【雪花算法、分布式应用】
    • 背景:
    • snowflake(雪花算法)方案:
    • 实现:
      • 雪花算法生成ID:
      • 二维码打包:
      • 多线程优化-批量插入:
      • 二维码识别+扫码激活:

SpringBoot实战:设备唯一ID生成【雪花算法、分布式应用】

背景:

分布式高并发的环境下,设备ID需要在全国各地同一时间申请,毫秒级的时间下可能生成数万个设备ID,此时确保生成设备ID的唯一性变得至关重要。此外,在秒杀环境下,不仅要保障ID唯一性、还得确保ID生成的优先度。

snowflake(雪花算法)方案:

雪花算法生成的是一个 Long 类型的 ID,Long 占用 64 个 bit 位,该算法将 bit 位分为以下部分:

  • 符号位:通常为 0,代表正数

  • 时间戳:毫秒数,不是直接从 1970 年开始的时间戳,而是代表可使用时间(当前时间减去开始使用时间),大约可使用 2^41 毫秒约 69 年。

  • 机器 id:高 5 位为数据中心 id,低 5 位为机器 id

  • 序列号:在同一毫秒内从 0 开始的计数器,最大 2^12 为 4096 个,当超过 4096 时,则阻塞等待下一毫秒。既每秒单机可支持并发约为 400 万。

使用雪花算法的原因:

  • 能满足高并发分布式系统环境下ID不重复
  • 基于时间戳,可以保证基本有序递增(有些业务场景对这个又要求)
  • 不依赖第三方的库或者中间件
  • 生成效率极高

实现:

雪花算法生成ID:

1、引入工具类

package com.example.demo.code;import lombok.extern.slf4j.Slf4j;/*** Twitter_Snowflake<br>* SnowFlake的结构如下(每部分用-分开):<br>* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>* 加起来刚好64位,为一个Long型。<br>* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。*/
@Slf4j
public class SnowflakeIdWorker {// ==============================Fields===========================================/** 开始时间截 (2015-01-01) */private final long twepoch = 1420041600000L;/** 机器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);/** 序列在id中占的位数 */private final long sequenceBits = 12L;/** 机器ID向左移12位 */private final long workerIdShift = sequenceBits;/** 数据标识id向左移17位(12+5) */private final long datacenterIdShift = sequenceBits + workerIdBits;/** 时间截向左移22位(5+5+12) */private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;/** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */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;//==============================Constructors=====================================/*** 构造函数* @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;}// ==============================Methods==========================================/*** 获得下一个ID (该方法是线程安全的)* @return SnowflakeId*/public synchronized long nextId() {long timestamp = timeGen();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - twepoch) << timestampLeftShift) //| (datacenterId << datacenterIdShift) //| (workerId << workerIdShift) //| sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳* @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间* @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}//==============================Test=============================================/** 测试 */public static void main(String[] args) {SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);int num = 1000000;long start = System.currentTimeMillis();for (int i = 0; i < num; i++) {long id = idWorker.nextId();
//            System.out.println(id);}long end = System.currentTimeMillis();log.info("数据量:{}, 消耗时间:{}",num, (end - start) / 1000);}
}

测试结果:100W条ID3秒

2、编写实体CodeEntity:

package com.example.demo.code;import lombok.Data;/*** @author xh* @Date 2022/9/24*/
@Data
public class CodeEntity {String codeId;
}

3、CodeController接口测试:

package com.example.demo.code;import com.example.demo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.util.ArrayList;
import java.util.List;/*** @author xh* @Date 2022/9/24*/
@RestController
@CrossOrigin
@Slf4j
public class CodeController {/*** 批量生成设备码*/@GetMapping("/getCode")public Result<List<CodeEntity>> getCode(@RequestParam(required = false) Long num) {if(num == null){num = 1L; // 默认生成一个Num;}List<CodeEntity> codeEntities = new ArrayList<>();SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);long start = System.currentTimeMillis();for (int i = 0; i < num; i++) {long id = idWorker.nextId();CodeEntity codeEntity = new CodeEntity();codeEntity.setCodeId(String.valueOf(id));codeEntities.add(codeEntity);}long end = System.currentTimeMillis();log.info("数据量:{}, 消耗时间:{}",num, (end - start) / 1000);return new Result<List<CodeEntity>>().ok(codeEntities);}
}

4、运行测试:

接下来,我们给Code加入状态:status,1代表已激活,0代表未激活

@Data
public class CodeEntity {String codeId;Integer status;
}

CodeController增加修改状态方法 :

@GetMapping("/updateStatus")public Result<String> updateStatus(@RequestParam String url) {Assert.notNull(url, "url不能为空");Assert.notNull(!url.contains("num=") ? null : true, "num参数缺失");String code = url.substring(url.indexOf("num=") + 4);Assert.notNull(code, "code不能为空");// TODO 根据Code修改状态return new Result<String>().ok(code + "激活成功!");
}

二维码打包:

Code转二维码并打包ZIP:

1、导入依赖:

<!-- 二维码生成开始 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.2.1</version></dependency><dependency><groupId>com.google.zxing</groupId><artifactId>javase</artifactId><version>3.3.0</version></dependency>
<!-- 二维码生成结束 -->

2、工具类:

package com.example.demo.code;import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;import javax.imageio.ImageIO;import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;/*** 二维码工具类**/
public class QrCodeUtils {/*** 生成二维码图片* @param outputStream 文件输出流路径* @param content 二维码携带信息* @param qrCodeSize 二维码图片大小* @param imageFormat 二维码的格式* @throws WriterException* @throws IOException*/public static boolean createQrCode(OutputStream outputStream, String content, int qrCodeSize, String imageFormat) throws WriterException, IOException{//设置二维码纠错级别MAPHashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<>();hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);  // 矫错级别QRCodeWriter qrCodeWriter = new QRCodeWriter();//创建比特矩阵(位矩阵)的QR码编码的字符串BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap);// 使BufferedImage勾画QRCode  (matrixWidth 是行二维码像素点)int matrixWidth = byteMatrix.getWidth();BufferedImage image = new BufferedImage(matrixWidth-200, matrixWidth-200, BufferedImage.TYPE_INT_RGB);image.createGraphics();Graphics2D graphics = (Graphics2D) image.getGraphics();graphics.setColor(Color.WHITE);graphics.fillRect(0, 0, matrixWidth, matrixWidth);// 使用比特矩阵画并保存图像graphics.setColor(Color.BLACK);for (int i = 0; i < matrixWidth; i++){for (int j = 0; j < matrixWidth; j++){if (byteMatrix.get(i, j)){graphics.fillRect(i-100, j-100, 1, 1);}}}return ImageIO.write(image, imageFormat, outputStream);}/*** 解析二维码图片*/public static void readQrCode(InputStream inputStream) throws IOException{//从输入流中获取字符串信息BufferedImage image = ImageIO.read(inputStream);//将图像转换为二进制位图源LuminanceSource source = new BufferedImageLuminanceSource(image);BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));QRCodeReader reader = new QRCodeReader();Result result = null ;try {result = reader.decode(bitmap);} catch (ReaderException e) {e.printStackTrace();}System.out.println("解析结果:"+result.toString());System.out.println("二维码格式类型:"+result.getBarcodeFormat());System.out.println("二维码文本内容:"+result.getText());}/*** main方法* @throws WriterException*/public static void main(String[] args) throws IOException, WriterException {createQrCode(new FileOutputStream(new File("F:\\test.png")),"www.baidu.com",800,"PNG");readQrCode(new FileInputStream(new File("F:\\test.png")));}
}

生成结果:

接下来我们实现二维码批量打包:

1、修改QrCodeUtils中的createQrCode方法:

/*** 生成二维码图片* @param content 二维码携带信息* @param qrCodeSize 二维码图片大小* @param imageFormat 二维码的格式* @throws WriterException* @throws IOException*/public static InputStream createQrCode(String content, int qrCodeSize, String imageFormat) throws WriterException, IOException{//设置二维码纠错级别MAPHashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<>();hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);  // 矫错级别QRCodeWriter qrCodeWriter = new QRCodeWriter();//创建比特矩阵(位矩阵)的QR码编码的字符串BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap);// 使BufferedImage勾画QRCode  (matrixWidth 是行二维码像素点)int matrixWidth = byteMatrix.getWidth();BufferedImage image = new BufferedImage(matrixWidth-200, matrixWidth-200, BufferedImage.TYPE_INT_RGB);image.createGraphics();Graphics2D graphics = (Graphics2D) image.getGraphics();graphics.setColor(Color.WHITE);graphics.fillRect(0, 0, matrixWidth, matrixWidth);// 使用比特矩阵画并保存图像graphics.setColor(Color.BLACK);for (int i = 0; i < matrixWidth; i++){for (int j = 0; j < matrixWidth; j++){if (byteMatrix.get(i, j)){graphics.fillRect(i-100, j-100, 1, 1);}}}ByteArrayOutputStream os = new ByteArrayOutputStream();try {ImageIO.write(image, imageFormat, os);InputStream input = new ByteArrayInputStream(os.toByteArray());return input;} catch (IOException e) {}return null;}

2、编写CodeController中的getCodeZip方法:

/*** 获取二维码压缩包** @param response* @param list     二维码字符串列表*/@PostMapping("/getCodeZip")public void getCodeZip(HttpServletResponse response, @RequestBody List<CodeEntity> list) {// 生成二维码图片response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment; filename=QrCode-" + System.currentTimeMillis() + ".zip");// 压缩包名ZipOutputStream zos = null;try {zos = new ZipOutputStream(response.getOutputStream());// zos.setLevel(5);//压缩等级for (int j = 0; j < list.size(); j++) {String codeString = list.get(j).getCodeId();// 获取二维码字符串// OutputStream outputStream, String content, int qrCodeSize, String imageFormatInputStream inputStream = QrCodeUtils.createQrCode(codeString, 900, "JPEG");// 生成二维码图片zos.putNextEntry(new ZipEntry(codeString + ".JPEG")); // 压缩文件名称 设置ZipEntry对象// zos.setComment("采样二维码"); // 设置注释int temp = 0;while ((temp = inputStream.read()) != -1) { // 读取内容zos.write(temp); // 压缩输出}inputStream.close(); // 关闭输入流}} catch (Exception e) {e.printStackTrace();} finally {try {if (null != zos) {zos.flush();zos.close();}} catch (IOException e) {e.printStackTrace();}}
}

3、使用Apifox测试:

测试数据:

[{"codeId": "123", "status": 0},{"codeId": "12223", "status": 0},{"codeId": "1232232", "status": 1},{"codeId": "1223233", "status": 1}
]

测试结果:

多线程优化-批量插入:

设备批量插入及性能调优:

测试数据量:10W,单量插入:

@Testvoid insertOneCode() {Long num = 100000L;List<CodeEntity> codeEntities = new ArrayList<>();SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);long start = System.currentTimeMillis();for (int i = 0; i < num; i++) {long id = idWorker.nextId();CodeEntity codeEntity = new CodeEntity();codeEntity.setCodeId(String.valueOf(id));codeEntities.add(codeEntity);codeService.save(codeEntity);}long end = System.currentTimeMillis();log.info("数据量:{}, 消耗时间:{}", num, (end - start) / 1000);}

测试结果:数据量:100000, 消耗时间:727

这样的性能我们肯定不能接受,所以我们接下来将数据等分为10份,每1W条批量插入一次,我们再看看性能:

@Testvoid insertCode() {Long num = 100000L; // 总数据量int singleNum = 10000; // 单次批量插入数据int CirNum = 10; // 循环次数SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);long start = System.currentTimeMillis();// 数据等分为10份for(int n = 0; n < CirNum; ++ n) {List<CodeEntity> codeEntities = new ArrayList<>();for (int i = 0; i < singleNum; i++) {long id = idWorker.nextId();CodeEntity codeEntity = new CodeEntity();codeEntity.setCodeId(String.valueOf(id));codeEntities.add(codeEntity);}codeService.saveBatch(codeEntities);}long end = System.currentTimeMillis();log.info("数据量:{}, 消耗时间:{}", num, (end - start) / 1000);}

测试结果:数据量:100000, 消耗时间:12

性能提升了不少

二维码识别+扫码激活:

我们重新修改更新状态的方法:

@GetMapping("/updateStatus")public Result<String> updateStatus(@RequestParam String code) {Assert.notNull(code, "code不能为空");boolean status = codeService.updateStatus(code);if(status) {return new Result<String>().ok(code + "激活成功!");}return new Result<String>().ok(code + "激活失败!");}

每一个设备ID都唯一,所以我们将设备ID设置为主键,加快查询速度

@Overridepublic boolean updateStatus(String code) {CodeEntity codeEntity = new CodeEntity();codeEntity.setCodeId(code);codeEntity.setStatus(1);baseMapper.updateById(codeEntity);return true;}

接下来我们测试一下设备状态修改:



对Code打包重新编写:

/*** 批量生成设备码*/@GetMapping("/getCode")public void getCode(HttpServletResponse response, @RequestParam(required = false) Long num) {if(num == null){num = 1L; // 默认生成一个Num;}List<CodeEntity> codeEntitiesAll = new ArrayList<>();// 总数据量int cirNum = 10; // 循环次数int singleNum = (int) (num / cirNum); // 单次批量插入数据SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);// 数据等分为10份for(int n = 0; n < cirNum; ++ n) {List<CodeEntity> codeEntities = new ArrayList<>();for (int i = 0; i <= singleNum; i++) {long id = idWorker.nextId();CodeEntity codeEntity = new CodeEntity();codeEntity.setCodeId(String.valueOf(id));codeEntities.add(codeEntity);codeEntitiesAll.add(codeEntity);}codeService.saveBatch(codeEntities);}getCodeZip(response, codeEntitiesAll);}/*** 获取二维码压缩包** @param response* @param list     二维码字符串列表*/private void getCodeZip(HttpServletResponse response, List<CodeEntity> list) {// 生成二维码图片response.setContentType("application/octet-stream");response.setHeader("Content-Disposition", "attachment; filename=QrCode-" + System.currentTimeMillis() + ".zip");// 压缩包名ZipOutputStream zos = null;try {zos = new ZipOutputStream(response.getOutputStream());// zos.setLevel(5);//压缩等级for (CodeEntity codeEntity : list) {String codeString = codeEntity.getCodeId();// 获取二维码字符串// OutputStream outputStream, String content, int qrCodeSize, String imageFormatInputStream inputStream = QrCodeUtils.createQrCode(codeString, 900, "JPEG");// 生成二维码图片zos.putNextEntry(new ZipEntry(codeString + ".JPEG")); // 压缩文件名称 设置ZipEntry对象// zos.setComment("采样二维码"); // 设置注释int temp = 0;while ((temp = inputStream.read()) != -1) { // 读取内容zos.write(temp); // 压缩输出}inputStream.close(); // 关闭输入流}} catch (Exception e) {e.printStackTrace();} finally {try {if (null != zos) {zos.flush();zos.close();}} catch (IOException e) {e.printStackTrace();}}}

编写二维码扫码:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>二维码:生成、扫描、识别</title><link rel="stylesheet" href="./css/base.css" /><script src="https://hm.baidu.com/hm.js?29d14c9fb6158fcbec79d1a0d1425404"></script><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head><body><menu class="menu" id="menu"><nav class="active">二维码识别</nav></menu><main class="main"><aside class="reader"><button class="sweep" onclick="sweep()">扫一扫</button><div class="imgurl"><img id="imgurl" src="https://img-blog.csdnimg.cn/4d4f1a8226584943b1849d6eb7aee233.png"alt="当前识别的二维码" /></div><textarea class="result" id="result" cols="32" rows="6" placeholder="二维码识别结果!"></textarea><canvas class="canvas" id="canvas"></canvas></aside></main><!-- 二维码识别 --><script src="./js/jimp.js"></script><script src="./js/jsqr.min.js"></script><script src="./js/base.js"></script><script>const result = document.querySelector('#result');const QrCode = new QrCodeRecognition({sweepId: '#canvas',uploadId: '#file',error: function (err) {// console.log(err)// 识别错误反馈result.value = err;},seuccess: function (res) {// 识别成功反馈console.log(res)console.log(res.data);result.value = res.data;$.get(`http://localhost:8080/updateStatus?code=${res.data}`,function (data) {console.log(data)},"json");}});// 扫一扫function sweep() {result.value = '';QrCode.sweep();};</script><!-- Demo页面交互 --><script>const menu = [...document.querySelectorAll('nav')];const aside = [...document.querySelectorAll('aside')];menu.forEach((nav, n) => {menu[n].classList.add('active');aside[n].style.display = 'block';});</script>
</body></html>


SpringBoot实战:设备唯一ID生成【雪花算法、分布式应用】相关推荐

  1. 雪花算法:分布式唯一ID生成利器

    前言 无论是在分布式系统中的ID生成,还是在业务系统中请求流水号这一类唯一编号的生成,都是软件开发人员经常会面临的一场景.而雪花算法便是这些场景的一个解决方案. 以分布式ID为例,它的生成往往会在唯一 ...

  2. 雪花算法:分布式唯一 ID 生成利器

    无论是在分布式系统中的 ID 生成,还是在业务系统中请求流水号这一类唯一编号的生成,都是软件开发人员经常会面临的一场景.而雪花算法便是这些场景的一个解决方案. 以分布式 ID 为例,它的生成往往会在唯 ...

  3. asp按时间自动递增编号_Java秒杀系统实战系列-分布式唯一ID生成订单编号

    本文是"Java秒杀系统实战系列文章"的第七篇,在本文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们将介绍两种方法 ...

  4. Java秒杀系统实战系列~分布式唯一ID生成订单编号

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第七篇,在本博文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们 ...

  5. java 唯一编号_Java秒杀系统实战系列~分布式唯一ID生成订单编号

    摘要: 本篇博文是"Java秒杀系统实战系列文章"的第七篇,在本博文中我们将重点介绍 "在高并发,如秒杀的业务场景下如何生成全局唯一.趋势递增的订单编号",我们 ...

  6. node 生成随机头像_唯一ID生成算法剖析

    引在业务开发中,大量场景需要唯一ID来进行标识:用户需要唯一身份标识:商品需要唯一标识:消息需要唯一标识:事件需要唯一标识-等等,都需要全局唯一ID,尤其是分布式场景下.唯一ID有哪些特性或者说要求呢 ...

  7. java 唯一id生成算法_唯一ID生成算法剖析

    在业务开发中,大量场景需要唯一ID来进行标识:用户需要唯一身份标识:商品需要唯一标识:消息需要唯一标识:事件需要唯一标识-等等,都需要全局唯一ID,尤其是分布式场景下. 唯一ID有哪些特性或者说要求呢 ...

  8. c#随机数生成编号_忘掉 Snowflake,感受一下性能高出587倍的全局唯一ID生成算法...

    今天我们来拆解 Snowflake 算法,同时领略百度.美团.腾讯等大厂在全局唯一 ID 服务方面做的设计,接着根据具体需求设计一款全新的全局唯一 ID 生成算法.这还不够,我们会讨论到全局唯一 ID ...

  9. 微信用户全局唯一标识_忘掉 Snowflake,感受一下性能高出587倍的全局唯一ID生成算法...

    今天我们来拆解 Snowflake 算法,同时领略百度.美团.腾讯等大厂在全局唯一 ID 服务方面做的设计,接着根据具体需求设计一款全新的全局唯一 ID 生成算法.这还不够,我们会讨论到全局唯一 ID ...

最新文章

  1. hashmap实现原理_Java中HashMap底层实现原理(JDK1.8)源码分析
  2. MySQL数据库介绍、安装(服务端软件安装、客户端软件安装(图形化界面客户端和命令行客户端))
  3. 如何安全设置无线路由
  4. VS2017的C++开发心得:头文件的路径问题与属性管理器
  5. 电脑软件:5个实用的Windows软件,大幅度提高你的工作效率!
  6. 动态规划经典题之年终奖
  7. cocos2d-x 3.0 画图节点——Node
  8. SIM: 基于搜索的超长行为序列上的用户兴趣建模
  9. Lynn/ Online digital filters for biological signals: some fast designs for a small computer
  10. PHP设计模式——六大原则
  11. C语言实现学生成绩管理系统设计
  12. Axure RP 9
  13. 极限数学计算机在线使用,健身最大重复次数(RM)在线计算器
  14. 面试官问:如何搭建测试环境?掌握这5种技能和知识就够了
  15. python制作qq机器人_使用python打造一个自己的QQ机器人 【基础篇】
  16. 跑步戴哪款无线耳机好?适合跑步用的运动耳机分享
  17. 2015年网易考拉海淘android面试
  18. lv_canvas 画布
  19. 计算机组成原理----有关数据通路
  20. 数仓建模—实时数仓架构发展史

热门文章

  1. nestJS连接mysql数据库过程
  2. Linux下 Qt界面程序嵌入另一个Qt界面程序_Qt应用嵌入外部进程窗口
  3. delphi 中 delete的用法
  4. 《炬丰科技-半导体工艺》 光刻胶的化学性质
  5. 导数的四则运算法则_利用导数求函数单调性典型例题
  6. 关于动态音乐设计的思考-Part 1-设计分类学
  7. Qt 渐变(五):线性渐变和径向渐变生成星空图
  8. Google新最近推出音乐
  9. BI报表之电网大屏显示制作
  10. 巴菲特说的七段话,简短精辟