dot 快速未来的脚步即将踏入您所在的城市。

加入我们参加 Redis 发布会

Redis 企业通过仅 40 个 AWS 实例在 <1ms 延迟下将线性可扩展性扩展到 200M ops/sec

所有现代消费者应用程序都必须易于扩展且成本效益高,为此,线性数据库性能至关重要。凭借我们的无共享架构,我们已经反复证明,单个 Redis 企业集群可以通过简单地添加分片和节点来以线性方式无限扩展。但这并不意味着我们已经取得了成果。在 RedisConf18 上,我们展示了一个单个 Redis 企业集群可以实现 每秒 5000 万次操作 (ops/sec),仅使用 26 个 AWS 实例,同时将延迟保持在 1 毫秒以下,今天我们很高兴地宣布 Redis 企业已创造了新的行业性能记录。 

在我们最新的基准测试中,Redis 企业在仅 40 个 AWS 实例上实现了超过 2 亿 ops/sec 的性能,延迟低于 1 毫秒。这代表着在不到 15 个月的时间里性能提高了 2.6 倍。在深入了解最新基准测试的配置、工具、工作负载和结果之前,让我们快速回顾一下 Redis 企业集群。

Redis 企业架构回顾

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

以下是 Redis 企业无共享集群架构的直观描述,该架构配置了开源集群 API 并利用了给定集群节点中的所有内核

单节点基准测试

作为基准,我们想测试在单个 AWS 实例上运行时可以实现多少 ops/sec,并牢记以下要求

  • 将延迟保持在 1 毫秒以下 
  • 将我们的管道保持在最大 9 个
  • 使用 100 字节的值大小
  • 具有 1:1 的 Get:Set 比例  

为了实现这一点,我们使用了以下系统设置

1) 我们安装了 Redis 企业。 
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_usernameshards_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)英特尔至强 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 实例上使用 8 分片和 10 分片配置获得更好的结果,方法是使用 NUMA 绑定进行 Redis 分片和代理线程亲和性

注意:以下脚本仅在双插槽系统上有效。 

  • 设置 代理线程在 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
吞吐量
(M ops/sec)
2.9 3.89 4.2 4.8 5.3
每个分片的吞吐量 (K ops/sec) 725 648 525 600 530

扩展集群

使用 10 个 AWS c5.18xlarge 实例实现 50M 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
每个节点的平均每秒操作数 5.17M

然后我们使用 20 个 AWS 5.18xlarge 实例扩展到每秒 100M 次操作

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

最后,使用 40 个 AWS c5.18xlarge 实例扩展到每秒 200M 次操作 

节点  (C5.18xlarge) 40
客户端 (C5.9xlarge) 30
每个节点的分片数 10
代理线程 32
预填充键 10M
连接 72,000
平均 ops/sec 201.17M
平均延迟 0.99
每个节点的平均每秒操作数 5.02M

结果汇总

我们在 5.0 版本中对 Redis 核心进行了多项改进(特别是在大型管道性能方面),再加上对 Redis Enterprise 代理以及它与 Redis 分片通信方式的诸多增强,再加上新的 AWS C5 实例系列和适当的 NUMA 配置,这一切都帮助我们实现了超过每秒 200M 次操作,而只使用了一个40 节点集群,同时保持延迟低于1 毫秒

我们的无共享架构为每个 Redis 分片额外提供了每秒 500K+ 次操作,并且为添加到集群中的每个节点提供了每秒 5M+ 次操作,接近最佳的 94% 线性扩展。换句话说,从单个节点到 40 节点集群,每个节点的吞吐量仅下降了 6%。

在这些测试中,我们甚至设法实现了每个 Redis 分片每秒 800K 次操作(并保持一致的亚毫秒级延迟),但由于每秒网络数据包的限制,我们无法扩展节点吞吐量以实现更高且稳定的性能。

我们对打破我们 15 个月前的数据库性能记录所看到的显著改进感到惊讶:

2018 年 3 月 2019 年 6 月 改进
集群吞吐量 每秒 50M 次操作 每秒 200M 次操作 x4
节点数 26 40 x2.6
分片数 512 400 x5

此实验表明,通过 Redis Enterprise 所实施的适当架构,Redis 可以打破数据库领域内的任何性能记录,同时使用比其他数据库少得多的硬件资源。