Java使用位图+redis生成抖音号、淘淘号、B站UID号等分布式ID
文章目录
前言
上一篇中我们介绍了《为什么亿级数据量时要使用位图》,今天这一篇就来实战使用位图。
做分布式项目首先要考虑分布式ID的生成,分布式ID暂时没有太好的高性能全局递增或趋势递增,即使是美团的lefa、百度的uid-generator、滴滴的tinyid都或多或少借用了第三方中间件不但对性能有所影响也增加了系统的复杂度。
我在之前的文章《 高并发下分布式ID各个的解决方案以及redis集群分布式ID代码实现》介绍了使用Redis生成,但是这些ID都有一个共同点,要么只是递增 例如 1、2、3…1000,规律太过明显,要么像雪花算法一样生成的过长,既增加了系统的复杂度也不利于展示给用户查看。
那么有没有一直方案能够高性能的生成分布式ID,且没有过于明显的规律,且生成的长度完全由自己掌握并且用户能够感觉到自己目前的账号所处的位置呢?
我们这里使用了位图BitMap,完全基于内存实现效率很高,平均1秒生成20万个ID,如果用到了redis那么生成的效率与redis支持的最高写操作齐平,1s/8w。
一、位图BitMap依赖
因为我们使用的这个位图在Maven开源仓库中没有,所以这里我们只能手动上传到本地,也可以上传到我们自己服务器的远程私服中。
jar包下载:CSDN下载资源
进入到jar包目录
cd d:
cd sa-jdi
mvn install:install-file -DgroupId=sun.jvm.hotspot -DartifactId=sa-jdi -Dversion=1.8.0 -Dpackaging=jar -Dfile=sa-jdi-1.8.0.jar
maven引入
<dependency>
<groupId>sun.jvm.hotspot</groupId>
<artifactId>sa-jdi</artifactId>
<version>1.8.0</version>
</dependency>
二、完全基于内存单实例代码
import org.apache.commons.lang3.RandomUtils;
import sun.jvm.hotspot.utilities.BitMap;
import java.util.ArrayList;
import java.util.List;
/**
* @Author liuy
* @Description 会员号生成器
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
//开始时间戳
Long startTime = System.currentTimeMillis();
List<Long> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
Long id = generateId();
System.out.println(id);
list.add(id);
}
System.out.println("生成id一共 : " + list.size());
//结束的时间戳
Long endTime = System.currentTimeMillis();
//所耗时间
System.out.println("花费时间" + (endTime - startTime) + "ms");
}
// base是以1000为单位,bitmap偏移
private static Integer base = 1;
//代表每个偏移量只生成4000个号码 例如 偏移量为1 那么 之后生成1001 - 4000 偏移量为4 生成范围:4001-7000
private static Integer batchSize = 4000;
// 一个号段4000
static BitMap bitMap = new BitMap(batchSize);
static {
loadFromDB();
}
/**
* 刷新数据库或redis 动态生成偏移量
*/
private static void loadFromDB() {
// query DB 生成一批 / 1000 为 base
//base 偏移量 每次增加三 能保证如果这次服务中断 那么下次生成的id不会是之前生成过的id
base += 2;
bitMap.clear();
}
public static Long generateId() {
int offset = RandomUtils.nextInt(0, batchSize);
// 已被使用,类似
while (offset < batchSize && bitMap.at(offset)) {
// 下一个未使用的bit
offset++;
}
if (offset == batchSize) {
loadFromDB();
return generateId();
}
bitMap.atPut(offset, true);
//这里号段内的随机数 * 3 是为了打乱 可以自定义
return (long) (base * 10000 + (offset * 3) );
}
}
2.1 运行结果:
2.2 优点
性能极高。
2.3 缺点
1.完全基于内存也就是平常说的用空间换时间,如果服务器配置不高有可能会引起服务崩溃。
2.这里只使用了本地缓存,如果是分布式部署的话,那么偏移量将会重置,有可能造成ID重复的情况。
2.4 思考:
把偏移量保存到redis 每次服务重启都要增加一次偏移量 保证不重复。
三、利用reids+位图的方式实现分布式ID的生成
3.1 流程图
3.2 代码
import com.soboot.common.core.utils.SoBootLogger;
import org.apache.commons.lang3.RandomUtils;
import org.junit.Test;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import sun.jvm.hotspot.utilities.BitMap;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* @Author liuy
* @Description 会员号生成器
* @Version 1.0
*/
@Component
public class IdTools extends ApplicationObjectSupport {
private static RedisTemplate<String, Object> redisTemplate;
@Test
public void test() {
//开始时间戳
Long startTime = System.currentTimeMillis();
List<Long> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
Long id = generateId();
System.out.println(id);
list.add(id);
}
System.out.println("生成id一共 : " + list.size());
//结束的时间戳
Long endTime = System.currentTimeMillis();
//所耗时间
System.out.println("花费时间" + (endTime - startTime) + "ms");
}
// base是以1000为单位,bitmap偏移
private static Integer base = 4;
//代表每个偏移量只生成4000个号码 例如 偏移量为1 那么 之后生成1001 - 4000 偏移量为4 生成范围:4001-7000
private static Integer batchSize = 1000;
// 一个号段4000
static BitMap bitMap = new BitMap(batchSize);
@PostConstruct
public void init() {
try {
redisTemplate = (RedisTemplate<String, Object>) getApplicationContext().getBean("redisTemplate");
if (null == redisTemplate) {
SoBootLogger.warn("缺少Redis相关配置,Id号段式生成器 无法使用");
}
} catch (Exception e) {
SoBootLogger.error("获取Id号段式生成器配置错误,Id生成器无法使用", e);
}
loadFromDB();
}
/**
* 刷新数据库或redis 动态生成偏移量 把偏移量保存到redis 每次服务重启都要增加一次偏移量 保证不重复
*/
private static void loadFromDB() {
ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
Object version = opsForValue.get("SOBOOT_ID_GENERATE_OFFSET_BASE");
int offset = 0;
if (!Objects.isNull(version)) {
offset = (Integer) version;
}
// query DB 生成一批 / 1000 为 base
//base 偏移量 每次增加三 能保证如果这次服务中断 那么下次生成的id不会是之前生成过的id
//3 代表步数 也可以自定义设置步数
base = offset + 2;
opsForValue.set("SOBOOT_ID_GENERATE_OFFSET_BASE", base);
bitMap.clear();
}
public static Long generateId() {
int offset = RandomUtils.nextInt(0, batchSize);
// 已被使用,类似
while (offset < batchSize && bitMap.at(offset)) {
// 下一个未使用的bit
offset++;
}
if (offset == batchSize) {
loadFromDB();
return generateId();
}
bitMap.atPut(offset, true);
//这里号段内的随机数 可以自定义
return (long) (base * 10000 + (offset * 3) );
}
}
3.3 优点
分布式部署时不会造成ID重复的情况,每次重启服务或部署新服务时会根据redis中的偏移量进行号段的增长,可以保证生成的ID是呈趋势递增的情况。
3.4 缺点
引入了redis,增加了系统的复杂度。
3.5 思考
若多次生成的offset和batchSize相同那么会浪费掉很多号段,会员号的长度也会大幅度增加。
后续优化方案需要避免浪费和避免会员号的长度大幅度增加。
相关文章
- java-spring基于redis单机版(redisTemplate)实现的分布式锁+redis消息队列,可用于秒杀,定时器,高并发,抢购
- Java 第十一届 蓝桥杯 省模拟赛 最大的元素距离
- Java实现 LeetCode 661 图片平滑器(暴力)
- Java实现 LeetCode 564 寻找最近的回文数(今天要GG在这道题了 头晕+题难(((φ(◎ロ◎;)φ))))...
- java实现求二十一位水仙花数(21位水仙花数)
- Java实现 蓝桥杯VIP 算法提高 十进制转八进制数
- Redis学习(5)-Jedis(Java操作redis数据库技术)
- Redis学习(3)-redis启动
- 如何用java获得字符串的ASCII值
- 运维基础之Docker(2)通过docker部署zookeeper nginx tomcat java redis kibana/elasticsearch/logstash mysql kafka mesos/marathon ftp git
- redis 简单整理——java 客户端jedis[十六]
- [Bug]redis问题解决(MISCONF Redis is configured to save RDB snapshots)
- 华为OD机试 - 创建二叉树(Java & JS & Python)
- How to improve Java's I/O performance( 提升 java i/o 性能)
- Java_IO流(精讲)包含练习题及答案
- 〖Python 数据库开发实战 - Python与Redis交互篇⑧〗- 利用 redis-py 实现缓存观众投票数据信息案例
- 【华为OD机试 2023】 查找重复代码(C++ Java JavaScript Python)
- java编程:jedis连接redis数据库实例
- Java-线程池专题(什么是线程池,如何使用,为什么要用)
- Java Logger(java日志)
- mysql-connector-java与Mysql、Java的对应版本
- 我的Android进阶之旅------>Android编译错误java.util.zip.ZipException: duplicate entry的解决方法
- 使用redis-shake工具迁移云Redis数据(二十一)
- java 反射