ShardingProxy源码阅读(一)配置与启动

1,375 阅读5分钟

前言

本章学习一下ShardingProxy的配置与启动流程。基于4.1.0版本。

一、配置

1、ShardingConfiguration

ShardingConfiguration是ShardingProxy的全局配置,配置可以分为两部分,一部分是服务端配置server.yaml,一部分是规则配置(如分片规则)。

@RequiredArgsConstructor
@Getter
public final class ShardingConfiguration {
    // sharding-proxy服务端配置
    private final YamlProxyServerConfiguration serverConfiguration;
    // schema - 规则配置
    private final Map<String, YamlProxyRuleConfiguration> ruleConfigurationMap;
}

2、YamlProxyServerConfiguration

YamlProxyServerConfiguration是ShardingProxy的服务端配置,一般是/conf/server.yaml。

orchestration:
  orchestration_ds:
    orchestrationType: registry_center,config_center,distributed_lock_manager
    instanceType: zookeeper
    serverLists: localhost:2181
    namespace: orchestration
    props:
      overwrite: false
      retryIntervalMilliseconds: 500
      timeToLiveSeconds: 60
      maxRetries: 3
      operationTimeoutMilliseconds: 500
authentication:
  users:
    root:
      password: root
    sharding:
      password: sharding
      authorizedSchemas: sharding_db
props:
  max.connections.size.per.query: 1
  acceptor.size: 16  # The default value is available processors count * 2.
  executor.size: 16  # Infinite by default.
  proxy.frontend.flush.threshold: 128  # The default value is 128.
  proxy.transaction.type: LOCAL
  proxy.opentracing.enabled: false
  proxy.hint.enabled: false
  query.with.cipher.column: true
  sql.show: true
  allow.range.query.with.inline.sharding: false

YamlProxyServerConfiguration配置又分为三部分:编排、授权、普通kv配置。

public final class YamlProxyServerConfiguration implements YamlConfiguration {
    // 授权配置 用户 - 密码&权限
    private YamlAuthenticationConfiguration authentication;
    // 编排名称 - 编排配置
    private Map<String, YamlCenterRepositoryConfiguration> orchestration;
    // 普通kv配置 包括netty、openTracing、sharding-jdbc相关配置
    private Properties props = new Properties();
}

3、YamlProxyRuleConfiguration

YamlProxyRuleConfiguration是sharding-jdbc的规则配置,一般是/conf/config-*.yaml。

schemaName: sharding_db

dataSources:
  ds_0:
    url: jdbc:mysql://127.0.0.1:3306/demo_ds_0?serverTimezone=UTC&useSSL=false
    username: root
    password:
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
  ds_1:
    url: jdbc:mysql://127.0.0.1:3306/demo_ds_1?serverTimezone=UTC&useSSL=false
    username: root
    password:
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50

shardingRule:
  tables:
    t_order:
      actualDataNodes: ds_${0..1}.t_order_${0..1}
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order_${order_id % 2}
      keyGenerator:
        type: SNOWFLAKE
        column: order_id
    t_order_item:
      actualDataNodes: ds_${0..1}.t_order_item_${0..1}
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order_item_${order_id % 2}
      keyGenerator:
        type: SNOWFLAKE
        column: order_item_id
  bindingTables:
    - t_order,t_order_item
  broadcastTables:
    - t_address
  defaultDatabaseStrategy:
    inline:
      shardingColumn: user_id
      algorithmExpression: ds_${user_id % 2}
  defaultTableStrategy:
    none:

与之前sharding-jdbc的主要区别是,sharding-proxy多了schemaName,这个schemaName是客户端配置的jdbc.url,例如:jdbc:mysql://127.0.0.1:3307/sharding_db?serverTimezone=UTC&useSSL=false。

YamlProxyRuleConfiguration包含了schema、数据源、规则配置。

public final class YamlProxyRuleConfiguration implements YamlConfiguration {
    // 客户端jdbc.url指定的schema
    private String schemaName;
    // dataSource
    private Map<String, YamlDataSourceParameter> dataSources = new HashMap<>();
    // 默认dataSource
    private YamlDataSourceParameter dataSource;
    // 分片规则 类比 ShardingRuleConfiguration
    private YamlShardingRuleConfiguration shardingRule;
    // 主从规则 类比 MasterSlaveRuleConfiguration
    private YamlMasterSlaveRuleConfiguration masterSlaveRule;
    // 加密规则 类比 EncryptRuleConfiguration
    private YamlEncryptRuleConfiguration encryptRule;
    // 影子规则 类比 ShadowRuleConfiguration
    private YamlShadowRuleConfiguration shadowRule;
}

