dot Redis 8 已推出——且它是开源的

了解更多

Redis Enterprise 在仅 40 个 AWS 实例上以 <1 毫秒延迟实现 2 亿 ops/sec,扩展了线性可伸缩性

所有现代消费者应用都必须轻松且经济高效地进行扩展,为此,数据库的线性性能是关键。凭借我们无共享架构,我们一次又一次地证明,通过简单地添加分片和节点,单个 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 架构回顾

在 Redis Enterprise 中,集群是由云实例、虚拟机、容器/Kubernetes POD 或裸机服务器组成的节点集合。集群允许您在这些节点共享的内存池中创建 Redis 数据库,同时完全分离数据路径和控制管理路径。每个集群包括 Redis 分片、零延迟代理和一个集群管理器。由于开源集群 API 允许 Redis 客户端直接访问持有键值对象的分片而无需额外的网络跳转,因此 Redis Enterprise 集群可以非常高效地扩展。控制路径(负责数据库的配置/解除配置、自动故障转移、分片迁移、集群平衡等)产生的开销非常小,不会影响这种线性可伸缩性。最后但同样重要的是,Redis Enterprise 在为扩展目的向集群添加更多节点之前,会高效利用每个集群节点上的所有核心。 

下图是 Redis Enterprise 无共享集群架构的可视化描述,该架构使用开源集群 API 配置并利用了给定集群节点中的所有核心

单节点基准测试

作为基准,我们想测试在单个 AWS 实例上运行时能达到多少 ops/sec,并考虑到以下要求

  • 保持延迟低于 1 毫秒 
  • 将我们的流水线最大设置为 9
  • 使用 100 字节的值大小
  • Get:Set 比例为 1:1  

为此,我们使用了以下系统设置:

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_countport

如下所示,API 将创建名为 name:<name > 、包含 shards:<shards_count> 个分片、监听 port:<port> 端口的数据库。即使我们在此基准测试中使用的是单节点集群,我们也设置了 proxy_policyshards_placementoss_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 节点之间的代理线程平衡:

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

  • 平衡并绑定 Redis 进程的 CPU 和内存

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

扩展集群

使用 10 个 AWS c5.18xlarge 实例实现 5000 万 ops/sec

我们首先希望复制在 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

然后,我们使用 20 个 AWS 5.18xlarge 实例扩展到 1 亿 ops/sec

节点 (C5.18xlarge) 20
客户端 (C5.9xlarge) 20
每节点分片数 10
代理线程数 32
预填充键数 10M
连接数 30,400
平均 ops/sec 102.37M
平均延迟 0.94
每节点平均 ops/sec 5.11M

最后,使用 40 个 AWS c5.18xlarge 实例实现了 2 亿 ops/sec 

节点 (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 可以在数据库领域打破任何性能记录,同时比其他数据库使用明显更少的硬件资源。