人生夜航时那一盏温暖的照明灯

注:upyun 技术博物馆及架构与运维大会活动约稿。转载请注明出处。

【技术博物馆】独家签名 ACM 教程,夜航时的明灯

我有一本《算法艺术与信息学竞赛》,作者签名版。不是普通的作者签售,只签了作者名字的那种,而是写了我的名字作为抬头,有单独的寄语。这是我的书架中最宝贵的一本书。

我的书架

那是 2003 年底,我在北京过的第一个冬天,感觉很冷。身体冷,心也冷。刚刚过去的 2003 年夏天,高考失利的阴影依然未能散去,考的不如预期,分不低,但因志愿填的“不服从专业调剂”而落榜,依靠 NOI (国家信息学奥赛)的获奖而被补录到北师大。来到大学,曾经所有骄傲的过往都成了往事,再也不是老师眼中的尖子生,再也不能轻松拿高分,我变成了以前自己嘲笑的对象:非常努力的学习,却怎么也追不上前面的优等生。

唯一的安慰是,我还在做编程比赛。只是比赛的名字从 NOI 换成了 ACM,比赛的形式从单人作战变成了 3 人的队伍,比赛用的语言从 Pascal 换成了 c/c++。在师大的 ACM 集训队里,牛人很多,我依然不是主力,只能非常努力地追赶,努力地保持自己的位置。每天上完课后,我都会跑到位于演播楼里的集训队训练室里默默地码代码。一度短暂地上过 poj(北大在线题库)排行榜第一页。真怀念那些年在 poj 上玩各种搞笑 Nick Name。

那段时间那些牛人队友们给了我很大压力,同时也给了我很大的动力。还有一些经常一起做题一起讨论的朋友,和一些久闻其名但一直无缘得见的前辈。《算法艺术与信息学竞赛》的作者刘汝佳老师就是这样一位前辈,虽然我们年龄相仿,但他大一时即参加 ACM/ICPC 国际大学生程序设计竞赛,获得 2001 年亚洲-上海赛区冠军和 2002 年世界总决赛银牌(世界第四),并担任 2002 年和 2003 年北京赛区裁判。

我对他仰慕已久,终于因偶然的机会见了一面。当时我们相谈甚欢,临别时我主动跟他说希望能送我一本他新写的书,由于书还没有正式出版,他答应说等第一批样书印出来就送我一本,让我帮忙找找有没有印刷错误。后来果然收到快递送过来的书,打开一看,还有作者的亲笔签名和寄语。

扉页签名

这本书陪伴我度过了后来 ACM 所有的时光。ACM 比赛时,每道题都会分配一种颜色的气球。当某个队伍做出这道题后,就会在他们的桌子上挂上一个气球。比赛过程中可以抬头看见别的队都挂了哪些颜色的气球,从而知道他们都做出了哪些题。《算法艺术与信息学竞赛》,和我一起见证了那些飘荡在赛场中的五颜六色的气球,那些 Accept,Wrong Answer 和 Time Limit Exceeded1,那些快乐和失落,那些荣耀和沮丧。

两年后的 2005 年冬天,ACM-ICPC 北京赛区,我带领的 Bnu Arbiter 队获得铜牌。走出赛场后,我登录 poj,最后一次修改签名“停止做题,离开 acm--2005 年 11 月 13 日  晴”。从 2000 年高一第一次接触编程,到 2005 年底以这样一种方式结束,我觉得自己还有遗憾,但是已经可以结束了。

后来的日子里,我依然写着各种各样的代码,从 PHP 到 C,从 Java 到 ObjC 。偶尔我还会想起那些算法,虽然我再也没有写过它们。

———————–

本次,唐福林将在“UPYUN 架构与运维大会『北京站』”的架构产品专场发表主题演讲,UPYUN 也将在大会现场展出这本《算法艺术与信息学竞赛》——唐福林人生夜航时那一盏温暖的照明灯。

注1:AC,WA,TLE 等ACM专有名词含义解释见 http://poj.org/page?id=1000

大会详情:http://upyun-archops-beijing.eventdove.com/

转:从开发人员到管理者,我学到了什么

技术工程师的晋升之路

技术工程师的晋升之路大体上可以分为两个方向,一是在技术方向上作为Individual Contributor(IC)继续深造,二是以管理者的身份管理工程师团队。IC这条路比较单纯,能走多远主要取决于个人的技术实力与经验。与IC相比,转型为管理者可以说是一种颠覆性的变化。管理者的工作不再是以个人或项目的成功作为主要目标,而是专注于整个团队的成功。这种目标的变化将直接改变管理者的心态与工作方式,以及与他人的互动方式。可以说,向管理者的转型是一个具有更大挑战性的选择。

David Haney来自于Stack Overflow团队,他是开源软件dache的作者,也曾在InfoQ上发表文章。今年二月,Haney被提升为工程经理。在担任经理的几个月中,他感受到了许多完全不同的挑战。在近期的一篇博文中,Haney详细地记录了这段时间以来所领悟的管理方法心得。

信任第一

Haney认为:要成为一个成功的管理者,首要条件就是取得团队成员的信任。因为没有谁愿意为一个他所不信任的人工作,这一点往往是员工离职的首要因素,其重要性显而易见。但信任不是凭空得来的,你必须表现出你是一个值得依赖的管理者。在这一点上Haney有一定的优势,因为他已经在作为团队成员的一年时间内凭着良好的表现博得了大家的信赖。如果没有共同工作的经历,那么就需要更大的耐心去逐步建立你的信任度。

为了取得团队的信任,Haney特别列举了以下几个要点,这是作为一名优秀的管理者最重要的品质:

  • 与员工之间的对话是一种隐私,不要在人背后嚼舌根。
  • 保持一种开放的姿态,让员工感到可以无所顾忌地与你谈论任何话题。
  • 一言九鼎,说到的就要做到。
  • 不要承诺一些你办不到的事,较好的说法是:“我尽力而为,但无法保证一定能做到。”
  • 绝对不要撒谎!员工不仅会失去对你的信任,而且一见到你就会远远躲开。
  • 不要逃避困难,即使有些事情确实令你感到不舒服,但逃避只会令问题失控。
  • 不要对员工宣泄你的情绪,也不要在背后指指点点。如果确实需要宣泄一下压力,也应当寻找一种合适的渠道,而不是冲着你的员工发火。
  • 公开表扬,私下批评。不要在整个团队面前让人感到难堪。
  • 保持客观与公正,不要在团队中搞特殊化待遇。

人类无法像代码一样伸缩自如

作为开发者,Haney能够写出具有高度可伸缩性的代码。但一名管理者却无法像代码一样做到伸缩自如。正如你很难在20个与会者之间达成一致一样,要有效地管理好20个员工也是一件难以完成的任务。Haney认为,向管理者直接汇报的员工数量最好在4至10人之间。一旦超出了这个数量,你就无法对每个人都给予足够的关注,这会让员工感到被忽视,乃至怀疑自己在团队中的重要性。同时过多的人反而会降低工作效率,因此最好是启用另一名管理者来分担你的工作。

将处理人际关系作为工作重心

对于开发者来说,人际关系往往会对工作产生干扰,而成为一名管理者之后,这一点就成为了你的工作重心。在Haney看来,人际关系不仅限于你的团队,还包括与其它团队打交道,这同样也应当具有较高的优先级。同时应当尽量表现出一定的灵活性,以配合其它团队的时间安排。

另一项重要的工作是与员工定期进行一对一的谈话,让员工借此机会表达他们的想法、意见、困难,并且通过员工的反馈改进管理者的工作。Haney每隔三周就会与每位员工进行一次45分钟的谈话,在他看来,这对他的工作产生了很大的帮助。

要注意的是,如果无故地推迟或取消原定的谈话,会令员工感到遭受轻视。因此要尽可

能避免这种情况的出现,它会使员工的士气受到很大的影响。

接受令人不安的谈话

在Haney看来,当你还是一位开发者的时候,你可能会时常与其他人抱怨一些令你感到不爽的人或者公司政策,这很正常。而作为经理,你的工作就是帮助他们直接处理好这些问题。你应当鼓励员工之间开诚布公地对话,而不是与同事在私底下嘀咕。同时,优秀的管理者也应当为员工树立一个良好的榜样。

如果有某位员工直接找到你投诉某人,你应当做的是让双方坐到一起,让他们坦诚地表达他们的感受。然后居中调解,以解决他们的问题。这种对话开始时往往会令人感到尴尬与不安,但你必须接受它,并让它成为团队文化的一部分。Haney认为,经过数次这种对话之后,员工就应当能够做到在无需他参与的情况下自行开展这种对话。重要的是确保问题最终能够得以解决,而不是随着时间的推移而变得越来越严重。

