查看原文
其他

DàYé首席路 | 架构界之六识(中篇)

曲健Nicholas 曲水流觞TechRill 2020-02-06

佛性导读


DàYé首席路 | 架构界之六识(初篇)


上回说到六识中的眼识耳识,感受开阔视野和认真倾听的力量。中篇我们集中聊一下鼻识(鼻嗅香)舌识(舌尝味),此二识的内容跨度相对更大也更驳杂一些,不求面面俱到,但求点到即说透。


鼻 识

鼻嗅香

芬香馥郁惹人嗅,形容嗅觉灵敏,我们通常都会想到狗鼻子。狗的嗅觉灵敏度大约是人类的1000倍以上,它不仅仅可以闻到淡淡的气味,还可以闻出主人的心情,神奇么?甚至有个说法:狗是通过鼻子来感知世界的。


架构师同样可以靠“嗅觉”感知程序世界。完美的系统架构,食之则甘,嗅之则香。但众生如我皆难撷完美之花,惟有香花之蜜採之,毒草之液避之。


1. 代码异味Code Smell

代码中的任何可能导致深层次问题的症状都可以叫做代码异味,这种味道通常不是错误,可能只是某些结构或写法违反了基本的原则规范,当下不一定会阻止程序运行,但未来出故障的风险系数极高。


Martin Fowler的经典之作《重构》就列举了22种典型的代码坏味道:重复代码、过长函数、过大的类...过多的注释等。其实只要足够敏感(高要求),很多代码异味都可以避免的,但就是有些人,对IDE里波浪线提示的重复代码视而不见,对错误的注释视而不见,对相似易混淆的命名视而不见... 这些程序运行起来可能都没有问题,但是日后来维护这些程序的人,却可能因为这种“异味”不小心引入致命的问题。踩坑不忘挖坑人,只剩破口大骂一地鸡毛。


架构师在自己做编码或者评审他人代码时,对代码异味的嗅探能力算是打底的基本素质之一。


2. 架构异味-反模式

有设计模式,就有反设计模式。前者是高人总结抽象出的具有普适意义的通用问题解决方案,是正向的“花香”;后者与代码异味类似,并不一定会立即导致错误,但它是一种低效、有隐患、有风险的解决方案,未来有极大可能导致错误,是慢性的“毒药”,也被分类整理出来,成为了反模式。


近阶段我最深恶痛绝的反模式列几个:

- 复用方形的轮子在我看来,编程领域的很多历史代码就像小龙虾的头。每次把头剥下来看到虾黄,都要吸两口汁水生怕浪费,即便知道头部里都是龙虾的器官,容易积聚一些有害物质。而这些已经染“毒”了的历史代码,复用一次就是引入一次危险。就像方形的轮子,压根跑不动跑不远。若问为什么用这个轮子,经常得到的回复是“这是之前的代码,一直这么跑着的,我也没管”。瞧,锅甩走了...架构师若不能对轮子了若指掌,想当然的不求甚解,是不负责任也是不合格的;


- 紊乱的结构分层:微服务体系下的业务系统层次相对比较清楚,比如简单粗暴的Controller-Business-Service,但就是架不住程序员那颗躁动的内心,似乎特别喜欢在这种地方做文章彰显自己的“设计”能力。曾经看到一个并不怎么复杂的业务系统,除了刚才说的三层,还设计好多层helper/component/handler/adapter...可能设计者觉得层高高一些,视野会更美丽吧。反正我是看懵逼了,无端增加了很多阅读和理解成本。最让人啼笑皆非的是,刚到第三个迭代周期,设计者自己已经分不清各层的调用关系,开始乱来了;


- 无视可预见的扩展:“留待日后优化”是程序员的一句世纪大谎言。相信我们都有一颗精益求精的心,若已预见到的扩展,比如将一个hardcode变量改成可动态配置,也就分分钟的事,就动手去做了吧,不要给自己的懒惰找借口,好么?拉钩上吊。



3. 性能异味-调优

