直播预约通道开启!2021腾讯数字生态大会邀您共探产业发展新机遇! 了解详情
写点什么

美图高性能 twemproxy 的改造之路

2020 年 2 月 21 日

美图高性能twemproxy的改造之路

摘要

美图在 2017 年下半年开始计划做 Redis/Memcached 资源 PaaS 平台,而 PaaS 化之后面临一个问题是如何实现资源缩容/扩容对业务无感,为了解决这个问题,美图技术团队于 17 年 11 月引入 twemproxy 作为资源网关。


但是长期的实践中,其开源版本不能完全适应美图的实际情况,其主要存在单线程模型无法利用多核,性能不佳;配置无法在线 Reload ;Redis 不支持主从模式;无延时指标等问题,所以美图技术团队对其进行了相应的改造。我们基于之上实现了多进程以及配置在线更新的功能,同时增加了一些延时的相关监控指标。


本文将为大家详细讲解 twemproxy 实现以及相应地改造,希望能给其他的技术团队提供一些可以借鉴的经验。


为什么要选择 twemproxy

twemproxy 是一款由 twitter 开源的 Redis/Memcached 代理,主要目标是减少后端资源的连接数以及为缓存横向扩展能力。 twemproxy 支持多种 hash 分片算法,同时具备失败节点自动剔除的功能。除此之外,其他比较成熟的开源解决方案还有 codis,codis 具备在线的 auto-scale 以及友好的后台管理,但整体的功能更接近于 Redis Cluster,而不是代理。美图这边需要的是一个 Redis 和 Memcached 协议类 PaaS 服务的代理(网关),所以我们最终选择了 twemproxy。


twemproxy 实现

twemproxy 主要的功能是解析用户请求后转发到后端的缓存资源,成功后在把响应转发回客户端。


代码实现的核心是三种连接对象:


1.proxy connection, 用来监听用户建立连接的请求,建立连接成功后会对应产生一个客户端连接;


2.client connection,由建连成功后产生,用户读写数据都是通过 client;connection 解析请求后,根据 key 和哈希规则选择一个 server 进行转发;


3.server connection,转发用户请求到缓存资源并接收和解析响应数据转回 client connection,client connection 将响应返回到用户。


三种连接的数据流向如下图:



上图的 client connection 之所以没有 imsgq 是因为请求解析完可以直接进入 server 的 imsgq


1.用户通过 proxy connection 建立连接,产生一个 client connection;


2.client connection 开始读取用户的请求数据,并将完整的请求根据 key 和设置的哈希规则选择 server, 然后将这个请求存放到 server 的 imsgq;


3.接着 server connection 发送 imsgq 请求到远程资源,发送完成之后(写 tcp buffer) 就会将 msg 从 imsgq 迁移到 omsgq,响应回来之后从 omsgq 队列里面找到这个对应的 msg 以及 client connection;


4.最后将响应内容放到 client connection 的 omsgq,由 client connection 将数据发送回客户端。


上面提到的用户请求和资源响应的数据都是在解析之后放到内存的 buf 里面,在 client 和 server 两种连接的内部流转也只是指针的拷贝(官网 README 里面提到的 Zero Copy)。这也是 twemproxy 单线程模型在小包场景能够达到 10w qps 的原因之一,几乎不拷贝内存。


但对于我们来说,当前开源版本存在几个问题:


1.单线程模型无法利用多核,性能不够好,极端情况下代理和资源需要 1:1 部署;


2.配置无法在线 Reload,twitter 内部版本应该是支持的,单元测试里面有针对 reload 的 case,PaaS 场景需要不断更新配置;


3.Redis 不支持主从模式(Redis 在作为缓存的场景下确实没必要使用主从),但部分场景需要;


4.数据指标过少,延时指标完全没有。


多进程版本

针对以上的几个问题,美图的开源版本都做了一些修改,最核心的功能是多进程和配置在线 reload。改造后整体进程模型类似 Nginx, 简单示意图如下:



master 的功能就是管理 worker 进程,不接收和处理用户请求。如果 worker 进程异常退出,那么 master 则会自动拉起新的进程来替代挂掉的老进程。除此之外,master 还会接收来自用户的几种信号:


  • SIGHUP, 重新加载新配置

  • SIGTTIN,提高日志级别, 级别越高日志越详细

  • SIGTTOU,降低日志级别,级别越低日志越少

  • SIGUSR1,重新打开日志文件

  • SIGTERM,优雅退出,等到一段时间后退出

  • SIGINT,强制退出


同时还增加了几个全局配置:



除了 worker_shutdown_timeout 其他几个配置应该比较好理解。worker_shutdown_timeout 是配置老 worker 在收到退出信号后多长时间退出。这个配置是跟多进程实现相关的参数,我们是通过启动新进程替代老进程的方式来实现配置以及进程数目的在线修改,所以这个配置就是用来指定老进程的保留时间。


