创业公司需要基础架构团队吗?[极牛编辑修改版]

关于「真格 · 极牛技术分享」

「真格 · 极牛技术分享」- 极牛为真格基金投资公司打造的定期技术分享交流活动,采用“微信群分享 + 线下沙龙”的方式,分享和讨论新技术优秀应用实践、知名创业项目架构分析、技术工具评测和分析等技术话题。极牛愿与真格基金投资公司一起努力,共同提高中国创业技术含金量,打造一流技术能力。

关于唐福林

雪球首席架构师,前微博技术委员会成员,微博平台架构师;Java 后端程序员,已经不写 PHP,C 和 Pascal 很多年;ACM/ICPC 和 NIO 比赛深度参与者。

技术交流欢迎在微博上关注我 @唐福林

炒股,理财,资产管理,量化交易相关,欢迎在雪球关注我 @唐福林 xueqiu.com/fulin


近期,在极牛举办的「真格 · 极牛技术分享」上,唐福林集中分享了在微博和雪球做架构师和带架构团队的经历,并且对创业公司是否需要基础架构团队这个问题给出了深度解答。以下是唐福林分享文字内容。

1. 基础架构团队到底做些什么

要解答创业公司到底需不需要基础架构团队这个问题,首先我们来来聊一聊基础架构团队的职能是什么。

基础架构团队到底做些什么?

  • 为实现业务功能:技术选型,决策
  • 为了更好的实现业务功能:引入新技术/做法,提供内部支持
  • 解决历史遗留的技术债务:发现问题,找出解决方案,并推动解决
  • 业务开发 与 线上运维 中间有很宽的“三不管”地带需要填充,还需要坚实的基础设施支持

架构组工作的特点有哪些?

  •  基础架构团队存在的价值是解决非业务逻辑的技术相关问题,没有产品或运营驱动
  • 从技术角度看,每个问题都有很多种解决办法
  • 这些问题一般都是重要不紧急,短期内不解决也不会崩
  • 解决这些问题带来的价值对于非技术人员(特别是老板)不是很直观

 

2. 基础架构团队是创业公司技术发展的必然产物

在“社会主义初级阶段”,还不需要独立的基础架构组。比如,一开始,1~3 人小团队team leader 自己做决策,出问题 team leader 解决,用啥语言?cache?db?上不上云?这些问题直接导致了团队后续技术力量的升级。

到了Demo阶段,也就是3~5 人小团队,这时候应该有个架构师了,技术问题就会留给架构师解决,比如用啥框架?第三方库怎么选择?

后期发展到上线阶段,大概是5~10 人小团队,可以分组了,每个组都要有一个架构师,解决的问题就变成相互之间怎么配合?怎么保持公共基础部分的一致性?怎么解决互相依赖问题?

做好了前期的准备工作,到了公司的发展阶段,技术团队变成10~30 人,就应该有专人负责基础架构了,主要负责业务无关的技术基础设施维护,服务化框架以及治理,上线的流程等一系列问题。

如果业务到了爆发阶段,有了30~100 人的团队,就应该设置专门的基础架构团队了,开始负责技术基础设施及服务,提供内部的 PaaS,或者 SaaS等。

最后到了100+人的“高级阶段”–平台阶段,应该设置平台团队了,将公司的主体核心业务功能做成服务,供商业化或创新业务使用。在平台内部设置架构组,提供高附加值的技术支撑,异地多机房部署,大规模虚拟化平台这些宏观上的部署和组建就要开始认真思索了。

 

3,基础架构能做的再好一点吗?

 

2012年,我在微博的个人的职责从工程师转变成了架构组的组长,最重要的职责从自己写代码,转变成了“如何创造条件让团队成员更好的写代码”。这一年,微博平台壮大,职责划分逐渐明晰,我开始真正从管理领导层面思考怎么才能让基础架构做的更好一点。

首先是怎么做的问题,最重要的要求是要主动多想,没有外力驱动的时候要自我驱动,提高主动性,寻求目标,进行自我评价,没有外力驱动,没人外人评价,我们很容易陷入一种自我满足的困境:看,我做好了,我做的很牛,这回肯定可以评个优,年底多发奖金了吧。

而实际上,大部分时候,我们做的都还不够好。这里的好有两层意思:从技术本身的角度评价,跟周边公司横向比较等等;另外,就是你做的东西有没有真正的完全解决问题,不是从做的人的角度看,而是从碰到问题需要解决的人的角度看,比如业务开发团队是不是觉得好用,是不是觉得这正是他们想要的?

其次在做事的态度上,要力往一处使,因为解决一个技术问题可能有很多种办法,基础架构团队的职责就是找出一种办法,并推行到所有适合的场合,在这个取其精华去其糟粕的过程中一定要见识一起改进,禁止另起炉灶。

做任何事都要讲求方式,做基础架构的方式就是用数据说话,找到合适的评价数据指标,比如写了一个牛B的rpc框架?到底好不好,好在哪里?

另外很重要的是对于选人的要求,要能耐得住寂寞,尤其是在业务爆发式增长时,基础架构团队更需要耐得住寂寞。组建一支牛B的基础架构团队,让业务开发人员跳槽到另外一家公司以后,还会想起你们的好。所有从 Google 出来的人,都会怀念 G 家的内部基础设施。

对创业公司来说,基础架构极其重要,尤其是小的创业团队应尽早安排一个“架构师”的职位,招聘或内部培养一个架构师,如果发现一个架构师忙不过来,那就是时候扩大成一个基础架构团队了。

漫谈基础架构团队的价值 [分享稿文字版]

漫谈基础架构团队的价值
@唐福林 weibo.com/tangfl
陆续在微博和雪球做了几年的架构师,也带了几次基础架构团队,因而在多个场合被问到类似创业公司需要基础架构团队吗”“基础架构团队怎么招聘,如何管理一类的问题。于是前些天在极牛举办的「真格 · 极牛技术分享」上集中分享了我做架构师和带架构团队的经历,以及我个人对架构团队的价值的理解和思考。现将分享的内容整理成文如下,请大家指正。
  • 关于我
     首先简单介绍一下我自己:
    • 雪球首席架构师
    • 前微博技术委员会成员,微博平台架构师
    • Java 后端程序员,已经不写 PHPC  Pascal 很多年
    • ACM/ICPC  NIO 比赛深度参与者
    • 技术交流欢迎在微博上关注我 @唐福林 weibo.com/tangfl
    • 炒股,理财,资产管理,量化交易相关,欢迎在雪球上关注我 @唐福林 xueqiu.com/fulin
  • 微博平台架构组经历分享
    • 2010年底加入微博平台部
      • 当时名称为微博开放平台
      • t.sina.com.cn 为两套独立体系

Description: weibo.png

     左边是当时的 t.sina.com.cn,内部一般称为主站PHP 写的。右边是我们的基于 Java  API 体系,当时称为 OpenApi,因为我们当时主要面向外面的第三方开发者,为他们提供 open apiOpenApi 主站之间通过 Queue 互相同步数据。微博当时选择的是内部开发的 MemcacheQ,简称 MCQ 。这种模式下最大的好处是互不影响,最大的问题呢,就是数据不一致,甚至业务逻辑不一致。
     当时我个人的职责是重构和维护微博短链接服务t.cn 。事实上,我最开始接手项目的时候,还是 sinaurl.cn,是用 PHP 写的,我拿过来用 Java 重写了一遍,正准备上线,老板说要把域名换成 t.cn,然后我就一通狂改代码。当时平台内部容器都是用 Tomcat 6,短链项目是第一个尝试用 Jetty,但后来失败了又换回 Tomcat 了。
     这一年最大的收获是:学会了怎么写好的业务实现代码。
    • 2011年微博平台化
      • 统一一个平台底层,为上层 OpenApiWeb主站和无线App端提供 api
      • 平台负责稳定性,性能和扩展性

Description: weibo2.png

     当时我的个人职责是负责引入Redis来实现计数器和关系缓存功能。微博大约是国内第一家大规模应用Redis 的公司了吧,到2011年底的时候,我们已经部署了超过100台服务器,总计超过8T的内存。我们有自己修改过的RedisCounter专门用来高效的存储计数(用户的关注数粉丝数,每条微博的转发数,评论数,赞数等等)。我在Jedis client 的基础上包装了内部使用的客户端,增加了大量的异常处理,HAFail Fast等功能,还开发了一个支持滚动删除旧数据的 Redis 集群功能。
     这一年最大的收获是:学会了怎么设计并实现一个大的模块功能,做到高可用,以及如何应付高并发流量。
     PS,这也是我现在给创业公司推荐的架构,后端HttpHttps Rest Api,优先推荐Java技术栈实现,Cache 推荐使用RedisDB推荐使用Mysql,前端WebAndroid iOS、微信公众号等等都独立实现,或者共享某些 H5 页面。如下图所示