二、启动

sharding-proxy-bootstrap模块下只有一个引导程序,即BootStrap作为main方法入口。

public final class Bootstrap {
  public static void main(final String[] args) throws IOException, SQLException {
      // SPI加载XXXDecorator
      registerDecorator();
      // 获取Netty启动端口 默认3307
      int port = getPort(args);
      // 加载Yaml配置
      ShardingConfiguration shardingConfig = new ShardingConfigurationLoader().load(getConfigPath(args));
      // 启动
      if (null == shardingConfig.getServerConfiguration().getOrchestration()) {
          // 无编排治理启动 SideCar
          startWithoutRegistryCenter(shardingConfig.getRuleConfigurationMap(), shardingConfig.getServerConfiguration().getAuthentication(), shardingConfig.getServerConfiguration().getProps(), port);
      } else {
          // 有编排治理启动 docs/document/content/features/orchestration/*.cn.md
          // 1 配置集中管理(配置中心)zk nacos etcd apollo
          // 2 支持proxy实例熔断,支持数据源禁用(注册中心) zk etcd
          startWithRegistryCenter(shardingConfig.getServerConfiguration(), shardingConfig.getRuleConfigurationMap().keySet(), shardingConfig.getRuleConfigurationMap(), port);
      }
  }
  
   private static void startWithoutRegistryCenter(final Map<String, YamlProxyRuleConfiguration> ruleConfigs,
                                                   final YamlAuthenticationConfiguration authentication, final Properties prop, final int port) throws SQLException {
        // Yaml配置转Authentication授权信息
        Authentication authenticationConfiguration = getAuthentication(authentication);
        // 初始化单例ShardingProxy上下文
        ShardingProxyContext.getInstance().init(authenticationConfiguration, prop);
        // 加载LogicSchema 类比sharding-jdbc的ShardingRuntimeContext
        LogicSchemas.getInstance().init(getDataSourceParameterMap(ruleConfigs), getRuleConfiguration(ruleConfigs));
        // 启动Netty服务端
        ShardingProxy.getInstance().start(port);
    }
}

组件注册->加载配置->初始化ShardingProxyContext上下文->初始化LogicSchemas->Netty启动。

1、组件注册

ShardingProxy基本复用了ShardingJDBC的组件,registerDecorator方法负责注册这些组件。

private static void registerDecorator() {
      // 1 解析 SQLParserEngine
      // 2 路由
      NewInstanceServiceLoader.register(RouteDecorator.class);
      // 3 重写
      NewInstanceServiceLoader.register(SQLRewriteContextDecorator.class);
      // 4 执行 ExecutorEngine
      // 5 归并
      NewInstanceServiceLoader.register(ResultProcessEngine.class);
  }

2、配置加载

ShardingConfigurationLoader加载/conf路径下的ShardingConfiguration全局配置。对于规则配置,一个schema只能有一个配置文件。

public final class ShardingConfigurationLoader {
	// path 默认为/conf
    public ShardingConfiguration load(final String path) throws IOException {
        Collection<String> schemaNames = new HashSet<>();
        // 加载/conf/server.yaml
        YamlProxyServerConfiguration serverConfig = loadServerConfiguration(new File(ShardingConfigurationLoader.class.getResource(path + "/" + SERVER_CONFIG_FILE).getFile()));
        File configPath = new File(ShardingConfigurationLoader.class.getResource(path).getFile());
        Collection<YamlProxyRuleConfiguration> ruleConfigurations = new LinkedList<>();
        // 加载/conf/config-*.yaml
        for (File each : findRuleConfigurationFiles(configPath)) {
            loadRuleConfiguration(each, serverConfig).ifPresent(yamlProxyRuleConfiguration -> {
                // 同一个schema只能有一个配置文件,否则报错
                Preconditions.checkState(
                        schemaNames.add(yamlProxyRuleConfiguration.getSchemaName()), "...");
                ruleConfigurations.add(yamlProxyRuleConfiguration);
            });
        }
        Preconditions.checkState(!ruleConfigurations.isEmpty() || null != serverConfig.getOrchestration(), "...");
        Map<String, YamlProxyRuleConfiguration> ruleConfigurationMap = ruleConfigurations.stream().collect(Collectors.toMap(YamlProxyRuleConfiguration::getSchemaName, each -> each));
        return new ShardingConfiguration(serverConfig, ruleConfigurationMap);
    }
}

