LinkedIn如何通过合并50万QPS身份服务将延迟降低10%

在LinkedIn , 为用户资料和会员设置提供数据的身份服务是一个非常关键的系统 。 在本文中 , 我们将分享我们如何通过合并身份服务(每秒处理超过50万个查询请求)将延迟减少了10% , 并极大降低了年度服务成本 。 内容将涉及我们所使用的基于数据驱动的架构、用到的工具以及从旧架构总结出的经验教训 。
1背景
LinkedIn的会员系统采用了面向服务架构 , 以此来提供各种不同的体验 。 服务隐藏了内部领域模型的复杂性 , 并通过定义良好的服务API把功能暴露出来 。 这种抽象保证了系统的可演化性和可组合性 。
下图是合并之前的身份服务的总体架构图 , 包括服务、客户端和下游服务 。 客户端调用中间层服务获取资料和设置信息 , 中间层服务依赖数据服务 , 数据服务对Espresso(LinkedIn的分布式NoSQL数据库)数据存储执行CRUD操作 。 数据服务只实现了有限的逻辑 , 比如数据验证(例如数据类型验证、字符串长度验证等) 。 中间层服务还调用了其他服务 , 这些服务由LinkedIn的其他团队负责开发 , 提供了重要的领域数据 。 这些下游服务提供了垃圾信息过滤和阻塞、会员邀请、会员连接等功能 。 中间层服务基于这些信息实现业务逻辑 , 确保用户可以自由设置自己的资料以及与LinkedIn和其他第三方应用程序交互 。
LinkedIn如何通过合并50万QPS身份服务将延迟降低10%
文章图片
【LinkedIn如何通过合并50万QPS身份服务将延迟降低10%】2动机
随着应用程序规模的增长和系统功能不断增加 , 开发团队开始把关注点放在了性能、服务成本和运维开销上 。 另外 , 我们也开始重新评估和思考之前的一些设计和开发方式 。
我们发现 , 继续让数据服务和中间层服务独立运行存在一些问题:
将数据服务和中间层服务分离 , 这样的设计可能没有当初想象得那么有价值 。 我们发现 , 大部分伸缩性方面的问题都可以在存储层解决 , 也就是在Espresso数据存储端 。 况且 , 对Espresso读写操作实际上都是来自数据服务 。
将数据服务作为单独的服务增加了运维开销和代码复杂性 。 为此 , 我们分配了1000个应用程序实例 。 另外 , 我们需要单独为中间层提供API , 涉及数据建模、API演化和安全等方面的工作 。
数据服务里只有很少的业务逻辑 , 大部分都与数据验证有关 。
对于客户端来说 , 中间层服务和数据服务的分离增加了网络跳数 。
基于以上这些考虑 , 我们打算在保持API不变的情况下把中间层服务和数据服务合并成一个服务 。 从面相服务架构的角度来看 , 这样做有点违反直觉 , 因为面相服务架构的意义在于将大系统拆分成小系统 , 以此来解决复杂性问题 。 不过 , 我们相信可以找到一个平衡点 , 合并服务从性能、服务成本和运维开销方面得到的好处比合并服务带来的复杂性要大得多 。
3实现
得益于微服务架构 , 我们可以在不影响客户端的情况下把两个服务合并成一个 。 我们保持中间层接口不变 , 把数据服务的代码合并到中间层 , 让中间层直接操作数据存储层 。 我们的一个重要的目标是尽量让新旧架构的功能和性能保持不变 。 另外 , 我们也要注意在合并两个重要系统时可能会遇到的风险 , 并最小化合并的开发成本 。
我们分四步实现服务的合并 。
第一步:我们有两种方式来合并代码库 。 最直接的方式是把数据服务的代码拷贝到中间层 , 这样就可以执行数据验证和调用数据存储 。 不过 , 虽然这种方式最为直接 , 但在确定可行之前需要做很多前期的开发工作 。 于是 , 我们选择了另外一种“取巧”的方式 , 我们直接将数据服务的RESTAPI作为中间层的一个本地库 。
第二步:我们逐步执行在第一步中定下的方案 。 LinkedIn有一个非常厉害的AB测试框架 , 叫作T-REX , 我们用它生成统计报告 , 基于风险等级和变更影响范围来安排进度 。 我们可以边观察边做出修改 , 在必要的情况下可以进行快速回滚(在几分钟内) 。 因为我们合并的是两个非常关键的系统 , 风险较高 , 影响较大 , 所以在安排进度时也非常谨慎 。 我们一个数据中心接一个数据中心地迁移 , 在每一个数据中心里也是先从小比例开始 , 再逐渐加大 , 确保有足够的时间生成统计报告 。
https://engineering.linkedin.com/teams/data/analytics-platform-apps/data-applications/t-rex
第三步:下线数据服务的主机 。
第四步:因为第一步的方案走了捷径 , 直接将RESTAPI作为本地库调用 , 所以现在需要清理这些代码 。 在LinkedIn , 工匠精神是我们文化的一个重要组成部分 。 我们把暴露REST服务需要的类和接口移除掉 , 只保留访问数据存储需要的类 。
下图显示了架构变更前后的区别 。
LinkedIn如何通过合并50万QPS身份服务将延迟降低10%
文章图片
4性能分析
为了比较合并前和合并后的性能 , 我们采用了一种叫作“DarkCanary”的机制 。 我们以一种可控的方式把真实的生产环境的只读流量导给测试主机 。 例如 , 我们可以把一台生产主机的流量加倍并导给一台测试主机 , 这些是在不影响生产主机的情况下进行的 。 也就是说 , 我们可以在不影响业务的情况下使用生产流量进行性能测试 。 下面是我们的DarkCanary架构 。
LinkedIn如何通过合并50万QPS身份服务将延迟降低10%
文章图片
下面的两张图显示了常规生产流量和测试流量的p90延迟区别 。 p90延迟平均从26.67毫秒下降到24.84毫秒(6.9%的下降幅度) 。
LinkedIn如何通过合并50万QPS身份服务将延迟降低10%
文章图片
通常 , 因为影响因素太多 , p99指标是很难提升的 。 不过 , 我们确实做到了 。 总体来说 , 合并后的服务分别将p50、p90、p99提升了14%、6.9%和9.6% 。
5内存分配
为了了解性能的特征 , 我们基于GC日志分析了内存分配情况 。 这些日志来自三种主机:中间层服务所在的测试主机、中间层所在的生产主机和数据服务主机 。 GC日志提供了非常有价值的有关对象分配模式的信息 , 这些信息通常可以说明应用程序的性能情况以及它们是如何使用内存的 。 下图显示了中间层所在的生产主机的内存分配情况 , 平均内存分配率是每秒350MB 。
LinkedIn如何通过合并50万QPS身份服务将延迟降低10%
文章图片
在合并之后 , 中间层服务的内存分配率比之前每秒减少了100MB左右(28.6%) 。 这不仅改进了性能 , 也降低了服务成本 。
6服务成本
服务成本是业务决策的一个参考因素 。 在LinkedIn , 我们使用了一种内部框架 , 基于硬件和运维成本来计算服务成本 。 我们的团队也使用这个框架对合并后的服务进行了分析统计 , 在将数据服务所在的主机移除之后 , 在物理资源方面节省了超过12000个核心和13000GB的内存 , 相当于每年节省了相当大一笔费用 。


    推荐阅读