Blage's Coding Blage's Coding
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
  • 网关

    • Api-gateway-core
    • Api-gateway-center
    • Api-gateway-assist
      • 1.服务发现gateway-assist
        • 网关算力服务注册发现
        • @Configuration
        • SPI机制
      • 2.重构:Netty服务端和配置类缓存
      • 3.Maven三大打包插件
        • maven-jar-plugin
        • maven-assembly-plugin
        • maven-shade-plugin
        • 普通jar包和Fat jar
      • 4.容器关闭监听与异常管理
      • 5.配置Dockerfile构建镜像
      • 6.NettyServer#bind与Docker虚拟网卡
        • ServerBootstrap#bind
        • Docker上启动Netty服务
      • 7.Redis发布订阅实现算力自动注册RPC
    • Api-gateway-sdk
    • 部署配置
  • 抽奖

  • 电商

  • 外卖

  • 项目笔记
  • 网关
phan
2023-05-15
目录

Api-gateway-assist

# Api-gateway-assist

# 1.服务发现gateway-assist

# 网关算力服务注册发现

核心:通过设计SpringBoot Starter,让gateway-engine引擎启动SpringBoot程序时,自动读取core算力节点配置信息(yml),并根据该信息发送HTTP请求,将gateway-core注册到网关注册中心。

image-20230425094053736

GatewayServiceProperties:通过注解@ConfigurationProperties实现对yml配置读取。

RegisterGatewayService#doRegister:根据yml读取的算力节点信息,通过HttpUtil.post向注册中心的网关注册接口发送HTTP请求。

GatewayApplication:实现ApplicationListener,利用消息监听发布机制,当refresh结束后触发doRegister流程。

# @Configuration

①在@Configuration注解配置类中,通过@Bean注解在返回new实例的方法上来实现注册自定义bean。

②在@Configuration注解配置类中,通过@ComponentScan指定自定义bean所在的包,实现注册自定义bean。

  • 原理

AnnotationConfigApplicationContext类中会初始化ConfigurationClassPostProcessor,他会在refresh中invokeBeanFactoryPostProcessors执行。

ConfigurationClassPostProcessor解析过程中,首先检查BeanDefinition是否@Configuration注解标记,然后再扫描并构建新的BeanDefinition,初始化容器。

# SPI机制

  • SPI概念

全称Service Provider Interface,服务提供者接口。调用方可以自定义实现服务提供方的服务接口,并替换其默认实现。也就是说通过SPI机制,我们可以自定义修改覆盖外部Jar包(服务提供方)里的接口实现。

而API(Application Provider Interface)的使用完全依赖于所提供的Jar包。

  • JDK原生SPI

java.util.ServiceLoader:JDK原生SPI的核心类,可以通过类名获取在"META-INF/services/"下的多个配置实现文件。

缺点:无法确认具体加载哪一个实现,仅靠ClassPath的顺序决定。同时不能按需加载,需要遍历所有内容并实例化,耗时。

  • Dubbo SPI

核心在于支持按”名“读取SPI服务实现类。

在服务接口添加@SPI注解(可以指定默认实现)。在 META-INF/dubbo路径下支持别名配置(键值对配置,key为别名,value为实现类名),从而解决了SPI服务具体加载的实现类。

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
1
2

内部框架获取服务通过ExtensionLoader实现(Dubbo内部也继承了轻量级的AOP和IOC):

public class DubboSPITest {
    @Test
    public void sayHello() throws Exception {
        ExtensionLoader<Robot> extensionLoader = 
            ExtensionLoader.getExtensionLoader(Robot.class);
        Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
        optimusPrime.sayHello();
        Robot bumblebee = extensionLoader.getExtension("bumblebee");
        bumblebee.sayHello();
    }
}
1
2
3
4
5
6
7
8
9
10
11
  • Spring SPI

JDK和Dubbo的SPI机制每个扩展点单独一个文件,而对于Spring框架来说,所有扩展点都存放在META-INF/spring.factories一个文件当中,指定全限定名的接口+自定义实现类。

org.springframework.boot.context.config.ConfigDataLocationResolver=\
com.example.LocationResolver
1
2

