learn-tech/专栏/周志明的架构课/春节特别放送(下)_积累沉淀,知行合一.md
2024-10-16 06:37:41 +08:00

24 KiB
Raw Blame History

                        因收到Google相关通知网站将会择期关闭。相关通知内容
                        
                        
                        春节特别放送_ 积累沉淀,知行合一
                        你好,我是编辑王惠,今天初四啦,同学们过年好啊~

今天呢,我们继续来复盘课程的第二个模块“架构师的视角”中的核心知识点,以及再次来感受、学习下在该模块中各位优秀同学的所学所得、所思所想。

“架构师的视角”模块内容复盘

在这个模块里,我们系统性地了解了在做架构设计时,架构师都应该思考哪些问题、可以选择哪些主流的解决方案和行业标准做法,以及这些主流方案都有什么优缺点、会给架构设计带来什么影响,等等,以此对架构设计这种抽象的工作有了更具体、更具象的认知。

服务风格设计

远程服务调用: RPC以模拟进程间方法调用为起点表示数据、传递数据和表示方法是RPC必须解决的三大基本问题。解决这些问题可以有很多方案这也是 RPC 协议/框架出现群雄混战局面的一个原因而另一个原因是简单的框架很难能达到功能强大的要求。一个RPC框架要想取得成功就要选择一个发展方向因此我们也就有了朝着面向对象发展、朝着性能发展和朝着简化发展这三条线。 RESTful服务 面向过程和面向对象两种编程思想虽然出现的时间有先后但在人类使用计算机语言来处理数据的工作中无论用哪种思维来抽象问题都是合乎逻辑的。而面向资源编程这种思想是把问题空间中的数据对象作为抽象的主体把解决问题时从输入数据到输出结果的处理过程看作是一个数据资源的状态不断发生变换而导致的结果。这符合目前网络主流的交互方式所以REST常常被看作是为基于网络的分布式系统量身定做的交互方式。

事务处理

本地事务: 本地事务是指仅操作特定单一事务资源的、不需要“全局事务管理器”进行协调的事务。ARIES理论提出了Write-Ahead Logging式的日志写入方法通过分析、重做、回滚三个阶段实现了STEAL、NO-FORCE从而实现了既高效又严谨的日志记录与故障恢复。此外在实现隔离性这方面我们要知道不同隔离级别以及幻读、脏读等问题都只是表面现象它们是各种锁在不同加锁时间上组合应用所产生的结果锁才是根本的原因。 全局事务: 全局事务可以理解为是一种适用于单个服务使用多个数据源场景的事务解决方案其中的两段式提交和三段式提交模式还会在一些多数据源的场景中用到它们追求ACID的强一致性这个目标不仅给它带来了很高的复杂度而且吞吐量和使用效果上也不够好。 共享事务: 共享事务是指多个服务共用同一个数据源,虽然目前共享事务确实已经很少见,不过通过了解事务演进的过程,也更便于我们理解其他三种事务类型。 分布式事务: 现在系统设计的主流已经变成了不追求ACID而是强调BASE的弱一致性事务也就是分布式事务它是指多个服务同时访问多个数据源的事务处理机制。我们要知道分布式系统中不存在放之四海皆准的万能事务解决方案针对具体场景选择合适的解决方案达到一致性与可用性之间的最佳平衡是我们作为一名设计者必须具备的技能。

透明多级分流系统

客户端缓存: 客户端缓存具体包括“状态缓存”、“强制缓存”和“协商缓存”三类,利用好客户端的缓存能够节省大量网络流量,这是为后端系统分流,以实现更高并发的第一步。 域名解析: 域名解析对于大多数信息系统尤其是基于互联网的系统来说是必不可少的组件它的主要作用就是把便于人类理解的域名地址转换为便于计算机处理的IP地址。 传输链路: 这也是一种与客户端关系较为密切的传输优化机制。这里我们要明确一点即HTTP并不是只将内容顺利传输到客户端就算完成任务了如何做到高效、无状态也是很重要的目标。另外在HTTP/2之前要想在应用一侧优化传输就必须要同时在其他方面付出相应的成本而HTTP/2 中的多路复用、头压缩等改进项,就从根本上给出了传输优化的解决方案。 内容分发网络: 内容分发网络CDN是一种已经存在了很长时间也被人们广泛应用的分流系统其工作过程主要涉及到路由解析、内容分发、负载均衡和它所能支持的应用内容四个方面。CDN能为互联网系统提供性能上的加速也能帮助增强许多功能比如说安全防御、资源修改、功能注入等。而且这一切又实现得极为透明可以完全不需要开发者来配合。 负载均衡: 负载均衡的两大职责就是“选择谁来处理用户请求”和“将用户请求转发过去”。如今一般实际用于生产的系统几乎都离不开集群部署,而在其中用于承担调度后方的多台机器,以统一的接口对外提供服务的技术组件,就是负载均衡器了。理解其工作原理,对于我们做系统的流量和容量规划工作是很有必要的。 服务端缓存: 服务端缓存也是一种通用的技术组件,它主要用于减少多个客户端相同的资源请求,缓解或降低服务器的负载压力,因此可以作为一种分流手段。

