- 微服务架构

>> 微服务架构基础

~ 微服务概念

微服务架构是一种架构概念,旨在通过将功能分解到各个离散的服务中,以实现对解决方案的解耦;它的主要作用是将功能分解离散到各个服务中,从而降低系统的耦合性,并添加更灵活的服务支持;

把一个大型的单体应用和服务拆分成数个微服务;

~ 微服务架构与传统架构的区别

1、系统架构需要遵循3个标准:

  • 提高敏捷性:及时响应业务需求;
  • 提升用户体验:减少用户流失;
  • 降低成本:降低增加产品、客户、业务方案的成本;

2、传统式开发(单体式开发):将所有的功能打包在一个war包里,基本没有外部依赖(除了容器),部署在一个JavaEE容器(Tomcat、JBoss、Weblogic)里面,包含MVC的所有逻辑;

优点

  • 开发简单,集中式管理;
  • 基本不会重复开发;
  • 功能都在本地(整个应用程序部署在一台服务器),没有分布式的管理和调用的消耗;

缺点

  • 效率低:开发都在同一个项目里改代码,相互等待,冲突不断;
  • 维护难:代码功能耦合在一起;
  • 不灵活:构建时间长,任何小修改都要重构整个项目,耗时间;
  • 稳定性差:一个微小的问题都可能导致整个应用挂掉;
  • 扩展性不够:无法满足高并发下的业务需求;

3、微服务架构:有效的拆分应用,将应用拆分成多个微服务,实现敏捷开发和部署;

微服务特征:

  • 一系列的独立的服务共同组成系统;
  • 单独部署;
  • 每个服务为独立的业务开发;
  • 分布式管理;
  • 非常强调隔离性;

>> 微服务实战 - 怎么实现微服务

要实际的应用微服务,需要解决一下问题:

  • 客户端如何访问这些服务;
  • 每个服务之间如何通信;
  • 如此多的服务,怎么实现;
  • 服务挂了,怎么解决;(备份方案,应急处理机制)

在这里插入图片描述

1、客户端如何访问这些服务

通过API网关的模式来访问

传统的单体开发,所有的服务都是在本地的,UI可以直接调用;现在按照功能拆分成独立的服务,每个服务都跑在独立的虚拟机上的Java进程里;UI要怎么去访问服务?

一个服务,一个应用,一个容器;每个服务都是一个独立的计算机,这就表示每个计算机都有一个独立的IP和端口;

后台有N个服务,前台就要去记住管理N个服务;用户去访问UI,UI去管理这么多服务,怎么管理是个问题;一个服务下线、更新、升级,前台就要重新部署;这不符合拆分的理念,特别是前台是移动应用的时候:PC访问的话,后台服务变了,把JSP页面改改,重启服务,重新部署一下,用户再次访问PC的时候,就自动变了;移动应用访问的话,后台服务变了,就得下载新的安装包,麻烦;

一般微服务在系统的内部,通常是无状态的,也就是说各个微服务之间的互相调用是无状态的,不知道是谁在调用;用户登录信息和权限管理最好有一个统一的地方维护管理(OAuth),这样就不用在每个服务都登录一次;当用户去访问UI的时候,都应该去访问OAuth服务器(授权服务器),OAuth再去访问服务;这样就由OAuth服务器统一管理后面微服务的授权(用户的登录状态);

无状态:Http的请求时无状态的,不会记录是谁来请求的;web应用为了实现有状态的效果,才有了会话,用session来记录状态,而http请求本身是无状态的;

一般在后台N个服务和UI之间会有一个API Gateway,由网关实现OAuth的功能,它的作用包括:

  • 提供统一的服务入口,让服务对前台透明;
  • 聚合后台的服务,节省流量,提升性能;
  • 提供安全,过滤,流量控制等API管理功能;

既然微服务内部是无状态的,我们就需要有一个东西让它有状态,这个东西就是授权服务器,这个东西又称为SSO(单点登录);

单点登录:单点就是一个点(一个地方),在一个地方登录了,就相当于在所有地方登录了;