核心是通过SpringFactoriesLoader获取对应的服务。

image-20230425162145482

# 2.重构:Netty服务端和配置类缓存

  • Netty通信服务和配置类的初始化

核心:将整个网关的Netty服务启动和配置初始化全部交给gateway-assist自动配置类实现。

①在GatewayApplication监听器中引入Configuration(交给Spring管理),在拉取RPC注册信息后,根据聚合信息实现配置缓存初始化。

②在GatewayAutoConfig启动配置类中引入initGateway方法,根据网关IP端口配置进行网关通信服务初始化。

GatewayAutoConfig#initGateway:把启动网关通信服务(gateway-core)交给Spring进行。在SpringBoot应用程序启动后,spring主线程在执行@Bean注解方法初始化Bean时,会从线程池中获取新的线程,异步执行Netty服务端的所有流程。

  • 大体流程

①启动zookeeper和真正的服务提供方api-gateway-test-provider,暴露RPC服务

②启动网关注册中心api-gateway-center

③启动api-gateway-assist00,而因为其中内嵌了assist和core网关算力,所以整个网关助手测试工程此时可以充当一个具有自动配置(服务拉取,注册,初始化)的算力节点。(相当于assist+core的一个胖jar)

当用户访问网关监听地址时,HTTP请求会打到assist0中的Netty服务端线程,触发监听事件,并根据uri从缓存中取出对应的Dubbo配置,向test-provider请求并响应给用户。

当网关服务启动后,如果前端操作增添接口,后台拿到接口数据需要①向注册中心center(相当于数据库)的注册接口发送请求,完成数据库中接口信息的写入②获取到gateway-assist00(gateway-core)里的交给Spring管理的Configuration Bean实例(@Autowired),然后通过手动式编程将当前接口信息加入配置缓存,完成注册。

assist00

  • 问题

网关算力真正与RPC服务建立远程连接,获取泛化实例过程还是在触发Netty监听事件中的openSession进行。

①注册添加配置缓存中,键值对key由name改为对应application和interface的Id

②zookeeper在虚拟机,采用host模式启动(bridge模式通信失败)只能访问虚拟机IP访问,127.0.0.1失败。

# 3.Maven三大打包插件

# maven-jar-plugin

默认的打包插件,用来打普通的project JAR包;

# maven-assembly-plugin

支持自定义的打包结构,也可以定制依赖项等。

# maven-shade-plugin

①将依赖的jar包打包到当前jar包(常规打包是不会将所依赖jar包打进来的),也就是说其他地方引用gateway-assist插件时,不需要再导入gateway-core依赖。

②对依赖的jar包进行重命名。

使用:将项目打成一个可执行jar包时,configuration下增加artifactSet,includes添加需要增加的第三方maven依赖,excludes排除不需要打包进来的第三方依赖,

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <configuration>
        <artifactSet>
            <includes>
                <include>com.panhai.gateway:api-gateway-core:jar:</include>
            </includes>
        </artifactSet>
    </configuration>
</plugin>
1
2
3
4
5
6
7
8
9
10
11

# 普通jar包和Fat jar

普通的jar只包含当前 jar的信息,不含有第三方 jar,因此当内部依赖第三方jar时直接运行则会报错,这时候需要将第三方jar内嵌到可执行jar里。

Fatjar:将一个jar及其依赖的三方jar全部打到一个包中。胖包到哪里都能用,而要使用瘦包必须引用的工程中自带依赖才行。

spring-boot-maven-plugin和maven-shade-plugin(指定)打的包都是胖jar。

# 4.容器关闭监听与异常管理

  • 功能

①添加一个容器关闭的监听器,当容器关闭时需要把网关通信core下的Netty服务也一起关闭。

GatewayApplication监听ContextClosedEvent上下文关闭事件,引入Channel作为成员属性,用于关闭Netty服务。

②将网关的注册和RPC服务拉取这两个操作,放入到上下文接口的setApplicationContext方法,这样可以在注册服务与拉取配置失败时,直接抛异常关闭容器。

  • ApplicationContextAware扩展点

