【微服务核心笔记】

一、注册中心、配置管理 Nacos

Alibaba Nacos,服务提供者和服务消费者将自己的信息注册到注册中心,注册中心通过心跳机制来确保每个服务都可以正常运行,服务消费者订阅注册中心,注册中心为服务消费者推送变更信息

Nacos 可以将配置集中管理,又可以在配置变更时,及时通知微服务,实现配置的热更新

1、功能

  • 动态服务发现
  • 配置管理

2、注册中心

1)搭建

  1. 创建 nacos 所需要的数据库
  2. 在服务器创建 nacos 配置文件 custom.env,指定模式和连接 Mysql 相关信息
  3. 使用 docker 创建 nacos 镜像,指明配置文件
  4. 访问 ip:8848/nacos,账号密码 nacos

2)服务注册

将服务注册到 Nacos 中,让它统一管理

  1. 服务中添加 spring-cloud-starter-alibaba-nacos-discovery 的依赖
  2. 服务中添加 Nacos 配置,改服务端口其他配置相同会自动变成集群
spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址

3)服务发现

在一个服务中得到已经注册的其他服务的信息

  1. 服务中添加 spring-cloud-starter-alibaba-nacos-discovery 的依赖
  2. 自动注入 DiscoveryClient,使用 List<ServiceInstance> instance = discoveryClient.getInstances("服务名称") 得到实例列表,然后通过 ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size())); 随机得到一个服务对象,就可以使用此对象获得相关信息,如:instance.getUri() 获得服务的地址

3、配置管理

搭建好注册中心即搭建好了 nacos 服务,里面就包含服务管理、配置管理、权限控制、集群管理等功能

1、服务的配置管理

  1. 在 nacos 服务上添加配置文件,Data ID(配置文件的ID):服务名称[-profile].[后缀名],不指定 profile 的配置文件会被所有环境优先加载,达到配置文件在不同环境下共享的效果,如果还有本地配置,本地配置加载的优先级最高,但也容易被覆盖
  2. 服务读取 nacos 配置文件:在本服务中配置 application.yml 文件,然后引入 spring-cloud-starter-alibaba-nacos-config 依赖
  3. 编码中使用配置内容:可以使用 @Value("${a.b(配置文件的键)}") 来注入到变量中
  4. 配置热更新:在 @Value 注入的变量所在类上添加注解 @RefreshScope 或者 使用 @ConfigurationProperties(prefix = "配置文件中的键") 注解代替 @Value 注解。注意 Spring 的配置不支持热更新
# 2.application.yml
# 在这个配置文件中,会去命名空间的组名下根据 `${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}`作为文件id
# 本例中是去读取 nacos 服务上名为 userservice-dev.yaml 的配置文件
spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #开发环境,这里是dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        namespace: 命名空间
        group: 组名
        file-extension: yaml # 文件后缀名

2、共享的配置管理

  1. 在 nacos 中添加共享配置,如数据库信息
  2. 服务中引入 spring-cloud-starter-alibaba-nacos-configspring-cloud-starter-bootstrap 依赖
  3. 服务中读取共享配置:在服务中创建 bootstrap.yaml,配置 file-extension 和 shared-configs
// 3.bootstrap.yaml
spring:
  cloud:
    nacos:
      server-addr: 192.168.150.101 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置。extension-configs可以实现引用其他配置文件,需要指定 dataId、group
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置

3、配置优先级

项目应用名配置文件 > 扩展配置文件 > 共享配置文件 > 本地配置文件(VM Options 参数)

想让本地配置文件最终生效,需要在 nacos 对应的配置文件中添加配置:spring:cloud:config:override-none: true

4、动态路由

Geteway 是在 Spring 中,所以不支持 @ConfigurationProperties 的热更新。我们需要编写自己实现