能闻出性能的味道来,是比较高端的玩家了。性能涉及的边界实在太广,从前端到后端到数据库,从协议到线程到网络,从内存到磁盘到缓存...简直无所不包。


性能没有尽头,当还在用锁来解决资源竞争问题的时候,无锁设计出现了;当多核并发处理能力到瓶颈的时候,单核单线程极限压榨性能的设计出现了(参考LMAX架构)...性能问题永远是架构师躲不过去必须面对的一道坎儿,你至少得先知晓它,才能分析它直到解开它。


然后特别重要也是特别容易被忽视的一点,就是性能优化不仅仅技术单一维度,还有业务维度。相信你一定碰到过界面上只是多展示一个参数,后端接口不得不做超级复杂的逻辑控制和计算,由此引入性能问题。比如剩余库存量在秒杀页面的展示,比如不同类型的交易流水(跨库)合并在一页上展示,再比如列表页的留言评论数显示具体数字还是999+...产品设计稍微做个妥协或者转换就可以避免好多问题,包括性能。


关于性能优化,部分技术点参见:高性能Web应用的优化技术


4. 安全异味-漏洞 

程序员三界内还有一些超然的角色,现在可以出场了,比如黑客。他们大多低调内敛,却时而性格乖张,阴晴不定,拥有翻云覆雨的能力,这种能力可以让你的系统、你的生意瞬间灰飞烟灭,黑客眼里的世界从来都是千疮百孔的。


信息安全就是保护信息资产不受偶然或者恶意的侵犯,而遭受破坏、更改或者泄露。为此我们不得不在信息流动的所有环节考虑安全性,如数据层、应用层、系统层、网络层和物理层。经常被忽视的还有一类,就是业务安全。如何在复杂的业务流转和依赖中,识别出漏洞并修复,也是架构师的职责之一。比如最基本的输入正确的支付密码才能付款成功,如果因为系统设计的问题,导致可以绕过输密码这个环节,那这个业务就极度不安全了。


虽说“没有绝对的安全”,“三份技术,七分管理”,架构师作为技术塔尖的代表,用技术来确保安全责无旁贷。架构师眼里的一方世界应该是美好的,修起高墙堵上漏洞挖开护城河,保这一方世界的安宁。


本小节收个尾。曾有研究者分析了上百种气味的化学结构,得到7种基本气味(无从考证):樟脑味、麝香味、花卉味、薄荷味、乙醚味、辛辣味和腐腥味,有馨香有醒脑有刺鼻。那么当把一个系统放到架构师的面前,架构师基本可以对系统的基本气味(健康/健壮/安全...等)有感觉,算达标;若对各种气味了然于胸并有针对性方案,就是高手了。


遥知不是雪,为有暗香来。

-王安石《梅花》


舌 识

舌尝味。

HOLD ON, 到这里我觉得不少人开始头疼嗅觉和味觉的事了。没错,上面鼻识讲的“味”是“气味”,靠嗅觉;而舌识的“味”是“味道”,靠味觉。吃任何美食,舌头上的味蕾和鼻内的嗅细胞需一同将信号传送至脑内,两种官能信号合并后成为人的最终体验。其实,嗅觉在美食还没入口之前就发生了,入口后才是嗅觉、味觉两者的合作。试试捏着鼻子吃东西,90%情况都是感觉不到“味道”的,可见嗅觉之于味觉的重要性。我们可以把嗅觉(鼻识)当做一种远观和直接感受,把味觉(舌识)当做一种近观和咀嚼感受。


那么,架构师应该嚼吧出哪些味道呢?这里我先把味道简单划分为三道层次:前味、主味和回味,能品出层次的人才是合格的美食家、品酒师。


1. 模型的味道

我在这里把很多概念都归类为了模型。通用设计模式之上搭建的系统可视作一种通用模型,DDD是一种领域模型,TDD是一种测试驱动模型,BDD是一种行为驱动模型,微服务本质就是一种高内聚松耦合服务模型,而近期火热的中台更是一种说不清道不明但就是具有迷人气息的抽象模型...模型单单放在那里的时候,你闻不出什么气味(好与坏),而一旦模型实施落地,个中酸甜苦辣都端上来了任君品尝。


