架构互联网产品
2011-01-04 15:17:20 阿炯

在规划一个互联网产品时候经常面临这样的窘境:一方面由于市场竞争的需要,留给产品规划的时间并不多,需要尽快规划出产品雏形以便投入开发及运营,然后在运营过程中逐步完善,按照当下时尚的说 法:“敏捷开发”、“迭代增量”、“持续改进”。对于互联网行业而言,再完美的规划并不能保证产品的成功,正如那些成功的互联网企业产品“无心插柳柳成荫”的故事所彰示的:Paypal原来是提供加密软件和通过Palm  pilot来转移资金的服务的,Twitter原来是做短信交换服务,Baidu最初是看好企业搜索的,迅雷原来是要做分布式邮件系统的。

于是乎大家都觉得:既然计划赶不上变化,与其耗费大量力气去做产品规划,还不如“摸着石头过河”,在做的过程中在逐步调整方向。但是由于时间紧迫,对产品规划核心的要素并没有想清楚,例如对于产品定位、产品核心功能、产品运营及营销策略等,导致在未来花大量力气进行调整,由于规划不合理导致产品失败乃至公司倒闭的例子举不胜数。因此众多的中国互联网公司为了规避产品规划的风险,则采用全面“山寨”国外的成功案例。而一些企业将产品规划放到了重要的位置,企业会按照标准产品研发的流程来进行互联网产品规划:先花上几个月时间进行市场调研、竞争对手分析,然后再花上几个月时间来做产品规划,再花上半年乃至一年时间来进行开发。最后做出来的产品可想而知,那么互联网产品规划是否有必要呢?

1、产品战略规划 VS  产品规划

我们一般说产品规划其实包含了产品战略规划和产品规划(姑且叫产品实现规划)两部分。

产品战略规划需要重点关注产品战略愿景、产品战略方向、产品定位、产品核心业务模式、产品市场细分等影响产品未来发展的重要因素,而产品规划主要偏重于产品具体功能的实现逻辑。如果说产品战略规划关注“该做什么”的话,产品规划则关注“怎样做”。

一般产品战略规划由负责战略的人负责,而产品功能规划则由产品经理负责。负责战略的人与负责产品的人的思维模式、做事风格并不相同,两者之间存在一定的鸿沟。例如负责战略的人经常很理想化,不关注方案具体实现的可行性等;而负责产品的人相对现实主义,关注于产品具体的实现、运营。

正是由于这种差异,导致在产品规划中经常出现产品战略规划与产品规划无法很好衔接的问题,这也是在互联网产品规划中最常见的问题。战略家们说“产品 战略已经很清晰了,只需要产品经理去细化即可”,但产品经理仍然无从下手。原因有多方面,例如:产品定位不清晰,什么都想做;产品核心业务模式不清晰;

从产品规划角度来说,产品战略规划不能够只是停留在在所谓的产品战略方向、产品战略愿景、产品核心模式这些宏观理念上,负责制定产品战略的人必须能够度量产品战略在现有公司资源下的可行性及实施路径,并将产品战略的执行思路以清晰简洁的方式告知产品经理,必要时候应当自己能够操刀上阵,否则战略始终只是战略。

会有人说,这不是让战略家去做产品经理做的事情吗,极端的答案:是。一个好的产品战略家必须是一个好的产品经理,这个世界不缺少能说会道的战略家,不缺少各种战略,缺少能够将自己的想法推动落实执行的实干家,正如一个好的商业模式必须能够通过所谓的电梯测试(三分钟测试),如果一个战略家无法度量自己想法在现有公司资源情况下的可行性及实施步骤,那这样的产品战略只能无疫而终。很奇怪很多初创型的互联网公司竟然养了专门做战略的人员,于是乎产品战略规划与产品规划无法衔接的悲剧遗憾在不停发生着。

2、产品规划的价值

既然可以山寨别人已有的产品,那么是否还有必要费老大劲进行产品规划呢,产品规划价值的在那儿呢?原因主要如下:

a、任何优秀的产品都有其内在的文化基因及气质,这种文化基因及气质与创造这个产品的公司的企业文化及产品人员息息相关,正是这种基因让其从其他产品中脱颖而出,我们在山寨别人时候并无法山寨此部分基因的,缺少了灵魂的产品只能是一个平庸的产品;另外我们所看到的产品可能是人家第一次迭代的产品,人家可能已经处于第3、4次迭代开发中了,被别人节奏所牵制的产品始终无法超越别人,我们始终只能是追随者。

b、产品规划最重要的还是做规划和实施的人,直接跳过产品规划去山寨别人,那么公司最宝贝的资源:“人”始终得不到相应的锻炼,一流的产品必然有一流的产品人员,反之亦然。这样才能够在未来转型时候有能力、有资源去快速完成转型,抓住市场机会,这也是Paypal、Twitter等能够实现顺利转型的根本原因,而我们只看见了Paypal、Twitter的最初的状态和现在的辉煌,而忘记了中间的过程和背后的实力所在。

c、产品规划的过程也是梳理思路、统一思想的过程,只有对产品战略规划及产品规划达成一致,才可能将规划的东西顺利落实。从这一点来说,规划过程比 规划的结果更为重要。因此在产品规划中应当尽量让团队团队成员都参与进来,对于初创型公司而言,这也是团队磨合、加深理解的好机会。

d、市场是不断变化的,而规划需要动态调整的,好的规划应当先于市场变化。对于市场变化趋势的把控,必须建立在对行业的深刻的洞察基础上。这恰恰是产品规划的价值所在,产品规划的过程可以帮助我们成体系梳理对市场的理解,没有这种理解,我们只能始终落后于市场变化。

3、产品规划的度

既然产品战略规划/产品规划重要,不能仓促为之,那怎样平衡互联网产品敏捷开发的要求呢?

