SpringCloud笔记

导读:本篇文章讲解 SpringCloud笔记,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、主流开发架构

1、SOA架构

面向服务编程

降低耦合

服务粒度比较粗

  • Apache Dubbo
    • 协议:RPC【远程过程调用】
      • 应用开发的对内接口

2、微服务架构

完全解耦

服务粒度:根据业务,单独把功能抽取成一个独立的服务,对外暴露HTTP接口

电商类

  • 分类服务
  • 商品服务【增加、删除、修改、简单条件的搜索(对卖家)】
  • 搜索服务(对买家)【Elasticsearch】
  • 首页服务
  • 内容服务

教育类

  • www.qfedu.com
  • SpringCloud Netflix
    • Eureka、Ribbon、OpenFeign、Hystrix、Zuul、Config、Bus…
  • SpringCloud Alibaba
    • Nacos、Sentinel…

是一套工具集,是多个框架的组合

二、微服务

1、概述

  • 是一种开发设计的思想,是SOA架构的进化后产物

  • 微服务架构风格是将具备独立功能的业务模块拆成一个服务【应用】,对外暴露HTTP接口,提供对其他服务进行远程通信的功能。

  • 一个微服务可以具备自己独立的业务功能和自己独立的数据库及独立的开发语言

  • 一个微服务就是一个SpringBoot项目

三、SpringCloud

1、概述

  • 微服务架构是一种风格,是一个种思想,是一种理念

  • SpringCloud是具体的落地实现,是技术栈

    • 是多个框架的组合

    • 实现技术栈

      • SpringCloud Netflix

        Eureka、Ribbon、OpenFeign、Hystrix、Zuul、Config、Bus…

      • SpringCloud Alibaba

        Nacos、Sentinel…

2、微服务存在问题

  • 服务数量多,调用关系复杂【服务注册与发现 —- Eureka(优瑞卡)】【Nacos】
  • 服务治理【Eureka(优瑞卡)】
  • 服务之间如何相互通信【Ribbon(瑞本)】
  • 服务熔断与降级【Hystrix(害四缺个四)】【Sentinel】
  • 统一拦截或过滤【Zuul】【认证、限流】
  • 分布式配置中心、Bus【Config、Bus(RabbitMQ)】【Nacos、Bus(RocketMQ)】

四、版本选择

https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
在这里插入图片描述
  • SpringBoot:2.3.2.RELEASE
  • SpringCloud:Hoxton.SR9
  • SpringCloud Alibaba:2.2.6.RELEASE

五、实现HTTP远程接口调用

1、需求

  • 准备两个微服务
    • 设备服务提供者【device-provider-8001】
    • 设备服务消费者【device-consumer-9001】
  • 服务消费者去调用服务提供者的一个接口
    • hello

2、实现

2.1 父工程【java2107-springcloud-parent】

  • pom.xml
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
</parent>

2.2 子模块-服务提供者【device-provider-8001】

继承父工程

  • pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
  • 通过插件生成引导类和配置文件

    插件
    在这里插入图片描述

|

  • 引导类
@SpringBootApplication
public class DeviceProviderApplication8001 {
    public static void main(String[] args) {
        SpringApplication.run(DeviceProviderApplication8001.class, args);
    }
}
  • 配置文件
server:
  port: 8001
  • Controller接口
@RestController
@RequestMapping("/device")
public class DeviceController {

    @Value("${server.port}")
    String port;
    
    @GetMapping("/hello")
    public String hello(){
        return "hello:" + port;
    }

}

2.2 子模块-服务消费者【device-consumer-9001】

继承父工程

  • pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
  • 通过插件生成引导类和配置文件

    • 引导类
    @SpringBootApplication
    public class DeviceConsumerApplication9001 {
        public static void main(String[] args) {
            SpringApplication.run(DeviceConsumerApplication9001.class, args);
        }
    }
    
    • 配置文件
    server:
      port: 9001
    
  • 增加远程调用接口bean【RestTemplate】

RestTemplate
在这里插入图片描述

|

  • Controller接口
@RestController
@RequestMapping("/device/consumer")
public class DeviceConsumerController {

    @Autowired
    RestTemplate restTemplate;

    @Value("${server.port}")
    String port;

    @GetMapping("/hello")
    public String hello(){
        //调用8001的 /device/hello 接口
        /**
         * 参数一:请求的url地址
         * 参数二:目标接口返回值类型
         */
        String url = "http://localhost:8001/device/hello";
        String result = restTemplate.getForObject(url, String.class);
        return port + " --> " + result;
    }

}

3、RestTemplate

3.1 API

  • Get请求
getForObject方法
在这里插入图片描述
getForEntity方法
在这里插入图片描述
  • Post请求
    • postForObject、postForEntity

请求方式跟Get不一样,其他功能一模一样

3.2 具体实现

3.2.1 新增服务提供者8002

除Controller接口和端口号不一样,其他都跟8001服务提供者一样

  • Controller接口
@RestController
@RequestMapping("/device")
public class DeviceController {

    @Value("${server.port}")
    String port;

    @GetMapping("/get")
    public String testGetVars(String name, Integer num){
        return "get:" + name + "-" + num;
    }

}

3.2.2 新增服务消费者9002

