shardingsphere在服务启动时会去加载表的元数据,导致启动缓慢,如果分表数量多,启动一次等待个上百秒都是可能的。可以添加扫描的线程的并发数量加快扫描速度
spring:
shardingsphere:
props:
max:
connections:
size:
per:
# 并发加载元数据,加快启动速度
query: 10
添加后再启动发现快了一点,效果不明显。
通过debug代码可以发现,慢的根源是ColumnMetaDataLoader#load这个方法,需要去查db信息,表多的话需要查询数量多,就耗时越高。分表的表元数据其实基本都是一样的,例如tabale_0到table_127,这128张表的元数据都是相同,其实只要查一次就够了。所以我们可以调整一下ColumnMetaDataLoader的逻辑,想办法让它获取分表元数据时只查询一次。
首先我们会想到使用aop去对原逻辑做切面调整,但是ColumnMetaDataLoader这个是不归spring管理的类,spring提供aop是无法对这个逻辑做处理的,这时可以通过javaAgent去修改字节码。我们用一个小工具集成javaagent完成对字节码的修改。
maven引入依赖
<dependency>
<groupId>io.github.yingxinguo95</groupId>
<artifactId>proxy-sdk</artifactId>
<version>0.0.2</version>
<!--<scope>test</scope>-->
</dependency>
然后添加代码对ColumnMetaDataLoader逻辑做调整
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import io.github.proxy.annotation.ProxyRecodeCfg;
import io.github.proxy.annotation.ReCodeType;
import io.github.proxy.service.ProxyReCode;
import io.github.proxy.utils.ProxyReferenceCtx;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.sql.parser.binder.metadata.column.ColumnMetaData;
import org.apache.shardingsphere.sql.parser.binder.metadata.column.ColumnMetaDataLoader;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.File;
import java.sql.Connection;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class ShardingSphereBoostReCoder implements ProxyReCode {
private static final String CACHE_DIR = "/data/cache/metaData/";
private static final Map<String, Collection<ColumnMetaData>> metaCache = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
try {
File dir = new File(CACHE_DIR);
if (dir.exists() && dir.isDirectory()) {
if (System.currentTimeMillis() - dir.lastModified() > 24 * 60 * 60 * 1000) {
FileUtils.deleteDirectory(new File(CACHE_DIR));
log.info("表元数据缓存文件已过期,删除缓存文件");
}
}
} catch (Exception e) {
log.error("表元数据缓存文件有前期检查异常", e);
}
}
/**
* 加载表元数据,前置重写
*/
@SneakyThrows
@ProxyRecodeCfg(proxyClass = ColumnMetaDataLoader.class, method="load", type = ReCodeType.BEFORE)
public static Collection<ColumnMetaData> loadBefore(Connection connection, String table, String databaseType) {
//移除表名中的数字,例如 table_1 -> table_ 分表共享元数据
String fmtTableName = StringUtils.replaceAll(table, "\\d", "");
if (metaCache.containsKey(fmtTableName)) {
return metaCache.get(fmtTableName);
}
if (hasLocalCache(fmtTableName)) {
//从硬盘读取缓存
return readLocalMata(fmtTableName);
}
//执行原始逻辑
return null;
}
/**
* 加载表元数据,前置重写
*/
@SneakyThrows
@ProxyRecodeCfg(proxyClass = ColumnMetaDataLoader.class, method="load", type = ReCodeType.AFTER)
public static Collection<ColumnMetaData> loadAfter(Connection connection, String table, String databaseType) {
//移除表名中的数字,例如 table_1 -> table_ 分表共享元数据
String fmtTableName = StringUtils.replaceAll(table, "\\d", "");
//获取原始方法读取的元数据
Collection<ColumnMetaData> columnMetaData = ProxyReferenceCtx.getAfterInvokeResult();
//写入硬盘
persistenceMata(fmtTableName, columnMetaData);
//添加缓存
metaCache.put(fmtTableName, columnMetaData);
return columnMetaData;
}
/**
* 判断是否存在本地缓存数据
*/
private static boolean hasLocalCache(String table) {
File file = new File(CACHE_DIR + "/" + table + ".json");
return file.exists();
}
/**
* 持久化写入本地硬盘
*/
@SneakyThrows
private static void persistenceMata(String table, Collection<ColumnMetaData> data) {
String metaData = JSON.toJSONString(data);
FileUtils.forceMkdir(new File(CACHE_DIR));
FileUtils.writeStringToFile(new File(CACHE_DIR + "/" + table + ".json"), metaData, "UTF-8", false);
}
/**
* 反序化读取磁盘文件
*/
@SneakyThrows
private static Collection<ColumnMetaData> readLocalMata(String table) {
String data = FileUtils.readFileToString(new File(CACHE_DIR + "/" + table + ".json"), "UTF-8");
return JSON.parseObject(data, new TypeReference<Collection<ColumnMetaData>>(){});
}
}
确保ShardingSphereBoostReCoder 这个类能被你的spring容器扫描到,然后启动服务可以发现启动速度提升了很多。首次启动还会把元数据保存到本地硬盘,第二次启动耗时更低。依赖引入那可以加上<scope>test</scope>,然后把这个类移动到test目录下,这样就不会影响到线上逻辑,你本地调试的时候启动也快了。