个人认为:对产品战略规划应当尽量详尽,要尽量理清楚产品的脉络,包括产品定位、产品业务模式、产品核心逻辑、核心业务功能等;对于产品规划则可以采用迭代规划方式。由于产品战略规划决定了产品未来的发展方向,因此应当尽量考虑清楚,未来不要轻易频繁做大的调整,这样才能够保证战略的延续性。产品战略规划实际上已经确定了产品各迭代周期总的目标计划;在产品战略清晰的情况下,产品规划主要偏重产品的实现,此时侯可以按照迭代增量的思路进行规划,在运营过程中根据反馈,及时调整、持续完善产品。

上文源自:译言网


说完成产品,再来看看系统架构,这里转引了陈皓总结的系统架构的一些原则。

2021年12月未就工作20多年了,这20来年看到了很多公司系统架构,也看到了很多问题,在跟这些公司进行交流和讨论的时候,包括进行实施和方案比较的时候,都有很多各种方案的比较和妥协,因为相关的经历越来越多,所以,逐渐形成了自己的逻辑和方法论。今天,想写下这篇文章,把我的这些个人的经验和想法总结下来,希望能够让更多的人可以参考和借鉴,并能够做出更好的架构来。另外,我的这些思维方式和原则都针对于现有市面上众多不合理的架构和方案,所以也算是一种“纠正”……(注意,这篇文章所说的这些架构上的原则,一般适用于相对比较复杂的业务,如果只是一些简单和访问量不大的应用,那么你可能会得出相反的结论)

原则一:关注于真正的收益而不是技术本身
原则二:以应用服务和 API 为视角,而不是以资源和技术为视角
原则三:选择最主流和成熟的技术
原则四:完备性会比性能更重要
原则五:制定并遵循服从标准、规范和最佳实践
原则六:重视架构扩展性和可运维性
原则七:对控制逻辑进行全面收口
原则八:不要迁就老旧系统的技术债务
原则九:不要依赖自己的经验,要依赖于数据和学习
原则十:千万要小心 X – Y 问题,要追问原始需求
原则十一:激进胜于保守,创新与实用并不冲突

原则一:关注于真正的收益而不是技术本身

对于软件架构来说,我觉得第一重要的是架构的收益,如果不说收益,只是为了技术而技术,而没有任何意义。对于技术收益来说,我觉得下面这几个收益是非常重要的:
1、是否可以降低技术门槛加快整个团队的开发流程。能够加快整个团队的工程流程,快速发布,是软件工程一直在解决的问题,所以,系统架构需要能够进行并行开发,并行上线和并行运维,而不会让某个团队成为瓶颈点。(注:就算拖累团队的原因是组织构架,也不妨碍我们做出并行的系统架构设计)

2、是否可以让整个系统可以运行的更稳定。要让整个系统可以运行的更为的稳定,提升整个系统的SLA,就需要对有计划和无计划的停机做相应的解决方案(参看《关于高可用的架构》)

3、是否可以通过简化和自动化降低成本。最高优化的成本是人力成本,人的成本除了慢和贵,还有经常不断的 human error。如果不能降低人力成本,反而需要更多的人,那么这个架构设计一定是失败的。除此之外,是时间成本,资金成本。

如果一个系统架构不能在上面三个事上起到作用,那就没有意义了。

原则二:以应用服务和 API 为视角,而不是以资源和技术为视角

国内很多公司都会有很多分工,基本上都会分成运维和开发,运维又会分成基础运维和应用运维,开发则会分成基础核心开发和业务开发。不同的分工会导致完全不同的视角和出发点。比如,基础运维和开发的同学更多的只是关注资源的利用率和性能,而应用运维和业务开发则更多关注的是应用和服务上的东西。这两者本来相关无事,但是因为分布式架构的演进,导致有一些系统已经说不清楚是基础层的还是应用层的了,比如像服务治理上的东西,里面即有底层基础技术,也需要业务的同学来配合,包括 k8s 也样,里面即有底层的如网络这样的技术,也有需要业务配合的 readniess和 liveness 这样的健康检查,以及业务应用需要 configMap 等等 ……

这些东西都让我感觉到所谓 DevOps,其实就是因为很多技术和组件已经分不清是 Dev 还是 Ops 的了,所以需要合并 Dev和Ops。而且整个组织和架构的优化,已经不能通过调优单一分工或是单一组件能够有很大提升的了。其需要有一种自顶向下的,整体规划,统一设计的方式,才能做到整体的提升(可以试想一下城市交通的优化,当城市规模到一定程度的时候,整体的性能你是无法通过优化几条路或是几条街区来完成的,你需要对整个城市做整体的功能体的规划才可能达到整体效率的提升)。而为了做到整体的提升,需要所有的人都要有一个统一的视角和目标,这几年来,我觉得这个目标就是——要站在服务和 对外API的视角来看问题,而不是技术和底层的角度。

原则三:选择最主流和成熟的技术

技术选型是一件很重要的事,技术一旦选错,那会导致整个架构需要做调整,而对架构的调整重来都不是一件简单的事,我在过去几年内,当系统越来越复杂的时候,用户把他们的  PHP,Python, .NET,或 Node.js 的架构完全都迁移到 Java + Go 的架构上来的案例不断的发生。这个过程还是非常痛苦的,但是你没有办法,当你的系统越来越复杂,越来越大时,你就再也不能在一些玩具技术上玩了,你需要的更为工业化的技术。

1、尽可能的使用更为成熟更为工业化的技术栈,而不是自己熟悉的技术栈。 所谓工业化的技术栈,你可以看看大多数公司使用的技术栈,比如:互联网,金融,电信……等等 ,大公司会有更多的技术投入,也需要更大规模的生产,所以,他们使用的技术通常来说都是比较工业化的。在技术选型上,千万不要被——“你看某个视频公司也在用这个技术”,或是一些在论坛上看到的一些程序员吐槽技术的观点(没有任何的数据,只有自己的喜好)来决定自己的技术,还是看看主流大多数公司实际在用的技术栈,会更靠谱一些。

