美图每天亿级消息存储演进——从Redis到Titan,完美解决扩容问题
mhr18 2024-10-22 12:34 19 浏览 0 评论
作者简介:王鸿佳,系统研发工程师 ,现任职于美图公司,主要负责通用长连接服务、美图推送系统基础服务研发。对分布式研发技术及开源项目有浓厚的兴趣,DistributedIO 团队核心成员。
导读
美图公司拥有众多的产品以及海量活跃用户,我们的日推消息也达到了上亿次,这对消息推送也提出了较高的要求。2017 年,美图自研推送服务,采用 Redis 作为消息存储,但随着业务接入量增加、数据量快速增加,服务的可维护性变得困难。公司已经搭建了一套新型的数据库 - Titan (已经开源),底层以 PingCAP 的 TIKV 做数据引擎,上层实现 Redis 协议解析,既可以水平弹性扩容,适合海量数据存储管理,性能又可以随水平扩容而提高。
在本文中,先简要的介绍推送现状及难题,后详细的说明Titan 在全面替换原有的存储的过程、以及在接入过程中遇到的问题和解决方案。
推送现状
2017 年初,美图自研推送服务(Thor),完成 APP 的定向推送、批量推送、离线推送、消息过期、 token 管理等功能。至今已经接入美图全部的 App 中,在线到达率为 99% 以上,消息秒级触达用户,日常服务端消息存储量约为 700G ,最高峰为 1T 。
随着服务接入量级的提升系统的架构模型,存储模型也不断在演进,下面将讲述推送系统目前的架构模型和存储模型以及当前推送服务面临的难题问题。
架构模型
推送服务整体拆分为三个部分:长链接服务(bifrost)、推送服务(thor)、路由分发(route_server)。服务之间串联通过发现服务(etcd)实现。长链接服务负责保持客户端和服务端的链路通畅。推送服务负责管理客户端的 token 信息和存储管理消息。
图一:美图推送整体架构图
存储模型
消息存储管理作为推送核心模块,构建一个恰到好处的模型至关重要。在推送业务中消息的操作可以划分为两个维度:
站在客户端维度来看,需要保证精准的接受消息和上报回执信息。
站在系统维度看,推送系统应该具备消息灵活管理功能,保证在系统异常崩溃、网络隔离等异常场景下消息不丢不乱能力。
综上所述,美图推送将数据模型抽象图如下,每个客户端(cid)拥有唯一的消息下发队列,每个消息会被绑定唯一的标识(mid),服务端通过指定 cid 下发消息,消息前先写入存储后进行消息投递,监测到客户端登陆后服务端查询离线消息重新下发,降低了消息在异常场景损失的概率。通过上报回执消息中的 mid 清除已下发成功消息,保证了队列的消息的有效性。
图二:数据模型图
当下难题
在开发初期选择消息存储的时候,一方面考虑到在推送业务场景中消息留存时间在可控的时间内且数据量较少,Redis 自身支持丰富的数据结构,提供高速的访问能力。另外一方面公司内部对 Redis 使用和管理有丰富的经验。因此选择 Redis 做消息存储,部署方式则采用一主两从客户端做分片写入的集群模式。
随着业务接入量的增加,数据量越来越大,服务的维护性越来越难。其中主要难题如下:
? 频繁的消息过期和删除导致Redis 的内存碎片比居高不下;
? 单节点数据量增大,持久化耗时加剧,导致服务抖动,短时间不可用;
? 存储扩容导致服务有损;
? 服务成本越来越高。
以上困难,只有替换存储才可以从根本解决这些问题。我们需要选择一种适合海量数据存储、水平弹性扩容、对业务迁移友好的存储。Titan 便是我们的不二人选,接下来要和大家简要介绍下 Titan 。
Titan
Titan 是美图公司研发并开源的 NoSQL,目前交给第三方DistributedIO 组织维护。DistributedIO 组织由源 Titan 开发团队发起构建。主要适合要求具备分布式事务的大规模数据存储,适用海量数据,少量事务冲突的场景。Titan 划分为两层:下层使用 PingCAP 的 TiKV 数据库做数据持久化; 上层通过解析 Redis 协议将各类数据结构转化为 KV 数据方式存储。
TIKV 是 PingCAP 开源的分布式 Key-Value 存储,它使用 Rust 开发,采用 Raft 一致性协议保证数据的强一致性,以及稳定性,同时通过 Raft 的 Configuration Change 机制实现了系统的可扩展性,提供了支持 ACID 事务的 Transaction API。虽然 PingCAP 也开源一个名字叫的 Titan 的存储引擎,但与本文介绍的 Titan 是不同的,只不过名字一样而已。
Titan 自身是无状态服务,提供 Redis 命令翻译执行功能。在设计上支持在共享一套集群情况下业务数据隔离,遵从 Redis 5.0 开发实现,自身集成 prometheus 监控。 目前 Ttian 已经支持 lists ,strings ,hashes ,sets ,sorted sets 等基础数据结构。自从开源以来,收到了广泛的关注,目前已经收到 700 多的 star 和 50 多次的 fork,在业内存在成功接入并投入线上使用的公司案例,如北京转转精神科技有限公司(转转)已经迁移 800G 的数据到 Titan中。
存储平滑迁移
现在大家对Titan 有了基本的了解,但存储的替换不是一蹴而就的事情。Titan集群的大小,存储迁移平滑过度,数据的迁移,这都是我们的绊脚石。下面将从业务角度出发给出答案。
业务评估及优化
推送服务经常面临高频的消息读写场景,因此对存储的性能要求也是极高的,我们首先要做的就是对Titan 进行压测。推送服务依赖 Redis 的 Hash 数据结构完成对消息的管理,使用的命令有 hset ,hgetall ,hdel。压测 Titan 在 1 台 sas 盘 CPU 40核 内存 96 G 机器和 3 台 ssd 盘 CPU 40 核 内存 96 G 机器上,部署采用了 1 个 Titan 12 个TiKV 实例方式。通过整理压测数据和统计线上监控给出对每个命令的期待的 QPS 如下表。
hset | hgetall | hdel | |
压测数据 | 31 k/s | 13k/s | 47k |
实际需求 | 150k/s | 20k/s | 30k/s |
表一:业务评估表
通过上图对比分析发现,Titan 按照这个集群配置hset 和 hgetall 两个命令明显不满足如线上要求。可以通过扩容 Titan 集群的方式提高系统吞吐,但初于对成本的考虑,我们从业务角度尝试优化。
hset
在Redis 中消息的保存使用 hset 命令根据客户端 cid 进行 hash 写到固定的 Redis 存储中,需要逐条写入。在 Titan 中我们尝试将 hset 命令从单条的执行改为 batch 操作,经过测试确立方案为每 100 条命令执行一次事务提交, 虽然延迟从 10 ms 增到到 100 ms 以内波动,但优化后性能要求从 150 k/s 将为 20 k/s 且延迟在可以接受的范围内。
hgetall
在Redis 中客户端登陆可以通过 hgetall 获取当前所有离线消息重新接收,在调研发现客户端存在少量离线消息或者不存在离线消息。针对这种情况重新对 hgetall 进行压测,hgetall 在这种场景下可以提供 25 k/s QPS 满足需求。
在经过优化之后,Titan 可以满足目前推送线上的基本要求。按照上述配置决定将所有消息存储逐步灰度到 Titan上。
平滑替换
消息存储作为美图推送核心单元,要保证7x 24小时可用,在迁移过程中,如何在Titan 异常情况下保证服务正常访问,旧数据如何业务无损的同步到新集群是我们面临的两大难题。
在推送业务中消息生命周期都在一周之内,如果采用双写模式,将数据顺序写入Redis ,Titan两个集群,读取只在 Redis 集群上。一周后在将读切到 Titan 上,在稳定运行一段时间后,下掉 Redis 集群完成存储集群迁移。其中 Titan 集群在任何时间下发生异常都可以下掉,操作切回 Redis 集群。
问题和解决方法
在美图推送服务接入Titan 的过程中,我们团队也遇到了不少的问题,比如事务冲突,短时间内 Titan 堆积大量命令,导致雪崩效应等问题。我们在具体实践中也总结了一些解决方法。
事务冲突
现象:在推送的高峰期期间事务冲突频繁,Titan 节点内存升高,TiKV 机器内存耗尽节点OOM。
原因:Titan 中对相同的 key 写入和删除并发操作会导致事务冲突,消息的写入采用 batch 方式写入,一旦发生事务冲突,这批数据会集体产生回滚、重试操作,短时间内Titan 会积压大量命令导致内存上升,TiKV 操作事务回滚导致内存耗尽 节点OOM。
解决方式:舍弃meta中数据数量记录字段,减少单个key 的操作冲突。通过这种方式仅仅降低了单个 key 的冲突,在 hash 操作中针对单个 felid 的修改仍然存在冲突。但此种优化已经到达业务接受水平。
TiKV OOM
原因:线上采用内存96G 机器部署 4 个 TiKV 模式,在高峰期队列请求处理队列积压,导致机器内存耗尽。
解决方式:减少TiKV 配置中 block-cache-size 的大小,降低内存占用。
Raft Store CPU 使用率过高
现象:redis 命令执行存在卡顿,TiKV 监控中 Raft Store CPU 使用率超过 90%。
原因:在TiKV 中 raftstore 是单线程工作。
解决方案:集群扩容,增加一个服务器,增加一个4个 TiKV 节点,提高 Titan 服务的处理能力,从根本上解决了问题。
TiKV channel full
现象:Redis 命令执行超时。
原因:在数据短时间持续大量写入,导致Raft 热点,直接导致TiKV 的 Region leader 迁移。
解决方式:通过配置调整TiKV 的 scheduler-notify-capacity 大小,增加scheduler 一次获取最大消息个数,降低了问题发生的频率。
总结
在经历半年的尝试后,推送存储整体替换为Titan,存储也由16台 Redis 机器切换为 4 台的 ssd TiKV 专属服务器和 2 台混部 Titan 服务器上,成本节约60%,可维护性大大提高,现在推送服务已经稳定跑了半年,期间未发生故障。随着 Titan 的数据结构的完善,未来准备推进更多业务接入。
感谢张同学(nioshield@gmail.com)及时实现的 hashes 数据结构。
感谢 PingCAP 公司在迁移过程中对于TiKV 的技术支持。
Redis 的压测工具:https://github.com/fperf/redis
Titan 项目地址:https://github.com/distributedio/titan
TiKV 项目地址:https://github.com/tikv
本文由高可用架构约稿。技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。
高可用架构
改变互联网的构建方式
相关推荐
- Redis合集-使用benchmark性能测试
-
采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...
- Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好
-
最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...
- redission YYDS(redission官网)
-
每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...
- 从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道
-
2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...
- SpringBoot系列——实战11:接口幂等性的形而上思...
-
欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...
- 如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享
-
如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...
- Java面试题: 项目开发中的有哪些成长?该如何回答
-
在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...
- 互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?
-
你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...
- 每日一题 |10W QPS高并发限流方案设计(含真实代码)
-
面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...
- Java面试题:服务雪崩如何解决?90%人栽了
-
服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...
- 面试题官:高并发经验有吗,并发量多少,如何回复?
-
一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...
- 瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪
-
“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...
- 其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。
-
其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...
- PHP技能评测(php等级考试)
-
公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...
- 你的简历在HR眼里是青铜还是王者?
-
你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)