第一步: 选型(前味)

任何从0到1的过程,架构选型是避不开的。需要架构师对模型本身要有充分的了解,模型之间的优劣更要了解,模型与团队专业、业务战略的匹配度也要充分评估。这一步是很重要的一道前菜,且称为“前味”。


第二步: 落地(主味)

模型落地的学问就更大了。首先,模型所需的相关技术点和规范点需要与团队明确吧,若团队内部未达成一致,不管多牛逼的模型被不同的人向不同的方向拉扯,最终都会变成浆糊。其次,实操过程中难免会碰到一些或明或暗的坑,架构师应该仔细辨别,尽早提醒团队。比如rpc框架序列化的坑,maven jar包依赖版本冲突的坑,日志打印控制台导致catalina.out过大的坑等等。最后,等系统全部READY上线,尽管之前经过了大量缜密的测试,但还是让人心慌慌,怎么办?这时候需要拿到线上系统的运行数据、工单数据,分析出瓶颈和隐患,给出最合理的优化修复方案。此步骤算是主菜即“主味”。


第三步: 演进(回味) 

系统架构需要演进,其驱动力的本质是由于架构成熟度与业务规模/预期之间的不匹配。通常情况是技术被业务所驱动的升级,这时候的演进不能只靠监控和踩坑来实现,需要大局观和更开阔的视野方可。另外近些年的情况有所转变,随着一些新型技术的喷薄而出,技术开始领先驱动业务场景的不断落地,比如区块链、图计算、人工智能、5G、IoT等技术催生出很多行业新玩法和想象空间。此步称为“回味”,除了需对现有系统架构有充分的理解之外,还需对未来系统架构的走向路线有一个清晰的预判和决策。还是要再强调一下,演进不能无节制的追逐热点,不管你有多想做中台,多想做cloud native,它们并不一定适合你。


2. 数据的味道

“数据”是程序世界的底座,无数据不架构。这句话不是危言耸听,在没有考虑好数据如何持久化,如何分片,如何缓存的情况下,谈架构都是耍流氓。通常的概要设计里都会有的ER图,DDD里的领域实体,实际上都与最终的数据设计有千丝万缕的联系。数据的味道该如何品尝?


数据库的选择

现在的数据库品类琳琅满目,看看DB-Engines收录的主要品类就这么多了,关系/KV/文档/列式/图/时序/对象...等等。还是那句话套用一下就是,没有最好的数据库只有最适合的。有些人索性不选了直接找支持多模型的数据库,期望一个数据库一同将上基本不太现实,所以未来可期的架构方式八九不离十就是它了:


Polyglot Persistence 混合持久化


其实说的就是不同场景选用不同的数据库,但是这种玩法的后遗症也很突出:复杂性和运维难度升高,哪天需要关联两种不同类型的数据库,可能面临“臣妾做不到”的尴尬。


数据库的设计

关系型数据库的设计无非表、列、主键、索引;列式数据库如HBase无非rowkey、列族;KV数据如Redis无非Key、数据结构;图数据库无非点、边和属性...当面对不同的数据量级、请求量级和数据库分布式部署方式,上面的设计会翻天覆地。

比如热点数据的Key应该设计为怎样的字符串组合,时间字符在开头的话就是顺序的Key,若想Hash均匀(分片)那就可以考虑时间字符放在Key的末尾之类;

打破固有认知,比如MYSQL里的唯一索引和普通索引,在业务允许的情况下,不出意外直接选择唯一索引,有了业务约束又有了更高的性能(Really?) 其实并不然。唯一索引和普通索引的检索性能基本近似,但是对于更新,唯一索引需要做唯一性校验,坏就坏在这里,校验需要比对物,就需要读出数据页数据判断与更新后的值是否冲突,唯一索引这里无法使用普通索引可以使用的Change Buffer;