reuse port

在 reuse port 之前,多线程/进程服务监听建连请求一般有两种方式:


1.由一个线程负责接收所有的新连接,其他线程服务只负责建立连接之后的处理。这种方式的问题是在短连接场景下,这个 accept 线程很容易成为瓶颈(单核我们这边测试一般是在 4w+/s 左右);


2.所有的线程/进程都同时 accept 同一个监听的文件句柄。 这种方式的问题是在高负载的场景下,不同线程/进程的唤醒会不均匀,另外会有惊群的效果(accept/epoll 在新版本内核中也有解决惊群问题)。


reuse port 的主要作用就是允许多个 socket 同时监听同一个端口,同时不会存在建立连接不均匀的问题。 使用 reuse port 也相当简单,只需要把监听通过一个端口的 socket 都设置上 reuse port 标识即可。



虽然 reuse port 是在 linux 3.9 才被合并进来,但有 backport 到更早之前的版本(至少我们在使用的 2.6.32 是有的),很多博客在这点上有些误导。另外,在 reload 时候也不能简单将老的监听关闭,会导致 tcp backlog 里面这些三次握手成功但未 accept 的连接丢失,业务在这些连接上发送数据则会收到 rst 包。


我们解决这个问题的方式是让监听连接都在 master 进程上面创建和维护,worker 进程只是在 fork 之后直接继承监听的连接,所以在 reload 的时候 master 就可以将老 worker 里面的监听连接迁移到新的 worker, 来保证 tcp backlog 里面的数据不会丢失。


具体代码见: nc_process.c#L172, 这种方式能够在进程数不变或者增多的场景下保证 backlog 里面的数据不会丢,进程数缩减时还是会丢失一些。


Redis 主从模式

在原生的 twemproxy 里面是不支持 Redis 主从模式的,这个应该主要是因为 twemproxy 把 Redis/Memcached 当做是缓存而不是存储,所以这种主从结构实际上是没有必要的,运维也比较简单。但是对于我们内部业务来说,有些并不是全部都是作为缓存,所以就需要这种主从结构。配置也比较简单:



如果检测到 server 的名字为 master 则认为该实例为主,一个池子里面只允许一个主,否则认为配置不合法。


统计指标

个人觉得 twemproxy 存在的另外一个问题是延时指标完全缺失,这个对于排查问题以及监控报警是比较不利的。针对这个问题,我们增加了两种延时指标:


1.request latency, 指的是客户端请求到返回的延时, 包含 twemproxy 内部以及 server 的耗时,这个指标更加接近业务的耗时;


2.server latency, 指的是 twemproxy 请求 server 的耗时,这个可以理解为 Redis/Memcached server 的耗时。


在偶发问题的场景下,根据两种延时可以定位是 twemproxy、server 还是客户端的问题(比如 GC)导致慢请求,另外也可以慢请求的比例进行监控报警。这两种指标是通过 bucket 的方式来记录的,比如 <1ms 的数目,<10ms 的数目等等。


仍然存在的问题

1.在 worker 数目减少的场景下,被销毁的老 Worker 的 tcp backlog 会丢失会导致一些连接超时;


2.unix socket 没有 reuse port 类似的机制,所以实际上还是单进程但可以支持在线 reload;


3.不支持 Memcached 二进制协议,几年前有人提供相关 PR 但一直都没有进入 master;


4.客户端的最大连接数有配置但实际上不生效,这个功能我们后续会加上;


5.命令支持不全(主要是没有 key 以及一些 blocking 的指令);


6.reload 期间新老进程的配置不一致会可能会导致脏数据。


性能压测

以下数据是在长连接小包场景下压测得出,主要是验证多进程版本是否跟预期的一致。没有其他硬件到达瓶颈之前,性能可以随着 CPU 核数线性增长。


压测环境如下:


  • CentOS 6.6

  • CPU Intel E5-2660 32 逻辑核

  • 内存 64G

  • 两张千兆网卡做 bond0

  • 单个 worker 场景和 twemproxy 改造之前的性能差不多,在 10w 左右。随着 worker 数目增加,后面性能和 worker 基本是保持线上增长,符合预期。8 核以上的瓶颈是 bond0 模式下包接收不均匀导致单网卡性能达到瓶颈,数据无法作为参考。上面的数据也是我们自己环境的压测数据,大家可以自行验证。如果是多网卡需要注意绑定中断或者多队列到多个 CPU, 避免 CPU0 软中断处理成为瓶颈。

  • 最后


多进程版本的 twemproxy 实现上算是比较简单,但过程中发现并修复不少 twemproxy 细节问题(一部分是使用方报告),比如 mbuf 一旦分配就不会收缩导致内存上涨之后不再下降的问题等等。很多功能细节我们也在不断优化,我们也只维护 Github 上的一个版本。