除Controller接口和端口号不一样,其他都跟9001服务提供者一样

  • Controller接口
/**
 * RestTemplate getForEntity
 * @param name
 * @param num
 * @return
 */
@GetMapping("/get/map/entity")
public String getMapEntity(String name, Integer num){
    //调用8002的 /device/get 接口,需要传递两个参数

    //http://localhost:8002/device/get?name=lucy&num=10
    String url = "http://localhost:8002/device/get?name={name}&num={num}";

    Map<String, Object> map = new HashMap<>();
    map.put("name", name);
    map.put("num", num);

    /**
     * 参数一:请求的url地址
     * 参数二:目标接口返回值类型
     * 参数三:Map
     */
    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, map);

    System.out.println("---------响应头---------");
    HttpHeaders headers = responseEntity.getHeaders();
    for (String key : headers.keySet()) {
        System.out.println(key + ":" + headers.get(key));
    }

    System.out.println("----------状态码-----------");
    HttpStatus status = responseEntity.getStatusCode();
    System.out.println(status.value() + ":" + status.getReasonPhrase());

    System.out.println("----------响应体-----------");
    //响应体
    String result = responseEntity.getBody();
    System.out.println(result);

    return port + " --> " + result;
}

/**
 * RestTemplate Map参数,map中的key由占位符的名称决定
 * @param name
 * @param num
 * @return
 */
@GetMapping("/get/map")
public String getMap(String name, Integer num){
    //调用8002的 /device/get 接口,需要传递两个参数

    //http://localhost:8002/device/get?name=lucy&num=10
    String url = "http://localhost:8002/device/get?name={name}&num={num}";

    Map<String, Object> map = new HashMap<>();
    map.put("name", name);
    map.put("num", num);

    /**
     * 参数一:请求的url地址
     * 参数二:目标接口返回值类型
     * 参数三:Map
     */
    String result = restTemplate.getForObject(url, String.class, map);
    return port + " --> " + result;
}

/**
 * RestTemplate 可变参数:参数只需要占位符即可 {1}
 * @param name
 * @param num
 * @return
 */
@GetMapping("/get/vars")
public String getVars(String name, Integer num){
    //调用8002的 /device/get 接口,需要传递两个参数

    //http://localhost:8002/device/get?name=lucy&num=10
    String url = "http://localhost:8002/device/get?name={1}&num={2}";

    /**
     * 参数一:请求的url地址
     * 参数二:目标接口返回值类型
     * 参数三:可变参数
     */
    String result = restTemplate.getForObject(url, String.class, name, num);
    return port + " --> " + result;
}

getForObject【传递Map参数】
在这里插入图片描述

3.2.3 测试

9002访问8002即可
在这里插入图片描述

3.3 传递Cookie

使用Post请求

3.3.1 8002新增接口

8002新增接口
在这里插入图片描述

3.3.2 9002新增接口

9002新增接口
在这里插入图片描述

3.3.3 postman测试

postman测试
在这里插入图片描述

3.4 微服务太多,可以使用DashBoard

DashBoard
在这里插入图片描述

六、服务的注册与发现

1、概述

  • 注册:每个微服务,都需要注册到注册中心【名称=服务列表】
  • 发现:服务的消费者在需要调用服务提供者接口时,可以根据嗠名称从注册中心订阅服务列表,选择一个可用服务提供者进行远程调用

可以实现服务注册与发现的组件

  • SpringCloud Netflix:Eureka
  • SpringCloud Alibaba:Nacos
  • Apache Dubbo:Zookeeper

2、工作原理

工作原理
在这里插入图片描述

3、EurekaServer搭建

3.0 父工程锁定SpringCloud的版本

<!-- 使用import来实现类似多继承 -->
<dependencyManagement>
    <!-- spring-cloud的依赖 -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR9</version>
            <type>pom</type>
            <!-- import: 可以实现类似于多重继承,子模块的版本就不用写了 -->
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

3.1 创建SpringBoot应用

  • eureka-server-8761

3.2 导入一个依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>

3.3 application.yml配置文件

server:
  port: 8761

spring:
  application:
    name: eureka-server

#服务注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:10086/eureka/
    #是否把自己注册到注册中心
    register-with-eureka: false
    #是否要拉取服务
    fetch-registry: false

3.4 引导类打一个注解

@EnableEurekaServer
在这里插入图片描述

3.5 启动、访问

localhost:8761
在这里插入图片描述

4、服务注册【搭建Eureka的客户端】

4.1 在服务提供者与服务消费者导入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

4.2 application.yml配置文件

  • 服务提供者
spring:
  application:
    name: device-provider

#服务注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  • 服务消费者
spring:
  application:
    name: device-consumer

#服务注册中心
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

4.3 引导类打注解【目前版本可以不打】

服务提供者【@EnableDiscoveryClient】
在这里插入图片描述
  • 服务消费者一样

4.4 访问Eureka注册中心

localhost:8761
在这里插入图片描述

5、服务发现

服务消费者要使用服务提供者的接口

  • 9001调用8001,能够成功即可

七、客户端负载均衡【Ribbon】

客户端:服务消费者9001

1、在服务消费者【device-consumer-9001】导入依赖