2、选择全球流行的技术,而不是中国流行的技术。技术这个东西一定是一个全球化的东西,不是一个局域化的事。所以,一定要选国际化的会更好。另外,千万不要被某些公司的“特别案例”骗过去了,那怕这个案例很性感,关键还是要看解决问题的思路和采用的技术是否具有普世性。只有普世性的技术有更强的生命力。

3、尽可能的使用红利大的主流技术,而不要自己发明轮子,更不要魔改。我见过好些个公司魔改开源软件,比如有个公司同魔改mesos,最后改着改着发现自己发明另一个 kubernetes。我还见过很多公司或技术团队喜欢自己发明自己的专用轮子,最后都会被主流开源软件所取代。完全没有必要。不重新发明轮子,不魔改,不是因为自己技术不能,而是因为,这个世界早已不是自己干所有事的年代了,这个时代是要想尽方法跟整个产业,整个技术社区融合和合作,这样才会有最大的收益。那些试图因为某个特例需要自成一套的玩法,短期没问题,但长期来说,我都不看好。

4、绝大多数情况下,如无非常特殊要求,选 Java基本是不会错的。一方面,这是因为 Java 的业务开发的生产力是非常好的,而且有 Spring 框架保障,代码很难写烂,另外,Java 的社区太成熟了,你需要的各种架构和技术都可以很容易获得,技术红利实在是太大。这种运行在JVM上的语言有太多太多的好处了。在 Java 的技术栈上,你的架构风险和架构的成本(无论是人力成本,时间成本和资金成本)从长期来说都是最优的

在我见过的公司中,好些公司的架构都被技术负责人个人的喜好、擅长和个人经验给绑架了,完全不是从一个客观的角度来进行技术选型。其实,从 0 到 1的阶段,你用什么样的技术都行,如果你做一个简单的应用,没有事务处理没有复杂的交易流程,比如一些论坛、社交之类的应用,你用任何语言都行。但是如果有一天你的系统变复杂了,需要处理交易了,量也上来了,从 1 到 10,甚至从 10 到 100,你的开发团队也变大了,需要构建的系统越来越大,你可能会发现你只有一个选择,就是 Java。想想京东从.NET 到 Java,淘宝从 PHP 到 Java……

注,一些有主观喜好的人一定会对我上述对 Java 的描述感到不适,我还用一些证据说明一下——全中国所有的电商平台,几百家银行,三大电信运营商,所有的保险公司,劵商的系统,医院里的系统,电子政府系统等等,基本都是用 Java 开发的,包括 AWS 的主流语言也是 Java,阿里云一开始用 C++/Python 写控制系统,后面也开始用 Java ……你可能会说 B站是用 go语言,但是你可能不知道 B 站的电商和大数据是用 Java……懂着数据分析的同学,建议上各大招聘网站上搜一下 Java 的职位数量,你就知道某个技术是否主流和热门……

原则四:完备性会比性能更重要

我发现好些公司的架构师做架构的时候,首要考虑的是架构的性能是否能够撑得住多大多大的流量,而不是考虑系统的完备性和扩展性。所以,我已经多次见过这样的案例了,一开始直接使用 MongoDB 这样的非关系型数据库,或是把数据直接放在 Redis 里,而直接放弃关系型数据库的数据完备性的模型,而在后来需要在数据上进行关系查询的时候,发现 NoSQL 的数据库在 Join 上都表现的太差,然后就开始各种飞线,为了不做 Join 就开始冗余数据,然而自己又维护不好冗余数据后带来的数据一致性的问题,导致数据上的各种错乱丢失。

所以,我给如下的一些如下的架构原则:
1、使用最科学严谨的技术模型为主,并以不严谨的模型作为补充。对于上面那个案例来说,就是——永远使用完备支持 ACID 的关系型数据库,然后用 NoSQL 作补充,而不是完全放弃关系型数据库。这里的原则就是所谓的“先紧后松”,一开始紧了,你可以慢慢松,但是开始松了,以后你想紧再也紧不过来了。

2、性能上的东西,总是有很多解的。我这么多年的经历告诉我,性能上的事,总是有解的,手段也是最多的,这个比起架构的完备性和扩展性来说真的不必太过担心。

为了追求所谓的性能,把整个系统的完备性丢失掉,相当地得不偿失。

原则五:制定并遵循服从标准、规范和最佳实践

这个原则是非常重要的,因为只有服从了标准,你的架构才能够有更好的扩展性。比如:我经常性的见到很多公司的系统既没有服从业界标准,也没有形成自己公司的标准,感觉就像一群乌合之众一样。最典型的例子就是 HTTP 调用的状态返回码。业内给你的标准是 200表示成功,3xx 跳转,4xx 表示调用端出错,5xx 表示服务端出错,我实在是不明白为什么无论成功和失败大家都喜欢返回 200,然后在 body 里指出是否error(前两年我在微信公众号里看到一个有一定名气的互联网老兵推荐使用无论正确还是出错都返回 200 的做法,我在后台再三确认后,我发现这样的架构师真是害人不浅)。这样做最大的问题是——监控系统将在一种低效的状态下工作。监控系统需要把所有的网络请求包打开后才知道是否是错误,而且完全不知道是调用端出错还是服务端出错,于是一些像重试或熔断这样的控制系统完全不知道怎么搞(如果是 4xx错,那么重试或熔断是没有意义的,只有 5xx 才有意义)。有时候,我会有种越活越退步的感觉,错误码设计这种最基本最基础的东西为什么会没有?并且一个公司会任由着大家乱来?这些基础技能怎么就这样丢掉了?

