影子库压测 #
在基于微服务的分布式应用架构下,业务需要多个服务是通过一系列的服务、中间件的调用来完成,所以单个服务的压力测试已无法代表真实场景。 在测试环境中,如果重新搭建一整套与生产环境类似的压测环境,成本过高,并且往往无法模拟线上环境的复杂度以及流量。 因此,业内通常选择全链路压测的方式,即在生产环境进行压测,这样所获得的测试结果能够准确地反应系统真实容量和性能水平。
概述 #
基于内核的 SQL 解析能力以及可插拔平台架构,可实现压测数据与生产数据的隔离,帮助应用自动路由,支持全链路压测。帮助用户实现在生产环境进行压测,来获得较为准确的系统真实容量水平和性能测试结果。
SphereEx-DBPlusEngine 关注于全链路压测场景下数据库层面的解决方案,将压测数据自动路由至用户指定的数据库,是 SphereEx-DBPlusEngine 影子库插件的主要设计目标。
全链路压测是一项复杂而庞大的工作。 需要各个微服务、中间件之间配合与调整,以应对不同流量以及压测标识的透传。 通常会搭建一整套压测平台以适用不同测试计划。 在数据库层面需要做好数据隔离,为了保证生产数据的可靠性与完整性,需要将压测产生的数据路由到压测环境数据库,防止压测数据对生产数据库中真实数据造成污染。 这就要求业务应用在执行 SQL 前,能够根据透传的压测标识,做好数据分类,将相应的 SQL 路由到与之对应的数据源。
核心概念 #
- 生产库
生产环境使用的数据库。
- 影子库
压测中用来存储压测数据的数据库,应与生产数据库使用相同的配置。
适用场景 #
在基于微服务的分布式应用架构下,为了提升系统压力测试的准确性、降低测试成本,通常选择在生产环境进行压力测试,测试中风险也会大大提高。 通过 SphereEx-DBPlusEngine 影子库插件能力,结合影子算法灵活的配置,可以获取系统真实的吞吐能力,同时可避免数据污染,满足复杂业务场景的在线压力测试需求。
使用前提 #
- 服务器中已安装 SphereEx-DBPlusEngine 和数据库集群,且服务运行正常;
- 已完成 SphereEx-Console 安装部署,在压测中实时掌握 SphereEx-DBPlusEngine 负载情况;(可选)
- 对于需要人为触发 Hint 的压测场景,MySQL 客户端需要使用 -c 参数,开启注释功能。
使用限制 #
基于 Hint 的影子算法
- 无。
基于列的影子算法
不支持情况:
不支持 DDL。
不支持范围、分组和子查询,如:
BETWEEN
、GROUP BY … HAVING
等。
支持情况:
- INSERT
SQL 是否支持 1 INSERT INTO table (column,…) VALUES (value,…) 支持 2 INSERT INTO table (column,…) VALUES (value,…),(value,…),… 支持 3 INSERT INTO table (column,…) SELECT column1 from table1 where column1 = value1 不支持 - SELECT/UPDATE/DELETE
条件类型 SQL 是否支持 1 = SELECT/UPDATE/DELETE … WHERE column = value 支持 2 LIKE/NOT LIKE SELECT/UPDATE/DELETE … WHERE column LIKE/NOT LIKE value 支持 3 IN/NOT IN SELECT/UPDATE/DELETE … WHERE column IN/NOT IN (value1,value2,…) 支持 4 BETWEEN SELECT/UPDATE/DELETE … WHERE column BETWEEN value1 AND value2 不支持 5 GROUP BY … HAVING… SELECT/UPDATE/DELETE … WHERE … GROUP BY column HAVING column > value 不支持 6 子查询 SELECT/UPDATE/DELETE … WHERE column = (SELECT column FROM table WHERE column = value) 不支持
注意事项 #
使用影子库压测方案时,需充分考虑业务系统的高可用问题,因此建议部署多个 SphereEx-DBPlusEngine 节点进行压测,避免发生单点故障。关于应用接入方案,可选择使用连接池配置多个 SphereEx-DBPlusEngine 地址,或采用其他成熟的负载均衡方案均可。
原理介绍 #
SphereEx-DBPlusEngine 通过解析 SQL,对传入的 SQL 进行影子判定,根据配置文件中用户设置的影子规则,将请求路由到生产库或者影子库。
影子算法和业务实现紧密相关,目前提供基于列和基于 Hint 共两种类型影子算法。
基于列的影子算法:通过识别 SQL 中的数据,匹配路由至影子库的场景。 适用于由压测数据名单驱动的压测场景,支持精准匹配字段值和正则匹配字段值。
基于 Hint 的影子算法:通过识别 SQL 中的「注释」,匹配路由至影子库的场景。 适用于由上游系统透传标识驱动的压测场景。
以 INSERT 语句为例,在写入数据时,SphereEx-DBPlusEngine 会对 SQL 进行解析,再根据配置文件中的规则,构造一条路由链。 在当前版本的功能中,影子功能处于路由链中的最后一个执行单元,即如果有其他需要路由的规则存在,如分片场景,SphereEx-DBPlusEngine 会首先根据分片规则,路由到某一个数据库,再执行影子路由判定流程,判定执行SQL满足影子规则的配置,数据路由到与之对应的影子库,生产数据则维持不变。
关于 DML 语句,SphereEx-DBPlusEngine 支持基于列和基于 Hint 两种影子库算法。
影子判定会首先判断执行 SQL 相关表与配置的影子表是否有交集。如果有交集,依次判定交集部分影子表关联的影子算法,有任何一个判定成功。SQL 语句路由到影子库。 影子表没有交集或者影子算法判定不成功,SQL 语句路由到生产库。
关于 DDL 语句,SphereEx-DBPlusEngine 仅支持 Hint 影子算法。在压测场景下,DDL 语句一般不需要测试。主要在初始化或者修改影子库中影子表时使用。 影子判定会首先判断执行 SQL 是否包含注解。如果包含注解,影子规则中配置的 HINT 影子算法依次判定。有任何一个判定成功,SQL 语句路由到影子库。 执行 SQL 不包含注解或者 HINT 影子算法判定不成功,SQL 语句路由到生产库。
使用指南 #
基于生产环境,需要部署数个 SphereEx-DBPlusEngine 来承载业务流量,具体配置及数量视压测规模而定。还需要部署一套与生产库配置、架构一致的影子库来承载压测数据。
- 环境确认
在使用 SphereEx-DBPlusEngine 读写分离插件前,需要对当前环境做较全面的确认,如生产库和影子库的配置信息、数据库软件版本信息,以及应用服务器到生产库和影子库的网络延迟等信息。
- 规则确认
根据压测内容,参考影子库使用限制选择合适的基于列或者基于 Hint 算法,也可同时配置两种算法。
- 数据源确认及注册
依次确认所有生产、影子数据源的连通性,可在 SphereEx-DBPlusEngine 所在节点进行访确认。 在 SphereEx-DBPlusEngine 中,通过 REGISTER STORAGE UNIT 命令对数据源进行注册。
- 创建影子库规则
创建影子库规则时,根据第二步所规划的规则进行配置,选择合适的影子库算法,或混合使用两种影子库算法。
- 影子配置验证
使用 PREVIEW SQL 命令验证 SQL 路由情况,该命令不会真实执行 SQL,仅仅确认执行路径。如果影子库规则未生效,请再次确认影子库规则配置详情以及压测 SQL 特征。 请务必确认影子规则准确无误后,再进入全链路压测流程。
- 修改应用程序 url
将应用程序中数据库的访问信息修改为 SphereEx-DBPlusEngine 的访问地址,可通过连接池配置或其他负载均衡方案完成应用程序接入。
- 进入压测流程
在合适的压测时间段,使用压测工具开始对系统进行全链路压测,压测过程需要通过 SphereEx-Console 实时观察 SphereEx-DBPlusEngine 负载情况。
- 再次修改应用程序 url
全链路压测完成后,可将应用程序中数据库访问信息修改回数据库的地址,恢复系统拓扑。
- 影子库处理
最后,根据使用需求,可对影子库中的测试数据保留或清理。如需清理,清理前请务必确保访问的数据库是影子库,避免对生产库误操作带来损失。
操作指南 #
- 准备一个 SphereEx-DBPlusEngine 实例,部署操作将不再演示,准备影子库
- 在 SphereEx-DBPlusEngine 中完成逻辑库的创建
- 添加数据源,完成集群构建
- 创建影子库规则及创建影子表
- 验证影子规则生效情况
配置示例 #
环境说明 #
本示例中,需使用 3 台测试机,分别部署 1 个 SphereEx-DBPlusEngine 实例和 2 个 MySQL 实例。
实例 | IP 地址 | 服务端口 | 主机名 | 备注 | |
---|---|---|---|---|---|
1 | DBPlusEngine 1.2.0 | 192.168.xx.102 | 3307 | dbplusengine | |
2 | MySQL 8.0.28 | 192.168.xx.103 | 3306 | ds_0 | 生产库 |
3 | MySQL 8.0.28 | 192.168.xx.104 | 3306 | ds_1 | 影子库 |
拓扑图 #
配置过程 #
- 准备生产库、影子库数据源
创建 ds_product 数据源,模拟生产库,同时创建 t_user 表并插入若干条记录。
mysql -uroot -p -h192.168.xx.103 -P3306
CREATE DATABASE IF NOT EXISTS ds_product;
Query OK, 1 row affected (0.01 sec)
USE ds_product;
Database changed
CREATE TABLE t_user
(
mobile char(11) PRIMARY KEY,
status varchar(18) NOT NULL,
type varchar(255) NOT NULL
);
Query OK, 0 rows affected (0.11 sec)
SHOW TABLES;
+----------------------+
| Tables_in_ds_product |
+----------------------+
| t_user |
+----------------------+
1 row in set (0.00 sec)
INSERT INTO t_user (mobile, status, type)
VALUES (18099515621, 1, 'ds_product'),
(15639784703, 1, 'ds_product'),
(15716172114, 1, 'ds_product'),
(18766747515, 1, 'ds_product'),
(18099515602, 1, 'ds_product'),
(15639784713, 1, 'ds_product'),
(18099515622, 1, 'ds_product'),
(15639784513, 1, 'ds_product'),
(15716173114, 1, 'ds_product'),
(18766746515, 1, 'ds_product');
Query OK, 10 rows affected (0.06 sec)
Records: 10 Duplicates: 0 Warnings: 0
SELECT * FROM t_user;
+-------------+--------+------------+
| mobile | status | type |
+-------------+--------+------------+
| 15639784513 | 1 | ds_product |
| 15639784703 | 1 | ds_product |
| 15639784713 | 1 | ds_product |
| 15716172114 | 1 | ds_product |
| 15716173114 | 1 | ds_product |
| 18099515602 | 1 | ds_product |
| 18099515621 | 1 | ds_product |
| 18099515622 | 1 | ds_product |
| 18766746515 | 1 | ds_product |
| 18766747515 | 1 | ds_product |
+-------------+--------+------------+
10 rows in set (0.00 sec)
创建 ds_shadow 数据源,为影子库。
mysql -uroot -p -h192.168.xx.104 -P3306
CREATE DATABASE IF NOT EXISTS ds_shadow;
Query OK, 1 row affected (0.05 sec)
CREATE TABLE t_user
(
mobile char(11) PRIMARY KEY,
status varchar(18) NOT NULL,
type varchar(255) NOT NULL
);
Query OK, 0 rows affected (0.11 sec)
- 使用 MySQL 客户端登陆 SphereEx-DBPlusEngine,创建逻辑库
注意:请务必加 -c
参数进行访问,来确保后续带有注释的 SQL 可生效。
--访问 DBPlusEngine 实例
mysql -uroot -p -P3307 -h192.168.xx.102 -c
CREATE DATABASE testdb;
Query OK, 0 rows affected (0.21 sec)
SHOW DATABASES;
5 rows in set (0.00 sec)
- 添加数据源,完成集群构建
在 SphereEx-DBPlusEngine 中注册两个数据库实例。
USE testdb;
Database changed
REGISTER STORAGE UNIT ds_0 (
URL="jdbc:mysql://192.168.xx.103:3306/ds_product?serverTimezone=UTC&useSSL=false",
USER="test",
PASSWORD="Test@123"
), ds_1 (
URL="jdbc:mysql://192.168.xx.104:3306/ds_shadow?serverTimezone=UTC&useSSL=false",
USER="test",
PASSWORD="Test@123"
);
Query OK, 0 rows affected (0.05 sec)
SHOW STORAGE UNITS\G
- 创建影子库规则及创建影子表
注意:此处创建影子表使用了注释操作,建表之前请再次确认该会话是通过 -c 参数开启的。
--创建影子库规则
CREATE SHADOW RULE shadow_rule(
SOURCE=ds_0,
SHADOW=ds_1,
t_user(TYPE(NAME=VALUE_MATCH, PROPERTIES("operation"="insert","column"="status", "value"='2' )))
);
Query OK, 0 rows affected (0.20 sec)
该步骤创建了一个影子库规则,其中包括了基于值和基于 hint 的影子算法匹配:
- 基于值匹配:当 insert 操作中,
status
为 2 的记录会被写入到影子库中
SHOW SHADOW RULES;
+-------------+-------------+-------------+--------------+
| rule_name | source_name | shadow_name | shadow_table |
+-------------+-------------+-------------+--------------+
| shadow_rule | ds_0 | ds_1 | t_user |
+-------------+-------------+-------------+--------------+
1 row in set (0.10 sec)
- 验证影子规则生效情况
先通过
PREVIEW
命令验证影子规则已生效,复合影子库规则的数据应被写入到 ds_1 数据源中。
PREVIEW INSERT INTO t_user (mobile, status, type) VALUES (18236483857, 2, 'ds_shadow');
+------------------+--------------------------------------------------------------------------------+
| data_source_name | actual_sql |
+------------------+--------------------------------------------------------------------------------+
| ds_1 | INSERT INTO t_user (mobile, status, type) VALUES (18236483857, 2, 'ds_shadow') |
+------------------+--------------------------------------------------------------------------------+
1 row in set (0.50 sec)
根据如上结果确认,复合影子规则的两条记录将会写入到影子库中,符合预期,下面将在 SphereEx-DBPlusEngine 中插入测试数据。
--在 Proxy 中查询 t_user 表的信息
SELECT * FROM t_user;
+-------------+--------+------------+
| mobile | status | type |
+-------------+--------+------------+
| 15639784513 | 1 | ds_product |
| 15639784703 | 1 | ds_product |
| 15639784713 | 1 | ds_product |
| 15716172114 | 1 | ds_product |
| 15716173114 | 1 | ds_product |
| 18099515602 | 1 | ds_product |
| 18099515621 | 1 | ds_product |
| 18099515622 | 1 | ds_product |
| 18766746515 | 1 | ds_product |
| 18766747515 | 1 | ds_product |
+-------------+--------+------------+
10 rows in set (0.01 sec)
--基于影子库列值算法的测试数据
INSERT INTO t_user (mobile, status, type)
VALUES (18236483857, 2, 'ds_shadow'),
(15686689114, 2, 'ds_shadow'),
(14523360225, 2, 'ds_shadow'),
(18143924353, 2, 'ds_shadow'),
(15523349333, 2, 'ds_shadow'),
(18143924153, 2, 'ds_shadow'),
(15523349313, 2, 'ds_shadow'),
(18143924253, 2, 'ds_shadow'),
(15523349323, 2, 'ds_shadow'),
(13261527931, 2, 'ds_shadow');
Query OK, 10 rows affected (0.06 sec)
mysql> SELECT * FROM t_user;
+-------------+--------+------------+
| mobile | status | type |
+-------------+--------+------------+
| 15639784513 | 1 | ds_product |
| 15639784703 | 1 | ds_product |
| 15639784713 | 1 | ds_product |
| 15716172114 | 1 | ds_product |
| 15716173114 | 1 | ds_product |
| 18099515602 | 1 | ds_product |
| 18099515621 | 1 | ds_product |
| 18099515622 | 1 | ds_product |
| 18766746515 | 1 | ds_product |
| 18766747515 | 1 | ds_product |
+-------------+--------+------------+
10 rows in set (0.01 sec)
可见,在插入符合影子库的数据后,SphereEx-DBPlusEngine 中仍然只能查到生产库的数据,符合预期。
最后,在影子库中确认数据。
SELECT * FROM t_user;
+-------------+--------+-----------+
| mobile | status | type |
+-------------+--------+-----------+
| 13261527931 | 2 | ds_shadow |
| 14523360225 | 2 | ds_shadow |
| 15523349313 | 2 | ds_shadow |
| 15523349323 | 2 | ds_shadow |
| 15523349333 | 2 | ds_shadow |
| 15686689114 | 2 | ds_shadow |
| 18143924153 | 2 | ds_shadow |
| 18143924253 | 2 | ds_shadow |
| 18143924353 | 2 | ds_shadow |
| 18236483857 | 2 | ds_shadow |
| 18816175114 | 0 | ds_shadow |
+-------------+--------+-----------+
11 rows in set (0.00 sec)
从输出结果确认,能够看到 status
为 2 和 0 的数据,影子库场景的验证结果符合预期。
FAQ #
- 使用影子库压测方案是否会给生产库带来影响?
不会有影响。在正确配置了规则的前提下,影子库会对测试数据进行隔离,不会对生产库带来任何影响。
- 能否创建两个完全一样的影子规则?
不支持。在 SphereEx-DBPlusEngine 可创建多个影子库规则,但是不能作用于同一个字段。