一、问题定义:
由于业务需求,需要实现给一条记录分配一个int值的不重复id,由于是多实例部署的服务,所以如何进行数据同步,避免插入重复id成为关键。
二、解决过程:
1.一开始想到的是,当系统初始化的时候,读取mongo库,找到当前最大的id值,加载到内存,然后多线程之间通过AtomicInteger
进行调用,获取下一个要使用的id值。这样,虽然单实例可以很好的工作,并发也没有问题。但是多实例却无法工作,因为实例与实例之间无法进行协调分配id。
2.接着又想借助与让每个实例都有一个前缀来标识,然后再进行分配id,但是这样,服务重启等问题需要额外一些工作去保证数据一致性。
3.可以把整个业务表加锁,每次插入让其id自增,但是会很影响性能。
三、最终方案:
最终发现了mongo db中有一个原子操作函数findAndModify,它可以指定将获取某个键并同时进行增长一定的值。完全符合我们的场景需求,所以新建一个表,用来记录当前的id值,然后即使多个实例之间想要获取id值,只要调用此函数即可,便可安全获得自增的唯一id值。保证多个服务之间通过原子操作这个表进行了id的同步,避免了id值的重复分配。
使用方式如下,我用的是spring boot,先定义一个mongo表的映射实体:
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Document(collection = "sequence")
public class SequenceId {
private String collName;
private Integer qid;
}
然后调用findAndModify原子操作,获取新的id值,用于分配:
package com.baidu.aibot.backoffice.console.faq.dao;
import com.baidu.aibot.backoffice.console.faq.vo.SequenceId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class QidMongoDao {
private static Logger logger = LoggerFactory.getLogger(QidMongoDao.class);
@Autowired
private MongoTemplate mongoTemplate;
/**
* 获取下一段自增ID
*
* @param collName 集合名称,要同步的ID是用在哪里的
* @param num 增长num大小
* @return 最后的id值
*/
private SequenceId getNextId(String collName, int num) {
Query query = new Query(Criteria.where("collName").is(collName));
Update update = new Update();
update.inc("qid", num);
FindAndModifyOptions options = new FindAndModifyOptions();
// 先查询,如果没有符合条件的,会执行插入,插入的值是查询值 + 更新值
options.upsert(true);
// 返回当前最新值
options.returnNew(true);
SequenceId seq = mongoTemplate.findAndModify(query, update, options, SequenceId.class);
return seq;
}
}
当前mongo db已经实现了文档级别的锁,性能得到了较大提高,如下是不同版本的mongo所使用的锁的级别:
Version < 2.2 : 只支持进程级锁,一个Mongod实例一个锁。
2.8 >Version >= 2.2 : 支持库级锁,一个db一把锁。
大于3.0.0 支持文档级别的锁。
四、参考资料
http://www.leftso.com/blog/268.html
http://blog.csdn.net/qq_16313365/article/details/72781469