spring-cloud-starter-netflix-eureka-client 已经包含了ribbon
在这里插入图片描述

2、在RestTemplate上打一个注解

@LoadBalanced
在这里插入图片描述

3、Controller新增一个接口

@GetMapping("/hello2")
public String hello2(){
    //使用@LoadBalanced后,需要指定服务名称来调用
    String url = "http://DEVICE-PROVIDER/device/hello";

    String result = restTemplate.getForObject(url, String.class);
    return port + " --> " + result;
}

4、实现负载均衡效果

4.1 准备两台服务提供者

  • 复制device-provider-8001,修改相关端口为8003
  • 启动
    • 查看注册中心,看到device-provider有两个实例

4.2 查看负载均衡效果

发现:默认是轮询

  • 访问http://localhost:9001/device/consumer/hello2,能够看到在服务提供者的两个实例中轮询

5、负载均衡策略

5.1 核心接口

  • IRule:可以来指定负载均衡策略

5.2 支持负载均衡策略

  • RandomRule:随机策略
  • RoundRibbonRule:轮询策略【默认】
  • WeightedResponseTimeRule:在轮询策略的基础上,后续会根据服务的响应时间,自动给你分配权重
  • BestAvailableRule:根据被调用方并发数最小的去分配
  • RetryRule:重试,调用失败过多久再次尝试【默认为500ms】

5.3 配置

  • 注解【全局】

在服务消费者的RibbonConfig中增加如下配置

/**
 * 指定负载均衡策略为随机
 * @return
 */
@Bean
public IRule rule(){
    return new RandomRule();
}
  • 配置文件【可以指定具体服务】

注释掉全局的配置,然后在application.yml中增加如下配置

#指定具体服务的负载均衡策略
DEVICE-PROVIDER:      # 编写服务名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule  # 具体负载均衡使用的类

八、Eureka的细节

Eureka的细节与自我保护机制
在这里插入图片描述

1、心跳机制

  • Eureka客户端会定时【每30秒】向注册中心发送心跳包进行续约

  • Eureka客户端超过指定时间【90秒】都没有向注册中心发送心跳包,那么注册中心就会认为服务已不可用,就会从服务列表中把该服务给剔除

  • Eureka客户端【消费者】会定时向注册中心更新需要服务列表的信息

配置在EurekaClient

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:10086/eureka/
      #每隔30秒去注册中心更新服务提供者的列表信息[配置在服务消费者]
      registry-fetch-interval-seconds: 30
  instance:
    #每30秒向注册中心发送心跳包
    lease-renewal-interval-in-seconds: 30
    #90秒没向注册中心发送心跳包,那么注册中心就会剔除服务
    lease-expiration-duration-in-seconds: 90

2、自我保护机制

  • 当短时间内,统计续约失败的比例,如果达到一定阈值【15分钟,续约的失败比例超过85%】,则会触发自我保护的机制。在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(eureka.server.enable-self-preservation: false),默认是自动开启的

配置在EurekaServer

eureka:
  server:
    #关闭自我保护机制
    enable-self-preservation: false

3、高可用

如果Eureka宕机了,是否会影响服务之间的调用?

  • 对于服务消费者来说,是否经过一次调用了,如果调用过并且成功,会有本地缓存,可以进行远程调用。没有经过调用,那么无法进行远程调用
  • 对于服务提供者来说,新服务就无法注册,老服务就无法续约
  • Eureka保证的是AP
  • 在分布式系统中,CAP是无法都满足的,且P【分区容错性是一定要保证的】
    • C:一致性
    • A:可用性
    • P:分区容错性
  • 在保证分区容错性的前提性,是选择保证CP还是AP
    • Eukera 保证的AP
      • 在无法做到数据一致性的情况下,保证系统可用是比较重要的。因为数据不一致,是可以有补救措施的,相对而言,付出的代价会小一点【张三、李四在同一时间买同一件商品,价格不一样,这时候,一般返补差价就可以解决了】
    • Zookeeper 保证的CP
      • 在无法做到系统可用的情况下,保证数据一致性是比较重要的。因为系统可用不可用,是可以有补救措施的,相对而言,付出的代价会大很多【张三在早上8点发现淘宝不可用,下午淘宝就上头条新闻了,造成的损失是很大的】
  • 避免单点故障

3.1 实现步骤

3.1.1 搭建两台Eureka

  • 需要向对方注册自己的信息
  • 允许拉取服务列表
  • 修改eureka-server-8761的配置文件【application.yml】
#单机版
#eureka:
#  client:
#    service-url:
#      defautZone: http://localhost:8761/eureka/
#    fetch-registry: false
#    register-with-eureka: false

#集群
eureka:
  client:
    service-url:
      defautZone: http://localhost:8762/eureka/
    fetch-registry: true
    register-with-eureka: true
  • 复制eureka-server-8761项目,修改端口为8762

    • 配置文件【application.yml】
    #集群
    eureka:
      client:
        service-url:
          defautZone: http://localhost:8761/eureka/
        fetch-registry: true
        register-with-eureka: true
    

3.1.2 修改device-provider-8001device-consumer-9001配置文件

