简介: 现在,几乎全部的事情都离不开软件,当你开车时,脚踩上油门,其实是车载计算机经过力度感应等计算输出功率,最终来控制油门,你从未想过这会是某个工程师的代码。html
做者 | 张羽辰(同昭)阿里云交付专家前端
导读:现在,几乎全部的事情都离不开软件,当你开车时,脚踩上油门,其实是车载计算机经过力度感应等计算输出功率,最终来控制油门,你从未想过这会是某个工程师的代码。
面向对象编程?函数式?模块化设计?微服务?这些词汇貌似都和架构这个 buzzword 有点关系,的确咱们这个领域充满了不少难以理解的词汇,这些词汇从英语翻译到中文已经丧失了部分上下文,再随着上下文的改变使得意义完全扭曲,好比:引擎、框架、架构、应用、系统……诚然你们都或多或少对这些词语达成共识,在工做中使用这些词汇进行沟通,某时就是指“咱们都懂的那个东西”,可是在我深刻的想聊聊架构或者说软件架构时,的确不得不问本身这个问题,咱们究竟是谈论什么?程序员
事实上,架构这个词根据上下文所肯定的范围较为固定,建筑学上的架构指代房屋结构、总体设计、组合构成等,而这些 high-level 设计每每并不须要全面了解底层,就像使用 RestTemplate 进行 WebService 调用时,咱们也不关心 socket 是在四层链接的同样,由于细节被隐藏了。golang
可是,建筑学上的架构与软件架构却又极大的不一样之处,问题出如今“软件”这个词上,按照 software 的词解,ware 是指产品同样的东西,而 soft 则强调易变,这是与 hardware 所对应的。咱们但愿“软件”可以进行快速的修改,应该可以快速响应甲方或者客户的需求,因此软件架构必然不像建筑架构同样,建筑一经建成,修改的成本极高,而软件应该走对应的方向,发挥易于修改的特色。web
“如今的大多数软件很是像埃及金字塔,在彼此之间堆建了成千上万的砖块,缺少结构完整性,只是靠蛮力和成千上万的奴隶完成。” —— Alan Kay。笔者认为,虽然这句话表达的意思我很赞同,但实际上,金字塔做为帝王的陵墓,是有着完整的设计逻辑,而且随着好几座金字塔的迭代的,以及逐渐完备的施工管理,后期金字塔是很是杰出的建筑表明,并做为地球上最高的人造建筑持续了好几千年。关于金字塔是否由奴隶建造仍是存有争议。(图片来自 Isabella Jusková @ Unsplash)。docker
做为工程师,咱们一方面关注软件产品的能力和行为,这每每是一个项目的起点,另外一方面咱们须要关注软件的架构设计,由于咱们但愿设计有着弹性、易于维护、高性能、高可用的系统,更但愿系统可以不断演进,而不是在将来被推倒重作。因此,回正咱们的视野,当咱们决心要设计一个好的架构时,咱们须要明确,架构每每决定的是软件的非功能性需求。这些非功能性需求有:数据库
这里引用 Robert C·Martin(Uncle Bob)的原语,“软件产品是有两方面的价值,一方面是实现功能的价值,另外一方面是架构的价值,而架构的价值可能更重要一些,由于它表明着软件 soft 的特性。”编程
本书例子过少,并且缺少现有流行框架的重构或者改进建议,有点形而上,可是在方法论层面笔者仍是认为值得一读。Robert C·Martin 对数据库(特指 RDBMS)的态度很值得讨论,首先他认为数据库是一种细节,在架构中应该与业务解耦,他强调业务代码与数据库的无关性。同时在咱们的代码进行计算时,表格每每不是理想的数据结构,好比有些场景会使用树、DAG 等等。能够回想一下,当你须要把一个树存入数据库时,你该如何实现?
根据咱们以前的讨论,后端系统采用微服务是不会影响到其功能上的价值,本质上微服务化和单体应用的差异并不会表达在功能上,不少微服务进展不顺利的同窗会常常说到:这东西用单体写早就完事儿!的确是这样,这侧面也印证了微服务只是一种软件架构,而不是别的神奇的东西,并非某个业务需求必需要使用微服务完成,咱们看中微服务,也是看中了架构方面的优点,即那些非功能性需求。也有人使用 pattern 来描述它,也有人说和 SOA 基本上是一个东西,只是粒度不一样,因此咱们一开始就别相信这个世界有灵丹妙药,也别指望有个什么技术可以瞬间替代 Oracle。后端
做为开发“企业级后端应用”的同窗,咱们常常会面临不少非业务需求上的苦恼:有时咱们须要同时支持移动端、移动 web、桌面端三种客户端;有时候咱们须要支持不一样的协议好比 JSON 或 XML;有时咱们又须要使用不一样的中间件传递消息;或者在研发时,咱们知道有一个地方写的很差,咱们想在将来补课重构;咱们想尝试最新的技术可是代价太高;系统没法扩容,或者成本极高;系统过于复杂没法在本地运行致使极低的效率……这些苦恼才是采用微服务的主要驱动力,回到咱们对软件架构的讨论之中,咱们但愿的是经过足够松耦合的独立服务,来下降组件之间变化的成本,也就是说今天更新发送通知的功能,并不会影响到用户查看购物车,也不会让研发人员半天改完,再等三天才能上线。设计模式
可是世界上没有免费的午饭,虽然咱们知道微服务有不少很好的特性,好比组件即服务、松耦合、独立部署、面向业务、高维护性、高扩展性等等,这里并不想展开讨论它的好处,咱们先考虑投入成本。假设咱们每一个同窗都完整的学习了微服务的全部知识,对市面上的框架、产品很是熟悉,摩拳擦掌准备开始,在拆解完几个服务后,咱们会发现,没有足够的自动化手段,靠手动的方式进行测试、编译、部署、监控,这是显而易见的会下降体验,若是没有优化好的部署策略,全部的服务都在某个发布日上线,那更是一种灾难。
随着规模的扩大,单体应用的代码改动成本会愈来愈大。不少时候咱们微服务的架构实践是存在误区的,咱们总认为流量通过某个 gateway 后直达某个服务,确忽视了服务之间调用的场景,理想的微服务架构应该是一张网,每一个节点都是独立的、自治的服务。
一些以前使用单体很容易作到的场景,在分布式的环境下会更加困难。好比咱们能够经过 RDBMS 提供的数据库事务来支撑一致性,可是若是订单服务和价格服务分离,势必要进行分布式事务来保证一致性(每每是最终一致性),而分布式事务的成本和难度就不用赘述了。在单体环境下,咱们能够很轻松的使用切面进行权限验证,而在微服务的场景中,服务之间相互调用是难以控制的。
拆分服务或者服务边界划分是另外一件很难作到的事情,最吃香的理论也许是根据 DDD 去进行划分,自然的领域或者子域(domain)貌似都能对应一个服务,由于足够的界限上下文(bounded context)可以保持服务的独立性,使其细节被隐藏在界限以内,听起来是个不错的主意。可是现实却十分残酷,使用 DDD 生搬硬套去进行软件开发的例子不在少数,成功例子也难以复制。
虽然我在实践中也常用业务领域去进行服务划分,可是我并不认为这是 DDD 的作法,没有必要规定有多少个 domain 就有多少服务,也不须要规定 sub domain 可否独立服务。与其进行顶层设计一揽子的解决方案,我更相信演进的力量,若是你真的须要拆分一个服务,足够的基础设施与自动化工具应该容许你低成本的去作,而不是一开始就画好全部的架构图。这就跟全部的改革同样,革命派每每不是一步功成,而是逐渐的积累的。因此使用微服务,当你可以负担的起(only you can afford it),也表示你能负担的失败同样,技术世界不存在一蹴而就,all in 很是危险。
卫报网站(Guardian)的微服务改造就是一个很好的例子,网站核心依旧是一个巨大的单体,可是新功能经过微服务实现,这些微服务调用单体所提供的 API 来完成功能。对于经常出现的市场活动(好比某个体育比赛的专用板块),这种方式可以快速实现活动页面与功能,完成业务需求,并在活动结束后删除或丢弃。我以前参与项目中,也经过等量替换与重构,慢慢绞杀(Strangler Pattern)掉一个巨大的陈旧的 JBoss 应用。
PlayStation 首席设计师 Mark Cerny 在今年的 PS5 新主机的技术分享中提到,游戏主机须要平衡好演进与革命(balance the evolution and revolution),咱们不想丢掉多年来开发者的积累,在复用过去的成功经验时,咱们也但愿你们可以使用更先进的技术。
看起来,在 Java 世界中,Spring Cloud 貌似是微服务的最优解了,甚至在不少同窗的简历上,Spring Cloud 几乎能够和微服务划等号了,不止一次的有人告诉我说:公司的技术栈不是 Java,因此搞不了微服务很难受,并非我没有学习精神和冒险精神云云。很遗憾,对于软件架构来讲,跟可没有规定编程语言,设计模式不是也出了不少版本吗?归根结底仍是 Spring Cloud 的全家桶策略更吸引人,什么事儿都不如加上几个 jar 就能拥有的神奇次时代架构更有吸引力。
不能否认,我在学习 Spring Cloud 的时候也惊叹其完整性,几乎常见的微服务需求都有足够完整的解决方案,而大多数方案是作在应用层,具备良好的适配性,好比 eurake 的注册发现、zuul 网关与路由、config service、hystrix circuit breaker 等等,经过统一的编程范式(基于 annotation 的注入与配置),足够丰富的功能选择(经常使用功能甚至都有两种选择),以及较好的集成方式。前有 Netflix 的成功经历,后随着微服务的浪潮,再加上足够庞大的 Java 社区,能够说是王道中的王道。但并不是 Spring Cloud 没有弱点,反倒这些功能设计与随后的容器化浪潮产生了分歧,至今融合 Spring Cloud 与 Kubernetes 都是热门话题,这里咱们展开说说它的不足或者限制(limitation)。
这多是最大的问题,基本只能使用 Java 做为研发语言,这一点在国内也备受争议,由于不管是做为架构师仍是入门的程序员,都须要尝试新的技术栈来进行储备或是采用新的功能,并且好比使用自制的 client 去实现 ribbon 的负载均衡也是很难的,可是若是不用 Java,作到这一点也很难,不是说 Java 语言不够优秀,而是咱们对将来应该有更多的选择,对于一个技术公司来讲编程语言应该不会成为限制,试问这个时代谁不想学习一点 golang 或者 rust 或者 scala 呢?其余服务好比 SSO、Config Service 也过于总体,若是想进行某项适配,则必须进行大量的修改(还好是开源的)。咱们很担忧这种状况都会随着框架的老去而面临推到重来的境界,Ruby on Rails 可能就是前车可鉴吧。侵入性是另外一个问题,还记得咱们在讨论软件架构时所提倡的实践规则吗?尽可能不要让顶层设计依赖底层的框架或者某种细节,可是满屏幕的 annotation 与 jar 的直接引用,无疑告诉咱们想去掉它们仍是很是难的。
对于云原生,不管是 CNCF 仍是 Pivotal (VMWare)都在强调容器化、微服务、面向云环境等,CNCF 围绕 Kubernetes 开始发展壮大,也随着这种先进的容器编排技术的流行人们渐渐发现它和 Spring Cloud 在功能上仍是存在不少重叠,虽然 k8s 与 IaaS 没有重叠,可是如今还有多少厂商再推纯 IaaS 呢?既然有功能重叠,就有取舍,考虑到 Spring Cloud 的全家桶属性,这个分歧处理一直都不能很好的解决。
不管是 Config 、Eureka 都是聚合的单点,及时它们有集群的方式达到近乎 100% 的可靠性,但在逻辑架构上,全部的微服务都依赖它们,这些集中式的资源的耦合是很是强的,它们会一直存在在你的生产环境之中,直到最后一个使用它们的系统下线。咱们在架构中须要避免使用共享的实例与资源,一个应用不会由于不能写日志而崩溃,也不该该由于本地没有 eureka 而没法启动。
诚然,在进程以内解决服务注册发现、负载均衡是很好的,它表明了最好了平台无关性,但平台的其余能力也很难享受的到了。绑定 k8s 貌似是个更好的选择,由于相对于 Spring Cloud 它更灵活,也能作到不会被基础的云平台绑定,但也更难以掌握与运维。固然我也不是认为 K8s 必须做为微服务的选择,做为容器的编排平台,它能够作更多的事情(好比跑数据库、中间件等),运行微服务应用只是其中之一。
2020年已通过了一半,从技术上来讲,Serverless 已经进入成熟期,Kubernetes 也更加成熟,已经成为事实的标准。可是不少时候咱们的方法论与架构设计是跟不上的技术发展的,不少同窗可能还在经历每周的发布日,不少同窗还没办法改进团队内老旧的技术,不少同窗的 Jenkins 仍是停留在打包的阶段,不少同窗机器上仍是没有安装 docker,不少时候并非框架或者平台的问题,而是方法论还停留在过去。
应用程序也应该践行开闭原则,对扩展开放使得咱们在将来有更多的选择。微服务是一个很好的机会能让咱们真正的演进架构而不须要付出过多的代价,当咱们须要组件化系统时,组件的关键特性正是可独立替换或升级,咱们能够不影响其余部分去进行替换和重构,这样的成本是显然低于抛弃旧的巨型框架而重写的。有着正确的态度和工具,咱们能够更快、更频繁的控制变动,咱们能够激进的选择新的技术栈,也能够合并两个耦合过紧的服务,随着服务的不断聚合、抽出,你会发现系统的逻辑架构会愈来愈清楚,再进行修改就会信心倍增了。咱们能够针对每一个服务使用不一样的存储技术,咱们可使用 OSS 处理文件,而不是继续往 Oracle 里面塞图片和视频。
这个开幕雷击虽然槽点满满,但并无下降社区对 Istio 的信心,反却是渐渐发现此次的大改动使 Istio 变得有点好用了,能够在生产中采用而不须要付出太多代价了。固然,漂亮话永远好说。
2017 年的时候 Service Mesh 仍是一个襁褓中的概念,如今已经成为了微服务领域的将来之选,但遗憾的是目前只有 Istio 足够成熟能表明这项技术,固然我也有幸实践过相似的 Sidecar 来进行反向代理、日志收集、性能监控、健康检查等功能,可是距离 Mesh 的愿景仍是有大的差距。
今天并不想展开 Service Mesh 或者 Istio 的优点,进程以外的解决方案可以确保系统的灵活性,而流量控制、服务治理、端对端的传输安全、限流、发现注册等等,咱们但愿工程师可以聚焦业务,实现架构的灵活性,聚焦真正的价值,而剩下的进行配置就好。因此我想这也是 Serverless 被无限看好的缘由,既然咱们想 delegate 对基础设施的控制,那为何还须要关心容器呢?Istio + k8s 的方案已经足够好,至少咱们团队正在认真学习,在向客户提供最佳实践以前,咱们依旧有不少问题须要解决,下面列出一些表明性的:
使用系统度量、参数等触发弹性伸缩是常见的需求,这里咱们是使用云监控?仍是本身搭一套?我一直倾向数据与用途解耦,使用 pub sub 模式解决问题,好比 CPU 太高可能会触发多个行为:触发警报,触发弹性规则,展现在 dashboard 上。咱们能够增长 pod 来分担服务的压力,或者由于某个 pod 异常退出后,启动新的 pod 来完成自恢复,这系列动做也是须要咱们本身解决的。
日志、警报等可观测性的问题,这一方面的实践较多,惟一比较担忧的是 ARMS 或者 Newrelic 这种 APM 功能目前没在目标平台上实践过,咱们但愿可以清晰的看到每一个服务的实时性能,目前这一部分还缺少考虑与设计。
Istio 的流量控制能力是很是强大的,如何对服务采起降级、限流这种常见的治理操做,也是须要总结出实践经验的。避免串流错误(cascade failure)在微服务领域也很常见,也须要避免故障蔓延。
蓝绿、灰度、金丝雀,这些多样的部署方式也须要落地,咱们可能会写一些 deployment util 之类的小脚本,部署的方式须要和 CICD 打通。一个部署工具,一个配置文件,一个 CICD 组成将来单个应用的部署方式。
基于属性的权限控制以及 OPA 的可定制性咱们是很是看好的,并且 sidecar 能够在进程以外解决权限验证问题,的确值得尝试,恰好也学习下 golang 用于定制 OPA。
分布式事务是微服务实践中的大坑,我曾经也写过相似的文章,项目经验中更多的是将事务放在单一的服务内,再加上我本身也没机会写过真正的网店系统。补偿事件也是经常使用的最终一致性方案,总之放在一块儿验证。
基于 K8s 实现应用的高可用应该不难,容器的天生优点就是易于启动与管理,若是随机杀掉 pod 不会影响系统可用性,这就算是实现了相似于 SLB + ECS + ASG 的能力,真正危险的是其余 非业务型 pod,好比 istio 的各类 supervisor。
实践微服务,做为架构师所考虑的东西远远大于只是实现业务,可是一旦铺平道路,下来的研发与迭代将会更加顺利。
若是你也对云原生架构有浓厚的兴趣,欢迎 加入钉钉交流群 与咱们交流!
《深刻浅出 Kubernetes》一书共聚集 12 篇技术文章,帮助你一次搞懂 6 个核心原理,吃透基础理论,一次学会 6 个典型问题的华丽操做!
<关注阿里巴巴云原生公众号,回复 排查 便可下载电子书>
张羽辰(同昭)阿里云交付专家,阿有着近十年研发经验,是一名软件工程师、架构师、咨询师,从 2016 年开始采用容器化、微服务、Serverless 等技术进行云时代的应用开发。同时也关注在分布式应用中的安全治理问题,整理《微服务安全手册》,对数据、应用、身份安全都有必定得研究。