打破思维定势,比如基于关系型的建模与基于图的建模差别甚大。一个简单的Friend社交关系,关系型就是一张表的两个列,而图里面就是Person点+Friend边;


数据的价值(数据仓库到大数据平台)

数据的话题太大了,赶紧收个尾。数据的价值这里把数据仓库、数据挖掘、数据可视化等等都Cover了。架构师这里有一项重要的取舍能力表现:实时数据 VS 离线数据。千万不要为了展现自己强大的架构设计能力,把一些明明是OLAP需求非得做成OLTP业务,把线下离线分析做成了线上实时生成,不要对自己太狠。


现在的互联网企业应该都有自己或大或小的“大数据平台”了,是不是名副其实不清楚,倒是有不少“大数据平台”本质就是个“数据仓库”。真正的“大数据平台”其上应该会在多种类数据之外架设更多的内容,比如流式计算、批处理、元数据、调度、质量管理、告警监控等。


数据之上就是应用,做决策、推荐、预测、运营、可视化,它们的前提都是高质量的数据。先抛开数据清洗、数据完整性等概念,最基本的两张表同一个冗余字段的类型都不同,坑就这么埋下了。


3. 交付的味道

任何的软件系统最终都是为了交付。架构师的成就感峰值有两种极端:一种就是交付的那一刻,对自己架构作品的线上表现充满了信心;还有一种是架构设计新鲜出炉的那一刻,因为还没经过任何验证和挑战,成就感达到峰值,伴随着测试的深入和BUG的涌现,交付的那一刻反而是成就感的低谷。


持续集成CI & 持续交付CD

我就不再重复解释这两个概念了,现在应该鲜有公司还没玩过的。而架构这块要强调的可不是怎么玩持续交付(当然了解清楚是必要的),而是表达架构应该在持续交付的过程中不断试错、持续迭代的,让每一次的交付成就一次全新的峰值体验。


面向交付编程

有个说法,“任何你写的代码,半年后再看,怎么看怎么像别人写的。” 代码是什么?是给人阅读的,给电脑运行的,给后来人吐槽的。那么代码编程到底应该面向什么?正常点的有面向过程、面向对象、面向函数编程,搞笑点的有面向工资、面向工期、面向CP(Copy&Paste)编程,我这里要严肃的提出“面向交付编程”,程序员的交付物可不仅仅只有代码,还有很多很多...


在部分人的眼里,写代码实现业务功能,没有什么BUG,就算是成功的交付了,其实这离成功还差十万八千里。我这里就抛出几个问题作为基本的checklist:


- 系统是自恰的么?(单元测试、幂等、无状态)

- 系统是透明的么?(接口说明、日志轨迹、埋点监控、易于排障)

- 系统是灵活的么?(动态可配、动态伸缩、热部署)

- 系统是安全的么?(防攻击、防泄漏、反伪装、防人祸...)


总结下四个单词:

自恰 self-Consistent

透明 Transparent

灵活 Flexible

安全 Safe

缩写为面向交付的四维度 CTFS,从易记的角度可以读作 从头反思。可不就是从需求到交付到运营整个流程从头到尾的不断反思不断优化么?架构师的眼界必须放到产品研发的整个链路上,交付的是系统,是价值,更是未来。


长江绕郭知鱼美,好竹连山觉笋香。

-苏轼《初到黄州》


从嗅觉到味觉,由鼻识至舌识,架构师的这二识水乳交融,共同协作给出一个系统的“味”,此过程中对气味要足够敏感,对味道要充分咀嚼,如此架构方能经得起当下和未来的考验。



To Be Continued.



往期精彩回顾




$ DàYé首席路 | 架构界之六识(初篇)

$ DàYé首席路 | 模仿之道$ 宝马雕车香满路,另类架构老司机$ 如何理解 Site Reliability$ 互联网安全知多少$ 漫谈“架构团队”之组织架构


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存