用户访问UI,UI访问API网关,API网关也是一个服务,这个服务去聚合后面的微服务;API网关又相当于后面服务的防火墙,因为API一个服务,把后面整个隔离了,所有安全的东西都可以在API网关这里来做;

在这里插入图片描述
不使用API网关的时候,UI的PC端需要记录所有后台服务的IP地址和端口,移动应用也需要记录一份IP地址,这时候如果后台有一个服务的IP改了一下,PC端在后台改一下代码配置就可以了,但是移动应用就需要更新一个整个的安装包,就为了后台服务改了一个IP,不现实;
在这里插入图片描述
有了API网关,解决了这个问题;UI的PC和移动应用上只需要记录一个API网关的地址,后台服务的IP地址由API网关来管理,只要API网关的地址不变,后台服务的IP随便怎么改,怎么增加,怎么减少都无所谓,因为只需要修改API网关管理的服务IP列表即可;这时候安卓端的每次后台修改就要更新整个安装包的尴尬问题就没有了;

用户访问某个服务的时候,只需要通过UI去访问API网关,然后由网关去调用不同的服务;


API网关有很多实现办法,可以是一个软硬一体的盒子,可以是一个简单的MVC框架,可以是一个Node.js的服务端;它最重要的作用是为UI提供后台服务的聚合,提供统一的服务出口,解除它们之间的耦合;但是API网关可能成为单点故障,或性能的瓶颈;

单点故障:网关挂掉了,所有后台服务都不能访问了,即使后台服务都能正常运行;
性能瓶颈:API网关只有一个服务器,所有流量都要经由这个网关服务器,然后由它分发到后台服务上面去;

2、每个服务之间如何通信

所有的微服务都是独立的Java进程运行在独立的虚拟机/容器上,所以服务间的通信就是IPC(Inter Process Communication 进程间通信);

解决服务间通信,有两种方案:

(1)同步调用:

  • REST(Spring Boot、SpringMVC):就是Http通信;
  • RPC(Thrift,Dubbo):远程过程调用;使用的时候就和调用本地应用一样;

对外REST,对内RPC;
对内RPC因为RPC在内网中调用的速度非常快;而对外REST是因为有防火墙,防火墙只能接收字符串,REST提供的是json格式数据,这个格式是由字符串组成的;网络中只有字符串可以穿透防火墙,RPC是远程过程调用,是穿透不了防火墙的;

同步调用比较简单,一致性强,但是容易出现调用问题: 同步调用会出现阻塞,出现阻塞就会出现单点故障; 性能体验也会差一些,特别是调用层次多的时候;

一般REST基于HTTP,更容易实现,服务端的实现技术也更灵活些,各种语言都支持,同时也能跨客户端,对客户端没有特殊的要求,只要支持HTTP请求,就能拿到字符串,拿到字符串就能在自己的程序里想怎么样就怎么样;

(2)异步消息调用:

  • kafka
  • Notify
  • MessageQueue

在这里插入图片描述

