SpringCloud笔记

71 阅读7分钟

微服务

系统架构演变

Eureka服务注册中心

Hystrix服务熔断

Zuul服务网关

服务链路追踪

Spring Cloud Stream 消息中间件

Spring Cloud Config 服务配置中心

Spring Cloud Bus 消息总线

Nacos 服务注册中心

  • 命名空间(Namespace):用于不同环境隔离。例如生产环境、测试环境、开发环境
  • 配置分组(Group):用于不同的服务分为一组。一般一个项目配置分到同一组。
  • 配置集(Data ID):一个配置文件就是一个配置集。

服务注册到Nacos

1、添加依赖

<!--nacos客户端-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2、主类添加@EnbaleDiscoverClient注解

@SpringBootApplication
@EnableDiscoveryClient //开启服务注册发现功能
public class OrderApplication{
    public static void main(String[] args) { 
        SpringApplication.run(OrderApplication.class, args); 
    }
}

3、在application.yml添加nacos服务地址

spring:
  cloud:
    nacos:
      discovery: #nacos服务注册和发现配置
        server-addr: 192.168.57.130:8848 #Nacos地址
        
        
#设置负载均衡
product-server: 
  ribbon: #负载均衡策略
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机方式
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #随机方式
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #权重策略,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #最小连接数策略,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。

4、使用discoverClient获取服务信息

后续使用openfeign替换discoveryClient

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


@RestController
public class OrderController {
    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/order/pod/{pid}")
    public Product getProduct(@PathVariable("pid") Integer pid){
        ServiceInstance serviceInstance = discoveryClient.getInstances("service-product").get(0);
        String url = serviceInstance.getHost() + ":" + serviceInstance.getHost();

        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
    }
}

Feign服务调用(集成Ribbon负载均衡)

通俗的讲,负载均衡就是将负载(工作任务、访问请求)进行分摊到多个操作单元(服务器、组件)上进行执行。

根据负载均衡发生位置的不同,一般分为服务端负载均衡客户端负载均衡

服务端负载均衡指的是发生在服务提供者一方,比如常见的nginx负载均衡

客户端负载均衡指的是发生在服务请求的一方,也就是在发送请求之前已经选好了由哪个实例处理请求。

QQ_1744563671655.png

我们在微服务调用关系中一般会选择客户端负载均衡,也就是在服务调用的一方来决定服务由哪个提供者执行。

远程调用方式

  • RPC(Remote Procedure Call):通过网络连接来实现程序之间的调用。RPC将本地的函数调用封装成网络数据包发送给服务端,服务端接收到请求后执行相应的函数并返回结果。
  • RESTful API:REST(Representational State Transfer)是一种基于HTTP协议设计和构建网络应用程序的架构风格。RESTful API提供了标准化的API接口,客户端可以通过HTTP协议访问服务器上提供的API接口并获取资源。例如Feign、RestTemplate
  • Socket编程:Socket编程是一种底层的网络编程方式,它允许两个进程在网络中建立连接并进行数据传输。通过Socket编程可以实现自定义协议、控制数据传输速率等高级功能。例如WebSocket
  • Message Queue:消息队列(Message Queue)是一种异步通信模式,它允许生产者向队列中添加消息,并由消费者从队列中取出消息进行处理。通过消息队列可以实现分布式系统中各个节点之间的异步通信。

Nacos很好的兼容了Feign, Feign默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。

1、引入依赖

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

2、主类添加Feign注解

添加@EnableFeignClients

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

3、创建一个interface,指定调用方法

@FeignClient("product-server")
public interface ProductServiceFeignClient {
    //指定调用提供者的哪个方法
    //@FeignClient+@GetMapping 就是一个完整的请求路径 http://product-service/product/{pid}
    
    @GetMapping("/product/{id}")
    public Product getProduct(@PathVariable("pid") Integer pid);
}

4、使用Feign调用

@RestController
public class OrderController {
    @Autowired
    private ProductServiceFeignClient productServiceFeignClient;


    @GetMapping("/order/pod/{pid}")
    public Product getProduct(@PathVariable("pid") Integer pid){

        //通过feign调用微服务
        Product product = productServiceFeignClient.getProduct(pid);
    }
}

5、设置负载均衡策略

