设计思路

一、核心问题

1、数据源信息的存储

2、数据源信息的动态同步、加载与缓存

3、数据源信息的切换

4、数据源对象的注入

 

二、问题分析

1、数据源信息的存储

(1)在分库分表的基础上,需要单独维护一套DBConnection的配置信息,然后组装成DataSource(以DruidDataSource为例)对象缓存起来。

(2)缓存的设计。通常情况下,动态数据源切换都会使用一个标识来选择对应的数据源对象。而且考虑到线程安全的问题,推荐使用currentHashMap。

 

2、数据源信息的同步与加载

(1)同步问题:在分布式的前提下,需要考虑到数据的一致性的问题。如果做的比较low的话,可以使用缓存,如果使用缓存的情况下,还需要注意缓存数据一致性的问题。LocalCache+ZooKeeper为例。

  • 使用ZK,监听数据源变化,保持分布式系统数据的一致性。

(2)数据源的加载:在Spring容器的帮助下,这个操作变得比较随意。通过几个注解即可实现。主要描述流程思路。

  • 通常情况下,要在访问DB前将数据源对象创建出来。

(3)缓存的问题

  1. 使用本地缓存的刷新问题:基于订阅-发布模型的监听(广播)机制。ZK、MQ、Redis都可以。如果想做的牛逼一点,可以考虑到冷热数据的问题。充分利用内存空间。可以借鉴LRU内存淘汰算法。
  2. Redis缓存:
  • 使用这个方案的话,就不需考虑分布式系统数据一致性的问题。但是需要考虑缓存穿透和缓存数据一致性的问题。通常情况下,DB路由数据变更频率不大,凭借着Redis的QPS,应该是可以支撑起频繁的数据访问请求。如果,DB路由变更,势必要更新缓存(删Key)
  • 缓存穿透的问题:并发量高的情况下,key被删除,势必会引起缓存穿透的问题。

(1)分布式锁策略:使用分布式锁限制访问DB的线程数,这样的话肯定要阻塞线程,导致超时。通常这种方案在业务上是不可取的。

(2)数据一致性策略:保证DB能承受住访问的压力(其实还好,谁家DB还扛不住几百的绝对并发?);引入数据版本机制,即在更新缓存时,判断当前数据的版本号与缓存数据的版本号是否一致。

3、数据源的切换

  • 首先需要考虑的就是线程安全的问题。这里可以使用简单粗暴的手段,ThreadLocal
public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder= new ThreadLocal<String>();

    public static void setDataSourceType(String groupId){
        contextHolder.set(groupId);
    }

    public static String getDataSourceType(){
        log.info("Service get datasource currently is : [" + contextHolder.get() + "]");
        return contextHolder.get();
    }

    public static void clearDataSourceType(){
        contextHolder.remove();
    }

}

4、数据源对象的注入

思路:从动态数据源工具中获取到DataSource标示,根据标示获取DataSrouce对象。在Spring容器中,这一点其实更好实现。原理就是在DB访问之前,切换数据源。