微服务学习笔记(一)

微服务技术栈(黑马程序员)

1、初识微服务

1.1、分布式架构:

  • 根据业务功能对系统进行拆分,每个业务模块作为独立项目进行开发,称为一个服务。

  • 优点:

    • 降低服务耦合

    • 有利于服务拓展升级

1.2、微服务:

微服务是一种经过良好架构设计的分布式架构方案,微服务架构的特征:

  • 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发。

  • 面向服务:微服务对外暴露业务接口

  • 自治:团队独立、技术独立、数据独立、部署独立

  • 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题

 

2、服务的拆分和远程调用

2.1、服务拆分

  1. 微服务拆分注意事项:

    • 不同的微服务,不要重复开发相同的业务

    • 微服务数据独立,不要访问其他微服务的数据库

    • 微服务可以将自己的业务暴露为接口,供其他微服务使用

2.2、远程调用

2.2.1、RestTemplate实现远程调用(发送一次跨服务的http请求)
  1. 使用 RestTemplate 发送 http 请求来实现远程调用:

    1. 注册 RestTemplate

      // 在启动类中注册RestTemplate
      @Bean
      public RestTemplate restTemplate(){
         return new RestTemplate();
      }
    2. 服务远程调用 RestTemplate

      public Order queryOrderById(Long orderId) {
             // 1.查询订单
             Order order = orderMapper.findById(orderId);
             // 2.利用RestTemplate发起http请求,查询用户
             // 2.1 url路径
             String url = "http://localhost:8081/user/" + order.getUserId();
             // 2.2 发送http请求,实现远程调用
        // get请求用getForObject,post请求用postForObject
             User user = restTemplate.getForObject(url, User.class);
             // 3. 封装user到order
             order.setUser(user);
             // 4.返回
             return order;
        }
    3. 总结:

      1. 微服务调用方式:

        • 基于restTemplate发起的http请求实现远程调用

        • HTTP请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。

2.2.2、消费者与提供者
  1. 服务提供者:一次业务中,被其他微服务调用的服务(提供接口给其他服务)

  2. 服务消费者:一次业务中,调用其他微服务的服务(调用其他微服务提供的接口)

  3. 一个服务既可以是服务者也可以是消费者,角色是相对的

 

3、Eureka注册中心

3.1、eureka的作用

image-20211103171832841

  1. 消费者该如何获取服务提供者具体信息?

    • 服务提供者启动时会向eureka注册自己的信息

    • eureka会保存这些信息

    • 消费者根据服务名称向eureka拉取提供者信息

  2. 如果有多个服务提供者,消费者应该如何选择呢?

    • 服务消费者利用负载均衡算法,从服务列表中挑选一个

  3. 消费者如何感知服务提供者的健康状态?

    • 服务提供者会每隔30秒向eurekaServer发送心跳请求,报告健康状态

    • eureka会更新记录服务列表,心跳不正常会被剔除

    • 消费者就可以拉取到最新的信息

       

  4. 总结:

    1. 在eureka架构中,微服务角色有两类:

      • eurekaServer:服务端,注册中心,记录服务信息

        • 心跳监控

      1. eurekaClient:客户端

      2. Provider:服务提供者

        • 注册自己的信息到eurekaService

        • 每隔30秒向eurekaService发送心跳

      3. consumer:服务消费者

        • 根据服务名称从eurekaServer拉取服务列表

        • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

3.2、搭建eureka

3.2.1、搭建eurekaServer
  1. 创建项目,引入依赖

    <!--eureka服务端-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
  2. 编写启动类,添加@EnabledEurekaServer注解

    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaApplication.class,args);
    }
    }
  3. 添加application.yml文件,编写如下配置:

    server:
    port: 10086 #服务端口
    spring:
    application:
      name: eurekaServer # eureka的服务名称。eureka会将自己注册到eureka上去,为了后面的集群管理
    eureka:
    client:
      service-url: # eureka的地址信息
        defaultZone: http://localhost:10086/eureka
