2024-04-07  阅读(2)
原文作者:Brand 原文地址: https://www.cnblogs.com/wzh2010/p/15540895.html

1 概述

回顾下前面几篇关于微服务的介绍,我们可以了解到从当单体系统到微服务,再到服务网格的演进过程。那单体系统和微服务相比,有哪些区别呢,下面是对功能性的对比?

 单体系统   微服务系统 
 单体系统   微服务系统 
程序、数据、配置集中管理 按照功能拆分、微服务化、松耦合
开发效率低下 分模块快速迭代
发布全量,启动慢 平滑发布,快速启动
可靠性差 熔断、限流、降级,超时重试,异常离群
服务内直接调用 轻量级通信
技术单一 跨语言

微服务有诸多的有利条件,但是如果微服务的粒度比较细(按照业务功能拆分),则他们之间服务调用就会比较复杂,链路会比较长。

202404072035225511.png

比如上图中,我们按照职能将服务进行了拆分,这时候从不同的客户端(如Web、App、3rd)访问,就有可能访问不同的服务。而服务与服务之间又有上下游的协作,调用就变得错综复杂。

因此,在微服务架构体系下,服务间的通讯就显得非常重要。

你可能需要关注很多问题,包括不同的技术栈不同的开发语言之间的上下游交互,服务之间的注册与发现,请求认证,接入授权。

下游对上游进行调用的时候,上游怎么做负载均衡、故障注入、超时重复、熔断、降级、限流、ABTesting等,端到端之间如何实现监控和trace,这些都是微服务体系下需要去思考的问题。

要解决上面这些问题,微服务通信可以从三个方面进行讨论

细化服务颗粒:按照功能拆分、微服务化、松耦合
细化服务颗粒:按照功能拆分、微服务化、松耦合
分模块快速迭代:可以将应用程序拆分为核心和非核心模块。非核心模块出现问题的时候,核心模块不会受到影响。参考这篇《微服务3:微服务拆分策略》
流量管理:金丝雀发布,ABTesting
实现服务的高可用治理:熔断、限流、降级,超时重试,异常离群
轻量级通信,使用RESTfulAPI或者RPC进行接口访问
跨语言,语言有特定的应用场景,比如go和Java、c++适合不同的业务方向,开发语言不同,但是遵循同一套标准,使用轻量级的API进行通信,实现服务语言上的解耦。

2 服务之间的通信方式

而微服务的通信,是在服务之间增加一个间接的中间层来完成服务间的通信过程。目前微服务的通信方式有以下三种:

1、基于网关的通信

2、基于RPC的通信

3、基于ServiceMesh的数据面(SideCar)的通信

接下来逐一介绍这三种通信方式的具体实现,本文先介绍基于网关的通信方式。

2.1 基于网关的通信

我们先看看,在没有网关的情况下,服务是怎么通信的?

202404072035230842.png

如上图有3个客户端,在调用4个服务的接口。这种直连调用的方式有很多问题:客户端需要保存所有服务的地址,同时也需要实现一些系统级的容错策略。

比如负载均衡、超时重试、服务熔断等,非常复杂,并且难以维护。因为是在各客户端保存的服务地址,一旦某个服务端出现问题或者发生迁移,所有的客户端都需要修改并且升级。

另外如果再增加一个E svc,所有的客户端也需要升级。而且在某些场景下存在跨域请求的问题,每个服务都需要实现独立的身份和权限认证等等。

这些问题导致 服务间的通信过于复杂,对于开发和维护都不优化。

如果我们在客户端和服务端增加一层网关,所有请求都经过网关转发到对应的下游服务,客户端只需要保存网关的地址并且只和网关进行交互,这样就大大简化了客户端的开发。

202404072035234993.png

如果需要访问用户服务,只需要构造右边这个请求发给网关,然后由网关将请求转发给对应的下游服务。

可以将网关简单理解为:路由转发+治理策略,治理策略是指和业务无关的一些通用策略,包括:负载均衡,安全认证,身份验证,系统容错等等

网关作为一个 API 架构层,用来保护、增强和控制对服务的访问。

2.1.1 网关的主要功能

请求接入

1、为各种应用提供统一的服务接入

2、管理所有的接入请求:提供流量分流、代理转发、延迟、故障注入等能力

安全防护

用户认证、权限校验、黑白名单、请求过滤、防web攻击

治理策略

负载均衡、接口限流、超时重试、服务熔断、灰度发布、协议适配、流量监控、日志统计等

统一管理

1、提供配置管理工具

2、对所有服务进行统一管理

3、对中介策略进行统一管理

2.1.2 网关使用场景

蓝绿部署

202404072035239544.png

我们前面看到,在单体应用中,部署是一件比较麻烦的事情,每次的改动,都需要把整个应用程序都发布启动一次。而且系统规模越大,部署过程越复杂,时间越长。

而在微服务架构中,模块部署起来相对更快,更容易。你可以在短时间内对于同一个模块做多次部署,网关可以帮你实现蓝绿部署。

