高效透明的主键策略
2016-07-29 00:00
190 查看
前段时间有个朋友问我如何能在保证单个节点上的主键高效并且唯一,并且能支持的住较大的访问量。同时和所采用的数据库无关,也就是说我可以使用任何数据库都能采用这个策略
1、这里采用既有的主键策略可能使用uuid是最直接的解决方案,但是缺点就是uuid产生的字符串检索较慢而且无规律,并不是每个需求都希望主键看起来杂乱无章
2、常见的sequence以及nativie,hilo等策略在很多场景下需要特定的数据库支持
这里总结了一个简单的版本
我不打算针对每个表都做独立的主键自增,那个留给有兴趣的人自己扩展吧
一、核心思路
1、创建一个表,两个字段name,value,name记录属性名,value记录主键当前值
2、采用synchronized方法来保证单个应用节点对获取id方法的独立性
3、通过步长来提升获取主键ID的性能。例如:当前值是1000,我将步长设置成100,那么下次访问此表获取主键就是1100了
4、利用单例模式,将当前值1000到1100之间的步长在当前节点进行线性消费,这样我们就不需要每次需要主键id自增的时候,每次都访问数据库来做查询更新操作
二、实施策略
1、存储当前id的对象类
2、访问数据库获取下个主键,这里设置了步长为100
3、获取下个主键的类(单例模式类,这里如果在spring配置文件中声明,就是单例了。保证在当前节点状态一致),这里在当前节点主键没有被消耗完毕是不需要访问数据库的
三、扩展思路:解决集群以及高并发的问题
1、如果我们需要给每个表做主键自增,这里只需要在查库的改成查询表名即可
2、如果我们需要多个集群节点同时读写,防止问题,我们可以给每个节点的id加一个前缀(一般采用ip的最后一个位号,或者ip值取余)
1、这里采用既有的主键策略可能使用uuid是最直接的解决方案,但是缺点就是uuid产生的字符串检索较慢而且无规律,并不是每个需求都希望主键看起来杂乱无章
2、常见的sequence以及nativie,hilo等策略在很多场景下需要特定的数据库支持
这里总结了一个简单的版本
我不打算针对每个表都做独立的主键自增,那个留给有兴趣的人自己扩展吧
一、核心思路
1、创建一个表,两个字段name,value,name记录属性名,value记录主键当前值
2、采用synchronized方法来保证单个应用节点对获取id方法的独立性
3、通过步长来提升获取主键ID的性能。例如:当前值是1000,我将步长设置成100,那么下次访问此表获取主键就是1100了
4、利用单例模式,将当前值1000到1100之间的步长在当前节点进行线性消费,这样我们就不需要每次需要主键id自增的时候,每次都访问数据库来做查询更新操作
二、实施策略
1、存储当前id的对象类
public class IdBlock { //下个主键id long nextId; //最新更新的数据库主键 long lastId; public IdBlock(long nextId, long lastId) { this.nextId = nextId; this.lastId = lastId; } public long getNextId() { return nextId; } public long getLastId() { return lastId; } }
2、访问数据库获取下个主键,这里设置了步长为100
public class GetNextIdBlockCmd implements Command<IdBlock> { private static final long serialVersionUID = 1L; //取得主键的步长,这里我们可以假定设置为100 protected int idBlockSize; public GetNextIdBlockCmd(int idBlockSize) { this.idBlockSize = idBlockSize; } public IdBlock execute(CommandContext commandContext) { //查询库里的主键id的最大值 PropertyEntity property = (PropertyEntity) commandContext .getPropertyManager() .findPropertyById("next.dbid"); //获取了库里逐渐id的最大值 long oldValue = Long.parseLong(property.getValue()); //将最大值增加了一个步长,这里假设我们步长为100,增加了100 long newValue = oldValue+idBlockSize; //更新库里的最大主键为步长增加后的值 property.setValue(Long.toString(newValue)); //将原来的主键值和最新的主键值传到主键存放对象类中 return new IdBlock(oldValue, newValue-1); } }
3、获取下个主键的类(单例模式类,这里如果在spring配置文件中声明,就是单例了。保证在当前节点状态一致),这里在当前节点主键没有被消耗完毕是不需要访问数据库的
public class DbIdGenerator implements IdGenerator { //主键步长 protected int idBlockSize; //下个主键id protected long nextId = 0; //数据库中存放的最新最大主键id protected long lastId = -1; //命令模式,就是用来做数据CRUD的,不必关注实现细节 protected CommandExecutor commandExecutor; //获取下个主键 public synchronized String getNextId() { //当我们从数据库取得最新更新主键id 小于我们 的下个id的时候。也就是本地主键获取消耗完毕的时候 if (lastId<nextId) { //直接读库,并作下个主键以及数据库最新主键id的更新 getNewBlock(); } //如果我们下个主键id值还是小于数据库更新的id值,那么就自增,因为这一个id主键段是我们按照步长获取的,所以不会重复 long _nextId = nextId++; return Long.toString(_nextId); } //带着步长去查询更新数据库,只有本地主键消耗完毕的时候才会调用这个方法 protected synchronized void getNewBlock() { //直接访问数据库来获取下个主键id,以及数据库最新的更新id。下个主键id理论上初始时候应该是 nextId+100=lastId IdBlock idBlock = commandExecutor.execute(new GetNextIdBlockCmd(idBlockSize)); this.nextId = idBlock.getNextId(); this.lastId = idBlock.getLastId(); } public int getIdBlockSize() { return idBlockSize; } public void setIdBlockSize(int idBlockSize) { this.idBlockSize = idBlockSize; } public CommandExecutor getCommandExecutor() { return commandExecutor; } public void setCommandExecutor(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; } }
三、扩展思路:解决集群以及高并发的问题
1、如果我们需要给每个表做主键自增,这里只需要在查库的改成查询表名即可
2、如果我们需要多个集群节点同时读写,防止问题,我们可以给每个节点的id加一个前缀(一般采用ip的最后一个位号,或者ip值取余)
相关文章推荐
- paxos 实现
- suse linux下jfreechart显示方框问题处理
- linux开机启动项的设置
- zookeeper的FAQ
- activiti5.10解决分布式集群部署的主键问题
- 开启 Apache Server Status
- 程序员眼中的编程语言和操作系统
- xfire 客户端编写错误
- 用GDB调试程序(四)
- 面向海量服务的设计原则和策略总结
- Jenkins入门系列之——02第二章 Jenkins安装与配置
- 以程序的方式操纵NTFS的文件权限(上)
- 工作随笔——CentOS6.4支持rz sz操作
- jquery中遍历读取json串中的对象
- Jenkins进阶系列之——15Maven获取Jenkins的Subversion的版本号
- 免费收录网站搜索引擎登录口大全
- GPLv3:大教堂和集市的新一轮对抗
- Linux 源码安装mysql及配置
- Jenkins进阶系列之——11修改Jenkins用户的密码
- “抄袭事件”开庭小志