架构整洁之道-组件构建原则

5 组件构建原则

  大型软件系统的架构过程与建筑物修建很类似,都是由一个个小组件组成的。所以,如果说SOLID原则是用于指导我们如何将砖块砌成墙与房间的,那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。

5.1 组件

  组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。例如,对于Java来说,它的最小组件是Jar文件。在编译运行语言中,组件是一组二进制文件的集合,而在解释运行语言中,组件则是一组源代码文件的集合。无论采用什么编程语言来开发软件,组件都是该软件在部署过程中的最小单元。

  在早期的软件开发中,程序员可以完全掌握自己编写的程序所处的内存地址和存放格式,在那里,程序中的第一条语句被称为起源(origin)语句,它的作用是声明该程序应该被加载到的内存位置。由于那时候没有重定位(relocate)技术,因此设计程序加载的内存地址是早期程序员在编程初期就要做的一个重要决策。而为了调用库函数,程序员们必须将所有要调用的库函数的源代码包含到自己的程序代码中, 然后再进行整体编译。在那个年代,存储设备十分缓慢,而内存则非常昂贵,也非常有限,编译器在编译程序的过程中需要数次遍历整个源代码,由于内存非常有限,驻留所有的源代码是不现实的,编译器只能多次从缓慢的存储设备中读取源代码,而这样做是十分耗时的——库函数越多,编译就越慢。大型程序的编译过程经常需要几个小时。

  为了缩短编译时间,程序员们改将库函数的源代码单独编译,库函数的源代码单独编译后被加载到一个指定的内存位置,然后编译器会针对该库文件创建一个符号表(symbol table),并将其和应用程序代码编译在一起,当程序运行时,它会先加载二进制形式的库文件,再加载编译过的应用程序,其内存布局如下所示:

在这里插入图片描述
  当然,只要应用程序的代码能够完全存放在地址0000~1777(八进制)内,这种组织方式完全没有问题,但是,当应用程序的代码超过这个范围时,程序员则不得不将应用程序切分成两个不同的地址段,以跳过库函数存放的内存范围:
在这里插入图片描述
  很显然,这种方案也是不可持续的,因为随着函数库中函数的增加,它的大小也随之增加,我们同样也需要为函数库划分新的区域,这样一来,程序和函数库的碎片化程度会随着计算机内存的增加而不断增加。

  程序员们提出的解决方案是——生成可重定位的二进制文件,其原理是:程序员修改编译器输出文件的二进制格式,使其可以由一个智能加载器加载到任意内存位置。

  重定位技术要求我们在加载器启动时为这些文件指定要加载到的内存地址,而且可重定位的代码中还包含了一些记号,加载器将其加载到指定位置时会修改这些记号对应的地址值。一般来说,这个过程就是将二进制文件中包含的内存地址都按照其加载到的内存基础位置进行递增。

  这样一来,程序员们就可以用加载器来调整函数库及应用程序的位置了,事实上,这种加载器还可以接受多个二进制文件的输入,并顺序在内存中加载它们,再逐个进行重定位。这样,程序员们就可以只加载他们实际会用到的函数了。

  除此之外,程序员们还对编译器做了另外一个修改,就是在可重定位二进制文件中将函数名输出为元数据并存储起来,这样一来,如果一段程序调用了某个库函数,编译器就会将这个函数的名字输出为外部引用(external reference),而将库函数的定义输出为外部定义(external definition)。加载器在加载完程序后,会将外部引用和外部定义链接(link)起来。这就是链接加载器的由来。

  链接加载器让程序员们可以将程序切分成多个可被分别编译、加载的程序段。在程序规模较小、外部链接也较少的情况下,这个方案一直很好用。然而,在20世纪60年代末期到70年代初期,程序的规模突然有了大幅度的增长,导致链接加载器的处理过程变得很慢。

  于是,程序员们将加载过程和链接过程也进行了分离,将耗时较长的部分——链接部分——放到了一个单独的程序中去进行,这个程序就是所谓的链接器(linker)。链接器的输出是一个已经完成了外部链接的、可以重定位的二进制文件,这个文件可以由一个支持重定位的加载器迅速加载到内存中,这使得程序员可以用缓慢的链接器生产出可以很快进行多次加载的可执行文件。

  而随着时间的推移,到了20世纪80年代,程序的规模进一步扩大,链接器的生产效率也变得很慢,直到20世纪90年代,随着磁盘物理尺寸不断缩小、速度不断提高、内存造价的不断降低,以及计算机时钟频率的不断提升,链接器的生产速度也不断提升,并远远超过了程序规模的增长速度。

  与此同时,编译领域中还诞生了Active-X、共享库、jar文件等组件形式,由于计算与存储速度的大幅提升,我们又可以在加载过程中进行实时链接了,链接几个jar文件或是共享库文件通常只需要几秒种时间,由此,插件化架构也随之诞生了。