如图所示之前的用户服务版本是V1.0,然后部署V1.1版本,在网关上只需要做一个转发配置的修改,就可以迅速的将所有流量都流到新版本。

灰度发布

202404072035244815.png

类似金丝雀的理念,你对一次性升级版本感到担忧,可以先配置5%的流量达到新版本,让部分人试用一下,等线上观察一段时间后,可以逐步增加对新版本的流量百分比,最终实现百分之百切流。

负载均衡

202404072035251936.png

此能力需要依赖服务注册和服务发现。

服务熔断

202404072035256057.png

网关还可以实现断路器的功能;如果某个下游忽然返回了大量错误,原因有可能是服务挂了或者网络问题或者服务器负载太高,如果此时继续给这个问题服务转发流量就可能会产生级联故障。

出问题的服务有可能产生雪崩,雪崩会沿着调用链向上传递,导致整个服务链都崩溃。

断路器可以停止向问题模块转发流量,在业务层面可以给用户返回一个服务降级之后的页面,开发人员就有相对充分的时间来定位和解决问题。

2.1.3 开源网关

202404072035260978.png

目前常见的开源网关按照语言大概分为上图的五种,如果按照使用数量和成熟度来划分的话,主流有4个,分别是 OpenResty、Kong、Zull和Spring Cloud。

其中 Zull 和Spring Cloud 是用java 实现,前两种是用Nginx+Lua实现的,其中,OpenResty 是一个基于Nginx+Lua实现的一个高性能web 平台,它集成了大量的第三方模块和lua库,用于方便的搭建高性能扩展性强的web 应用服务或者网关,

Kong 是一个基于OpenResty实现的一个高性能可扩展的API网关。从性能上来说:Kong的性能是最好的,其次分别是OpenResty、Spring Cloud 和Zuul。

2.1.4 Nginx介绍

大家熟悉的Nginx是用C语言实现的一个开源、跨平台、高性能的HTTP和反向代理Web服务器,具有高度模块化、扩展性强、轻量级、资源消耗少、高并发、高性能等特点。

Nginx进程模型

202404072035265449.png

Nginx是一个多进程模型,包含一个master 进程和多个worker 进程。

master 进程主要负责接收外部信号,向各个worker 进程发送信号,监控worker 进程的状态。当worker 进程异常退出后,服务不会中断的。master 进程会迅速拉起新的worker 进程来工作。基本的网络事件比如读写,都是在worker 进程来实现。

多个worker 进程是相互独立的,他们共同竞争来自客户端的请求,一个请求只能在一个worker 中进行处理。

Nginx网络模型

2024040720352687010.png

首先,master 进程会listen socket,然后再fork出多个worker 进程,每个worker 进程都可以去accept 这个socket。
Nginx 提供了一把共享accept mutex锁来保证只有一个worker 进程可以把这个请求accept 成功。当这个worker 进程accept 连接成功之后,就可以进行请求的解析读取。

大概数据流:master 先 fork 多个worker 进程,然后当有client 来的时候,client 会连接到一个worker 进程里面,发送消息request。worker 进程再去读取、解析并处理,最终把response 返回给客户端。

那么大家可以想一下这个问题,Nginx 采用了一个多worker 的方式来处理请求,每个worker 里面其实是只有一个主线程,那么它是怎么实现高并发的呢?

答案是采用异步非阻塞的方式;什么是异步非阻塞?

举个例子,当worker 收到一个来自client request 时,就会有一个worker 进程去处理它。但这个worker 进程并不是全程处理,worker 会处理到请求可能会发生阻塞的地方。比如它向后端服务器转发的这个request,并等待请求的返回,

worker 进程不会同步的等待,而是注册一个事件,如果下游返回了,再继续处理这个事件。如果有新的请求再进来,它就可以很快按照这种方式再进行处理。这其实就是非阻塞和IO多路复用。

一旦服务端返回了,就会触发worker 刚才注册的回调事件,worker 才会继续接手这个request。

2.2 Zuul实战

刚才前面已经说过,网关是客户端和服务器之间的中间层,作为系统唯一对外的入口,能够处理流量治理、安全访问等工作。

 功能类型   功能说明 
 功能类型   功能说明 
 统一接入  智能路由
  AB测试、灰度测试
  负载均衡、容灾处理
  日志埋点(类似Nignx日志)
 流量监控  限流处理
  服务降级
 安全防护  鉴权处理
  监控 
   机器网络隔离

业内主要的网关有如下几种:
1、zuul:是Netflix开源的微服务网关,可以和Eureka,Ribbon,Hystrix等组件配合使用,Zuul提供了动态路由、监控、弹性负载和安全功能。

2、kong: 由Mashape公司开源的,基于OpenResty(Nginx+Lua)的 API gateway。

3、Nginx + Lua:高性能的HTTP和反向代理服务器,Lua作为脚本语言,为Nginx提供执行程序,可以高并发、非阻塞的处理各种请求