还有,我还见过一些公司,他们整个组织没有一个统一的用户 ID 的设计,各个系统之间同步用户的数据是通过用户的身份证 ID,是的,就是现实世界的身份证 ID,包括在网关上设置的用户白名单居然也是用身份证 ID。我对这个公司的内的用户隐私管理有很大的担忧。一个企业,一个组织,如果没有标准和规范,也就会有抽象,这一定是要出各种乱子的。

下面罗列一些你需要注意的标准和规范(包括但不限于):
1、服务间调用的协议标准和规范。这其中包括 Restful API路径, HTTP 方法、状态码、标准头、自定义头等,返回数据 JSon Scheme……等。

2、一些命名的标准和规范。这其中包括如:用户 ID,服务名、标签名、状态名、错误码、消息、数据库……等等

3、日志和监控的规范。这其中包括:日志格式,监控数据,采样要求,报警……等等

4、配置上的规范。这其中包括:操作系统配置、中间件配置,软件包……等等

5、中间件使用的规范。数据库,缓存、消息队列……等等

6、软件和开发库版本统一。整个组织架构内,软件或开发库的版本最好每年都升一次级,然后在各团队内统一。

这里重要说一下两个事:
1、Restful API 的规范。我觉得是非常重要的,这里给两个我觉得写得最好的参考:Paypal 和 Microsoft 。Restful API 有一个标准和规范最大的好处就是监视可以很容易地做各种统计分析,控制系统可以很容易的做流量编排和调度。

2、另一个是服务调用链追踪。对于服务调用链追踪来说,基本上都是参考于 Google Dapper 这篇论文,目前有很多的实现,最严格的实现是 Zipkin,这也是 Spring Cloud Sleuth 的底层实现。Zipkin 贴近 Google Dapper 论文的好处在于——无状态,快速地把 Span 发出来,不消耗服务应用侧的内存和 CPU。这意味着,监控系统宁可自己死了也不能干扰实际应用。

3、软件升级。我发现很多公司包括 BAT,他们完全没有软件升级的活动,全靠开发人员自发。然而这种成体系的活动,是永远不可能靠大众的自发形成的。一个公司至少一年要有一次软件版本升级的review,然后形成软件版本的统一和一致,这样会极太简化系统架构的复杂度。

原则六:重视架构扩展性和可运维性

在我见过很多架构里,技术人员只考虑当下,但从来不考虑系统的未来扩展性和可运维性。所谓的管生不管养。如果你生下来的孩子胳膊少腿,严重畸形,那么未来是很难玩的。因为架构和软件不是写好就完的,是需要不断修改不断维护的,80%的软件成本都是在维护上。所以,如何让你的架构有更好的扩展性,可以更容易地运维,这个是比较重要的。所谓的扩展性,意味着,我可以很容易地加更多的功能,或是加入更多的系统,而所谓可运维,就是说我可以对线上的系统做任意的变更。扩展性要求的是有标准规范且不耦合的业务架构,可运维性要求的则是可控的能力,也就是一组各式各样的控制系统。

1、通过服务编排架构来降低服务间的耦合。比如:通过一个业务流程的专用服务,或是像 Workflow,Event Driven Architecture , Broker,Gateway,Service Discovery 等这类的的中间件来降低服务间的依赖关系。

2、通过服务发现或服务网关来降低服务依赖所带来的运维复杂度。服务发现可以很好的降低相关依赖服务的运维复杂度,让你可以很轻松的上线或下线服务,或是进行服务伸缩。

3、一定要使用各种软件设计的原则。比如:像SOLID这样的原则(参看《一些软件设计的原则》),IoC/DIP,SOA 或 Spring Cloud 等 架构的最佳实践(参看《SteveY对Amazon和Google平台的吐槽》中的 Service Interface 的那几条军规),分布式系统架构的相关实践(参看:《分布式系统的事务处理》)……等等。

原则七:对控制逻辑进行全面收口

所有的程序都会有两种逻辑,一种是业务逻辑,一种是控制逻辑,业务逻辑就是完成业务的逻辑,控制逻辑是辅助,比如你用多线程,还是用分布式,是用数据库还是用文件,如何配置、部署,运维、监控,事务控制,服务发现,弹性伸缩,灰度发布,高并发,等等,等等……这些都是控制逻辑,跟业务逻辑没有一毛钱关系。控制逻辑的技术深度会通常会比业务逻辑要深一些,门槛也会要高一些,所以,最好要专业的程序员来负责控制逻辑的开发,统一规划统一管理,进行收口。这其中包括:
1、流量收口。包括南北向和东西向的流量的调度,主要通过流量网关,开发框架 SDK或 Service Mesh 这样的技术。

2、服务治理收口。包括:服务发现、健康检查,配置管理、事务、事件、重试、熔断、限流……主要通过开发框架 SDK 如:Spring Cloud,或服务网格Service Mesh等技术。

3、监控数据收口。包括:日志、指标、调用链……主要通过一些标准主流的探针,再加上后台的数据清洗和数据存储来完成,最好是使用无侵入式的技术。监控的数据必须统一在一个地方进行关联,这样才会产生信息。

4、资源调度有应用部署的收口。包括:计算、网络和存储的收口,主要是通过容器化的方案,如k8s来完成。
中间件的收口。包括:数据库,消息,缓存,服务发现,网关……等等。这类的收口方式一般要在企业内部统一建立一个共享的云化的中间件资源池。

对此,这里的原则是:
1、要选择容易进行业务逻辑和控制逻辑分离的技术。这里,Java 的 JVM+字节码注入+AOP 式的Spring 开发框架,会带给你太多的优势。

2、要选择可以享受“前人种树,后人乘凉”的有技术红利的技术。如:有庞大社区而且相互兼容的技术,如:Java, Docker,  Ansible,HTTP,Telegraf/Collectd……

3、中间件你要使用可以支持HA集群和多租户的技术。这里基本上所有的主流中间件都会支持 HA 集群方式的。