Description: commonArch.png

 
    • 2012年,平台壮大,职责划分
      • 用户关系
      • 内容
      • 私信IM
      • 公共:通知,提醒,导航 etc
      • 架构
     当时我个人的职责从工程师转变成了架构组的组长,最重要的职责从自己写代码,转变成了如何创造条件让团队成员更好的写代码。那一年微博平台架构团队最重要的几个产出包括:
      • 新版聚合框架 poly
      • redis 二次开发
      • 多机房同步队列 weibus
      • 数据管道firehose
      这一年,我最大的收获是,在摸索中逐渐理解了基础架构团队应该做什么,应该怎么做,并且逐渐的学习如何带基础架构团队,如何为团队中的其它人创造更好的条件,帮助他们写更好的代码。
    • 2013年,微博高可用改进
      • 压测平台 touchstone
      • 平台SLA体系
      • 平台多机房隔离部署
    • 2013年,微博平台服务化
      • RPC 框架 motan
      • 服务化治理体系:config servicetrace 系统,monitorCI & CD
     2013年因为人员变动,我从架构组组长空降到用户关系组任组长。虽然换到了业务组,但还是主导了平台的多个重大技术改造项目,比如压测平台touchstonerpc框架motan,以及motan上线后的服务化治理体系。
     这一年,我最大的收获是,从业务组的角度观察架构团队,更清晰的感受到了架构组的作用和价值,也更深的理解了架构组如何做项目,如何将项目成功落地推广。
    • 2014年,微博上市
      • 商业化:各种商业化需求支持
      • 成本缩减
      • feed流性能优化
     2014年主要的技术工作是feed流性能优化,和成本缩减。在feed流性能优化项目中,我负责探索从 App 客户端到 PHP 再到 Java Api 再到 Cache 最后到 DB 的全链路耗时监控统计;而成本缩减项目最后演变成了 Docker 应用预研。后来我离职后,Docker 的应用转交给用户关系组的接任组长继续进行了。
     这一年,我最大的收获是,学会了从更高层(部门,公司,甚至行业)的角度去理解基础架构团队的工作,去决策做什么,以及更重要的:不做什么。
  • 雪球平台组经历分享
    • 2015年,雪球首席架构师
      • 上半年牛市:性能优化,可用性改进
      • 下半年熊市:大数据平台,推荐,反垃圾,流式计算平台
      • 对外分享,招聘面试
      • 对内建立技术规范,上线流程
     在2015年底,雪球基础架构组升级成了平台组。当前负责维护雪球内部多个重要服务,包括用户关系服务,IM服务,搜索服务,大数据平台,推荐服务,反垃圾服务,以及开发流程和代码质量改进等等。
     这一年,我最大的收获是理解了基础架构团队在不同的公司中,以及公司的不同发展阶段的差异,包括价值观,做事方式,评价方式等等。
  • 基础架构团队的价值与思考
首先来聊一聊基础架构团队应该做什么
    • 做什么
      • 为实现业务功能:技术选型,决策
      • 为了更好的实现业务功能:引入新技术/做法,提供内部支持
      • 解决历史遗留的技术债务:发现问题,找出解决方案,并推动解决
      • 业务开发  线上运维 中间有很宽的三不管地带需要填充,还需要坚实的基础设施支持
从团队发展历程的角度看
    • 一开始,1~3 人小团队
      • team leader 自己做决策
      • 出问题 team leader 解决
      • 用啥语言?cachedb?上不上云?
    • Demo阶段,3~5 人小团队
      • 应该有个架构师了
      • 技术问题架构师解决
      • 用啥框架?第三方库选择?
    • 上线阶段,5~10 人小团队
      • 应该分组了
      • 每个组有一个架构师
      • 怎么配合?怎么保持公共基础部分的一致性?怎么解决互相依赖问题?
    • 发展阶段,10~30 人团队
      • 应该有专人负责基础架构了
      • 业务无关的技术基础设施维护
      • 偏技术的
        • 性能,高可用等等
        • docker
        • HadoopELK
      • 偏工程的
        • 重构
        • 服务化框架,治理
        • 公共第三方依赖管理,升级:jvmtomcatlog4j
      • 偏运维的
        • trace 系统
        • monitor
        • alert
        • 线上开关系统
      • 偏流程的
        • 开发流程规范
        • 代码质量保证:UTcode review
        • 上线流程:CI & CD
    • 爆发阶段,30~100 人团队
      • 应该设置基础架构团队了
      • 技术基础设施即服务
      • 提供内部的 PaaS,或者 SaaS
        • DB / Cache / Queue Service
        • 虚拟化平台
        • 大数据平台
        • 流式计算平台
    • 平台阶段,100+ 人团队
      • 应该设置平台团队了
      • 平台即服务
      • 将公司的主体核心业务功能做成服务,供商业化或创新业务使用
        • 用户服务
        • 帖子服务
        • 商品服务
        • 评论服务
      • 在平台内部设置架构组
      • 提供高附加值的技术支撑
        • 异地多机房部署
        • 大规模虚拟化平台
    
     再往后?我也不知道,大概是设立研究院之类的吧
    • 架构组工作的特点
      • 基础架构团队存在的价值是解决非业务逻辑的技术相关问题,没有产品或运营驱动
      • 从技术角度看,每个问题都有很多种解决办法 
      • 这些问题一般都是重要不紧急,短期内不解决也不会崩
      • 解决这些问题带来的价值对于非技术人员(特别是老板)不是很直观
    • 怎么做
      • 最重要的要求:主动,多想
        • 没有外力驱动:自我驱动
        • 没有产品KPI:自己寻找目标
        • 没有外人评价:自己评价
        • 有哪些要做的,哪些应该先做,哪些应该延后做
        • 最后一公里:我能做的再好一点吗
     没有外力驱动,没人外人评价,我们很容易陷入一种自我满足的困境:看,我做好了,我做的很牛,这回肯定可以评个优,年底多发奖金了吧。而实际上,大部分时候,我们做的都还不够好。这里的好有两层意思:从技术本身的角度评价,跟周边公司横向比较等等;另外,就是你做的东西有没有真正的完全解决问题,不是从做的人的角度看,而是从碰到问题需要解决的人的角度看,比如业务开发团队是不是觉得好用,是不是觉得这正是他们想要的?

 

      • 做事的态度:力往一处使
        • 解决一个技术问题有很多种办法
        • 基础架构团队的职责就是找出一种办法,并推行到所有适合的场合
        • 觉得不满或有意见?一起改进,禁止另起炉灶
      • 做事的方式:数据说话
        • 找到合适的评价数据指标
        • 写了一个牛Brpc框架?到底好不好,好在哪里?
        • 服务质量数据指标三板斧
        • qpsp99响应时间,error rate
      • 选人的要求:耐得住寂寞
        • 业务爆发式增长时,基础架构团队需要耐得住寂寞
        • 业务不增长时,基础架构团队更需要耐得住寂寞
    • B的基础架构团队
      • 让业务开发人员跳槽到另外一家公司以后,才想起你们的好
      • 举例:所有从 Google 出来的人,都会怀念 G 家的内部基础设施
  • 简单总结
    
     简单总结一下,结论就是
    • 基础架构很重要,应尽早安排一个“架构师”的职位,招聘或内部培养一个架构师;如果发现一个架构师忙不过来,那就应该扩大成一个基础架构团队了
    • 架构师自己要有想法,知道要做什么,知道先做什么,知道要做成什么样
    • 架构师自己要动手写代码
    • 架构团队不能离业务太远,要适当负责一些底层业务
    • 用数据说话,不仅架构团队,整个技术团队都应该这样
 
啰嗦了很多,回过头一看,自己感觉还是有些凌乱,大概是自己也还有很多没有想清楚的地方吧。欢迎大家就这个话题一起来讨论。

雪球在股市风暴下的高可用架构改造分享

本文根据唐福林老师在“高可用架构”微信群所做的《股市风暴下的雪球架构改造经验分享》整理而成,转发请注明来自微信公众号ArchNotes。

 

唐福林,雪球首席架构师,负责雪球业务快速增长应对及服务性能与稳定架构优化工作。毕业于北京师范大学,之前曾任微博平台资深架构师,微博技术委员会成员。长期关注并从事互联网服务后端性能及稳定性架构优化工作。

 

分享主题


一. 雪球公司介绍

二. 雪球当前总体架构

三. 雪球架构优化历程

四. 聊聊关于架构优化的一些总结和感想

 



一. 雪球公司介绍

 

雪球 聪明的投资者都在这里。

 

  • web 1.0:新闻资讯,股价信息,K线图
  • web 2.0:SNS 订阅,分享,聊天
  • web 3.0:移动 APP,交易闭环

 

雪球现在员工数还不到100,其中技术人员占一半。去年9月C轮融资4kw刀。我们现在的技术栈由下列组件组成:Java,Scala,Akka,Finagle,Nodejs,Docker ,Hadoop。我们当前是租用IDC机房自建私有云,正在往“公私混合云”方向发展。

 

在雪球上,用户可以获取沪深港美2w+股票的新闻信息,股价变化情况,也可以获取债券,期货,基金,比特币,信托,理财,私募等等理财产品的各类信息,也可以关注雪球用户建立的百万组合,订阅它们的实时调仓信息,还可以关注雪球大V。雪球当前有百万日活跃用户,每天有4亿的API调用。App Store 财务免费榜第 18 名。历史上曾排到财务第二,总免费榜第 19。

 

二. 雪球当前总体架构

 