eureka:
  client:
    service-url:
      #单机版
      #defaultZone: http://localhost:8761/eureka/
      #向两个注册中心注册信息
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
  • 测试

    • 启动两个Eureka,再启动两个微服务,正访问
    • 停止8761的注册中心
    • 过一会,大概1分钟左右,刷新8762,可以看到8001、9001服务信息被同步过来了
    • 再尝试进行http调用,结果仍旧可以调得通
    停止8761的注册中心,等8762同步数据
    在这里插入图片描述

|

九、OpenFegin

1、概述

  • 可以把服务之间的调用过程,变成基于接口的调用方法,如同Controller调用Service一样
  • 支持SpringMVC注解
  • 底层是通过动态代理实现
    • Feign是一个客户端的调用技术,是一个接口,用来替换掉RestTemplate
    • 会动态生成代理对象,让代理对象去调用目标接口
  • 默认集成了ribbon的负载均衡策略

https://spring.io/projects/spring-cloud-openfeign

2、实现

2.1 创建了一个新工程

  • 【device-consumer-openfeign-9002】【服务提供者】

大家也可以改9001项目,增加openfeign依赖

  • 导入Feign依赖
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

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

2.2 创建一个Feign接口

  • 接口上标记一个注解@FeignClient,指定需要调用的服务名
  • 复制提供者控制器的方法,去掉方法体,修改注解为@RequestMapping,写上value
//path : 请求的统一前缀
@FeignClient(name = "device-provider", path = "/device")
public interface DeviceFeign {

    //@RequestMapping(value = "/device/hello")   //不指定path属性
    @RequestMapping(value = "/hello")
    String hello();

}

2.3 在引导类标记注解

@EnableFeignClients
在这里插入图片描述

2.4 修改消费者控制器的方法为Feign接口调用

消费者控制器接口
在这里插入图片描述

3、Feign底层【动态代理】

Feign底层
在这里插入图片描述

4、Feign参数处理

4.1 参数分类

  • 普通参数:需要加上@RequestParam(name=“参数名”)
  • 路径传参:需要加上@PathVariable(name=“参数名”)
  • JSON:需要加上@RequestBody
  • Cookie:需要通过拦截器来实现Cookie传递

4.2 服务提供者【8001】增加测试接口

//=================fegin参数传递开始==========
//普通参数
@GetMapping("/testVar")
public String testVar(Integer id){
    return "testVar " + port + ":" + id;
}

//路径传参
@GetMapping("/testUrlVar/{name}")
public String testUrlVar(@PathVariable String name){
    return "testVar " + port + ":" + name;
}

//多个参数
@GetMapping("/testManyVar")
public String testManyVar(Integer id, String name){
    return "testManyVar " + port + ":" + id + " " + name;
}

//实体参数
@PostMapping("/testJson")
public Device testJson(@RequestBody Device device){
    return device;
}

//实体参数
@PostMapping("/testJsonCookie")
public String testJsonCookie(@RequestBody Device device, @CookieValue(name = "token", required = false) String token){
    System.out.println("device = " + device);
    return token;
}
//=================fegin参数传递结束==========

4.3 修改服务消费者【9002】的Feign接口

  • 增加拦截器【组装Cookie】
public class CookieInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {

        String token = "";

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttr = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = requestAttr.getRequest();

        Cookie[] cookies = request.getCookies();
        if(!Objects.isNull(cookies)) {
            for (Cookie cookie : cookies) {
                //客户端传递过来的是login_token
                if("login_token".equals(cookie.getName())){
                    token = "token=" + cookie.getValue();
                    break;
                }
            }
        }
        System.out.println("token-->" + token);
        requestTemplate.header(HttpHeaders.COOKIE, token);
    }

}
  • Feign接口增加方法
//=================fegin参数传递开始==========
//普通参数
@RequestMapping(value = "/testVar")
String testVar(@RequestParam(name = "id") Integer id);

//路径传参
@RequestMapping(value = "/testUrlVar/{name}")
String testUrlVar(@PathVariable(name = "name") String name);

//多个参数
@RequestMapping(value = "/testManyVar")
String testManyVar(@RequestParam(name = "id") Integer id, @RequestParam(name = "name") String name);

//实体参数
@RequestMapping(value = "/testJson")
Device testJson(@RequestBody Device device);

//实体参数
@RequestMapping(value = "/testJsonCookie")
//public String testJson(@RequestBody Device device, @CookieValue(name = "token") String token);
String testJsonCookie(@RequestBody Device device);
//=================fegin参数传递结束==========

  • Fegin类上增加配置属性
Fegin类上增加配置属性
在这里插入图片描述
  • 控制器增加方法【DeviceConsumerController】
//使用Feign调用,参数详解
//普通参数
@GetMapping("/testVar")
public String testVar(Integer id){
    String result = deviceFeign.testVar(id);
    return port + "-->" + result;
}

//路径传参
@GetMapping("/testUrlVar/{name}")
public String testUrlVar(@PathVariable String name){
    String result = deviceFeign.testUrlVar(name);
    return port + "-->" + result;
}

//多个参数
@GetMapping("/testManyVar")
public String testManyVar(Integer id, String name){
    String result = deviceFeign.testManyVar(id, name);
    return port + "-->" + result;
}

