数据库sequence序列高速缓存池——尽量减少排队和数据库的交互

时间:2022-12-02 07:40:35

获取数据库序列

林小应

1.    问题描述

              由于业务需要,应用程序中很多地方会用到数据库序列sequence,如果每次都去数据库获取,会比较耗时。
        在Java代码中,我们通常调用存储过程或执行sql生成一个sequence,
select xxx.nextnvl from dual;
 
            如果直接调用存储过程或执行sql取sequence,算上事务开销,一次需要3~4ms。在大型分布式系统中(如江苏电信的营业系统、淘宝网、大型社交网站),4ms将是一个很大的开销,试想如果需要的序列已经存储在于内存中,平均每次取sequence的时间损耗将低于0.01ms。
        

2.    期望

       避免每次前往数据库取序列,让获取sequence就像从内存中取一个对象一样简单、决不允许重复读取。

3.    设计原理

                 获取sequence的Java代码类似如下:

     String seq = generateSeq(String seqType);

          输入参数:seqType

          输出参数:sequence

       我的思路是:在内存中开辟一个大容器(HashMap),存放各种seqType的sequence队列(Queue)。由一个守护线程来完成队列的装填。
       当第一次取某种类型的sequence,在容器中注册一下,记录下seqType。这样守护线程就能察觉到,守护线程不断检测所有类型的sequence队列。如果容量不足,就会启动装填机制,从数据获取一批新的sequence将空队列装满。       

 4.  实现

               具体实现过程需要保证,多线程环境中资源锁的问题。这里我们用的HashMapConcurrentHashMap,是一种线程安全的高性能Map。Map中的seqType所对应的“sequence队列”,已经换成了队列组、即一个ArrayList中存放多个sequence队列。这样主要是为了降低并发带来的开销。

         根据入参seqTypeA,首先取到的是一个ArrayList<ConcurrentLinkedQueue>,再根据ArrayList的size随机一个整数cur,再取ArrayList中第cur个sequence队列。并发压力和size的大小成反比!

示意图:

数据库sequence序列高速缓存池——尽量减少排队和数据库的交互

 

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