衡量你的成功并不容易

开发者有一个很大的优势,他们的工作都是为了解决某个特定的问题,其范围总是确定的,并且其成果总是能够很快地显现出来。与之相比,管理者的工作似乎没有那么具体,因为这些工作的影响往往是无形的,例如员工的想法、感受、软技能以及他们的总体成长。并且这些工作没有一个具体的开始与结束时间,因为学习与进步是永无止境的。

Haney认为,管理者的任务总是与帮助员工成长有关,而这些工作的成就感通常来自于他人的反馈。比如有员工对于你的建议与反馈表示感谢,或是有人告诉你他看到团队产生了积极的变化,以及来自于团队外部的称赞等等。这些反馈能够有效地表现出你的工作是否达到预期,但管理者需要保持足够的耐心。

没有人了解你的实际工作

开发者的工作是非常透明的,其他人可以很清楚地了解你的工作内容和完成的任务,代码库里的签入记录也会清晰地反映出你的工作。与之相对的是,管理者总是在做一些幕后的工作,这很容易给别人造成一种印象:这家伙根本什么事都不干!而Haney认为,这种误解是因为他与员工的谈话往往是私下进行的,他坚持通过一对一的形式与员工谈论沟通技巧以及人际关系上的问题。由于这种交流的私密性,使其他人无法了解管理者的工作情况,因此产生了管理者不干活的误解。

在Haney看来,只有通过团队成员的各方面不断提高,才能够表现出管理者的工作价值。这是一个缓慢的过程,但当团队发现团队之间的人员互动正朝着积极的方面转变时,他们就会体会到管理者的辛勤工作。随着员工的成长与进步,团队将变得愈加强大与高效,员工们也会承认你为团队所做出的贡献。

你的成功就是他人的成功

在这篇博文的最后,Haney简单地总结了他的观点。管理工作最重要的是取得信任,通过直接的对话解决问题(虽然这种对话有时会令人不安),在幕后完成你的工作,同时保证工作方式的直接与公正性。管理工作具有很高的挑战性,同时也能够带来极大的回报。

最后,Haney引用了动画“飞出个未来”中的一名名言,它非常贴切地表现出了管理者的工作特性。

“如果某件事你做的足够出色,那么人们甚至不会感觉到它。”

 

转自 infoq http://www.infoq.com/cn/news/2015/08/developer-to-manager

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

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

6行代码实现一个 id 发号器

id 发号器的问题,@一乐 的这篇文章说的很透彻了:http://weibo.com/p/1001603800404851831206 但参考实现就显得有些复杂。最近在雪球工作中正好需要用到发号器,于是用 Lua 在 Redis 上实现了一个最简单的:


-- usage: redis-cli -h 10.10.201.100 -p 10401 EVAL "$(cat getID.lua)" 1 XID:01:02
local arg = KEYS[1]
-- project id must has 2 digits, 01 - 15
local pid = tonumber(string.sub(arg, 5, 6))
-- instance id must has 2 digits, 01 - 15
local iid = tonumber(string.sub(arg, 8, 9))
local idnum = redis.call("INCR", "ID_IDX") % 65536
local sec = redis.call("TIME")[1] - 1420041600
return sec*16777216 + pid*1048576 + iid*65536 + idnum

解释:

  • id 总长度 52bit,为了兼容 js,php,flex 等语言 long 类型最长只能 52 bit
  • 最高 28 bit 为秒级时间戳,因为位数限制,不能从 1970.1.1 开始,-1420041600 表示从 2015.1.1 开始,大约可以使用10年(3106天)
  • 接下来4个bit为 project id,客户端传入,区分业务
  • 再接下来4个bit为 instance id,HA 用的,支持最多16个instance。如果业务只需要“秒级粗略有序”,比如发微博或发评论,则可以多个 instance 同时使用,不需要做任何特殊处理;如果业务需要“因果有序”,比如某个user短期内快速做的多个操作必须有因果顺序(程序化交易,必须先卖再买,几个毫秒内完成),那么就需要做一些特殊处理,要么用 uid 做一致性hash,或者像我们这样偷懒:一段时间内固定使用一个 instance
  • 最低16个bit是 sequence id,每个 instance 支持每秒 65535 个 id。bit数再大,redis 该成为瓶颈了。
  • twitter和微博都是把 instance id 写死到 server 端,这样 server端就变成有状态的了:16个instance,每个 instance 都与其它的配置不一样。在雪球我们不希望 server 端有状态,于是设计成 instance id 由客户端传入,server 端退化成一个普通的 redis cache server (不需要 rdb,不需要 aof,宕机重启即可)
  • 几个注意点:宕机不能自动立即重启,必须间隔1秒以上,避免 sequence id 重复导致 id 重复;迁移时必须先 kill 老的 instance,再启动新的,也是为了避免 sequence id 重复。

id 发号器说简单也确实挺简单。任何一个技术点,只要理解了本质,大约都是这么简单罢。

转:Coding 孙宇聪:《人,技术与流程》

我先做一下自我介绍,我是 07 年加入的 Google,在 Moutain View 总部任Google SRE,今年年初回国加入 Coding。

在 Google 我参与了两个 Project, 第一个就是 Youtube, 其中包括 Video transcoding, streaming 等,Google 的量很大,每个月会有 1PB 级别的存储量,存储,转码后,我们还做 Golbal CDN ,最大的时候峰值达到 10 TB,我们在全球 10 万个节点,每台机器都是 24 核跑满状态。然后我从 Youtube 团队离开加入 Google Cloud Platform Team。我们做的主要工作是管理 Google 全球的机器,大概有 100 万台左右。我离开 Google 之前做的就是 Omega Project,一个集群管理系统,管理 Google 整个云平台的任务调度,协作。可能很多人会说“然并卵”,因为这是都是国内不存在的网站。(笑)

 

从 Google 回来在 Coding 做 CTO,对我来说也是人生的一个大改变。最近我在知乎看到一个好问题,“从大公司离开到小公司当 CTO 是怎样的体验”,我摘抄了一个好答案:“ 顶着 CTO 的名号,招聘,培训,鼓励程序猿,拉网线,查机房,装系统这都是 CTO 要做的事;讨论方案,推方案,定方案,确定进度,拖延进度,安抚程序猿,挨老板骂,安抚老板,这也是 CTO 的职务。” 不包括 Coding,而我的工作还包括 Coding,很伤心。(笑)

 

所以我把这个问题作了归类:

 

CTO 是什么:

第一、他在公司里是一个鼓励师,(我这个形象做鼓励师其实也不是很合适)

第二、可能是网管,这个我确实干过,接网线,架服务器,

第三、可能就是受气包,程序员对我很不满,老板对我也很不满。

 

那我们把这三个角色做为一个对应:

 

第一、就是研发人员上的管理,包括怎么招人合适,怎样让这些人更好地协作;

第二、是研发技术和研发环境的管理,就是你有了这些人怎样让大家更好地协作在一起,更高效地开发;

第三、就是研发流程的管理,怎样让公司这台机器转得更加灵活,顺畅。

 

那这里我提出了三点要素,人,技术与流程,这是一个公司研发体系中不可缺少的关键要素。

 

那我们就先来说一说人,创业公司都需要全栈工程师。这个全栈工程师长的什么样子呢?一般来说他就像是一个大侠,踩着白马,手持金枪,什么问题都能解决,一人单挑好几百人。可是有些时候我们招不到全栈工程师,我们就招了一些全干工程师,这也是我们最近发明的一个词汇。这个人就是他什么都干,写完网站后写 iOS 最后写安卓。这样的人也很难找,更难的是把这些招来的人员合理地安排在一起,合理地组织起来协作。

 

要想解释 Coding 在研发人员管理上的演变,我首先讲一下 Coding 的服务架构,这是我们去年 5 月份(第一版本上线) 的服务架构,非常简单只有一个 core 程序,经过一年多的演变,现在变成了这个样子。我特意写的字很小,因为不想让大家看清楚,这里面其实错综复杂也不一定对。一个简单的架构是如何一步步演变复杂的呢? 这里我想先介绍一个康威定律,就是

 

“任何一个设计系统的组织,最终产生的设计都等同于组织之内、之间的沟通结构”。

 

回想起 Coding 以前的交流方式,老板说”我们要做一个新的功能”,大家就开始把功能拆,前端需要怎么改,要改谁来做,后端要不要改,改的话谁来,服务层,DB 层,测试,部署,每个人做的事情都不一样,每个人都只做一件事情,每个人都做流水线上的一个螺丝钉。这样会造成什么情况呢?前端程序员在等后端程序员,什么时候把接口接上了再开工,后端程序员在等数据库,什么时候加上这个字段我再开工,测试和部署就更在等了。每次一开项目进度会议时,老板问功能做的什么程度了,前端说后端没写好接口,后端说数据库还没搞定,经常是这个状态。

 