异步消息的方式在分布式系统中有特别广泛的应用,它既能降低调用服务之间的耦合,又能成为调用之间的缓冲,确保消息积压不会冲垮被调用方,同时能保证调用方的服务体验,继续干自己该干的活,不至于被后台性能拖慢;不过需要付出的代价是一致性的减弱,需要接受数据 最终一致性;还有就是后台服务一般要实现 幂等性,因为消息送出于性能的考虑一般会有重复(保证消息的被收到且仅收到一次对性能是很大的考验);最后就是必须引入一个独立的 Broker;

  • 调用服务之间的耦合:用户服务与产品服务之间存在调用,若RPC的方式,用户服务就得依赖产品服务,产品服务在线,用户服务才能上线;产品服务不在线,用户服务无法启动,因为用户服务无法远程调用到产品服务,相当于没有添加需要的依赖;这就形成了耦合度;将RPC换成Broker能降低耦合;

  • Broker是一个服务器,它能成为用户服务和产品服务之间的缓冲,用户服务调用产品服务会传递消息过来,Broker是一个服务器,形成一个缓冲的效果,这样就不会因为用户服务而导致产品服务挂掉;

  • 用户服务调用产品服务的时候,会有大量的请求过来,这就已经在拖产品服务的速度、内存消耗、CPU等;以此同时用户也在通过UI调用网关,在单独的独立的请求产品服务,这就分成了两个业务线在调用产品服务:别的用户在调用产品服务,用户服务在调用产品服务;这时有Broker服务器缓冲一些后台服务的调用,能降低一些压力;

  • 一致性的减弱:比如用户通过UI调用API网关下了一个订单,这个时候是用HTTP请求,是同步的,下完订单马上就有反馈;这个时候如果是异步,有一个缓冲,下完订单,可能不会立刻处理,这样也就不会立刻得到反馈;这就是一致性的减弱;

  • 最终一致性:不是实时反馈,但是最终的结果是正确的就可以了;

  • 幂等性:无论多少次请求,返回的结果都是一样的;

  • 独立的Broker:称之为消息队列的中间服务器;

消息队列:就是一个设计模式 - 生产者消费者模式:生产者只管生产,消费者只管消费;

消息队列分两种:

  • 有Broker的:
    Broker是对消息的持久化;
    有Broker就说明有个服务器 ,做两个服务之间的中间缓冲;
    有Broker是为了做消息的持久化,

  • 无Broker的:
    无Broker的就是两个服务之间直连;
    无Broker就是在对消息的完整型要求不高的情况下使用,kafka是代表;
    kafka:全球最快消息队列;一般拿来做日志;

在这里插入图片描述

比如创建一个订单,由生产者进行生产,然后传递给消费者;生产者这个服务只管生产,而不管消费者服务怎么处理、什么时候处理这个订单,只是一直生产一直发,这就会导致消费者服务被拖垮;所以生产者和消费者这个两个服务之间要有一个称为Broker的缓冲地带;

生产者不管消费者什么时候消费、怎么消费,消费者可能不能处理那么大的信息,这就会形成消息积压;消费者发送的请求都发送都Broker,Broker先存着,然后由Broker一条一条发送给消费者,消费者性能跟不上了,Broker就会等待,等消费者能处理了再发送请求给消费者;

生产者往消费者发消息,若消费者挂掉了,而生产者还在一直发,若没有缓冲地带,这些消息就会消失,影响一致性;中间有一个Broker,对消息做了持久化,当消费者下线以后,再次上线,消息还在Broker中;

若消息很重要,不能丢失,就不可以在两个服务之间直连,必须要有Broker来做消息的持久化;但有些消息可以丢失,要的是异步、速度,对数据的完整性(一致性)不考虑,比如日志传输,日志不需要完整,丢几条也没有问题;这时候就可以使用kafka;

3、如此多的服务,如何实现

在这里插入图片描述

在微服务架构中,一般每个服务都有多个拷贝,来做负载均衡;一个服务随时可能下线,也可能应对临时访问压力增加新的服务节点;

一个容器一个服务,一个容器是一个节点;一个服务有N个节点的时候,用户请求的时候,每个节点都有可能去,多个用户请求这个服务,就把请求的压力分散开了,这就叫负载均衡;一个服务下线了,还有其他服务节点继续提供服务,这个效果就称为高可用

服务之间如何相互感知?服务如何管理?这就是服务发现的问题了;

服务发现:API网关怎么知道服务的IP和端口?这些服务下线了怎么办?新增了一个服务节点06,网关怎么知道06节点的存在?
在这里插入图片描述

服务发现一般有2种做法,基本都是通过Zookeeper等类似技术做服务注册信息的分布式管理;当服务上线时,服务提供者将自己的服务信息注册到ZK(或类似框架),并通过心跳维持长链接(Tcp长链接),实时更新链接信息;调用者通过ZK寻址,根据可指定算法,找到一个服务,还可以将一个服务信息缓存在本地以提高性能;当服务下线时,ZK会通知给服务客户端;

