获取数据库序列
林小应
1. 问题描述
select xxx.nextnvl from dual;
2. 期望
避免每次前往数据库取序列,让获取sequence就像从内存中取一个对象一样简单、决不允许重复读取。
3. 设计原理
获取sequence的Java代码类似如下:
String seq = generateSeq(String seqType);
输入参数:seqType
输出参数:sequence
我的思路是:在内存中开辟一个大容器(HashMap),存放各种seqType的sequence队列(Queue)。由一个守护线程来完成队列的装填。
当第一次取某种类型的sequence,在容器中注册一下,记录下seqType。这样守护线程就能察觉到,守护线程不断检测所有类型的sequence队列。如果容量不足,就会启动装填机制,从数据获取一批新的sequence将空队列装满。
4. 实现
具体实现过程需要保证,多线程环境中资源锁的问题。这里我们用的HashMap是ConcurrentHashMap,是一种线程安全的高性能Map。Map中的seqType所对应的“sequence队列”,已经换成了队列组、即一个ArrayList中存放多个sequence队列。这样主要是为了降低并发带来的开销。
根据入参seqTypeA,首先取到的是一个ArrayList<ConcurrentLinkedQueue>,再根据ArrayList的size随机一个整数cur,再取ArrayList中第cur个sequence队列。并发压力和size的大小成反比!
示意图:
5. JVM序列池的生命周期
序列池的生命周期为:第一次注册 到 应用程序终止
6. 需要注意的问题
A. 不能读到脏数组,一个sequence不能被两次读到。上面我们的sequence都是从数据库来,保存在同步队列ConcurrentLinkedQueue中,这一点可以保证。
B. sequence的顺序,如果从数据库直接读取顺序是一致的。采用sequence池机制,可能后面读到的比前面读到的小/大。我们可以将上面的ArrayList的size设置成1就可以解决顺序问题(不使用于分布式环境)。
C.命中率问题,如果sequence池空了,主线程就会直接前往数据库。为了提高命中率,我们的系统中采用了备用池——主池空了以后,立即从备用池拉一个现成的队列替换当前队列(这个非常有效)。
D.灵活的配置,如队列池的深度(队列的长度)、宽度(ArrayList的size)、实时的开关机制等
7. 总结
以上只能简单说一下,我们对于这种频繁获取sequence情况的解决方案。我们的系统是40000000用户,分布式系统,多数据库。采用了sequence池机制以后,获取sequence的时间大大缩短了。
还有很多细节,大家动手做的时候再慢慢琢磨。
qq:346420558