那么 Coding 今天采用了一个组织方式,我们叫他全员全栈,这个全栈的意思和普通意义上的全栈意义不是很一样,这个全栈我们指的是产品,功能上的全栈。具体是怎么实现的呢?其实对任何一个比较合格的程序员来说,语言并不是他的瓶颈,也不应该作为他的限制条件。我们更希望的是我们公司里更多的是这种全栈工程师。我们在做任何功能的时候,我都会和这个人说你要负责这个功能从头到尾的实现,你需要改前端就改前端,需要改后端就改后端。做这个事情可能刚开始有一定难度,后端工程师对前端不是很熟悉,前端工程师对后端不是很熟悉,那我们就用一些其他方法去克服它。这个过程就强迫了组织内部的 Knowledge sharing

 

很多大公司的工程师可能只关心这一小块,比如说前端工程师只写了这一点前端的东西,后端我一概不管,原因是我不敢动,大家也不让我动。在小公司里面,每一个人都要了解公司的使用的各种技术,我们共同努力去降低系统的复杂度。 全员全栈是我们公司的一个战略方向,我们希望每个人都有主人翁的意识,在做需求的时候他可以从前到后一直跟到完,但是想要把全员全栈做好就要做研发环境和研发工具的调整,接下来我们讲我们是如何通过技术手段来协助的。

 

技术手段在一个研发环境里有三个要素:

第一个就是代码如何管理,如何操作

第二个就是运行,前端后端代码运行起来要求繁杂,如何调节。

第三点就是你写好之后怎么部署、运维。开发环境和生产环境经常打不通。

 

为了代码这方面讲好,我先来给大家介绍下在 Google 当码农是一种怎样的体验。Google 最强大的就是他的研发体系经过了 10 多年的积累和锤炼,现在是一套非常高效的体系。那这个“不存在”的公司是怎样管理自己的代码呢?

 

第一点,整个公司只有一个仓库,一个版本控制系统。这个东西说起来简单,其实很难实现的,很多人觉得这个东西好像可行,但他细想难道我们用的都是SVN?很明显不是,那 SVN 没有用,Git 也没用,那如何做到只有一个仓库呢?只有一个仓库带来的问题就是所有程序代码都在一个目录下,如果你有一个特别大的硬盘,你把整个公司全都 down 下来,他就是一个目录下的很多子目录,可能几十级子目录,可能整个公司代码有几百 G。那如何同步,管理,操作,这个都是 Google 的独门秘方。

 

这个有什么好处呢?

第一,你可以看到任何人的代码,你可以看到这个服务是如何实现的,为何会出现异常,会有什么错误;

第二,它带来一种更高效的复用方式,比如我写了一个小程序,我 Gmail 的一个程序用了一段 bigtable 的代码,那我直接在代码里引用过去即可,反正大家都在一个目录下,编译环境也一样,有了这个 repo 之后,还有一套编译系统,这个编译系统能做到一键编译任何程序,我运行一个 Gmail 也是一行程序,一个命令是就是编译程序 build 加上 Gmail 的路径即可编译,编译 Go,Python,C++全都是一个命令,不用你去关心底层是如何实现的,运行起来即可。有了这个之后就做到了部署,开发很容易,就是说如果我写业务代码需要用到一个服务,那我可以很容易地启动这个服务,还可以对该服务做小改动后再次运行起来,这都很容易。

 

这里说多了大家又会说:道理我们都懂,但是如何实现呢?

 

那 Coding 怎么组织我们的代码呢?因为我们确实没有 Google 集中式的代码服务,为了调整我们的代码结构,让大家更好地复用,引用彼此的代码,又因为我们本身提供 Git 服务,那我们也是用了 Git 仓库的方式,但我们每个项目都是一个独立的仓库,比如我们前端代码放一个仓库里,后端代码放一个仓库了,各个服务都放在不同仓库里。

 

这种方式就产生一个问题,如何同步?

 

我们用的是 Google 开源的东西叫 Android repo,大家做安卓开发时可能用到过,这个 repo 是什么意思呢?就是他定义了一个 Workspace,这个 Workspace 有一个固定的格式/结构,然后你统一用 repo 这个工具去 sync,这个 repo 应该到哪个 commit ,那个 repo 应该到哪个 commit,全公司都用同样一种 Workspace 的方式,就保证了每个人看到的代码都是一致的。

 

做了这个代码结构定义的好处就是:

第一,我们有代码阅读功能,比如我们 Coding 的代码阅读可以根据这个 Workspace,可以看到每个项目的代码,互相引用的状态;

第二就是质量分析,你可以针对这个 Workspace 做质量分析。这个 Workspace 有一个 repo.sh 这个是一个命令,比较关键的是有一个 default.xml 是这个命令的配置文件,然后大家打开 Workspace 运行 repo sync 即可,它会自动更新每个组件到最新版本。我们也鄙视 xml 但是没办法,这个程序就是这么写的,其实很简单,里面主要定义了很多 Project,这个 Project 可能是代码仓库的路径映射到了本地的路径,它有一个比较先进的功能就是它可以同时 sync 好几个 Project,就是你一敲 repo.sh sync -g=4 就是开 4 个线程去同步。实践起来这个是很容易的,有了这个东西后打开了这个局面,为大家下一步搞开发环境做了基础铺垫。

 

工具有了,那我们真正想要的开发环境是什么呢?

 

我从 Google 的得来的感触就是我们想要一个统一的,代码化的,可复制的,可重现的一个开发环境。

 

第一、每当新同事来公司后,他带着自己的电脑或用公司的电脑,他所用的工具是不一样的,环境是不一样的,怎样能让公司的代码在自己机器上跑起来其实是一件比较困难的事情。大家的解决办法可能是通过写文档,但这是一件非常痛苦,浪费时间的事情。

 

第二就是说如果你这个开发环境如果不可以复制,重现,那你的自动化测试怎么做?不能说某个人手动配一下,跑一下,今天挂明天又改吧?

 

那如何做到统一化的开发环境?我们做法就是定义了一个通用的接口,我认为这个编译系统的内部实现是无所谓的,怎样都可以,但它的接口比实现要重要一百倍。大家可以想下如果你每个程序,每个组件都是用同一个方法去 Build,去运行,这是一种怎样的体验?我们定义了一个 build.sh 和 package.sh ,这个 build 就是说我用 Java,Python,Ruby也好,我就定义 build.sh 它最后能产生一个结果能把这个东西 build 好,对我来说我不关心下面的程序怎么来写,但我只要 build 它,我改了一行代码它能够 build 出新的东西即可。这是我们现在比较土的做法。

 

Google 最近开源了 bazel ,它是用 Java 写的编译工具,它实际上就是 build 命令,之后两个反斜杠代表整个 Workspace 的根,然后 coding 是一个 project,server 是 project 的 target。有了这个东西之后其实你 build 任何项目你都考虑的是说它逻辑上的分层而不是物理上的分层,逻辑上的分层就是我要 build 一个 coding server,那这个 coding server 可能里面引用了其他的第三方库,头文件,Ruby 程序,Java 程序都无所谓,我只要说我能 build 一个 coding server 来即可,那这个 bazel 更好的就是它可以自动处理递归依赖,就是你这个 rule 可以依赖到另外一个 rule。

 

有了这个编译后我们还需要有可复制,可重用的开发环境,那这个开发环境我们怎么做的呢?

我们用的是 Vagrant 和 docker。Vagrant 是 VM 的一个管理工具,它可以生成一个新的 VM 来,这个 VM 使用代码来定义的, 然后把每一个服务作为一个 docker 服务在 VM 来运行的。

 

Vagrant 其实做了三件事:

第一件事是它从一个指定的地方下载 Base Box,Base Box 是我们自己做的,比如说是一个 Ubuntu 镜像加一些本地依赖.

第二就是它支持脚本定义,你可以运行 shell 脚本来定制,再选一个所谓的 Provider,这个 Provider 就是你可以比如说本地是 Virtual Box 的 Provider,远端和很多云厂商可以对接。

 

进行完这两个操作之后,他就产生了一个 VM,这个 VM 就是一个你可以一键 ssh 进去,它自动把你所有东西都搭配好,这是一个属于你的开发环境,那有了这个东西之后,整个公司可以有一套一致的开发环境,因为是一个 VM,它在哪台机子上运行的方式都一样,所有的依赖库都可以放进去,所以最后的结果就是我们有一个几G的镜像放在我们内网里,每个新同事来了后我们让他装一个 Vagrant 或 Virtual Box,然后他敲一个命令,自动把镜像下载,启动,然后一键把整个 Coding 的项目在他的机器上跑起来。

 