3、初始化上下文

ShardingProxyContext全局单例,将server.yml中的授权配置和kv配置放入ShardingProxy全局上下文。同时ShardingProxyContext构造时将自身注册到了EventBus,如果配置了服务编排,可以动态更新(配置更新都是通过EventBus实现的)。

@Getter
public final class ShardingProxyContext {
    
    private static final ShardingProxyContext INSTANCE = new ShardingProxyContext();
    // server.yaml kv配置
    private ConfigurationProperties properties = new ConfigurationProperties(new Properties());
    // server.yaml 授权配置
    private Authentication authentication;
    // 断路器是否打开
    private boolean isCircuitBreak;
    
    private ShardingProxyContext() {
        ShardingOrchestrationEventBus.getInstance().register(this);
    }
    
    public static ShardingProxyContext getInstance() {
        return INSTANCE;
    }
    
    public void init(final Authentication authentication, final Properties props) {
        this.authentication = authentication;
        properties = new ConfigurationProperties(props);
    }
    
    @Subscribe
    public synchronized void renew(final PropertiesChangedEvent event) {
        ConfigurationLogger.log(event.getProps());
        properties = new ConfigurationProperties(event.getProps());
    }
    
    @Subscribe
    public synchronized void renew(final AuthenticationChangedEvent event) {
        ConfigurationLogger.log(event.getAuthentication());
        authentication = event.getAuthentication();
    }
    
    @Subscribe
    public synchronized void renew(final CircuitStateChangedEvent event) {
        isCircuitBreak = event.isCircuitBreak();
    }
}

4、初始化LogicSchemas

回顾一下sharding-jdbc中ShardingRuntimeContext的结构,如下:

ShardingRuntimeContext一个ShardingDataSource中是一个单例,基本可以认为是全局单例。持有下面这些实例:

  • CachedDatabaseMetaData:数据库元信息
  • ShardingTransactionManagerEngine:全局事务引擎
  • ShardingSphereMetaData:sharding-jdbc数据源和数据表的元信息
  • BaseRule:对应规则配置,如分片、加密、主从、影子
  • DatabaseType:数据库类型,如MySQL
  • ExecutorEngine:SQL执行引擎
  • SQLParserEngine:SQL解析引擎

LogicSchemas全局单例,管理多个LogicSchemaLogicSchemas可以类比sharding-jdbc中的RuntimeContext

@Getter
public final class LogicSchemas {
    // 全局单例
    private static final LogicSchemas INSTANCE = new LogicSchemas();
    // schema - LogicSchema
    private final Map<String, LogicSchema> logicSchemas = new ConcurrentHashMap<>();
    // 数据库类型
    private DatabaseType databaseType;
    // 注册到EventBus
    private LogicSchemas() {
        ShardingOrchestrationEventBus.getInstance().register(this);
    }
}

LogicSchema抽象类。

@Getter
public abstract class LogicSchema {
    // schema名称
    private final String name;
    // SQL解析引擎
    private final SQLParserEngine sqlParserEngine;
    // 持有 DataSource 事务引擎
    private JDBCBackendDataSource backendDataSource;
    
    public LogicSchema(final String name, final Map<String, YamlDataSourceParameter> dataSources) {
        this.name = name;
        sqlParserEngine = SQLParserEngineFactory.getSQLParserEngine(DatabaseTypes.getTrunkDatabaseTypeName(LogicSchemas.getInstance().getDatabaseType()));
        backendDataSource = new JDBCBackendDataSource(dataSources);
        ShardingOrchestrationEventBus.getInstance().register(this);
    }
}

LogicSchema构造时创建了SQL解析引擎,创建了JDBCBackendDataSourceJDBCBackendDataSource持有所有DataSource和全局事务引擎。

public final class JDBCBackendDataSource implements BackendDataSource, AutoCloseable {
    @Getter
    private Map<String, DataSource> dataSources;
    @Getter
    private ShardingTransactionManagerEngine shardingTransactionManagerEngine = new ShardingTransactionManagerEngine();
}

ShardingSchema分片配置的LogicSchema实现类,持有运行时的ShardingRule和ShardingSphereMetaData。