实现
  1. 创建 nacos 配置文件 gateway-routes.json 来记录路由信息
  2. 在 Geteway 服务中引入 spring-cloud-starter-alibaba-nacos-configspring-cloud-starter-bootstrap 依赖
  3. 在 Geteway 服务中编写类实现动态路由:在组件类中通过 @PostConstruct 注解创建一个项目启动就加载的方法,在方法中使用注入对象的方法来监听指定文件实现动态路由,nacosConfigManager.getConfigService().getConfigAndSignListener("1中配置文件的id","配置文件的分组",超时时间, new Listener() {},监听变化的接口 Listener 需要实现两个方法,Executor getExecutor() 来确定使用什么线程池,void receiveConfigInfo(String configInfo) 具体实现动态路由的方法,configInfo 是变化后的路由信息
# 1.创建 nacos 配置文件 gateway-routes.json 来记录路由信息
[
    {
   
        "id": "item",
        "predicates": [{
   
            "name": "Path",
            "args": {
   "_genkey_0":"/items/**", "_genkey_1":"/search/**"}
        }],
        "filters": [],
        "uri": "lb://item-service"
    },
    {
   
        "id": "cart",
        "predicates": [{
   
            "name": "Path",
            "args": {
   "_genkey_0":"/carts/**"}
        }],
        "filters": [],
        "uri": "lb://cart-service"
    }
]
// 3.在 Geteway 服务中编写类实现动态路由
@Slf4j
@Component
@RequiredArgsConstructor
public class DynamicRouteLoader {
   

    private final RouteDefinitionWriter writer;//更新路由对象
    private final NacosConfigManager nacosConfigManager;//实现动态路由的核心对象

    // 路由配置文件的id和分组
    private final String dataId = "gateway-routes.json";//配置文件名字 id
    private final String group = "DEFAULT_GROUP";//配置文件分组
    // 保存更新过的路由id,然后每次先删除
    private final Set<String> routeIds = new HashSet<>();

    @PostConstruct //让项目启动就加载
    public void initRouteConfigListener() throws NacosException {
   
        // 1.注册监听器并首次拉取配置
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
   
                    @Override
                    public Executor getExecutor() {
   
                        return 线程池对象;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
   
                        updateConfigInfo(configInfo);
                    }
                });
        // 2.首次启动时,更新一次配置,不写的话路由信息会一直为空
        updateConfigInfo(configInfo);
    }

    private void updateConfigInfo(String configInfo) {
   
        log.debug("监听到路由配置变更,{}", configInfo);
        // 1.反序列化
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 2.更新前先清空旧路由
        //  2.1.清除旧路由
        for (String routeId : routeIds) {
   
            writer.delete(Mono.just(routeId)).subscribe();
        }
        routeIds.clear();
        //  2.2.判断是否有新的路由要更新
        if (CollUtils.isEmpty(routeDefinitions)) {
   
            // 无新路由配置,直接结束
            return;
        }
        // 3.更新路由
        routeDefinitions.forEach(routeDefinition -> {
   
            // 3.1.更新路由
            writer.save(Mono.just(routeDefinition)).subscribe();
            // 3.2.记录路由id,方便将来删除
            routeIds.add(routeDefinition.getId());
        });
    }
}

4、集群

  1. 搭建数据库,初始化数据库表结构,一般采用主从模式
  2. 进入 nacos 的 conf 目录,修改配置文件 cluster.conf.example,重命名为 cluster.conf,然后添加每个 nacos 服务的地址
  3. 修改 application.properties 文件,添加数据库配置
  4. 启动每个 nacos 服务
  5. 使用 nginx 进行反向代理:监听 80 端口,将 /nacos 请求反向代理给每个 nacos 服务实现集群
  6. 编程中的 yml 配置文件,直接改成 nginx 监听的端口即可

二、注册中心 Eureka

Spring Cloud Eureka

1、搭建

  1. 创建 eureka-server 服务
  2. 导入 spring-cloud-starter-netflix-eureka-server 依赖
  3. 编写启动类:添加 @EnableEurekaServer
  4. 编写 yml 配置文件:eureka:client:service-url: defaultZone: http://127.0.0.1:10086/eureka(本eureka服务地址)
  5. 启动服务,访问:http://127.0.0.1:10086

2、服务注册

  1. 服务中引入依赖:spring-cloud-starter-netflix-eureka-client
  2. 编写 yml 配置文件:eureka:client:service-url: defaultZone: http://127.0.0.1:10086/eureka(eureka服务地址)
  3. 启动服务,服务就注册到 eureka 中了

3、服务发现

  1. 先进行服务注册
  2. 给 RestTemplate 这个 Bean 添加一个 @LoadBalanced 注解,然后路径请求就可以直接使用服务名代替达到负载均衡的效果

三、远程调用 OpenFeign

Spring Cloud OpenFeign,是一个声明式的 http 客户端,是 SpringCloud 在 Eureka 公司开源的 Feign 基础上改造而来。基于 SpringMVC 的常见注解,优雅的实现 http 请求的发送

1、基础使用

  1. 服务中添加 spring-cloud-starter-openfeignspring-cloud-starter-loadbalancer 依赖
  2. 在启动类上添加 @EnableFeignclients 注解来开启远程调用
  3. 编写 OpenFeign 客户端:@FeignClient 指定要请求的服务列表;@GetMapping 指定请求方式和请求路径;方法参数指定请求参数;方法返回值指定返回后封装的类型
// 3. 编写 OpenFeign 客户端
// 向 http://item-service/items 发送一个 GET 请求,携带 ids 为请求参数,并自动将返回值处理为 List<ItemDTO>
@FeignClient("item-service")
public interface ItemClient {
   
    @GetMapping("/items")
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

2、开启连接池

Feign 底层默认 HttpURLConnection 实现,不支持连接池,为了更好的性能,通常使用支持连接池的实现 OKHttp、Apache HttpClient

使用