Zookeeper是一个框架,主要用来做 服务的注册与发现;

(1)基于客户端的服务注册与发现:

  • 优点是架构简单,扩展灵活,只对服务注册器依赖;
  • 缺点是客户端要维护所有调用服务的地址,有技术难度,一般大公司都有成熟的内部框架支持,比如 Dubbo;

Dubbo是RPC远程调用框架,Zookeeper是服务注册与发现框架,两者结合使用完成微服务的实现;

在这里插入图片描述
客户端服务自己注册到服务注册中心(ZK)去,ZK维护这个IP列表,订单服务要调用产品服务,就去注册中心查找产品服务的IP、端口、服务名称;
在这里插入图片描述
客户端服务器启动的时候,需要把自己的IP、端口、服务名称告诉 服务注册与发现 服务器,API网关想要调用什么服务,就告诉服务注册中心服务名称,注册中心反馈回一个对应的服务的IP与端口,网关就能直接调用这个服务了;

原来是由网关管理后台服务的IP列表,现在由注册中心来管理,服务器的信息,由服务自己注册到服务中心;


(2)基于服务端的服务注册与发现:
优点是简单,所有服务对于前台调用方透明,一般在小公司在云服务上部署的应用采用的比较多;

在这里插入图片描述
由服务调用者去调用负载均衡服务器,负载均衡服务器去调用注册中心,再去调用对应的服务;比基于客户端的方式多个一个LB服务器;
在这里插入图片描述
UI调用负载均衡服务器,再由LB调用API网关,因为网关也需要开辟多个节点,做负载均衡,以避免单点故障的出现,从而实现高可用;服务注册中心也要做负载均衡;

所有的服务都要经过注册服务注册中心,有服务注册中心统一管理IP、端口、服务名称;

4、服务挂了怎么办

分布式最大的特性就是网络是不可靠的:ping的时候偶尔会有丢包;

解决办法:

  • 重试机制:一次没请求成功,再请求一次; ZK去请求后台服务的时候,由于网络原因,没有连接上服务,那就在超时之后再试一次;

  • 限流:同时有一万个并发过来请求访问服务器2,压力很大,因为是同步请求,就会阻塞,阻塞就可能挂掉,就会出现单点故障;这时候就可以在客户端调用ZK那里进行限流,让一部分请求停止在客户端那里,不会发送到后台服务器上;

  • 熔断机制:客户端的请求全部通过ZK发送到了后台,流量一上来,就开启熔断机制,在真正到达处理请求的服务器之前被阻断,阻止请求发送到处理服务器;同时返回客户端服务无法响应的提示;

  • 负载均衡

  • 降级(本地缓存):把服务下线,以保障系统最基本的功能能使用,表面看整个系统依然是高可用的;
    当大量请求发送到后台的时候,没有限流也没有启动熔断机制,因为确实需要提供服务,但是因为流量过大,计算机承载不了这些流量以后,就要停止部分服务,以保障数据一致性问题;比如:订单服务,订单服务后面又要去访问其他服务,这时如订单服务承载不了那么大的压力了,一旦再往下传递请求,就可能出现数据不一致了,这时候若没有更好的解决方案,那就停掉订单服务器,让整个订单服务下线,不要再产生新的订单了;但是网站的其他服务还可以继续使用;

    在这里插入图片描述

微服务实现总结:

单体应用拆分成多个独立的服务,每一个服务是一个应用程序,使用Docker的容器化部署将所有这些服务进行隔离;

服务与服务之间有一个通信问题要解决,有两种方式:同步请求方式和异步请求方式;同步请求方式有2种方式:REST和RPC方式;异步请求方式只有一种方式:消息队列方式;

这个时候因为由一个API Gateway来统一的存储所有服务的IP与端口,对维护起来就增加了难度,于是就要使用另一个机制:服务注册与发现机制;由统一的调度中心(API网关)去请求服务注册与发现机制 ,去获取所需要的服务的对应的IP和端口;由服务注册中心来保障下面服务的高可用与一致性;