原则八:不要迁就老旧系统的技术债务

我发现很多公司都很非常大的技术债务,这些债务具体表现如下:

1、使用老旧的技术。比如使用HTTP1.0,Java 1.6,Websphere,ESB,基于socket的通讯协议,过时的模型……等等

2、不合理的设计。比如在 gateway 中写大量的业务逻辑,单体架构,数据和业务逻辑深度耦合,错误的系统架构(把缓存当数据库,用消息队列同步数据)……等等

3、缺少配套设施。比如没有自动化测试,没有好的软件文档,没有质量好的代码,没有标准和规范……等等

来找我寻求技术帮助的人都有各种各样的问题。我都会对他们苦口婆心地说同样的一句话——“如果你是来找我 case-by-case 解决问题,我兴趣不大,因为,你们千万不要寄希望能够很简单的把一辆夏利车改成一辆法拉利跑车,或是把一栋地基没打好的歪楼搞正。以前欠下的技术债,都得要还,没打好的地基要重新打,没建配套设施都要建。这些基础设施如果不按照正确科学的方式建立的话,你是不可能有一个好的的系统,我也没办法帮你 case-by-case 的解决问题……”,一开始,他们都会对我说,没问题,我们就是要还债,但是,最后发现要还的债真多,有点承受不了,就开始现原形了。

他们开始为自己的“欠的技术债”找各种合理化的理由——给你解释各种各样的历史原因和不得以而为之的理由。谈着谈着,让我有一种感觉——他们希望得到一种什么都不改什么都不付出的方式就可以进步的心态,他们宁可让新的技术 low 下来迁就于这些技术债,把新的技术滥用地乱七八糟的。有一个公司,他们的系统架构和技术选型基本都搞错了,使用错误的模型构建系统,导致整个系统的性能非常之差,也才几千万条数据,但他们想的不是还债,不是把地基和配套设施建好,而且要把楼修的更高,上更多的系统——他们觉得现有的系统挺好,性能问题的原因是他们没一个大数据平台,所以要建大数据平台……

我见过很多很多公司,包括大如 BAT 这样的公司,都会在原来的技术债上进行更多的建设,然后技术债越来越大,利息越来越大,最终成为一个高利贷,再也还不了(我在《开发团队的效率》一文中讲过一个 WatchDog 的架构模式,一个系统烂了,不是去改这个系统,而是在旁边建一个系统来看着它,我很难理解为什么会有这样的逻辑,也许是为了要解决更多的就业……)

这里有几个原则和方法我是非常坚持的,分享给大家:
1、与其花大力气迁就技术债务,不如直接还技术债。是所谓的长痛不如短痛。
2、建设没有技术债的“新城区”,并通过“防腐层 ”的架构模型,不要让技术债侵入“新城区”。

原则九:不要依赖自己的经验,要依赖于数据和学习

有好些人来找我跟我说他们的技术问题,然后希望我能够给他们一个答案。我说,我需要了解一下你现有系统的情况,也就是需要先做个诊断,我只有得到这些数据后,我才可能明白真正的原因是什么,我才可能给你做出一个比较好的技术方案。我个人觉得这是一种对对方负责的方法,因为技术手段太多了,所有的技术手段都有适应的场景,并且有各种trade-off,所以,只有调研完后才能做出决定。这跟医生看病是一样的,确诊病因不能靠经验,还是要靠诊断数据。在科学面前,所有的经验都是靠不住的……

另外,如果有一天你在做技术决定的时候,开始凭自己以往的经验,那么你就已经不可能再成长了。人都是不可能通过不断重复过去而进步的,人的进步从来都是通过学习自己不知道的东西。所以,千万不要依赖于自己的经验做决定。做任何决定之前,最好花上一点时间,上网查一下相关的资料,技术博客,文章,论文等,同时也看看各个公司,或是各个开源软件他们是怎么做的?然后,比较多种方案的Pros/Cons,最终形成自己的决定,这样,才可能做出一个更好的决定。

原则十:千万要小心 X Y 问题,要追问原始需求

对于 X-Y 问题,也就是说,用户为了解决 X问题,他觉得用 Y 可以解,于是问我 Y 怎么搞,结果搞到最后,发现原来要解决的 X 问题,这个时候最好的解决方案不是 Y,而是 Z。 这种 X-Y 问题真是相当之多,见的太多太多了。所以每次用户来找我的时候,我都要不断地追问什么是 X 问题。

比如,好些用户都会来问我他们要一个大数据流式处理,结果追问具体要解决什么样的问题时,才发现他们的问题是因为服务中有大量的状态,需要把相同用户的数据请求放在同一个服务上处理,而且设计上导致一个慢函数拖慢整个应用服务。最终就是做一下性能调优就好了,根本没有必要上什么大数据的流式处理。

我很喜欢追问为什么,这种追问会让客户也跟着来一起重新思考。比如有个客户来找我评估的一个技术架构的决定,从理论上来说,好像这个架构在用户的这个场景下非常不错。但是,这个场景和这个架构是我职业生涯从来没有见过的。于是,我开始追问这个为什么会是这么一个场景?当我追问的时候,我发现用户都感到这个场景的各种不合理。最后引起了大家非常深刻的研讨,最终用户把那个场景修正后,而架构就突然就变成了一个常见且成熟的的模型……

原则十一:激进胜于保守,创新与实用并不冲突