  1. 引入 feign-okhttp 依赖
  2. 在配置文件中配置:feign:okhttp:enabled: true # 开启 OKHttp 功能

3、最佳实践

项目中一般是将所有的远程调用封装到一个模块中,然后让其他服务引入这个模块,但是可能会出现注解扫描不到的情况,所以要在启动类的注解上添加 @EnableFeignclients(basePackages = "远程调用模块的包路径")

4、输出日志

OpenFeign 只会在 FeignClient 所在包的日志级别为 DEBUG 时,才会输出日志;四个级别:NONE:不记录,默认值;BASIC:仅记录请求的方法,URL 以及响应状态码和执行时间;HEADERS:在 BASIC 的基础上,额外记录了请求和响应的头信息;FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据

配置

  1. 创建一个普通类,使用 @Bean 注解注入指定级别的 Logger.Level 对象
  2. 局部生效:@FeignClient(value = "item-service", configuration = 创建的普通类.class)
  3. 全局生效:@EnableFeignClients(defaultConfiguration = 创建的普通类.class)

5、支持 Multipart 格式传参

  1. 引入 feign-formfeign-form-spring 依赖
  2. 导入配置类,注入 Encoder 对象
  3. 在 feign 接口的注解上添加:@FeignClient(value = "服务名",configuration = 配置类.class)

6、项目中传递用户信息

  1. 在配置类中注入 RequestInterceptor Bean 接口对象
  2. 在匿名内部类方法中实现用户信息的传递
@Bean
public RequestInterceptor userInfoRequestInterceptor(){
   
    return new RequestInterceptor() {
   
        @Override
        public void apply(RequestTemplate template) {
   
            // 获取登录用户,在网关有通过全局过滤器到服务的前置拦截器传递过来
            Long userId = UserContext.getUser();
            if(userId == null) {
   
                // 如果为空则直接跳过
                return;
            }
            // 如果不为空则放入请求头中,传递给下游微服务
            template.header("user-info", userId.toString());
        }
    };
}

四、网关 Gateway

Spring Cloud Gateway,网关就是网络的关口,负责请求的路由、身份校验、权限控制、限流

1、功能

  • 权限控制: 网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截
  • 路由和负载均衡: 一切请求都必须先经过 gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡
  • 限流: 当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大

2、项目使用

  1. 创建服务 gateway,引入依赖 spring-cloud-starter-gatewayspring-cloud-starter-loadbalancer
  2. 在 yml 配置文件中编写基础配置和路由规则
  3. 启动网关服务进行测试:访问 http://网关地址/user/1时,符合 /user/** 规则,请求转发到 uri:http://userservice/user/1
spring:
    gateway:
      routes: # 网关路由配置:将符合 Path 规则的一切请求,都代理到 uri 参数指定的地址
        - id: user-service # 路由id,自定义,只要唯一即可
          uri: lb://userservice # 路由的目标地址,lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
         #filters: # 过滤器
            #- AddRequestHeader=Truth, XTL! # 添加请求头

3、断言工厂

名称 说明 示例
Path 请求路径必须符合指定规则 - Path=/red/{segment},/user/**
After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]
Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Header 请求必须包含某些header - Header=X-Request-Id,\d+
Method 请求方式必须是指定方式 - Method=GET,POST
Query 请求参数必须包含指定参数 - Query=name

4、过滤器工厂

GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

1、使用

  1. 在 yml 配置文件中添加 spring:cloud:gateway:routes:filters: - 过滤器名称=过滤值
  2. filters 只会对当前服务生效,想要全局生效,需要使用默认过滤器 default-filters,它与 routes 同级

2、种类

Spring 提供了 33 种不同的路由过滤器工厂

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除有一个响应头
RequestRateLimiter 限制请求的流量

3、自定义过滤器工厂

  1. 编写组件类继承 AbstractGatewayFilterFactory,重写 apply 方法,该类的名称一定要以 GatewayFilterFactory 为后缀
  2. apply 方法中返回 GatewayFilter 匿名内部类对象,内部类实现 filter 方法
  3. 在 filter 方法中实现自己的逻辑
  4. 在 yml 配置文件中的 default-filters 或者 filter 中指明才能使用,名字是类名称的前缀,然后该自定义过滤器就生效了
  5. 想要实现参数传递,需要配置 Config 内部类来定义属性充当参数,再重写 shortcutFieldOrder 方法实现参数传递
//实现参数传递的自定义过滤器工程
@Component
public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型
		extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> {
   

    @Override
    public GatewayFilter apply(Config config) {
   
        // OrderedGatewayFilter是GatewayFilter的子类,包含两个参数:
        // - GatewayFilter:过滤器
        // - int order值:值越小,过滤器执行优先级越高
        return new OrderedGatewayFilter(new GatewayFilter() {
   
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   
                // 获取config值
                String a = config.getA();
                String b = config.getB();
                String c = config.getC();
  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玄天灵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值