5.2 组件聚合

  组件聚合是一个非常重要的设计决策,通常我们会遵循三个与构建组件相关的基本原则,即复用/发布等同原则(REP)、共同闭包原则(CCP)和共同复用原则(CRP)。

5.2.1 复用/发布等同原则(Reuse/Release Equivalence Principle)

  核心定义:软件复用的最小粒度应等同于其发布的最小粒度。

  该原则强调了在软件设计中,一个组件或模块的修改只应该影响到那些需要同时被复用和发布的部分,这意味着当组件发生变化时,只有那些相关联且同样需要更新的组件才应当一起重新发布,这一原则有助于降低组件间的耦合度,并确保系统的稳定性和可维护性。

5.2.2 共同闭包原则(Common Closure Principle)

  核心定义:我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同组件中。

  这其实是SRP(Single Responsibility Principle,单一职责原则)原则在组件层面的再度阐述。CCP的主要作用是提示我们将所有可能会被一起修改的类集中在一处,也就是说,如果两个类紧密相关,不管是源代码层面还是抽象理论层面,永远都会一起被修改,那么它们就应该归属为同一个组件。

  CCP和SRP的共同点是:将由于相同原因而修改,并且需要同时修改的东西放在一起。将由于不同原因而修改,并且不同时修改的东西分开。

5.2.3 共同复用原则(Common Reuse Principle)

  核心定义:不要强迫一个组件的用户依赖他们不需要的东西。

  CRP建议我们将经常共同复用的类和模块放在同一个组件中,更重要的是,不是紧密相连的在不应该被放在同一个组件中。

  当我们决定要依赖某个组件时,最好是实际需要依赖该组件中的每个类,而不是只依赖其中的某些类,但不依赖其他类的情况。

  CRP原则实际上是ISP(Interface Segregation Principle,接口隔离原则)原则的一个普适版,ISP原则建议我们不要依赖带有不需要的函数的类,而CRP原则则是建议我们不要依赖带有不需要的类的组件。它们的共同点是:不要依赖不需要用到的东西。

5.2.4 组件聚合张力图

  组件构建的这三个原则实际上是存在彼此竞争关系的,REP和CCP是黏合性原则,它们会上组件变更更大,而CRP原则则是排除性原则,它会尽量让组件变小。软件架构师的任务就是要在这三个原则中进行取舍。

  下图是组件聚合三大原则的张力图,图的边线所描述的是忽视对应原则的后果:
在这里插入图片描述
  只关注REP和CRP的软件架构师会发现,即使是简单的变更也会同时影响到许多组件,相反,如果架构师过于关注CCP和REP,则会导致很多不必要的发布。

  通常情况下,在项目早期,CCP原则会比REP原则更重要,因为在这一阶段研发速度比复用性更重要。

  一般来说,一个软件项目的重心会从该三角区域的右侧开始,先期主要牺牲的是复用性,然后随着项目逐渐成熟,其他项目会逐渐开始对其产生依赖,项目重心就会逐渐向该三角区域的左侧滑动。

  换句话说,一个项目在组件结构设计上的重心是根据该项目的开发时间和成熟度不断变动的,我们对组件结构的安排主要与项目开发的进度和它被使用的方式有关,与项目本身功能的关系其实很小。

5.3 组件耦合

  接下来要讨论的三条原则主要关注的是组件之间的关系,在这些原则中,我们同样会面临着研发能力和逻辑设计之间的冲突。这三条原则是无依赖环原则、稳定依赖原则和稳定抽象原则。

5.3.1 无依赖环原则(Acyclic Dependencies Principle)

  组件依赖关系图中不应该出现环

  如下是一个典型的应用程序的组件结构:
在这里插入图片描述
  在这个组件结构图中,不管我们从该图中的哪个节点开始,都不能沿着这些代表了依赖关系的边最终走回到起点。也就是说,这种结构中不存在环,这种结构被称为有向无环图(Directed Acyclic Graph,简称为DAG),这是符合无依赖环原则的。

  再看下面的组件结构图:
在这里插入图片描述
  可以明显看到,Interactors组件、Authorizer组件和Entities组件间形成了一个环,这就是循环依赖。

  在这种循环依赖的场景下,如果我们要修改Interactors组件,由于Authorizer组件依赖它,因此要考虑Interactors组件对Authorizer组件的影响而可能修改Authorizer组件,而由于Entities组件依赖Authorizer组件,因此要考虑Authorizer组件对Eneities组件的影响而可能修改Entities组件,由于Interactors组件依赖Entities组件,那么Entities组件的修改又可能影响Interactors组件导致其修改,出现了无限循环。

  而要打破这些组件中的循环依赖,将依赖图转化为DAG,则可以通过两种机制做到:

  (1) 应用依赖反转原则(DIP):在上图中,假设是Entities组件中的User类依赖了Authorizer中的Permissions类,那么我们可以在Entities中创建一个Permission接口,并在Authorizer组件中实现它,那么依赖关系就倒转过来了,如图5.3.1-1;

  (2) 创建一个新组件:在上图中,我们创建一个新组件Permissions,然后让Entities组件和Authorizer组件都依赖它,就可以打破循环依赖,如图5.3.1-2;

在这里插入图片描述

5.3.1-1 依赖反转

在这里插入图片描述

5.3.1-2 创建新组件

  当然,采用第二种解决方案也意味着在需求变更时,项目的组件结构也要随之变更,因此,我们必须要持续地监控项目中的循环依赖关系,当循环依赖出现时,必须以某种方式消除它们。为此,我们有时候不可避免地需要创建新的组件,而使整个组件结构变得更大。

  根据上述讨论,我们可以得出一个无法逃避的结论:组件结构图是不可能自上而下设计出来的。它必须随着软件系统的变化而变化和扩张,而不可能在系统构建的最初就被完美设计出来。

  这与我们通常了解的信息不一致,同样不一致的还有另一个概念:项目粗粒度的组件分组规则所产生的就是组件的依赖结构,也应该在某种程度上与项目的系统功能分解的结果相互对应,但是很明显,组件依赖关系图其实不具备这样的属性。

  事实上,组件依赖结构图并不是用来描述应用程序功能的,它更像是应用程序在构建性与维护性方面的一张地图。这就是组件的依赖结构图不能在项目的开始阶段被设计出来的原因——这时候该项目还没有任何被构建和维护的需要,自然也不需要一张地图来指引。然而,随着早期被设计并实现出来的模块越来越多,项目中就逐渐出现了要对组件依赖关系进行管理的需求,以此来预防“一觉醒来综合征”的爆发。除此之外,我们还希望将项目变更所影响的范围被限制得越小越好,因此需要应用单一职责原则(SRP)和共同闭包原则(CCP)来将经常同时被变更的类聚合在一起。

  组件结构图中的一个重要目标是指导如何隔离频繁的变更,因此软件架构师才有必要设计并且铸造出一套组件依赖关系图来,以便将稳定的高价值组件与常变的组件隔离开,从而起到保护作用。另外,随着应用程序的增长,创建可重用组件的需要也会逐渐重要起来,这时,CRP又会开始影响组件的组成。最后,当循环依赖出现时,随着无循环依赖原则(ADP)的应用,组件依赖关系会产生相应的抖动和扩张。组件依赖关系是必须要随着项目的逻辑设计一起扩张和演进的。

5.3.2 稳定依赖原则(Stable Dependencies Principle)

  依赖关系必须要指向更稳定的方向

  设计这件事不可能是完全静止的,如果我们要让一个设计是可维护的,那么其中某些部分就必须是可变的。通过遵守共同闭包原则(CCP),我们可以创造出对某些变更敏感,对其他变更不敏感的组件,这其中的一些组件在设计上就已经是考虑了易变性,预期它们会经常发生变更的。

  任何一个我们预期会经常变更的组件都不应该被一个难以修改的组件所依赖,否则这个多变的组件也将会变得非常难以被修改。

  下面是一个稳定的组件的例子,虽然非常理想化:
在这里插入图片描述
  X是一个稳定的组件,因为有三个组件依赖着X,所以X有三个不应该被修改的原因,即X需要对三个组件负责,另一方面,X不依赖于任何组件,所以不会有任何原因导致它需要被变更,我们称它为“独立组件”。

  而下图中的Y则是一个不稳定组件,它不被任何组件依赖,因此Y不需要对任何组件负责,但因为Y同时依赖于三个组件,所以它的变更就可能由三个不同的源产生:

在这里插入图片描述

  组件的稳定性是可以通过计算所有入和出的依赖关系来量化的,这里涉及到几个概念:

  (1) Fan-in:入向依赖,表示组件外部类依赖于组件内部类的数量;

  (2) Fan-out:出向依赖,表示组件内部类依赖于组件外部类的数量;

  (3) I:不稳定性, I = F a n − o u t F a n − i n + F a n − o u t I=\frac{Fan-out}{Fan-in + Fan-out} I=Fanin+FanoutFanout,该指标的范围是[0,1],I=0意味着组件是最稳定的,I=1意味着组件是最不稳定的;

  以下图为例:
在这里插入图片描述
  对于组件Cc来说,有三个组件外部类依赖于组件内部类,即Ca的q类依赖于Cc的t类、Ca的r类依赖于Cc的u类、Cb的s类依赖于Cc的u类,有一个组件内部类依赖于组件外部类,即Cc的u类依赖于Cd的v类,那么,Fan-in=3,Fan-out=1,于是 I = F a n − o u t F a n − i n + F a n − o u t = 1 3 + 1 = 0.25 I=\frac{Fan-out}{Fan-in + Fan-out}=\frac{1}{3 + 1}=0.25 I=Fanin+FanoutFanout=3+11=0.25

  稳定依赖原则(SDP)的要求是让每个组件的I指标都必须大于其所依赖的组件的I指标。也就是说,组件结构依赖图中各组件的I指标必须要按其依赖关系方向递减。

  由此可见,遵守稳定依赖原则(SDP)的组件结构是:可变更的组件位于顶层,而稳定组件位于底层。

5.3.3 稳定抽象原则(Stable Abstractions Principle)

  一个组件的抽象化程度应该与其稳定性保持一致。

  稳定抽象原则(SAP)为组件的稳定性与它的抽象化程度建立了一种关联:

  (1) 该原则要求稳定的组件同时应该是抽象的,这样它的稳定性就不会影响到扩展性;

  (2) 该原则要求一个不稳定的组件应该包含具体的实现代码,这样它的不稳定性就可以通过具体的代码被轻易修改;

  因此,如果一个组件想要成为稳定组件,那么它就应该由接口和抽象类组成,以便将来做扩展。如此,这些既稳定又便于扩展的组件可以被组合成既灵活又不会受到过度限制的架构。

  将SAP(稳定抽象原则)和SDP(稳定依赖原则)这两个原则结合起来,就等于组件层次上的DIP(依赖反转原则)。因为SDP(稳定依赖原则)要求的是让依赖关系指向更稳定的方向,而SAP(稳定抽象原则)则告诉我们稳定性本身就隐含了对抽象化的要求,即 依赖关系应该指向更抽象的方向

  抽象化程序同样可以量化:

  (1) Nc表示组件中类的数量(包括抽象类和接口);

  (2) Na表示组件中抽象类和接口的数量;

  (3) 抽象程度 A = N a N c A=\frac{Na}{Nc} A=NcNa

  A指标的取值范围从0到1,值为0代表组件不没有任何抽象类,值为1则意味着组件中只有抽象类。

  现在我们可以来定义组件的稳定性I与其抽象化程度A之间的关系了,如下图所示:
