所有现代消费者应用都必须轻松且经济高效地进行扩展,为此,数据库的线性性能是关键。凭借我们无共享架构,我们一次又一次地证明,通过简单地添加分片和节点,单个 Redis Enterprise 集群可以以线性方式无限扩展。但这并不意味着我们可以止步不前。在 RedisConf18 大会上,我们演示了单个 Redis Enterprise 集群如何通过仅 26 个 AWS 实例达到 每秒 5000 万次操作 (ops/sec),同时将延迟保持在 1 毫秒以下。今天,我们很高兴地分享 Redis Enterprise 再创新的行业性能记录。
在我们最新的基准测试中,Redis Enterprise 在仅 40 个 AWS 实例上实现了超过 2 亿 ops/sec,延迟低于 1 毫秒。这表明在不到 15 个月的时间里,性能提升了 2.6 倍。在我们深入探讨最新基准测试的配置、工具、工作负载和结果之前,让我们快速回顾一下 Redis Enterprise 集群。
在 Redis Enterprise 中,集群是由云实例、虚拟机、容器/Kubernetes POD 或裸机服务器组成的节点集合。集群允许您在这些节点共享的内存池中创建 Redis 数据库,同时完全分离数据路径和控制管理路径。每个集群包括 Redis 分片、零延迟代理和一个集群管理器。由于开源集群 API 允许 Redis 客户端直接访问持有键值对象的分片而无需额外的网络跳转,因此 Redis Enterprise 集群可以非常高效地扩展。控制路径(负责数据库的配置/解除配置、自动故障转移、分片迁移、集群平衡等)产生的开销非常小,不会影响这种线性可伸缩性。最后但同样重要的是,Redis Enterprise 在为扩展目的向集群添加更多节点之前,会高效利用每个集群节点上的所有核心。
下图是 Redis Enterprise 无共享集群架构的可视化描述,该架构使用开源集群 API 配置并利用了给定集群节点中的所有核心
作为基准,我们想测试在单个 AWS 实例上运行时能达到多少 ops/sec,并考虑到以下要求
为此,我们使用了以下系统设置:
1) 我们安装了 Redis Enterprise。
https://docs.redis.com/latest/rs/installing-upgrading/downloading-installing/
2) 我们设置了 Route53。
https://docs.redis.com/latest/rs/installing-upgrading/configuring/cluster-name-dns-connection-management/configuring-aws-route53-dns-redis-enterprise/
3) 我们创建了数据库。
指定 cluster_api_user, name, shards_count 和 port
如下所示,API 将创建名为 name:<name > 、包含 shards:<shards_count> 个分片、监听 port:<port> 端口的数据库。即使我们在此基准测试中使用的是单节点集群,我们也设置了 proxy_policy、shards_placement 和 oss_cluster 参数。这些参数用于平均放置分片并在所有节点上创建端点,以与多节点集群配置保持一致。
curl -v -k -u <cluster_api_user> https://localhost:9443/v1/bdbs -H "Content-type: application/json" -d
'{
"name": <name>,
"memory_size": 10000000000,
"type" : "redis",
"sharding": true ,
"shards_count": <shards_count>,
"shard_key_regex": [{"regex": ".*{(?<tag>.*)}.*"},{"regex": "(?<tag>.*)" }],
"proxy_policy": "all-master-shards",
"shards_placement": "sparse",
"oss_cluster":true,
"port":<port>
}'
为确保测试公平,我们首先填充了数据库,以避免对不存在的键执行 GET 操作(指定 NUM_OF_KEYS):
memtier_benchmark -s <EndPoint> -p <Port> -t 48 --ratio=1:0 --pipeline=9 -c 1 -d 100 --key-minimum=1 --key-maximum=<NUM_OF_KEYS> -n allkeys --key-pattern=S:S
4) 我们选择 AWS EC2 上的 c5.18xlarge 实例类型作为我们的节点,它配备 72 个 vCPU、144GB 内存和 3.0 GHz(使用英特尔 Turbo Boost 技术最高可达 3.5 GHz)英特尔 Xeon Skylake-SP 处理器。
5) 我们选择 c5.9xlarge 实例类型来运行客户端机器上的负载生成工具。
6) 最后,对于我们的负载生成工具,我们使用了 memtier_benchmark 并带有以下参数:
memtier_benchmark -s <redis.endpoint.address> -p <port> -t $Threads —ratio=1:1 --pipeline=9 -c $Clients -d 100 --key-minimum=1 --key-maximum=<NUMBER_OF_KEYS> -n 1000000000 --cluster-mode
然后我们尝试寻找最佳的分片和代理线程平衡,并观察到在每个节点 8 个分片的情况下获得了最佳结果,这使我们达到了 420 万 ops/sec。由于代理线程的上下文切换以及 Redis 进程在不同 NUMA 节点 CPU 之间的切换,吞吐量不稳定。这种跨内存访问增加了处理延迟,即使系统负载仅为 50%。
如下图所示,在 c5.18xlarge 实例上,通过使用 NUMA 绑定来固定 Redis 分片和代理线程的亲和性,我们在 8 分片和 10 分片配置下获得了更好的结果
注意:以下脚本仅适用于双插槽系统。
NUMA_CNT=$(numactl --hardware | grep '^available' | awk '{print $2}')
if [ $NUMA_CNT -eq 2 ]; then
NODE_ID=$(cat /etc/opt/redis/node.id)
DMC_HALF_COUNT=$(expr $(/opt/redis/bin/ccs-cli hget dmc:$NODE_ID threads) / 2)
NUMA0_CPUS=$(numactl --hardware | grep 'node 0 cpus' | awk -F ': ' '{print $2}' | sed 's/ /,/g')
NUMA1_CPUS=$(numactl --hardware | grep 'node 1 cpus' | awk -F ': ' '{print $2}' | sed 's/ /,/g')
DMC_PID=$(sudo /opt/redis/bin/dmc-cli -ts root list | grep listener | awk '{printf "%in",$3}')
sudo taskset -apc $NUMA0_CPUS $DMC_PID
sudo /opt/redis/bin/dmc-cli -ts root list | grep worker | tail -$DMC_HALF_COUNT |
awk '{printf "%in",$3}' |
xargs -i sudo taskset -pc $NUMA1_CPUS {}
fi
NUMA_CNT=$(numactl --hardware | grep '^available' | awk '{print $2}')
REDIS_HALF_CNT=$(expr $(pgrep redis-server-5 | wc -l) / 2)
NUMA0_CPUS=$(numactl --hardware | grep 'node 0 cpus' | awk -F ': ' '{print $2}' | sed 's/ /,/g')
NUMA1_CPUS=$(numactl --hardware | grep 'node 1 cpus' | awk -F ': ' '{print $2}' | sed 's/ /,/g')
pgrep redis-server-5 | sort | head -$REDIS_HALF_CNT | xargs -i sudo taskset -apc $NUMA0_CPUS {} &&
pgrep redis-server-5 | sort | head -$REDIS_HALF_CNT | xargs -i sudo migratepages {} 1 0
pgrep redis-server-5 | sort | tail -$REDIS_HALF_CNT | xargs -i sudo taskset -apc $NUMA1_CPUS {} &&
pgrep redis-server-5 | sort | tail -$REDIS_HALF_CNT | xargs -i sudo migratepages {} 0 1
下表显示了我们如何在 AWS c5.18xlarge 实例上确定最佳的 Redis 分片和代理线程配置,以在保持亚毫秒级延迟的同时实现最佳的吞吐量(ops/sec)
分片 | 4 | 6 | 8 | 8 | 10 |
代理线程 | 16 | 24 | 28 | 28 | 32 |
备注 | 默认 | 默认 | 默认 | NUMA 优化 | NUMA 优化 |
延迟 (毫秒) | 0.92 | 0.91 | 0.98 | 0.81 | 0.92 |
吞吐量 (百万 ops/sec) |
2.9 | 3.89 | 4.2 | 4.8 | 5.3 |
每分片吞吐量 (千 ops/sec) | 725 | 648 | 525 | 600 | 530 |
我们首先希望复制在 RedisConf18 大会上使用 26 个 m4.16xlarge 实例演示的 5000 万 ops/sec 基准测试。这一次,我们仅使用 10 个 AWS 5.18xlarge 实例就达到了 5172 万 ops/sec
节点 (C5.18xlarge) | 10 |
客户端 (C5.9xlarge) | 10 |
每节点分片数 | 10 |
代理线程数 | 32 |
预填充键数 | 10M |
连接数 | 14,800 |
平均 ops/sec | 51.72M |
平均延迟 | 0.92 |
每节点平均 ops/sec | 5.17M |
节点 (C5.18xlarge) | 20 |
客户端 (C5.9xlarge) | 20 |
每节点分片数 | 10 |
代理线程数 | 32 |
预填充键数 | 10M |
连接数 | 30,400 |
平均 ops/sec | 102.37M |
平均延迟 | 0.94 |
每节点平均 ops/sec | 5.11M |
节点 (C5.18xlarge) | 40 |
客户端 (C5.9xlarge) | 30 |
每节点分片数 | 10 |
代理线程数 | 32 |
预填充键数 | 10M |
连接数 | 72,000 |
平均 ops/sec | 201.17M |
平均延迟 | 0.99 |
每节点平均 ops/sec | 5.02M |
我们在 5.0 版本中对 Redis 内核进行的多次改进(尤其是在大流水线性能方面),加上对 Redis Enterprise 代理及其与 Redis 分片通信方式的多项增强,并结合新的 AWS C5 实例系列和适当的 NUMA 配置,所有这些都有助于在仅包含 40 个节点 的集群上实现超过 2 亿 ops/sec,同时将延迟保持在 1 毫秒 以下。
我们的无共享架构使得向集群中每增加一个 Redis 分片可以增加 50 万+ ops/sec,每增加一个节点可以增加 500 万+ ops/sec,实现了接近最优的 94% 线性扩展。换句话说,单节点和 40 节点集群之间的每节点吞吐量仅下降了 6%。
在这些测试中,我们甚至设法实现了每个 Redis 分片 80 万 ops/sec(具有稳定的亚毫秒级延迟),但由于每秒网络包限制,我们无法扩展节点吞吐量以长时间获得更高且稳定的性能。
我们对于打破 15 个月前我们自己创下的数据库性能记录所看到的显著改进感到惊叹:
2018 年 3 月 | 2019 年 6 月 | 改进 | |
集群吞吐量 | 50M ops/sec | 200M ops/sec | x4 |
节点数 | 26 | 40 | x2.6 |
分片数 | 512 | 400 | x5 |
此实验表明,通过适当的架构(如 Redis Enterprise 所实现的),Redis 可以在数据库领域打破任何性能记录,同时比其他数据库使用明显更少的硬件资源。