安全架构

认证: 认证解决的是“你是谁?”的问题,即如何正确分辨出操作用户的真实身份。在课程中我们了解了三种主流的认证方式,分别为通讯信道上的认证、通讯协议上的认证、通讯内容上的认证。 授权: 授权解决的是“你能干什么”的问题即如何控制一个用户该看到哪些数据、能操作哪些功能。我们可以使用OAuth 2.0来解决涉及到多方系统调用时可靠授权的问题而针对如何确保授权的结果可控的问题可以通过基于角色的访问控制RBAC来解决。 凭证: 凭证解决的是“你要如何证明”的问题即如何保证它与用户之间的承诺是准确、完整且不可抵赖的。对此我们也了解了Cookie-Session机制和无状态的JWT两种凭证实现方案它们分别适用于不同的场景因此我们在做架构设计时要做好权衡。 保密: 即解决如何保证敏感数据无法被内外部人员所窃取、滥用的问题。这里我们要知道,保密是有成本的,追求越高的安全等级,我们就要付出越多的工作量与算力消耗。 传输: 即解决如何保证通过网络传输的信息无法被第三方窃听、篡改和冒充的问题。传输环节是最复杂、最有效,但又是最早就有了标准解决方案的,不管是哈希摘要、对称加密和非对称加密这三种安全架构中常见的保密操作,还是通过数字证书达成共同信任、通过传输安全层隐藏繁琐的安全过程。 验证: 验证解决的是“你做的对不对?”的问题,即如何确保提交的信息不会对系统稳定性、数据一致性、正确性产生风险。虽然貌似数据验证并不属于安全的范畴,但其实它与程序如何编码是密切相关的。这里我们需要明确一点,就是缺失的校验会影响数据质量,而过度的校验也不会让系统更加健壮,反而在某种意义上会制造垃圾代码,甚至还会有副作用。

模块留言精选

第7讲

来自@zhanyd

让计算机能够跟调用本地方法一样去调用远程方法应该是RPC的终极目标。但是目前的技术水平无法实现这一终极目标所以就有了其他更可行的折中方案。

事物是慢慢演化发展的,目标可以远大,但是做事还是要根据实际情况,实事求是。

第8讲

来自@Mr.Chen

RPC只是服务进程之间简化调用的一种方式它可以让开发者聚焦于业务本身。而对于服务间通信的各种细节交给框架处理这个维度来说如果撇开这一层面分布式系统的服务调用可以采用任何一种通信方式比如HTTP、Socket等。

第9讲

来自@tt

对于REST我的第一印象就是服务端无状态有利于水平扩展但更多的是停留于具体的技术层面。

过程如果有意义的话,一定会产生一个结果,这个结果就是资源的状态发生了转移(幂等前提下的重试不算),但是过程的细节更多,所以抽象程度无法做到向面向资源看齐。在一个过程中,我们可以对有关联关系的不同层次与结构的资源同时进行处理,但是面向资源却不容易做到。

我能想到的例子是转账操作同时操作两个资源即账户而且要保证事务的ACID。在转账的过程中要处理很多异常情况尤其是涉及到多方交易的时候所以写这样的交易就非常复杂容易出错。

如果用面向资源的角度去考察,可以看成是对三个资源的操作:转出账户、转入账户以及事务。这里把事务列为单独的资源,是为了呼应上面提到的一个资源状态变化引起的关联资源的变化。

