微服务组件的使用(nacos,feign,sentinel,gateway)

微服务总结

一. nacos

nacos 充当注册中心和配置中心。作为注册中心主要的作用:

  1. 实现服务间的解耦。
  2. 方便服务的水平扩展。

二. 单机版使用

2.1 安装

一. 解压

二. 修改 NACOS_HOME/bin 目录下的 startup.cmd

set MODE="standalone"

第三步,双击 startup.cmd

2.2 在项目中使用

一. 引入依赖

 <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
 </dependency>

二. 配置

spring:
  application:
    # 注册到注册中心的服务名,也就给服务端调用的时候使用的名字
    name: ms-provider
  cloud:
    nacos:
      discovery:
        # nacos的地址
        server-addr: 127.0.0.1:8848
        # 是否将服务注册到 nacos. false就是不注册,但是可以获取服务
        register-enabled: true

三. 服务的调用方使用

private RestTemplate restTemplate;

private DiscoveryClient discoveryClient;  // 服务发现的客户端

public TestController(RestTemplate restTemplate, DiscoveryClient discoveryClient) {
    this.restTemplate = restTemplate;
    this.discoveryClient = discoveryClient;
}

@GetMapping
public Object getRemoteData() {
    // ms-provider 是服务提供方的服务名 spring.application.name 的值
    List<ServiceInstance> serviceInstances = discoveryClient.getInstances("ms-provider");

    ServiceInstance si = serviceInstances.get(0); // 获取集群中的第一个服务

    String url = "http://" + si.getHost() + ":" + si.getPort();
    String fullURL = url + "/user";

    List<String> results = restTemplate.getForObject(fullURL, List.class);

    return results;
    //        List<String> results = restTemplate.getForObject("http://localhost:7834/user", 
    	List.class);
    //        return results;
}

三. 集群的使用

将两个nacos的端口号改的不一样,NACOS_HOME/conf/application.properties

3.1 集群的搭建

一. 将 NACOS_HOME/bin 下的 startup.cmd 的模式改为 cluster

set MODE="cluster"

二. 将 NACOS_HOME/conf 下的 cluster.conf.example 拷贝一份,叫做 cluster.conf ,将所有的集群配置到文件中:

# 看实际情况
192.168.137.1:8848
192.168.137.1:9948

三. 分别启动两个nacos

3.2 在项目中使用

相对于单机版,只需要在 application.yml 文件中列出所有的集群节点:

spring:
  application:
    name: ms-provider
  cloud:
    nacos:
      discovery:
        # 集群配置
        server-addr: 192.168.137.1:8848,192.168.137.1:9948
        register-enabled: true

四. 修改nacos密码

一. 新建数据 nacos, 设置编码

二. 导入 NACOS_HOME/conf/nacos-mysql.sql 文件到新建的 nacos 数据库中。

三. 修改 users 表, 密码根据 springboot-mp 中测试代码中 PasswordTest 中的代码来生成

PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
System.out.println(passwordEncoder.encode("3455"));

四. 将根据上面生成的代码替换数据数据中的密码

五. 微服务的版本

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.3.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>Hoxton.SR8</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

六. Ribbon

Ribbon是客户端一个负载均衡的工具,所谓的客户端指的是服务的调用方.

一. 只需要将 RestTemplate 纳入到IOC容器的位置加上 @LoadBalanced 即可

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

二. 调用服务的时候,不用再根据服务名获取IP和端口号,可以直接使用

List<String> results =  restTemplate.getForObject("http://ms-provider/user", List.class);

七. Feign

​ Feign是基于Ribbon的另外一个客户端的负载均衡工具,进一步的简化服务间的调用代码。让我们在调用远程服务就像调用本地服务一样。

一. 添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