实现上下文容器感知接口的对象的方法setApplicationContext,在beanPostProcessorsBeforeInitialization阶段中,通过调用ApplicationContextAwareProcessor(实现了BeanPostProcessor)的增强方法实现。

# 5.配置Dockerfile构建镜像

将打包好的Jar包传入服务器,通过构建镜像文件运行在docker上

  • 步骤

①将打包好的可执行jar包与Dockerfile传入服务器

②编写Dockerfile文件,其中常用指令:

ENV:指定容器启动后,所要执行指令的运行环境,配合外部传入

FROM:指定基础镜像,必须为第一个命令

ADD:将本地文件(jar包)添加到容器中

WORKDIR:配置指定当前工作目录,后续所有指令(CMD)和操作都是把该目录作为相对路径。

EXPOSE:配置镜像暴露的服务端口,一般配合host网络模式使用。(不配置host模式启动容器,会被-p接口映射覆盖)

ENTRYPOINT / CMD:配置容器启动后,调用执行的命令。

# 基础镜像
FROM openjdk:8-jdk-alpine
# 作者
MAINTAINER  "panhai"
# 时区
ENV TZ=PRC
WORKDIR /usr/local/dockerfile
# 添加应用
ADD api-gateway-engine.jar /api-gateway-engine.jar
# 执行镜像
ENTRYPOINT ["java","-jar","/api-gateway-engine.jar"]
1
2
3
4
5
6
7
8
9
10
11

③编译Dockerfile文件,生成Docker镜像。其中后面的点表示从当前上下文相对路径获取。

docker build -f ./Dockerfile -t api-gateway-engine:1.0.1 .
1

④执行镜像,并暴露对应的端口给外部访问

docker run -p 7397:7397 -p 8002:8002  --name api-gateway-engine -d api-gateway-engine:1.0.1
1

不适用容器与宿主机自动映射,只当宿主机网络模式,宿主机EXPOSE暴露的端口会直接使用宿主机对应的端口

docker run --network host --name api-gateway-engine -d api-gateway-engine:1.0.1
1

# 6.NettyServer#bind与Docker虚拟网卡

核心:在主机启动Netty服务端时,绑定IP地址设置为0.0.0.0,代表监听所有发往本地主机的请求。

# ServerBootstrap#bind

①一台机器上可能会有多张网卡,通过ifconfig查看当前机器的所有网卡配置。

②在程序中Netty服务端绑定的IP只能是其所在机器中(ifconfig所能感知到)的某个网卡的IP地址。

③Netty通过bind绑定的地址,是指服务端能够监听到**目的地IP为所绑定网卡地址的IP包**。比如你的主机有网卡A和B,程序中bind(A),那么操作系统就会把所有发往A网卡地址的IP包数据,从内核态复制到用户态,转发给程序使用。

④如果bind绑定0.0.0.0,那么Netty服务端可以监听并收到外部发给你主机上任意一张网卡的请求。

# Docker上启动Netty服务

  • 环境

虚拟机(宿主机)的IP为192.168.200.200

  • 问题排查与实验

①bind绑定192.168.200.200:网关引擎在宿主机直接java -jar可以正常运行,并且外部可通过192.168.200.200访问RPC服务。但是放进宿主机的docker后网关引擎启动失败。

②bind绑定127.0.0.1:网关引擎在宿主机和docker都可以正常启动,但是外部通过宿主机IP或是127.0.0.1都无法访问网关通信组件,获取RPC服务。(-p和host模式两种容器启动方法都尝试过,均访问不到)

  • 结论

①实验一说明容器内部感知不到宿主机的网卡IP,进入容器内部通过ifconfig查看也验证了这个想法。docker内部只能感知到自己的虚拟网卡(eth0),因此Netty服务端不能正常启动。

docker exec -it api-gateway-engine sh
1

Snipaste_2023-04-27_13-31-13

②实验二的结论就很好体现了对bind的理解,docker内部bind监听环回地址相当于禁止外部访问,除非请求也是在容器内部进行或者配置响应的host映射,否则Netty都监听不到外部的请求。