spring:
  cloud:
    nacos:
      discovery: #nacos服务注册和发现配置
        server-addr: 192.168.57.130:8848 #Nacos地址

feign:
  compression:
    request:
      enabled: true #开启请求压缩
      min-request-size: 2048 #设置触发压缩得大小下限
      mime-types: text/html,application/xml,application/json #设置压缩的数据类型
    response:
      enabled: true #开启响应压缩
  client:
    config:
      product-server: #对应FeignClient服务名称
        loggerLevel: FULL
logging:
  level:
    com.itcast.consumer.OpenfeignConsumerApplication: debug

#设置负载均衡
product-server: #对应FeignClient服务名称
  ribbon: #负载均衡策略
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机方式
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #随机方式
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #权重策略,根据每个服务提供者的响应时间分配一个权重,响应时间越长,权重越小,被选中的可能性也就越低。
#    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #最小连接数策略,也叫最小并发数策略,它是遍历服务提供者列表,选取连接数最小的⼀个服务实例。如果有相同的最小连接数,那么会调用轮询策略进行选取。

Nacos持久化

Nacos默认自带derby数据库持久化我们注册的服务与配置,可以使用外部数据库如mysql存储nacos数据。

Nacos 服务配置中心

注意:不能使用原来的application.yml作为配置文件,而是新建一个bootstrap.yml作为配置文件

配置文件优先级(由高到低):

bootstrap.properties -> bootstrap.yml -> application.properties -> application.yml

1、引入依赖

<!-- nacos配置中心 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2、配置application.yml

server:
  port: 8600
spring:
  application:
    name: my-nacos-server
  cloud:
    nacos:
      discovery: #nacos服务注册和发现配置
        server-addr: 192.168.57.130:8848 #Nacos地址
      config:
        server-addr: 192.168.57.130:8848 #Nacos地址
        file-extension: yaml #这里指定的文件格式需要和nacos上新建的配置文件后缀相同,否则读不到
        group: DEFAULT_GROUP
        namespace: dev

3、Nacos配置

image.png

4、使用 通过@Value注入

5、配置动态刷新

在读取配置类上面加上@RefreshScope

@RestController
@RefreshScope //动态刷新配置
public class NacosConfigController {
    @Value("${config.appName}")
    private String appName;

    @GetMapping("/getAppName")
    public String getAppName(){
        return appName;
    }
}

6、共享配置

bootstrap.yml添加shared-configsdata-id配置指向共享文件

spring:
  cloud:
    nacos:
      discovery: #nacos服务注册和发现配置
        server-addr: 192.168.57.130:8848 #Nacos地址
      config:
        server-addr: 192.168.57.130:8848 #Nacos地址
        file-extension: yaml #这里指定的文件格式需要和nacos上新建的配置文件后缀相同,否则读不到
        group: DEFAULT_GROUP
        namespace: 404ad727-33e0-40ba-933e-484222d9c57a
        shared-configs: #共享配置
          - data-id: share-config1-data.yaml #共享文件配置1
          - data-id: share-config2-data.yaml #共享文件配置2
@RestController
@RequestMapping("/share")
public class ShareConfigController {
    @Value("${share.config1.name}")
    private String name1;

    @Value("${share.config1.age}")
    private int age1;

    @Value("${share.config2.name}")
    private String name2;

    @Value("${share.config2.age}")
    private int age2;

    /**
     * 测试获取nacos配置的共享文件
     * 配置文件名称 share-config1-data.yaml 对应 bootstrap.yaml 的 shared-configs 配置的 dataId
     * @return
     */
    @GetMapping("/getShareConfig1")
    public String getShareConfig1(){
        return name1 + age1;
    }

    @GetMapping("/getShareConfig2")
    public String getShareConfig2(){
        return name2 + age2;
    }
}

Gateway服务网关

Nginx网关和GateWay网关的区别

  • Nignx是流量网关,GateWay是业务网关。流量网关相当于访问的一个总入口,前端页面的一个容器,类似于防火。主要的功能有管理日志,流量监控,黑白名单,请求的负载均衡,全局限流等。而业务网关是针对具体的后端应用和服务,主要的功能是缓存策略、鉴权策略等
  • 一般流量网关配置在前,业务网关配置在后
  • Nginx是C语言写的,GateWay是java语言写的
  • GateWay主要是路由、断言和过滤器,利用这些可以做流控。Nginx主要是负载均衡,反向代理,以及做web服务器