作为一个典型的移动互联网创业公司,雪球的总体架构也是非常典型的设计:

 

  • 最上层是三个端:web端,android端和iOS端。流量比例大约为 2:4:4 。web3.0 的交易功能,在 web 端并不提供。

     

  • 接入层以及下面的几个层,都在我们的自建机房内部。雪球当前只部署了一个机房,还属于单机房时代。正在进行“私有云+公有云混合部署”方案推进过程中。
  • 我们当前使用 nodejs 作为 web 端模板引擎。nodejs 模块与android 和 ios 的 app 模块一起属于大前端团队负责。
  • 再往下是位于 nginx 后面的 api 模块。跟 linkedin 的 leo 和微博的 v4 一样,雪球也有一个遗留的大一统系统,名字就叫 snowball 。最初,所有的逻辑都在 snowball 中实现的。后来慢慢的拆出去了很多 rpc 服务,再后来慢慢的拆出去了一些 http api 做成了独立业务,但即便如此,snowball 仍然是雪球系统中最大的一个部署单元。
  • 在需要性能的地方,我们使用 netty 搭建了一些独立的接口,比如 quoto server,是用来提供开盘期间每秒一次的股价查询服务,单机 qps 5w+,这个一会再细说;而 IM 服务,起初设计里是用来提供聊天服务,而现在,它最大的用途是提供一个可靠的 push 通道,提供 5w/s 的消息下发容量,这个也一会再细说。
  • 雪球的服务化拆分及治理采用 twitter 开源的 finagle rpc 框架,并在上面进行了一些二次开发和定制。定制的功能主要集中在 access log 增强,和 fail fast,fail over 策略及降级开关等。 finagle 的实现比较复杂,debug 和二次开发的门槛较高,团队内部对此也进行了一些讨论。
  • 雪球的业务比较复杂,在服务层中,大致可以分为几类:第一类是web1.0,2.0 及基础服务,我们称为社区,包括用户,帖子,新闻,股价,搜索等等,类比对象就是新浪财经门户+微博;第二类是组合及推荐,主要提供股票投资策略的展示和建议,类比对象是美国的motif;第三类是通道,类似股市中的“支付宝”,接入多家券商,提供瞬间开户,一键下单等等各种方便操作的功能。
  • 雪球的业务实现中,包含很多异步计算逻辑,比如搜索建索引,比如股票涨跌停发通知,比如组合收益计算等等,为此,我们设计了一个独立的 Thread/Task 模块,方便管理所有的后台计算任务。但随着这些 task 越来越多,逻辑差异越来越大,一个统一的模块并不是总是最佳的方案,所以,我们又把它拆成了两大类:流式的,和批量式的。
  • 雪球的推荐体系包括组合推荐“买什么”和个性化推荐。我们最近正在重新梳理我们的大数据体系,这个感兴趣的话可以单聊。
  • 最下面是基础设施层。雪球基础设施层包括:redis,mysql,mq,zk,hdfs,以及容器 docker。
  • 线上服务之外,我们的开发及后台设施也很典型:gitlab开发,jenkins打包,zabbix 监控系统向 openfalcon 迁移,redimine向confluence迁移,jira,以及内部开发的 skiing 后台管理系统。

 


三. 雪球架构优化历程

 

首先描述一下标题中的“股市动荡”定语修饰词吧:

 

上证指数从年初的3000点半年时间涨到了5000多,6月12号达到最高点5200点,然后就急转直下,最大单日跌幅 8.48%,一路跌回4000点以下。最近一周都在3900多徘徊。

 

3月最后一周,A股开户 166万户,超过历史最高纪录 2007年5月第二周165万户。

 

4月份,证监会宣布A股支持单用户开设多账户。

 

6月底,证金公司代表国家队入场救市。

 

7月份,证监会宣布严打场外配资。

 

 

7月27号将近2000股跌停,IM推送消息数超过平时峰值300倍

 

 

外网带宽消耗,一年10倍的增长

 

中国好声音广告第一晚,带来超过平时峰值200倍的注册量


挑战:小 VS 大:

 

  • 小:小公司的体量,团队小,机器规模小
  • 大:堪比大公司的业务线数量,业务复杂度,瞬间峰值冲击

     

     

     

     

     

     

雪球的业务线 = 1个新浪财经 + 1 个微博 + 1 个 motif + 1 个大智慧/同花顺。由于基数小,API调用瞬间峰值大约为平时峰值的 30+ 倍。

 

挑战:快速增长,移动互联网 + 金融,风口,A股大盘剧烈波动。


首先,在app端,在我们核心业务从 web2.0 sns 向 3.0 移动交易闭环进化的过程中,我们开发了一个自己的 hybrid 框架:本地原生框架,加离线 h5 页面,以此来支撑我们的快速业务迭代。当前,雪球前端可以做到 2 周一个版本,且同时并行推进 3 个版本:一个在 app store 等待审核上线,一个在内测或公测,一个在开发。我们的前端架构师孟祥宇在今年的 wot 上有一个关于这方面的详细分享,有兴趣的可以稍后再深入了解。

 

雪球App实践—构建灵活、可靠的Hybrid框架 http://wot.51cto.com/2015mobile/ http://down.51cto.com/data/2080769

 

另外,为了保障服务的可用性,我们做了一系列的“端到端服务质量监控”。感兴趣的可以搜索我今年4月份在环信SM meetup上做的分享《移动时代端到端的稳定性保障》。其中在 app 端,我们采用了一种代价最小的数据传输方案:对用户的网络流量,电池等额外消耗几乎为0

 

每个请求里带上前一个请求的结果

  • succ or fail : 1 char
  • 失败原因:0 – 1 char
  • 请求接口编号: 1 char
  • 请求耗时:2 – 3 char
  • 其它:网络制式,etc

     

炒股的人大多都会盯盘:即在开盘期间,开着一个web页面或者app,实时的看股价的上下跳动。说到“实时”,美股港股当前都是流式的数据推送,但国内的A股,基本上都是每隔一段时间给出一份系统中所有股票现价的一个快照。这个时间间隔,理论上是3秒,实际上一般都在5秒左右。 交了钱签了合同,雪球作为合作方就可以从交易所下属的数据公司那里拿到数据了,然后提供给自己的用户使用。

 

刚才介绍总体架构图的时候有提到 quote server ,说到这是需要性能的地方。

 

业务场景是这样的,雪球上个人主页,开盘期间,每秒轮询一次当前用户关注的股票价格变动情况。在内部,所有的组合收益计算,每隔一段时间需要获取一下当前所有股票的实时价格。起初同时在线用户不多,这个接口就是一个部署在 snowball 中的普通接口,股价信息被实时写入 redis ,读取的时候就从 redis 中读。后来,A股大涨,snowball 抗不住了。于是我们就做了一个典型的优化:独立 server + 本地内存存储。开盘期间每次数据更新后,数据接收组件主动去更新 quote server 内存中的数据。 后续进一步优化方案是将这个接口以及相关的处理逻辑都迁移到公有云上去。

 

对于那些不盯盘的人,最实用的功能就是股价提醒了。在雪球上,你除了可以关注用户,还可以关注股票。如果你关注的某只股票涨了或跌了,我们都可以非常及时的通知你。雪球上热门股票拥有超过 50w 粉丝(招商银行,苏宁云商)粉丝可以设置:当这支股票涨幅或跌幅超过 x%(默认7%)时提醒我。曾经连续3天,每天超过1000股跌停,证监会开了一个会,于是接下来2天超过1000股涨停

 

 

原来做法:

 

  • 股票涨(跌)x%,扫一遍粉丝列表,过滤出所有符合条件的粉丝,推送消息

 

新做法:

 

  • 预先建立索引,开盘期间载入内存
  • 1%:uid1,uid2
  • 2%:uid3,uid4,uid5
  • 3%:uid6
  • 问题:有时候嫌太及时了:频繁跌停,打开跌停,再跌停,再打开。。。的时候

     

内部线上记录:

  • 4台机器。
  • 单条消息延时 99% 小于 30秒。
  • 下一步优化目标:99% 小于 10 秒

 

IM 系统最初的设计目标是为雪球上的用户提供一个聊天的功能:


  • 送达率第一
  • 雪球IM:Netty + 自定义网络协议
  • Akka : 每个在线client一个actor
  • 推模式:client 在线情况下使用推模式
  • 多端同步:单账号多端可登录,并保持各种状态同步

     

     

移动互联网时代,除了微信qq以外的所有IM,都转型成了推送通道,核心指标变成了瞬间峰值性能。原有架构很多地方都不太合适了。

 

优化:

 

  • 分配更多资源:推送账号actor池
  • 精简业务逻辑:重复消息只存id,实时提醒内容不推历史设备,不更新非活跃设备的session列表等等
  • 本地缓存:拉黑等无法精简的业务逻辑迁移到本地缓存
  • 优化代码:异步加密存储,去除不合理的 akka 使用

     

akka这个解释一下:akka 有一个自己的 log adapter,内部使用一个 actor 来处理所有的 log event stream 。当瞬间峰值到来的时候,这个 event stream 一下子就堵了上百万条 log ,导致 gc 颠簸非常严重。最后的解决办法是,绕过 akka 的 log adapter,直接使用 logback 的 appender

 

线上记录:5w/s (主动限速)的推送持续 3 分钟,p99 性能指标无明显变化

 

7月10号我们在中国好声音上做了3期广告。在广告播出之前,我们针对广告可能带来的对系统的冲击进行了压力测试,主要是新用户注册模块,当时预估广告播出期间2小时新注册100万

 

压测发现 DB 成为瓶颈:

 

  • 昵称检测 cache miss > 40%
  • 昵称禁用词 where like 模糊查询
  • 手机号是否注册 cache miss > 80%
  • 注册新用户:5 insert

     

优化:

  • redis store:昵称,手机号
  • 本地存储:昵称禁用词
  • 业务流程优化:DB insert 操作同步改异步

     

     

     

下一步优化计划:

  • 将 sns 系统中所有的上行操作都改成类似的异步模式
  • 接口调用时中只更新缓存,而且主动设置5分钟过期,然后写一个消息到 mq 队列,队列处理程序拿到消息再做其它耗时操作。
  • 为了支持失败重试,需要将主要的资源操作步骤都做成幂等。


前置模块HA:


  • 合作方合规要求:业务单元部署到合作方内网,用户的敏感数据不允许离开进程内存
  • 业务本身要求:业务单元本身为有状态服务,业务单元高可用

     