在这里插入图片描述

  我们无法要求所有组件都是最稳定的且包含了无限抽象类的(I=0,A=1)或者最不稳定的且无任何抽象的(I=1,A=0),因为组件通常都有各自的稳定程度和抽象化程度,因此我们会认为A/I图上存在着一个合理组件的区间,也存在不合理组件的区间,以下是两个不合理组件的区间,而这个区间外的区间,则可认为是合理组件区间:

  (1) 痛苦区:如上图所示,假设某个组件处于(0,0)的位置,那么它应该是一个非常稳定但也非常具体的组件,这样的组件在设计上是最不佳的,因为它很难被修改,这意味着该组件不能被扩展,当然,如果这个组件是不可变组件的话,它在这一区域是无害的,例如一些工具型类库,更具体的如String类;而如果一个多变的组件落在这个区域则会非常麻烦,因为它的任何修改都可能造成依赖它的组件的变更;

  (2) 无用区:如上图所示,当某个组件处于(1,1)的位置时,它是无限抽象的,但没有被任何组件所依赖,因此这个区域通常包含了大量的无用代码;

  很明显,最多变的组件应该离上述两个区域越远越好。

  距离两个区域最远的点连成的一条线,即从(0,1)连接到(1,0)的线,被称为 主序列线(main sequence) ,位于主序列线上的组件不会为了追求稳定性而被设计得“太过抽象”,也不会为了避免抽象化而被设计得“太过不稳定”,这样的组件既不会特别难以被修改,又可以实现足够多的功能,对于这些组件来说,通常会有足够多的组件依赖于它们,这使得它们会具有一定程度的抽象,同时它们也依赖了足够多的其他组件,这又使得它一定会包含很多具体实现。

  在整条主序列线上,组件所处的最优位置是线的两端,我们应该争取将大部分组件尽可能地推向这两个位置,但这很难,因此实践中,我们通常会将组件设计成在主序列线上,或者贴近主序列线。

  衡量一个组件距离最佳位置的距离也是可以量化衡量的:D指标,距离 D = ∣ A + I − 1 ∣ D=|A+I-1| D=A+I1∣,该指标的取值范围是[0,1],值为0意味着组件是直接位于主序列线上的,值为1则意味着组件在距离主序列最远的位置。

  通过计算每个组件的D指标,就可以量化一个系统设计与主序列的契合程度了,另外,我们也可以用D指标大于0多少来指导组件的重构与重新设计。

  对于一个良好的系统设计来说,D指标的平均值和方差都应该接近于0,其中,方差还可以被当作组件的“达标红线”来使用,我们可以通过它找出系统中那些不合常规的组件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/662980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

想好新年去哪了吗?合合信息扫描全能王用AI“留住”年味

还有不到十天,除夕就要到了。近几年春节假期中,有人第一次带着孩子直击海面冰风,坐船回老家;也有人选择“漫游”国内外,在旅行中迎接新春的朝气。合合信息旗下扫描全能王APP通过AI扫描技术,提供了一种全新的…

Acwing---798.差分矩阵