1、依赖

<dependencies>
    <!-- nacos作为注册中心的依赖 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--网关-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
</dependencies>

2、配置

server:
  port: 8081 # 网关端口
spring:
  cloud:
    nacos:
      server-addr: 192.168.57.130:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: consumer-server # 路由id,自定义,只要唯一即可
#          uri: http://192.168.57.130:8082 # 路由的目标地址 http就是固定地址
          uri: lb://consumer-server # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/consumer/** # 这个是按照路径匹配,只要以/user/开头就符合要求
        - id: provider-server
          # uri: http://192.168.57.130:8083
          uri: lb://provider-server 
          predicates: 
            - Path=/provider/** 

路由断言工厂

官网12种示例地址 : docs.spring.io/spring-clou…

在这里插入图片描述 在这里插入图片描述

GatewayFilter路由过滤器

官网示例地址 : docs.spring.io/spring-clou…

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

在这里插入图片描述

在这里插入图片描述

跨域

spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://192.168.57.130:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

Sentinel服务熔断

几种服务熔断器对比

image.png

防止服务雪崩,常见容错方案

  • 隔离:它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。常见的隔离方式有:线程池隔离和信号量隔离。
  • 超时:在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断开请求,释放掉线程。
  • 限流:限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
  • 熔断:在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
  • 降级:降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。

1、引入依赖

<dependencies>
    <!-- nacos作为注册中心的依赖 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--引入sentinel依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
</dependencies>    

2、配置连接sentinel

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 192.168.57.130:8080 #sentinel控制台地址

3、配置限流 通过资源名称限流 通过URL限流

/**
 * 测试限流功能
 */
@RestController
@RequestMapping("/rateLimit")
public class RateLimitController {
    /**
     * 按资源名称限流,需要指定限流处理逻辑
     * 限流规则在sentinel-dashboard配置
     * @return
     */
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handlerException") //@SentinelResource 定义一些限流行为,value对应Sentinel控制台配置流量规则资源名
    public String byResource(){
        return "访问/byResource,正常返回";
    }

    /**
     * 按URL限流,有默认的限流处理逻辑
     * 限流规则在sentinel-dashboard配置
     * @return
     */
    @GetMapping("/byUrl")
    @SentinelResource(value = "byUrl",blockHandler = "handlerException") //@SentinelResource 定义一些限流行为,value对应Sentinel控制台配置流量规则资源名
    public String byUrl(){
        return "访问/byUrl,正常返回";
    }

    /**
     * 按资源限流,使用自定义限流处理逻辑
     * @return
     */
    @GetMapping("/byMyCustomBlock")
    @SentinelResource(value = "byMyCustomBlock",blockHandler = "handlerException",blockHandlerClass = CustomBlockHandler.class) //@SentinelResource 定义一些限流行为,value对应Sentinel控制台配置流量规则资源名
    public String byMyCustomBlock(){
        return "访问/byMyCustomBlock,正常返回";
    }