解决方案:

 

  • 使用 Hazelcast In-Memory Data Grid 的 replication map 在多个 jvm 实例之间做数据同步。
  • java 启动参数加上 -XX:+DisableAttachMechanism -XX:-UsePerfData,禁止 jstack,jmap 等等 jdk 工具连接

     

关于前置模块,其实还有很多很奇葩的故事,鉴于时间关系,这里就不展开讲了。以后有机会可以当笑话给大家讲。


组合净值计算性能优化:


  • 一支股票可能在超过20万个组合里(南车北车中车,暴风科技)
  • 离线计算,存储计算后的结果
  • 股价3秒变一次,涉及到这支股票的所有组合理论上也需要每 3 秒重新计算一次

 

大家可能会问,为什么不用户请求时,实时计算呢?这是因为“组合净值”中还包括分红送配,分股,送股,拆股,合股,现金,红利等等,业务太过复杂,开发初期经常需要调整计算逻辑,所以就设计成后台离线计算模式了。当前正在改造,将分红送配逻辑做成离线计算,股价组成的净值实时计算。接口请求是,将实时计算部分和离线计算部分合并成最终结果。

 

实际上,我们的计算逻辑是比较低效的:循环遍历所有的组合,对每个组合,获取所有的价值数据,然后计算。完成一遍循环后,立即开始下一轮循环。

 

优化:

  • 分级:活跃用户的活跃组合,其它组合。
  • 批量:拉取当前所有股票的现价到 JVM 内存里,这一轮的所有组合计算都用这一份股价快照。

     

关于这个话题的更详细内容,感兴趣的可以参考雪球组合业务总监张岩枫在今年的 arch summit 深圳大会上的分享:构建高可用的雪球投资组合系统技术实践 http://sz2015.archsummit.com/speakers/201825

 

最后,我们还做了一些通用的架构和性能优化,包括jdk升级到8,开发了一个基于 zookeeper 的 config center 和开关降级系统

 

四. 聊聊关于架构优化的一些总结和感想

 

在各种场合经常听说的架构优化,一般都是优化某一个具体的业务模块,将性能优化到极致。而在雪球,我们做的架构优化更多的是从问题出发,解决实际问题,解决到可以接受的程度即可。可能大家看起来会觉得很凌乱,而且每个事情单独拎出来好像都不是什么大事。

 

我们在对一个大服务做架构优化时,一般是往深入的本质进行挖掘;当我们面对一堆架构各异的小服务时,“架构优化”的含义其实是有一些不一样的。大部分时候,我们并不需要(也没有办法)深入到小服务的最底层进行优化,而是去掉或者优化原来明显不合理的地方就可以了。

 

在快速迭代的创业公司,我们可能不会针对某一个服务做很完善的架构设计和代码实现,当出现各种问题时,也不会去追求极致的优化,而是以解决瓶颈问题为先。

 

即使我们经历过一回将 snowball 拆分服务化的过程,但当我们重新上一个新的业务时,我们依然选择将它做成一个大一统的服务。只是这一次,我们会提前定义好每个模块的 service 接口,为以后可能的服务化铺好路。

 

在创业公司里,重写是不能接受的;大的重构,从时间和人力投入上看,一般也是无法承担的。而“裱糊匠”式做法,哪里有性能问题就加机器,加缓存,加数据库,有可用性问题就加重试,加log,出故障就加流程,加测试,这也不是雪球团队工作方式。我们一般都采用最小改动的方式,即,准确定义问题,定位问题根源,找到问题本质,制定最佳方案,以最小的改动代价,将问题解决到可接受的范围内。

 

我们现在正在所有的地方强推3个数据指标:qps,p99,error rate。每个技术人员对自己负责的服务,一定要有最基本的数据指标意识。数字,是发现问题,定位根源,找到本质的最重要的依赖条件。没有之一。

 

我们的原则:保持技术栈的一致性和简单性,有节制的尝试新技术,保持所有线上服务依赖的技术可控,简单来说,能 hold 住。

能用cache的地方绝不用db,能异步的地方,绝不同步。俗称的:吃一堑,长一智。

 

特事特办:业务在发展,需求在变化,实现方式也需要跟着变化。简单的来说:遗留系统的优化,最佳方案就是砍需求,呵呵。

当前,雪球内部正在推行每个模块的方案和代码实现的 review,在 review 过程中,我一般是这样要求的:

 

技术方案:

 

  • 20倍设计,10倍实现,3倍部署
  • 扩展性:凡事留一线,以后好相见

 

技术实现:

 

  • DevOps:上线后还是你自己维护的项目,实现的时候记得考虑各种出错的处理
  • 用户投诉的时候需要你去解释,实现的时候注意各种边界和异常
  • 快速实现,不是“随便实现”,万一火了呢:性能,方便扩容

     


 

Q&A

 

Q1:im能详细讲下吗?


关于雪球 im 和推模式,有群友问到了,我就再展开讲一下(其实下周我约了去给一家号称很文艺的公司内部交流IM实现):雪球自己设计了一套 IM 协议,内部使用 netty 做网络层,akka 模式,即为每一个在线的 client new 一个 actor ,这个 actor 里面保持了这个在线 client 的所有状态和数据。如果有新消息给它,代码里只要把这个消息 tell 给这个 actor 即可。actor 里面会通过 netty 的 tcp 连接推给实际的 client。

 

Q2:问一个小问题,App的接口可用上报里 如果是网络问题引起的故障 怎么兼容?


app 如果发起一个请求,因为网络问题失败了(这其实是我们的上报体系设计时,最主要针对的场景),那么 app 会把这个失败的请求记录到本地内存,等下一次发请求时,把上次的这个失败请求结果及相关信息带上。如果连续多次失败,当前我们的做法是,只记录最后一次失败的请求结果,下次成功的请求里会带上它。

 

Q3:监控系统为何从zabbix切换到openfalcon,原因是什么?


简单来说,机器数上百之后,zabbix就会有很多问题,个人感受最大的问题是,新增 key 非常不方便。小米因为这些问题,自己做一个 falcon,然后开源了。我们碰到的问题很类似,看了看小米的,觉得可以不用自己再折腾了,就用它了。

 

Q4:前置模块的Hazelcast In-Memory Data Grid 的稳定性怎么样,采用的考虑是什么呢?用sharding redis 怎么样呢?


稳定性不能算很好,只能说还好。因为上线几个月,已经出了一次故障了。采用它,主要是开发简单,因为它就只有一个 jar 包依赖,不像其它备选项,一个比一个大。至于为什么不用 redis,因为这是要部署到别人的内网的啊,更新很麻烦的啊,运维几乎没有的啊,各种悲剧啊。我们当前为了做到“一键更新”,把shell脚本和所有jar包都打成一个自解压的文件这事我会随便说吗?

 

Q5:雪球im如何判断用户是否在线?要给给定的用户发消息,怎么找到对应的actor?不在线的时候消息如何存储?

 

IM 用户在线判断(转化成指定的 actor 是否存在)和路由,这些都是 akka 内置提供的,感兴趣的,算了,我个人建议是不要去碰 akka 这货。用户不在线的时候,消息会进 mysql 和 redis

 

Q6:大师,为了支持失败重试,需要将主要的资源操作步骤都做成幂等。这个怎么理解,具体怎么玩?

 

举个例子:用户发一个帖子,api调用的时候已经给用户返回成功了,但后端写 db 的时候超时了,怎么办?不能再告诉用户发帖失败了吧?那就重试重试再重试,直到写 db 成功。但万一重试的时候发现,上次写入超时,实际上是已经写成功了呢?那就需要把这个写入做成幂等,支持多次写入同一条记录。简单来说,db 层就是每个表都要有业务逻辑上的唯一性检查

 

Q7:另外用户对应的Actor需不需要持久化呢?

 

actor 不持久化。重启 server 的话,app 端会自动重连的

 

Q8:基于zookeeper的config center设计有什么指导原则或遇到什么坑吗?如何方便业务开发修改又不影响到其他?

 

我们的 config center 有两个版本:一个是参考 netflix 的 archaius,另一个是纯粹的 zk style 。风格问题,我个人的回答是:大家喜欢就好。 config center 本来就不影响业务开发修改啊?没有太明白问题点,sorry

 

Q9:刚才的追问下 如果只报最后一次故障 那么会不会不能准确评估影响?

 

不会的,因为这种情况一般都是用户进电梯或者进地铁了,呵呵

 

Q10:rpc是怎么选的呢,比如为什么不用thrift呢?

 

finagle 底层就是 thrift 啊。就我个人而言,我个人对于任何需要预先定义 proto 的东西都深恶痛绝。所以现在我们也在尝试做一个基于 jsonrpc 的简单版本的 rpc 方案,作为我们后续微服务容器的默认 rpc

 

Q11:实质上是用actor包住了netty的session吧?不建议用akka的原因是量大了后承载能力的问题吗?雪球im的dau约在50万左右吧?

 

是的,actor 内部持有网络连接。不建议用 akka 的原因是:我个人的原则是,我 hold 不住的东西就不做推荐。就当前来说,我觉得我 hold 不住 akka:使用太简单太方便,但坑太多,不知道什么时候就踩上了,想用好太难了

 

Q12:唐老师您好,雪球的架构中,rabbitmq 主要用在哪些场景,rabbit的 负载是通过哪些手段来做呢?

 

