java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(一:总览篇)~整起

Posted AIminminHu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(一:总览篇)~整起相关的知识,希望对你有一定的参考价值。

PART1:项目情景发展历程:很久很久以后,敏小言和老胡开的小超市,突然发生了一些变故:

  • 超市中除了柜台格子业务之外,还有用户、视频、签到、评论、数据处理等业务【之前,咱们项目中的用户、视频、签到、评论、数据处理等都在一台机器上或者一整个项目(的jar包中。部署到服务器中运行,然后被访问)(好多好多controller嘛,忘了吗?,好多controller一直调用到service再到dao,再去调用Mybatis的映射文件,借助实体向后调用一直调用到DB中)】,----->
    • 具体到接触的XXX高速项目中的话,比如 数据管理【分为历史数据、实时数据、预测数据】、视频监控模块、预警处理等三个模块,也是SSM下的,那么跟超市是一个路子。
    • 出现了一个问题:用户太多了访问量太大了怎么办?---->这样来解决,看框图中用硬件或者软件实现的负载均衡横向扩展(水平拆分)服务器数量----->假设有两台服务器,A服务器占用98%资源,B服务器占用了10%资源,首先资源分配不公平,万一A服务器崩了多数人不久疯了,所以得让AB服务器资源均衡一点----------->负载均衡:nginx,让用户觉得就像在访问一台服务器,但其实不是)----->通过负载均衡技术,将流量尽可能均摊到集群中的每台机器上,以此克服单台机器硬件资源的限制,做到横向扩展
    • 另一个问题是:(用户太多了,用户相关操作量较大,而比如课程、或者其他功能比如签到非常少(有些业务的实际作用率并不高,有些业务的承重量很大)【或者说业务系统本身的实现较为复杂、扩展性较差、性能也有上限,代码和功能的复用能力较弱,我们会将一个巨型业务系统拆分成多个微服务,根据不同服务对资源的不同要求,选择更合理的硬件资源。例如,咱们的业务系统本身中有些流量较小的服务只需要几台机器构成的集群即可,而核心业务则需要成百上千的机器来支持,此时将一个巨型业务系统拆分成多个微服务,然后根据不同服务对资源的不同要求,选择更合理的硬件资源就可以最大化系统资源的利用率。并且除了最大化系统资源的利用率,还可以在服务维度进行重用,在需要某个服务的时候,直接接入即可,从而提高开发效率。】,你像上面横向扩展服务器数量就解决不了这个问题---------->将原来的整体项目拆开模块化各个模块(IDEA中一个module或者一个jar包)放到不同的电脑上(所有的模块都有自己的数据库),模块之间相互调用就行了,就可以实现给操作量高的功能模块分多一点服务器,给操作量少的功能模块分少一点服务器(HTTP RestFul风格,上一个模块所在的电脑把上一个操作处理完之后给下一个模块所在的另一台电脑发个请求,让另一个电脑上的模块去处理)----->但是问题来了,你把原来整体项目的各个功能模块分为不同的单独的模块,这些模块之间肯定要相互通信相互联系呀,在解决相互通信中产生的问题(问题主要分为四个方面,从这四个方面进行解决)的过程中出现了很多的解决方案---->)
      • 随着互联网的发展,网站的规模越来越大,用户数量越来越多。单一应用架构 、垂直应用架构无法满足我们的需求,这个时候分布式服务架构就诞生了。分布式服务架构下,系统被拆分成不同的服务比如短信服务、安全服务,每个服务独立提供系统的某个核心服务
      • 总而言之,我们将一个巨型系统拆分为多个微服务模块,大体上是因为下面两个原因:
        • 有些流量较小的服务只需要几台机器构成的集群即可,而核心业务则需要成百上千的机器来支持,拆分为多个微服务模块就可以最大化系统资源的利用率
        • 另外一点就是,可以在服务维度对代码和功能进行重用或者复用,在需要某个服务的时候,直接接入即可,从而提高开发效率,整个服务可以最大化地实现重用,也可以更加灵活地扩展
      • 来个小插曲。其实,一般咱们互联网项目一般有这些特点:
        • 互联网项目特点:
          • 用户量大,流量大,并发高
          • 海量数据
          • 容易受到攻击
          • 功能比较繁琐
          • 变更快
    • (插曲完成)但是新问题又来了,你把原来整体项目的各个功能模块分为不同的单独的模块,这些模块之间肯定要相互通信相互联系呀,在解决相互通信中产生的问题
      • (单独分开的模块相互通信中产生的问题主要分为四个方面,从这四个方面进行解决)的过程中出现了很多的解决方案
        • 四个方面问题之一:1.这么多服务,客户端该如何去选择哪一个去访问(API网关,服务路由)
          • 解决思路就是:设置一个统一的API接口(网关),然后这个接口会拦截所有到来的客户端的请求,然后再将这些请求均衡的分发到各个服务器上面去【微服务背景下,一个系统被拆分为多个服务,但是像安全认证,流量控制,日志,监控等功能是每个服务都需要的,没有网关的话,我们就需要在每个服务中单独实现,这使得我们做了很多重复的事情并且没有一个全局的视图来统一管理这些功能。网关主要做了一件事情:请求过滤 】【服务网关(API Gateway)不是为了解决微服务之间调用的紧耦合问题,服务网关(API Gateway)主要是为了简化客户端的工作。其实它还可以用来降低函数之间的耦合度。有了API Gateway之后,一旦服务接口修改,你可能只需要修改API Gateway, 而不必修改每个调用这个函数的客户端,这样就减少了程序的耦合性。】
            • 当UI需要调用很多微服务时,它需要了解每个服务的接口,这个工作量很大。于是就用服务网关创建了一个Facade,把几个微服务封装起来,这样UI就只调用服务网关就可以了,不需要去对付每一个微服务
            • 网关,看这里:https://blog.csdn.net/m0_52436398/article/details/128226141
        • 四个方面问题之二:2.这么多服务,位于不同服务器上的服务之间该如何通信或者说互相调用(HTTP,RPC框架,异步调用)
          • 在微服务架构中,需要调用很多服务才能完成一项功能。服务之间如何互相调用就变成微服务架构中的一个关键问题。服务调用有两种方式,一种是RPC方式【紧耦合的RPC方式】,另一种是事件驱动(Event-driven)方式,也就是发消息方式【消息方式是松耦合方式】。也有换一个角度看服务调用的两种方式的:
            • RPC方式是紧耦合的:
              • 紧耦合的主要问题就是客户端和服务端的升级不同步。服务端总是先升级,客户端可能有很多,如果要求它们同时升级是不现实的。它们有各自的部署时间表,一般都会选择在下一次部署时顺带升级。一般有两个办法可以解决这个问题:
                • 同时支持多个版本:这个工作量比较大,因此大多数公司都不会采用这种方式
                • 服务端向后兼容:这是更通用的方式。例如你要加一个新功能或有些客户要求给原来的函数增加一个新的参数,但别的客户不需要这个参数。这时你只好新建一个函数,跟原来的功能差不多,只是多了一个参数。这样新旧客户的需求都能满足。它的好处是向后兼容(当然这取决于你使用的协议)。有点像方法重载emmm
                  • 它的坏处是当以后新的客户来了,看到两个差不多的函数就糊涂了,不知道该用那个。而且时间越长越严重,你的服务端可能功能增加的不多,但相似的函数却越来越多,无法选择。它的解决办法就是 使用一个支持向后兼容的RPC协议,现在最好的就是Protobuf gRPC,尤其是在向后兼容上【它给每个服务定义了一个接口,这个接口是与编程语言无关的中性接口,然后你可以用工具生成各个语言的实现代码,供不同语言使用。函数定义的变量都有编号,变量可以是可选类型的,这样就比较好地解决了函数兼容的问题。】
            • 消息方式是松耦合方式,比紧耦合的RPC方式要优越,但RPC方式如果用在适合的场景也有它的一席之地。
              • 我们总在谈耦合,那么耦合到底意味着什么呢?耦合的种类:
                • 时间耦合:客户端和服务端必须同时上线才能工作。发消息时,接受消息队列必须运行,但后台处理程序暂时不工作也不影响
                  • 时间耦合,对于多数应用来讲,你希望能马上得到回答,因此即使使用消息队列,后台也需要一直工作
                • 容量耦合:客户端和服务端的处理容量必须匹配。发消息时,如果后台处理能力不足也不要紧,消息队列会起到缓冲的作用
                  • 容量耦合,如果你对回复有时间要求,那么消息队列的缓冲功能作用不大,因为你希望及时响应
                • 接口耦合:RPC调用有函数标签,而消息队列只是一个消息。例如买了商品之后要调用发货服务,如果是发消息,那么就只需发送一个商品被买消息
                  • 真正需要的是自动伸缩(Auto-scaling),它能自动调整服务端处理能力去匹配请求数量。第三和第四,接口耦合和发送方式耦合,这两个确实是RPC方式的软肋
                • 发送方式耦合:RPC是点对点方式,需要知道对方是谁,它的好处是能够传回返回值。消息既可以点对点,也可以用广播的方式,这样减少了耦合,但也使返回值比较困难AI今后会渗入到具体程序中,使程序具有学习功能。而RPC模式注定没有自适应功能。事件驱动本身就具有对事件进行反应的能力,这是自我学习的基础
            • 事件驱动(Event-Driven)方式:Martin Fowler把事件驱动分成四种方式(What do you mean by “Event-Driven”),简化之后本质上只有两种方式:【实际上从应用的角度来讲,它们并不应该分属一类,它们的用途完全不同。事件通知是微服务的调用(或集成)方式,应该和RPC分在一起。事件溯源是一种存储数据的方式,应该和数据库分在一起。】
              • 事件通知(Event Notification):就是微服务之间不直接调用,而是通过发消息来进行合作
              • 事件溯源(Event Sourcing):事件溯源有点像记账,它把所有的事件都记录下来,作为永久存储层,再在它的基础之上构建应用程序。
                • 把系统中所有的数据都以事件(Event)的方式记录下来,它的持久存储叫Event Store, 一般是建立在数据库或消息队列(例如Kafka)基础之上,并提供了对事件进行操作的接口,例如事件的读写和查询。事件溯源是由领域驱动设计(Domain-Driven Design)提出来的。
                • 事件溯源是微服务的一种存储方式,它是微服务的内部实现细节。因此你可以决定哪些微服务采用事件溯源方式,哪些不采用,而不必所有的服务都变成事件溯源的。通常 整个应用程序只有一个Event Store【Event Store内部可以分成不同的stream(相当于消息队列中的Topic), 供不同的微服务中的领域实体(Domain Entity)使用。】, 不同的微服务都通过向Event Store发送和接受消息而互相通信
                  • 事件通知只是微服务的集成方式,程序内部是不使用事件溯源的,内部实现仍然是传统的数据库方式。只有当要与其他微服务集成时才会发消息。而在事件溯源中,事件是一等公民,可以不要数据库,全部数据都是按照事件的方式存储的在一个系统中,可以某些微服务用事件溯源,另外一些微服务用数据库
                • 事件溯源的一个短板是数据查询,它有两种方式来解决。
                  • 第一种是直接对stream进行查询,这只适合stream比较小并且查询比较简单的情况。
                  • 查询复杂的话,就要采用第二种方式,那就是建立一个只读数据库,把需要的数据放在库中进行查询。数据库中的数据通过监听Event Store中相关的事件来更新。
                • 数据库存储方式只能保存当前状态,而事件溯源则存储了所有的历史状态,因而能根据需要回放到历史上任何一点的状态,具有很大优势。但它也不是一点问题都没有
                  • 第一,它的程序比较复杂,因为事件是一等公民,你必须把业务逻辑按照事件的方式整理出来,然后用事件来驱动程序
                  • 第二,如果你要想修改事件或事件的格式就比较麻烦,因为旧的事件已经存储在Event Store里了(事件就像日志,是只读的),没有办法再改。
            • 【也可以说如今的互联网环境下,分布式系统很流行,而分布式系统的根基在于网络编程,这个网络编程就是来解决咱们位于不同服务器上的服务之间通信的,而网络编程领域的王者就是Netty【网络编程框架】
          • 我们可以使用 Java RMI(Java Remote Method Invocation)、Hessian这种支持远程调用的RPC框架来简单地暴露和引用远程服务。但是!当服务越来越多之后,服务调用关系越来越复杂。当应用访问压力越来越大后,负载均衡以及服务监控的需求也迫在眉睫。我们可以用 F5 这类硬件来做负载均衡,但这样增加了成本,并且存在单点故障的风险。所以,Dubbo出现了
        • 四个方面问题之三:3.这么多服务,如果对服务们进行统一管理(也就是如何实现服务注册与发现,高可用)
        • 四个方面问题之四:4.4.这么多服务,用着用着服务突然挂了怎么办
          • 利用熔断机制,服务降级,限流、超时与重试等机制来保护自己
          • 备份一份呗防止崩,做备份、做副本、搞集群,防止自己拉垮,
          • 保证系统稳定性,跟保证系统可用性挺吻合
          • 防止别人坏了影响我,故做隔离,用线程池等池化技术、机器隔离、信号量隔离
      • 为了解决这四个问题,市面上的大公司们纷纷打出了自己的组合拳,也就是不同的解决四个问题的解决方案
        • 1.Spring Cloud NetFlix公司首先拿出来一套一站式解决(上面四个问题的)方案
        • 2.Apache Dubbo zookeeper,第二套解决方案,半自动(Dubbo详情,看这篇
          • Dubbo 的出现让上述问题得到了解决。Dubbo 帮助我们解决了什么问题呢?
            • 负载均衡 : 同一个服务部署在不同的机器时该调用哪一台机器上的服务。
            • 服务调用链路生成 : 随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的
            • 服务访问压力以及时长统计、资源调度和治理 :基于访问压力实时管理集群容量,提高集群利用率。
          • Dubbo:Dubbo点这里
          • Dubbo是比较早期的阿里开源的一个分布式服务框架,一款微服务架构,可以使得应用通过高性能的 RPC 实现远程服务的输出和输入功能,并且可进行相关的服务治理,和Spring框架无缝集成,从功能上,Dubbo 可以对标 gRPC、Thrift 等典型的 RPC 框架。Dubbo优点是RPC长连接+NIO,性能更高;但协议的局限性,会限制生态发展和兼容性
            • Dubbo的应用特性:Dubbo 是一个可扩展性很强的组件,主要的特性如下
            • Dubbo 和 Spring Cloud 对比:
              • Dubbo 主要是从 RPC 服务调用的特性入手,而Spring Cloud 更多的是强调其在微服务方面提供的整体解决方案
              • Dubbo 更多关注远程服务调用功能特性,Spring Cloud 则包含了整体的解决方案,可以认为 Dubbo 支持的功能是 Spring Cloud 的子集。或者说 Dubbo 和 Spring Cloud 的目标不同,关注的是微服务实现的不同维度Dubbo 看重远程服务调用,Spring Cloud 则是作为一个微服务生态,覆盖了从服务调用,到服务治理的各个场景。
              • 功能对比:
                • 生产环境使用 Dubbo 组件实现服务调用,需要强依赖 ZooKeeper 注册中心;如果要实现服务治理的周边功能,比如配置中心、服务跟踪等,则需要集成其他组件的支持。
              • 对于 Spring Cloud,提供的功能更加多样,服务治理只是其中的一个方面,面向的是微服务整体的解决方案。
              • 调用方式:
                • Dubbo 使用 RPC 协议进行通讯,支持多种序列化方式,包括 Dubbo 协议、Hessian、Kryo 等,如果针对特定的业务场景,用户还可以扩展自定义协议实现
                • Spring Cloud 一般使用 HTTP 协议的 RESTful API 调用,RESTful 接口相比 RPC 更为灵活,服务提供方和调用方可以更好地解耦,不需要依赖额外的 jar 包等,更适合微服务的场景。从性能角度考虑,一般来说,会认为 PRC 方式的性能更高,但是如果对请求时延不是特别敏感的业务,是可以忽略这一点的
              • 服务发现:Dubbo 的服务发现通过注册中心实现,支持多种注册中心,另外本地测试支持 Multicast、Simple 等简单的服务发现方式。Spring Cloud 有各种服务发现组件,包括 Eureka、Consul、Nacos 等。前面提到过,ZooKeeper 实现的是 CAP 中的 CP 一致性,Spring Cloud 中的 Eureka 实现的是 AP 一致性,AP 更适合服务发现的场景
              • 开发成本:
                • 应用 Dubbo 需要一定的开发成本,自定义功能需要实现各种 Filter 来做定制,使用 Spring Cloud 就很少有这个问题,因为各种功能都有了对应的开源实现,应用起来更加简单。特别是,如果项目中已经应用了 Spring 框架、Spring Boot 等技术,可以更方便地集成 Spring Cloud,减少已有项目的迁移成本
        • 3.SpringCloud Alibaba,是 微服务系统架构的一站式解决方案(springcloud,是一套生态,就是用来解决以上分布式架构的4个问题的(springcloud是基于springboot的,想用springcloud之前就得把springboot学好,Spring Cloud 基于 Spring Boot,是一系列组件的集成,为微服务开发提供一个比较全面的解决方案,包括了服务发现功能、配置管理功能、API 网关、限流熔断组件、调用跟踪等一系列的对应实现。))Java基基老师的用 Spring Boot 创建微服务的替代方案的文章
          • SpringCloud:平时我们构建微服务的过程中需要做如 服务发现注册 、配置中心 、消息总线 、负载均衡 、断路器 、数据监控 等操作,而 Spring Cloud 为我们提供了一套简易的编程模型,使我们能 在 Spring Boot 的基础上轻松地实现微服务项目的构建【SpringCloud中每个服务都是一个单独的SpringBoot,】


            • Spring Cloud 的各个组件:【Spring Cloud 的核心组件如下,技术组件的完备性是 Spring Cloud 框架的主要优势】【整体流程大概就是:用户的url经过网关,由网关拦截住URL请求映射到服务调用端中的哪个Controller中(此时的Controller就算是咱们的客户端,而每个controller都可以部署在不同的节点上或者说服务器上),而controller也是很多controller服务,或者说web端的微服务是很多的,每一个微服务都是单独部署在一个机器上【对应不同的IP地址和port号】,而客户端每建立一个连接都会产生一个session,为了实现sessionid等信息的共享,搞了session共享数据信息。【此时做集群,因为所有的流量都经过网关,平衡一下负载,并且提高吞吐量】。做一个最简单的微服务,用户—>controller—>service(调用远程service像调用本地service一样,@Service,像不像RPC或者说Dubbo)


          • SpringCloud的服务发现框架:Eureka 服务发现框架:https://blog.csdn.net/m0_52436398/article/details/128347429
          • 负载均衡之 Ribbon、Nginx、Feign
          • Spring Cloud Bus:用于将服务和服务实例与分布式消息系统链接在一起的事件总线在集群中传播状态更改很有用(例如配置更改事件)。拥有了 Spring Cloud Bus 之后,我们只需要创建一个简单的请求,并且加上 @ResfreshScope 注解就能进行配置的动态修改了
            • Spring Cloud Bus 的作用就是 管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式。当然作为 消息总线 的 Spring Cloud Bus 可以做很多事而不仅仅是客户端的配置刷新功能。
          • Spring Cloud拥有更成熟的Spring社区生态,更多成熟的企业应用案例;但也存在一定不足,比如跨语言平台问题、微服务治理对代码侵入性较强。【Spring Cloud应用特性:Spring Cloud 目前主要的解决方案包括 Spring Cloud Netflix 系列,以及 Spring Cloud Config、Spring Cloud Consul 等。】
        • 4.服务网格,下一代微服务标准,Server Mesh,用一个istio
          • Istio
            • 作为新一代的微服务架构,Istio的微服务治理与开发更彻底解耦,适应场景更广泛,很多企业都正在逐步从Spring Cloud向 Service Mesh过渡;但也正是因为技术比较新,企业自研需要一定的学习成本,打破传统IT运维/开发壁垒,考虑引入专业的技术厂商则能够完美地解决这一问题
            • Istio的基本运行原理:

PART2:架构的发展:

  • 架构的概念定义涉及的东西有很多,Linux有架构,mysql有架构,JVM也有架构,使用Java开发、MySQL存储、跑在Linux上的业务系统也有架构,应该关注哪一个?想要清楚以上问题需要梳理几个有关系又相似的概念:系统与子系统、模块与组建【模块是逻辑单元,组件是物理单元。】、框架与架构:
    • 模块与组件
      • 模块就是从逻辑上将系统分解, 即分而治之, 将复杂问题简单化。模块的粒度可大可小, 可以是系统,几个子系统、某个服务,函数, 类,方法、 功能块等等。
      • 组件可以包括应用服务、数据库、网络、物理机、还可以包括MQ、容器、Nginx等技术组件。
    • 框架与架构:框架是组件实现的规范,例如:MVC、MVP、MVVM等,是提供基础功能的产品,例如开源框架:Ruby on Rails、Spring、Laravel、Django等,这是可以拿来直接使用或者在此基础上二次开发。框架是规范,架构是结构【软件架构指软件系统的顶层结构,架构分类可细分为业务架构、应用架构、技术架构, 代码架构, 部署架构芋道源码老师关于架构分类的文章,很赞 + 码农翻身关于架构师养成的文章
      • 架构是经过系统性地思考, 权衡利弊之后在现有资源约束下的最合理决策, 最终明确的系统骨架: 包括子系统, 模块, 组件. 以及他们之间协作关系, 约束规范, 指导原则.并由它来指导团队中的每个人思想层面上的一致。涉及四方面:就跟咱们最早学面向对象时,思考问题的步骤就分为三步:有哪些类哪些对象,类或者对象中哪些方法哪些成员属性等等、有没有继承等关系。
        • 系统性思考的合理决策:比如技术选型、解决方案等。也就是这么多技术我该用哪个和哪个呢
        • 明确的系统骨架:明确系统有哪些部分组成
        • 系统协作关系:各个组成部分如何协作来实现业务请求。
        • 约束规范和指导原则:保证系统有序,高效、稳定运行。
      • 咱们玩Java的,接触的最多的架构知识,最经典的解说当属Dubbo官网:里面详细解释了架构的演进。
        • Dubbo官网:https://dubbo.apache.org/zh/docs/v2.7/user/preface/background/
        • 再就是,经典之作,Martin Fowler关于微服务的原文翻译:http://martinfowler.com/articles/microservices.html
    • 玩Java的,最近距离的架构演进过程:
      • 单体架构:(单一应用架构(第一代架构))。A\\B\\C\\D在一个项目里面或者一个应用程序中或者说在一个服务器里面部署着

        • 优缺点也很明显
          • 优点:开发部署都很方便
          • 缺点:项目启动慢、可靠性很差、可伸缩性很差。第一代架构看似很简单,却支撑了平台的早期业务发展,满足了网站用户访问量在几万规模的处理需求但是当用户访问量呈现大规模增长,问题就暴露出来了
      • 垂直架构:其实就是开一个连锁店,每个店里面干的事情是一模一样的。垂直应用架构(第二代架构)。可以看到第二代架构是通过 水平扩展解决应用级别的扩展等问题,经过优化后,该架构支撑了几十万用户的访问需求,在这一阶段有部分应用已经使用java 完成了mvc架构的重写。当然也存在一些问题。

        • 垂直架构优缺点:
      • 分布式架构:分布式架构主要内容解说,很重要,自己阅读
        • 为了解决第一代与第二代架构存在的问题,需要对平台进行了梳理优化。根据平台业务需要以及对第一二代架构的总结,确定第三代架构的核心需求:
        • 集群一般和分布式是并存的
          • 集群:只是简简单单的横向扩展机器(跟搞连锁店一样)并且扩展出来的门店都是干一样的事情;而分布式需要先将业务进行拆分然后再横向扩展
          • 比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上【拆分不就形成分布式了嘛】,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上【这不就形成集群了嘛】。
        • 分布式或者说 SOA 分布式重要的就是面向服务,说简单的 分布式就是我们把整个系统拆分成【分布式系统的代码根据业务被拆分】不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能
          • 从咱们开发者的角度而言,分布式系统的代码根据业务被拆分后,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。此外,把整个系统拆分成不同的服务/系统,然后 每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢?因为处理速度肯定比之前融在一块快呀
        • SOA架构:Dubbo是SOA时代的产物,SpringCloud 是微服务时代的产物
      • 微服务架构:微服务是一起合作的独立小服务单元,可以同步异步调用,也可以独立拆分、独立部署、独立升级,后端中间件、存储资源、数据库等也是独立的,最佳实践是每个微服务都有自己的database,真正意义上实现微服务应用解耦
        • 该不该盲从呢,就像之前学DB一样,索引好是好,但是也不能滥用呀。评估一家企业或者一个项目是否需要采用微服务架构,往往考察这 五大关键条件
          • 数据量和业务复杂度
            • 如果单体程序已经没法管理了,那么你别无选择。如果没有管理上的问题,那么微服务带给你的只有问题和麻烦。其实,一般公司都没有太多选择,只能采用微服务,不过你可以选择建立比较少的微服务。如果还是没法决定,有一个折中的方案,“内部微服务设计”【如果你还是不能确定是否采用微服务架构,可以先从“内部微服务设计”开始,再逐渐拆分。】。来自SpringForAll老师的文章
          • 团队规模
          • 应对业务流量变化
          • 是否需求足够的容错容灾
          • 功能重复度和差错成本
        • 那如果我想要从单体改造到微服务的时候,应该怎样搞:
          • 芋道源码老师也有单体架构服务转型至分布式的上述过程的相关经验,看看老师们的不同见解呗
          • 步骤一:先确定业务领域,拆分存储,定义各微服务的边界
            • 在按照拆分步骤拆分之前,首先要明确咱们搞项目时,小项目拆包即可,大项目屎山,一般咱们拆分的思路有下面两种:
              • 面向需求,进行拆分,拆成了一堆服务 。比如上面的XXX高速项目中的话,拆为 数据管理【分为历史数据、实时数据、预测数据】、视频监控模块、预警处理等三个模块
              • 面向应用拆分,面向技术,比如上传图片、excel表导出、网络传输…
            • 代码库分开了,减少了麻烦的解决代码冲突的困扰【代码库是分开了,但每个服务都在独立迭代吗?是不是每个需求都要协调一大堆同步接口?】;CI/CD分开了,每个拆分后的服务都可以独立开发、部署、运行【CI/CD是分开了,但每次发布都是自由的吗?是不是每次功能的发布都拖上了一大推的服务要一起发布?】;数据库分开了,独立运行,不同业务模块不会互相影响【数据库是分开了,但似乎有个服务挂了,依然导致很多功能就都不正常了?
            • 内部微服务设计:这种设计表面上看起来是一个单体程序,它只有一个源代码存储仓库,一个数据库,一个部署,但在程序内部可以按照微服务的思想来进行设计。它可以分成多个模块,每个模块是一个微服务,可以由不同的团队管理
              • 这个所谓的“内部微服务设计”其实就是DDD,但当时还没有微服务,因此外表看起来是单体程序,但内部已经是微服务的设计了
              • 这样设计的好处是 它是一个单体程序,省去了多个微服务带来的部署、运维的麻烦。但它内部是按微服务设计的,如果以后要拆分成微服务会比较容易。至于什么时候拆分不是一个技术问题。【如果负责这个单体程序的各个团队之间不能在部署时间表,服务器优化等方面达成一致,那么就需要拆分了。】
              • 每个模块都有自己的数据库表,它们都在一个数据库中,但模块之间不能跨数据库访问(不要建立模块之间数据库表的外键)。虽然它们的数据库中的数据应该大致相同,但DDD建议每一个有界上下文中都建一个新表,它们之间再进行数据同步。
          • 步骤二:改造代码逻辑,将原来的内部service调用改成dubbo或feign这样的远程调用
          • 但是,上面这两个步骤下来,其实是有问题的。原本我们在单体应用中,未拆分的远程调用都是内部调用,这个内部调用所能引发的故障率是微乎其微的,而将这部分内容拆成了远程调用后,每一个调用都增加了网络IO的因素,每一次调用的故障率都增加了。那么系统的整体故障率是随着系统拥有多少同步远程调用的数量增加而增加的。当运维团队与开发水平没有没有支持好这部分增加的复杂度的时候,那么改造的系统,必然的稳定性会比原来的单体应用更差
          • 所以,改造的过程中得注意下面几点:
            • 微服务的数量不要太多,不然会有比较重的运维负担。或者说微服务的颗粒度不宜过细,否则工作量还是太大,可以先创建比较大的微服务(更像是服务组合)
              • 微服务的流行不是因为技术上的创新,而是为了满足管理上的需要。单体程序大了之后,各个模块的部署时间要求不同,对服务器的优化要求也不同,而且团队人数众多,很难协调管理。把程序拆分成微服务之后,每个团队负责几个服务,就容易管理了,而且每个团队也可以按照自己的节奏进行创新,但它给运维带来了巨大的麻烦。后来微服务建立了全套的自动化体系,从程序集成到部署,从全链路跟踪到日志,以及服务检测,服务发现和注册,这样才把微服务的工作量降了下来
                • 微服务的流行还是大大推动了容器技术,服务网格(Service Mesh)和全链路跟踪等新技术的发展。
                • 正常情况下,一个微服务可以有两、三张表到五、六张表,一般不超过十张表。但如果要减少微服务数量的话,可以把这个标准放宽到不要超过二十张表。
            • 领域拆分的不合理,引出了过多的同步远程调用【在做拆分的时候,尽可能的减少同步远程调用,取而代之的是走消息的异步交互,同时根据业务需要也可以做适当的数据冗余。这样就能保证,每个被拆分后的微服务之间可以获得更低耦合度。】
            • 简单粗暴的实现,缺少分布式的保护机制【很容易出现对远程调用不做足够的保护机制,比如:接口提供方的限流策略(保护自己不被别人搞死),接口调用方的降级策略(保护业务更高的可用性),接口调用方的熔断策略(保护自己不被别人拖死)。只有认真对待每一个分布式环境下的依赖点,那么才能解决因为分布式改造所牵连出的诸多问题。】
          • 微服务架构如何正确设计?
            • 企业进行微服务所需遵循的一大原则——康威定律:组织形式等同系统设计。设计系统的组织,其产生的设计等同于组织之内、组织之间的沟通结构。
  • 但是呢,说哪个架构好与坏肯定有一些衡量指标,哪怕你用生动的图片或者白话文啥的,指标总归是要有的,就是下面这些:咱们做项目,要达到的目标
    • 一个好理解的开饭店例子:

    • 可伸缩:通过硬件增减,提高降低处理能力
    • 高扩展性:系统间耦合低,方便的通过新增/移除方式,增加/减少新的功能/模块。
    • 安全性:提供网站安全访问和数据加密,安全存储等策略。
    • 敏捷性:随需应变,快速响应
    • 高性能:
      • 衡量网站的性能指标:
        • 响应时间:指执行一个请求从开始到最后收到响应数据所花费的总体时间。【响应时间就是 用户发出请求到用户收到系统处理结果所需要的时间。响应时间是系统最重要的性能指标,其直观地反映了系统的“快慢”。
          • 比较出名的 2-5-8 原则是这样描述的:通常来说,2到5秒,页面体验会比较好,5到8秒还可以接受,8秒以上基本就很难接受了。另外,据统计当网站慢一秒就会流失十分之一的客户。
        • 并发数: 指系统同时能处理的请求数量。这个数字反映了系统的负载特性。【并发数=QPS*平均响应时间】
          • 并发连接数:指的是客户端向服务器发起请求,建立了TCP连接。 每秒钟服务器连接的总TCP数量
          • 请求数:也称为QPS(Query Per Second)指每秒多少请求.
          • 并发用户数:单位时间内有多少用户
        • 吞吐量:指单位时间内系统能处理的请求数量。衡量吞吐量有几个重要的参数:QPS(TPS)、并发数、响应时间。
          • QPS: Query Per Second每秒查询数。服务器每秒可以执行的查询次数
            • 常用软件的QPS:通过了解这些软件的QPS可以更清楚地找出系统的瓶颈所在。
          • 经常听到的一些系统活跃度的名词:
            • PV(Page View):页面点击量或者浏览量,用户每次对网站中的每个页面访问均被记录一个PV,多次访问则会累计。
            • UV(Unique visitor):独立访客,统计一天内访问网站的用户数,一个用户多次访问网站算一个用户
            • IP(Internet Protocol):指一天内访问某站点的IP总数,以用户的IP地址作为统计的指标,相同IP多次访问某站点算一次
            • DAU(Daily Active User):日活跃用户数量。
            • MAU(monthly active users):月活跃用户人数。
          • TPS: Transactions Per Second 服务器每秒处理的事务数。(一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。)
    • 高可用:一个系统在大部分时间都是可用的【高可用代表系统即使在发生硬件故障或者系统升级的时候,服务仍然是可用的。】,可以为我们提供服务的。因为有了集群所以就可以东方不亮西方亮。这块出问题了那边可以顶上来,说明我的菜馆是不用歇业关门的。也就是实现了高可用
      • 一般情况下,我们使用多少个 9 来评判一个系统的可用性,比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的,这样的系统就是非常非常高可用的了!当然,也会有系统如果可用性不太好的话,可能连 9 都上不了。系统的可用性还可以用某功能的失败次数与总的请求次数之比来衡量,比如对网站请求 1000 次,其中有 10 次请求失败,那么可用性就是 99%
      • 哪些情况会导致系统不可用?
        • 黑客攻击;硬件故障,比如服务器坏掉;并发量/用户请求量激增导致整个服务宕掉或者部分服务不可用;代码中的坏味道导致内存泄漏或者其他问题导致程序挂掉;网站架构某个重要的角色比如 Nginx 或者数据库突然不可用;自然灾害或者人为破坏…
      • 冗余思想【其实就是做备份呗,对于数据来说,冗余的思想就是相同的数据备份多份,这样就可以很简单地提高数据的安全性。】在高可用系统设计中的应用:javaGuide老师关于冗余的文章
      • 有哪些提高系统可用性的方法?
        • 代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。比较实际可用的就是 CodeReview,不要在乎每天多花的那 1 个小时左右的时间,作用可大着呢!
        • 使用集群,减少单点故障【当我们使用一个 Redis 实例作为缓存的时候,这个 Redis 实例挂了之后,整个缓存服务可能就挂了。使用了集群之后,即使一台 Redis 实例挂了,不到一秒就会有另外一台 Redis 实例顶上。】
        • 限流:Hystrix:服务熔断、降级、限流,看这里https://blog.csdn.net/m0_52436398/article/details/128366137
        • 超时和重试机制设置:一旦用户请求超过某个时间的得不到响应,就抛出异常。这个是非常重要的,很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候,尤其适合设置超时和重试机制。一般我们使用一些 RPC 框架的时候,这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢,甚至导致请求堆积进而让系统无法再处理请求。重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)
          • 由于网络问题、系统或者服务内部的 Bug、服务器宕机、操作系统崩溃等问题的不确定性,我们的系统或者服务永远不可能保证时刻都是可用的状态。为了最大限度的减小系统或者服务出现故障之后带来的影响,我们需要用到的 超时(Timeout) 和 重试(Retry) 机制。【单体服务通常只涉及数据库、缓存、第三方 API、中间件等的网络调用,而微服务系统内部各个服务之间还存在着网络调用。】
          • 超时机制:当一个请求超过指定的时间(比如 1s)还没有被处理的话,这个请求就会直接被取消并抛出指定的异常或者错误(比如 504 Gateway Timeout)。
            • 超时时间如何设置:设置太长没啥意义,太短大量请求重试继续加重系统或者服务的压力,进而导致整个系统或者服务被拖垮的问题。通常情况下,我们建议读取超时设置为 1500ms ,这是一个比较普适的值。如果你的系统或者服务对于延迟比较敏感的话,那读取超时值可以适当在 1500ms 的基础上进行缩短。反之,读取超时值也可以在 1500ms 的基础上进行加长,不过,尽量还是不要超过 1500ms 。连接超时可以适当设置长一些,建议在 1000ms ~ 5000ms 之内。【可以将超时弄成可配置化的参数而不是固定的,比较简单的一种办法就是将超时的值放在配置中心中。这样的话,我们就可以根据系统或者服务的状态动态调整超时值了。】
          • 重试机制:重试机制一般配合超时机制一起使用,指的是多次发送相同的请求来避免瞬态故障和偶然性故障
        • 熔断机制:
        • 异步调用:异步调用的话我们不需要关心最后的结果,这样我们就可以用户请求完成之后就立即返回结果,具体处理我们可以后续再做,秒杀场景用这个还是蛮多的。但是,使用异步之后我们可能需要 适当修改业务流程进行配合,比如用户在提交订单之后,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功。除了可以在程序中实现异步之外,我们常常还使用消息队列,消息队列【javaGuide老师关于消息队列de的文章,很赞】可以通过异步处理提高系统性能(削峰、减少响应所需时间)并且可以降低系统耦合性。
        • 使用缓存:单纯使用数据库的话,当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据,因为缓存存储在内存中,所以速度相当地快
        • 注意备份,必要时候回滚。
  • 追随云原生脚步的 Java【参考“juejin.cn/post/
    7116304688630202382”、芋道源码老师、周志明博士老师的视频】
    • what is 云原生(CloudNative):云原生就是在云中构建、运行应用程序的一套完整的技术体系和方法论【技术体系和方法论就目前来说指的是 微服务+DevOps+持续交付+容器化】
      • 从这个单词看:可以分为两部分Cloud+Native:
        • Cloud 可以理解为 应用程序部署在云中
        • Native 可以理解为应用程序从设计之初就是原生为云而设计的,需要充分利用和发挥云平台的弹性+分布式优势,提升云上资源利用率、缩短开发周期
      • CNCF(Cloud Native Computing Foundation,云原生计算基金会)是云原生领域的权威组织,其对云原生提供了官方定义:
      • 腾讯云副总裁黄俊洪大佬在Techo Day(腾讯技术开放日)的直播中对云原生的定义:
        • 上云跟云原生确实是不能划等号的上云只是简单地把基础设施能够搬到云上,而云原生是上云的更深层面。云原生它需要借助的是云的 弹性伸缩的能力 ,还有 按量付费 的这种模式,去实现云上的开发、运维、测试、部署等生命周期,只有充分享受到云计算红利的这种模式,我觉得才是叫是真正的云原生
          • 云计算是一种按需按使用量付费的模式,其实就是 按照你的需要给你提供不同粒度的服务【服务模式是分层模型】,你用的话付费就行【这种模式提供了可用的便捷的按需的网络访问、资源共享池(池子中有网络、服务器、存储、应用、服务)】、提供了 以租代买 的商业模式
            • 服务分层模

              Java基础巩固——反射

              什么是反射


                 反射机制就是指程序运行时能够获取自身的信息。在Java中,只要给出类的名字,就可以通过反射机制来获取类的信息

              哪里用的到反射机制


                 在jdbc中就是使用的反射来实例化对象,比如:Class.forName("com.mysql.jdbc.Driver.class").newInstance();

                 框架都用到反射机制,spring,hibernate、struts都是用反射机制实现的。

              反射机制的优点和缺点


                为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念,
                  静态编译:在编译时确定类型,绑定对象,即通过。
                  动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多
                  态的应用,有以降低类之间的藕合性。
                  一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中.

                它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功
                  能。
                     它的缺点是对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它
                  满足我们的要求。这类操作总是慢于只直接执行相同的操作。

              利用反射机制能干什么


              Class c=Class.forName("className");注明:className必须为全名,也就是得包含包名,比如,cn.netjava.pojo.UserInfo;
              Object obj=c.newInstance();//创建对象的实例

              获取构造器

                Constructor getConstructor(Class[] params)//根据指定参数获得public构造器

                Constructor[] getConstructors()//获得public的所有构造器

                Constructor getDeclaredConstructor(Class[] params)//根据指定参数获得public和非public的构造器

                Constructor[] getDeclaredConstructors()//获得public的所有构造器

              获取类的方法

                Method getMethod(String name, Class[] params),根据方法名,参数类型获得方法

                  Method[] getMethods()//获得所有的public方法

                  Method getDeclaredMethod(String name, Class[] params)//根据方法名和参数类型,获得public和非public的方法

                  Method[] getDeclaredMethods()//获得所以的public和非public方法

              获取类的属性

                Field getField(String name)//根据变量名得到相应的public变量

                    Field[] getFields()//获得类中所以public的方法

                    Field getDeclaredField(String name)//根据方法名获得public和非public变量

                    Field[] getDeclaredFields()//获得类中所有的public和非public方法

              使用实例


               获取类属性:

              public class TestGetField extends Object {
                  private static final long serialVersionUID = -2862585049955236662L;
              
                  public static void main(String args[]) throws Exception {
                      Class<?> clazz = Class.forName("reflect.TestGetField");
                      System.out.println("===============本类属性===============");
                      // 取得本类属性
                      Field[] fields = clazz.getDeclaredFields();
                      getField(fields);
              
              
                      System.out.println("==========实现的接口或者父类的属性==========");
                      // 取得实现的接口或者父类的属性
                      Field[] fatherField = clazz.getFields();
                      getField(fatherField);
                  }
              
                  public static void getField(Field[] fields) {
                      for (Field field : fields) {
                          // 权限修饰符
                          int mo = field.getModifiers();
                          String priv = Modifier.toString(mo);
                          Class<?> type = field.getType();
                          System.out.println(priv + " " + type.getName() + " " + field.getName() + ";");
                      }
                  }
              }

               

               获取类的方法:

              public class TestGetMethod implements Serializable{
                  private static final String  testString= "hello";
                  public static void main(String args[]) throws Exception{
                      Class<?> clazz = Class.forName("reflect.TestGetMethod");
                      Method[] methods = clazz.getMethods();
                      for (Method method :methods){
                          Class<?> returnType = method.getReturnType();
                          Class<?> para[] = method.getParameterTypes();
                          int temp = method.getModifiers();
                          System.out.print(Modifier.toString(temp));
                          System.out.print(returnType.getName());
                          System.out.print(method.getName());
              
                          for (Class par:para){
                              System.out.println(par.getName());
                          }
                          
                      }
                  }
              }

               

               实例化类:

              public class TestNewInstance {
                  public static void main(String[] args) throws Exception{
                      Class<?> class1 = null;
                      class1 = Class.forName("reflect.User");
                      // 第一种方法,实例化默认构造方法,调用set赋值
                      User user = (User)class1.newInstance();
                      user.setAge(20);
                      user.setName("adf");
                      System.out.println(user);
                      // 第二种 取得全部的构造函数 使用构造函数赋值
              
                  }
              }
              
              class User {
                  private int age;
                  private String name;
                  public User() {
                      super();
                  }
                  public User(String name) {
                      super();
                      this.name = name;
                  }
                  public User(int age, String name) {
                      super();
                      this.age = age;
                      this.name = name;
                  }
                  public int getAge() {
                      return age;
                  }
                  public void setAge(int age) {
                      this.age = age;
                  }
                  public String getName() {
                      return name;
                  }
                  public void setName(String name) {
                      this.name = name;
                  }
                  @Override
                  public String toString() {
                      return "User [age=" + age + ", name=" + name + "]";
                  }
              }

               

              使用类的方法:

              public class TestUseMethod {
                  public static void main(String[] args)throws Exception{
                      Class<?> clazz = Class.forName("reflect.TestUseMethod");
                      // 调用reflect1方法
                      Method method = clazz.getMethod("reflect1");
                      method.invoke(clazz.newInstance());
              
                      // 调用reflect2方法
                      method = clazz.getMethod("reflect2", int.class, String.class);
                      method.invoke(clazz.newInstance(),20,"test");
                  }
              
                  public void reflect1() {
                      System.out.println("Java 反射机制 - 调用某个类的方法1.");
                  }
                  public void reflect2(int age, String name) {
                      System.out.println("Java 反射机制 - 调用某个类的方法2.");
                      System.out.println("age -> " + age + ". name -> " + name);
                  }
              }

               

              动态代理:

              public class TestProxy {
                  public static void main(String args[]) throws Exception{
                      MyInvocationHandler demo = new MyInvocationHandler();
                      Subject subject = (Subject) demo.bind(new RealSubject());
                      System.out.println(subject.say("janti",20));
                  }
              }
              
              interface Subject{
                  public String say(String name, int age);
              }
              
              // 定义真实项目
              class RealSubject implements Subject {
                  public String say(String name, int age) {
                      return name + "  " + age;
                  }
              }
              
              //如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,已完成代理的具体操作。
              
              class MyInvocationHandler implements InvocationHandler{
              
                  private Object object = null;
                  public Object bind(Object obj){
                      this.object = obj;
                      return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
                  }
              
                  @Override
                  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                      Object temp = method.invoke(this.object,args);
                      return temp;
                  }
              }

               









              以上是关于java基础巩固-宇宙第一AiYWM:为了维持生计,架构知识+分布式微服务+高并发高可用高性能知识序幕就此拉开(一:总览篇)~整起的主要内容,如果未能解决你的问题,请参考以下文章

              Java基础巩固

              负载均衡(纯手写,极度适合巩固基础面试突击)

              数据库(纯手写,极度适合巩固基础面试突击)

              python基础巩固第一篇

              java基础巩固笔记-注解

              中国工程院院士:分布式数据存储成为维持元宇宙持久运转的基本方式