③综上,此处给出的方案是算力节点的启动Netty服务时,监听的IP地址设置为0.0.0.0。

④虚拟机内部gateway-assist向外部windows环境下的gateway-center拉取服务时,ip不能够为localhost。宿主机与外部环境进行通信时需要访问外部ip,也就是注册中心的IP需要改为虚拟机IP。

  • 关于Docker与Vmware

虚拟机端口转发:访问本机端口时,配置所要转发给虚拟机的IP端口。从而实现外网(访问本体某个端口)访问内网虚拟机。

-it:docker run的参数,表示交互式运行,配合/bin/bash进行命令行输入

容器之间进行访问通过docker0网球进行:docker 查看虚拟网卡 (opens new window)

虚拟机三种连接方式:虚拟机三种网络连接方式 (opens new window)

# 7.Redis发布订阅实现算力自动注册RPC

  • 功能实现

目前整个网关系统启动需要遵循以下顺序:①注册中心②RPC应用提供③网关算力引擎。也就是说后续有新的应用接口暴露服务后,不会被算力引擎存入缓存,也就使用不了整个网关调用服务。解决的关键点是engine如何感知到每个provider提供的服务。

长轮询、长链接方案:①网关引擎在Spring生命周期初始化中,另起一个线程,在后台while(true)不断重复拉取所有服务,并存入缓存。显然不论是对于网关引擎还是注册中心都消耗占用不少资源。 ②网关引擎再启动一个Netty服务端(监听端口不能和已有网关通信服务监听端口相同),注册中心启动一个Netty客户端和engine建立连接,每次有新的服务接口注册进来后,通过管道writeAndFlush(systemId)通知服务端,拉取注册新的接口。但维持这样的长链接也会占用不少资源。

最佳实践:使用事件发布订阅机制异步进行,比如redis、MQ,这里网关引擎与注册中心采用redis的发布订阅模式进行通信。新的应用接口启动注册后,触发注册中心的事件发布机制,gateway-center向网关引擎推送新注册接口的信息,gateway-assist收到后进行更新。

此处每启动一个新的provider注册进数据库时,都是以systemId为单位,assist注册时需要保存应用下的所有接口+所有方法。因此事件推送时仅需要传递systemId即可。而后续如果细化到只注册某个方法时,addmapper也需要细化拆分注册不同的模块。

redis

  • 细节

GatewayApplication#addMappers:因为向Configuration注册需要复用,所以可以抽离注册模块成一个方法。

queryApplicationSystemRichInfo(String gatewayId, String systemId):此处在方法复用上的设计十分巧妙。因为第一次注册时需要拉取网关下注册的所有应用接口,而第二次仅需要拉取指定应用下的所有接口方法。因此传参时systemId为空,多加一次网关应用分配信息的查询;而第二次注册直接指定变化的systemId。

Center-RpcRegisterManage#registerEvent:消息事件发布,由sdk触发调用。

Assist-GatewayApplication#receiveMessage:指定的消息监听方处理器方法,入参为推送的消息内容。

  • redis消息订阅发布

①redis事件发布端:

redistemplate:通过配置方式注入Bean,设置默认序列化器fastjsonredisserializer,入参自动注入RedisConnectionFactory根据yml配置的redis端口建立链接。

RedisTemplate#convertAndSend:发布消息,指明接收方Topic通信信道,和消息内容。

②redis监听器:

RedisConnectionFactory:负责设置连接参数,redis服务地址。

注入连接工厂Bean并修改配置,从注册中心拉取redis的端口IP信息(properties),创建Jedis客户端连接(不需要在assist重新配置redis服务地址)。

RedisMessageListenerContainer:注入消息监听器容器,需要设置连接工厂和监听器适配器。并将消息通信Topic与监听器适配器绑定。

MessageListenerAdapter:指明消息处理委托对象,以及消息处理方法(最终发布方的消息会被该方法接收)。

编辑 (opens new window)
#网关
上次更新: 2023/12/15, 15:49:57
Api-gateway-center
Api-gateway-sdk

← Api-gateway-center Api-gateway-sdk→

Theme by Vdoing | Copyright © 2023-2024 blageCoder
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式