做到了这种可重用的开发环境,我们还做到了所谓的一键运行。一键运行是和开发另外一个层面的东西,它既不关心这个东西是怎么构建出来的,我只关心我启动这个服务,比如 Coding 开发环境的时候我需要哪些服务,那我们采用的就是一个自己写的脚本来给 Docker 做编排,这个配置文件里定义了一些 Job,每个 Job 有一些 image(运行程序的版本、环境变量等)。其实很多时候我们都是用 go run 命令一键启动很多服务和镜像,达到了我们刚才说到的统一的代码化的,可复制,可重现的开发环境。

 

技术工具是我最怀念 Google 的地方,因为这些工具带来很多好处: 首先他鼓励公司内部的协作, 每个人写程序时不是首先想到自己搞一个小东西出来,我们更多的时候是去看公司里面其他人做了一个什么东西,它是怎么实现的,能不能引用过来,能不能使用,能不能把常用的类库都抽离出来;

第二它可以让新人很快上手,我们新同事也好,老同事也好,更多的场景是老同事换 Project,以前可能只是使用者,瞬间就可以变成开发者。开发环境的无缝切换无形中降低了很多公司运行的成本。

第三点就是它让自动化变为可能。刚才我们是装了 Vagrant 执行一个命令就搞定了,这个东西同样可以作为自动化的东西来实现,比如每次我们在发 Code Review 时,我们后台就可以自动启动一个新的 VM,把所有东西下载后运行测试,最后给出改动对错,造成影响等结果,这都是可以自动化的。这点我觉得是一个关键点,接下来有了这个环境之后你有了质量分析,工具后它才能够进行,才能有更多生产力工具接入进来。

 

最后我再讲一讲流程的管理。

 

作为一个 CTO,我们小小的梦想是持续交付更好的软件。老板说你作为一头老黄牛就得不停地往前跑,你也能够按时完成任务。

 

公司内部有很多实现方式:第一就是 Code Review, 这个流程工具毁誉参半:

 

第一点就是这个 Code Review 绝对是脑抽检测器,每个人都有脑抽的时候,评审期就是缓冲期,让你想一想你真的是要做这件事情吗?你让别人帮你检查一下是很有好处的。

第二是Code Review 是一种很好的知识分享的方式。我一个人在写这个功能时我把代码交给别人去看,那另一个人就会对这个有了解,将来他可能来做你的功能,你换过去做他的工作。鼓励公司内部的知识共享是很重要的。

第三它是一个 idea generation ,大家在做知识共享时很容易发现自己另外一个地方也发现了这个问题,我是这么解决的,你为什么不这么解决?有什么更好的办法解决吗?Code Review 为促进交流来做这件事。

 

那 Code Review 做的不好时有哪些方面呢:

 

第一就是程序员鄙视链,就是老程序员对新程序员很鄙视,说你写的东西太次了,我都懒的看。这个是很不好的一种行为,我们一定要避免这种东西。我们做了一些程序上的要求,比如你每次 Review 时必须把整篇文档都看完,不能说我看了一行发现太烂了不看了,你改好了我再来看,这是不允许的。

 

第二我们讲到 Ownership ,就是你在 Code Review 时这个代码属于谁的,我们讲代码评审是谁写代码谁负责,你写了 rm -rf 你就要对此负责,我看了之后觉得你写的有道理,但我没看出 bug, 这是你的问题不是评审者的问题。这是我们在Google,Coding 实践出来的经验,

 

第三点就是拥抱变化,就是我原来可能某员工写了一个程序,他觉得我写的这个程序实在是太好了,你们不要改,一改我就看不懂了。所以他对任何改动都非常抵触,这也是错误的,不论在 Google 还是 Coding ,我们坚持的一大原则就是说我们有 Business case 你就可以改代码。你只能去把这个代码写得更好,不能不允许他改,这个是关键,你所写的代码都是属于公司的,那公司怎么能用代码来做更好的事情呢,你要讲清你的道理,你为什么要改这段代码,改了有什么好处,把这个东西变成了技术上的讨论。而不是一个职责,权限上的讨论。

 

所以我觉得这几点是把 Code Review 做的更好比较关键的几点。接下来说一下 Release Schedule 的问题。

 

因为创业公司和大公司不一样,每个公司都有自己的 Release Schedule,以前我们基本靠吼,今天我们要上线,所以全员就上线,我们今天要 Release 那就 Release,你如果和老板吼今天上不了线,老板想了想那就算了吧,明天上,并不是很严肃。我们在内部推行的改革是我们要把冲刺变成长跑,我们已经创业一年多了,不能总是冲刺状态了。我们要变成长跑,能够持续不断地交付高质量的软件,而不是每天上完线后加班到后半夜才把它改好。

 

方式很简单,就是一周钉死 Release 两次,我说服老板每周两次就够了,三次就多了。Google 一个月才 Release 一次,大项目半年才 Release 一次,我们创业公司能做到这点已经很好了。一周 Release 两次是什么概念,星期一就要上 Staging 测试环境,星期二就要 Release,星期三又要上 Staging,星期四又要 Release,留给大家周五写写程序,修修 Bug。其实一周 Release 两次也很频繁,但是保证现阶段我们的转化率,我们采用了这个方式。把这个东西摊在桌面上来讲有什么好处呢,他让大家可以计划我这个功能下周二上还是下周四上,你和项目经理打仗的时候有理有据了,他说你看你是星期二上还是星期四上,程序员说星期二可能搞不定,那我们星期四来吧,一来二去就把这个冲刺改成了长跑。

 

接下来还有一点,我们要区分 Feture Team 和 Infrastructure Team, Infrastructure Team 也是我们以前谷歌的一个名词。他讲的是什么意思呢?就是说虽然全员都在做业务逻辑,但我们一定要抽出一定时间来推行技术演进,你不能说每天我上来就是写代码,拷贝粘贴,这样搞得大家所有东西都非常混乱。我们在内部提出了三个点:

 

第一叫 Coding One,就是我们公司内部所有的项目,所有的技术,所有的服务都争取用同一套方式来运行。比如说用同一个 Java 版本。这就是一件很难的事情,我觉得好多公司都做不到,用同一个第三方类库,这也很难,所以用起来类库都不一样,但干起来事情是一样的。这样每个人代码看起来都好像是差不多,但是又不一样,所以 Coding One 就是解决这个问题,就是 Java 版本,第三方库的版本,第三方库的类型,编译方式,运行环境,启动方式,都应该是一样的。这样会降低很多程序员之间内耗,提高大家的效率。

 

第二就是 Coding Two,刚创业的时候大家觉得我们都用一台机器,所有东西都跑在上面,这个东西如果坏了那就全挂了。每次更新大家都觉得太危险了,我们还是后半夜进行吧,十二点还不够半夜,我们三四点钟进行吧。因为你这个东西没有备份,没有任何的灰度。我们叫 Coding Two,以前是从零到一的过程,现在要把从一到二的过程迈过去。从一到二的意思是你这个东西可以是多份的,你这个东西挂了那边还可以继续顶上,这个程序你做发布的时候你可以先发布给自己用,用好了再发给用户,这才是负责任的方式。

 

第三点: Coding CI 是我要讲到的最后一点,最后我们的终极目标,就是所谓的 Push On Green ,意思就是你提交了代码一分钟之内只要所有的测试都跑动过,马上就上生产环境,大家可以想一想在自己公司里能做到这一点吗?如果不能那是为什么?你程序员写了代码,我们认为程序员写的代码都是善意的,你经过了 Code Review,业务流程上都没问题,他为什么不能直接去上生产环境呢?Push On Green 就是终极考验,来考验你这个研发体系能不能做到这一点,如果能做到这一点你才是好的研发体系。

 

这就是我和大家分享的三点,人,技术和流程,我认为这在一个公司里是一个齿轮一样组合起来的,公司我们可以说是一个大机器,如果将机器润滑,将齿轮转得更好,结合得更紧密,这就是我们想追求的目标。谢谢大家。

转自 扣钉Coding 微信公众号

转:iOS应用架构谈 开篇

转自:iOS应用架构谈 开篇  http://casatwy.com/iosying-yong-jia-gou-tan-kai-pian.html

缘由

之前安居客iOS app的第二版架构大部分内容是我做的,期间有总结了一些经验。在将近一年之后,前同事zzz在微信朋友圈上发了一个问题:假如问你一个iOS or Android app的架构,你会从哪些方面来说呢?

当时看到这个问题正好在乘公车回家的路上,闲来无聊就答了一把。在zzz在微信朋友圈上追问了几个问题之后,我觉得有必要开个博客专门来讲讲一些个人见解。

其实对于iOS客户端应用的架构来说,复杂度不亚于服务端,但侧重点和入手点却跟服务端不太一样。比如客户端应用就不需要考虑类似C10K的问题,正常的app就根本不需要考虑。