差分矩阵 1.题目2.基本思想3.代码实现 1.题目 输入一个 n n n 行 m m m列的整数矩阵,再输入 q q q 个操作,每个操作包含五个整数 x 1 , y 1 , x 2 , y 2 , c x1,y1,x2,y2,c x1,y1,x2,y2,c,其中 ( x 1 , y 1 ) (x1,y1) (x1,y1) 和 ( x …

创新大赛专访丨移步到岗荣膺2023年度人力资源服务质量卓越品牌:“人财税法”综合解决方案专家

日前,2023第三届全国人力资源创新大赛颁奖典礼暨成果展圆满举行。自2023年10月份启动以来,大赛共吸引了457个案例报名参赛,经组委会专家团队评审严格审核,企业赛道共有103个案例获奖、72家企业、13位个人、7个产业园斩获荣誉。 广…

RocketMQ消息队列(一)—— 基本概念和消息类型

RocketMQ是一个来自阿里巴巴的分布式消息中间件,于2012年开源,并在2017年正式成为Apache顶级项目。据了解,包括阿里云上的消息产品以及收购的子公司在内,阿里集团的消息产品全线都运行在RocketMQ上,并且最近几年的双十…

mybatis的一级缓存和二级缓存

一、介绍 1、mybatis缓存: mybatis包含一个非常强大的查询缓存特性,可以非常方便的定制和配置缓存,通过缓存减少Java Application与数据库的交互次数,从而提升程序的运行效率。 2、mybatis一二级缓存 mybatis的缓存分为一级缓存…

Docker中配置MySql环境

目录 一、简单安装 1. 首先从Docker Hub中拉取镜像 2. 启动尝试创建MySQL容器,并设置挂载卷。 3. 查看mysql8这个容器是否启动成功 4. 如果已经成功启动,进入容器中简单测试 4.1 进入容器 4.2 登录mysql中 4.3 进行简单添加查找测试 二、主从复…

MySQL-----初识

一 SQL的基本概述 基本概述 ▶SQL全称: Structured Query Language,是结构化查询语言,用于访问和处理数据库的标准的计算机语言。SQL语言1974年由Boyce和Chamberlin提出,并首先在IBM公司研制的关系数据库系统SystemR上实现。 ▶美国国家标…

MySQL亿级数据的查询优化-历史表该如何建

前端时间在知乎上看到一个问题,今天有空整理并测试了一下: 这个问题很具体,所以还是可以去尝试优化一下,我们基于InnoDB并使用自增主键来讲。 比较简单的做法是将历史数据存放到另一个表中,与最近的数据分开。那是不是…

如何使用Linux Archcraft结合内网穿透实现SSH远程连接

📑前言 本文主要是使用Linux Archcraft结合内网穿透实现SSH远程连接的文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是青衿🥇 ☁️博客首页:CSDN主页放风讲故事 &#…

go gin 响应数据

go gin 响应数据 package mainimport ("fmt""github.com/gin-gonic/gin" )type UserInfo struct {UserName string json:"user_name"Age int json:"age"Password string json:"-" }func JsonTest(ctx *gin.Context…

黑马Java——常见API

一、游戏打包exe 游戏打包exe要考虑的因素: 游戏打包exe核心步骤: 详见《打包exe文档》 二、Math (一) Math类的常用方法 1、代码实现 2、小结

JVM 笔记

JVM HotSpot Java二进制字节码的运行环境 好处: 一次编写,到处运行自动内存管理,具有垃圾回收功能数组下标越界检查多态(虚方法表) JVM组成 类加载子系统(Java代码转换为字节码)运行时数据…

【JavaEE进阶】 图书管理系统开发日记——贰

文章目录 🌲前言🎄设计数据库🍃引⼊MyBatis和MySQL驱动依赖🌳Model创建🎍约定前后端交互接口🍀服务器代码🚩控制层🚩业务层🚩数据层 🌴效果展示⭕总结 &#…

01- k8s基础网络知识 之 underlay与overlay网络

前言: 我们在学习k8s网络之前,必须要了解k8s网络相关的一些基础知识,比如什么是underlay网络、overlay网络等,只有把基础知识掌握之后,后续学习k8s网络的时候,一些知识点就不会再云里雾里了。 1 underlay与…

协作办公开源神器:ONLYOFFICE

目录 前言ONLYOFFICE为什么选择ONLYOFFICE强大的文档编辑功能多种协作方式多人在线协同支持跨端多平台连接器安全性极高本地部署 ONLYOFFICE 8.0版本震撼来袭可填写的 PDF 表单显示协作用户头像更新插件界面设计更快更强大 总结 前言 近几年来,随着互联网技术的不断…

如何解决 docker registry x509 证书不信任问题?

最近想尝试一下极狐GitLab(可以理解为 GitLab 在中国的发行版)内置的容器镜像仓库,这样就不用自己安装 Harbor 之类的了。于是找了个服务器安装了一个极狐GitLab 的私有化部署版本,安装过程可以参考过往的技术文章使用Omnibus 安装…

在Windows搭建gRPC C++开发环境

本文介绍在Windows下使用Visual Studio 2017编译gRPC 1.48.0并配置开发环境,以及开发、配置一个简单的c服务端以及.net客户端。 0、前置条件 1、下载gRPC源码 使用git命令行在预备存放grpc源码的目录下执行, 此处我们下载的是 grpc 1.48.0 git clone -b v1.48.0 …

Pycharm python用matplotlib 3D绘图显示空白解决办法

问题原因: matplotlib版本升级之后显示代码变了,修改为新的 # ax Axes3D(fig) # 原代码 ax fig.add_axes(Axes3D(fig)) # 新代码import numpy as np import matplotlib.pyplot as plt from matplotlib import cm from mpl_toolkits.mplot3d import Ax…

测试环境搭建整套大数据系统(一:基础配置,修改hostname,hosts,免密,时间同步)

一:使用服务器配置。 二:修改服务器名称hostname,hosts。 在 Linux 系统中,hostname 和 /etc/hosts 文件分别用于管理主机名和主机名解析。 在三台服务器上,分别执行以下命令。 vim /etc/hostnamexdso-hadoop-test-0…

༺༽༾ཊ—Unity之-04-原型模式—ཏ༿༼༻

首先创建一个项目, 在这个初始界面我们需要做一些准备工作, 建基础通用文件夹, 创建一个Plane 重置后 缩放100倍 加一个颜色, 任务1:使用 建造者模式 创建三种 金刚猿猴 零部件 拼接组合 首先资源商店下载 金刚猿猴 模…