sharding-jdbc元素据扫描导致服务启动缓慢

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目录下,这样就不会影响到线上逻辑,你本地调试的时候启动也快了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值