这系列文章我会主要专注在iOS应用架构方面,很多方案也是基于iOS技术栈的特点而建立的。因为我个人不是很喜欢写Java,所以Android这边的我就不太了解了。如果你是Android开发者,你可以侧重看我提出的一些架构思想,毕竟不管做什么,思路是相通的,实现手段不同罢了。

当我们讨论客户端应用架构的时候,我们在讨论什么?

 

其实市面上大部分应用不外乎就是颠过来倒过去地做以下这些事情:

    ---------------     ---------------     ---------------     ---------------
    |             |     |             |     |             |     |             |
    | 调用网络API  | --> |   展现列表    | --> |  选择列表    | --> |   展现单页   |
    |             |     |             |     |             |     |             |
    ---------------     ---------------     ---------------     ---------------
                               ^                                        |
                               |                                        |
                               |                                        |
                               ------------------------------------------

简单来说就是调API,展示页面,然后跳转到别的地方再调API,再展示页面。

 

那这特么有毛好架构的?

 

非也,非也。 —- 包不同 《天龙八部》

App确实就是主要做这些事情,但是支撑这些事情的基础,就是做架构要考虑的事情。

  • 调用网络API
  • 页面展示
  • 数据的本地持久化
  • 动态部署方案

上面这四大点,稍微细说一下就是:

  • 如何让业务开发工程师方便安全地调用网络API?然后尽可能保证用户在各种网络环境下都能有良好的体验?
  • 页面如何组织,才能尽可能降低业务方代码的耦合度?尽可能降低业务方开发界面的复杂度,提高他们的效率?
  • 当数据有在本地存取的需求的时候,如何能够保证数据在本地的合理安排?如何尽可能地减小性能消耗?
  • iOS应用有审核周期,如何能够通过不发版本的方式展示新的内容给用户?如何修复紧急bug?

上面几点是针对App说的,下面还有一些是针对团队说的:

  • 收集用户数据,给产品和运营提供参考
  • 合理地组织各业务方开发的业务模块,以及相关基础模块
  • 每日app的自动打包,提供给QA工程师的测试工具

一时半会儿我还是只能想到上面这三点,事实上应该还会有很多,想不起来了。

所以当我们讨论客户端应用架构的时候,我们讨论的差不多就是这些问题。

这系列文章要回答那些问题?

 

这系列文章主要是回答以下这些问题:

  1. 网络层设计方案?设计网络层时要考虑哪些问题?对网络层做优化的时候,可以从哪些地方入手?
  2. 页面的展示、调用和组织都有哪些设计方案?我们做这些方案的时候都要考虑哪些问题?
  3. 本地持久化层的设计方案都有哪些?优劣势都是什么?不同方案间要注意的问题分别都是什么?
  4. 要实现动态部署,都有哪些方案?不同方案之间的优劣点,他们的侧重点?

本文要回答那些问题?

 

上面细分出来的四个问题,我会分别在四篇文章里面写。那么这篇文章就是来讲一些通识啥的,也是开个坑给大家讨论通识问题的。

架构设计的方法

 

所有事情最难的时候都是开始做的时候,当你开始着手设计并实现某一层的架构乃至整个app的架构的时候,很有可能会出现暂时的无从下手的情况。以下方法论是我这些年总结出来的经验,每个架构师也一定都有一套自己的方法论,但一样的是,不管你采用什么方法,全局观、高度的代码审美能力、灵活使用各种设计模式一定都是贯穿其中的。欢迎各位在评论区讨论。

第一步:搞清楚要解决哪些问题,并找到解决这些问题的充要条件

 

你必须得清楚你要做什么,业务方希望要什么。而不是为了架构而架构,也不是为了体验新技术而改架构方案。以前是MVC,最近流行MVVM,如果过去的MVC是个好架构,没什么特别大的缺陷,就不要推倒然后搞成MVVM。

关于充要条件我也要说明一下,有的时候系统提供的函数是需要额外参数的,比如read函数。还有翻页的时候,当前页码也是充要条件。但对于业务方来说,这些充要条件还能够再缩减。

比如read,需要给出file descriptor,需要给出buf,需要给出size。但是对于业务方来说,充要条件就只要file descriptor就够了。再比如翻页,其实业务方并不需要记录当前页号,你给他暴露一个loadNextPage这样的方法就够了。

搞清楚对于业务方而言的真正充要条件很重要!这决定了你的架构是否足够易用。另外,传的参数越少,耦合度相对而言就越小,你替换模块或者升级模块所花的的代价就越小。

第二步:问题分类,分模块

 

这个不用多说了吧。

第三步:搞清楚各问题之间的依赖关系,建立好模块交流规范并设计模块

 

关键在于建立一套统一的交流规范。这一步很能够体现架构师在软件方面的价值观,虽然存在一定程度上的好坏优劣(比如胖Model和瘦Model),但既然都是架构师了,基本上是不会设计出明显很烂的方案的,除非这架构师还不够格。所以这里是架构师价值观输出的一个窗口,从这一点我们是能够看出架构师的素质的。

另外要注意的是,一定是建立一套统一的交流规范,不是两套,不是多套。你要坚持你的价值观,不要摇摆不定。要是搞出各种五花八门的规范出来,一方面有不切实际的炫技嫌疑,另一方面也会带来后续维护的灾难。

第四步:推演预测一下未来可能的走向,必要时添加新的模块,记录更多的基础数据以备未来之需

 

很多称职的架构师都会在这时候考虑架构未来的走向,以及考虑做完这一轮架构之后,接下来要做的事情。一个好的架构虽然是功在当代利在千秋的工程,但绝对不是一个一劳永逸的工程。软件是有生命的,你做出来的架构决定了这个软件它这一生是坎坷还是幸福。

第五步:先解决依赖关系中最基础的问题,实现基础模块,然后再用基础模块堆叠出整个架构

 

这一步也是验证你之前的设计是否合理的一步,随着这一步的推进,你很有可能会遇到需要对架构进行调整的情况。这个阶段一定要吹毛求疵高度负责地去开发,不要得过且过,发现架构有问题就及时调整。否则以后调整的成本就非常之大了。

第六步:打点,跑单元测试,跑性能测试,根据数据去优化对应的地方

 

你得用这些数据去向你的boss邀功,你也得用这些数据去不断调整你的架构。

总而言之就是要遵循这些原则:自顶向下设计(1,2,3,4步),自底向上实现(5),先测量,后优化(6)。

什么样的架构师是好架构师?

 

  1. 每天都在学习,新技术新思想上手速度快,理解速度快

做不到这点,你就是码农

  1. 业务出身,或者至少非常熟悉公司所处行业或者本公司的业务

做不到这点,你就是运维

  1. 熟悉软件工程的各种规范,踩过无数坑。不会为了完成需求不择手段,不推崇quick & dirty

做不到这点,你比较适合去竞争对手那儿当工程师

  1. 及时承认错误,不要觉得承认错误会有损你架构师的身份

做不到这点,公关行业比较适合你

  1. 不为了炫技而炫技

做不到这点,你就是高中编程爱好者

  1. 精益求精

做不到这点,(我想了好久,但我还是不知道你适合去干什么。)

什么样的架构叫好架构?

 

  1. 代码整齐,分类明确,没有common,没有core
  2. 不用文档,或很少文档,就能让业务方上手
  3. 思路和方法要统一,尽量不要多元
  4. 没有横向依赖,万不得已不出现跨层访问
  5. 对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件
  6. 易测试,易拓展
  7. 保持一定量的超前性
  8. 接口少,接口参数少
  9. 高性能

以上是我判断一个架构是不是好架构的标准,这是根据重要性来排列的。客户端架构跟服务端架构要考虑的问题和侧重点是有一些区别的。下面我会针对每一点详细讲解一下:

代码整齐,分类明确,没有common,没有core

代码整齐是每一个工程师的基本素质,先不说你搞定这个问题的方案有多好,解决速度有多快,如果代码不整齐,一切都白搭。因为你的代码是要给别人看的,你自己也要看。如果哪一天架构有修改,正好改到这个地方,你很容易自己都看不懂。另外,破窗理论提醒我们,如果代码不整齐分类不明确,整个架构会随着一次一次的拓展而越来越混乱。

分类明确的字面意思大家一定都了解,但还有一个另外的意思,那就是:不要让一个类或者一个模块做两种不同的事情。如果有类或某模块做了两种不同的事情,一方面不适合未来拓展,另一方面也会造成分类困难。

