目录
1. 简介
2. 新功能测试验证
2.1 新功能——ACL权限控制
2.1.1 ACL简介
2.1.2 ACL 参数解析
2.1.3 ACL 赋权配置及示例
(1)ACL权限持久化方式
(2)ACL权限持久化配置示例
2.1.4 ACL部分源码简析
(1)ACL初始化
(2)ACL权限加载
(3)ACL用户操作
2.2 新功能——TLS加密管理
2.2.1 TLS简介
2.2.2 TLS 配置与示例
(1)TLS 编译示例
2.2.3 Redis TLS使用示例
2.2.4 TLS部分源码简析
(1)TLS初始化
(2)TLS读写
2.2.5 TLS局限性
2.3新功能——多线程IO
2.3.1 多线程IO简介
2.3.2 多线程IO实现及配置示例
(1)多线程IO流程
(2)多线程IO参数解析
2.3.3多线程IO使用及性能对比
(1)Redis版本间对比测试
(2)Redis CPU间性能对比测试
2.3.4 多线程IO部分源码简析
(1)多线程IO初始化
(2)多线程IO任务处理
3. 小结
1. 简介
Redis(Remote Dictionary Server),即远程字典服务,是一个使用 C编写的开源(BSD许可)、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,是现在最受欢迎的NoSQL数据库之一。依赖于Redis的高性能、多数据类型等特性,Redis应用场景十分广泛,可用于游戏缓存应用、互联网缓存应用、电商高并发应用及其他缓存加速访问应用,能有效承载巨大的读写压力,轻松实现高并发访问。
Redis最新版本为Redis6.X,其中有Redis6.0.X及Redis6.2.X两个分支。Redis6.0中已于2020年上半年发布,6.0版本最新已发布至6.0.15。Redis6.0带来了诸多新功能:
- 新增ACL权限控制,可实现账号的权限管控;
- 新增TLS加密管理,可实现Redis加密访问控制;
- 多线程IO,可实现单个Redis实例更高性能。
除此之外,Redis6.0还有RESP3协议、客户端缓存加速及redis-benchmark、redis-cli优化等新功能。
本文主要对Redis6.0的ACL权限管控、TLS加密管理及多线程IO进行了测试验证和解析。
2. 新功能测试验证
2.1 新功能——ACL权限控制
2.1.1 ACL简介
Redis ACL 是Access Control List(访问控制列表)的缩写,该功能允许对访问 Redis 的连接做一些可执行命令和可访问 KEY 的限制。它的工作方式是,在连接之后,要求客户端进行身份验证,以提供用户名和有效密码,如果身份验证成功,该客户端连接与给定用户绑定,并具有该用户的访问权限。
Redis6.0版本之前,用户只有一个default用户,同一Redis实例所有读写操作都共享此用户,难免出现误操作删除数据或泄露数据情况。虽然可以使用rename命令禁用部分敏感操作,但也同时意味着需要进行敏感操作时,将有额外操作需要进行,权限管控复杂且不完善。在Redis6.0之后,通过ACL(权限管理)功能,可以设置不同的用户并对其授权命令或数据权限。这一功能,可以有效降低用户误操作导致数据丢失或数据泄露风险。其中,密码由SHA256进行加密。
Tips:linux下sha256加密方式:echo -n passowrd | sha256sum
2.1.2 ACL 参数解析
本章节简要说明ACL常用命令,以及笔者使用时遇到的问题和需关注的地方。
常用的命令如下:
参数 | 说明 |
---|---|
CAT | 查看类别 |
DELUSER | 删除用户 |
GENPASS | 创建密码 |
GETUSER | 获得用户 |
HELP | 帮助 |
LIST | 查看用户详情 |
LOAD | 加载aclfile |
SAVE | 保存至aclfile |
SETUSER | 设置用户 |
USERS | 查看用户 |
WHOAMI | 查看当前用户 |
LOG | 显示日志 |
1)ACL CAT示例:
ACL将所有命令分为21子类,ACL可以较细粒度地进行权限划分,但部分命令同时处于不同的权限子类中,设置多个子类权限时,需关注这些命令权限问题。
2)ACL LIST 示例:
通过ACL生成的用户以如下default、test账号为例,进行简要说明:
参数 | 说明 |
---|---|
user test | 代表用户是test |
on | 代表用户是启用的,如果是off,代表用户是禁用的,在off状态下,登录会失败; |
#e...cc7 | 是sha256加密后的密码串 |
~* | ~为添加指定模式的键(~*代表allkeys) |
+ | 代表用户可以使用该命令,如果是 - ,代表用户无法使用该命令 |
@ | 用户可以使用某类别命令,类别可以通过acl cat获得 |
3)ACL SETUSER示例:
ACL SETUSER时,可通过“>”符号分次设置多个密码且密码均为有效;可通过“<”符号使密码无效化,不建议同一账号设置过多密码。当密码无法找回时,可先删除账号再建立相同权限用户方式更新密码,以防有效密码过多导致密码泄露进而导致数据泄露。
对default用户的权限设置需谨慎。笔者进行测试时,设置了一个与default完全相同的admin用户并将default用户权限缩小,因操作原因出现了权限不可用情况,尽可能保留default权限不删减。
普通用户赋权后,可通过auth username password方式登录普通账号。此方式登录需使用6.0及以上版本redis-cli客户端,低版本客户端无法识别,多版本混用需注意。
笔者测试时,一个实例中生成7000+普通账号,redis运行正常权限管控正常。示例如下:
acl setuser test1 >Admin123 ~* +get #账号test1,密码Admin123
acl setuser test2 >Admin123 ~* +set
acl setuser test2 >Admin123 ~* * +get +set
...
acl setuser test7000 >Admin123 ~* +@all
acl list #7000个用户显示正常
auth test7000 Admin123 #登陆正常
2.1.3 ACL 赋权配置及示例
(1)ACL权限持久化方式
通过上述的ACL SETUSER方式,对用户的账号进行了赋权后可进行持久化。Redis 实现ACL权限持久化的方式主要有两种:
① redis.conf方式:直接将账号密码持久化保存在redis.conf中;
② aclfile方式:将账号密码保存至users.acl文件中,并把users.acl 路径写入redis.conf。
两种方式中,更推荐aclfile方式。因为redis.conf方式加载配置需要重启Redis,而aclfile方式执行acl load即可。
此外,我们可以在客户端对账户密码进行操作,如果使用redis.conf方式,可通过config rewrite持久化;如果使用aclfile方式,可使用acl save进行持久化。
对比项 | redis.conf方式 | aclfile方式 |
---|---|---|
加载配置 | 重启Redis | 执行acl load |
持久化配置 | 执行config rewrite | 执行acl save |
(2)ACL权限持久化配置示例
两种持久化方式的示例如下:
① redis.conf方式
cat redis.conf | grep "~*"可得:
② aclfile方式
- cat users.acl,查看账号密码权限:
- cat redis.conf | grep users.acl,查看aclfile文件的位置:
Tips:redis6.2.X与redis6.0.X在acl权限控制上,明显区别在于增加了 Pub/Sub channel (&*)管控。
2.1.4 ACL部分源码简析
上面提到了ACL权限控制和持久化方式,下面对ACL初始化、权限加载、用户操作源码进行简要的解析。
(1)ACL初始化
在server.c的main函数中,有ACLInit()函数,ACLInit()中,主要为初始化ACL的结构。同时,ACLInitDefaultUse初始化默认用户default。default用户初始化时,具有全部权限及默认不需要密码。
void ACLInit(void) {
Users = raxNew();
UsersToLoad = listCreate();
ACLLog = listCreate();
ACLInitDefaultUser();
server.requirepass = NULL; /* Only used for backward compatibility. */
}
(2)ACL权限加载
在ACLInit()完成后,main函数中通过ACLLoadUsersAtStartup()进行ACL权限加载,分别通过ACLLoadConfiguredUsers()及ACLLoadFromFile()函数判断用户信息及加载方式。ACLLoadConfiguredUsers()使用redis.conf进行权限加载,ACLLoadFromFile()使用acl文件进行权限加载。
void ACLLoadUsersAtStartup(void) {
if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
serverLog(LL_WARNING,
"...");
exit(1);
}
if (ACLLoadConfiguredUsers() == C_ERR) {
serverLog(LL_WARNING,
"Critical error while loading ACLs. Exiting.");
exit(1);
}
if (server.acl_filename[0] != '\0') {
sds errors = ACLLoadFromFile(server.acl_filename);
if (errors) {
serverLog(LL_WARNING,
"Aborting Redis startup because of ACL errors: %s", errors);
sdsfree(errors);
exit(1);
}
}
}
(3)ACL用户操作
在进行ACL操作命令时,实际调用了acl.c的aclCommad()命令。aclCommand中,对各类ACL操作分别进行了定义。如setuser,通过判断命令中是否含setuser关键字,再判断usename是否含有空格,user是否已存在,最后调用ACLSetUser(),ACLCreateUser()实现setuser操作。
void aclCommand(client *c) {
char *sub = c->argv[1]->ptr;
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
sds username = c->argv[2]->ptr;
/* Check username validity. */
if (ACLStringHasSpaces(username,sdslen(username))) {
addReplyErrorFormat(c,
"Usernames can't contain spaces or null characters");
return;
}
user *tempu = ACLCreateUnlinkedUser();
user *u = ACLGetUserByName(username,sdslen(username));
if (u) ACLCopyUser(tempu, u);
for (int j = 3; j < c->argc; j++) {
if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
char *errmsg = ACLSetUserStringError();
addReplyErrorFormat(c,
"Error in ACL SETUSER modifier '%s': %s",
(char*)c->argv[j]->ptr, errmsg);
ACLFreeUser(tempu);
return;
}
}
/* Overwrite the user with the temporary user we modified above. */
if (!u) u = ACLCreateUser(username,sdslen(username));
serverAssert(u != NULL);
ACLCopyUser(u, tempu);
ACLFreeUser(tempu);
addReply(c,shared.ok);
}
...
}
在普通用户使用权限时,通过ACLCheckCommandPerm()及ACLGetUserCommandBit()函数判断用户是否具有操作权限,如无权限则报ACL_DENIED_CMD权限不足。
2.2 新功能——TLS加密管理
2.2.1 TLS简介
TLS(Transport Layer Security,安全传输层)是建立在传输层TCP协议之上的协议,服务于应用层,在实现上分为记录层和握手层两层,其中握手层又含四个子协议:握手协议、更改加密规范协议、应用数据协议和警告协议。实现了将应用层的报文进行加密后再交由TCP进行传输的功能,解决了保密、完整性、认证等网络安全问题。Redis从6.0版本开始支持TLS安全加密。
2.2.2 TLS 配置与示例
Redis 内部使用 OpenSSL 开发库来实现TLS功能。因此,需要在 Redis 编译之前预先安装 OpenSSL 套件库。此外,TLS是可选功能,需要在编译时加上参数后才会加入TLS功能。
下面将对TLS编译使用进行示例,并简要说明过程中遇到的问题和需关注的地方。
(1)TLS 编译示例
编译时,需添加 BUILD_TLS=yes以加入TLS功能。因为依赖主机OpenSSL版本,所以不同OpenSSL版本主机编译出的redis-server不通用。此外,需要注意gcc版本。下面以CentOS7.6、Ubuntu16.04为例进行说明及错误示例。
① 以CentOS7.6为例
执行 “make BUILD_TLS=yes”时出现has no member named报错:
原因为gcc版本过低,升级gcc版本即可,升级方式:
yum -y install centos-release-scl scl-utils-build
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
升级完成后,执行gcc -v,出现gcc version 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC)即为升级成功。再次编译执行“make distclean;make BUILD_TLS=yes”,即可编译成功。
② 以Ubuntu16.04为例
执行“make BUILD_TLS=yes”,若出现“jemalloc/jemalloc.h: No such file or directory”或“Newer version of jemalloc required”报错:
方案一:README.md文件的官方解决方案
在README.md文件中,Redis官方给出了解决方案“To force compiling against libc malloc, use: % make MALLOC=libc”,再次执行“make distclean”。笔者将“make BUILD_TLS=yes”命令,改为“make BUILD_TLS=yes MALLOC=libc”,即可编译成功。
方案二:关注deps/jemalloc权限是否错误
通过wget获得对应Redis版本后,替换掉deps文件夹中内容,再次执行“make distclean;make BUILD_TLS=yes”。若通过这种方式可以编译成功,Redis采用tcmalloc时碎片率会更低最低。
使用redis:6.0.X替换容器版Redis6.0的镜像时,使用CentOS7.6编译镜像无法启动,使用Ubuntu16.04可成功启动。经分析,CentOS7.6与Ubuntu16.04的OpenSSL版本分别为OpenSSL 1.0.2k-fips、OpenSSL 1.1.1,因OpenSSL版本不同,所以编译出的redis-server不通用。验证时,使用Ubuntu16.04上编译的redis-server启动于CentOS7.6时,出现报错,可证明不通用。
(2)TLS 证书生成
在完成Redis TLS编译后,根据Redis官方文档TLS.md中说明,可通过如下命令生成根CA证书和服务器证书:
./utils/gen-test-certs.sh
其中,生成DH(Diffie-Hellman)params文件耗时较长。
相关证书在配置时限制如下:
证书 | 是否必选 |
---|---|
ca.crt | 必选 |
redis.crt | 必选 |
redis.key | 必选 |
redis.dh | 可选 |
分析gen-test-certs.sh可知,所有证书均通过openssl生成。在实际使用中,除了使用shell脚本生成证书,还可以通过java、golang等代码方式调用ssl、tls、x509库生成。其中,证书有效期可以通过days动态生成,到期后可以对证书进行续期。
(3)TLS 参数解析
Redis启用TLS主要涉及如下参数:
--tls-cert-file /path/to/redis.crt
--tls-key-file /path/to/redis.key
--tls-ca-cert-file /path/to/ca.crt
--tls-dh-params-file /path/to/redis.dh
#以上4个参数为Redis证书及私钥,包括可信任的根证书和DH PARAMS文件路径,本次测试中为对tls-dh-params-file进行测试。
--port 0
--tls-port 6379
#以上2个参数为TLS连接端口和非TLS连接端口,端口0代表完全禁用非TLS端口。
--tls-auth-clients yes
#以上为是否启用双向TLS并要求客户端使用有效证书进行身份验证,默认开启。
--tls-replication yes
#Redis主服务器以相同的方式处理连接客户端和从服务器,在从服务器端,必须指定tls-replication yes才能将TLS用于到主服务器的对外连接,sentinel也需要同步设置。
--tls-cluster yes
#Redis集群需使用tls-cluster yes以便为集群和跨节点连接启用TLS,本次测试中未涉及此参数。
2.2.3 Redis TLS使用示例
为方便查看,启动参数直接置于启动命令行,以单机版redis-server启动为例:
./src/redis-server --tls-port 6379 --port 0 --tls-cert-file ./tests/tls/redis.crt --tls-key-file ./tests/tls/redis.key --tls-ca-cert-file ./tests/tls/ca.crt
redis server成功启动,使用redis-cli进行连接。连接时,同时需要加上证书信息:
./src/redis-cli --tls --cert ./tests/tls/redis.crt --key ./tests/tls/redis.key --cacert ./tests/tls/ca.crt
在进行主备版测试时,redis-server及sentinel除新增上述参数外,需额外增加“--tls-auth-clients yes”和“--tls-replication yes”方可启动成功,否则将无法连接,解释见Redis TLS参数解析。
2.2.4 TLS部分源码简析
(1)TLS初始化
在server.c的main函数中,tlsInit()函数用于初始化SSL库,initServerConfig()、initConfigValues()用于初始化tls_port和tls_ctx_config等TLS相关参数(相关参数均在redis.conf中获得)。在InitServer()中,获得的TLS参数tls_ctx_config和tls_port被用于tls.c的tlsConfigure()中用于初始化TLS。
if ((server.tls_port || server.tls_replication || server.tls_cluster)
&& tlsConfigure(&server.tls_ctx_config) == C_ERR) {
serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info.");
exit(1);
}
(2)TLS读写
读写操作由connTLSRead()和connTLSWrite()组成。connTLSRead()是一个SSL_Read的封装,将从tls连接中读取的字节缓冲到buf中。connTLSWrite()是一个SSL_Write的封装,将字节写入data套接字。其中,tls_connection为建立起的tls连接。
static int connTLSRead(connection *conn_, void *buf, size_t buf_len) {
tls_connection *conn = (tls_connection *) conn_;
int ret;
int ssl_err;
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
ERR_clear_error();
ret = SSL_read(conn->ssl, buf, buf_len);
...
return ret;
}
2.2.5 TLS局限性
Redis特性在于支持高并发且6.0开始支持多线程,然而开启了TLS功能后,出现了明显的局限性:
① 不再支持多线程功能(TLS.md:**Multi-threading I/O is not currently supported for TLS**);
② QPS显著下降,经测试只有未开启TLS的60%(详见下图);
③ 6.0.X版本的redis-benchmark不支持TLS功能,需使用6.2.X版本的redis-benchmark才能执行测试。
使用redis-benchmark在同一台主机上进行对SET、GET、LRANGE_600测试,结果如图所示。
可以看出,开启TLS后,QPS明显降低。
2.3新功能——多线程IO
2.3.1 多线程IO简介
Redis6.0 之前都是使用单线程异步IO处理数据的,单线程IO只能使用一个CPU核。当key的value比较大时容易拖垮Redis,而且QPS到达10W+后难以更进一步。从Redis6.0开始支持多线程IO,多线程IO不仅可以充分利用服务器CPU资源,还可以分摊IO读写负荷,大幅提升了Redis性能。
2.3.2 多线程IO实现及配置示例
(1)多线程IO流程
流程主要如下:
① 主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列;
② 主线程处理完读事件后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程;
③ 主线程阻塞等待 IO 线程读取 socket 完毕;
④ 主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行;
⑤ 主线程阻塞等待 IO 线程将数据回写 socket 完毕;
⑥ 解除绑定,清空等待队列。
(2)多线程IO参数解析
在redis.conf中,可以对多线程进行配置(修改配置需重启redis实例)。多线程配涉及两个参数:
参数 | 说明 |
---|---|
io-threads | 开启多线程的个数(io-thread最大值不能超过服务器CPU可使用总核数) |
io-threads-do-reads | 取yes表示多线程开启 |
2.3.3多线程IO使用及性能对比
添加多线程IO参数重启redis后,通过redis-cli config get *命令可查看当前io-threads及io-threads-do-reads配置现况。
多线程IO主要表现在提升了redis的并发能力上,故对不同版本的redis及同版本redis在不同机器上进行了比对测试,分析多线程IO提升能力。
(1)Redis版本间对比测试
在同一台主机上,对Redis4.0和Redis6.0进行redis-benchmark性能测试。测试时,redis-benchmark使用了--threads参数进行多线程测试。测试结果详见下图:
可以看出,Redis6.0较Redis4.0同在单线程时,性能并没有得到提升;Redis6.0 二线程,较redis6.0单线程、Redis4.0并发量得到了明显提升。因此,可以得出结论,多线程IO可明显提高Redis的并发量。
(2)Redis CPU间性能对比测试
多线程IO可明显提升Redis并发量,在不同类型的CPU主机上测试,也有不同的提升,具体如下:
① 多线程IO 并发上限不到单线程IO的三倍,io-threads到一定数之后(测试时值为6-10,因服务器CPU差别),并发增幅极为有限。在这种情况下,即使增加线程数,也不能明显增加Redis 并发量,只会导致服务器负载增加。
② CPU频率越高,能达到的最大并发相对也越高。故选用CPU时,选择较高频率CPU可以提升Redis性能。使用时,打开高性能模式也可以提升Redis性能。
Redis6.0 SET Intel Xeon测试结果:
2.3.4 多线程IO部分源码简析
多线程IO中,配置文件redis.conf主要涉及的参数是io_threads_do_reads、io-threads,源码中,除了这两个关键变量外,还会涉及io_threads_op、io_threads_pending等变量。
(1)多线程IO初始化
在server.c的main函数InitServerLast()中,有初始化多线程函数initThreadedIO()。函数中调用了pthread_create 来创建线程,并且注册了线程回调函数IOThreadMain()。
void initThreadedIO(void) {
server.io_threads_active = 0;
if (server.io_threads_num == 1) return;
if (server.io_threads_num > IO_THREADS_MAX_NUM) {
serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
"The maximum number is %d.", IO_THREADS_MAX_NUM);
exit(1);
}
/* Spawn and initialize the I/O threads. */
for (int i = 0; i < server.io_threads_num; i++) {
/* Things we do for all the threads including the main thread. */
io_threads_list[i] = listCreate();
if (i == 0) continue;
pthread_t tid;
pthread_mutex_init(&io_threads_mutex[i],NULL);
io_threads_pending[i] = 0;
pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
exit(1);
}
io_threads[i] = tid;
}
}
(2)多线程IO任务处理
以多线程读为例,使用了handleClientsWithPendingReadsUsingThreads 函数进行多线程读,步骤如下:
① 通过 io_threads_active 和io_threads_do_reads 两个标志判断是否开启了多线程IO,其中 io_threads_do_reads为redis.conf配置参数,io_threads_active通过调用startThreadedIO()进行置1操作;
② 主线程给工作线程分配client对象策略即轮询策略,io_threads_op指定读类型,通过给io_threads_pending[id]赋值启动工作线程,工作线程在处理完自己链表的 client 对象后会清空自己的链表并重置 io_threads_pending[id] 为0;
③ 主线程的利用while将自己链表中的 client 处理完毕;
④ 最后,线程的pending和为0,跳出循环完成读事件的处理。
int handleClientsWithPendingReadsUsingThreads(void) {
if (!server.io_threads_active || !server.io_threads_do_reads) return 0;
int processed = listLength(server.clients_pending_read);
if (processed == 0) return 0;
if (tio_debug) printf("%d TOTAL READ pending clients\n", processed);
/* Distribute the clients across N different lists. */
listIter li;
listNode *ln;
listRewind(server.clients_pending_read,&li);
int item_id = 0;
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
int target_id = item_id % server.io_threads_num;
listAddNodeTail(io_threads_list[target_id],c);
item_id++;
}
/* Give the start condition to the waiting threads, by setting the
* start condition atomic var. */
io_threads_op = IO_THREADS_OP_READ;
for (int j = 1; j < server.io_threads_num; j++) {
int count = listLength(io_threads_list[j]);
io_threads_pending[j] = count;
}
/* Also use the main thread to process a slice of clients. */
listRewind(io_threads_list[0],&li);
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
readQueryFromClient(c->conn);
}
listEmpty(io_threads_list[0]);
/* Wait for all the other threads to end their work. */
while(1) {
unsigned long pending = 0;
for (int j = 1; j < server.io_threads_num; j++)
pending += io_threads_pending[j];
if (pending == 0) break;
}
if (tio_debug) printf("I/O READ All threads finshed\n");
...
/* Update processed count on server */
server.stat_io_reads_processed += processed;
return processed;
}
3. 小结
本次主要对Redis6.0的三个新功能ACL权限控制、TLS加密管理及多线程IO进行了功能性能测试及分析,测试时也兼带对redis-cli、redis-benchmark新功能进行了一定的测试验证:
- ACL权限控制实现了Redis的账号管理及权限分配,对Redis数据安全防止误删起到了一定的作用。
- TLS加密管理可以明显增加Redis的访问安全性,但牺牲了一定的性能。Redis作为一款高并发kv数据库,使用TLS时需考虑安全与性能间的取舍。
- 多线程IO可明显增加Redis并发量,在对Redis并发有较高需求时,可使用高频多核CPU以提升Redis性能。
- redis-benchmark在6.0版本支持了多线程,测试时可以多个线程同时进行,无需通过多台主机同时压测,也是一个较大的优化点,方便了压测进行。