    /**
     * 限流处理逻辑
     * @param exception
     * @return
     */
    public String handlerException(BlockException exception){
        HashMap<String, Object> map = new HashMap<>();
        ObjectMapper objectMapper = new ObjectMapper();
        map.put("name","限流");
        map.put("msg",exception.getClass().getCanonicalName());
        try {
            String result = objectMapper.writeValueAsString(map);
            return result;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
}

自定义限流处理逻辑

/**
 * 自定义限流处理逻辑
 */
public class CustomBlockHandler {
    public static String handlerException(BlockException exception){
        return "返回自定义限流处理逻辑CustomBlockHandler";
    }
}

4、配置熔断

  • 方式一:配合RestTemplate;
  • 方式二:配合OpenFeign

方式一:配合RestTemplate

使用@SentinelRestTemplate包装RestTemplate

@Configuration
public class RibbonConfig {
    @Bean
    @SentinelRestTemplate
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

fallback设置降级逻辑

/**
 * 测试熔断功能
 */
@Slf4j
@RestController
@RequestMapping("/break")
public class CircleBreakerController {
    @Autowired
    private RestTemplate restTemplate;
    @Value("${service-url.user-service}")
    private String userServiceUrl;

    @RequestMapping("/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handleFallback")
    public CommonResult fallback(@PathVariable Long id) {
        return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
    }

    @RequestMapping("/fallbackException/{id}")
    @SentinelResource(value = "fallbackException",fallback = "handleFallback2", exceptionsToIgnore = {NullPointerException.class})
    public CommonResult fallbackException(@PathVariable Long id) {
        if (id == 1) {
            throw new IndexOutOfBoundsException();
        } else if (id == 2) {
            throw new NullPointerException();
        }
        return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
    }

    public CommonResult handleFallback(Long id) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser,"服务降级返回",200);
    }

    public CommonResult handleFallback2(@PathVariable Long id, Throwable e) {
        log.error("handleFallback2 id:{},throwable class:{}", id, e.getClass());
        User defaultUser = new User(-2L, "defaultUser2", "123456");
        return new CommonResult<>(defaultUser,"服务降级返回",200);
    }
}

方式二:配合OpenFeign

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
feign:
  sentinel:
    enabled: true #激活Sentinel对Feign的支持
#自定义配置信息
service-url:
#  项目位置在study/study04-cloud-hystrix/user-service
  user-service: http://nacos-server

创建一个UserService接口,用于定义对nacos-user-service服务的调用:

@FeignClient(value = "${service-url.user-service}",fallback = UserFallbackService.class)
public interface UserService {
    @PostMapping("/user/create")
    CommonResult create(@RequestBody User user);

    @GetMapping("/user/{id}")
    CommonResult<User> getUser(@PathVariable Long id);

    @GetMapping("/user/getByUsername")
    CommonResult<User> getByUsername(@RequestParam String username);

    @PostMapping("/user/update")
    CommonResult update(@RequestBody User user);

    @PostMapping("/user/delete/{id}")
    CommonResult delete(@PathVariable Long id);
}

创建UserFallbackService类实现UserService接口,用于处理服务降级逻辑

@Component
public class UserFallbackService implements UserService {
    @Override
    public CommonResult create(User user) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser,"服务降级返回",200);
    }

    @Override
    public CommonResult<User> getUser(Long id) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser,"服务降级返回",200);
    }

    @Override
    public CommonResult<User> getByUsername(String username) {
        User defaultUser = new User(-1L, "defaultUser", "123456");
        return new CommonResult<>(defaultUser,"服务降级返回",200);
    }

    @Override
    public CommonResult update(User user) {
        return new CommonResult("调用失败,服务被降级",500);
    }

    @Override
    public CommonResult delete(Long id) {
        return new CommonResult("调用失败,服务被降级",500);
    }
}

5、使用Nacos储存规则 默认情况下,当我们在Sentinel控制台中配置规则一旦我们重启应用,规则将消失。可以把规则保存到nacos config里。

spring:
  application:
    name: sentinel-server
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.57.130:8848 #nacos地址
    sentinel:
      transport:
        dashboard: 192.168.57.130:8858 #nacos地址
        port: 8719 #sentinel-dashboard的端口
      datasource: #sentinel配置持久化,sentinel相关配置保存在nacos,注意目前只支持public命名空间
        ds1:
          nacos:
            server-addr: 192.168.57.130:8848
            dataId: ${spring.application.name}
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

image.png 相关参数解释:

  • resource:资源名称;
  • limitApp:来源应用;
  • grade:阈值类型,0表示线程数,1表示QPS;
  • count:单机阈值;
  • strategy:流控模式,0表示直接,1表示关联,2表示链路;
  • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
  • clusterMode:是否集群。

SMS短信服务

Seata分布式事务

事务

事务在一般情况下都是指代数据库事务,它是由系统对数据进行访问和更新的操作所组成的一个逻辑单元,具备原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )、持久性( Durability )四大特征,用来确保无论发生什么情况数据都处在一个合理的状态,在日常开发过程中能够不需要考虑网络波动、服务器宕机等问题。所以从某种角度来看,事务是用来服务应用层的。

为什么需要分布式事务

单体应用可以依赖数据库事务,将一系列操作限制在一个会话之中同时成功或失败,但是在分布式系统中,每个服务本身的数据库会话之间是隔离的,就无法单纯的依赖数据库事务,在这种场景下想要保证数据的一致性,就需要引入分布式事务。