服务挂了之后可以使用”重试机制“、”限流“、”熔断机制“、”负载均衡“、”服务降级“等方式以保障整个服务还处于高可用状态;


>> 单点故障与分布式锁

  • 单点故障:通俗讲就是 由一个服务阻塞或挂机了,导致后面的服务都不可以使用了,这时候就称为单点故障;

解决单点故障:使用分布式锁;

Zookeeper:是一个服务注册与发现的框架,同时也是一个分布式协调技术; 最厉害的地方是它实现了分布式锁的问题;

  • 分布式锁:为了防止分布式系统中多个进程之间相互干扰,就需要一种分布式协调技术,来对这些进程进行协调,而分布式协调的核心就是来实现分布式锁;ZK就是这样的一个实现了分布式锁的分布式协调技术;

  • 分布式系统中的单点故障:
    在分布式锁服务中,有一种典型的应用场景,就是通过对集群进行master选举,来解决分布式系统中的单点故障问题;通常分布式系统采用主从模式,就是一个主机控制多个处理节点,主节点负责分发任务,从节点负责处理任务,当主节点发生故障时,任务没人分发,那么整个系统就都挂掉了;这种故障就叫做单点故障;

在这里插入图片描述

~ 单点故障传统解决方案:

在这里插入图片描述
采用一个备用节点,这个备用节点定期给主节点发送ping包,主节点收到ping包以后向备用节点发送回复Ack,备用节点收到回复的时候,就会认为当前主节点还活着,让它继续提供服务;

当主节点挂了,这个时候备用节点就收不到回复了,然后它就认为主节点挂了,就接替它成为主节点;

但是这种方式存在一个隐患,就是网络问题;主节点没有挂掉,但是由于网络震荡问题,导致备用节点向主节点发送ping包之后,主节点没有收到包,或是收到了,但是在返回Ack字节码的时候,网络突然丢了一个包,将主节点回应给备用节点的Ack包丢了,这就一下,备用节点没收到回复,就认为主节点挂了,然后备用节点就将它的Master实例启动起来,这样分布式系统中就出现了两个主节点 - 双Master(双主问题);

出现Master以后,从节点就会将它所做的事一部分汇报给了主节点,一部分汇报给了从节点,这样服务就乱套了:当发送插入记录的请求的时候,2个主节点都会收到这个请求,机会调用2此从节点处理这个请求,这样就会插入两次数据;

为了防止双主问题,就要使用ZK加入分布式锁的概念,锁住主节点的资源;

~ Zookeeper 解决方案

(1)Master启动:
在引入ZK以后,启动两个主节点A和B,主节点-A主节点-B启动以后,都向ZK去注册一个节点,注册后就会有一个编号;假设主节点-A 锁注册的节点是master-00001主节点-B 锁注册的节点是master-00002,注册以后进行选举,编号最小的节点将会在选举中获得锁称为主节点,这里主节点-A获胜;然后主节点-B将会被阻塞成为一个备用节点;通过这种方式就完成了对两个Master进程的调度;

在我们通过UI发送请求的时候,回去请求ZK,由ZK去请求需要的主节点进行消息的分发;因为是分布式系统,会有多个主节点的实例,谁来当主节点,由ZK自己来选举;
在这里插入图片描述

(2)Master故障:
在这里插入图片描述
如果”主节点-A“挂了,这时候它在ZK中注册的节点就会自动删除,ZK会自动感知节点的变化,然后再次发出选举,这时候主节点-B获胜,替代A称为新的主节点;

ZK维护主节点们,也会向主节点们发送ping包,当ZK向主节点-A发送Ping包,没有回复之后,就会通知主节点-B重新选举;如果主节点-A是因为网络震荡无法回复ZK的话,ZK就会将它从 服务注册与发现 列表中直接删除;当A重新恢复上线以后,就会重新注册到ZK中,这时候就会变成master-00003,而不是原来的00001;这时候ZK会感知节点的变化再次发动选举,这时候主节点-B会再次获胜继续担任主节点,节点A就会阻塞称为备用节点,等待下次选举;这样就不会出现之前的双主e问题了;

