【源码解析】Sharding-Jdbc中的算法

时间:2023-03-09 19:50:18
【源码解析】Sharding-Jdbc中的算法

Sharding-jdbc中的很多地方涉及到算法,比如主从配置这块、分库分表这块。本文主要从源码角度介绍下,目前主要包含哪些算法,以及这些算法的内容。

一、读写分离(主从配置)

这块的代码主要在core模块中的api/algorithm/masterslave下面,这块的算法的主要目的,其实是在读的时候,如何选择从数据库。按照常规的理解,可以有以下几种(参考dubbo):

  • 随机算法
  • 轮询
  • 最小活跃
  • 一致性hash

目前在sharding-jdbc中,实现了前两种,我们看下是如何实现的。

首先是一个接口,定义了获取slave的方法,即:

public interface MasterSlaveLoadBalanceAlgorithm {

    /**
* Get data source.
*
* @param name master-slave logic data source name
* @param masterDataSourceName name of master data sources
* @param slaveDataSourceNames names of slave data sources
* @return name of selected data source
*/
String getDataSource(String name, String masterDataSourceName, List<String> slaveDataSourceNames);
}

最终需要返回的是slave的名称,后续根据这个名称可以获取到连接池。

下面我们看下两种实现:

  • RandomMasterSlaveLoadBalanceAlgorithm
public String getDataSource(final String name, final String masterDataSourceName, final List<String> slaveDataSourceNames) {
return slaveDataSourceNames.get(new Random().nextInt(slaveDataSourceNames.size()));
}

可以看到,随机了一个slave集群大小的随机数,然后从slave list中获取对应的slave name。

  • RoundRobinMasterSlaveLoadBalanceAlgorithm
private static final ConcurrentHashMap<String, AtomicInteger> COUNT_MAP = new ConcurrentHashMap<>();

@Override
public String getDataSource(final String name, final String masterDataSourceName, final List<String> slaveDataSourceNames) {
AtomicInteger count = COUNT_MAP.containsKey(name) ? COUNT_MAP.get(name) : new AtomicInteger(0);
COUNT_MAP.putIfAbsent(name, count);
count.compareAndSet(slaveDataSourceNames.size(), 0);
return slaveDataSourceNames.get(count.getAndIncrement() % slaveDataSourceNames.size());
}

这块用了一个缓存,COUNT_MAP,缓存的内容是name-count的键值对,而count是一个原子类,它的值一直是0,1,...,slaves.size()-1,0,1..这样循环,所以每个slave都会被轮询到。

二、分库分表

分库分表的算法目前支持的内容包括:

  • 精确分库分表PreciseShardingValue
  • 按照范围分库分表RangeShardingValue
  • 按照列表分库分表ListShardingValue

这块主要是定义了一些接口,具体的实现还是要看自己来实现。我们来看下example中的一些已经实现的算法。

public final class PreciseModuloDatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Integer> {

    @Override
public String doSharding(final Collection<String> availableTargetNames, final PreciseShardingValue<Integer> shardingValue) {
for (String each : availableTargetNames) {
if (each.endsWith(shardingValue.getValue() % 2 + "")) {
return each;
}
}
throw new UnsupportedOperationException();
}
}

这块就是实现了一个精准分片的算法,我们主要看下doSharding这个方法,里面包含两个参数availableTargetNames和shardingValue。那么这两个是什么呢?我们debug一下,跟踪一下代码,可以看到,shardingValue其实就是分片项,也就是比如order_id、user_id等等字段值,而availableTargetNames就是所谓的实际数据库表节点。这边遍历的也是实际节点,当分片项(或分片字段)满足一定的条件时,返回实际数据库表节点,用于组装sql。

总的来说,分片算法这块其实根据自己的业务需求自己进行扩展的,总的来说还是要根据实际的机器部署情况来。另外读写分离这块是否需要进行扩展,也是看后续的需要。