雪花算法
使用机器id和时间推移,生成19位long型id。
在分布式环境下,一台机器部署多个项目,或者docker部署项目,会产生id重复
int count = 100000;
CountDownLatch latch = new CountDownLatch(count);
Set<Long> set = new HashSet<>(count * 2);
for (int i = 0; i < count; i++) {
new Thread(() -> {
try {
set.add(getSnowflakeId());
} catch (Exception e) {
e.printStackTrace();
}
latch.countDown();
}).start();
}
latch.await();
//99991
System.out.println(set.size());
可见重复率很低,万分之一的重复率,在并发量低时,几乎不会重复。
防止墨菲定律,重复了,数据就很难处理了。
方案设计
1.用mysql数据库生成id
创建一个id表,专门负责生成id,写块代码实现orcale序列的功能,再加redis分布式锁,可以解决。
在分库环境下,可以指定一个数据库生成id,其他库的服务用feign调用,或者每个库都用一个id表,感觉还是指定一个库生成id好,这样可以全局看到所有id
缺点:开发稍微麻烦点。如果每个库都建立一个id表,每个服务一套逻辑,新建服务的处理,麻烦,易出错。
如果指定一个库生成id,本来只需要启动一个服务开发测试,这样每次都要至少启动两个服务,加eureka,共三个服务。
2.redis生成id
redis内存操作,速度快。lua脚本操作实现原子性,不需要用redis加分布式锁。
每个服务都连接到redis了,分布式环境操作很方便。可以说redis是性能最好的。
缺点:redis只用作缓存,不存储数据。
3.es生成id
每个服务都连接到es,操作方便。
搜索引擎查询快,redis加分布式锁。
在common模块,写个工具类,所有服务都能调用,使用方便。
本文讲的就是es。
4.mongodb生成id
和es差不多
es生成id
用es的high-level的api
依赖
<!--es-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.7.2</version>
<exclusions>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.7.2</version>
</dependency>
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
<version>6.3.1</version>
<exclusions>
<exclusion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</exclusion>
</exclusions>
</dependency>
配置
@Configuration
public class EsConfig {
@Autowired
private EsProperties esProperties;
@Bean
public RestHighLevelClient initClient() {
RestClientBuilder restClientBuilder = RestClient.builder(
new HttpHost(esProperties.getHost(), Integer.valueOf(esProperties.getPort()), esProperties.getSchema()));
restClientBuilder.setRequestConfigCallback(
requestConfigBuilder -> {
requestConfigBuilder.setConnectTimeout(esProperties.getConnectTimeout());
requestConfigBuilder.setSocketTimeout(esProperties.getSocketTimeout());
return requestConfigBuilder;
});
return new RestHighLevelClient(restClientBuilder);
}
}
id索引库,实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EsNoEntity {
public static final String INDEX = "distribute_no";
public static final String TYPE = "es";
@JestId
private String noName;
/**
* 因为long值太大时,es取出会四舍五入,数据失真,所以用string
*/
private String initNo;
private String currentNo;
}
工具类
初始化,创建索引
@PostConstruct
private void init() {
try {
//查询索引
GetIndexRequest request = new GetIndexRequest(EsNoEntity.INDEX);
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
//索引存在
if (exists) {
log.info("索引{}已经存在", EsNoEntity.INDEX);
return;
}
//索引不存在,创建
CreateIndexRequest createIndexRequest = new CreateIndexRequest(EsNoEntity.INDEX);
CreateIndexResponse response = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
if (response != null && response.isAcknowledged()) {
log.info("索引{}创建成功", EsNoEntity.INDEX);
return;
}
log.error("索引{}创建失败", EsNoEntity.INDEX);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
维护自增订单编号
/**
* 订单编号
*/
private static final String ORDER_NO = "order_no";
/**
* 雪花算法当前生成id为1193737508566913024L,因为时间推移,id越来越大,所以以前id一定比这个小
* 最高位+1,1->2,这样生成id和以前的数据都不会重复
*/
private static final long ORDER_NO_INIT = 2193737508566913024L;
/**
* 订单编号
* @author lipo
* @date 2019-11-11 11:10
*/
public long nextOrderNo() {
try {
return nextNo(ORDER_NO, ORDER_NO_INIT + "");
} catch (IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e.getMessage());
}
}
/**
* 生成编号,分布式锁保证并发安全
* @author lipo
* @date 2019-11-11 11:10
*/
private long nextNo(String noName, String initNo) throws IOException {
Lock lock = redisLockRegistry.obtain("NO_NAME:" + noName);
try {
lock.lock();
//查询数据
EsNoEntity esNoEntity = getNo(noName);
long currentNo;
if (esNoEntity == null) {
//不存在数据,插入初始值
currentNo = Long.parseLong(initNo);
esNoEntity = new EsNoEntity(noName, initNo, initNo);
} else {
//已经存在, +1
currentNo = Long.parseLong(esNoEntity.getCurrentNo()) + 1;
esNoEntity.setCurrentNo(currentNo + "");
}
//保存
save(noName, esNoEntity);
return currentNo;
} finally {
lock.unlock();
}
}
/**
* 添加更新数据,自动创建索引
* @author lipo
* @date 2019-11-12 09:55
*/
private void save(String noName, EsNoEntity esNoEntity) throws IOException {
IndexRequest request = new IndexRequest(EsNoEntity.INDEX, EsNoEntity.TYPE, noName);
request.source(JSON.toJSONString(esNoEntity), XContentType.JSON);
restHighLevelClient.index(request, RequestOptions.DEFAULT);
}
/**
* 查询数据
* @author lipo
* @date 2019-11-11 11:37
*/
private EsNoEntity getNo(String noName) throws IOException {
GetRequest getRequest = new GetRequest(EsNoEntity.INDEX, EsNoEntity.TYPE, noName);
GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
String source = getResponse.getSourceAsString();
return JSON.parseObject(source, EsNoEntity.class);
}
}
通用id,100001初始值
/**
* 通用编号
*/
private static final String COMMON_NO = "common_no";
private static final long COMMON_NO_INIT = 100001L;
/**
* 通用编号
* @author lipo
* @date 2019-11-11 11:10
*/
public long nextCommonNo() {
try {
return nextNo(COMMON_NO, COMMON_NO_INIT + "");
} catch (IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e.getMessage());
}
}
扩展方便,像这个通用id一样扩展
测试
SpringBootApplication
@RestController
@Slf4j
public class EsApplication {
public static void main(String[] args) {
SpringApplication.run(EsApplication.class, args);
}
@Autowired
private EsNoUtil esNoUtil;
/**
* http://localhost:8080/orderNo
* @author lipo
* @date 2019-11-12 10:49
*/
@GetMapping("orderNo")
public Long orderNo() {
return esNoUtil.nextOrderNo();
}
}
2193737508566913024
时间消耗
@GetMapping("orderNoMany")
public String orderNoMany() throws InterruptedException {
final int count = 1000;
LocalDateTime begin = LocalDateTime.now();
int i = 0;
for (; i < count; i++) {
esNoUtil.nextOrderNo();
}
//PT1M40.807S,平均一个id时间0.1秒
System.out.println(Duration.between(begin, LocalDateTime.now()));
return "ok";
}
平均一个id时间0.1秒,时间比雪花算法慢很多,分布式安全的代价吧,有失必有得