如果转账操作利用TCCTry-Confirm-Cancel的方法我觉得就是一种更偏向于面向资源的做法每次只改变一个资源的状态。如果某个关联资源的状态改变失败就对它发起一个逆操作比如冲正。这样可以做到很高的并发在做到保序性的前提下做差错处理也很简单。相当于把一个复杂操作分解成了多个简单操作这样开发起来也很快很容易复用。有点类似CISC和RISC指令集的关系。

第10讲

来自@陈珙

谢谢老师的分享我也谈谈自己实践REST和RPC后的感想主要在第一、第二的争议点感触非常深。

争议一面向资源的编程思想只适合做CRUD只有面向过程、面向对象编程才能处理真正复杂的业务逻辑。- 争议二REST与HTTP完全绑定不适用于要求高性能传输的场景中。

之前我们用REST到了第2成熟度关于第一点争议包括我现在的想法也是认为面向资源更加适合做数据读写接口的场景例如某NoSQL的应用服务API封装或者提供某内部使用API的微服务更加适合。

原因主要有两个。首先如果是作为提供给前端使用处理起复杂业务的时候不好抽象其次原本一个接口可以处理的复杂逻辑但是因为REST原因导致接口粒度要细到N个假如由前端人员对接那么就会增大他们的业务组合难度我更加倾向大部分的业务逻辑由后端解决前端尽量关注数据展示与动画交互数据离后端人员最近

那么当粒度细化了以后就会引申出第二个争议所说的性能问题。这里也有两方面原因首先是因为要做接口的编排组合其次也是因为REST被HTTP绑得死死的那么开发人员就不得不去关注那些细节了。

举个例子HTTP Status Code的参数除了是body外可能还会是header也有可能是URI参数对于实际开发的便捷度来说并不够友好。

另外课程中老师还提到了GraphQL。该技术的确能缓解Query的部分问题但是我认为它同时也存在不可避免的问题就是如何让使用端可以很好地了解并对接数据源及其属性

对于这种存在争议性的东西,我的建议是尽可能地少引入团队。毕竟争议性越大,就意味着大家对它的理解越少,无论是引入推广还是实施的具体效果,都会存在很长周期的磨合与统一。

不过它也不是一文不值的。我们团队做的是解决方案解决的是针对性的问题场景对于一些比较清晰、比较接近数据的场景如NoSQL的API或者简单的、方便抽象的、相对需求稳定的业务场景如某内部微服务的API我认为是可以尝试使用的。

第11讲

来自@zhanyd

FORCE策略要求事务提交后变动的数据马上写入磁盘没有日志保护但是这样不能保证事务的原子性。比如用户的账号扣了钱、写入了数据库这时候系统崩溃了商品库存的变更信息和商家账号的变更信息都还没来得及写入数据库这样数据就不一致了。

因此为了实现原子性保证能够恢复崩溃绝大多数的数据库都采用NO-FORCE策略。而为了实现NO-FORCE策略就需要引入Redo Log重做日志来实现即使修改数据时系统崩溃了重启后根据Redo Log就可以选择恢复现场继续修改数据或者直接回滚整个事务。

换句话说就是,我先把我要改的东西记录在日志里,再根据日志统一写到磁盘中。万一我在写入磁盘的过程中晕倒了,等我醒来的时候,我照着日志重新做一遍,也能成功。

Commit Logging方式实行了NO-FORCE策略照理说这样已经实现了事务的功能已经很牛了但是当一个事务中的数据量特别大的时候等全部变更写入Redo Log然后再统一写入磁盘这样性能就不是很好就会很慢老板就会不开心。

那能不能在事务提交之前,偷偷地先写一点数据到磁盘呢(偷跑)?

答案是可以的这就是STEAL策略。但是问题来了你偷摸地写了数据万一事务要回滚或者系统崩溃了这些提前写入的数据就变成了脏数据必须想办法把它恢复才行。

这就需要引入Undo Log回滚日志在偷摸写入数据之前必须先在Undo Log中记录都写入了什么数据、改了什么地方到时候事务回滚了就按照Undo Log日志一条条恢复到原来的样子就像没有改过一样。

这种能够偷摸先写数据的方式就叫做Write-Ahead Logging。性能提高了同时也更复杂了。不过虽然它复杂了点但是效果很好啊MySQL、SQLite、PostgreSQL、SQL Server等数据库都实现了WAL机制呢。

第12讲

来自@Wacky小恺

在软件开发的发展历程中“提供简洁的API”始终贯穿至今因此这一讲中提到的透明事务在我看来对普通开发人员的使用层面来说是完全有必要的。