下面我们以Zuul为案例,来测试下网关的使用。

2.2.1 配置路由规则

新建一个spring-clouid项目,导入Maven 依赖:

     1  <!-- eureka client -->
     2         <dependency>
     3             <groupId>org.springframework.cloud</groupId>
     4             <artifactId>spring-cloud-netflix-eureka-server</artifactId>
     5         </dependency>
     6         <!-- zuul网关 -->
     7         <dependency>
     8             <groupId>org.springframework.cloud</groupId>
     9             <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    10             <version>2.2.10.RELEASE</version>
    11         </dependency> 

配置yaml文件:

     1 server:
     2   port: 1002
     3 spring:
     4   application:
     5     name: zuul-proxy # 服务的名称
     6 eureka:
     7   instance:
     8     hostname: localhost
     9   client:
    10     service-url:  # 这边就保证了注册到 eureka-service 这个注册中心去
    11       defaultZone: http://localhost:1000/eureka/
    12 
    13 #自定义路由映射
    14 zuul:
    15   routes: #路由规则
    16     key-v1:  #自定义key
    17       path: /proxy/**   # 匹配路径,/proxy/ 会路由到 zuul-proxy服务
    18       serviceId: zuul-proxy
    19       url: http://${eureka.instance.hostname}:${server.port}/zuulservice/api/v1.0/  # 只要路径匹配,就转到这个服务对应路径下 

Application中启动网关代理:

    1 @SpringBootApplication
    2 @EnableZuulProxy // 开启网关代理
    3 public class ZuulGatewayApplication {
    4     public static void main(String[] args) {
    5         SpringApplication.run(ZuulGatewayApplication.class, args);
    6         System.out.println("start zuulgateway!");
    7     }
    8 } 

接口实现:

     1 /**
     2  * @author brand
     3  * @Description:
     4  * @Copyright: Copyright (c) 2021
     5  * @Company: Helenlyn, Inc. All Rights Reserved.
     6  * @date 2021/12/5 12:30 下午
     7  * @Update Time:
     8  * @Updater:
     9  * @Update Comments:
    10  */
    11 @Controller
    12 @RequestMapping("/zuulservice/api/v1.0")
    13 public class ZuulServiceController {
    14 
    15     /**
    16      * 获取注册服务信息
    17      */
    18     @RequestMapping(value = "/serviceinfo", method = {RequestMethod.GET})
    19     @ResponseBody
    20     public String getServiceInfo() {
    21         return  "serviceinfo:v1.0,instance 1";

查看效果,proxy路由成功了:

2024040720352726411.png

2024040720352769012.png

2.2.2 测试负载均衡

我们在添加一个端口为1003的服务,跟端口为1002做zuul-proxy服务做负载均衡,这时候先修改 zuul-proxy 的yaml配置。

     1 # 自定义路由映射
     2 zuul:
     3   routes: # 路由规则
     4     key-v1:  # 自定义key
     5       path: /proxy/**   # 匹配路径,/proxy/ 会路由到 zuul-proxy服务
     6       serviceId: zuul-proxy
     7 ribbon:
     8   eureka:
     9     enabled: true # 允许Ribbon使用Eureka
    10 zuul-proxy:
    11   ribbon:
    12     listOfServers: localhost:1002,localhost:1003 # 这边需要建立两个服务 1002,1003,在这两个服务间做负载均衡 

创建一个module,命名zuul-client,增加注册依赖

    1         <!-- eureka client -->
    2         <dependency>
    3             <groupId>org.springframework.cloud</groupId>
    4             <artifactId>spring-cloud-netflix-eureka-server</artifactId>
    5         </dependency> 

配置zuul-client的yaml文件:

    1 server:
    2   port: 1003 # 这边注意,端口为1003,跟上面对应起来了
    3 spring:
    4   application:
    5     name: zuul-client # 服务的名称
    6 eureka:
    7   client:
    8     service-url:  # 这边就保证了注册到 eureka-service 这个注册中心去
    9       defaultZone: http://localhost:1000/eureka/ 

补充一个接口,注意返回的信息不一样:

     1 /**
     2  * @author brand
     3  * @Description:
     4  * @Copyright: Copyright (c) 2021
     5  * @Company:Helenlyn, Inc. All Rights Reserved.
     6  * @date 2021/12/5 3:52 下午
     7  * @Update Time:
     8  * @Updater:
     9  * @Update Comments:
    10  */
    11 @Controller
    12 @RequestMapping("/zuulservice/api/v1.0")
    13 public class ZuulServiceController {
    14     /**
    15      * 获取注册服务信息
    16      */
    17     @RequestMapping(value = "/serviceinfo", method = {RequestMethod.GET})
    18     @ResponseBody
    19     public String getServiceInfo() {
    20         return  "serviceinfo:v1.0,instance 2";
    21     }
    22 } 

网关 zuul-proxy 和 服务zuul-client都启动起来,可以看到如下的效果:

2024040720352799613.png

2024040720352849814.png

2024040720352898715.png


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文