不要搞Common,Core这些东西。每家公司的架构代码库里面,最恶心的一定是这两个名字命名的文件夹,我这么说一定不会错。不要开Common,Core这样的文件夹,开了之后后来者一定会把这个地方搞得一团糟,最终变成Common也不Common,Core也不Core。要记住,架构是不断成长的,是会不断变化的。不是每次成长每次变化,都是由你去实现的。如果真有什么东西特别小,那就索性为了他单独开辟一个模块就好了,小就小点,关键是要有序。

不用文档,或很少文档,就能让业务方上手

谁特么会去看文档啊,业务方他们已经被产品经理逼得很忙了。所以你要尽可能让你的API名字可读性强,对于iOS来说,objc这门语言的特性把这个做到了极致,函数名长就长一点,不要紧。

 

    好的函数名:
        - (NSDictionary *)exifDataOfImage:(UIImage *)image atIndexPath:(NSIndexPath *)indexPath;

    坏的函数名:
        - (id)exifData:(UIImage *)image position:(id)indexPath callback:(id<ErrorDelegate>)delegate;

    为什么坏?
        1. 不要直接返回id或者传入id,实在不行,用id<protocol>也比id好。如果连这个都做不到,你要好好考虑你的架构是不是有问题。
        2. 要告知业务方要传的东西是什么,比如要传Image,那就写上ofImage。如果要传位置,那就要写上IndexPath,而不是用position这么笼统的东西
        3. 没有任何理由要把delegate作为参数传进去,一定不会有任何情况不得不这么做的。而且delegate这个参数根本不是这个函数要解决的问题的充要条件,如果你发现你不得不这么做,那一定是架构有问题!

思路和方法要统一,尽量不要多元

解决一个问题会有很多种方案,但是一旦确定了一种方案,就不要在另一个地方采用别的方案了。也就是做架构的时候,你得时刻记住当初你决定要处理这样类型的问题的方案是什么,以及你的初衷是什么,不要摇摆不定。

另外,你当初设立这个模块一定是有想法有原因的,要记录下你的解决思路,不要到时候换个地方你又灵光一现啥的,引入了其他方案,从而导致异构。

要是一个框架里面解决同一种类似的问题有各种五花八门的方法或者类,我觉得做这个架构的架构师一定是自己都没想清楚就开始搞了。

没有横向依赖,万不得已不出现跨层访问

没有横向依赖是很重要的,这决定了你将来要对这个架构做修补所需要的成本有多大。要做到没有横向依赖,这是很考验架构师的模块分类能力和是否熟悉业务的。

跨层访问是指数据流向了跟自己没有对接关系的模块。有的时候跨层访问是不可避免的,比如网络底层里面信号从2G变成了3G变成了4G,这是有可能需要跨层通知到View的。但这种情况不多,一旦出现就要想尽一切办法在本层搞定或者交给上层或者下层搞定,尽量不要出现跨层的情况。跨层访问同样也会增加耦合度,当某一层需要整体替换的时候,牵涉面就会很大。

对业务方该限制的地方有限制,该灵活的地方要给业务方创造灵活实现的条件

把这点做好,很依赖于架构师的经验。架构师必须要有能力区分哪些情况需要限制灵活性,哪些情况需要创造灵活性。比如对于Core Data技术栈来说,ManagedObject理论上是可以出现在任何地方的,那就意味着任何地方都可以修改ManagedObject,这就导致ManagedObjectContext在同步修改的时候把各种不同来源的修改同步进去。这时候就需要限制灵活性,只对外公开一个修改接口,不暴露任何ManagedObject在外面。

如果是设计一个ABTest相关的API的时候,我们又希望增加它的灵活性。使得业务方不光可以通过Target-Action的模式实现ABtest,也要可以通过Block的方式实现ABTest,要尽可能满足灵活性,减少业务方的使用成本。

易测试易拓展

老生常谈,要实现易测试易拓展,那就要提高模块化程度,尽可能减少依赖关系,便于mock。另外,如果是高度模块化的架构,拓展起来将会是一件非常容易的事情。

保持一定量的超前性

这一点能看出架构师是否关注行业动态,是否能准确把握技术走向。保持适度的技术上的超前性,能够使得你的架构更新变得相对轻松。

另外,这里的超前性也不光是技术上的,还有产品上的。谁说架构师就不需要跟产品经理打交道了,没事多跟产品经理聊聊天,听听他对产品未来走向的畅想,你就可以在合理的地方为他的畅想留一条路子。同时,在创业公司的环境下,很多产品需求其实只是为了赶产品进度而产生的妥协方案,最后还是会转到正轨的。这时候业务方可以不实现转到正规的方案,但是架构这边,是一定要为这种可预知的改变做准备的。

接口少,接口参数少

越少的接口越少的参数,就能越降低业务方的使用成本。当然,充要条件还是要满足的,如何在满足充要条件的情况下尽可能地减少接口和参数数量,这就能看出架构师的功力有多深厚了。

高性能

为什么高性能排在最后一位?

高性能非常重要,但是在客户端架构中,它不是第一考虑因素。原因有下:

 

  • 客户端业务变化非常之快,做架构时首要考虑因素应当是便于业务方快速满足产品需求,因此需要尽可能提供简单易用效果好的接口给业务方,而不是提供高性能的接口给业务方。
  • 苹果平台的性能非常之棒,正常情况下很少会出现由于性能不够导致的用户体验问题。
  • 苹果平台的优化手段相对有限,甚至于有些时候即便动用了无所不用其极的手段乃至不择手段牺牲了稳定性,性能提高很有可能也只不过是100ms到90ms的差距。10%的性能提升对于服务端来说很不错了,因为服务端动不动就是几十万上百万的访问量,几十万上百万个10ms是很可观的。但是对于客户端的用户来说,他无法感知这10ms的差别,如果从10s优化成9s用户还是有一定感知的,但是100ms变90ms,我觉得吧,还是别折腾了。

 

但是!不重要不代表用不着去做,关于性能优化的东西,我会对应放到各系列文章里面去。比如网络层优化,那就会在网络层方案的那篇文章里面去写,对应每层架构都有每层架构的不同优化方案,我都会在各自文章里面一一细说。

2015-4-2 11:28 补: 关于架构分层?

 

昨晚上志豪看了这篇文章之后说,看到你这个题目本来我是期望看到关于架构分层相关的东西的,但是你没写。

嗯,确实没写,当时没写的原因是感觉这个没什么好写的。前面谈论到架构的方法的时候,关于问题分类分模块这一步时,架构分层也属于这一部分,给我一笔带过了。

既然志豪提出来了这个问题,我想可能大家关于这个也会有一些想法和问题,那么我就在这儿讲讲吧。

其实分层这种东西,真没啥技术含量,全凭架构师的经验和素质。

 

我们常见的分层架构,有三层架构的:展现层、业务层、数据层。也有四层架构的:展现层、业务层、网络层、本地数据层。这里说三层四层,跟TCP/IP所谓的五层或者七层不是同一种概念。再具体说就是:你这个架构在逻辑上是几层那就几层,具体每一层叫什么,做什么,没有特定的规范。这主要是针对模块分类而言的。

也有说MVC架构,MVVM架构的,这种层次划分,主要是针对数据流动的方向而言的。

在实际情况中,针对数据流动方向做的设计和针对模块分类做的设计是会放在一起的,也就是说,一个MVC架构可以是四层:展现层、业务层、网络层、本地数据层。

那么,为什么我要说这个?

大概在五六年前,业界很流行三层架构这个术语。然后各种文档资料漫天的三层架构,并且喜欢把它与MVC放在一起说,MVC三层架构/三层架构MVC,以至于很多人就会认为三层架构就是MVCMVC就是三层架构。其实不是的。三层架构里面其实没有Controller的概念,而且三层架构描述的侧重点是模块之间的逻辑关系。MVCController的概念,它描述的侧重点在于数据流动方向。

好,为什么流行起来的是三层架构,而不是四层架构五层架构

 

因为所有的模块角色只会有三种:数据管理者数据加工者数据展示者,意思也就是,笼统说来,软件只会有三层,每一层扮演一个角色。其他的第四层第五层,一般都是这三层里面的其中之一分出来的,最后都能归纳进这三层的某一层中去,所以用三层架构来描述就比较普遍。

那么我们怎么做分层?

 

应该如何做分层,不是在做架构的时候一开始就考虑的问题。虽然我们要按照自顶向下的设计方式来设计架构,但是一般情况下不适合直接从三层开始。一般都是先确定所有要解决的问题,先确定都有哪些模块,然后再基于这些模块再往下细化设计。然后再把这些列出来的问题和模块做好分类。分类之后不出意外大多数都是三层。如果发现某一层特别庞大,那就可以再拆开来变成四层,变成五层。


举个例子:你要设计一个即时通讯的服务端架构,怎么分层?

记住,不要一上来就把三层架构的规范套上去,这样做是做不出好架构的。