当前我们的 mq 功能都是有 rabbitmq 提供的,我们在内部封装了一个叫 event center 的模块,所有的跟 queue 打交道的地方,只需要调用 event center 提供的 api 即可。我们对于 rabbit 并没有做太多的调优,大约也是因为现在消息量不大的缘故。后续我们的大数据体系里,queue 的角色会由 kafka 来承担

 

Q13:大师,能说说akka为啥不推荐么

 

看上面问题 12

 

Q14:唐大师,关于交易这块,能说下你们的账户体系吗?

 

股票交易跟支付宝模式还是很大不一样的,本质上,雪球上只是一个纯粹的通道:钱和股票都不在雪球内部。所以,我们当前的账户体系就像我们页面上描述的那样:将用户的券商账号跟雪球id做绑定

 

Q15:性能规划上有什么经验或者推荐资料阅读吗?谢谢。

 

通读 allthingsdistributed 上的精华文章 http://www.allthingsdistributed.com/

 

Q16:唐大师,雪球的docker是怎么用?怎么管理的?

 

参考雪球sre高磊在 dockercn 上的分享

 

Q17:追加一个问题:对业务合规要求的需求不是很了解,但是“-XX:+DisableAttachMechanism -XX:-UsePerfData”这样无法禁止“jstack -F”的吧,只是禁止了普通的jstack。

 

应该是可以禁止的,因为 jvm 内部所有挂载机制都没有启动,jstack 加 -f 也挂不上去。要不我一会测一下再给你确认?

 

Q18:帮我问个问题:这么多系统,如何保证迭代保质保量按时交付?

 

这就要感谢我们雪球的所有技术,产品,运营同事们了 [Tongue]

 

Q19:为什么用kafka替换rbt?

 

因为 rbt 是 erlang 写的啊,我不会调优啊,出了问题我也不会排查啊。事实上,event center 模块极偶发的出现丢消息,但我们一直没有定位到根源。所以,只好换了

 

Q20:请问百万活跃用户session是怎么存储的?怎么有效防止大面积退出登录?

 

用户登录 session 就存在 jvm 内部。因为是集群,只要不是集群突然全部挂,就不会出现大面积重新登录的

 

Q21:每个请求里带上前一个请求的结果,这个得和用户请求绑定上吧?

 

收集 app 端的访问结果,大部分情况下用于统计服务质量,偶尔也用于用户灵异问题的追逐

 

Q22:akka的设计居然和Erlang的抢占式调度很像,也存在单进程瓶颈问题?

 

可以这么说,主要是它的 log actor 是单个的

 

Q23:集群环境下如何保存在jvm内部?各个jvm如何共享的?

 

我们的 im 系统其实分 2 层,前面有一层接入层,后面是集群。接入层到集群的链接是按 uid 一致性 hash。简单来说,一个用户就只连一个 jvm 服务节点。所以我们只在收盘后更新服务。在雪球,盘中严禁更新服务的

 

Q24:接入层的实现架构方便描述一下吗?

 

雪球IM的接入层分为2类:app接入层和web接入层。app接入层是一个 netty 的 server,开在 443 端口,但没有使用 ssl ,而是自己用 rsa 对消息体加密。netty 收到消息后,解包,根据包里的描述字段选择发往后端的业务节点。web 接入层是一个基于 play 的 webserver,实现了 http 和 websocket 接口,供 web 使用

 

(完)


 

雪球App是一款解决股票买什么好问题的手机炒股应用。因为独特的产品设计和运营策略,雪球App在市场上拥有领先的份额和影响力。雪球正在招聘,热招职位请参考 http://xueqiu.com/about/jobs (可点击阅读原文进入) 我们在望京 SOHO 等你!

 

本文策划 庆丰@微博, 内容由王杰编辑,庆丰校对与发布,Tim审校,其他多位志愿者对本文亦有贡献。读者可以通过搜索“ArchNotes”或长按下面图片,关注“高可用架构”公众号,查看更多架构方面内容,获取通往架构师之路的宝贵经验。转载请注明来自“高可用架构(ArchNotes)”公众号

在线数据迁移那点事

说明:infoQ 投稿文章,刊出稿地址:在线数据迁移经验:如何为正在飞行的飞机更换引擎

博客上的文字与刊出稿有细微差别。

在线数据迁移,是指将正在提供线上服务的数据,从一个地方迁移到另一个地方,整个迁移过程中要求不停机,服务不受影响。根据数据所处层次,可以分为 cache 迁移和存储迁移;根据数据迁移前后的变化,又可以分为平移和转移。

平移是指迁移前后数据组织形式不变,比如 Mysql 从1个实例扩展为 4 个实例,redis 从 4 个端口扩展到 16 个端口,HBase 从 20 台机器扩展到 30 台机器等等。如果在最初的设计里就为以后的扩容缩容提供了方便,那么数据迁移工作就会简单很多,比如Mysql已经做了分库分表,扩展实例的时候,只需要多做几个从库,切换访问,最后将多余的库表删除即可。更进一步,在实现上已经做到全自动数据迁移,如 HBase ,就更简单了:添加机器,手工修改配置或者系统自动发现,然后,沏一杯咖啡,等待系统完成迁移。