//实体参数
@PostMapping("/testJson")
public String testJson(@RequestBody Device device){
    Device device1 = deviceFeign.testJson(device);
    return port + "-->" + device1.getDeviceName() + (device1.getPrice() * 100);
}

//实体参数
@PostMapping("/testJsonCookie")
public String testJsonCookie(@RequestBody Device device, @CookieValue(name = "login_token", required = false) String token){
    //result : 就是token
    String result = deviceFeign.testJsonCookie(device);
    return port + "-->" + result;
}
//=================fegin参数传递结束==========

4.4 测试

测试Cookie
在这里插入图片描述

十、Hystrix

1、概述

  • 熔断器、断路器【理解成生活中的触电保护器
  • 作用:就是保护微服务的正常调用,避免服务雪崩

2、微服务的雪崩现象

服务雪崩
在这里插入图片描述

3、解决方案

2.1 服务降级

  • 当某个服务不可用时,让对应的接口快速返回一个失败的结果【托底的数据】,以防止线程阻塞

2.2 线程隔离

  • 让客户端和微服务之间使用的线程池
    • 客户端请求到微服务:使用的是Tomcat的线程池
    • 微服务之间的调用:使用的Hystrix的线程池

2.3 信号量

  • 由信号量来合理的分配Tomcat的线程池

4、服务不可用的几种情况

  • 网络延迟(故障)
    • 服务在Eureka中被剔除了
    • 服务超时【默认是1S】
  • 异常

5、Hystrix的使用

5.1 在Ribbon去使用Hystrix

9001应用

  • 导入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  • 开启熔断【引导类加注解】
@EnableHystrix  //开启熔断
引导类加注解
在这里插入图片描述
  • 为需要熔断的接口编写降级方法

    跟目标接口一样,只是方法名与方法体不一样

    控制器方法
    在这里插入图片描述

|

  • 测试

    • 先启动各个服务,正常调用
    • 停掉8001,再调用,就会出现服务降级
    http://localhost:9001/device/consumer/hello
    在这里插入图片描述

|

5.2 在OpenFeign去使用Hystrix

5.2.1 修改提供者应用

  • 让目标接口出现服务不可用的情况

    • 修改8001的/device/testVar接口
    制造异常
    在这里插入图片描述

|

5.2.2 修改消费者应用【9002】

  • 导入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
  • 配置文件【application.yml】开启熔断开关
#开启熔断
feign:
  hystrix:
    enabled: true
  • 如果使用了拦截器,则需要增加如下配置【application.yml】
#openfeign中如果使用到了拦截器,需要加该配置【信号量管理】
hystrix:
  command:
    default:
      execution:
        isolation:
          #隔离策略之信号量
          strategy: SEMAPHORE
  • 编写一个服务降级实现类,让Feign使用该降级类
DeviceFeignFallback
在这里插入图片描述
DeviceFeign
在这里插入图片描述

5.3 测试超时熔断

提供者接口
在这里插入图片描述
  • 设置超时时间
消费者设置服务超时时间【全局配置】
在这里插入图片描述
#openfeign中如果使用到了拦截器,需要加该配置【信号量管理】
hystrix:
  command:
    default:
      execution:
        isolation:
          #隔离策略之信号量
          strategy: SEMAPHORE
          #线程隔离
          thread:
            timeoutInMilliseconds: 2000
#默认超时时间为1S
ribbon:
  ReadTimeout: 2000
  ConnectionTimeout: 2000

6、熔断器的工作原理

工作原理
在这里插入图片描述
  • 经过测试
    • 版本更换一下
      • SpringBoot:2.2.5.RELEASE
      • SpringCloud:SR3

6.1 配置熔断仪表盘【了解】

  • 导入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
  • 配置Servlet
@WebServlet("/hystrix.stream")
public class HystrixDashboardServlet extends HystrixMetricsStreamServlet {
}
  • 引导类加注解
@EnableHystrixDashboard
@ServletComponentScan(basePackages = "com.qf.java2107.device.servlets")
  • 访问http://localhost:9001/hystrix
http://localhost:9001/hystrix
在这里插入图片描述

第一次看到的是一个Loading

监控熔断的界面
在这里插入图片描述

6.2 测试熔断器效果

  • 增加熔断方法
static Integer count = 0;

@HystrixCommand(fallbackMethod = "hystrixError", commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
        //请求总数
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
        //失败比例
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
        //休眠时间
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000")
})
@RequestMapping("/test/hystrix")
public String testHystrix(Integer id){
    if(id == 1) {
        throw new RuntimeException("id不能为1");
    }
    return "服务正常:" + id;
}

public String hystrixError(Integer id){
    count++;
    return "熔断成功:" + count;
}
  • 引导类增加注解
@EnableCircuitBreaker  //开启熔断
  • 测试
    • 测试成功的接口
    • 测试失败的接口,失败的比例达到你自己的配置,就会发现熔断器打开,那么这个时候,即使成功的请求,也会被熔断
      • 如果没有配置,则使用的默认配置
达到熔断器打开的条件
在这里插入图片描述

四阶段学习要求

1.课堂纪律

  • 课堂上只做一件事情:就是听
  • 跟着思路来听,不能走神,如果犯困,就站起来。