二. 开启Feign, 在工程的启动类上加上 @EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class ConsumerApplication {
    public static void main( String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

三. 编写接口,在接口上直接使用 @FeignClient 这个注解

// value是需要调用的服务名,spring cloud 会在底层帮我们生成该接口的实现类,并纳入到容器中
@FeignClient(value = "ms-provider")
public interface IUserService {
	
	// ms-provider 服务中的接口
    @GetMapping("/user")
    List<String> getList();
}

四. 在Controller中使用

private IUserService userService;

public TestController2(IUserService userService) {
    this.userService = userService;
}

@GetMapping
public Object get() throws InterruptedException {
	return userService.getList();
}

八. sentinel

​ 整个spring cloud 和 spring cloud alibaba 的区别就在于熔断的组件不同,spring cloud alibaba提供的熔断组件 sentinel, 然后 spring cloud 本身提供的组件是 hystrix

​ sentinel 提供一整套服务的 限流热点热点授权 的规则。主要用于服务的降级与熔断。

​ 服务降级:就是调用服务的时候,因为服务本身原因,目前无法提供给我们想要的结果,但是还是会返回一个结果,但是结果并不是一个正确的结果。也是为了保护我们服务和整个系统。

​ 熔断:服务暂时是无法提供对外访问的。

8.1 安装

下载:https://github.com/alibaba/Sentinel/releases

启动:java -jar sentinel-dashboard-1.8.2.jar

8.2 概念解释

QPS(Query Per Second): 表示每秒的请求次数。

单机阈值:是每秒请求次数。

8.2 流控

下图传达的意思是:每秒中查询次数最多一次,如果超过一次,就直接

请添加图片描述

sentinel内部有一个 ‘coldFactor(冷因子)’, 值为3.

下图传达意思是:在刚开开始访问的时候,每秒的访问此处最多 9 / 3(冷因子) = 3, 然后在 5秒(预热时长) 后达到9.

请添加图片描述

下图传达的意思是:每秒中最多处理 5 个请求,其余的请求会等待,如果等待的时间超过2000ms,还没能获取到请求,那么会失败。

请添加图片描述

8.3 熔断

RT(Response Time): 响应时间。

下图的意思:在连续5个请求中,如果有超过(包括) 20%(0.2)的请求,响应时间超过 200ms, 就会熔断。熔断时长为 5s, 也就是在未来的5s钟之内,所有的请求都无法处理。

请添加图片描述

8.4 热点规则

热点规则就是针对请求参数来进行限流的,我们以下面的一个请求为例:

@GetMapping("/1")
@SentinelResource("get")
public Object get(Integer id, String name) {
    System.out.println(id + "###" + name);
    return personService.getAll();
}

对于下图:参数索引指的是代码中 Integer id, 如果在请求的过程中,可以不携带参数,但是如果携带了 id,那么在1秒中之内,只能有一个请求。

请添加图片描述

​ 当我们在配置了热点规则之后,在热点规则中点击编辑又会多出 高级选项,主要是针对特定的某个参数的值进行限流。

​ 下图的意思是:参数索引指的是代码中 Integer id, 如果在请求的过程中,可以不携带参数,但是如果携带了 id,那么在1秒中之内,只能有10请求;但是当id的值为 56 的时候,那么每秒中只能处理2个请求(需要点击"添加"按钮)。场景就是电商中突然出现爆款问题。

请添加图片描述

8.5 授权规则

授权规则就是用户在请求必须要携带指定 “白名单” 中的参数值。

下图的意思是:用户请求中(可以通过请求、或者请求头)携带一个指定参数,值为 web 或者 android 才能访问我们的应用,必须要配合才能实现这个效果。

请添加图片描述

配合热点规则代码,如下所示:

// 这个类必须要实现 RequestOriginParser 接口
@Component
public class SentinelOriginParse implements RequestOriginParser {
	// 该方法的返回值是作为授权规则的白名单或者黑名单来进行比对,如果符合白名单就是可以访问应用,
	// 如果是在黑名单中或者不在白名单中,就不能访问应用
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String assign = request.getParameter("assign");
		// 三元判断的目的,是为了解决当用户不传入的时候,还是可以正常访问,所以我们
        // 就在用户什么都不传的时候,随机给他一个值。
        return assign == null ? RandomStringUtils.random(16) : assign;
    }
}

九. 自定义返回的错误信息

如果我们在 sentinel 中配置了各种的规则,请求违反这些规则之后,都是报一个 500 错误,不便于前端给用户进行一个友好的提示,所以我们需要根据实际的情况,返回特定的提示信息。

9.1 在类的内部进行处理
/**
 * sentinel 针对违反了不同的规则的处理,底层是通过抛出异常的方式来进行处理。
 * blockHandler的值就是针对违反规则的错误的处理函数。
 */
@GetMapping
@SentinelResource(value = "test2", blockHandler = "getHandler")
public Object get() throws InterruptedException {
	return personService.getAll();
}

/**
 * BlockException 是所有的限流或者熔断规则异常的顶级异常。
 * AuthorityException: 授权异常
 * FlowException: 流控异常。
 * DegradeException: 熔断异常。
 * ParamFlowException: 热点异常
 */
/**
 *  getHandler 要和 @SentinelResource 注解中的 blockHandler 的值要对应上,而且函数必须接受
 *  BlockException 接受这个异常,我们通过具体的异常类型,来判断用户的请求到底是违反了那个规则。
 */
// BlockException 是 限流、熔断、热点、授权四种异常的一个父级接口.
public Object getHandler(BlockException ex) {
    if(ex instanceof AuthorityException) {
    	return "授权失败";
    }else if(ex instanceof FlowException) {
    	return "你点的太快了";
    }else if(ex instanceof DegradeException) {
    	return "熔断了";
    }else if(ex instanceof ParamFlowException) {
    	return "你访问的数据被太多人访问.";
    }else {
        return "其他异常";
    }
}
9.2 单独定义类来处理异常

我们可能存在一个请求,不同的接口中都会用到同一个错误的处理,但是 9.1 章节中的处理方式只能将错误的处理和接口放到一个类中。如果多个类中多个接口都要使用同一种错误处理,那么势必需要在每个类中都定义相同的代码。所以就很有必要将错误的处理,单独放到一个类中,以供其他接口来使用。

@RestController
@RequestMapping("/test2")
public class TestController2 {
    private IUserService userService;

    public TestController2(IUserService userServic) {
        this.userService = userService;
        this.personService = personService;
    }

    /**
     * 需要在 blockHandlerClass 所对应的类来定义统一的错误处理。
     * blockHandler对应的是 SentinelBlockHandler 这个类中的一个 “静态方法“,用来处理当前接口
     * 违反规则之后的统一错误处理。
     */
    @GetMapping
    @SentinelResource(value = "test2", blockHandler = "getHandler", blockHandlerClass = SentinelBlockHandler.class)
    public Object get() throws InterruptedException {
        return userService.getList();
    }
}
public class SentinelBlockHandler {
	// 该方法必须是静态的
    public static Object getHandler(BlockException ex) {
        if(ex instanceof AuthorityException) {
            return "授权失败";
        }else if(ex instanceof FlowException) {
            return "你点的太快了";
        }else if(ex instanceof DegradeException) {
            return "熔断了";
        }else if(ex instanceof ParamFlowException) {
            return "你访问的数据被太多人访问.";
        }else {
            return "其他异常";
        }
    }
}

十. Feign与Sentinel的整合

​ 如果A服务调用B服务,如果B服务暂时无法提供服务,或者内部异常了,A就无法正常的使用,所以在服务间我们也需要进行服务的降级处理。

配置就是在 @FeignClient 注解加上一定的参数

10.1 简单的服务降级
// 通过fallback 去指定降级处理的类,该类需要实现 IUserService ,实现接口中所有的方法,当调用失败,就调用
// 实现类中对应的方法就行了。
@FeignClient(value = "ms-provider", fallback = UserServiceFallback.class)
public interface IUserService {

    @GetMapping("/user")
    List<String> getList();
}
/**
 * 1.该类必须要纳入到IOC容器中。
 * 2.该类必须要实现需要进行服务降级的接口,如果调用远程失败了,就调用该类中对应的方法。
 */
@Component
public class UserServiceFallback implements IUserService {

    @Override
    public List<String> getList() {
        System.out.println("服务降级");
        return Arrays.asList(new String[]{});
    }
}

这种简单的服务降级,存在一个问题,就是我们无法得知我们的系统是因为什么原因导致了服务的降级,说白就是我们无法获取异常的信息。

10.2 异常处理服务降级
/**
 * 通过 fallbackFactory 指定一个类,该类需要实现 FallbackFactory 接口
 */
@FeignClient(value = "ms-provider", fallbackFactory = UserServiceFallbackFactory.class)
public interface IUserService {

    @GetMapping("/user")
    List<String> getList();
}
/**
 * 1.该类必须要纳入到IOC容器中。
 * 2.泛型类型要是需要降级的接口。
 */
@Component
@Slf4j
public class UserServiceFallbackFactory implements FallbackFactory<IUserService> {
    /*
     * 我们可以通过 throwable 获取到错误的信息,无论是接口中哪个方法,调用失败之后
     * 都会将错误传递给 throwable
     */
    @Override
    public IUserService create(Throwable throwable) {
        return new IUserService() {
            @Override
            public List<String> getList() {
                if(log.isDebugEnabled()) {
                    log.debug(throwable.getMessage());
                }
                return Arrays.asList(new String[]{});
            }
        };
    }
}

十一. Gateway网关

zuul是spring cloud的第一代网关,gateway是第二代网关,它是基于netty来实现的。网关的作用:

  1. 实现路由。
  2. 过滤。
11.1 基础配置
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        register-enabled: false
    gateway:
      discovery:
        locator:
          # 为true, 表示我们可以通过服务名的方式去找到对应的服务
          # http://localhost:9090/ms-consumer/test2
          # http://localhost:9090/ms-provider/user
          # 可以通过上面的形式来访问我们的服务
          enabled: true

如上方式存在的问题:将服务名暴露出来的,这是不安全的。

12.2 升级配置
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        register-enabled: false
    gateway:
      discovery:
        locator:
          # 为true, 表示我们可以通过服务名的方式去找到对应的服务
          # http://localhost:9090/ms-consumer/test2
          # http://localhost:9090/ms-provider/user
          # 可以通过上面的形式来访问我们的服务
          enabled: false
      routes:
          # 每个路由都需要一个id, 名字可以任意, 但是我们通常写成服务名的形式
		- id: ms-consumer
       # lb -> load balance, 下面这句话, lb:// 是固定的写法, ms-consumer 必须是微服务中某个服务的名字
          # 当请求 predicates 中 Path 下的某个路径的时候, 就会将服务打到该 ms-consumer 这个服务中
          uri: lb://ms-consumer
          # predicates 在程序中我们将其翻译成 "谓词". 表示判断, 是否满足某个条件
          predicates:
              # http://localhost:9090/test2
              # http://localhost:9090/test/a
#            - Path=/test2,/test/**
              # http://localhost:9090/mc/a/test, http://localhost:9090/mc/a/test2
            - Path=/mp/**
              # 表示请求的参数必须是 GET 请求
            - Method=GET
              # 表示请求头中必须要携带 token, 而且只必须是 \w
            - Header=token,\w+
              # 表示请求的参数中必须有 origin 这个参数名, 参数值为 \w+
            - Query=origin,\w+
              #表示访问该服务必须是 2021-08-01 00:00:00 之后才能访问
            - After=2021-07-01T00:00:00+08:00[Asia/Shanghai]
            - RemoteAddr=192.168.52.64/27
          # filters下能写的过滤器都具有一个特征, 以 GatewayFilterFactory 来结尾, 位于: spring-cloud-gateway-core-2.2.5.RELEASE.jar 包下
          #  org.springframework.cloud.gateway.filter.factory 这个包下的内容, 用的时候将 GatewayFilterFactory 这些去掉就可以了
          filters:
              # StripPrefix 对应的是 StripPrefixGatewayFilterFactory 这个类, 作用是当用户发起一个请求:
              # http://localhost:9090/test 会将 uri 部分第一个 /字母  部分去掉, 也就是将 /mc 去掉.
            - StripPrefix=1
12.3 限流

sentinel的限流只是针对某一个资源,在网关中的限流是针对整个服务的。在Gateway内部提供了一个基于Redis令牌桶的限流器。

spring:
  redis:
    host: localhost
    port: 6379

# 前面部分和12.2 是相同的
filters:
    # StripPrefix 对应的是 StripPrefixGatewayFilterFactory 这个类, 作用是当用户发起一个请求:
    # http://localhost:9090/test 会将 uri 部分第一个 /字母  部分去掉, 也就是将 /mc 去掉.
    - StripPrefix=1
    - name: RequestRateLimiter
      # 给 RequestRateLimiterGatewayFilterFactory 给它传入参数
      args:
        # 需要配置 IOC 容器中获取IP的 Bean, 使用 SpringEL  表达式语言来获取
        keyResolver: '#{@hostKeyResolver}'
        # 每秒中往令牌桶中放令牌的数量
        redis-rate-limiter.replenishRate: 3
        # 令牌桶中允许存放的令牌的总数
        redis-rate-limiter.burstCapacity: 3

因为限流是根据ip来进行限流的,所以我们必须要获取到访问的远程客户端的ip地址,获取方式如下:

/**
 * 在如上的配置中,keyResolver 中通过 #{@hostKeyResolver} 其实就是获取的当前的bean.
 */
@Component
public class HostKeyResolver implements KeyResolver {
    /**
     * React 反应式编程:
     *     Mono, 针对单个数据返回
     *     Flux, 针对批量数据返回.
     * 反应式兴起不起来的主要原因, 受限与数据库.
     */
    // 只需要返回用户的 ip
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}
12.4 全局过滤器

全局过滤器是针对所有的请求都会进行过滤。

// @Order(20) 是用来控制过滤器的顺序,数字越小越先执行
@Bean
@Order(20)
public GlobalFilter firstFilter() {
    return new GlobalFilter() {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            System.out.println("firstFilter");
            return chain.filter(exchange);   // 表示接着往下走
        }
    };
}