转移是指数据迁移前后,数据组织形式发生了变化。多年前,微博平台曾经为 ID 升级做过一次数据迁移,由 @XiaoJunHong @麦俊生 操刀,将微博 id 由最初的自增算法修改为巧妙设计的 uuid 算法(http://weibo.com/p/1001603800404851831206 ),这次迁移最大的挑战是要修改数据的主键,主键本来是数据的唯一标识,它发生变化,也就意味着原来的数据不复存在,新的数据凭空产生,对于整个系统中所有业务流程,周边配套,上下游部门都会产生巨大的兼容性挑战。不过大部分数据迁移项目都不会修改主键,甚至不会修改数据本身,改变的只是数据的组织形式。比如微博计数器原本为了节约存储空间,使用 redis hash 进行存储,后来为了提升批量查询的性能,迁移成 KV 形式;又比如微博的转发列表和粉丝列表,最初都使用 Mysql 存储,后来为了更好的扩展性和成本,都迁移到 HBase 存储。

在线数据迁移最大的挑战是如何保证迁移过程服务不受影响。很多人将其比喻成“飞行过程中换发动机”“给行驶的汽车换轮胎”,但实际上并没有那么困难,一个入行一两年的技术人员,遵从一些经验指导,完全可以完成。下面就跟大家分享一下微博平台在这方面的一些经验,作为抛砖引玉。

在线数据迁移一般分为四个步骤:一,上线双写,即同时写入新旧两种数据;二,历史数据离线搬迁,即离线将历史存量数据从旧系统搬到新系统;三,切读,即将读请求路由到新系统;四,清理沉淀,包括清理旧的数据,回收资源,及清理旧的代码逻辑,旧的配套系统等等,将迁移过程中的经验教训进行总结沉淀,将过程中开发或使用的工具进行通用化改造,以备下次使用。(备注1)

图1:在线数据迁移步骤示意图

最近我们刚刚完成了粉丝列表从Mysql迁移到HBase,下面以这次迁移为例子,展开来讲讲每个步骤具体实施、可能的问题及对策。

在迁移之前,根据以往的经验,我们为此次迁移工作制定了更详细的流程,如图:

图2:粉丝列表迁移到HBase工作流程图

上线双写
编写双写的代码逻辑之前,首先要根据业务规则和性能指标确定HBase的表结构和主键设计。
对于列表类的需求,HBase有两种典型的用法,一种是高表模式,与传统的Mysql模式非常类似,列表中的每一项存一行,每一行有固定的属性列;另一种是宽表模式,一个列表存一行,列表中的每一项存成一个单独的列,各种属性都打包到列内部的 value 中。如图:


图3:粉丝列表业务分别使用HBase高表模式和宽表模式存储示意图

高表模式的好处在于与Mysql类似,各种业务逻辑的实现也比较像,认知和改造成本较低,劣势在于因为HBase的实现机制导致单个列表可能被分别存储在多个不同的Region里,查询的性能较差(备注2)。而宽表的优劣势正好与高表相反。在微博平台看来,技术方案很多特性都可以妥协,但唯独性能永远是不能妥协的,所以我们选择宽表模式。
微博平台的另一个传统是上行异步化,通过将操作转化为消息,写入消息队列,后台异步处理的方式来削峰填谷,并获得更好的可用性。消息队列已经支持单个消息被多个业务模块重复处理,并支持串联和并联。所以我们将写入HBase的代码逻辑单独封装到一个模块中,将它配置为与写入旧Mysql代码串联或并联即可。
为了支持消息异步处理的重试机制,平台很多模块都设计成具有幂等特性,即同一条消息可以重试多次,而不会破坏最终的结果。有一些模块,如计数器,提醒等,业务本身不支持重试,平台通过“重复消息检测模块”为它们提供短时间内的重试支持。大部分Mysql存储都通过主键或者单独的Unique key索引来达到幂等要求,相应的,HBase高表模式通过主键保证,宽表模式通过 column qualifier 保证。在粉丝列表迁移过程中,因为我们设计的 column qualifier 不能保证幂等,导致数据一致性无法达到要求,最后也是通过引入额外的重复消息检测模块解决。
另外,HBase当前不提供二级索引,覆盖索引,join,order by等Mysql高级查询功能,需要在迁移之前做好评估,确定新方案能够支持所有的业务特性。比如粉丝列表一般都是查询最新的5000个粉丝,但在一个偏僻的角落里还提供了查询最初100个粉丝列表的功能,就这样一个很不起眼的小功能,花费了我们大量的时间精力和机器资源,很浪费,但却不得不做(备注3)。
上线双写完成后,需要对双写的数据进行一致性校验。数据一致性校验需要从两个维度进行:存储维度和业务维度。存储维度是指直接取Mysql和HBase里的数据进行对比;业务维度是指从最终用户看到的数据维度进行校验,对于微博平台来说,就是调用平台的取粉丝列表的API接口,看结果是否与原来一致。微博平台的数据一致性校验及格线是6个9,即 99.9999%,也就是说每一百万条数据中,差别不能超过1条。

历史数据搬迁
上线双写并校验确认通过后,就可以开始搬迁历史数据了。
搬迁历史数据的步骤中,最大的困难是保证搬迁过程与线上业务写入互不干扰。对于列表类功能,最大的干扰是来自于这样一种业务场景:搬迁程序从Mysql中select出来一个列表,在插入到HBase之前,这个列表发生了变化。如果是增加一个元素,由于HBase的幂等保证,最终结果并不会产生偏差,但如果是删除一个或多个元素,那么最终会表现为HBase中删除操作未生效,因为线上业务执行完删除操作后,搬迁程序又执行了插入操作。本质上,这是因为我们在这样的数据量规模下不能使用事务引起的,如果引入事务,能够解决这个问题,但同时也会将搬迁耗时从几天延长到几周甚至几个月。在微博平台,我们通过引入轻量级的memcache锁来模拟Serializable级别的事务隔离。
历史数据搬迁完成后也需要进行一致性校验。实际上,平台建议在搬迁全量数据之前,先搬迁部分数据,并进行一致性校验。部分数据一致性校验通过后,再对全量数据进行搬迁。这种方式可以极大的节约搬迁时间,降低因为搬迁流程或代码不完善导致的延期风险。

切读
全量数据搬迁并校验完成后,即可以进行读请求切换了。
微博平台的通用切换方式是在代码中埋入开关,通过 Config Service 进行切换操作。切换的流程为:Tcpcopy环境 –> 线上环境 uid 白名单(内部工程师)–> 线上环境百分比灰度 0.01%,1%,10% –> 线上环境全量。tcpcopy 环境用来验证代码在线上环境是否正常,uid白名单用来验证功能是否正常,百分比灰度用来验证性能和资源压力是否正常,所有验证都通过后,最后才进行全量切换。一般这个过程会持续一周到两周。

清理沉淀
切读完成后,整个数据迁移过程可以认为已经完成了。但项目工作并没有完结,旧的逻辑代码清理,旧的配套系统下线,旧资源回收,以及最重要的一个环节:经验教训总结、分享,流程完善,工具通用化改造。正是因为平台团队内部有了最后这个环节的强制要求,我们才能逐渐的积累下越来越丰富的经验,越来越多的工具,越来越强的信心。厂内如果有类似的数据迁移,想省事,可以找平台咨询,甚至可以直接委托给平台来实施。

在线数据迁移并不是一项需要高深技术的工作,它更多需要的是对业务逻辑的把控,对操作流程的理解,对新旧系统特性的掌握,以及对细节的敬畏之心。

备注1:某些情况下,步骤一和步骤二也可能倒过来,先做历史数据搬迁,然后再写入新数据,这时候就需要谨慎的处理搬迁这段时间里产生的新数据,内部一般使用 queue 缓存写入的方式,称为“追数据”。
备注2:通过实现prefixSplitPolicy,可以保证同一个user(prefix)的数据一定在同一个region里的,这不是一个不能克服的劣势。感谢 @冯宏华 同学的指正
备注3:小米为HBase引入了高表模式下的reverse scan功能,见 https://issues.apache.org/jira/browse/HBASE-4811 ,但宽表模式下还是需要额外成本。

微博平台的RPC服务化实践

微博平台的RPC服务化实践

 

2014年第一分钟,新浪微博的发布量以808298条再次刷新记录,第一秒微博发布量相较去年提升55%。(数据来源:新浪科技 http://tech.sina.com.cn/i/2014-01-01/02409059293.shtml )这是微博平台 RPC 框架 “Motan” 上线后第一次抗峰值,整体表现平稳,基本达到最初的“应用方无感知”的目标。

 

在RPC服务化这个事情上,微博平台不是第一个吃螃蟹的:早的有亚马逊和eBay等国外先驱,近的有Twitter的finagle,淘宝的dubbo等等,网上各种公开的资料铺天盖地。另一方面,单纯的RPC调用功能实现,从技术上看其实并不复杂:client 发起调用,框架拦截调用信息,序列化,传输,server端收到调用信息,反序列化,根据调用信息发起实际调用获取结果,再原路返回。实现这些功能可能也就三五天的事情,但在一个复杂的业务环境下,稳定可靠的应用它,才是最大的挑战。

 

微博平台的 RPC 服务化拆分历程始于2013年7月。在此之前,我们花了很长的时间讨论服务化的目标,主要是项目的范围:哪些问题不属于服务化项目需要解决的问题。 实际的框架代码开发花了三个工程师(王喆@wangzhe_asdf9 陈波@fishermen 麦俊生@麦俊生)大约一个月时间,然后花了将近两个月的时间推动在第一个业务上线:调整工程师的开发模式,调整测试流程,修改上线系统,添加监控和报警,小流量测试,灰度发布,最后才是全量上线。然后又花了一个月,在微博平台主要业务中全部上线。

 

微博RPC的一些基本的数据指标:

  • Motan 框架:2w+ 行 Java 代码,1w 行 test 代码,UT行覆盖率超过 70%(当前 Motan 实现中,与微博平台内部多个系统都有功能绑定,还不具备开源条件,但开源是我们从一开始就设立好的目标之一)
  • 支持 2 种调用方式:inJVM 和 TCP远程调用。inJVM 方式类似 loopback 网卡:数据经过了协议栈流程处理,但没有流经真正的网络设备。inJVM方式主要用来支持开发调试和测试,以及在RPC服务上线初期作为Fail-Back降级使用
  • 典型业务场景下单实例 tps 极限 20k,微博平台一般采用单机双实例,即单机极限 40k
  • 典型业务场景下平均响应时间 <3 ms,框架层额外消耗 < 0.01 ms
  • 最大的单个核心业务日调用量超过 800亿次

 motan rpc 调用架构图

Motan 架构图

 

RPC服务化的目的大约有两种:将一个大一统的应用拆分成多个小的RPC服务,那么目的就是为了解耦和,提升开发效率;如果是将传统的Http或其它方式远程调用改造成高效的RPC调用,那么就是为了提升运行效率。不幸的是,微博平台的RPC框架,需要同时达到这两个目的:既要在平台内部将一个大一统的应用拆分,又要考虑到后续向开放平台的大客户们提供RPC接入的可能。因此,微博平台在技术选型和方案设计上做了很多的权衡和妥协,在实践中也获得了不少的经验和教训:

  • 首先,是选择已有开源方案,还是自己开发一个新方案?选择的依据按重要程度排序:是否满足自己的核心需求,方案成熟度,认知成本(即二次开发难度)。由于是拆分一个已有的复杂应用,微博平台的一个核心需求是:应用开发方希望尽可能的平滑迁移,最好能做到应用方无感知。我们评估的多个开源方案没有一个能满足,所以只能自己做一个了
  • 灵活性与误用的可能性:框架开发方总是有一个偏见,觉得我这个框架越灵活越好,最好每个步骤每个环节都是可以由使用方自己配置或定义。但对于一个内部强制使用的框架来说,使用方式的统一性也同样重要,换句话说,对于大部分的环节步骤,都需要保证团队内部各使用方都按同样的方式进行配置,防止误用,并降低学习和沟通成本。我们的经验是,框架开发完成后,还需要有“框架使用方”角色,将所有的灵活性限制在框架使用方的层面,避免直接暴露所有细节到最终的业务开发方
  • 序列化方式选择:微博平台从2011年引入了 PB 序列化方式,以替代 cache 和 db 中的 json 文本。但在 RPC 框架上线过程中,我们选择了对 Java 对象更为友好的 Hessian2 。因为之前的 PB 序列化需要定义 proto 文件和生成代码,平台只对必要的 model 类做了支持,而 rpc 可能涉及到更多的 wrap 类,业务逻辑类等,为所有的类提供 pb 支持的工作量太大,而且后期维护困难。当然了,Motan 框架支持各种不同的序列化方式配置。
  • 通讯协议选择:在评估了几个开源RPC框架的协议设计后,我们最终选择了在 TCP 链接基础上设计自己的 RPC 通讯协议,一个简单的二进制协议:定长 header 中包含一个 length 字段,然后就是二进制的 body payload,即序列化之后的 rpc request 或 response 。
  • 集群管理:微博平台Motan框架当前依赖于内部开发的Config Service(Code name Vintage,based on ZooKeeper)来进行服务注册,服务发现和变更通知。
  • Trace系统:微博平台Motan框架当前依赖于内部开发的类似Twitter Zipkin的Trace系统(Code name Watchman)来对RPC请求做抽样及全量trace。

 

一套新的架构在大规模的推广使用过程中总会遇到各种问题,微博平台RPC服务化也不例外。总结起来,我们遇到的问题包括:

1. 整体服务的SLA水平降低。由于前端接口依赖多个后端RPC服务,每个RPC服务风吹草动都会直接影响接口成功率。初期改造过程中,对RPC服务并没有做明确的SLA要求,加上前端有些地方对调用超时异常兼容不够,导致前端调用失败几率增加,接口成功率降低。后来我们对每一个RPC服务设立了具体的SLA要求,并优化了前端重试及失败处理机制,从而保证了整体服务的SLA。

2. 必须提供稳定的测试环境。前期改造过程中,服务调用方在进行线下测试时,由于被调用方同时也在调整,导致经常出现测试环境服务不可用的情况,严重影响了调用方的测试使用。因此快速搭建一个独立的以RPC服务为单位的测试环境,在整个服务化过程中还是十分重要的。当前微博平台以 OpenStack 为基础搭建了一套快速测试环境分配系统,支持RPC服务粒度的测试环境分配。

3. 统一的开发、测试、上线、监控流程。在初期改造中,由于涉及多个部门和开发团队,各RPC服务的开发测试上线流程都沿用原来团队的做法,不统一,导致多个互相依赖的RPC服务同时上线新版本的时候,经常出现衔接不上,无法线下集成测试,只能线上测试的情况,严重影响业务迭代速度。后来平台通过搭建一套统一的CI流程以及优化运维上线系统,初步解决了这个问题。

 

当前 Motan 框架完成了在微博平台的核心业务上线,接下来,我们的工作重点方向包括:

  • 多语言支持:RPC多语言支持很难,如果没有特别的理由,建议绕过。RPC多语言有两种不同的思路:一种是类似 Thrift/PB 定义语言无关的 proto,自动生成对应语言的代码;另一种是没有 proto 定义,只有文档规范说明,业务方自己实现,或者使用框架自带的lib实现,类似 Hessian/MsgPack 。微博平台选择的是第二种,当前已经支持 PHP client (兼容 Yar 协议),以及 C Server 。
  • RPC Service 运行时环境支持:为 Java 和 C service 提供统一的运行时环境支持。对于 Java RPC Service,目标是让业务方像写 Servlet 一样写 Service,统一打成 war 包后在 Tomcat 中运行;对于 C Service,将业务代码抽离成 so,由框架进行加载。
  • 标准化 RPC 接口改造,并推广到其它部门及开放平台使用:将当前平台内部的 Service 改造成类似微博 OpenApi 那样的标准化接口,推广给其它部门使用,并最终通过微博开放平台,开放给外部开发者使用。

 

微博平台 RPC 服务化拆分的故事,2014年依然继续。

 

撰稿:@唐福林

 

 

微博关系服务与Redis的故事

微博关系服务与Redis的故事

 

新浪微博的工程师们曾经在多个公开场合都讲到过,微博平台当前在使用并维护着可能是世界上最大的Redis集群,其中最大的一个业务,单个业务使用了超过 10T 的内存,这个业务就是微博关系服务。

 

2009年微博刚刚上线的时候,微博关系服务使用的是最传统的 memcache+mysql 的方案。Mysql 按 uid hash 进行了分库分表,表结构非常简单:

tid fromuid touid addTime
自增id 关系主体 关系客体 加关注时间

 

业务方存在两种查询:

  • 查询用户的关注列表:select touid from table where fromuid=?order by addTime
  • 查询用户的粉丝列表:select fromuid from table where touid=?order by addTime

两种查询的业务需求与分库分表的架构设计存在矛盾,最终导致了冗余存储:以 fromuid 为hash key存一份,以 touid 为hash key再存一份。memcache key 为 fromuid.suffix ,使用不同的 suffix 来区分是关注列表还是粉丝列表,cache value 则为 PHP Serialize 后的 Array。后来为了优化性能,将 value 换成了自己拼装的 byte 数组。

 

2011年微博进行平台化改造过程中,业务提出了新的需求:在核心接口中增加了“判断两个用户的关系”的步骤,并增加了“双向关注”的概念。因此两个用户的关系存在四种状态:关注,粉丝,双向关注和无任何关系。为了高效的实现这个需求,平台引入了 Redis 来存储关系。平台使用 Redis 的 hash 来存储关系:key 依然是 uid.suffix,关注列表,粉丝列表及双向关注列表各自有一个不同的 suffix,value 是一个hash,field 是 touid,value 是 addTime。order by addTime 的功能则由 Service 内部 sort 实现。部分大V的粉丝列表可能很长,与产品人员的沟通协商后,将存储限定为“最新的5000个粉丝列表”。

 

需求实现:

  • 查询用户关注列表:hgetAll uid.following ,then sort
  • 查询用户粉丝列表:hgetAll uid.follower,then sort
  • 查询用户双向关注列表:hgetAll uid.bifollow,then sort
  • 判断两个用户关系:hget uidA.following uidB && hget uidB.following uidA

后来又增加了几个更复杂的需求:“我与他的共同关注列表”、“我关注的人里谁关注了他”等等,就不展开来讲了。

 

平台在刚引入 Redis 的一段时间里踩了不少坑,举几个例子:

  • 运维工具和流程从零开始做,运维成熟的速度赶不上业务增长的速度:在还没来得及安排性能调优的工作,fd 已经达到默认配置的上限了,最后我们只能趁凌晨业务低峰期重启 Redis 集群,以便设置新的 ulimit 参数
  • 平台最开始使用的 Redis 版本是 2.0,因为 Redis 代码足够简单,从引入到微博起,我们就开始对其进行了定制化开发,从主从复制,到写磁盘限速,再到内存管理,都进行了定制。导致的结果是,有一段时间,微博的线上存在超过5种不同的 Redis 修改版,对于运维,bugfix,升级都带来了巨大的麻烦。后来由田风军 @果爸果爸 为内部 Redis 版本提供了不停机升级功能后,才慢慢好转。
  • 平台有一个业务曾经使用了非默认 db ,后来费了好大力气去做迁移
  • 平台还有一个业务需要定期对数据进行 flush db ,以腾出空间存储最新数据。为了避免在 flush db 阶段影响线上业务,我们从 client 到 server 都做了大量的修改。
  • 平台每年长假前都会做一些线上业务排查,和故障模拟(2013年甚至做了一个名叫 Touchstone 的容灾压测系统)。2011年十一假前,我们用 iptables 将 Redis 端口的所有包都 drop 掉,结果 client 端等了 120 秒才返回。于是我们在放假前熬夜加班给 client 添加超时检测功能,但真正上线还是等到了假期回来后。

 

对于微博关系服务,最大的挑战还是容量和访问量的快速增长,这给我们的 Redis 方案带来了不少的麻烦:

 

第一个碰到的麻烦是 Redis 的 hgetAll 在 hash size 较大的场景下慢请求比例较高。我们调整了 hash-max-zip-size,节约了1/3的内存,但对业务整体性能的提升有限。最后,我们不得不在 Redis 前面又挡了一层 memcache,用来抗 hgetAll 读的问题。

 

第二个麻烦是新上的需求:“我关注的人里谁关注了他”,由于用户的粉丝列表可能不全,在这种情况下就不能用关注列表与粉丝列表求交集的方式来计算结果,只能降级到需求的字面描述步骤:取我的关注人列表,然后逐个判断这些人里谁关注了他。client 端分批并行发起请求,还好 Redis 的单个关系判断非常快。

 

第三个麻烦,也是最大的麻烦,就是容量增长的问题了。最初的设计方案,按 uid hash 成 16 个端口,每台 64G 内存的机器上部署 2 个端口,每个业务 IDC 机房部署一套。后来,每台机器上就只部署一个端口了。再后来,128G 内存的机器还没有进入公司采购目录,64G 内存就即将 OOM 了,所以我们不得不做了一次端口扩容:16端口拆64端口,依然是每台 64G 内存机器上部署 2 个端口。再后来,又只部署一个端口。再后来,升级到 128G 内存机器。再后来,128G 机器上出现 OOM 了!现在怎么办?

 

为了从根本上解决容量的问题,我们开始寻找一种本质的解决方案。最初选择引入 Redis 作为一个 storage,是因为用户关系判断功能请求的数据热点不是很集中,长尾效果明显,cache miss 可能会影响核心接口性能,而保证一个可接受的 cache 命中率,耗费的内存与 storage 差别不大。但微博经过了 3 年的演化,最初作为选择依据的那些假设前提,数据指标都已经发生了变化:随着用户基数的增大,冷用户的绝对数量也在增大;Redis 作为存储,为了数据可靠性必须开启 rdb 和 aof,而这会导致业务只能使用一半的机器内存;Redis hash 存储效率太低,特别是与内部极度优化过的 RedisCounter 对比。种种因素加在一起,最终确定下来的方向就是:将 Redis 在这里的 storage 角色降低为 cache 角色。

 

前面提到的微博关系服务当前的业务场景,可以归纳为两类:一类是取列表,一类是判断元素在集合中是否存在,而且是批量的。即使是 Redis 作为 storage 的时代,取列表都要依赖前面的 memcache 帮忙抗,那么作为 cache 方案,取列表就全部由 memcache 代劳了。批量判断元素在集合中是否存在,redis hash 依然是最佳的数据结构,但存在两个问题:cache miss 的时候,从 db 中获取数据后,set cache 性能太差:对于那些关注了 3000 人的微博会员们,set cache 偶尔耗时可达到 10ms 左右,这对于单线程的 Redis 来说是致命的,意味着这 10ms 内,这个端口无法提供任何其它的服务。另一个问题是 Redis hash 的内存使用效率太低,对于目标的 cache 命中率来说,需要的 cache 容量还是太大。于是,我们又祭出 “Redis定制化”的法宝:将 redis hash 替换成一个“固定长度开放hash寻址数组”,在 Redis 看来就是一个 byte 数组,set cache 只需要一次 redis set。通过精心选择的 hash 算法及数组填充率,能做到批量判断元素是否存在的性能与原生的 redis hash 相当。

 

通过微博关系服务 Redis storage 的 cache 化改造,我们将这里的 Redis 内存占用降低了一个数量级。它可能会失去“最大的单个业务Redis集群”的头衔,但我们比以前更有成就感,更快乐了。

 

 

撰稿:@唐福林

 

“关注” 互联网

从 2000 年开始接触互联网,到后来工作在这个行当,摸爬滚打已经十余年。这十年还正好是互联网风起云涌的十年,各路英雄你方唱罢我登场,自己却一直是舞台下的一个看客,即使后来在一个也可以算是引领潮流的产品里工作,也积累了少许知名度,但依旧是依托平台的高度,而且是作为技术人员,对产品本身的贡献微乎其微。实际上,我一直觉得自己对产品是有一些理解和想法的,也想过转去做一个懂技术的产品人员,只是缺乏一个机会而已。

最近,在思考一些类似个人或部门的明年规划之类的问题的时候,也对自己理想中的微博产品的未来做了一些思考,在此记录一下。

简单的来说,我个人觉得,微博目前的仅“用户”为第一层次概念,用户可以关注和被关注的模式,太过单薄,需要将第一层次概念扩张到所有的互联网对象,即所有对象都可以关注和被关注。整个互联网应该是一个关注和被关注的网络,而微博,应该成为这个网络的基础设施平台。当前的互联网已经完全进入了“眼球”时代,用户的注意力是所有互联网产品争夺的终极目标。而目前已有的各种产品模式,都处于低层次的无序竞争状态,没有统一的迹象。从眼下来看,微博是最有希望成为“基础设施平台”的了。

如果把微博上的“用户”扩展到互联网所有的对象,我们可以想象一下会发生什么:每一个Web1.0网站的每一个频道或分类都可以被“订阅”到微博,就像RSS被订阅到昔日的GoogleReader一样,反过来,微博上的Feed流也可以成为它们的一个频道或分类;每个Web2.0网站上的每个用户,每个Page或每个企业,也都可以被微博账号“关注”,就像他们也是一个微博账号一样,反过来,它们也可以直接在自己的网站上关注微博用户;如果再往外扩展到物联网的对象,所有能产生动态的物体——家里的微波炉空调,附近的气象站摄像头,高速路上一百米范围内的其它车,都可以在微博上进行关注,反过来,这些物联网对象也能关注一些微博用户账号,接收用户的指令;如果关注的概念再进一步细化,用户关注的不是一个对象本身,而是这个对象的某些我感兴趣的动态,或符合我设定的规则的某些对象(而不是一个特定的对象),事情就更好玩了:我可以只关注我附近一公里内的星巴克,我也可以只在早上8点到10点间接受这些星巴克推送的优惠信息,我可以只关注气象站发布的低温或大风警告,也可以只关注摄像头视频通过一定的后端处理后,发布的堵车信息(而不是裸的视频流数据)。

信息爆炸的时代,所有人都在说信息过滤,推荐,个性化等等,但再好的推荐算法也无法满足用户内心深处的迷茫,因为,每家网站推荐的,都只是用户需求列表中很小的一部分,更悲剧的是,大部分甚至都不在用户的真实需求列表中。用户的需求,永远只有用户自己知道。如果我们提供一种办法,一种途径,一个地方,让用户能找到自己想要的所有,而又不会被强制塞进或夹杂着不想要的东西,能自由选择自己想看的,想听的,想说的,而不用担心任何的噪音,那就是产品的最高境界了。

个人意见,仅供娱乐。

 

 

一种新的 OpenAPI 设计思想(草稿)

Open API V3 接口设计规范草案

问题

在 v2 版本的接口中,我们发现接口最大的问题是:使用方容易用错,而究其根源,主要在于接口的说明文档,实现逻辑,测试用例,使用方理解等之间存在一致性问题,具体来说,在于以下几点:

  1. 接口设计越来越严重依赖某一个具体的产品需求,而不是基于普遍需求的抽象,导致越来越多的后期调整。接口设计及调整缺乏一致性保证,从而使得理解及使用成本上升,用错的概率增大
  2. 单个接口的功能及返回会随着时间的推移而发生变化,而这种变化信息无法及时的传递给以及在使用的调用方。比如增加传入参数,比如做一些性能优化,还有微调某些字段的含义(如曾经不返回正确的关注粉丝数,后来改为返回正确的数了)
  3. 测试人员对需求的理解与开发人员不一致,偶尔不全面,导致功能点漏测,自动化测试覆盖不足
  4. 使用方依赖的接口定义文档,SDK都出现与实现不一致,严重过时现象。文档与代码无法保持完全一致
  5. 使用方阅读文档描述文字,容易出现遗漏某些说明,从而因信息缺失导致不当甚至错误的使用

解决思路

因为 API 的设计,实现,使用都是程序员,或 IT 相关人士,在这个群体中,最通用的,且不容易发生理解错误的,不是自然语言,而是代码(为了避免编程语言的选择不同,这里我们使用类似 C 语言的“伪代码”模式进行描述),所以在 v3 版本中,主要的解决思路是,将 API 接口相关的所有信息,都以伪代码的形式进行描述,描述信息本身也以 API 的形式进行组织和公开,给所有相关方提供同等的,详细的,具体的信息,包括设计,实现,测试和使用方。

  1. 以一种程序可识别和处理的 DSL schema 方式定义 API,且 API schema 定义是公开的,类似 wsdl 文件。
  2. API schema 中包括以下约束信息:参数,返回字段,状态码(错误码),SLA保证,当前状态等
  3. 提供一套根据 API schema 进行 API 实现的规则说明,工具和最佳实践例子
  4. 提供一套根据 API schema 进行自动化测试的规则说明,工具和例子
  5. 提供一套根据 API schema 进行客户端编程使用的规则说明,工具和例子

具体的工作流程:

  1. 设计阶段
    • 需求收集阶段:平台产品与各端产品人员一起进行需求决策(必须要有平台产品的角色)
    • 设计阶段:平台产品与开发人员一起进行接口设计,产出 DSL schema。框架自动生成配套的工具,Demo,初步的文档等等
    • 实现阶段:开发人员根据公开获取的 DSL schema 进行开发
    • 测试阶段:测试人员根据公开获取 DSL schema 进行测试
    • 使用阶段:使用方根据公开获取 DSL schema 进行使用
    • 线上运行阶段:各方都可以根据公开获取的 DSL api 获得每个 API 的设计原则,实现细节,测试用例,最佳使用方式,以及当前的运行状态(如被降级,出问题等)。如果 API 有过更新或升级,则可以获得每次升级的需求,改动点等
  2. 调整阶段
    • 需求收集阶段:平台产品收集需求,进行决策
    • 设计阶段:平台产品与开发人员一起进行接口调整设计,产出 DSL schema。框架自动修改已有的配套工具,Demo,文档等
    • 实现阶段:开发人员根据更新记录,改动点等信息进行实现
    • 测试阶段:测试人员根据更新记录,改动点等信息进行测试
    • 使用阶段:使用方根据更新记录,改动点等信息进行使用方式修改
    • 线上运行阶段:各方都可以根据公开获取的 DSL api 获取该 API 的每次升级的更新记录,改动点

示例

假设 API V3 的框架已经开发完成。以 /3/users/show.json 接口为例:

  1. 需求收集阶段:需求明确为:展示用户基本信息
  2. 设计阶段(示例):(where goes models? pojo?)
    • 在 /3/ 和 /3/users/ 的返回结果里,增加 /3/users/show 这一条目。增加后的例子:

    /3/: {"result":"OK"; "version":3; "desc":"Weibo API Version 3, API index"; "list":["/3/users/", "/3/friendships/", "/3/status/", "/3/comments/"]}

    /3/users/:{"result":"OK"; "version":3; "desc":"Weibo API Version 3, API index, user section"; "list":["/3/users/show", "/3/users/show_batch"]}

    • /3/users/show.api 接口说明(聚合下面所有的?)
    • /3/users/show.desc 接口描述

    {"result":"OK"; "desc":"展示用户基本信息"; "..."}

    • /3/users/show.method 接口调用方式说明,http get/post,rpc 等

    {"result":"OK"; "method":{"http_get":{"url":"http://api.weibo.com/3/user/show.json"; "..."}; "rpc_get":{"url":"motan://api.weibo.com/3/user/show.json"}}}

    • /3/users/show.constraint 接口约束说明,转码,频率限制,注意事项等

    {"result":"OK"; "char":"UTF8"; "html_escape":true; "rate_limit":10000; "must_login":true; "..."}

    • /3/users/show.params 接口参数说明

    {"result":"OK"; "params":{"source":"the source"; "uid":""; "screen_name":""; "has_extend":""; "..."}}

    • /3/users/show.output 接口正常输出说明

    {"result":"OK"; "output":{}}

    • /3/users/show.errors 接口错误输出说明

    {"result":"OK"; "errors":{}}

    • /3/users/show.example 接口使用例子(建议用法),包括一个真实可运行(沙箱环境,可以直接拷贝到curl或其它使用方式进行测试)的接口调用及返回例子

    {"result":"OK"; "example":{}}

    • /3/users/show.status 接口状态(当前,或指定时间段),SLA 数字等

    {"result":"OK"; "status":"succ"; "SLA":{"%90":"3ms"; "99%":"8ms"; "99.9%":"32ms"; "99.99%":"79ms"}}

  3. 实现阶段:
  4. 测试阶段:
  5. 使用阶段:

    根据 example 给的例子进行使用

  6. 升级阶段:
    • /3/users/show.update 接口所有升级说明,改动点等(小版本化?)
  7. 线上运行阶段:

未完,待续。

原创版权所有,由于未完成,请勿转载,多谢!