全链路在线生产数据库压测利器:Apache ShardingSphere 影子库特性升级

侯阳

SphereEx 中间件研发工程师,Apache ShardingSphere Contributor。

目前专注于 ShadowDB 及全链路压测相关的设计和研发。

什么是全链路压测

随着互联网行业的快速发展,业务也进入了快速扩张阶段,多变的用户需求对整个系统的稳定性冲击可想而知。比如外卖平台大量用户产生的订单集中分布在中午和傍晚两个时间段,再比如电商平台的购物节、秒杀活动等。

每种业务都由一系列不同的业务系统来提供服务,每个业务系统都分布式地部署在不同的机器上。“流量规划” 既能保障系统稳定性、又能节约成本对于技术团队来说是一重大难题,为了精准地获取到单台机器的服务能力,压力测试要在生产环境进行。既能保证环境的真实性,也能保证流量的真实性,大大提高“流量规划”的准确性。

影子库与全链路压测

但在线上业务系统做压测,风险不言而喻,比如数据污染问题或是性能问题。试想一下,如果压测结束后用户发现自己订单丢失了或是凭空多出一批待支付的订单,是否极大影响用户体验?

全链路在线压测是一项复杂而庞大的工作,需要各个微服务、中间件之间配合完成。Apache ShardingSphere 关注于全链路在线压测场景下数据库层面的解决方案,推出压测影子库功能。借助于 ShardingSphere 强大的 SQL 解析能力,对执行 SQL 进行影子判定,同时结合影子算法灵活的配置,满足复杂业务场景的在线压测需求;压测流量路由到影子库,线上正常流量路由到生产库,从而帮助用户对压测数据进行隔离,解决数据污染问题。

影子库功能升级

影子库功能最早实现于 v4.1.0 时期,通过添加一个逻辑的影子列,Apache ShardingSphere 通过解析执行 SQL,对其进行路由并改写,删除逻辑的影子列与列值。用户无需关注具体过程,使用时仅对 SQL 进行相应改造,添加影子字段与相应的配置即可。

添加影子列的方式有两个痛点:

  1. 用户在进行压力测试前需要根据实际的业务需求,对压测相关 SQL 进行相应改造。

  2. SQL 改写操作会增加执行损毁,降低压测结果的准确性。

经过 ShardingSphere 社区的讨论,决定对影子库功能进行重构。

Apache ShardingSphere 4.1.1 GA 版影子库 API,总体上功能较为简单,根据逻辑列对应的值判断是否开启影子库。

rules:
- !SHADOW
  column: # 影子字段名称名称
  shadowMappings:
    ds: shadow_ds # 生产数据源名称列表:影子数据库名称列表

5.0.0 GA 重构以后的影子库 API 更加强大。用户可以通过 enable 属性控制是否开启影子库功能,可配置影子表可以按照表的维度控制需要进行压测的范围,并支持多种不同的影子算法,例如:列值匹配算法、列正则表达式匹配算法以及 SQL 注释匹配算法。

rules:
- !SHADOW
  enable: true # 影子库开关。 可选值:true/false,默认为false
  dataSources:
    shadowDataSource:
      sourceDataSourceName: ds # 生产数据源名称
      shadowDataSourceName: shadow_ds # 影子数据源名称
  tables:
    <shadow-table-name>:
      dataSourceNames: shadowDataSource # 影子表关联影子数据源名称列表(多个值用","隔开)
      shadowAlgorithmNames: 
        - <shadow-algorithm-name> # 影子表关联影子算法名称列表
  shadowAlgorithms:
    <shadow-algorithm-name>:
      type: # 影子算法类型
      props: 
        xxx: xxx # 影子算法属性配置

影子库实战

在线全链路压测架构图:

准备压测环境

假设一个电商网站要对下单业务进行在线压测。(演示使用单机部署方式)

假设压测相关表 t_order 订单表,测试用户的 ID 为 0。测试用户下单产生的数据执行到 ds_shadow 影子库,生产数据执行到 ds 生产数据库。

测试环境准备:

  1. 下载 ShardingSphere-Proxy 5.0.0 GA Download Page:https://shardingsphere.apache.org/document/5.0.0/en/downloads/),安装配置详情参考ShardingSphere-Proxy-Quick-Start:https://shardingsphere.apache.org/document/current/en/quick-start/shardingsphere-proxy-quick-start/)

  2. 配置 ShardingSphere-Proxy 以上文假设压测场景为例:

server.yaml

rules:
  - !AUTHORITY
    users:
      - root@%:root
      - sharding@:sharding
    provider:
      type: NATIVE
  - !TRANSACTION
    defaultType: XA
    providerType: Atomikos

props:
  max-connections-size-per-query: 1
  executor-size: 16  # Infinite by default.
  proxy-frontend-flush-threshold: 128  # The default value is 128.
  proxy-opentracing-enabled: false
  proxy-hint-enabled: false
  sql-show: true
  check-table-metadata-enabled: false
  lock-wait-timeout-milliseconds: 50000 # The maximum time to wait for a lock
  show-process-list-enabled: false
    # Proxy backend query fetch size. A larger value may increase the memory usage of ShardingSphere Proxy.
    # The default value is -1, which means set the minimum value for different JDBC drivers.
  proxy-backend-query-fetch-size: -1
  check-duplicate-table-enabled: false
  sql-comment-parse-enabled: true
  proxy-frontend-executor-size: 0 # Proxy frontend executor size. The default value is 0, which means let Netty decide.
    # Available options of proxy backend executor suitable: OLAP(default), OLTP. The OLTP option may reduce time cost of writing packets to client, but it may increase the latency of SQL execution
    # if client connections are more than proxy-frontend-netty-executor-size, especially executing slow SQL.
  proxy-backend-executor-suitable: OLAP

config-shadow.yaml

schemaName: shadow_poc_database

dataSources:
  ds:
    url: jdbc:mysql://127.0.0.1:3306/ds?serverTimezone=UTC&useSSL=false
    username: root
    password:
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
  ds_shadow:
    url: jdbc:mysql://127.0.0.1:3306/ds_shadow?serverTimezone=UTC&useSSL=false
    username: root
    password:
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1

rules:
- !SHADOW
  enable: true
  dataSources:
    shadowDataSource:
      sourceDataSourceName: ds
      shadowDataSourceName: ds_shadow

  tables:
    t_order:
      dataSourceNames:
        - shadowDataSource
      shadowAlgorithmNames:
        - user-id-insert-match-algorithm
        - simple-note-algorithm

  shadowAlgorithms:
    user-id-insert-match-algorithm:
      type: COLUMN_REGEX_MATCH
      props:
        operation: insert
        column: user_id
        regex: "[0]"
    simple-note-algorithm:
      type: SIMPLE_NOTE
      props:
        foo: bar
  1. 订单服务

这里不讨论订单相关业务,以最简单的收请求直接插入订单表为例。订单表结构如下:

  • 订单表结构
CREATE TABLE `t_order` (
    `id` INT(11) AUTO_INCREMENT COMMENT '订单ID',
    `user_id` VARCHAR(32) NOT NULL COMMENT '下单用户ID',
    `sku` VARCHAR(32) NOT NULL COMMENT '订单商品sku',
    PRIMARY KEY (`id`)
)ENGINE = InnoDB COMMENT = '订单表';

模拟压测过程

  • 使用 postman 模拟测试用户创建订单请求如下图:

  • ShardingSphere-Proxy 中执行日志可以看到 执行 SQL 被路由到影子库执行

验证压测结果

  • 影子库 ds_shadow 执行查询语句
SELECT * FROM t_order;

查询结果:

  • 生产库 ds 执行查询语句
SELECT * FROM t_order;

查询结果:

测试用户创建订单产生的数据会路由到影子库中。更多复杂配置请参考 ShardingSphere 官方文档影子库压测。(https://shardingsphere.apache.org/document/5.0.0/cn/features/shadow/)

全链路在线压测完整解决方案-CyborgFlow

前文中也提到全链路在线压测是一项复杂而庞大的工作,需要各个微服务、中间件之间配合与调整,以应对不同流量以及压测标识的透传,而且要做到测试服务无状态并且开箱即用。

由 Apache ShardingSphere 联合 Apache APISIX、Apache SkyWalking 共同维护的项目 CyborgFlow(https://github.com/SphereEx/CyborgFlow)提供开箱即用 (OOTB) 解决方案来在您的在线系统上执行负载测试。

Apache APISIX 负责在网关层对测试数据进行标记,并由 Apache SkyWalking 负责在整个调用链路上进行传递,最后交由 Apache ShardingSphere-Proxy 做数据隔离,压测数据路由到影子库中。

项目已发布 0.1.0 版本,欢迎下载使用:

京ICP备2021015875号