~ 总结 - 面试:什么是ZK

什么是Zookeeper:服务注册与发现中心;
ZK解决了什么问题:分布式锁的问题;

先解释单点故障:分布式系统才采用主从模式,就是一个主服务器调用两个从服务器的资源;当主服务器挂掉了,从服务器还在继续提供服务,但是由于主服务器挂掉了,两个从服务器访问不到了,这就叫做单点故障;

传统的解决单点故障的方式是有两个节点:主节点和备用节点,备用节点会一直ping主节点,若没有收到主节点的回复,就认为主节点挂掉了,备用节点就上线代替原来的主节点称为新的主节点提供服务;但是若之前的主节点没有挂掉,而是由于网络震荡问题没有及时回复备用节点,使得备用节点误以为主节点挂掉了,这就出现了双主问题;一旦出现双主问题,所有的请求都会出现多次、重复,数据就会出现问题;

为了解决这个问题,就引入了分布式锁的问题,而ZK这个框架就是解决分布式锁的问题的;

ZK要求 所有服务启动的时候都要想ZK进行注册,这时候ZK就维护了一个节点列表;ZK会发发动选举,决定谁会成为主节点,选举出一个主节点,剩下的节点就会阻塞称为备用节点;ZK会一直向主节点发送ping包,如没有收到主节点的回复,就会从它维护的节点列表中删掉,而不是停掉,然后通知所有备用节点进行选举,选出新的主节点继续提供服务;若出现刚才相同的网络震荡的导致主节点无法回复ZK的时候,主节点重新上线,就会重新到ZK去注册,这个时候就会注册成新的节点,变成备用节点,等待下次选举;


~ 为什么要使用分布式锁

在这里插入图片描述

三个并发同时去访问负载均衡服务器,负载均衡服务器会去调度后台服务;后台有3个服务,3个服务提供的是3个相同的应用程序;

部署了3套应用程序,就变成了有3个独立的JVM进程运行在3台不同的服务器上;但是这里面我们可能要调用相同的变量A(因为系统是一样的,所以变量是一致的);

3个变量A在3个JVM内存中,变量A同时都会在JVM分配内存,3个请求发送过来同时对这个变量进行操作,显然结果是不对的;

不是同时发送过来,三个请求分别操作3个不同JVM内存区域的数据,变量A之间不存在共享,也不具可见性,处理的结果也是不对的;

这个变量A主要体现是在一个类中的一个成员变量,是一个有状态的对象;

如果业务中确实存在这个场景,就需要一种方法解决这个问题;

为了保证一个方法或属性在高并发情况下的同一时间只能被一个线程执行,在单机环境中,Java提供了很多并发处理相关的API:同步锁;

由于分布式系统多线程、多进程,并且分布在不同机器上,这使得原单机部署情况下的并发控制锁策略生效;

这就需要一种跨JVM的互斥机制来控制共享资源的访问;分布式锁就是解决这个问题的;

Zookeeper就能做到跨进程协作;

分布式锁应该具备的条件:

  • 在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
  • 高可用的获取锁和释放锁:获取锁、释放锁的服务器(ZK)本身也得高可用;
  • 高性能的获取锁和释放锁:获取/释放锁的操作要快;
  • 具备可重入性:可理解为重新进入(重新触发这个事情的时候),由多于一个任务并发使用,而不必担心数据错误;
  • 具备锁失效机制,防止死锁;
  • 具备非阻塞锁特性,就是没有获取到锁将直接返回获取锁失败;类似熔断机制,拿不到锁直接返回结果,不能让程序阻塞在这个地方;

在这里插入图片描述
在这里插入图片描述


>> 微服务架构设计模式

https://www.funtl.com/zh/micro-service-about/再谈微服务-微服务架构设计模式.html#微服务架构需要考虑的问题

文章整理自此博客视频教程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值