3.2.2、搭建eureka-client
3.2.2.1、服务注册
  1. 在项目中引入依赖

    <!--eureka客户端依赖-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  2. 在项目的application.yml文件中,编写如下配置

    spring:
    application:
      name: orderService # 注册到eureka所使用的的服务名称
    eureka:
    client:
      service-url: # 注册到eureka, eureka的地址信息
        defaultZone: http://localhost:10086/eureka
3.2.2.2、服务发现

服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡

  1. 修改项目的代码,修改访问的URL路径,用服务名代替IP、端口:

    // 2.1 url路径
    String url = "http://userService/user/" + order.getUserId();
  2. 在项目中的启动类的RestTemplate添加负载均衡的注解:

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
    return new RestTemplate();
    }

     

4、Ribbon负载均衡

4.1、负载均衡流程

image-20211130163840275

4.2、负载均衡策略

4.2.1、默认负载均衡策略
  1. ZoneAvoidanceRule: 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。

4.2.2、IRule更改策略
  1. 通过定义IRule实现可以修改负载均衡规则,有两种方式:

    • 代码方式: 在项目(消费者)的启动类中,定义一个新的IRule:(针对全局的配置,修改时需要重新打包发布

      // 将默认的轮询负载均衡方式更改为随机的负载均衡方式
      @Bean
      public IRule randomRule(){
      return new RandomRule();
      }
    • 配置文件方式:在项目(消费者)的application.yml文件中,添加新的配置也可以修改规则:(针对某个服务的,修改后无需重新打包发布

      userService: # 服务名称
      ribbon:
      NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
4.2.3、饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBanlanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时。可以通过下面的配置开启饥饿加载:

ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称,这里的clients是一个列表
- userService

 

5、Nacos

5.1、Nacos安装

  1. 下载安装包

  2. 解压

  3. 在bin目录下运行指令:startup.cmd -m standalone(单机运行),默认端口为8848

5.2、Nacos服务注册

  1. 在cloud-demo父工程中添加spring-cloud-alibaba 的管理依赖

    <!--nacos的管理依赖-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
  2. 注释掉项目中原有的eureka依赖

  3. 添加nacos的客户端依赖

    <!--nacos客户端依赖-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
  4. 修改项目中的application.yml文件,注释eureka地址,添加nacos地址:

    spring:
    application:
    name: userService #user服务的服务名称
    cloud:
    nacos:
    server-addr: localhost:8848 # nacos服务地址,将服务注册到nacos中去
  5. 启动并测试。

image-20211201133738864

5.3、Nacos服务分级存储模型

  1. 存储模型:防止跨服务调用问题出现,不到万不得已不跨服调用

    • 一级是服务

    • 二级是集群

    • 三级是实例

  2. 设置服务集群属性:

    1. 修改application.yml,添加如下内容:

      spring:
      cloud:
      nacos:
      server-addr: localhost:8848
      discovery:
      cluster-name: HZ # 集群名称,HZ代指杭州

5.4、NacosRule负载均衡

  1. 修改order-service中的application.yml,设置集群为HZ:

    spring:
      cloud:
          nacos:
            server-addr: localhost:8848
            discovery:
              cluster-name: HZ # 集群名称,HZ代指杭州
  2. 在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:(若不配置,则依旧会采用轮询的机制,不会考虑同集群的问题),优先选择本地集群,在本地集群的多个实例中进行随机访问。

    userService: # 要做配置的微服务名称
    ribbon:
      NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
  3. 总结:使用了NacosRule的负载均衡策略时:

    • 优先使用同集群服务实例列表

    • 本地集群找不到提供者时,会去寻找其他集群,同时,控制台会报警告

    • 确定了可用实例列表后,再采用随机负载均衡挑选实例

5.5、根据权重负载均衡

  1. 在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮

    image-20211201144536949

  2. 可以设置权重(0~1)

    image-20211201144735582

  3. 总结:实例的权重控制

    • Nacos控制台可以设置实例的权重值,0~1之间

    • 同集群内的多个实例,权重越高被访问的频率越高

    • 权重设置为0则完全不会被访问

5.6、Nacos环境隔离

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离。

  1. 在Nacos控制台可以创建namespace,用来隔离不同环境

    image-20211201150017115

  2. 然后填写一个新的命名空间信息:

    image-20211201150214614

  3. 保存后会在控制台看到这个命名空间的id:(这个id很重要

    image-20211201150439171

  4. 修改order-service的application.yml,添加namespace:

     spring:
    datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 1234
    driver-class-name: com.mysql.jdbc.Driver
    application:
    name: orderService # order的服务名称
    cloud:
    nacos:
    server-addr: localhost:8848 # 将服务注册到nacos中去,nacos的地址配置
    discovery:
    cluster-name: HZ # 集群名称
    namespace: b61fc8f8-8b0f-4437-b05d-dc6d87f48567 # dev环境,命名空间,填ID
  5. 总结:

    • namespace用来做环境隔离

    • 每个namespace都有唯一的id

    • 不同的namespace下的服务不可见

5.7、Nacos和Eureka的比较

5.7.1、nacos注册中心细节分析

image-20211201153134737

5.7.2、临时实例和非临时实例

服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置:

spring:
cloud:
nacos:
discovery:
ephemeral: false # 表示是否为临时实例

临时实例宕机时,会从nacos的服务列表中剔除,而非临时实例则不会

5.7.3、Nacos和Eureka的异同点
  1. 共同点:

    • 都支持服务注册和服务拉取

    • 都支持服务提供者心跳方式做健康监测

  2. 区别:

    • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

    • 临时实例心跳不正常会被剔除,非临时实例则不会

    • Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

    • Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP模式

       

6、Nacos配置管理

6.1、统一配置管理

  1. 在Nacos中添加配置信息:

image-20211201160623285

  1. 在弹出的表单中填写配置信息:

    image-20211201161443847

注意:Nacos中的配置不是把application.yaml文件中的所有配置都拿来,建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义的配置

  1. 引入Nacos的配置管理客户端依赖:

<!--添加nacos的配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. 在userService中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml文件。

    目的是在nacos的客户端寻找到对应的Data ID的配置文件

spring:
application:
  name: userService  # 服务名称
profiles:
  active: dev  # 环境
cloud:
  nacos:
    server-addr: localhost:8848  # Nacos地址
    config:
      file-extension: yaml  # 文件后缀名
      namespace: b61fc8f8-8b0f-4437-b05d-dc6d87f48567 # 开发环境,若环境为public时不需要添加,反之则需要说明在哪一个环境下

注意:在进行配置读取时一定要注意命名空间的问题,要么配置bootstrap.yml的命名空间;要么将nacos配置文件放在public下

在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos的哪个环境读取哪个文件

在配置namespace时使用id而不是名字

6.2、配置自动更新

Nacos中的配置文件变更后,微服务无需重启就可以感知(热更新)。不过需要下面两种配置实现:

  • 方式一:在@Value注入的变量所在类上添加注解@RefreshScope

@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {

   @Value("${pattern.dateformat}")
   private String dateFormat;
   
  ......
}
  • 方式二:使用@ConfigurationProperties注解

@Data
@Component
@ConfigurationProperties(prefix = "pattern") // 前缀为pattern的配置都能够读到
public class PatternProperties {
   private String dateformat;
}

6.3、多环境配置共享

微服务启动时会从nacos读取多个配置文件:

  • [spring.application.name]-[spring.profiles.active].yaml,例如:userService-dev.yaml

  • [spring.application.name].yaml,例如:userService.yaml

无论profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此,多环境共享配置可以写入这个文件

多种配置的优先级

  • 服务名-profile.yaml > 服务名称.yaml > 本地配置

 

6.4、Nacos集群搭建

  1. 集群搭建步骤:

    • 搭建MySQL集群并初始化数据库表

    • 下载nacos

    • 修改集群配置(节点信息)、数据库配置

    • 分别启动多个nacos

    • Nginx反向代理

7、http客户端Feign

7.1、基于Feign的远程调用

7.1.1、ReatTemplate方式存在的问题
  1. 代码可读性差,编程体验不统一

  2. 参数复杂URL难以维护

7.1.2、定义和使用Feign客户端

使用Feign的步骤如下:

  1. 引入依赖:

    <!--feign客户端依赖-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  2. 在order-service的启动类添加注解开启Feign的功能:

    @MapperScan("cn.itcast.order.mapper")
    @SpringBootApplication
    @EnableFeignClients // 开启Feign的功能
    public class OrderApplication {

    public static void main(String[] args) {
    SpringApplication.run(OrderApplication.class, args);
    }
    ......
    }
  3. 编写Feign客户端:

    @FeignClient("userService") // 括号里为服务的名称
    public interface UserClient {

    @GetMapping("/user/{id}")
    User findById (@PathVariable("id") long id);
    }
  4. 使用FeignClient中定义的方法代替RestTemplate:

    @Service
    public class OrderService {

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private UserClient userClient;
     
       public Order queryOrderById(Long orderId) {
           // 1.查询订单
           Order order = orderMapper.findById(orderId);
           // 2.用Feign远程调用
           User user = userClient.findById(order.getUserId());
           // 3. 封装user到order
           order.setUser(user);
           // 4.返回
           return order;
      }
    }

 

7.2、自定义Feign的配置

Feign运行自定义配置来覆盖默认配置,一般我们需要配置的就是日志级别。

配置Feign日志有两种方式:

  • 方式一:配置文件的方式:

    • 全局生效

      feign:
      client:
        config:
          default: # 这里的default就是全局配置,如果写服务名称就是针对某个微服务的配置
            loggerLevel: FULL  #日志级别
    • 局部生效

      feign:
      client:
        config:
          userService:
            loggerLevel: FULL
  • 方式二:java代码方式,首先需要先声明一个Bean:

    public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level logLevel(){
    return Logger.Level.BASIC;
    }
    }
    • 而后如果是全局配置,则把它放到@EnableFeignClients这个注解中:(在启动类中)

      @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
    • 如果是 局部配置,则把它放到@FeignClient这个注解中:(在具体的Feign客户端里)

      @FeignClient(value = "userService",configuration = DefaultFeignConfiguration.class)

7.3、Feign的性能优化

  1. Feign底层的客户端实现:

    • URLConnection:默认实现,不支持连接池

    • Apache HTTPClient:支持连接池

    • OKHttp:支持连接池

  2. 因此,优化Feign的性能主要包括:

    • 使用连接池代替默认的URLConnection

    • 日志级别,最好用basic或none

  3. Feign的性能优化--连接池配置:

    • Feign添加HttpClient的支持:

      • 引入依赖:

        <!--httpClient的支持-->
        <dependency>
           <groupId>io.github.openfeign</groupId>
           <artifactId>feign-httpclient</artifactId>
        </dependency>
      • 配置连接池:

        feign:
        httpclient:
          enabled: true # 支持HTTPClient的开关
          max-connections: 200 #最大连接数
          max-connections-per-route: 50 #单个路径的最大连接数

8、统一网关Gateway

8.1、网关的功能

  1. 对用户请求做身份认证和权限校验

  2. 将用户请求路由到微服务、并实现负载均衡

  3. 对用户请求做限流

8.2、搭建网关服务

搭建服务网关的步骤:

  1. 创建新的module,引入SpringCloudGateway的依赖和Nacos的服务发现依赖:

    <!--nacos服务注册发现依赖-->
    <dependency>
       <groupId>com.alibaba.cloud</groupId>
       <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!--网关gateway的依赖-->
    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
  2. 编写路由配置及nacos地址

    server:
    port: 10010 # 网关端口
    spring:
    application:
      name: gateway # 服务名称
    cloud:
      nacos:
        server-addr: localhost:8848 # nacos地址
      gateway:
        routes: # 网关路由配置,是一个列表可以定义多个路由
          - id: user-service # 路由id,自定义,只要唯一即可
           # uri: http://localhost:8081 # 路由的目标地址 http就是固定地址,不建议使用
            uri: lb://userService # 路由的目标地址 lb就是负载均衡,后面跟要访问的微服务名称
            predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
              - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求
          - id: order-service
            uri: lb://orderService
            predicates:
              - Path=/order/**
  3. 总结:

    网关路由可以配置的内容包括:

    • 路由id:路由唯一标识

    • uri:路由目的地,支持lb和http两种

    • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地

    • filter:路由过滤器,处理请求或者响应

8.3、路由断言工厂Route Predicates Factory

我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicates Factory 读取并处理,转变成为路由判断的条件

spring提供了11种基本的Predicate工厂。(使用时前往官网查找即可)

 

8.4、路由过滤器GatewayFilter

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

  • 配置局部过滤器:

gateway:
    routes: # 网关路由配置
      - id: user-service # 路由id,自定义,只要唯一即可
           # uri: http://localhost:8081 # 路由的目标地址 http就是固定地址,不建议使用
          uri: lb://userService # 路由的目标地址 lb就是负载均衡,后面跟要访问的微服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
          - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求
          filters: # 局部过滤器,配置在一个id下面
          - AddRequestHeader=Truth,chongchongchong! # 添加请求头
  • 配置全局过滤器:

    gateway:
        routes: # 网关路由配置
          - id: user-service # 路由id,自定义,只要唯一即可
           # uri: http://localhost:8081 # 路由的目标地址 http就是固定地址,不建议使用
            uri: lb://userService # 路由的目标地址 lb就是负载均衡,后面跟要访问的微服务名称
            predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
              - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求
        default-filters: # 全局过滤器
          - AddRequestHeader=Truth,chongchongchong! # 添加请求头
  1. 过滤器的作用:

    • 对路由的请求或者响应做加工处理,比如添加请求头

    • 配置在路由下的过滤器只对当前路由的请求生效

  2. default-filters的作用:

    • 全局过滤器的配置

 

8.5、全局过滤器 GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务的响应,与GatewayFilter的作用一样。

区别在于,GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。

定义方式是实现GlobalFilter接口。

案例:定义全局过滤器,拦截并判断用户身份

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面的条件:

  • 参数中是否有authorization

  • authorization参数值是否为admin

    如果同时满足则放行,否则拦截。

@Order(-1) // 该注解的作用是定义过滤器的执行顺序,值越小优先级越高,也可以通过实现ordered接口实现
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2.获取参数中的authorization参数
String authorization = params.getFirst("authorization");
// 3.判断参数值是否等于admin
if ("admin".equals(authorization)){
// 4.是,放行
return chain.filter(exchange);
}
// 5,否,拦截
// 5.1 设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2 拦截请求
return exchange.getResponse().setComplete();
}
}

 

8.6、过滤器的执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter。

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilte,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。

  1. 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。

  2. GlobalFilte通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定。

  3. 路由的过滤器和DefaultFilter的order由Spring指定,默认顺序是按照声明顺序从1递增,两者分开计数。

  4. 当过滤器的order值一样时,会按照 DefaultFilter > 当前路由的过滤器 > GlobalFilte 的顺序执行。

  5.  

 

posted @ 2021-12-30 14:38  唯美食不可辜负  阅读(218)  评论(0)    收藏  举报