public final class ShardingSchema extends LogicSchema {
    // BaseRule 分片配置
    private ShardingRule shardingRule;
    // 数据库数据表 元数据信息
    private final ShardingSphereMetaData metaData;
}

可以看到LogicSchemasShardingRuntimeContext非常相似,前者比后者少了执行引擎,前者比后者多持有了数据源(通过持有JDBCBackendDataSource而间接持有了所有数据源)。

5、Netty启动

sharding-proxy-frontend模块负责处理网络通信、数据库协议。

Netty启动入口ShardingProxy::start。

public final class ShardingProxy {
    // 单例
    private static final ShardingProxy INSTANCE = new ShardingProxy();
    
    private EventLoopGroup bossGroup;
    
    private EventLoopGroup workerGroup;
    
    public static ShardingProxy getInstance() {
        return INSTANCE;
    }
    
    public void start(final int port) {
          ServerBootstrap bootstrap = new ServerBootstrap();
          // bossGroup 线程数量 1
          bossGroup = createEventLoopGroup();
          // workerGroup 线程数量 核数*2
          if (bossGroup instanceof EpollEventLoopGroup) {
              groupsEpoll(bootstrap);
          } else {
              groupsNio(bootstrap);
          }
          ChannelFuture future = bootstrap.bind(port).sync();
          future.channel().closeFuture().sync();
          
          // 省略try catch
    }
}

重点看一下Acceptor线程池和Worker线程池的配置。

对于Acceptor线程池,线程数为1。

private EventLoopGroup createEventLoopGroup() {
    return Epoll.isAvailable() ? new EpollEventLoopGroup(1) : new NioEventLoopGroup(1);
}

对于Worker线程池,线程数是2倍核数。

private void groupsNio(final ServerBootstrap bootstrap) {
    // 默认线程数 = 核数*2
    // Math.max(1, SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
    workerGroup = new NioEventLoopGroup();
    bootstrap.group(bossGroup, workerGroup)
            .channel(NioServerSocketChannel.class)
            .option(ChannelOption.SO_BACKLOG, 128)
            .option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(8 * 1024 * 1024, 16 * 1024 * 1024))
            // 使用PooledByteBufAllocator
            .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
            .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
            .childOption(ChannelOption.TCP_NODELAY, true)
            .handler(new LoggingHandler(LogLevel.INFO))
            // ChannelInitializer
            .childHandler(new ServerHandlerInitializer());
}

此外关注一下ServerHandlerInitializer,初始化ChannelPipeline时加入了哪些Handler。

@RequiredArgsConstructor
public final class ServerHandlerInitializer extends ChannelInitializer<SocketChannel> {
    
    @Override
    protected void initChannel(final SocketChannel socketChannel) {
        // 数据库协议引擎
        DatabaseProtocolFrontendEngine databaseProtocolFrontendEngine = DatabaseProtocolFrontendEngineFactory.newInstance(LogicSchemas.getInstance().getDatabaseType());
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 针对不同数据库协议的编解码器
        pipeline.addLast(new PacketCodec(databaseProtocolFrontendEngine.getCodecEngine()));
        // 实际的业务处理器
        pipeline.addLast(new FrontendChannelInboundHandler(databaseProtocolFrontendEngine));
    }
}
  • PacketCodec:针对不同数据库协议的编解码器。
  • FrontendChannelInboundHandler:实际的业务处理器。

总结

  • ShardingConfiguration是ShardingProxy的全局配置。配置可以分为两部分:
    • YamlProxyServerConfiguration:通常是/conf/server.yml,是sharding-proxy的服务端配置。它包括三部分:编排、授权、普通kv配置。
    • YamlProxyRuleConfiguration:通常是/conf/config-*.yaml,是sharding-jdbc的规则配置。它包括schema、数据源、规则配置。其中schema对应客户端jdbc.url中的数据库schema,如jdbc:mysql://127.0.0.1:3307/sharding_db?serverTimezone=UTC&useSSL=false。
  • ShardingProxy的启动入口是sharding-proxy-bootstrap模块下的BootStrap#main。启动流程可以划分为五步:
    • 组件注册
    • 加载配置
    • 初始化上下文
    • 初始化LogicSchemas
    • Netty启动
  • ShardingProxyContext持有sharding-proxy运行时的配置,并且支持动态更新。
  • LogicSchemas可以类比sharding-jdbc中的ShardingRuntimeContext,是运行时上下文。包含数据源、分片配置、事务引擎、SQL解析引擎等。