你要先确定都需要解决哪些问题。这里只是举例子,我随意列出一点意思意思就好了:

  1. 要解决用户登录、退出的问题
  2. 解决不同用户间数据交流的问题
  3. 解决用户数据存储的问题
  4. 如果是多台服务器的集群,就要解决用户连接的寻址问题

 

解决第一个问题需要一个链接管理模块,链接管理模块一般是通过链接池来实现。
解决第二个问题需要有一个数据交换模块,从A接收来的数据要给到B,这个事情由这个模块来做。
解决第三个问题需要有个数据库,如果是服务于大量用户,那么就需要一个缓冲区,只有当需要存储的数据达到一定量时才执行写操作。
解决第四个问题可以有几种解决方案,一个是集群中有那么几台服务器作为寻路服务器,所有寻路的服务交给那几台去做,那么你需要开发一个寻路服务的Daemon。或者用广播方式寻路,但如果寻路频次非常高,会造成集群内部网络负载特别大。这是你要权衡的地方,目前流行的思路是去中心化,那么要解决网络负载的问题,你就可以考虑配置一个缓存。

于是我们有了这些模块:

链接管理、数据交换、数据库及其配套模块、寻路模块

做到这里还远远没有结束,你要继续针对这四个模块继续往下细分,直到足够小为止。但是这里只是举例子,所以就不往下深究了。

另外,我要提醒你的是,直到这时,还是跟几层架构毫无关系的。当你把所有模块都找出来之后,就要开始整理你的这些模块,很有可能架构图就是这样:

    链接管理  收发数据                     收发数据
        数据交换                       /         
                               链接管理        数据交换
    寻路服务          ========                   /  
                    ========/            数据库服务   寻路服务
        数据库服务           /

然后这些模块分完之后你看一下图,嗯,1、2、3,一共三层,所以那就是三层架构啦。在这里最消耗脑力最考验架构师功力的地方就在于:找到所有需要的模块, 把模块放在该放的地方

这个例子侧重点在于如何分层,性能优化、数据交互规范和包协议、数据采集等其他一系列必要的东西都没有放进去,但看到这里,相信你应该了解架构师是怎么对待分层问题的了吧?

 

对的,答案就是没有分层。所谓的分层都是出架构图之后的事情了。所以你看别的架构师在演讲的时候,上来第一句话差不多都是:”这个架构分为以下几层…”。但考虑分层的问题的时机绝对不是一开始就考虑的。另外,模块一定要把它设计得独立性强,这其实是门艺术活。

 

另外,这虽然是服务端架构,但是思路跟客户端架构是一样的,侧重点不同罢了。之所以不拿客户端架构举例子,是因为这方面的客户端架构苹果已经帮你做好了绝大部分事情,没剩下什么值得说的了。

2015-4-5 12:15 补:关于Common文件夹?

 

评论区MatrixHero提到一点:

 

关于common文件夹的问题,仅仅是文件夹而已,别无他意。如果后期维护出了代码混乱可能是因为,和服务器沟通协议不统一,或代码review不及时。应该有专人维护公共类。

 

这是针对我前面提出的不要Common,不要Core而言的,为什么我建议大家不要开Common文件夹?我打算分几种情况给大家解释一下。

一般情况下,我们都会有一些属于这个项目的公共类,比如取定位坐标,比如图像处理。这些模块可能非常小,就h和m两个文件。单独拎出来成为一个模块感觉不够格,但是又不属于其他任何一个模块。于是大家很有可能就会把它们放入Common里面,我目前见到的大多数工程和大多数文档里面的代码都喜欢这么做。在当时来看,这么做看不出什么问题,但关键在于:软件是有生命,会成长的。当时分出来的小模块,很有可能会随着业务的成长,逐渐发展成大模块,发展成大模块后,可以再把它从Common移出来单独成立一个模块。这个在理论上是没有任何问题的,然而在实际操作过程中,工程师在拓张这个小模块的时候,不太容易会去考虑横向依赖的问题,因为当时这些模块都在Common里面,直接进行互相依赖是非常符合直觉的,而且也不算是不遵守规范。然而要注意的是,这才是Commom代码混乱的罪魁祸首,Common文件夹纵容了不精心管理依赖的做法。当Common里面的模块依赖关系变得复杂,再想要移出来单独成立一个模块,就不是当初设置Common时想的等规模大了再移除也不迟那么简单了。

 

另外,Common有的时候也不仅仅是一个文件夹。

 

在使用Cocoapods来管理项目库的时候,Common往往就是一个pod。这个pod里面会有A/B/C/D/E这些函数集或小模块。如果要新开一个app或者Demo,势必会使用到Common这个pod,这么做,往往会把不需要包含的代码也包含进去,我对项目有高度洁癖,这种情况会让我觉得非常不舒服。

 


 

举个例子:早年安居客的app还不是集齐所有新房二手房租房业务的。当你刚开始写新房这个app的时候,创建了一个Common这个pod,这里面包含了一些对于新房来说比较Common的代码,也包含了对于这个app来说比较Common的代码。过了半年或者一年,你要开始二手房这个app,我觉得大多数人都会选择让二手房也包含这个Common,于是这个Common很有可能自己走上另一条发展的道路。等到了租房这个业务要开app的时候,Common已经非常之庞大,相信这时候的你也不会去想整理Common的事情了,先把租房搞定,于是Common最终就变成了一坨屎。

就对于上面的例子来说,还有一个要考虑的是,分出来的三个业务很有可能会有三个Common,假设三个Common里面都有公共的功能,交给了三个团队去打理,如果遇到某个子模块需要升级,那么三个Common里面的这个子模块都要去同步升级,这是个很不效率的事情。另外,很有可能三个Common到最后发展成彼此不兼容,但是代码相似度非常之高,这个在架构上,是属于分类条理不清

就在去年年中的时候,安居客决定将三个业务归并到同一个App。好了,如果你是架构师,面对这三个Common,你打算怎么办?要想最快出成果,那就只好忍受代码冗余,赶紧先把架子搭起来再说,否则你面对的就是剪不断理还乱的Common。此时Common就已经很无奈地变成一坨屎了。这样的Common,你自己说不定也搞不清楚它里面到底都有些什么了,交给任何一个人去打理,他都不敢做彻底的整理的。

 


 

还有就是,Common本身就是一个粒度非常大的模块。在阿里这样大规模的团队中,即便新开一个业务,都需要在整个app的环境下开发,为什么?因为模块拆分粒度不够,要想开一个新业务,必须把其他业务的代码以及依赖全部拉下来,然后再开新入口,你的新业务才能进行正常的代码编写和调试。然而你的新业务其实只依赖首页入口、网络库等这几个小模块,不需要依赖其他那么多的跟你没关系的业务。现在每次打开天猫的项目,我都要等个两三分钟,这非常之蛋疼。

但是大家真的不知道这个原因吗?知道了这个原因,为什么没人去把这些粒度不够细的模块整理好?在我看来,这件事没人敢做。

  1. 原来大家用的好好的,手段烂就烂一点,你改了你能保证不出错?
  2. 这么复杂的东西,短期之内你肯定搞不好,任务量和工时都不好估,你leader会觉得你在骗工时玩自己的事情。
  3. 就算你搞定了,QA这边肯定再需要做一次全面的回归测试,任务量极大,难以说服他们配合你的工作。

花这么大的成本只是为了减少开启项目时候等待IDE打开时的那几分钟时间?我想如果我是你leader,我也应该不会批准你做这样的事情的。所以,与其到了后面吃这个苦头,不如一开始做架构的时候就不要设置Common,到后面就能省力很多。架构师的工作为什么是功在当代利在千秋,架构师的素质为什么对团队这么重要?我觉得这里就是一个最好的体现。

 


 

简而言之,不建议开Common的原因如下:

  1. Common不仅仅是一个文件夹,它也会是一个Pod。不管是什么,在Common里面很容易形成错综复杂的小模块依赖,在模块成长过程中,会纵容工程师不注意依赖的管理,乃至于将来如果要将模块拆分出去,会非常的困难。
  2. Common本身与细粒度模块设计的思想背道而驰,属于一种不合适的偷懒手段,在将来业务拓张会成为阻碍。
  3. 一旦设置了Common,就等于给地狱之门打开了一个小缝,每次业务迭代都会有一些不太好分类的东西放入Common,这就给维护Common的人带来了非常大的工作量,而且这些工作量全都是体力活,非常容易出错。

 

那么,不设Common会带来哪些好处?

 

  1. 强迫工程师在业务拓张的时候将依赖管理的事情考虑进去,让模块在一开始发展的时候就有自己的土壤,成长空间和灵活度非常大。
  2. 减少各业务模块或者Demo的体积,不需要的模块不会由于Common的存在而包含在内。
  3. 可维护性大大提高,模块升级之后要做的同步工作非常轻松,解放了那个苦逼的Common维护者,更多的时间可以用在更实质的开发工作上。
  4. 符合细粒度模块划分的架构思想。