我对技术的态度是比较激进的,但是,所谓的激进并不是瞎搞,也不是见新技术就上,而是积极拥抱会改变未来的新技术,如:Docker/Go,我就非常快地跟进,但是像区块链或是 Rust 这样的,我就不是很积极。因为,其并没有命中我认为的技术趋势的几个特征(参看《Go,Docker 和新技术》)。当然也不是不喜欢的就不学了,我对区块链和 Rust 我一样学习,我也知道这些技术的优势,但我不会大规模使用它们。另外,我也尊重保守的决定,这里面没有对和错。但是,我个人觉得对技术激进的态度比起保守来说有太多的好处了。一方面来说,对于用户来说,很大程度上来说,新技术通常都表面有很好的竞争力,而且我见太多这样成功的公司都在积极拥抱新的技术的,而保守的通常来说都越来越不好。

有一些人会跟我说,我们是实用主义,我们不需要创新,能解决当下的问题就好,所以不需要新技术,现有的技术用好就行了。这类的公司,他们的技术设计第一天就在负债,虽然可以解决当下问题,但是马上就会出现新的问题,然后他们会疲于解决各种问题。最后呢,最后还是会走到新的技术上。

这里的逻辑很简单——进步永远来自于探索,探索是要付出代价的,但是收益更大。对我而言,不敢冒险才是最大的冒险,不敢犯错才是最大的错误,害怕失去会让你失去的更多……


秒杀架构设计的几个思路技巧

秒杀系统的架构设计,主要知识点如下:
Nginx + 前后端分离 + CDN 缓存 + 网关(限流+熔断)
集群的路由层 + Redis(缓存热点数据、分布式锁)
MQ 集群
业务处理层
数据库层(读写分离、热点隔离)

1. 秒杀业务的特点

瞬间大量的刷新页面的操作
瞬间大量的抢宝的操作
可能有秒杀器的恶性竞争

2. 总体思路

2.1 削峰限流
前端+Redis拦截,只有redis扣减成功的请求才能进入到下游
MQ堆积订单,保护订单处理层的负载,Consumer根据自己的消费能力来取Task,实际上下游的压力就可控了。重点做好路由层和MQ的安全
引入答题验证码、请求的随机休眠等措施,削峰填谷

安全保护
页面和前端要做判断,防止活动未开始就抢单,防止重复点击按钮连续抢单
防止秒杀器恶意抢单,IP限流、UserId限流限购、引入答题干扰答题器,并且对答题器答题时间做常理推断
IP黑名单、UserId黑名单功能
过载丢弃:QPS或者CPU等核心指标超过一定限额时,丢弃请求,避免服务器挂掉,保证大部分用户可用

页面优化,动静分离
秒杀商品的网页内容尽可能做的简单:图片小、js css 体积小数量少,内容尽可能的做到动静分离
秒杀的抢宝过程中做成异步刷新抢宝,而不需要用户刷新页面来抢,降低服务器交互的压力
可以使用Nginx的动静分离,不通过传统web浏览器获取静态资源
Nginx开启gzip压缩,压缩静态资源,减少传输带宽,提升传输速度
或者使用Varnish,把静态资源缓存到内存当中,避免静态资源的获取给服务器造成的压力

异步处理
redis抢单成功后,把后续的业务丢到线程池中异步的处理,提高抢单的响应速度
线程池处理时,把任务丢到MQ中,异步的等待各个子系统处理(订单系统、库存系统、支付系统、优惠券系统)
异步操作有事务问题,本地事务和分布式事务,但是为了提升并发度,最好牺牲一致性。通过定时扫描统计日志,来发现有问题的订单,并且及时处理

热点分离
尽量的避免秒杀功能给正常功能带来的影响,比如秒杀把服务器某个功能拖垮了。

分离可以提升系统的容灾性,但是完全的隔离的改造成本太高了,尽量借助中间件的配置,来实现冷热分离。
集群节点的分离:nginx配置让秒杀业务走的集群节点和普通业务走的集群不一样。
MQ的分离:避免秒杀业务把消息队列堆满了,普通业务的交易延迟也特别厉害。
数据库的分离:根据实际的秒杀的QPS来选择,热点数据分库以后,增加了分布式事务的问题,以及查询的时候跨库查询性能要差一些(ShardingJDBC有这种功能),所以要权衡以后再决定是否需要分库
避免单点:各个环节都要尽力避免
降级:临时关闭一些没那么重要的功能,比如秒杀商品的转赠功能、红包的提现功能,待秒杀峰值过了,设置开关,再动态开放这些次要的功能

2.2 Nginx的设计细节

动静分离,不走后端获取静态资源
server {
listen 8088;
location ~ \.(gif|jpg|jpeg|png|bmp|swf)$ {
root path_of_dir;
}

location ~ \.(jsp|do)$ {
proxy_pass http://localhost:8082;
}
}

开启gzip压缩,减少静态文件传输的体积,节省带宽,提高渲染速度
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 3;
gzip_disable "MSIE [1-6]\.";
gzip_types text/plain application/x-javascript text/css application/xml text/javascript image/jpeg image/gif image/png;

配置集群负载和容灾,设置失效重连的时间,失效后,定期不会再重试挂掉的节点,参数:
fail_timeout默认为10s
max_fails默认为1。就是说,只要某个server失效一次,则在接下来的10s内,就不会分发请求到该server上
proxy_connect_timeout 后端服务器连接的超时时间_发起握手等候响应超时时间

upstream netitcast.com { #服务器集群名字
server 127.0.0.1:8080;
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}

server {
listen 88;
server_name localhost;
location / {
proxy_pass http://freeoa.net;
proxy_connect_timeout 1;
fail_timeout 5;
}
}

还可以集成Varnish做静态资源的缓存,或使用tengine做过载的保护。

2.3 页面优化细节

降低交互的压力
尽量把js、css文件放在少数几个里面,减少浏览器和后端交互获取静态资源的次数
尽量避免在秒杀商品页面使用大的图片,或者使用过多的图片

安全控制
时间有效性验证:未到秒杀时间不能进行抢单,并且同时程序后端也要做时间有效性验证,因为网页的时间和各自的系统时间决定,而且秒杀器可以通过绕开校验直接调用抢单
异步抢单:通过点击按钮刷新抢宝,而不是刷新页面的方式抢宝(答题验证码等等也是ajax交互)
redis做IP限流
redis做UserId限流