2.授课时间

  • 8:00-8:50 晨读 (前一天的知识、面试题)

  • 8:50-9:05 晨考(前一天的知识)

  • 9:05-10:20 第一节课

  • 10:20-10:40 课间休息

  • 10:40-11:30 第二节课

  • 11:30-12:00 整理上午的内容 上午答疑 完成上午的任务

  • 14:00-15:20 第一节课

  • 15:20-15:40 课间休息

  • 15:40-16:30 第二节课

  • 16:30-16:50 课间休息

  • 16:50-18:00 整理整天的内容 下午答疑 完成作业

  • 19:00-21:30 自习 自己思考:瞻前 顾后,需要独立思考的时间,这个时间绝对不能被看视频占用了。

  • 21:30-22:30 小组成员一对一互面 (班长:拍一个小视频10s,发到微信群)

3.交流的通道

碰到问题怎么办?

  • 思考:前端?后端?数据层?
  • 自己查
  • 相互讨论或者问老师

十一、服务网关-Zuul

1.网关的作用

在这里插入图片描述

在服务的调用过程中,如果都需要通过服务的ip和端口来调用,由于ip地址是变化的,所以调用方需要频繁的调整ip,这样就比较麻烦,通过网关来解决服务转发的功能。除此之外,网关还具有其他的作用:

  • 智能路由(服务转发)
  • 安全控制
  • 限流和降级
  • 服务聚合

2.网关的选型

网关非常重要,在企业中网关是一定要先开发的。网关可以使用以下组件来实现:

  • nginx:负载均衡、路由的效果,但是功能较为单一,配置也不友好
  • spring cloud gateway: 网关模仿了zuul
  • spring cloud netflix Zuul: 目前较为主流、值得推荐使用的网关。Zuul具备以下功能:
    • 路由转发(服务聚合)
    • 错误回调(熔断器)
    • 过滤器(服务降级、限流、安全控制)
  • 自研的网关

3.Zuul的路由转发功能

  • 引入依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
  • 编写配置文件
spring:
  application:
    name: zuul-server
server:
  port: 8763
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
# zuul路由表
zuul:
  routes:
    api-ribbon-consumer:
      path: /api/ribbon/**
      serviceId: DEVICE-CONSUMER
    api-feign-consumer:
      path: /api/feign/**
      serviceId: DEVICE-CONSUMER-OPENFEIGN

  • 启动类上打上注解@EnableZuulProxy
@SpringBootApplication
@EnableZuulProxy
public class ZuulServer8763Application {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServer8763Application.class, args);
    }

}
  • 启动服务,通过path来访问指定的服务

访问服务A:http://localhost:8763/api/ribbon/device/consumer/hello

访问服务B:http://localhost:8763/api/feign/device/consumer/hello

4.路由表中的详细配置

  • 统一前缀和剥离前缀
# zuul路由表
zuul:
  routes:
    api-ribbon-consumer:
      path: /ribbon/**
      serviceId: DEVICE-CONSUMER
      # path中的路径没有被剥离,也就是说path中的路径是在服务中真实存在的。
      strip-prefix: false
    api-feign-consumer:
      path: /feign/**
      serviceId: DEVICE-CONSUMER-OPENFEIGN
  # 统一前缀
  prefix: /api
#  strip-prefix: false
  • 保护敏感路径
# zuul路由表
zuul:
  routes:
    api-ribbon-consumer:
      path: /ribbon/**
      serviceId: DEVICE-CONSUMER
      # path中的路径没有被剥离,也就是说path中的路径是在服务中真实存在的。
      strip-prefix: false
    api-feign-consumer:
      path: /feign/**
      serviceId: DEVICE-CONSUMER-OPENFEIGN
  # 统一前缀
  prefix: /api
  # 保护敏感路径:一旦路径中带有admin,表示敏感路径,不做转发
  ignored-patterns: /**/admin/**
  • 保护敏感请求头——携带cookie的话必须要配置
spring:
  application:
    name: zuul-server
server:
  port: 8763
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
# zuul路由表
zuul:
  routes:
    api-ribbon-consumer:
      path: /ribbon/**
      serviceId: DEVICE-CONSUMER
      # path中的路径没有被剥离,也就是说path中的路径是在服务中真实存在的。
      strip-prefix: false
    api-feign-consumer:
      path: /feign/**
      serviceId: DEVICE-CONSUMER-OPENFEIGN
  # 统一前缀
  prefix: /api
  # 保护敏感路径:一旦路径中带有admin,表示敏感路径,不做转发
#  ignored-patterns: /**/admin/**
  # 保护敏感头 把默认的sensitive-headers配置的cookie 删除掉
  sensitive-headers:
#  strip-prefix: false

5.实现错误回调(熔断)

  • 编写错误回调配置类,实现FallbackProvider接口
  • 指定要回调的服务
  • 指定要回调的内容
package com.qf.zuul.server.fallback;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Thor
 * @公众号 Java架构栈
 */
@Component
public class MyZuulFallback implements FallbackProvider {
    /**
     * 如果要对zuul中配置的所有路由的服务进行错误回调(熔断),return null
     * 如果要对指定的服务进行错误回调,return "服务ServiceID"
     * @return
     */
    @Override
    public String getRoute() {
        return "DEVICE-CONSUMER";
        //return null;
    }