十二. 配置中心

​ 配置中心的作用就是实现配置的集中管理,让整个产品从开发到生产不会出现回流问题,对于一些敏感的信息起到一个保护的作用。

​ 配置中心使用 nacos, 将所有的配置都放到 nacos 上,在 bootstrap.yml 文件中连接到 nacos, 在启动的时候,会从nacos中读取所有的配置信息,用于项目的配置。

一. 引入依赖

<dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
 </dependency>

二. 在工程中新建一个 bootstrap.yml 文件,该文件的启动是优先于 application.yml, 如果两个文件中配置出现冲突,那么以 application.yml 为准。配置如下:

# 如果按照如下的配置, 在应用启动的时候会根据 
# ${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
spring:
  cloud:
    nacos:
      config:
        # 配置中心 nacos的地址
        server-addr: localhost:8848
        # 配置文件的后缀名
        file-extension: yml
  application:
    # 这是服务名,也是从nacos中去获取配置的前缀名, 如果这里配置了,那么 application.yml 中就不用配置了
    name: ms-consomer
  profiles:
    # active是哪个环境配置
    active: test

三. 在nacos中新建不同环境的配置文件 ms-consomer-dev.yml ms-consomer-test.yml

请添加图片描述

四. 在ms-consomer-dev.ymlms-consomer-test.yml 中依据不同的环境配置不同的信息,例如:

server:
  port: 7835

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        # 表示自己的服务不注册上去
        register-enabled: true
    sentinel:
      transport:
        # 这个配置是我们应用将信息上传到sentinel的连接
        dashboard: http://localhost:8080
        # 表示sentinel将某个配置信息推给我们的应用
        port: 8719

feign:
  sentinel:
    # 开启feign与sentinel的整合
    enabled: true

十三. 链路追踪

链路追踪的作用就是根据整个微服务调用的链路,查看分析各个服务调用的情况(耗时等信息)

一. 引入依赖(在每一个需要追踪的微服务中都需要添加依赖)

<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

二. 下载zip,下载地址:https://github.com/openzipkin/zipkin

请添加图片描述

三. 启动zipkin, java -jar zipkin-server-2.23.2-exec.jar

四. 在每个需要进行链路追中的服务配置中加上如下的配置信息:

spring:
  zipkin:
    # zipkin的地址
    base-url: http://localhost:9411
    # 是否从nacos上获取zipkin的地址。
    discovery-client-enabled: false
  sleuth:
    sampler:
      # 抽样率 100%, 默认10%
      probability: 0.2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值