在最近的项目中,因为序列的问题,造成部分数据丢失,因此仔细思考了下载分布式环境下如何使用序列问题!
下面分几类情况说明下。
一、 数据量较小的应用
面对数据量较少的应用单个数据库既可以解决问题,那么针对序列来说,依靠数据库本身的序列既可以实现。(mysql此类没有序列的数据库,一样有对应的方法来实现类似序列的功能)
二、 大量数据的应用
针对数据量比较大的情况,多数企业对数据库的选择,最主要的需求就是方便扩充,目前对此类的实现是分库分表数据库! 另外为提高效率,多数还会同时使用读写分离,即吧不常用的参数等单独放在一个数据库中,另把实例数据放在另外一个数据库中!
在这种情况下使用序列会存在哪些问题呢?
1: 分库分表,或者读写分离面临跨库事物问题!
2: 重复问题!
解决方案:
通过缓存服务来实现
这种情况不建议走数据库序列,可以使用缓存,如redis,redis提供方法可以直接获取值,并且原值加1 incr 方法 redis为单线程执行,固改方法不会造成脏读,脏写
public int getNextValue(String sequenceId) throws Exception{
// redisTemplate.incr()
int nextValue = (int)((Math.random()*9+1));
//生成四位随机数
Long value = redisTemplate.incr(sequenceId+"_SEQUENCE");
JSONObject sequenceJson = JSONObject.parseObject(redisTemplate.get("PARAM_CONFIG_SEQUENCE_KEYZZ").toString());
JSONObject sequenceKey = JSONObject.parseObject(sequenceJson.getString("SEQUENCE_KEY"));
String valueRule = sequenceKey.getString(sequenceId);
Long valueMix = Long.parseLong(valueRule.split("-")[1]);
if(value>=valueMix){
value =Long.parseLong( valueRule.split("-")[0]);
redisTemplate.set(sequenceId+"_SEQUENCE",value.toString());
}
return value.intValue();
}
线程池加载序列
基本思想:
1: 使用线程池批量执行异步任务
2: 使用 FutureTask异步执行获取单个序列()
3: 使用ConcurrentLinkedQueue 缓存序列
private void _getSeq16() throws Exception {
long START_TIME = System.currentTimeMillis();
int CACHE_SIZE = Integer.valueOf(this.disConfig.getProperty("config.sequence.cacheSize", "100")).intValue();
int THREADS = StringUtils.isEmpty(this.disConfig.getProperty("config.sequence.threads")) ? Integer.valueOf(this.disConfig.getProperty("config.sequence.threads")).intValue() : Runtime.getRuntime().availableProcessors();
ExecutorService executorService = Executors.newFixedThreadPool(THREADS * 2);
List<FutureTask<String>> futureTaskList = new ArrayList();
this.sequenceCacheQueue.add(this.getSingleSequence());
List<String> tempSequenceList = new ArrayList();
this.logger.debug("get first sequence cost:" + (System.currentTimeMillis() - START_TIME) + "ms");
FutureTask futureTask;
for(int i = 0; i < CACHE_SIZE - 1; ++i) {
futureTask = new FutureTask(new Callable<String>() {
public String call() throws Exception {
return SequenceUtil.this.getSingleSequence();
}
});
futureTaskList.add(futureTask);
executorService.submit(futureTask);
}
Iterator i$ = futureTaskList.iterator();
while(i$.hasNext()) {
futureTask = (FutureTask)i$.next();
String sequenceStr = (String)futureTask.get();
if (!StringUtils.isEmpty(sequenceStr)) {
tempSequenceList.add(sequenceStr);
}
}
executorService.shutdown();
Collections.sort(tempSequenceList, new Comparator<String>() {
public int compare(String o1, String o2) {
return Long.parseLong(o1) > Long.parseLong(o2) ? 1 : -1;
}
});
i$ = tempSequenceList.iterator();
while(i$.hasNext()) {
String sequence = (String)i$.next();
this.sequenceCacheQueue.add(sequence);
}
this.logger.debug("get sequence size " + this.sequenceCacheQueue.size() + ",total execute time:" + (System.currentTimeMillis() - START_TIME) + "ms");
}
序列中的误区:
固定格式+随机数 方式
这种虽然实现起来简单,但是重复几率很高,哪怕固定格式是时间串
单机序列方式
单机应用的情况下没有问题,但是在分布式应用中,两天主机之前,存在重复