    /**
     * 当服务出现错误后具体要返回的内容,在这个方法里定义
     * @param route
     * @param cause
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            //响应行:状态码
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            //响应行
            @Override
            public int getRawStatusCode() throws IOException {
                return HttpStatus.OK.value();
            }
            //响应行
            @Override
            public String getStatusText() throws IOException {
                return HttpStatus.OK.toString();
            }

            @Override
            public void close() {

            }

            /**
             * 响应体:
             * 返回一个指定的json字符串
             * @return
             * @throws IOException
             */
            @Override
            public InputStream getBody() throws IOException {
                //把对象转换成json字符串——jackson
                ObjectMapper objectMapper = new ObjectMapper();
                //封装一个携带数据的map
                Map<String,Object> map = new HashMap<>();
                map.put("code",1000);
                map.put("message","fall back");
                map.put("data",null);
                //把map转换成json
                String json = objectMapper.writeValueAsString(map);
                //获得拥有json字节数据的字节流
                ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
                return byteArrayInputStream;
            }

            /**
             * 响应头:
             * 返回的数据是json
             * @return
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

6.实现过滤器

1)zuul中过滤器的介绍

在这里插入图片描述

zuul有四种过滤器:

  • pre
  • routing
  • post
  • error

2)实现过滤器

继承ZuulFilter,配置过滤器

package com.qf.zuul.server.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author Thor
 * @公众号 Java架构栈
 */
@Component
public class MyZuulFilter extends ZuulFilter {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 过滤器的类型
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 相同类型的过滤器的顺序
     * 0 表示第一个执行
     * @return
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 是否执行该过滤器:过滤器会被调用,但是可以通过这个方法来手动告知是否执行该过滤器。
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 存放的具体的业务逻辑,怎么过滤?
     * 做一个计数器限流:
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        //获得一个操作zuul过滤器的上下文对象
        RequestContext context = RequestContext.getCurrentContext();
        String limitRedisKey = "my:limit";
        Long count = redisTemplate.opsForValue().increment(limitRedisKey);
        if(count<5){
            if(count==1){
                redisTemplate.expire(limitRedisKey,60, TimeUnit.SECONDS);
            }
            //放行
            context.setSendZuulResponse(true);
            return null;

        }
        //不放行
        context.setSendZuulResponse(false);
        context.setResponseStatusCode(200);
        context.setResponseBody("request limited!");
        return null;
    }
}

3)实现多个过滤器的配合使用

在这里插入图片描述

  • 第一个过滤器
package com.qf.zuul.server.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;

/**
 * 验证登录的过滤器
 * @author Thor
 * @公众号 Java架构栈
 */
@Component
public class MyLoginFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 验证是否已登陆
     *    看cookie中有没有login_token
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {

        //1.获得Cookie
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletRequest httpServletRequest = context.getRequest();
        Cookie[] cookies = httpServletRequest.getCookies();
        if(Objects.nonNull(cookies)){
            for (Cookie cookie : cookies) {
                if("login_token".equals(cookie.getName())){
                    //表示已登陆: ->sso的验证是否已登陆的接口 返回是否已登陆
                    context.set("loginToken",cookie.getValue());
                    //放行?
                    context.setSendZuulResponse(true);
                    return null;
                }
            }
        }
        //未登陆
        //不放行?
        context.setSendZuulResponse(false);
        context.setResponseStatusCode(200);
        context.setResponseBody("no login!");
        return null;
    }
}

  • 第二个过滤器
package com.qf.zuul.server.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author Thor
 * @公众号 Java架构栈
 */
@Component
public class MyZuulFilter extends ZuulFilter {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 过滤器的类型
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * 相同类型的过滤器的顺序
     * 0 表示第一个执行
     * @return
     */
    @Override
    public int filterOrder() {
        return 1;
    }

    /**
     * 是否执行该过滤器:过滤器会被调用,但是可以通过这个方法来手动告知是否执行该过滤器。
     * 如果未登陆,第二个过滤器就不执行
     * @return
     */
    @Override
    public boolean shouldFilter() {
        RequestContext context = RequestContext.getCurrentContext();
        String loginToken = (String) context.get("loginToken");
        if(StringUtils.isNotBlank(loginToken)){
            //已登陆
            return true;
        }
        //未登陆
        return false;
    }