但是作为开发人员一定要有精益求精的品质也许我们在日常使用中已经习惯了使用简洁的API来实现强大的功能。但如果遇到棘手的问题或者需要自己思考解决方案的场景那么“内功”就能显露出它的威力。

第13讲

来自@zhanyd

学校组织知识竞赛,学生们(参与者)以一组为单位参加比赛,由一个监考老师(协调者)负责监考。考试分为考卷和答题卡,学生必须先在十分钟内把答案写在考卷上(记录日志),然后在三分钟内把答案涂到答题卡上(提交事务)。

两段式提交

准备阶段: 老师宣布“开始填写考卷时间十分钟”。十分钟内写好考卷的学生就回答Prepared。十分钟一到动作慢还没写好的学生就回答Non-Prepared。如果有学生回答Non-Prepared该小组被淘汰。 提交阶段: 如果所有的学生都回答了Prepared老师就会在笔记本上记下“开始填答题卡”Commit然后对所有的学生说“开始填答题卡”发送 Commit 指令)。学生听到指令后,就开始根据考卷去涂答题卡。 如果学生在涂答题卡的时候,过于紧张把答题卡涂错了,还可以根据考卷重新涂;如果所有的学生在规定时间内都填好了答题卡,老师宣布该小组考试通过。

三段式提交

CanCommit阶段 老师先给学生看一下考卷,问问学生能不能在十分钟内做完。如果有学生说没信心做完,该小组直接淘汰。 PreCommit阶段 如果学生都说能做完,老师就宣布:“开始填写考卷,时间十分钟”,和两段式提交的准备阶段一样。 DoCommit阶段 和两段式提交的提交阶段一样。

第14讲

来自@Goku

XA事务成立的前提是所有的服务都可用在分布式环境下使用的代价是如果有一个服务不可用那么整个系统就不可用了。另外XA事务可能会对业务有侵入而依靠可靠消息队列和重试机制则不需要侵入业务。

第15讲

来自@tt

我觉得可靠事件队列最适用的场景就是在内部系统中做高可靠。

TCC的范围扩大了一些适合于新设计的系统SAGA的适用性最广因为对服务提供的接口没有要求可以有落地人工处理做保证。我们现在涉及三方互联的老系统都可以看作是SAGA的一种形式。

第16讲

来自@zhanyd

一个不起眼的DNS竟然暗藏了这么多精妙的设计计算机技术发展的每个阶段成果都是人类智慧的结晶。

关于奥卡姆剃刀原则我也想做点补充。奥卡姆剃刀原则又被称为“简约之原则”它是由14世纪圣方济各会修道士奥卡姆英格兰的一个地方的威廉William of Occam提出来的他说过这样一段话

切勿浪费较多东西,去做“用较少的东西,同样可以做好的事情”。

更有名的一句话是:如无必要,勿增实体。

在历史上各个时代,最高深的物理学理论,从形式上讲都不复杂,从牛顿力学,到爱因斯坦的相对论,到今天物理学的标准模型。例如,质能方程 E=mc^2 ,欧拉恒等式 e^(iπ) + 1 = 0都以极简的方式描述了极其复杂的规律。

关于计算机系统,在能满足需求的前提下,最简单的系统就是最好的系统。很多人为了显示自己的技术水平,明明是很简单的需求,却上了一堆高大上的技术,为了技术而技术,忘了技术的本质是为业务服务的,这显然违背了奥卡姆剃刀原则。

第17讲

来自@追忆似水年华

我在做前端开发的时候遇到了微信会自动缓存页面静态资源的问题必须要手动刷新页面才行有时候还得刷新好几遍才可以有些极端情况则是短时间内连续刷新依然显示旧页面这个问题在公司内一些同事的手机上均出现过。学习了周老师的这节课对缓存有了基本的了解明天就用Charles抓包看看微信内对网页的客户端缓存策略是什么。

第18讲

来自@Jxin

这里想补充一下QUIC相对于TCP的两点内容

自定义重传机制: TCP是通过采样往返时间RTT不断调整的但这个采样存在不准的问题。第一次发送包A超时未返回第二次重发包A这时收到了包A的响应但TCP并不能识别当前包A的响应是第一次发送还是第二次重发返回的这时不管怎么减都可能出现计时偏长或过偏短的问题。而QUIC为每次发送包都打了版本号包括重发所以可以很好地识别返回的包是哪次发送包的进而计算也就相对准确。 自定义流量控制: TCP的流量控制是通过滑动窗口协议是在连接上控制的窗口。QUIC也玩滑动窗口但是力度是可以细分到具体的Stream。