Common的好处只有一个,就是前期特别省事儿。然而它的坏处比好处要多太多。不设置Common,再小的模块再小的代码也单独拎出来,最多就是Podfile里面要多写几行,多写几行最多只花费几分钟。但若要消除Common所带来的罪孽,不是这几分钟就能搞定的事情。既然不用Common的好处这么多,那何乐而不为呢?

假设将来你的项目中有一个类是用来做Location的,哪怕只有两个文件,也给他开一个模块就叫Location。如果你的项目中有一个类是用来做ImageProcess的,那也开一个模块就叫ImageProcess。不要都放到Common里面去,将来你再开新的项目或者新的业务,用Location就写依赖Location,用ImageProcess就写依赖ImageProcess,不要再依赖Common了,这样你的项目也好管理,管理Common的那个人日子过得也轻松(这个人其实都可以不需要了,把他的工资加到你头上不是更好?:D),将来要升级,顾虑也少。

结束

 

一下子挖了个大坑,在开篇里扯了一些淡。

 

嗯,干货会在后续的系列文章里面扑面而来的!

转:在全员都在投身「投资事业」的公司里工作 是一种怎样的体验?

转正微信公众号:公司精选(ID:gongsijingxuan)

(雪球ceo 方三文 雪球ID:不明真相的群众)

 
在全员都在投身「投资事业」的公司里工作 是一种怎样的体验?公司精选(ID:gongsijingxuan)
 

在这家公司赚得根本就不是工资!如果你想问为什么?请把文章看完。这简直就是一家颠覆你价值观的公司!
 
1雪球是什么?

名字出处,
巴菲特说:“人生就像滚雪球,最重要的是发现很湿的雪和很长的坡。”

 
那雪球到底是什么呢?
中国第一家从社交做交易的互联网金融公司。

不好意思,我又装逼了,我说人话:其实你可以把雪球想象成为微博,或者是Facebook。只不过在这里,大家讨论的是股票而已。你可以动态选择自己应该关注什么人,通过持续和他们交流,知道哪些人最可能解答你哪方面的问题。边交朋友边赚钱!当然不放盘的时候也会八卦,因为这完全是你的一个赚钱朋友圈!不信?你看…

 


 
2在雪球工作是一种怎样的体验?

原来总有人问:“在雪球工作是一种怎样的体验?”。我还是想用图说话:

 

这是办公环境

 

(办公室整体的设计很大胆,很有现代感。更重要的是很舒服!他们每一个地方都是打通的,阳光从不同的角度洒进来,整个办公室都暖了。刚进门的地方就会看到上面有一个投影仪,都会把每天的股票指数投影下来,非常专业。还有不知道你有没有注意到,他们每个会议室的门上都有一个名字。比如:彼得·林奇、巴菲特,都是在向大师致敬。)

 

这是餐厅和零食吧台

 

(对于吃货的我根本无法拒绝,因为大家来自天南海北,所以每天都会有很多口味供大家选择,很贴心。有员工生日的时候也会特意准备一下,就像在家一样!)

 

这是健身房

 

(额、这怎么说,连男女都分开了。有跑步机,有自行车等健身器材。哦,对了,每周三下班后女生还可以留下来上瑜伽课哦~简直不更更赞了!)

 

这是书房


这好像还不够生动的说明这是一种怎样的体验吧?那换个角度说吧!

 

其实,除了是一家普通的互联网公司,他们还是一个全员投身[投资事业]的公司。(讲真,这很严肃的~)

 

重点是:他们的投资水平如何咧?

 

我们调查了码农,产品经理,运营小妹,设计师,BD等,为大家制作了以下精美的雪球员工2014年投资收益调查,据听说他们2013年可比这多多了!而且我还听说,跟会赚钱的人在一起呆久了,自己也会很有钱~


 

最印象深刻的是,当我问到他们为什么要在雪球工作的时候本来说想扯点儿情怀方面的东西。可是他们给了我一个特别接地气的答案:赚钱多!这里的钱并不是指工资,而是自己的炒股利润…

 

好吧,我也必须承认这是他们员工流失率低很重要的一个原因。一边工作一边学赚钱,福利还那么高,换我我也不会辞职…


差点忘了他们的年度旅行,去年的太远,我们只说今年的日本!

 

 

对不起,还没完。我还要把仇恨继续拉下去。。。

 

这次说雪球里面的员工氛围,仅用几张图表示。

 


雪球工程师(从左至右):方杨 蔡书华 周敏 周嵘屿 杨海锦



雪球工程师:周嵘屿 雪球ID:山荣

(对,你没看错,那真的是无人机)

 


雪球运营:潘高颖 雪球ID:潘羊羊Anais

(学舞蹈的妹纸就是有气质)

 


雪球设计师:王梓 李徽 雪球ID:阵阵 阿拉徽

(CEO不明真相的群众说:好好干有肉吃)

 


雪球前台:韩阳 雪球ID:阳囡囡

(备注:衣服完全没有显示出来她的身材)

 


雪球工程师:方杨 雪球ID:fyXIII

(为了让他滑雪,食堂算是拼尽全力了)

 


雪球工程师:高林植 雪球ID:秃一了

(不评)

 


雪球工程师:熊猫 雪球ID:pawaca

(一个会功夫的熊猫)

 


雪球总裁助理:张诗婧 雪球ID:诗婧

(完全被这娃萌翻了…)

 

说完之后,我好想问你,你说在雪球上班是一种怎样的体验?

 

还不能说出什么感受,那么,我们再来看一下他们的福利吧~

 

  • 五险一金,补充医疗保险

 

  • 带薪病假、年假

 

  • 年13—16薪

 

  • 免费豪华自助午餐

 

  • 期权计划(转正后)

 

  • 出国旅行一年一次(现在是这个频率)

 

  • 生日福利

 

  • 免费加班餐

 

  • 每年一次体检

 

  • 结婚、生孩子发红包

 

  • 不加班(除特殊情况)

 

(不加班这点我真的要吐槽一下,今天小编去雪球踩点儿,刚六点半公司就没什么人了,小编只好抱着电脑回自己公司继续写稿子。真的好过分的,都不加班的公司好让人羡慕的!)

 

3雪球在做什么呢?

如果加入雪球,可能会是你在互联网中干过的最酷的事。为什么这么说呢?

 

去年9月份,雪球就已经完成了4000万美元C轮融资。其实,融资没有什么好祝贺的,融资都是没有办法的事,说明没有外部输血公司还活不下去,不融资而公司赚钱才是值得祝贺的。但是从另一个角度看,为什么投资人愿意拿几千万美元投资上线将近三年,还没有一分钱盈利的雪球呢?

 

所以我们会好奇雪球的价值所在。他们的创办人给出的答案让我特别震撼。

 

方三文说:频繁的交流会产生社交关系,社交关系会产生信用。传统的金融产品销售都是通过某种程度上的增信完成的,比如银行可以说是牌照增信最后担保人增信,线下的第三方销售靠的是人肉增信。线上社交产生的信用,在其他产品的销售上已经有很好的应用案例,它一定也可以应用在信用最为稀缺珍贵的金融交易上面。匹配和增信做到了,离交易就是一步之遥了。所以也可以这样说,做社区本身并不是目的,做交流也不是目的,让交流服务于交易才是最终目的。当交流与交易融合的时候,社区的价值将会爆发。获得新一轮的融资后,雪球将尽快促成这个融合。这是雪球已经做和正要做的事情,大概也是它的价值所在。

 

好吧,说到底,雪球其实是一个“社交投资网络”。它不荐股,依靠用户生产内容,让喜欢独立思考的投资者都聚到这里,一起研究公司、聊聊投资——这就是雪球。如果说雪球做了那么一点创新,也许就是它选择了用互联网来做这些事吧。

 

 

雪人说:

 

因为我们渴望着自由,因为我们有着同样的梦想,

所以我们聚在一起,

所以我们出现在彼此的青春里,

所以我们都爱着雪球!

 

 

急招的5个职位包括:互联网金融Java 开发工程师、前端开发工程师、Android开发工程师、高级交互设计师、商务运营经理。

 

如果您能分享给正在找工作的朋友,我们万分感谢!

 

感谢阅读,有兴趣的朋友请将简历发送至:Hr@xueqiu.com(标题注明应聘岗位)

阅读原文 ,查看更多详细招聘职位

 
来源丨公司精选(ID:gongsijingxuan)

编辑丨琥珀(ID:weimic)
如需转载,注明出处及ID即可

在线数据迁移那点事

说明: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 ,但宽表模式下还是需要额外成本。