2.4 Redis集群的应用

分布式锁(悲观锁)
缓存热点数据(库存):如果QPS太高的话,另一种方案是通过localcache,分布式状态一致性通过数据库来控制

分布式悲观锁(参考redis悲观锁的代码)

悲观锁(因为肯定争抢严重)
Expire时间(抢到锁后,立刻设置过期时间,防止某个线程的异常停摆,导致整个业务的停摆)
定时循环和快速反馈(for缓存有超时设置,每次超时后,重新读取一次库存,还有货再进行第二轮的for循环争夺,实现快速反馈,避免没有货了还在持续抢锁)

异步处理订单
redis抢锁成功后,记录抢到锁的用户信息后,就可以直接释放锁,并反馈用户,通过异步的方式来处理订单,提升秒杀的效率,降低无意义的线程等待
为了避免异步的数据不同步,需要抢到锁的时候,在redis里面缓存用户信息列表,缓存结束后,触发抢单成功用户信息持久化,并且定时的比对一致性

2.5 消息队列限流

消息队列削峰限流(RocketMQ自带的Consumer自带线程池和限流措施),集群。一般都是微服务,订单中心、库存中心、积分中心、用户的商品中心

2.6 数据库设计

拆分事务提高并发度
根据业务需求考虑分库:读写分离、热点隔离拆分,但是会引入分布式事务问题,以及跨库操作的难度
要执行的操作:扣减库存、生成新订单、生成待支付订单、扣减优惠券、积分变动

库存表是数据库并发的瓶颈所在,需要在事务控制上做权衡:可以把扣减库存设置成一个独立的事务,其它操作成一个大的事务(订单、优惠券、积分操作),提高并发度,但是要做好额外的检测。
update 库存表 set 库存=库存-1 where id=xx and 库存>1

2.7 答题验证码的设计

可以防止秒杀器的干扰,让更多用户有机会抢到
延缓请求,每个人的反应时间不同,把瞬间流量分散开来了

验证码的设计可以分为2种:
验证失败重新刷新答题(12306):服务器交互量大,每错一次交互一次,但是可以大大降低秒杀器答题的可能性,因为没有试错这个功能,答题一直在变
验证失败提示失败,但是不刷新答题的算法:要么答题成功,进入下单界面,要么提示打错,继续答题(不刷新答题,无须交互,用js验证结果)。这种方案,可以在加载题目的时候一起加载MD5加密的答案,然后后台再校验一遍,实现类似的防止作弊的效果。好处是不需要额外的服务器交互。MD加密答案的算法里面要引入 userId PK这些因素进来来确保每次答案都不一样而且没有规律,避免秒杀器统计结果集

答题的验证:除了验证答案的正确性意外,还要统计反应时间,例如12306的难题,正常人类的答题速度最快是1.5s,那么,小于1s的验证可以判定为机器验证

3. 注意事项

为了提升并发,需要在事务上做妥协:
单机上拆分事务:比如扣减库存表+(生成待支付订单+优惠券扣减+积分变动)是一个大的事务,为了提高并发,可以拆分为2个事务
分库以后引入分布式事务问题,为了保证用户体验,最好还是通过日志分析来人工维护,否则阻塞太严重,并发差。


如何做架构设计


作者:京东云技术团队-董健

也许对软件设计存在一些疑惑,或者缺乏明确思路,那么本节将非常适合您。

1、设计很重要

可以看一下周边的事物,那些好的东西,它们并不会天然存在,都是被设计出来的,因此设计就是创造和改善事物的重要过程。设计的重要之处在于,最初的设计往往决定最终的结果,甚至决定着事物的长期的发展。例如两个品牌的手机之间,他们可以使用同一个代工厂,但他们差异在设计时就已经决定了。

架构设计也是如此,我见过很多的软件系统经过了很多年的演进,在没有完全重构的情况下,始终无法改变最初设计模样,最初的设计决定了长期的发展。而对于业务深度耦合的系统,重构成本非常高,风险也非常大,变化也更加不确定,所以要更加重视设计。

我们要寻求更好的技术方案,推动架构的良性演进,每一步都是经过深度思考的,而架构设计方法就是帮助我们思考的框架。通过做架构设计来提升软件的质量和效率,降低风险和成本。

2、架构设计的目的是什么?

是为了解决软件系统复杂度带来的问题(架构的目标是用于管理复杂性、易变性和不确定性,以确保在长期的系统演化过程中,一部分架构的变化不会对其它部分产生不必要的负面影响。这样做可以确保业务和研发效率的敏捷,让应用的易变部分能够频繁地变化,对应用的其它部分的影响尽可能地小。)

要解决复杂度问题,首先需要识别复杂度的来源,主要集中在以下三个方面:

业务复杂度:流程多,参与者多、状态和变量多等;由业务本身决定,但业务复杂不代表软件系统复杂,例如工作流引擎并不复杂,但他可以做非常复杂的业务,在面对复杂业务时,我们常使用抽象思维,不要让软件逻辑与业务逻辑绑定在一起。

技术复杂度:高性能、高可用、高可扩展、安全,成本、规模等;这部分复杂度常常由技术本身决定,也应该由技术本身解决,通常是采用更合理的框架和工具;避免这些技术特性穿透到应用层。也可以有所取舍,在不同业务情况下,采用不同的实现程度。

设计复杂度:职责不是最小的完备的、概念不清晰的、层次不清的、业务逻辑与技术实现绑定的,组件过多以及关联依赖复杂的;这部分是由设计不合理导致的,也是对业务系统影响最大的一部分,要通过良好的设计来解决。

3、架构设计的主要内容是什么?