其实应用层的协议多种多样比如直播的RTMP、物联网终端的MQTT等但感觉都是两害取其轻的专项优化、对症下药的方案。只有QUIC直面了TCP的问题通过应用层的编码实现系统地提供更好的“TCP连接”。

第19讲

来自@zhanyd

在网上看到华为云CDN主要的应用场景希望借此可以帮助我们更好地理解内容

网站加速CDN网络能够对加速域名下的静态内容提供良好的加速服务。支持自定义缓存规则用户可以根据数据需求设置缓存过期时间缓存格式包括但不限于zip、exe、wmv、gif、png、bmp、wma、rar、jpeg、jpg等。 文件下载加速适用于使用http/https文件下载业务的网站、下载工具、游戏客户端、App商店等。 点播加速适用于提供音视频点播服务的客户。通过分布在各个区域的CDN节点将音视频内容扩展到距离用户较近的地方随时随地为用户提供高品质的访问体验。 全站加速适用于各行业动静态内容混合含较多动态资源请求如asp、jsp、php等格式的文件的网站。

第20讲

来自@zhanyd

为什么负载均衡不能只在某一个网络层次中完成,而是要进行多级混合的负载均衡?

因为每一个网络层的功能是不一样的这样就决定了每一层都有自己独有的数据在不同的网络层做负载均衡能达到不同的效果。比如要修改MAC地址在数据链路层修改最方便要修改IP地址最好在网络层修改。

关于网络分层我这里也打个比方。小帅在网上下单买东西卖家需要寄快递把要寄的商品物理层打包到包装盒里数据链路层然后把包装盒放到快递盒子里网络层在快递单上写上寄件地址和收件地址Headers

然后快递员打电话给小帅拿快递传输层这里出现了TCP三次握手连接

快递员:“喂,这里有你的快递,麻烦到门口拿一下”。 小帅:“好的,我这就过来”。 快递员:“那我在门口等你”。

小帅拿到快递后,在网上点击确认收货按钮,确认收货(返回 http Status Code 200应用层

第22讲

来自@zhanyd

“能满足需求的前提下最简单的系统就是最好的系统”。这句话的隐藏前提是我们的选择空间要足够大。不管是CDN、负载均衡、客户端缓存、服务端缓存还是分布式缓存都给我们提供了大量的选择余地可以根据自己系统的实际情况灵活地选择最适合的方案。

这句话的另一种理解是:没有最好的方案,只有最合适的方案。

第25讲

来自@zhanyd

角色是为了解耦用户和权限之间的多对多关系。比如有100个用户他们的权限都是一样的如果给每个用户都设一遍权限这就太麻烦了而且还很容易出错。这时候设置一个角色把对应的权限配置到角色上然后这100个用户加到这个角色中就行了。

角色还有一个好处,如果角色的权限变了,所有角色中用户的权限也会同时变更,不用一个个用户去设置了。

许可是为了解耦操作与资源之间的多对多关系,比如有新增用户、编辑用户、删除用户的三种操作,通常这些都是一起的,要么都能操作,要么都不能操作。这时候就可以把这三种操作打包成一个用户维护许可,用许可和角色关联更简洁。

关于Spring Security中的Role和Authority我是这么理解的Role就是普通的角色拥有一组许可这个角色下的所有用户的权限都是一样的。

但是如果一个角色中的一些用户有个性化的需求,比如销售助理角色,本来没有查看客户的权限,但是某个销售助理比较特殊,需要查看客户的信息,这时如果是单角色的系统,就需要新增一个“销售助理可查看客户角色”,这样很容易导致角色数量爆炸。

而有了Authority就可以满足这种个性化需求只要把查看客户的权限加到Authority中赋予用户就行了。

第26讲

来自@zhanyd

Cookie-Session就相当于是坐飞机托运了行李只要带着登机牌就行了。但是一旦托运了行李行李就和飞机绑定了你就不能随意换航班了。

JWT就相当于是坐飞机拎着行李到处跑每次过安检还要打开行李箱检查而且箱子太小也带不了多少东西。但它的优点是可以随意换航班行李都在自己身边。