Python 编程环境重新构建
本文记录了重新构建Python编程环境的过程,主要包括从零开始构建面向PyQt5的环境和整理混乱的Python环境。使用miniconda管理多个Python环境,强调了conda的包管理和虚拟环境管理功能,介绍了mamba作为conda的替代品以提高依赖解析和下载速度,并详细说明了安装CUDA和PyTorch的步骤。
本文探讨了软件架构中的微服务和微前端概念,强调了微服务作为SOA架构的细化,解决了系统复杂性和效率问题。微前端则将微服务的思想应用于前端开发,允许将前端应用拆分为独立的小模块,提升可维护性和灵活性。文章还讨论了微服务和微前端的实现方案、优缺点,以及在特定场景下是否需要采用微前端架构。
之前在另一家公司实习的时候就被微服务架构等一系列词汇搞得晕乎乎的,好在最后也没涉及这方面的事情,也就不了了之了。
最近又刷到了这方面的东西,正好现在公司里有一些相关的事情(在架构上),也正好做一个学习了解,捋一捋微服务(micro-service)、微前端(micro-frontend) 还有一个更摸不着头脑的 “无服务”(serverless) 是什么东西。
当然,因为最近做的一直是前端开发这方面,我的重心自然会放在微前端上,但由于这是个历史发展的概念,只有理解了微服务的发展历史才能更好地理解为什么需要、如何利用微前端架构进行开发。因此我不会细究微服务的部署,但会尝试微前端的部署过程与实例。
软件架构分为许多类别,如单体架构、微服务架构、分布式架构等,它们虽有出现的先后顺序之别,但严格意义上并没有先进与否的区别,只是适用于不同的应用场景项目规模。
比如你一个小的不能再小的应用,非要上分布式或者微服务,那可能就是脱了裤子放屁,可能RPC开销、管理成本、MapReduce开销等等都会超过你任务本身的开销,因此还是应该按需选择最合适的方案。
要理解微前端本身,我们首先要从软件架构的发展讲起,理解为什么需要微前端架构。
单体架构(monolithic software)是一种将所有功能和逻辑写在一个项目(或部署在一个项目/容器)中的写法,一个实例中集成了一个系统的所有功能,并通过负载均衡软件/设备实现多实例调用。
如我们熟知的MVC、MVVM架构严格意义上都属于单体架构,它们虽然在业务逻辑(如视图层、View Model和逻辑层)上做了区分,但本质上仍是单体应用。
区别一个单体应用最好用的方法就是:你对其中某一个功能进行改动,会不会需要重新编译整个项目?需不需要担心耦合造成的蝴蝶效应? 如果会,那就是单体应用。
最开始的单体架构是所有功能都耦合在一起,甚至前后端不分离,但随着项目体量的不断扩大,单体模式的弊端逐渐显露出来,如:扩展性差、无法实现复杂业务、技术升级困难等。
为了解决上述问题,人们给单体架构打了一些“补丁”,对代码进行拆分来提高维护性:
面向服务架构(SOA,Service-Oriented Architecture)伴随着互联网的兴起而兴起,将功能单元拆分出来放在互联网上来提供“服务”,在某些地方也被称为“分布式架构”,由于出现的年代也比较久远,一般被看作一种“中型架构”,作为“单体架构的并行拓展”。
具体地说,面向服务架构或者说分布式架构,将一个大的系统划分为多个业务模块,业务模块分别部署在不同的服务器上,各个业务模块之间通过接口进行数据交互。数据库也大量采用分布式数据库。通过LVS/Nginx代理应用,将用户请求均衡的负载到不同的服务器上。
所谓服务(service),就是在后台不间断运行、提供某种功能的一个程序。最常见的服务就是 Web 服务,通过80端口向外界提供网页访问,所以我们常见的C/S架构其实也算一种面向服务架构(后端向前端提供某种“服务”)。
采用SOA架构有以下优点:
既然服务是运行在Web上的,那么如何制定通信标准就是一个棘手且紧急的问题。
早期的SOA多以基于SOAP (Simple Object Access Protocol)协议的Webservice实现,因其通过WSDL (Web Service Description Language)实现了严格服务间通讯格式约束,所以它非常适用于对稳定性要求高、不易变、防御式的场景, 这种方式在当下的银行、电信系统中还有很大市场。但SOAP的优势也为其带来了:笨重、不灵活、修改困难、通讯效率低等问题。
我个人是搞前端的,入行的时候也已经REST大行其道了,因此这里也主推RESTful API规范。
REST(Representational State Transfer, 代表性的状态迁移)是Roy Thomas Fielding博士于2000年在他的博士论文中提出来的一种互联网软件架构风格, 目的是便于不同软件/程序在网络中互相传递信息。 使用上看似简单,但背后有着深厚的理论支持。 详见此处
HTTP是REST的一个主要表现协议,但REST本身是协议无关的。在实际开发中,我们可近似地认为REST就是用于HTTP的,使用HTTP的请求头操作字段,如GET/POST/MODIFY等。
REST的核心特征是面向资源(Resource Oriented)、可寻址(Addressability)、连通性(Connectedness)、无状态(Statelessness)、统一接口(Uniform Interface)、超文本驱动(Hypertext Driven)。
RESTful API 将所有服务抽象为资源,资源不仅代表服务器中的文件、数据库中的表等具体的东西,它可以是任何对象。 通过URL定位资源,使用 HTTP 方法(HTTP Method)确定对资源执行什么操作,通俗地说(严格而言有一定问题,见下文说明)所有操作都可以抽象为CRUD(Create-创建,Retrieve-查询,Update-更新,Delete-删除),可分别对应于HTTP的 POST、GET、PUT、DELETE。 由于使用标准的HTTP操作、没有Schema约束,REST成为系统间轻量交互的首选方式。
虽然REST的大道理很多,背后有很多理论支持,但我们使用它只需要遵循简单的规范,并不需要去证明高深的理论,就可以构造出简明易懂且灵活的API,这也是REST的魅力所在。
说了这么久终于说到主角了啊,介绍了SOA架构后理应就可以满足大部分需求了,为什么还要引入微服务架构呢?
SOA架构有以下缺点,在项目规模被进一步放大后其劣势将被凸显出来:
因此,随着Docker等容器技术的兴起,**微服务(micro-service)**作为SOA架构的进一步延申出现了。
对SOA架构系统内部的功能再进一步进行抽象和解耦,我们就可以得到一条条独立运行的微服务。
我们以一个网上商城系统为例,在SOA架构下如下图所示,每个前端都对应了一系列的服务,按业务群进行划分:
可以直观地看出,不同系统之间有较多的重复和耦合部分,增加需求和更改现有功能都比较困难,统一的数据库也容易产生性能瓶颈。
这时我们就可以对其进行进一步地抽象,降低系统内部地复杂度,如独立出用户功能、促销活动、商品查询等一系列微服务,彼此独立运行、维护和开发,每个服务也有自己的数据库,并交给一个或多个负载均衡器进行负载分配,这样系统的可维护性和稳定性就大幅增加了。
简单地说:微服务在开发方面,就是对系统功能的进一步解耦,注重不同服务之间的独立性和隔离性,提高系统整体和功能的可维护性;在部署方面,基于容器技术,使部署更灵活和便利。
虽说微服务在发展上是SOA的进一步细化,但二者在设计思路上还是有不少的区别。
没有银弹! ——The Mythical Man-Month By Fred Brooks
一个老生常谈的问题,任何架构或方案的选择并不存在绝对的优劣,而是针对实际开发场景的trade-off。
最显然的问题就是,当应用规模并不需要微服务这样细粒度的服务划分时,对系统的细分和分布式系统的开销就会超过系统本身,成为累赘。
除此之外,微服务的分布式系统包括分布式数据库、负载均衡还有系统之间复杂的依赖网络等特征显著提高了系统的复杂度,最明显的地方就是每年arXiv都有无数和微服务指标诊断和预测还有异常处理的论文产出,个人也在这方面做过一些研究(如AutoMap)。
举个例子,一个庞大的系统内微服务之间的依赖通常是极为复杂的,不仅有直接依赖,还有影响更深远但难以察觉的间接依赖,这就容易导致雪崩效应(一个服务崩溃的蝴蝶效应导致整个系统崩溃)。更糟的是,由于复杂的依赖关系,你无法快速地定位到错误产生的源头,导致服务维护时间被延长,产生巨大损失(这也是为什么这个领域频繁产出论文的原因)。
比如说,你的订单系统崩了,你从订单系统出发,一路检查了商品数据库、负载均衡系统等等,始终没有头绪,最终发现是你的订单服务在读取名称时由于XX客户正在修改用户名触发了写保护,而某个服务恰好没有做一致性回退,直接Crash掉了,导致下一个调用这个服务的服务成了幸运儿,一传十十传百,最后整个服务都崩了。(虽然这种东西不太可能发生啦)
当我们还在容器的浪潮中前行时,已经有一些革命先驱悄然布局另外一个云计算战场:Serverless架构。
不得不说,serverless真的是一个非常容易引起误会的词,第一次遇到这个词还是在论文里,当时以为是一种触发无服务的异常,后来才知道这是一种服务提供的类型。
Serverless(无服务器计算,或者说no-server)是一种新型云服务提供方式,用户不需要关心基础设施和如何部署,只需要关心服务本身。
个人感觉serverless更像是一种商业概念,它是云服务的一种新型计费方式,也就是说不再按固定的服务器资源计费,而是基于微服务架构更灵活地按量计费。
云计算通常有基础设施即服务(IaaS),平台即服务(PaaS)和软件即服务(SaaS) 等计费模式:
IaaS和PaaS时期:云计算主要以虚拟机的形式提供纯粹的硬件资源,并且通过统一化开发平台(PaaS),提供更丰富的计算和开发体验服务。云计算商业模式之所以能够成立,是因为其一直在满足一个基本假设:『如果把云用作出租的计算基础设施,那么云的弹性部署和集中维护,可以让云服务成本低于拥有成本』
因此,「无服务器计算」并不是严格意义上的「无服务器」,因为不论如何抽象或者封装底层设备,程序总是要有一个服务器作为物理载体才能运行。与服务器相对,「无服务器计算」这个概念其实更加强调计算资源自由扩展,无需人工手动配置,相当于云计算用户把设备资源的缩放管理放置在云端,从而可以很方便地维护或者扩展平台。
这也是我把serverless归入微服务这一栏的原因,因为serverless的兴起很大程度上就是和微服务架构的兴起是一致的,它也仅仅是云服务提供商在部署层基于微服务架构提出的概念而已。
回顾微服务的历史,我们发现微服务的出现是因为整个服务系统过于厚重,而对其进行进一步的拆分和抽象为更小的服务(微服务),可以独立维护、部署,满足敏捷开发需求。
然而,越来越重的前端工程也面临同样的问题,自然地想到了将微服务思想应用(照搬)到前端,于是有了「微前端(micro-frontends)」的概念:将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块,而在用户看来仍然是内聚的单个产品。当然,具有了微服务的优点,自然也有微服务带来的复杂性和缺点。
微前端架构的出现主要可以满足以下需求:
渐进地升级、更新甚至重写部分前端功能成为了可能(主要需求)
微前端架构中一般会有个容器应用(container application) 将各子应用集成起来,职责如下:
集成方式分为 3 类:
服务端集成的关键在于_如何保证各部分模板(各个微前端)能够独立发布_,必要的话,甚至可以在服务端也建立一套与前端相对应的结构:
每个子服务负责渲染并服务于对应的微前端,主服务向各个子服务发起请求
常见的构建时集成方式是将子应用发布成独立的 npm 包,共同作为主应用的依赖项,构建生成一个供部署的 JS Bundle
然而,构建时集成最大的问题是会在发布阶段造成耦合,任何一个子应用有变更,都要整个重新编译,意味着对于产品局部的小改动也要发布一个新版本,因此,不推荐这种方式
将集成时机从构建时推迟到运行时,就能避免发布阶段的耦合。常见的运行时集成方式有:
虽然直觉上用 iframe 好像不太好(性能、通信成本等),但在这里确实是个合理选项,因为 iframe 无疑是最简单的方式,还天然支持样式隔离以及全局变量隔离
但这种_原生的隔离性,意味着很难把应用的各个部分联系到一起_,路由控制、历史栈管理、深度链接(deep-linking)、响应式布局等都变得异常复杂,因而限制了 iframe 方案的灵活性
另一种最常见的方式是前端路由,每个子应用暴露出渲染函数,主应用在启动时加载各个子应用的独立 Bundle,之后根据路由规则渲染相应的子应用。目前看来,是最灵活的方式
还有一种类似的方式是Web Components,将每个子应用封装成自定义 HTML 元素(而不是前端路由方案中的渲染函数),以获得Shadow DOM带来的样式隔离等好处。
这个问题其实是微服务中常问的你真的需要微服务吗 ?,只是作为一个前端我把这部分放在了微前端的部分。
设计系统的架构受制于产生这些设计的组织的沟通结构。 — M.Conway
康威定律几乎就是微前端(准确来说是微服务架构)的理论基础了。它指出了组织架构越庞大,其系统间沟通成本越高的问题。而解决这一问题的有效手段就是,将大的系统拆分成一个个微小的,可以独立自治的子系统。一旦系统的依赖限制在了内部,功能上更加内聚,对外部的依赖变少,那么就能显著的减少跨系统之间的沟通成本了。
微前端(微服务)的假设是,所有大型系统都逃不过熵增定律,所有大型系统都将从有序变为无序,他们背后的 codebase 的归宿都将是「屎山」
基于此,微前端很多时候是「悲观主义工程师」在工程上的妥协,是一种防御性,有时候甚至是「掩耳盗铃」式的架构策略。当然,微前端倡导的不是消极的、投降主义的去回避系统中的历史遗留问题,而是告诉我们,很多时候我们可以通过分而治之的手段,让「上帝的归上帝,凯撒的归凯撒」。
基于以上两个观点,我们可以概括出,存在以下场景时,你可能就不需要微前端:
现在流行的微前端框架为micro-app, qiankun和single-spa,我将采取在现有Vue3 SPA中嵌入React 18的页面和组件的方式来测试这些微前端的架构、性能和使用体验。
作者: ChlorineC
创建于: 2023-05-25 15:27:00
更新于: 2025-02-12 15:41:00
版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
本文记录了重新构建Python编程环境的过程,主要包括从零开始构建面向PyQt5的环境和整理混乱的Python环境。使用miniconda管理多个Python环境,强调了conda的包管理和虚拟环境管理功能,介绍了mamba作为conda的替代品以提高依赖解析和下载速度,并详细说明了安装CUDA和PyTorch的步骤。
本文探讨了在React中使用react-use-websocket库来实现WebSocket功能的最佳实践,包括如何封装WebSocket逻辑、处理连接状态、发送和接收消息,以及使用ArrayBuffer传输文件。此外,文中还介绍了Socket.IO的工作原理及其与WebSocket的区别,提供了Node.js后端服务器的示例代码,展示了如何实现低延迟的双向通信和进度反馈。
本文深入探讨了浏览器的事件循环机制,解释了JavaScript的执行过程、事件循环的必要性以及如何实现非阻塞调用。通过分析JS引擎的单线程特性和渲染线程的互斥,提出了通过消息队列和事件循环来处理异步任务的方案。此外,文章还介绍了宏任务与微任务的优先级机制,并通过示例代码展示了事件循环的执行顺序和现代浏览器的多线程模型。
作者分享了个人博客框架迭代历程:从Hexo迁移到Astro的尝试,到发现Elog平台带来的启发,最终思考将Notion作为内容管理系统的新方案。文章重点描述了作者对博客系统的核心需求——解决创作同步问题,以及在探索NotionNext等解决方案过程中遇到的技术限制和思考。