    /**
     * 存放的具体的业务逻辑,怎么过滤?
     * 做一个计数器限流:
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        //获得一个操作zuul过滤器的上下文对象
        RequestContext context = RequestContext.getCurrentContext();
        String limitRedisKey = "my:limit";
        Long count = redisTemplate.opsForValue().increment(limitRedisKey);
        if(count<5){
            if(count==1){
                redisTemplate.expire(limitRedisKey,60, TimeUnit.SECONDS);
            }
            //放行
            context.setSendZuulResponse(true);
            return null;

        }
        //不放行
        context.setSendZuulResponse(false);
        context.setResponseStatusCode(200);
        context.setResponseBody("request limited!");
        return null;
    }
}

7.服务聚合

在这里插入图片描述

十二、分布式配置中心-Spring Cloud Config

1.应用场景

在这里插入图片描述

服务越来越多,对服务的配置文件也需要统一管理及实现服务配置文件的热更新。

此时,就需要一个配置中心的服务专门来做这个事情。

2.实现分布式配置中心的服务端

  • 准备动作

新建git仓库,往仓库中存放配置文件

在这里插入图片描述

  • 创建分布式配置中心项目,引入依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
  • 编写配置文件,注意端口得写死:8888
spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          # git仓库的地址
          uri: https://gitee.com/nn214490523/java2107.git
          # 具体的文件夹
          search-paths: repo
          # 默认的分支
          default-label: master
          # 账号密码
#          username: zh
#          password: 123456
server:
  port: 8888

  • 启动类上打上注解@EnableConfigServer
package com.qf.config.server;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServer8764Application {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServer8764Application.class, args);
    }

}

3.实现分布式配置中心客户端

在已有的服务中,去连接分布式配置中心服务端来获取配置文件。

  • 引入依赖
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
  • 编写配置文件
spring:
  cloud:
    config:
      uri: http://localhost:8888
      # 要获取的配置文件的名称
      name: device-consumer
      # 环境
      profile: prod
      # 分支
      label: master

4.在外部切换多环境配置

  • 方式一:idea中切换

把多个环境的文件准备好

在这里插入图片描述

在idea中启动配置中配置profile->dev

在这里插入图片描述

  • 方式二:在启动jar包指定项目的环境

打包命令:

mvn clean package -Dmaven.skip.test=true

使用命令来指明环境

java -jar my-consumer-1.0-SNAPSHOT.jar --spring.profiles.active=test

十三、Nacos组件

1.Nacos介绍

Nacos组件是springcloud组件库中的一个较为推荐的注册中心和配置中心的实现组件。

  • 作为注册中心
  • 作为配置中心

2.安装Nacos注册中心

  • 去官网下载 https://github.com/alibaba/nacos/releases/tag/1.4.2
  • 安装:解压缩后,进入到bin文件夹中,在cmd中执行如下命令:
startup -m standalone
  • 访问控制台:
localhost:8848

3.编写服务提供者注册到Nacos上

springboot初始化器连的代理服务器地址:https://start.aliyun.com

  • 引入依赖
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
  • 编写配置文件
# 应用名称
spring.application.name=my-provider
# 应用服务 WEB 访问端口
server.port=8001
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=public
  • 编写服务接口
package com.qf.my.provider.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author Thor
 * @公众号 Java架构栈
 */
@RestController
@RequestMapping("/device")
public class MyDeviceController {
    @GetMapping("/hello")
    public String hello(){
        return "hello nacos!";
    }

}

  • 启动服务即可

4.编写服务消费者

  • 引入依赖
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
  • 编写配置文件
# 应用名称
spring.application.name=my-consumer
server.port=9001
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=localhost:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=public
  • 编写bean配置类,注入RestTemplate,实现ribbon的通信
package com.qf.my.consumer.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author Thor
 * @公众号 Java架构栈
 */
@Configuration
public class RestConfig {

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

}

  • 编写接口,声明要向nacos订阅的服务,进行远程调用
package com.qf.my.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.xml.ws.FaultAction;

/**
 * @author Thor
 * @公众号 Java架构栈
 */
@RestController
@RequestMapping("/device")
public class DeviceController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/sayHello")
    public String sayHello() {
        String url = "http://my-provider/device/hello";
        String result = restTemplate.getForObject(url, String.class);
        return result;
    }


}

5.Nacos健康状态检查机制

服务注册:Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。

服务心跳:在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。

服务同步:Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。

服务发现:服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存

服务健康检查:Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

6.Nacos实现配置中心的功能

跟Spring Cloud Config相同,Nacos实现的配置中心也需要两个角色:服务端(已经和注册中心整合在一起了)、客户端。

1)服务端设置配置文件

在这里插入图片描述

2)客户端的配置

  • 引入依赖
				<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
  • 编写bootstrap.properties(早于application.properties加载)
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.config.username=nacos
spring.cloud.nacos.config.password=nacos
#spring.cloud.nacos.config.contextPath=/nacos
## 设置配置中心服务端地址
spring.cloud.nacos.config.server-addr=localhost:8848
# Nacos 配置中心的namespace。需要注意,如果使用 public 的 namcespace ,请不要填写这个值,直接留空即可
#spring.cloud.nacos.config.namespace=
spring.cloud.nacos.config.name=my-config
spring.cloud.nacos.config.file-extension=properties
spring.cloud.nacos.config.group=DEFAULT_GROUP
  • 启动项目,体验热更新(在服务端修改配置,客户端能实时获取)
package com.qf.my.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class MyConsumerApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(MyConsumerApplication.class, args);
      //反复的获取配置文件中的最新的user.name
        for (;;) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String userName = applicationContext.getEnvironment().getProperty("user.name");

            System.out.println("user name :" +userName);
        }
    }

}

十四、整合springcloud组件完成demo

1.需求分析 在这里插入图片描述

2.建数据库表

  • tb_user
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  • tb_address
DROP TABLE IF EXISTS `tb_address`;
CREATE TABLE `tb_address` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `uid` bigint DEFAULT NULL COMMENT '用户id',
  `address` varchar(255) DEFAULT NULL COMMENT '收件地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.创建操作数据库实体的服务

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/65612.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!