找到系统中的元素并搞清楚他们之间关系(如果我们不知道系统是怎么运行的,那么他一定是很复杂的。对于庞大的软件系统,如何才可以被掌控?这就需要将大系统分解为很元素,每个元素需要足够简单,并且元素与元素之间的关系清晰)

软件架构是一种结构,结构中包含了一些元素和元素之间的关系描述;

元素的种类:系统、子系统、模块,组件、服务、类、接口...

关系的种类:层次关系、数据关系、调用关系、影响力关系...

"架构表示对一个系统的成型起关键作用的设计决策,架构定系统基本就成型了,这里的关键性可以由变化的成本来决定。"-- Grady Booch. )

"Architecture represents the significant design decisions that shape a system, where significant is measured by cost of change." -- Grady Booch.

4、架构设计有什么原则?

合适原则:“合适优于业界领先”。 真正优秀的架构都是在企业当前人力、条件、业务等各种约束下设计出来的,能够合理地将资源整合在一起并发挥出最大功效,并且能够快速落地。

简单原则:“简单优于复杂”。 优先使用直接的不复杂的方案解决问题;

演化原则:“演化优于一步到位”。软件需要根据业务的发展不断地变化,架构要不断地在实际应用过程中迭代,在某个阶段必定有所取舍,但架构的演化必须是低成本的,当业务发生变化时能够最高效的迭代;在这个过程中修复缺陷的设计,积累优秀的设计;

5、架构师的职责是什么?

业务分析:梳理对业务和技术的理解和判断、形成业务领域知识、明确的业务目标和本质的业务诉求;

系统建设:降低系统复杂性、规划系统远期架构、推动架构的合理演化;

技术方案:选择合适的技术、提供对业务的解决方案,把控全局,包括质量、效率、成本、风险;

关键问题:攻克难点,解决关键问题,指导研发落地;

知识沉淀:以体系化的表达方式,面向不同人员的视图语言,持续完善知识系统;

6、架构设计过程如何?

过程:全局分析业务 → 设计方案 → 概要设计 → 详细设计 → 补充设计

视角:业务级 → 系统级 → 应用级 → 模块级 → 技术级 → 代码级 → 实施级;

架构师的协作链路较长,每一个过程都应该留下资料,越下游的角色往往需要更全面的资料;架构设计文档应该包含架构师参与的所有环节,以及这些环节产生的图文说明;不仅仅是空洞的结果,应该包含架构师的思路和想法;


全局分析阶段

这阶段需要对业务需求进行全面分析,需要将名词罗列出来,区分名词是功能、流程、名词、参与者的哪一种。再通过分析业务的本质并找到其中的关键名词,关键的名词被称之为领域,可以围绕关键的领域构建业务模型;

在这个过程中,需要统一语言、识别核心领域、按照相关性将功能归属到对应的领域,对领域之间的关系做出必要的描述,输出物是名词与解释、领域以及拥有的能力,业务架构。

名词的概念必须是清晰的,领域的职责必须是明确的,领域拥有的能力必须是相关的;其中业务架构可按照场景层、功能层、领域层、依赖层划分,例如下图:


设计方案阶段

在完成全局分析之后,我们应该设计技术方案,尽可能提供多个备选方案的图文说明。需要对备选方案做充分的优劣分析,最终取舍一项最合适的方案,没有被选择的方案(或者取舍的部分)也要被说明;

我们需要找到各项约束条件(时间、人力、硬件等),评估在约束条件允许的情况下,哪个备选方案更合适,可能考虑如下方面:

方案对业务影响:主要判断需求覆盖程度、实现业务的短期目标、考虑业务的长期目标;

方案的技术需求:安全是否满足、性能是否满足、规模是否满足、可维护性;

方案的可扩展性、方案的复杂程度、方案是否能够演进、方案演进成本如何(高成本的 慎重考虑)、方案的影响力传播如何(对上下游影响较大的 慎重考虑);

架构设计阶段 - 应用架构

用以说明当前系统的元素(系统、子系统、模块,组件)以及他们之间的关系(层次关系、依赖关系)

重点是将可复用的组件抽象后下沉,越往下层越是稳定和通用,由上层承接不稳定的业务;

应用架构图体现了层次关系,以及不完全体现了依赖关系,依赖只能是上层依赖下层,示例如下图


架构设计阶段 - 部署架构

用以说明支持应用所需要的硬件能力、以及外部中间件、网络、机房等情况;可参考下面两张图;


架构设计阶段 - 数据架构

描述数据资产结构、存储、流转、灾备的情况;最常用的是 ER 图;



架构设计阶段 - 技术架构

描述一些关键技术的说明,比如性能、安全、交互等:



描述技术选型和代码框架的说明,比如 DDD 推荐的菱形对称架构,文字和图片描述都可以:


7、有什么方法能做的更好?

学习和使用领域驱动设计,使用正确的方法梳理和理解业务,并落实到架构过程;

尽早的介入,从业务领域建模和在产品方案阶段介入、推动领域知识的传递、为后续做好铺垫;

积累业务能力和洞察力,需要识别关键部分与辅助部分、预料可扩展部分与不变部分,识别水平能力与垂直扩展;

对于架构设计产物,不要只画图,多辅以文字表述图中内容。

8、还需要掌握什么知识?

业务知识:业务架构(是对当前业务、领域、能力、流程、参与者、场景的介绍),现状架构(是对当前架构的描述,可以包含应用架构、技术架构、部署架构、数据架构等),愿景架构( 是架构应该演进到的完美情况),存在问题(现在面对的痛点、无用部分、缺陷部分)

高性能:多线程、队列、缓存、分片、异步化,前置化、静态化、预处理;

高可用:限流、降级、冗余、灾备、回滚、灰度;

扩展性:多态、防腐,依赖反转(业务身份、扩展点、SPI),抽象化(比如流程引擎、规则引擎等)、事件驱动、设计模式。