代码地址: https://github.com/meitu/twemproxy


除此之外,我们团队目前也开源其他一些项目:


golang 版本的 kafka consumer group


php 版本的 kafka consumer group


基于以太坊的 DPoS 实现


后续还会开源更多的东西,欢迎大家多多关注~


最后感谢相关代码贡献者: @ruoshan @karelrooted @Huang-lin @git-hulk


作者介绍:


林添毅,美图技术经理, 主要负责 NoSQL/消息队列/中间件和区块链基础服务相关研发。对分布式缓存和高可用架构设计方面有较为深入地研究,带领团队完成了美图缓存以及消息队列服务化。在加入美图之前,曾就职于新浪微博架构平台从事基础服务的研发。


本文转载自美图技术公众号。


原文链接:https://mp.weixin.qq.com/s/7THrh3cXg8xJT5mgZVw4vw


2020 年 2 月 21 日 22:48686

评论

发布
暂无评论
发现更多内容

内存数据库解析与主流产品对比(三)

星环科技

数据库 大数据

智能汽车为什么新势力有胜算(28天写作 Day20/28)

mtfelix

28天写作 新能源汽车 智能汽车 造车新势力

30天消化MyBatis源码解析笔记,吊打面试官,offer接到手软

Java架构之路

Java 程序员 架构 面试 编程语言

智慧社区建设,平安社区解决方案服务商

135深圳3055源中瑞8032

《程序员修炼之道》- 务实的哲学(3)

石云升

读书笔记 28天写作 批判性思维 程序员修炼之道 完成好过完美

淦!阿里P9加班4个多月肝出这份包含了30多个技术点的Java核心笔记

Java成神之路

Java 程序员 架构 面试 编程语言

5年Java经验不会源码被拒,苦学这些Spring源码笔记后,面试不再慌

Java架构之路

Java 程序员 架构 面试 编程语言

kotlin高阶函数let、with、apply、run、also使用场景

陈吉米

kotlin

抽奖助手利益相关方

千竹

区块链+数字版权-区块链技术如何保护版权

135深圳3055源中瑞8032

3年JAVA开发经验成功入职美团,面试回顾及个人总结:算法+框架+Redis+分布式+JVM

Java成神之路

Java 程序员 架构 面试 编程语言

4位阿里架构师共著的《深入浅出Java多线程》,膜拜大佬

Java成神之路

Java 程序员 架构 面试 编程语言

10 个 JavaScript 简洁代码小技巧(文末彩蛋)

零和幺

JavaScript 前端 CleanCode

阿里四年技术 TL 的得失总结:如何做好技术 Team Leader

阿里巴巴云原生

云计算 项目管理 程序员 微服务 云原生

译文《最常见的10种Java异常问题》

潘潘和他的朋友们

Java 异常 java异常处理 Exception

区区一个SpringBoot问题就干趴下了?我却凭着这套“神级PDF文档”吊打面试官

云流

Java 编程 面试 微服务

爱了!Alibaba技术官甩出的SpringCloud笔记,GitHub已标星81.6k

周老师

Java 编程 程序员 架构 面试

新思科技发布《美国不良软件质量成本:2020年报告》

InfoQ_434670063458

软件质量 新思科技

数据库恢复子系统的常见技术和方案对比(一)

星环科技

数据库 大数据

读阿里P8大佬15W字的Spring文档,面试犹如开了挂,成了Offer收割机

Crud的程序员

spring

产品训练营--第二期作业

曦语

产品训练营

一个 3 年 Java 程序员 5 家大厂的面试总结(已拿Offer)

Java架构之路

Java 程序员 架构 面试 编程语言

数据库恢复子系统的常见技术和方案对比(二)

星环科技

数据库 大数据

【推荐收藏!】Gradle 与 Android 构建入门

百度Geek说

研发工具 andiod

阿里巴巴内部MySQL宝典意外流出!极致经典,堪称数据库的天花板

Crud的程序员

MySQL 数据库

拍乐云 Flutter SDK 全新发布,跨平台音视频开发更easy

拍乐云Pano

flutter 音视频 WebRTC RTC

Flink 学习路线总结

大数据学习指南

大数据 flink

就业篇-如何抉择自己合适的路

小诚信驿站

团队管理 团队建设 成长笔记 28天写作

“区块链+电子证照”-助推数字政府建设

135深圳3055源中瑞8032

Elasticsearch 基于脚本进行 partial update

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试

同事跳槽京东后,分享给我一份JAVA核心开发手册(架构筑基+开源框架+分布式架构+微服务架构+性能调优)

Java成神之路

Java 程序员 架构 面试 编程语言

技术为帆,纵横四海- Lazada技术东南亚探索和成长之旅

技术为帆,纵横四海- Lazada技术东南亚探索和成长之旅

美图高性能twemproxy的改造之路-InfoQ