SpringCloud Alibaba 学习

一:SpringCloud Alibaba介绍

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服
务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接
入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

主要功能

服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud
Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修
改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任
何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有
Worker(schedulerx-client)上执行。
阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建
客户触达通道

二:微服务环境搭建

备注:将搭建好的环境进行备份,以便练习

2.1 数据库准备

创建数据库
在这里插入图片描述

2.3 创建工程

结构图
在这里插入图片描述
pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yc</groupId>
    <artifactId>springcloud-alibaba-02</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>shop-common</module>
        <module>shop-user</module>
        <module>shop-product</module>
        <module>shop-order</module>
    </modules>

    <!--  父工程   -->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <!--        <spring-cloud.version>Greenwich.RELEA</spring-cloud.version>-->
        <!--        <spring-cloud-alibaba.version>2.1.1.RELEASE</spring-cloud-alibaba.version>-->
        <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
    </properties>


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

</project>

2.4 shop-common 模块

1.pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-alibaba-02</artifactId>
        <groupId>com.yc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>shop-common</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
    </dependencies>

</project>

2.结构
在这里插入图片描述
3.实体类
Order

package com.yc.domain;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.domain
 * @Date 2024/3/4 18:17
 */
@Entity(name = "shop_order")
@Data
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long oid;//订单id
    private Integer uid;//用户id
    private String username;//用户名

    private Integer pid;//商品id
    private String pname;//商品名称
    private Double pprice;//商品单价
    private Integer number;//购买数量
}

Product

package com.yc.domain;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.domain
 * @Date 2024/3/4 18:18
 */
@Entity(name = "shop_product")
@Data
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer pid;//主键
    private String pname;//商品名称
    private Double pprice;//商品价格
    private Integer stock;//库存
}

** User**

package com.yc.domain;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.domain
 * @Date 2024/3/4 18:17
 */
@Entity(name = "shop_user")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer uid;//主键
    private String username;//用户名
    private String password;//密码
    private String telephone;//手机号
}

2.5 shop-user 模块

1.pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-alibaba-02</artifactId>
        <groupId>com.yc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>shop-user</artifactId>


    <dependencies>
        <dependency>
            <groupId>com.yc</groupId>
            <artifactId>shop-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

2.结构
在这里插入图片描述
3.启动类

package com.yc;

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


/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc
 * @Date 2024/3/4 18:22
 */
@SpringBootApplication
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class,args);
    }
}

4.配置文件 application.yml

server:
  port: 8071
spring:
  application:
    name: service-product
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///hmshop02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: ****
    password: ****
  jpa:## 自动生成数据库表配置
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

2.6 shop_product 模块

1.pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-alibaba-02</artifactId>
        <groupId>com.yc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>shop-product</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.yc</groupId>
            <artifactId>shop-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

2.结构
在这里插入图片描述
3.启动类

package com.yc;

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

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc
 * @Date 2024/3/4 18:29
 */
@SpringBootApplication
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class,args);
    }
}

4.配置文件 application.yml

server:
  port: 18082
spring:
  application:
    name: service-product
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///hmshop02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 123456
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

5.ProductController

package com.yc.controller;

import com.alibaba.fastjson.JSON;
import com.yc.domain.Product;
import com.yc.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/4 18:33
 */
@RestController
@Slf4j
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/product/{pid}")
    public Product product(@PathVariable("pid") Integer pid) {
        Product product = productService.findByPid(pid);
        log.info("查询到商品:" + JSON.toJSONString(product));
        return product;
    }

}

6.ProductDao

package com.yc.dao;

import com.yc.domain.Product;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.dao
 * @Date 2024/3/4 18:31
 */
public interface ProductDao extends JpaRepository<Product,Integer> {
}

7.ProductService

package com.yc.service;

import com.yc.domain.Product;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service
 * @Date 2024/3/4 18:31
 */
public interface ProductService {
    Product findByPid(Integer pid);
}

8.ProductServiceImpl

package com.yc.service.impl;

import com.yc.dao.ProductDao;
import com.yc.domain.Product;
import com.yc.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/4 18:32
 */
@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductDao productDao;

    @Override
    public Product findByPid(Integer pid) {
        return productDao.findById(pid).get();
    }
}

9.启动后查看数据库
在这里插入图片描述
10.写入测试数据

INSERT INTO shop_product VALUE(NULL,'小米','1000','5000');
INSERT INTO shop_product VALUE(NULL,'华为','2000','5000');
INSERT INTO shop_product VALUE(NULL,'苹果','3000','5000');
INSERT INTO shop_product VALUE(NULL,'OPPO','4000','5000');

11.通过浏览器访问服务
http://localhost:18082/product/1
在这里插入图片描述

2.7 shop-order 模块

1.pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-alibaba-02</artifactId>
        <groupId>com.yc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>shop-order</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.yc</groupId>
            <artifactId>shop-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

</project>

2.结构
在这里插入图片描述
3.启动类

package com.yc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc
 * @Date 2024/3/4 18:39
 */
@SpringBootApplication
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    @Bean
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

4.application.yml

server:
  port: 8091
spring:
  application:
    name: service-product
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///hmshop02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 123456
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

5.OrderController

package com.yc.controller;

import com.alibaba.fastjson.JSON;
import com.yc.domain.Order;
import com.yc.domain.Product;
import com.yc.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/4 18:45
 */
@RestController
@Slf4j
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderService orderService;
    //准备买1件商品
    @GetMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info(">>客户下单,这时候要调用商品微服务查询商品信息");
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://localhost:18082/product/" + pid, Product.class);
        log.info(">>商品信息,查询结果:" + JSON.toJSONString(product));
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(product.getPid());
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);
        orderService.save(order);
        return order;
    }
}

6.OrderDao

package com.yc.dao;

import com.yc.domain.Order;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.dao
 * @Date 2024/3/4 18:42
 */
public interface OrderDao extends JpaRepository<Order,Long> {
}

7.OrderService

package com.yc.service;

import com.yc.domain.Order;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service
 * @Date 2024/3/4 18:42
 */
public interface OrderService {
    void save(Order order);
}

8.OrderServiceImpl

package com.yc.service.impl;

import com.yc.dao.OrderDao;
import com.yc.domain.Order;
import com.yc.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/4 18:43
 */
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderDao orderDao;

    @Override
    public void save(Order order) {
        orderDao.save(order);
    }

}

9启动后浏览器访问
http://localhost:8091/order/prod/1
在这里插入图片描述

三 Nacos Discovery–服务治理

什么是服务治理

服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现。

  • 服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服
    务的详细信息。并在注册中心形成一张服务的清单,服务注册中心需要以心跳的方式去监测清单中
    的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。
  • 服务发现:服务调用方向服务注册中心咨询服务,并获取所有服务的实例清单,实现对具体服务实
    例的访问。
    在这里插入图片描述
    通过上面的调用图会发现,除了微服务,还有一个组件是服务注册中心,它是微服务架构非常重要
    的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:
  1. 服务发现:
    服务注册:保存服务提供者和服务调用者的信息
    服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息
  2. 服务配置:
    配置订阅:服务提供者和服务调用者订阅微服务相关的配置
    配置下发:主动将配置推送给服务提供者和服务调用者
  3. 服务健康检测
    检测服务提供者的健康情况,如果发现异常,执行服务剔除

3.1 nacos简介

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速
实现动态服务发现、服务配置、服务元数据及流量管理。
从上面的介绍就可以看出,nacos的作用就是一个注册中心,用来管理注册上来的各个微服务。

3.2 ncaos实战入门

1.搭建nacos环境

第1步: 安装nacos
备注 :以windos系统为例子

下载地址: https://github.com/alibaba/nacos/releases
下载zip格式的安装包,然后进行解压缩操作
官方文档:https://nacos.io/zh-cn/docs/quick-start.html
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第2步:修改配置

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

第3步: 启动nacos
在这里插入图片描述
在这里插入图片描述

第4步: 访问nacos

打开浏览器输入http://localhost:8848/nacos,即可访问服务, 默认密码是nacos/nacos
在这里插入图片描述

2.将所有模块注册到nacos

shop-order 模块 shop-product 模块 shop-user模块

1 在pom.xml中添加nacos的依赖

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

2 在主类上添加@EnableDiscoveryClient注解
在这里插入图片描述
3 在application.yml中添加nacos服务的地址

spring:
	cloud:
		nacos:
			discovery:
				server-addr: 127.0.0.1:8848

在这里插入图片描述
4 启动服务, 观察nacos的控制面板中是否有注册上来的微服务
在这里插入图片描述

3.修改OrderController, 实现微服务调用

修改OrderController, 实现微服务调用
DiscoveryClient是专门负责服务注册和发现的,我们可以通过它获取到注册到注册中心的所有服

OrderController中注入依赖

@Autowired
    private DiscoveryClient discoveryClient;

将通过restTemplate直接调用商品微服务 ,改为通过 nacos中获取服务地址

//从nacos中获取服务地址
        ServiceInstance serviceInstance =
                discoveryClient.getInstances("service-product").get(0);
        String url = serviceInstance.getHost() + ":" +
                serviceInstance.getPort();
        log.info(">>从nacos中获取到的微服务地址为:" + url);
//通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject(
                "http://" + url + "/product/" + pid, Product.class);

        /*
        //通过restTemplate直接调用商品微服务
        Product product = restTemplate.getForObject("http://localhost:18082/product/" + pid, Product.class);
        */

重新启动订单服务,
重启后 访问浏览器 : http://localhost:8091/order/prod/1

在这里插入图片描述

四:Ribbon实现负载均衡

Ribbon是Spring Cloud的一个组件, 它可以让我们使用一个注解就能轻松的搞定负载均衡
链接: Ribbon负载均衡
1.启动两个shop-product 微服务
在这里插入图片描述
-Dserver.port=18084
在这里插入图片描述
在这里插入图片描述
2:在RestTemplate 的生成方法上添加@LoadBalanced注解

package com.yc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc
 * @Date 2024/3/4 18:39
 */
@SpringBootApplication
@EnableDiscoveryClient
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

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

3:修改服务调用的方法

package com.yc.controller;

import com.alibaba.fastjson.JSON;
import com.yc.domain.Order;
import com.yc.domain.Product;
import com.yc.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/4 18:45
 */
@RestController
@Slf4j
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderService orderService;

    @Autowired
    private DiscoveryClient discoveryClient;

    //准备买1件商品
    @GetMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info(">>客户下单,这时候要调用商品微服务查询商品信息");

        //直接使用微服务名字, 从nacos中获取服务地址
        String url = "service-product01";
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
        
        /*
        //从nacos中获取服务地址
        ServiceInstance serviceInstance = discoveryClient.getInstances("service-product01").get(0);
        String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
        log.info(">>从nacos中获取到的微服务地址为:" + url);
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
        */
        /*
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://localhost:18082/product/" + pid, Product.class);
        */
        log.info(">>商品信息,查询结果:" + JSON.toJSONString(product));
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(product.getPid());
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);
        orderService.save(order);
        return order;
    }
}

4.通过修改配置来调整Ribbon的负载均衡策略

# ribbon 实现负载均衡
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机进行负载

5.重启后 访问浏览器
多次范围跟: http://localhost:8091/order/prod/1
shop-order服务日志
在这里插入图片描述
shop-product01服务日志
在这里插入图片描述

shop-product02服务日志
在这里插入图片描述

五:Feign实现服务调用

Feign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务
一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了Feign, Feign默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负
载均衡的效果。
链接: Feign服务调用

1 加入Fegin的依赖

<!--fegin组件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2 在主类上添加Fegin的注解

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

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

3 创建一个service, 并使用Fegin实现微服务调用
在这里插入图片描述

package com.yc.service;

import com.yc.domain.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service
 * @Date 2024/3/5 17:54
 */
@FeignClient("service-product01")//声明调用的提供者的name
public interface ProudService {

    //指定调用提供者的哪个方法
    //@FeignClient+@GetMapping 就是一个完整的请求路径 http://serviceproduct/product/{pid}
    @GetMapping(value = "/product/{pid}")
    Product findByPid(@PathVariable("pid") Integer pid);
    
    
}

4 修改controller代码,并启动验证

package com.yc.controller;

import com.alibaba.fastjson.JSON;
import com.yc.domain.Order;
import com.yc.domain.Product;
import com.yc.service.OrderService;
import com.yc.service.ProudService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/4 18:45
 */
@RestController
@Slf4j
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private OrderService orderService;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private ProudService productService;

    //准备买1件商品
    @GetMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info(">>客户下单,这时候要调用商品微服务查询商品信息");

//通过fegin调用商品微服务
        Product product = productService.findByPid(pid);

        /*
        //直接使用微服务名字, 从nacos中获取服务地址
        String url = "service-product01";
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
        */

        /*
        //从nacos中获取服务地址
        ServiceInstance serviceInstance = discoveryClient.getInstances("service-product01").get(0);
        String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
        log.info(">>从nacos中获取到的微服务地址为:" + url);
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
        */
        /*
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://localhost:18082/product/" + pid, Product.class);
        */
        log.info(">>商品信息,查询结果:" + JSON.toJSONString(product));
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(product.getPid());
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);
        orderService.save(order);
        return order;
    }
}

5 重启order微服务, 浏览器访问查看效果
http://localhost:8091/order/prod/1
在这里插入图片描述

六:Sentinel–服务容错

什么是Sentinel

Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量
为切入点, 从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即
突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用
应用等。
完备的实时监控:Sentinel 提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒
级数据, 甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块, 例如与 Spring
Cloud、Dubbo、gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入
Sentinel
完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快
速地定制逻辑。例如定制规则管理、适配动态数据源等

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo /
    Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等
    应用容器。

链接: Sentinel 服务容错

6.1服务雪崩效应

在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可用。如果一个服务出现了
问题,调用这个服务就会出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等
待,进而导致服务瘫痪。
由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是
服务故障的 “雪崩效应” 。

在这里插入图片描述

雪崩发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某
台机器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问
题,不会影响到其它服务的正常运行。也就是"雪落而不雪崩"。

6.2 常见容错方案

要防止雪崩的扩散,我们就要做好服务的容错,容错说白了就是保护自己不被猪队友拖垮的一些措
施, 下面介绍常见的服务容错思路和组件。

常见的容错思路

常见的容错思路有隔离、超时、限流、熔断、降级这几种,下面分别介绍一下。

隔离
它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故
障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的
系统服务。常见的隔离方式有:线程池隔离和信号量隔离.
在这里插入图片描述
超时
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,
就断开请求,释放掉线程。
在这里插入图片描述

限流
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到
的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
在这里插入图片描述

熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整
体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
在这里插入图片描述
服务熔断一般有三种状态:

  • 熔断关闭状态(Closed)
    服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制
  • 熔断开启状态(Open)
    后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法
  • 半熔断状态(Half-Open)
    尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预
    期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状

    降级
    降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。
    在这里插入图片描述

6.3 集成Sentinel

1 在pom.xml中加入下面依赖

<!--Sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

2 编写一个Controller测试使用
在这里插入图片描述

package com.yc.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/5 18:11
 */
@RestController
@Slf4j
public class OrderControllerTest001 {

    @RequestMapping("/order/message1")
    public String message1() {
        return "message1";
    }

    @RequestMapping("/order/message2")
    public String message2() {
        return "message2";
    }

}

6.4 安装Sentinel控制台

Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能。
1 下载jar包,解压到文件夹
https://github.com/alibaba/Sentinel/releases
在这里插入图片描述
2 启动控制台

监听端口 + sentinel 服务端口地址 + 项目名称 + 运行sentinel的jar包

# 直接使用jar命令启动项目(控制台本身是一个SpringBoot项目)
java -Dserver.port=18080 -Dcsp.sentinel.dashboard.server=localhost:18080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.0.jar

3 修改 shop-order模块配置类 ,在里面加入有关控制台的配置

server:
  port: 8091
spring:
  application:
    name: service-order02
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.93.1:8848
    sentinel:
      transport:
        port: 18081 #跟控制台交流的端口,随意指定一个未使用的端口即可
        dashboard: localhost:18080 # 指定控制台服务的地址
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql:///hmshop02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: 123456
  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

# ribbon 实现负载均衡
service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机进行负载

第4步: 通过浏览器访问 http://localhost:8091/order/message1
在这里插入图片描述

第5步: 通过浏览器访问localhost:18080 进入控制台 ( 默认用户名密码是 sentinel/sentinel )
在这里插入图片描述

补充:了解控制台的使用原理
Sentinel的控制台其实就是一个SpringBoot编写的程序。我们需要将我们的微服务程序注册到控制台上,
即在微服务中指定控制台的地址, 并且还要开启一个跟控制台传递数据的端口, 控制台也可以通过此端口
调用微服务中的监控程序获取微服务的各种信息
在这里插入图片描述

6.5 实现接口限流

1 通过控制台为message1添加一个流控规则
在这里插入图片描述
在这里插入图片描述
2 通过控制台快速频繁访问, 观察效果
在这里插入图片描述

6.6 Sentinel的概念和功能

1.基本概念

资源
资源就是Sentinel要保护的东西
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,可以是一个服务,也可以是
一个方法,甚至可以是一段代码。
我们入门案例中的message1方法就可以认为是一个资源
规则
规则就是用来定义如何进行保护资源的
作用在资源之上, 定义以什么样的方式保护资源,主要包括流量控制规则、熔断降级规则以及系统
保护规则。
我们入门案例中就是为message1资源设置了一种流控规则, 限制了进入message1的流量

2 重要功能

在这里插入图片描述
Sentinel的主要功能就是容错,主要体现为下面这三个:
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是
随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。
Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
熔断降级
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则
对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
Sentinel 对这个问题采取了两种手段:

  • 通过并发线程数进行限制
    Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。当某个资源
    出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆
    积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的
    线程完成任务后才开始继续接收请求。
  • 通过响应时间对资源进行降级
    除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。
    当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的
    时间窗口之后才重新恢复。
  • Sentinel 和 Hystrix 的区别
    两者的原则是一致的, 都是当一个资源出现问题时, 让其快速失败, 不要波及到其它服务
    但是在限制的手段上, 确采取了完全不一样的方法:
    Hystrix 采用的是线程池隔离的方式, 优点是做到了资源之间的隔离, 缺点是增加了线程
    切换的成本。
    Sentinel 采用的是通过并发线程的数量和响应时间来对资源做限制。
    系统负载保护
    Sentinel 同时提供系统维度的自适应保护能力。当系统负载较高的时候,如果还持续让
    请求进入可能会导致系统崩溃,无法响应。在集群环境下,会把本应这台机器承载的流量转发到其
    它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,Sentinel 提供了对应的保
    护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请
    求。
    总之一句话: 我们需要做的事情,就是在Sentinel的资源上配置各种各样的规则,来实现各种容错的功

6.7 Sentinel规则

1 流控规则

流量控制,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时
对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

第1步: 点击簇点链路,我们就可以看到访问过的接口地址,然后点击对应的流控按钮,进入流控规则配
置页面。新增流控规则界面如下:
在这里插入图片描述
资源名:唯一名称,默认是请求路径,可自定义
针对来源:指定对哪个微服务进行限流,默认指default,意思是不区分来源,全部限制
阈值类型/单机阈值:

  • QPS(每秒请求数量): 当调用该接口的QPS达到阈值的时候,进行限流
  • 线程数:当调用该接口的线程数达到阈值的时候,进行限流
    是否集群:暂不需要集群
    接下来我们以QPS为例来研究限流规则的配置。
2 简单配置

我们先做一个简单配置,设置阈值类型为QPS,单机阈值为1。即每秒请求量大于3的时候开始限流。
接下来,在流控规则页面就可以看到这个配置
在这里插入图片描述

然后快速访问 /order/message1 接口,观察效果。此时发现,当QPS > 1的时候,服务就不能正常响
应,而是返回Blocked by Sentinel (flow limiting)结果。

在这里插入图片描述

3.配置流控模式

点击上面设置流控规则的编辑按钮,然后在编辑页面点击高级选项,会看到有流控模式一栏。
在这里插入图片描述
sentinel共有三种流控模式,分别是:

  1. 直接(默认):接口达到限流条件时,开启限流
  2. 关联:当关联的资源达到限流条件时,开启限流 [适合做应用让步]
  3. 链路:当从某个接口过来的资源达到限流条件时,开启限流
    下面呢分别演示三种模式:
    直接流控模式
    直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。上面案例使用的就是直接流控
    模式。
    关联流控模式
    关联流控模式指的是,当指定接口关联的接口达到限流条件时,开启对指定接口开启限流。

链路流控模式
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对
来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度
更细

第1步: 编写一个service,在里面添加一个方法message

@Service
public class OrderServiceImplTest001 {
    @SentinelResource("message")
    public void message() {
        System.out.println("message");
    }
}

第2步: 在Controller中声明两个方法,分别调用service中的方法m

@RestController
@Slf4j
public class OrderControllerTest001 {

    @Autowired
    private OrderServiceImplTest001 orderServiceImplTest001;
    
    @RequestMapping("/order/message1")
    public String message1() {
        orderServiceImplTest001.message();
        return "message1";
    }

    @RequestMapping("/order/message2")
    public String message2() {
        orderServiceImplTest001.message();
        return "message2";
    }

}

第3步: 禁止收敛URL的入口 context
备注:如果使用的版本过低需要配置,如果是最新版本无需配置
从1.6.3 版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在CommonFilter 引入了
WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context。将其配置为 false 即可根据不同的
URL 进行链路限流。
SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即
可关闭收敛
我们当前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE,无法实现链路限流。
目前官方还未发布SCA 2.1.2.RELEASE,所以我们只能使用2.1.1.RELEASE,需要写代码的形式实

配置文件中关闭sentinel的CommonFilter实例化

server:
  port: 8091
spring:
  application:
    name: service-order02
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.93.1:8848
    sentinel:
      transport:
        port: 9999 # 跟控制台交流的端口,随意指定一个未使用的端口即可
        dashboard: localhost:18080 #指定控制台服务的地址
      filter:
        enabled: false

添加一个配置类,自己构建CommonFilter实例

package com.itheima.config;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterContextConfig {
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
// 入口资源关闭聚合
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}

第4步:配置完重启服务。浏览器分别访问
http://localhost:8091/order/message1
http://localhost:8091/order/message2

第5步: 控制台配置限流规则

在这里插入图片描述
在这里插入图片描述
第6步: 分别通过 /order/message1 和 /order/message2 访问, 发现关联的接口资源被限流了
在这里插入图片描述

4 配置流控效果

快速失败(默认): 直接失败,抛出异常,不做任何额外的处理,是最简单的效果
Warm Up:它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的
1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。
排队等待:让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设
置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。

5 降级规则

降级规则就是设置当满足什么条件的时候,对服务进行降级。Sentinel提供了三个衡量条件:

  1. 慢调用比例 :(SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。
    设置熔断策略,1s 的 QPS>5 (最小请求数)并且这些请求的RT>1(即最大的响应时间 默认是 4900ms ) 并且大于比例阈值1(100%)触发熔断
    在这里插入图片描述
    经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  2. 异常比例:异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
    注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
    int i = 0;
    @RequestMapping("/order/message2")
    public String message2() {
//        orderServiceImplTest001.message();
        i++;
        //异常比例为0.333
        if (i % 3 == 0){
            throw new RuntimeException();
        }

        return "message2";
    }

在这里插入图片描述
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

  1. 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。
    设置异常数策略,当1秒钟内请求超过2并且异常数大于1个的时候触发熔断
    在这里插入图片描述
    经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
6 热点规则

热点参数流控规则是一种更细粒度的流控规则, 它允许将规则具体到参数上。

热点规则简单使用
第1步: 编写代码

@RequestMapping("/order/message3")
@SentinelResource("message3")//注意这里必须使用这个注解标识,热点规则不生效
public String message3(String name, Integer age) {
return name + age;
}

第2步: 配置热点规则
在这里插入图片描述
第3步: 分别用两个参数访问,会发现对配置对应下标的参数限流了
在这里插入图片描述
热点规则增强使用
参数例外项允许对一个参数的具体值进行流控
编辑刚才定义的规则,增加参数例外项
根据参数类型 添加对应值 如age=15
在这里插入图片描述

7 授权规则

很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源
访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:

  • 若配置白名单,则只有请求来源位于白名单内时才可通过;
  • 若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过。
    在这里插入图片描述
    上面的资源名和授权类型不难理解,但是流控应用怎么填写呢?
    其实这个位置要填写的是来源标识,Sentinel提供了 RequestOriginParser 接口来处理来源。
    只要Sentinel保护的接口资源被访问,Sentinel就会调用 RequestOriginParser 的实现类去解析
    访问来源
    在这里插入图片描述
package com.yc.config;

import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.config
 * @Date 2024/3/6 21:36
 */
@Component
public class RequestOriginParserDefinition implements RequestOriginParser {

    //定义区分来源: 本质作用是通过request域获取到来源标识  然后交给流程控制应用  位置进行匹配
    @Override
    public String parseOrigin(HttpServletRequest request) {
        String serviceName = request.getParameter("serviceName");
        if (StringUtils.isEmpty(serviceName)){
            throw new RuntimeException("serviceName is not empty");
        }
        return serviceName;
    }
}

白名单:根据RequestOriginParser 实现类的 parseOrigin 里面进行的校验 如果与流程配置中的值一样 则可以访问,其他均不能访问
黑名单:根据RequestOriginParser 实现类的 parseOrigin 里面进行的校验 如果与流程配置中的值一样 则不能访问 ,其他均能访问

8 系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、CPU
使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。

  1. Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过
    系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般
    是 CPU cores * 2.5
  2. RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒
  3. 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  4. 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护
  5. CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。

扩展: 自定义异常返回
在这里插入图片描述

package com.yc.config;

import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.config
 * @Date 2024/3/6 21:49
 */
//自定义异常处理页面
//@Component
public class ExceptionHandlerPage implements UrlBlockHandler {
    //BlockException 异常接口,包含Sentinel的五个异常
    // FlowException 限流异常
    // DegradeException 降级异常
    // ParamFlowException 参数限流异常
    // AuthorityException 授权异常
    // SystemBlockException 系统负载异常
    @Override
    public void blocked(HttpServletRequest request, HttpServletResponse
            response, BlockException e) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        ResponseData data = null;
        if (e instanceof FlowException) {
            data = new ResponseData(-1, "接口被限流了...");
        } else if (e instanceof DegradeException) {
            data = new ResponseData(-2, "接口被降级了...");
        }else if (e instanceof ParamFlowException) {
            data = new ResponseData(-3, "参数限流异常...");
        }else if (e instanceof AuthorityException) {
            data = new ResponseData(-4, "授权异常...");
        }else if (e instanceof SystemBlockException) {
            data = new ResponseData(-5, "系统负载异常...");
        }
        response.getWriter().write(JSON.toJSONString(data));
    }

}

@Data
@AllArgsConstructor//全参构造
@NoArgsConstructor//无参构造
class ResponseData {
    private int code;
    private String message;
}
9 @SentinelResource的使用

在定义了资源点之后,我们可以通过Dashboard来设置限流和降级策略来对资源点进行保护。同时还能
通过@SentinelResource来指定出现异常时的处理策略。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。其主要参数如下:
|属性| 作用 |
|value|资源名称|
| entryType | entry类型,标记流量的方向,取值IN/OUT,默认是OUT |
| blockHandler | 处理BlockException的函数名称,函数要求:

  1. 必须是 public
    2.返回类型 参数与原方法一致
  2. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置
    blockHandlerClass ,并指定blockHandlerClass里面的方法。 |
    | blockHandlerClass | 存放blockHandler的类,对应的处理函数必须static修饰。|
    |fallback | 用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所
    有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进
    行处理。函数要求:
  3. 返回类型与原方法一致
  4. 参数类型需要和原方法相匹配
  5. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置
    fallbackClass ,并指定fallbackClass里面的方法。 |
    | fallbackClass | 存放fallback的类。对应的处理函数必须static修饰 |
    | defaultFallback | 用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进
    行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函
    数要求:
  6. 返回类型与原方法一致
  7. 方法参数列表为空,或者有一个 Throwable 类型的参数。
  8. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置
    fallbackClass ,并指定 fallbackClass 里面的方法 |
    |exceptionsToIgnore | 指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入
    fallback逻辑,而是原样抛出。 |
    | exceptionsToTrace | 需要trace的异常 |

定义限流和降级后的处理方法

方式一:直接将限流和降级方法定义在方法中

package com.yc.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/7 10:06
 */
@Service
@Slf4j
public class OrderServiceImpl00301 {


    int i = 0;
    /*
    //定义一个资源
    //定义当资源内部发生异常的时候的处理逻辑
    //blockHandler  定义当资源内部发生了BlockException应该进入的方法[捕获的是Sentinel定义的异常]
    //fallback      定义当资源内部发生了Throwable应该进入的方法
    */
    @SentinelResource(
            value = "message",
            blockHandler = "blockHandler",//指定发生BlockException时进入的方法
            fallback = "fallback"//指定发生Throwable时进入的方法
    )
    public String message() {
        i++;
        if (i % 3 == 0) {
            throw new RuntimeException();
        }
        return "message";
    }
    /*
    //BlockException时进入的方法
     //blockHandler
    //要求:
    //1 当前方法的返回值和参数要跟原方法一致
    //2 但是允许在参数列表的最后加入一个参数BlockException, 用来接收原方法中发生的异常
    */
    public String blockHandler(BlockException ex) {
        //自定义异常处理逻辑
        log.error("触发了BlockException,内容为{}", ex);
        return "BlockException:::接口被限流或者降级了...";
    }
    /*
    //Throwable时进入的方法
     //fallback
    //要求:
    //1 当前方法的返回值和参数要跟原方法一致
    //2 但是允许在参数列表的最后加入一个参数BlockException, 用来接收原方法中发生的异常
    */
    public String fallback(Throwable throwable) {
        //自定义异常处理逻辑
        log.error("触发了Throwable,内容为{}", throwable);
        return "Throwable::接口发生异常了...";
    }
}


  @Autowired
    private OrderServiceImpl00301 orderServiceImpl00301;

 
  @RequestMapping("/order/message4")
    public String message4() {
        return orderServiceImpl00301.message();
    }

方式二: 将限流和降级方法外置到单独的类中

package com.yc.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/7 10:37
 */
@Service
@Slf4j
public class OrderServiceImpl00302 {
    int i = 0;
    /*
    //定义一个资源
    //定义当资源内部发生异常的时候的处理逻辑
    //blockHandler  定义当资源内部发生了BlockException应该进入的方法[捕获的是Sentinel定义的异常]
    //fallback      定义当资源内部发生了Throwable应该进入的方法
    */
    @SentinelResource(
            value = "message",
            blockHandlerClass = OrderServiceImpl00302BlockHandler.class, // 指定发生BlockException时进入的方法所在的类
            blockHandler = "blockHandler",//指定发生BlockException时进入的方法
            fallbackClass = OrderServiceImpl00302Fallback.class,//指定发生Throwable时进入的方法所在的类
            fallback = "fallback"//指定发生Throwable时进入的方法
    )
    public String message() {
        i++;
        if (i % 3 == 0) {
            throw new RuntimeException();
        }
        return "message";
    }

}

package com.yc.service.impl;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/7 10:38
 */
//OrderServiceImpl3对应的BlockException处理的类
@Slf4j
public class OrderServiceImpl00302BlockHandler {
    //blockHandler
    //要求:
    //1 当前方法的返回值和参数要跟原方法一致
    //2 但是允许在参数列表的最后加入一个参数BlockException, 用来接收原方法中发生的异常
    public static String blockHandler(String name, BlockException e) {
        //自定义异常处理逻辑
        log.error("触发了BlockException,内容为{}", e);
        return "BlockException";
    }
}

package com.yc.service.impl;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/7 10:39
 */
//OrderServiceImpl3对应的Throwable处理的类
@Slf4j
public class OrderServiceImpl00302Fallback {

    //fallback
    //要求:
    //1 当前方法的返回值和参数要跟原方法一致
    //2 但是允许在参数列表的最后加入一个参数BlockException, 用来接收原方法中发生的异常
    public static String fallback(String name, Throwable e) {
        //自定义异常处理逻辑
        log.error("触发了Throwable,内容为{}", e);
        return "Throwable";
    }
}

备注:两种方式根据需求选其一即可

10 Sentinel规则持久化

我们已经知道,可以通过Dashboard来为每个Sentinel客户端设置各种各样的规
则,但是这里有一个问题,就是这些规则默认是存放在内存中,极不稳定,所以需要将其持久化。
本地文件数据源会定时轮询文件的变更,读取规则。这样我们既可以在应用本地直接修改文件来更
新规则,也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:
在这里插入图片描述
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的
规则保存到本地的文件中

1 编写处理类
在这里插入图片描述

package com.yc.config;

import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
import com.alibaba.csp.sentinel.datasource.*;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.config
 * @Date 2024/3/7 10:47
 */
//规则持久化
public class FilePersistence implements InitFunc {

    @Value("spring.application:name")
    private String appcationName;


    @Override
    public void init() throws Exception {
//        String ruleDir = System.getProperty("user.home") + "/sentinelrules/" + appcationName;
        String ruleDir = "E:/SpringCLoudAlibaba/sentinel"+"/sentinel-rules/" + "service-order02";//这里那不到配置文件中的值 直接写死 自定义目录
        String flowRulePath = ruleDir + "/flow-rule.json";
        String degradeRulePath = ruleDir + "/degrade-rule.json";
        String systemRulePath = ruleDir + "/system-rule.json";
        String authorityRulePath = ruleDir + "/authority-rule.json";
        String paramFlowRulePath = ruleDir + "/param-flow-rule.json";

        this.mkdirIfNotExits(ruleDir);
        this.createFileIfNotExits(flowRulePath);
        this.createFileIfNotExits(degradeRulePath);
        this.createFileIfNotExits(systemRulePath);
        this.createFileIfNotExits(authorityRulePath);
        this.createFileIfNotExits(paramFlowRulePath);

        // 流控规则
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                flowRuleListParser
        );
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        WritableDataSource<List<FlowRule>> flowRuleWDS = new FileWritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);

        // 降级规则
        ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS = new FileRefreshableDataSource<>(
                degradeRulePath,
                degradeRuleListParser
        );
        DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
        WritableDataSource<List<DegradeRule>> degradeRuleWDS = new FileWritableDataSource<>(
                degradeRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);

        // 系统规则
        ReadableDataSource<String, List<SystemRule>> systemRuleRDS = new FileRefreshableDataSource<>(
                systemRulePath,
                systemRuleListParser
        );
        SystemRuleManager.register2Property(systemRuleRDS.getProperty());
        WritableDataSource<List<SystemRule>> systemRuleWDS = new FileWritableDataSource<>(
                systemRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);

        // 授权规则
        ReadableDataSource<String, List<AuthorityRule>> authorityRuleRDS = new FileRefreshableDataSource<>(
                authorityRulePath,
                authorityRuleListParser
        );
        AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
        WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new FileWritableDataSource<>(
                authorityRulePath,
                this::encodeJson
        );
        WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);

        // 热点参数规则
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleRDS = new FileRefreshableDataSource<>(
                paramFlowRulePath,
                paramFlowRuleListParser
        );
        ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
        WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new FileWritableDataSource<>(
                paramFlowRulePath,
                this::encodeJson
        );
        ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
    }

    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<FlowRule>>() {
            }
    );
    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<DegradeRule>>() {
            }
    );
    private Converter<String, List<SystemRule>> systemRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<SystemRule>>() {
            }
    );

    private Converter<String, List<AuthorityRule>> authorityRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<AuthorityRule>>() {
            }
    );

    private Converter<String, List<ParamFlowRule>> paramFlowRuleListParser = source -> JSON.parseObject(
            source,
            new TypeReference<List<ParamFlowRule>>() {
            }
    );

    private void mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }


}


2 添加配置
在resources下创建配置目录 META-INF/services ,然后添加文件
com.alibaba.csp.sentinel.init.InitFunc
在文件中添加配置类的全路径:

com.yc.config.FilePersistence

6.8 Feign整合Sentinel

第1步: 引入sentinel的依赖

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

第2步: 在配置文件中开启Feign对Sentinel的支持

# 开启feign对sentinel的支持
feign:
  sentinel:
    enabled: true

第3步: 创建容错类
在这里插入图片描述

package com.yc.service.fallback;

import com.yc.domain.Product;
import com.yc.service.ProudService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.fallback
 * @Date 2024/3/7 13:02
 */
//容错类要求必须实现被容错的接口,并为每个方法实现容错方案
@Component
@Slf4j
public class ProductServiceFallBack implements ProudService {

    @Override
    public Product findByPid(Integer pid) {
        Product product = new Product();
        product.setPid(-1);
        return product;
    }

}

第4步: 为被容器的接口指定容错类

package com.yc.service;

import com.yc.domain.Product;
import com.yc.service.fallback.ProductServiceFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service
 * @Date 2024/3/5 17:54
 */
//value用于指定调用nacos下哪个微服务
//fallback 指定当调用出现问题之后,要进入到哪个类中的同名方法之下执行备用逻辑
//@FeignClient("service-product01")//声明调用的提供者的name
@FeignClient(value = "service-product01", fallback = ProductServiceFallBack.class)
public interface ProudService {

    //指定调用提供者的哪个方法
    //@FeignClient+@GetMapping 就是一个完整的请求路径 http://serviceproduct/product/{pid}
    @GetMapping(value = "/product/{pid}")
    Product findByPid(@PathVariable("pid") Integer pid);


}

第5步: 修改controller

 //准备买1件商品
    @GetMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info(">>客户下单,这时候要调用商品微服务查询商品信息");

        //通过fegin调用商品微服务
        Product product = productService.findByPid(pid);

        //处理容错类返回结果
        if (product.getPid() == -1) {
            Order order = new Order();
            order.setPname("下单失败");
            return order;
        }

        /*
        //直接使用微服务名字, 从nacos中获取服务地址
        String url = "service-product01";
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
        */

        /*
        //从nacos中获取服务地址
        ServiceInstance serviceInstance = discoveryClient.getInstances("service-product01").get(0);
        String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
        log.info(">>从nacos中获取到的微服务地址为:" + url);
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://" + url + "/product/" + pid, Product.class);
        */
        /*
        //通过restTemplate调用商品微服务
        Product product = restTemplate.getForObject("http://localhost:18082/product/" + pid, Product.class);
        */
        log.info(">>商品信息,查询结果:" + JSON.toJSONString(product));
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(product.getPid());
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);
        orderService.save(order);
        return order;
    }

第6步: 停止所有 shop-product 服务,重启 shop-order 服务,访问请求,观察容错效果
在这里插入图片描述
扩展: 如果想在容错类中拿到具体的错误,可以使用下面的方式

在这里插入图片描述

package com.yc.service;

import com.yc.domain.Product;
import com.yc.service.fallback.ProductServiceFallBack;
import com.yc.service.fallback.ProductServiceFallBackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service
 * @Date 2024/3/5 17:54
 */
//value用于指定调用nacos下哪个微服务
//fallback 指定当调用出现问题之后,要进入到哪个类中的同名方法之下执行备用逻辑
//@FeignClient("service-product01")//声明调用的提供者的name
//@FeignClient(value = "service-product01", fallback = ProductServiceFallBack.class)
@FeignClient(value = "service-product01", fallbackFactory = ProductServiceFallBackFactory.class)
public interface ProudService {

    //指定调用提供者的哪个方法
    //@FeignClient+@GetMapping 就是一个完整的请求路径 http://serviceproduct/product/{pid}
    @GetMapping(value = "/product/{pid}")
    Product findByPid(@PathVariable("pid") Integer pid);


}

package com.yc.service.fallback;

import com.yc.domain.Product;
import com.yc.service.ProudService;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.fallback
 * @Date 2024/3/7 13:13
 */
@Component
@Slf4j
public class ProductServiceFallBackFactory implements FallbackFactory<ProudService> {
    //Throwable  这就是fegin在调用过程中产生异常
    @Override
    public ProudService create(Throwable throwable) {
        return new ProudService() {
            @Override
            public Product findByPid(Integer pid) {
                log.error("{}",throwable);
                Product product = new Product();
                product.setPid(-100);
                product.setPname("商品微服务调用出现异常了,已经进入到了容错方法中");
                return product;
            }
        };
    }
}

注意: fallback和fallbackFactory只能使用其中一种方式

七:Gateway网关

7.1 网关简介

大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用
这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调
用。
在这里插入图片描述
在业界比较流行的网关,有下面这些:

  1. Ngnix+lua
    使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用
    lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
  2. Kong
    基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:
    只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
  3. Zuul Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配
    置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx
  4. Spring Cloud Gateway
    Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍
    注意:SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway
    来做网关

7.2 Gateway简介

Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术
开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代
Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安
全,监控和限流。

优点:

  1. 性能强劲:是第一代网关Zuul的1.6倍
  2. 功能强大:内置了很多实用的功能,例如转发、监控、限流等
  3. 设计优雅,容易扩展

缺点:

  1. 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高
  2. 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行
  3. 需要Spring Boot 2.0及以上的版本,才支持

7.3 Gateway快速入门

要求: 通过浏览器访问api网关,然后通过网关将请求转发到各个微服务

基础版

在这里插入图片描述

第1步:创建一个 api-gateway 的模块,导入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-alibaba-02</artifactId>
        <groupId>com.yc</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api-geteway</artifactId>

    <dependencies>
        <!--gateway网关   此模块不能引入starter-web-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
</project>

第2步: 创建主类

package com.yc;

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

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc
 * @Date 2024/3/7 13:28
 */
@SpringBootApplication
public class GatewayApplication {

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

第3步: 添加配置文件


server:
  port: 7000
spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]转发过程中还可以动一些手脚
        - id: product_route # 当前路由的标识, 要求唯一,默认是UUID
          uri: http://localhost:8081 # 请求要转发到的地址
          order: 1 # 路由的优先级,数字越小级别越高
          predicates: # 断言(就是路由转发要满足的条件 返回值是boolean,转发请求要满足的条件)
            - Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
          filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
            - StripPrefix=1 # 转发之前去掉1层路径

第4步: 启动项目, 并通过网关去访问微服务
网关服务ip+端口/自定义需要调用的服务名/微服务路径
http://localhost:7000/product-serv/product/1
在这里插入图片描述

增强版

现在在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从
注册中心获取此地址。

第1步:加入nacos依赖

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

第2步:在主类上添加注解

package com.yc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc
 * @Date 2024/3/7 13:28
 */
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {

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

第3步:修改配置文件


server:
  port: 7000
spring:
  application:
    name: api-gateway01
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true # 让gateway可以发现nacos中的微服务
      routes:
        - id: product_route
          uri: lb://service-product01 # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
          predicates:
            - Path=/product-serv/**
          filters:
            - StripPrefix=1

第4步:测试
网关服务ip+端口/自定义需要调用的服务名/微服务路径
http://localhost:7000/product-serv/product/1

在这里插入图片描述

简写版

第1步: 去掉关于路由的配置

server:
  port: 7000
spring:
  application:
    name: api-gateway01
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true

第2步: 启动项目,并通过网关去访问微服务
网关服务ip+端口/需要调用的服务名(nacos注册的服务名)/微服务路径
http://localhost:7000/service-product01/product/1
在这里插入图片描述
这时候,就发现只要按照网关地址/微服务/接口的格式去访问,就可以得到成功响应。

7.4 Gateway核心架构

基本概念

路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个
信息

  1. id,路由标识符,区别于其他 Route。
  2. uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
  3. order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高
  4. predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
  5. filter,过滤器用于修改请求和响应信息。
执行流程

执行流程大体如下:

  1. Gateway Client向Gateway Server发送请求
  2. 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
  3. 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给
    RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
  5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用
  6. 请求会一次经过PreFilter–微服务–PostFilter的方法,最终返回响应

7.5 断言

Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。
断言就是说: 在 **什么条件下 **才能进行路由转发

内置路由断言工厂
  1. 基于Datetime类型的断言工厂
    此类型的断言根据时间做判断,主要有三个:
    AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期
    BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期
    BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内
    -After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
  2. 基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主
    机地址是否在地址段中
    -RemoteAddr=192.168.1.1/24
  3. 基于Cookie的断言工厂
    CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求
    cookie是否具有给定名称且值与正则表达式匹配。
    -Cookie=chocolate, ch
  4. 基于Header的断言工厂
    HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否
    具有给定名称且值与正则表达式匹配。
    -Header=X-Request-Id, \d+
  5. 基于Host的断言工厂
    HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。
    -Host=**.testhost.org
  6. 基于Method请求方法的断言工厂
    MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
    -Method=GET
  7. 基于Path请求路径的断言工厂
    PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。
    -Path=/foo/{segment}
  8. 基于Query请求参数的断言工厂
    QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具
    有给定名称且值与正则表达式匹配。
    -Query=baz, b
  9. 基于路由权重的断言工厂
    WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发
    routes:
    -id: weight_route1 uri: host1 predicates:
    -Path=/product/**
    -Weight=group3, 1
    -id: weight_route2 uri: host2 predicates:
    -Path=/product/**
    -Weight= group3, 9

内置路由断言工厂的使用
自行验证即可

server:
port: 7000
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: product_route
uri: lb://service-product
predicates:
- Path=/product-serv/**
- Before=2019-11-28T00:00:00.000+08:00 #限制请求时间在2019-11-28之前
- Method=POST #限制请求方式为POST
filters:
- StripPrefix=1
自定义路由断言工厂

我们来设定一个场景: 假设我们的应用仅仅让age在(min,max)之间的人来访问。
第1步:在配置文件中,添加一个Age的断言配置


server:
  port: 7000
spring:
  application:
    name: api-gateway01
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: product_route
          uri: lb://service-product01
          predicates:
            - Path=/service-product03/** #网关ip+网关端口后第一个路径
            - Name=18,60
          filters:
            - StripPrefix=1

第2步:自定义一个断言工厂, 实现断言方法

在这里插入图片描述

package com.yc.predicates;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import javax.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.predicates
 * @Date 2024/3/7 17:53
 */
//泛型 用于接收一个配置类,配置类用于接收中配置文件中的配置
//1 名字必须是 配置+RoutePredicateFactory
//2 必须继承AbstractRoutePredicateFactory<配置类>
@Component
public class NameRoutePredicateFactory  extends AbstractRoutePredicateFactory<NameRoutePredicateFactory.Config>  {


    public static final String DATETIME_KEY = "datetime";

    public NameRoutePredicateFactory() {
        super(NameRoutePredicateFactory.Config.class);
    }

    //用于从配置文件中获取参数值赋值到配置类中的属性上
    @Override
    public List<String> shortcutFieldOrder() {
        //这里的顺序要跟配置文件中的参数顺序一致
        return Arrays.asList("minAge", "maxAge");
    }

    @Override
    public Predicate<ServerWebExchange> apply(NameRoutePredicateFactory.Config config) {
        System.out.println("1111111111");
        return exchange -> {
            //1 接收前台传入的断言参数
            String name = exchange.getRequest().getQueryParams().getFirst("name");
            System.out.println("name::::"+name);
            //2 先判断是否为空
            if (name != null && !name.isEmpty()) {
                int age = Integer.parseInt(name);
                //3 如果不为空,再进行路由逻辑判断
                if (age >= config.getMinAge() && age <= config.getMaxAge()) {
                    return true; //允许访问
                } else {
                    return false; //不允许访问
                }
            } else {
                return false; //不允许访问
            }
        };
    }

    //自定义一个配置类, 用于接收配置文件中的参数
    @Data
    @NoArgsConstructor
    public static class Config {
        private int minAge;
        private int maxAge;
    }
}

第3步:启动测试

#网关ip+网关端口+Path+接口路径?name=19
http://localhost:7000/service-product03/product/1?name=19
http://localhost:7000/service-product03/product/1?name=190

7.6 过滤器

三个知识点:

  1. 作用: 过滤器就是在请求的传递过程中,对请求和响应做一些手脚
  2. 生命周期: Pre Post
  3. 分类: 局部过滤器(作用在某一个路由上) 全局过滤器(作用全部路由上)

在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。

  1. PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择
    请求的微服务、记录调试信息等。
  2. POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP
    Header、收集统计信息和指标、将响应从微服务发送给客户端等

Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。

  1. GatewayFilter:应用到单个路由或者一个分组的路由上。
  2. GlobalFilter:应用到所有的路由上。
局部过滤器

局部过滤器是针对单个路由的过滤器。

内置局部过滤器

在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。具体如下:

过滤器工厂作用参数
AddRequestHeader为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名
称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名
FallbackHeaders为fallbackUri的请求头中添加具
体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个
preserveHostHeader=true的属
性,路由过滤器会检查该属性以
决定是否要发送原始的Host
RequestRateLimiter用于对请求限流,限流算法为令
牌桶keyResolver、
rateLimiter、
statusCode、
denyEmptyKey、
emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的
url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的
一系列Header默认就会启用,可以通
过配置指定仅删除哪些
Header
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RewritePath重写原始的请求路径原始路径正则表达式以
及重写后路径的正则表
达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正
则表达式,重写后的值
SaveSession在转发请求之前,强制执行
WebSession::save操作
secureHeaders为原始响应添加一系列起安全作
用的响应头无,支持修改这些安全
响应头的值
SetPath修改原始的请求路径修改后的路径
电SetResponseHeader修改原始响应中某个Header的值Header名称,修改后
的值
SetStatus修改原始响应的状态码HTTP 状态码,可以是
数字,也可以是字符串
StripPrefix用于截断原始请求的路径使用数字表示要截断的
路径的数量
Retry针对不同的响应进行重试retries、statuses、
methods、series
RequestSize设置允许接收最大请求包的大
小。如果请求包大小超过设置的
值,则返回 413 Payload Too
Large请求包大小,单位为字
节,默认值为5M
ModifyRequestBody在转发请求之前修改原始请求体
内容修改后的请求体内容
ModifyResponseBody修改原始响应体的内容修改后的响应体内容
自定义局部过滤器

第1步:在配置文件中,添加一个Log的过滤器配置

server:
  port: 7000
spring:
  application:
    name: api-gateway01
#  zipkin:
#    base-url: http://127.0.0.1:9411/ #zipkin server的请求地址
#    discoveryClientEnabled: false #让nacos把它当成一个URL,而不要当做服务名
#  sleuth:
#    sampler:
#      probability: 1.0 #采样的百分比
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: product_route
          uri: lb://service-product01
          order: 1
          predicates:
            - Path=/service-product0101/**
          #            - Age=18,60 # 限制年龄只有在18到60岁之间的人能访问
          filters:
            - StripPrefix=1
#            - SetStatus=2000 # 修改返回状态
#            - Log=true,false # 控制日志是否开启
        - id: order_route
          uri: lb://service-order02
          order: 1
          predicates:
            - Path=/service-order0201/**
          #            - Age=18,60 # 限制年龄只有在18到60岁之间的人能访问
          filters:
            - StripPrefix=1
            - SetStatus=2000 # 修改返回状态
            - Log=true,false # 控制日志是否开启

第2步:自定义一个过滤器工厂,实现方法

package com.yc.predicates;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.predicates
 * @Date 2024/3/14 9:55
 */
//自定义局部过滤器
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config> {

    //构造函数
    public LogGatewayFilterFactory() {
        super(LogGatewayFilterFactory.Config.class);
    }

    //读取配置文件中的参数 赋值到 配置类中
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("consoleLog", "cacheLog");
    }

    //过滤器逻辑
    @Override
    public GatewayFilter apply(LogGatewayFilterFactory.Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange,
                                     GatewayFilterChain chain) {
                if (config.isCacheLog()) {
                    System.out.println("cacheLog已经开启了....");
                }
                if (config.isConsoleLog()) {
                    System.out.println("consoleLog已经开启了....");
                }
                return chain.filter(exchange);
            }
        };
    }


    //配置类 接收配置参数
    @Data
    @NoArgsConstructor
    public static class Config {
        private boolean consoleLog;
        private boolean cacheLog;
    }

}
全局过滤器

全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功

内置全局过滤器

在这里插入图片描述

自定义全局过滤器

内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们
自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的权限校验。
开发中的鉴权逻辑:

  1. 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  2. 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  3. 以后每次请求,客户端都携带认证的token
  4. 服务端对token进行解密,判断是否有效。

在这里插入图片描述
如上图,对于验证用户是否已经登录鉴权的过程可以在网关统一检验。
检验的标准就是请求中是否携带token凭证以及token的正确性。
下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求
参数“token”则不转发路由,否则执行正常的逻辑

package com.yc.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.filter
 * @Date 2024/3/14 10:10
 */
//自定义全局过滤器(作用是统一鉴权)需要实现GlobalFilter和Ordered接口
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    //完成判断逻辑
    //过滤器逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isBlank(token)) {
//        if (!StringUtils.equals("admin",token)) {
            //认证失败
            log.info("认证失败......");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        //放行 调用chain.filter继续向下游执行
        return chain.filter(exchange);
    }

    //标识当前过滤器的优先级 顺序,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;

    }
}

在这里插入图片描述

8 网关限流

网关是所有请求的公共入口,所以可以在网关进行限流,而且限流的方式也很多,我们本次采用前
面学过的Sentinel组件来实现网关的限流。Sentinel支持对SpringCloud Gateway、Zuul等主流网关进
行限流。
在这里插入图片描述
从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:

  • route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeId
  • 自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组

1 导入依赖

        <!--  限流   -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
        </dependency>

2 编写配置类
基于Sentinel 的Gateway限流是通过其提供的Filter来完成的,使用时只需注入对应的
SentinelGatewayFilter实例以及 SentinelGatewayBlockExceptionHandler 实例即可

package com.yc.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.config
 * @Date 2024/3/14 10:20
 */
@Configuration
public class GatewayConfiguration {
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;

    public GatewayConfiguration(ObjectProvider<List<ViewResolver>>
                                        viewResolversProvider,
                                ServerCodecConfigurer serverCodecConfigurer) {
        this.viewResolvers =
                viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    // 初始化一个限流的过滤器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public GlobalFilter sentinelGatewayFilter() {
        return new SentinelGatewayFilter();
    }

    // 配置初始化的限流参数
    @PostConstruct
    public void initGatewayRules() {
        Set<GatewayFlowRule> rules = new HashSet<>();
        rules.add(
                new GatewayFlowRule("service-product0101") //资源名称,对应路由id
                        .setCount(1) // 限流阈值
                        .setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
        );
        GatewayRuleManager.loadRules(rules);
//        Set<GatewayFlowRule> rules = new HashSet<>();
//        rules.add(new
//                GatewayFlowRule("product_api1").setCount(1).setIntervalSec(1));
//        rules.add(new
//                GatewayFlowRule("product_api2").setCount(1).setIntervalSec(1));
//        GatewayRuleManager.loadRules(rules);
    }

    // 配置限流的异常处理器
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SentinelGatewayBlockExceptionHandler
    sentinelGatewayBlockExceptionHandler() {
        return new SentinelGatewayBlockExceptionHandler(viewResolvers,
                serverCodecConfigurer);
    }

    // 自定义限流异常页面
    @PostConstruct
    public void initBlockHandlers() {
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
                Map map = new HashMap<>();
                map.put("code", 0);
                map.put("message", "接口被限流了");
                return ServerResponse.status(HttpStatus.OK).
                        contentType(MediaType.APPLICATION_JSON_UTF8).

                        body(BodyInserters.fromObject(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }

//    //自定义API分组
//    @PostConstruct
//    private void initCustomizedApis() {
//        Set<ApiDefinition> definitions = new HashSet<>();
//        ApiDefinition api1 = new ApiDefinition("product_api1")
//                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
 以/product-serv/product/api1 开头的请求
//                    add(new ApiPathPredicateItem().setPattern("/service-product0101/product/api1/**").
//                            setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
//                }});
//        ApiDefinition api2 = new ApiDefinition("product_api2")
//                .setPredicateItems(new HashSet<ApiPredicateItem>() {{
 以/product-serv/product/api2/demo1 完成的url路径匹配
//                    add(new ApiPathPredicateItem().setPattern("/service-order0201/product/api2/demo1"));
//                }});
//        definitions.add(api1);
//        definitions.add(api2);
//        GatewayApiDefinitionManager.loadApiDefinitions(definitions);
//    }
}

3 测试
在一秒钟内多次访问http://localhost:7000/service-product0101/product/1就可以看到限流启作用了。
在这里插入图片描述
4 自定义API分组

自定义API分组是一种更细粒度的限流规则定义

准备分组接口
在这里插入图片描述

   @RequestMapping("/product/api1/demo1")
    public String demo1() {
        return "demo11";
    }

    @RequestMapping("/product/api1/demo2")
    public String demo2() {
        return "demo12";
    }

    @RequestMapping("/product/api2/demo1")
    public String demo3() {
        return "demo21";
    }

    @RequestMapping("/product/api2/demo2")
    public String demo4() {
        return "demo22";
    }

5.重启后 浏览器访问
http://localhost:7000/service-product0101/product/api1/demo1
http://localhost:7000/service-product0101/product/api1/demo2
http://localhost:7000/service-product0101/product/api2/demo1
http://localhost:7000/service-product0101/product/api2/demo2

前三个会被限流 ,最后一个不会限流
在这里插入图片描述

八 Sleuth–链路追踪

1. 链路追踪介绍

在大型系统的微服务化构建中,一个系统被拆分成了许多模块。这些模块负责不同的功能,组合成
系统,最终可以提供丰富的功能。在这种架构中,一次请求往往需要涉及到多个服务。互联网应用构建
在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实
现、有可能布在了几千台服务器,横跨多个不同的数据中心,也就意味着这种架构形式也会存在一些问
题:

  1. 如何快速发现问题?
  2. 如何判断故障影响范围?
  3. 如何梳理服务依赖以及依赖的合理性?
  4. 如何分析链路性能问题以及实时容量规划?

在这里插入图片描述

分布式链路追踪(Distributed Tracing),就是将一次分布式请求还原成调用链路,进行日志记
录,性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节点上的耗时、请求具体到达哪
台机器上、每个服务节点的请求状态等等。

  1. ** cat **由大众点评开源,基于Java开发的实时应用监控平台,包括实时应用监控,业务监控 。 集成
    方案是通过代码埋点的方式来实现监控,比如: 拦截器,过滤器等。 对代码的侵入性很大,集成
    成本较高。风险较大
  2. **zipkin **由Twitter公司开源,开放源代码分布式的跟踪系统,用于收集服务的定时数据,以解决微
    服务架构中的延迟问题,包括:数据的收集、存储、查找和展现。该产品结合spring-cloud-sleuth
    使用较为简单, 集成很方便, 但是功能较简单。
  3. pinpoint Pinpoint是韩国人开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点
    是支持多种插件,UI功能强大,接入端无代码侵入。
  4. SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多
    种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。
  5. ** Sleuth **SpringCloud 提供的分布式系统中链路追踪解决方案。

注意:SpringCloud alibaba技术栈中并没有提供自己的链路追踪技术的,我们可以采用Sleuth +
Zinkin来做链路追踪解决方案

2 Sleuth入门

Sleuth介绍

SpringCloud Sleuth主要功能就是在分布式系统中提供追踪解决方案。它大量借用了Google
Dapper的设计, 先来了解一下Sleuth中的术语和相关概念。

  1. Trace 由一组Trace Id相同的Span串联形成一个树状结构。为了实现请求跟踪,当请求到达分布式系统的
    入口端点时,只需要服务跟踪框架为该请求创建一个唯一的标识(即TraceId),同时在分布式系
    统内部流转的时候,框架始终保持传递该唯一值,直到整个请求的返回。那么我们就可以使用该唯
    一标识将所有的请求串联起来,形成一条完整的请求链路。
  2. Span 代表了一组基本的工作单元。为了统计各处理单元的延迟,当请求到达各个服务组件的时
    候,也通过一个唯一标识(SpanId)来标记它的开始、具体过程和结束。通过SpanId的开始和结
    束时间戳,就能统计该span的调用时间,除此之外,我们还可以获取如事件的名称。请求信息等
    元数据
  3. ** Annotation** 用它记录一段时间内的事件,内部使用的重要注释:
    cs(Client Send)客户端发出请求,开始一个请求的生命
    sr(Server Received)服务端接受到请求开始进行处理, sr-cs = 网络延迟(服务调用的时间)
    ss(Server Send)服务端处理完毕准备发送到客户端,ss - sr = 服务器上的请求处理时间
    cr(Client Reveived)客户端接受到服务端的响应,请求结束。 cr - sr = 请求的总时间

在这里插入图片描述

在这里插入图片描述

Sleuth入门

微服务名称, traceId, spanid,是否将链路的追踪结果输出到第三方平台
[api-gateway01,020e201bf6b43d2b,020e201bf6b43d2b,false]
[service-order02,020e201bf6b43d2b,9e8a727270c46670,false]
[service-product01,020e201bf6b43d2b,04fc51c93896579a,false]

接下来通过之前的项目案例整合Sleuth,完成入门案例的编写。
因为所有模块都需要所以引入父工程
修改父工程引入Sleuth依赖

  <!--链路追踪 Sleuth-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>

启动微服务,调用之后,我们可以在控制台观察到sleuth的日志输出
在这里插入图片描述
其中 第二个参数 是TraceId, 第三个参数 是SpanId,依次调用有一个全局的
TraceId,将调用链路串起来。仔细分析每个微服务的日志,不难看出请求的具体过程。

查看日志文件并不是一个很好的方法,当微服务越来越多日志文件也会越来越多,通过Zipkin可以
将日志聚合,并进行可视化展示和全文检索

3 Zipkin的集成

ZipKin介绍

Zipkin 是 Twitter 的一个开源项目,它基于Google Dapper实现,它致力于收集服务的定时数据,
以解决微服务架构中的延迟问题,包括数据的收集、存储、查找和展现
我们可以使用它来收集各个服务器上请求链路的跟踪数据,并通过它提供的REST API接口来辅助我
们查询跟踪数据以实现对分布式系统的监控程序,从而及时地发现系统中出现的延迟升高问题并找出系
统性能瓶颈的根源。
除了面向开发的 API 接口之外,它也提供了方便的UI组件来帮助我们直观的搜索跟踪信息和分析请
求链路明细,比如:可以查询某段时间内各用户请求的处理时间等。
Zipkin 提供了可插拔数据存储方式:In-Memory、MySql、Cassandra 以及 Elasticsearch。
在这里插入图片描述
上图展示了 Zipkin 的基础架构,它主要由 4 个核心组件构成:

  1. Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为
    Zipkin内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
  2. Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,
    我们也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中。
  3. RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接
    系统访问以实现监控等
  4. Web UI:UI 组件, 基于API组件实现的上层应用。通过UI组件用户可以方便而有直观地查询和分
    析跟踪信息。

Zipkin分为两端,一个是 Zipkin服务端,一个是 Zipkin客户端,客户端也就是微服务的应用。 客户端会
配置服务端的 URL 地址,一旦发生服务间的调用的时候,会被配置在微服务里面的 Sleuth 的监听器监
听,并生成相应的 Trace 和 Span 信息发送给服务端

4 ZipKin服务端安装

第1步: 下载ZipKin的jar包
可以百度找其他版本的下载

https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec

访问上面的网址,即可得到一个jar包,这就是ZipKin服务端的jar包
第2步: 通过命令行,输入下面的命令启动ZipKin Server

java -jar zipkin-server-2.12.9-exec.jar

第3步:通过浏览器访问 http://localhost:9411访问
在这里插入图片描述

5 Zipkin客户端集成

ZipKin客户端和Sleuth的集成非常简单,只需要在微服务中添加其依赖和配置即可。

第1步:在每个微服务上(父工程)添加依赖

   <!--   ZipKin客户端     -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>

第2步:添加配置 所有服务的配置文件

spring:
  zipkin:
    base-url: http://127.0.0.1:9411/ #zipkin server的请求地址
    discoveryClientEnabled: false #让nacos把它当成一个URL,而不要当做服务名
  sleuth:
    sampler:
      probability: 1.0 #采样的百分比 0.0 - 1.0

第3步: 访问微服务
http://localhost:7000/service-order0201/order/prod/1

第4步: 访问zipkin的UI界面,观察效果
在这里插入图片描述

第5步:点击其中一条记录,可观察一次访问的详细线路
在这里插入图片描述

6 ZipKin数据持久化

Zipkin Server默认会将追踪数据信息保存到内存,但这种方式不适合生产环境。Zipkin支持将追踪
数据持久化到mysql数据库或elasticsearch中

第1步: 创建mysql数据环境

/*
 Navicat Premium Data Transfer

 Source Server         : yangchao
 Source Server Type    : MySQL
 Source Server Version : 80018
 Source Host           : localhost:3306
 Source Schema         : zipkin

 Target Server Type    : MySQL
 Target Server Version : 80018
 File Encoding         : 65001

 Date: 14/03/2024 11:40:29
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for zipkin_annotations
-- ----------------------------
DROP TABLE IF EXISTS `zipkin_annotations`;
CREATE TABLE `zipkin_annotations`  (
  `trace_id_high` bigint(20) NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` bigint(20) NOT NULL COMMENT 'coincides with zipkin_spans.trace_id',
  `span_id` bigint(20) NOT NULL COMMENT 'coincides with zipkin_spans.id',
  `a_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'BinaryAnnotation.key or Annotation.value if type == -1',
  `a_value` blob COMMENT 'BinaryAnnotation.value(), which must be smaller than 64KB',
  `a_type` int(11) NOT NULL COMMENT 'BinaryAnnotation.type() or -1 if Annotation',
  `a_timestamp` bigint(20) DEFAULT NULL COMMENT 'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
  `endpoint_ipv4` int(11) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_ipv6` binary(16) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
  `endpoint_port` smallint(6) DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null',
  `endpoint_service_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'Null when Binary/Annotation.endpoint is null',
  UNIQUE INDEX `trace_id_high`(`trace_id_high`, `trace_id`, `span_id`, `a_key`, `a_timestamp`) USING BTREE COMMENT 'Ignore insert on duplicate',
  INDEX `trace_id_high_2`(`trace_id_high`, `trace_id`, `span_id`) USING BTREE COMMENT 'for joining with zipkin_spans',
  INDEX `trace_id_high_3`(`trace_id_high`, `trace_id`) USING BTREE COMMENT 'for getTraces/ByIds',
  INDEX `endpoint_service_name`(`endpoint_service_name`) USING BTREE COMMENT 'for getTraces and getServiceNames',
  INDEX `a_type`(`a_type`) USING BTREE COMMENT 'for getTraces and autocomplete values',
  INDEX `a_key`(`a_key`) USING BTREE COMMENT 'for getTraces and autocomplete values',
  INDEX `trace_id`(`trace_id`, `span_id`, `a_key`) USING BTREE COMMENT 'for dependencies job'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compressed;

-- ----------------------------
-- Table structure for zipkin_dependencies
-- ----------------------------
DROP TABLE IF EXISTS `zipkin_dependencies`;
CREATE TABLE `zipkin_dependencies`  (
  `day` date NOT NULL,
  `parent` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `child` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `call_count` bigint(20) DEFAULT NULL,
  `error_count` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`day`, `parent`, `child`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compressed;

-- ----------------------------
-- Table structure for zipkin_spans
-- ----------------------------
DROP TABLE IF EXISTS `zipkin_spans`;
CREATE TABLE `zipkin_spans`  (
  `trace_id_high` bigint(20) NOT NULL DEFAULT 0 COMMENT 'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
  `trace_id` bigint(20) NOT NULL,
  `id` bigint(20) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `remote_service_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  `parent_id` bigint(20) DEFAULT NULL,
  `debug` bit(1) DEFAULT NULL,
  `start_ts` bigint(20) DEFAULT NULL COMMENT 'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
  `duration` bigint(20) DEFAULT NULL COMMENT 'Span.duration(): micros used for minDuration and maxDuration query',
  PRIMARY KEY (`trace_id_high`, `trace_id`, `id`) USING BTREE,
  INDEX `trace_id_high`(`trace_id_high`, `trace_id`) USING BTREE COMMENT 'for getTracesByIds',
  INDEX `name`(`name`) USING BTREE COMMENT 'for getTraces and getSpanNames',
  INDEX `remote_service_name`(`remote_service_name`) USING BTREE COMMENT 'for getTraces and getRemoteServiceNames',
  INDEX `start_ts`(`start_ts`) USING BTREE COMMENT 'for getTraces ordering and range'
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compressed;

SET FOREIGN_KEY_CHECKS = 1;

第2步: 在启动ZipKin Server的时候,指定数据保存的mysql的信息

java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=mysql --MYSQL_HOST=127.0.0.1 --MYSQL_TCP_PORT=3306 --MYSQL_DB=zipkin --MYSQL_USER=root --MYSQL_PASS=root

7 使用elasticsearch实现数据持久化

第1步: 下载elasticsearch
下载地址:https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-8-4
第2步: 启动elasticsearch
第3步: 在启动ZipKin Server的时候,指定数据保存的elasticsearch的信息

java -jar zipkin-server-2.12.9-exec.jar --STORAGE_TYPE=elasticsearch --ES--HOST=localhost:9200

九 Rocketmq–消息驱动

1 MQ简介

什么是MQ

MQ(Message Queue)是一种跨进程的通信机制,用于传递消息。通俗点说,就是一个先进先出的数
据结构。
在这里插入图片描述

MQ的应用场景
1 异步解耦

最常见的一个场景是用户注册后,需要发送注册邮件和短信通知,以告知用户注册成功。传统的做法如
下:
在这里插入图片描述

此架构下注册、邮件、短信三个任务全部完成后,才返回注册结果到客户端,用户才能使用账号登录。
但是对于用户来说,注册功能实际只需要注册系统存储用户的账户信息后,该用户便可以登录,而后续
的注册短信和邮件不是即时需要关注的步骤。
所以实际当数据写入注册系统后,注册系统就可以把其他的操作放入对应的消息队列 MQ 中然后马上返
回用户结果,由消息队列 MQ 异步地进行这些操作。架构图如下:
在这里插入图片描述

异步解耦是消息队列 MQ 的主要特点,主要目的是减少请求响应时间和解耦。主要的使用场景就是将比
较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列。同时,由于使用了消息队列
MQ,只要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即
解耦合。

2 流量削峰

流量削峰也是消息队列 MQ 的常用场景,一般在秒杀或团队抢购(高并发)活动中使用广泛。
在秒杀或团队抢购活动中,由于用户请求量较大,导致流量暴增,秒杀的应用在处理如此大量的访问流
量后,下游的通知系统无法承载海量的调用量,甚至会导致系统崩溃等问题而发生漏通知的情况。为解
决这些问题,可在应用和下游通知系统之间加入消息队列 MQ。
在这里插入图片描述

秒杀处理流程如下所述:

  1. 用户发起海量秒杀请求到秒杀业务处理系统
  2. 秒杀处理系统按照秒杀处理逻辑将满足秒杀条件的请求发送至消息队列 MQ
  3. 下游的通知系统订阅消息队列 MQ 的秒杀相关消息,再将秒杀成功的消息发送到相应用户。
  4. 用户收到秒杀成功的通知
3 常见的MQ产品

目前业界有很多MQ产品,比较出名的有下面这些:

  1. ZeroMQ 号称最快的消息队列系统,尤其针对大吞吐量的需求场景。扩展性好,开发比较灵活,采用C语言
    实现,实际上只是一个socket库的重新封装,如果做为消息队列使用,需要开发大量的代码。
    ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。
  2. RabbitMQ 使用erlang语言开发,性能较好,适合于企业级的开发。但是不利于做二次开发和维护
  3. ActiveMQ 历史悠久的Apache开源项目。已经在很多产品中得到应用,实现了JMS1.1规范,可以和springjms轻松融合,实现了多种协议,支持持久化到数据库,对队列数较多的情况支持不好。
  4. RocketMQ 阿里巴巴的MQ中间件,由java语言开发,性能非常好,能够撑住双十一的大流量,而且使用起来
    很简单。
  5. ** Kafka** Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,
    相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布
    式系统

2 RocketMQ入门

RocketMQ是阿里巴巴开源的分布式消息中间件,现在是Apache的一个顶级项目。在阿里内部使用
非常广泛,已经经过了"双11"这种万亿级的消息流转。

RocketMQ的架构及概念

在这里插入图片描述
如上图所示,整体可以分成4个角色,分别是:NameServer,Broker,Producer,Consumer

  • Broker(邮递员) : Broker是RocketMQ的核心,负责消息的接收,存储,投递等功能
  • NameServer(邮局) :消息队列的协调者,Broker向它注册路由信息,同时Producer和Consumer向其获取路由信息
  • Producer(寄件人):消息的生产者,需要从NameServer获取Broker信息,然后与Broker建立连接,向Broker发送消
  • Consumer(收件人) : 消息的消费者,需要从NameServer获取Broker信息,然后与Broker建立连接,从Broker获取消
  • Topic(地区):用来区分不同类型的消息,发送和接收消息前都需要先创建Topic,针对Topic来发送和接收消息
  • Message Queue(邮件) :为了提高性能和吞吐量,引入了Message Queue,一个Topic可以设置一个或多个Message
    Queue,这样消息就可以并行往各个Message Queue发送消息,消费者也可以并行的从多个
    Message Queue读取消息
  • Message :Message 是消息的载体。
  • Producer Group:生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组
  • Consumer Group :消费者组,消费同一类消息的多个 consumer 实例组成一个消费者组。
安装RocketMQ

** 环境要求**

  • Linux 64位操作系统
  • 64bit JDK 1.8+
1 下载

https://rocketmq.apache.org/release-notes/
下载需要的版本,然后上传至linux 并解压
在这里插入图片描述

2 安装RocketMQ

1 上传文件到Linux系统 (自定义目录)
2 解压到安装目录(自定义目录)
unzip rocketmq-xxxxx.zip
在这里插入图片描述

3 启动RocketMQ

1进入目录 rocketmq
在这里插入图片描述
2 启动NameServer

nohup ./bin/mqnamesrv &

linux新开一个窗口 查看 日志 (只要进程不报错,就应该是启动成功了,可以查看一下日志)

tail -f /root/logs/rocketmqlogs/namesrv.log

3 启动Broker
调整启动参数,根据需求调整

# 编辑bin/runbroker.sh 和 bin/runserver.sh文件,修改里面的
# JAVA_OPT="${JAVA_OPT} -server -Xms8g -Xmx8g -Xmn4g"
# 为JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m"
nohup bin/mqbroker -n localhost:9876 &

linux新开一个窗口 查看 日志 (只要进程不报错,就应该是启动成功了,可以查看一下日志)

tail -f /root/logs/rocketmqlogs/broker.log
4 关闭RocketMQ
bin/mqshutdown broker
bin/mqshutdown namesrv
RocketMQ控制台安装

1 下载

# 在git上下载下面的工程 rocketmq-console-1.0.0
https://github.com/apache/rocketmq-externals/releases

2 修改配置文件

# 修改配置文件 rocketmq-console\src\main\resources\application.properties
server.port=7777 #项目启动后的端口号
rocketmq.config.namesrvAddr=192.168.109.131:9876 #nameserv的地址,注意防火墙要开启
9876端口

3 打成jar包,并启动

# 进入控制台项目,将工程打成jar包
mvn clean package -Dmaven.test.skip=true
# 启动控制台
java -jar target/rocketmq-console-ng-1.0.0.jar

4 访问控制台
浏览器访问 : localhost:第2步配置的端口号
在这里插入图片描述

3 消息发送和接收演示

接下来我们使用Java代码来演示消息的发送和接收
在这里插入图片描述

        <!-- rocketmq -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>
发送消息

消息发送步骤:

  1. 创建消息生产者, 指定生产者所属的组名
  2. 指定Nameserver地址
  3. 启动生产者
  4. 创建消息对象,指定主题、标签和消息体
  5. 发送消息
  6. 关闭生产者

在这里插入图片描述

package com.itheima.test;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.spring.core.RocketMQTemplate;

public class RocketMQSendMessageTest {


    //发送消息
    public static void main(String[] args) throws Exception {
        //1.创建消息生产者,并且设置生产组名
        DefaultMQProducer producer = new DefaultMQProducer("myproducer-group");

        //2 为生产者设置NameServer的地址
        producer.setNamesrvAddr("192.168.109.131:9876");

        //3 启动生产者
        producer.start();

        //4 构建消息对象,主要是设置消息的主题 标签 内容
        Message message = new Message("myTopic", "myTag", ("Test RocketMQ Message").getBytes());

        //5 发送消息 第二个参数代表超时时间
        SendResult result = producer.send(message, 10000);
        System.out.println(result);

        //6 关闭生产者
        producer.shutdown();

    }
}

接收消息

消息接收步骤:

  1. 创建消息消费者, 指定消费者所属的组名
  2. 指定Nameserver地址
  3. 指定消费者订阅的主题和标签
  4. 设置回调函数,编写处理消息的方法
  5. 启动消息消费者
package com.itheima.test;

import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.spring.core.RocketMQTemplate;

public class RocketMQSendMessageTest {


    //发送消息
    public static void main(String[] args) throws Exception {
        //1.创建消息生产者,并且设置生产组名
        DefaultMQProducer producer = new DefaultMQProducer("myproducer-group");

        //2 为生产者设置NameServer的地址
        producer.setNamesrvAddr("192.168.109.131:9876");

        //3 启动生产者
        producer.start();

        //4 构建消息对象,主要是设置消息的主题 标签 内容
        Message message = new Message("myTopic", "myTag", ("Test RocketMQ Message").getBytes());

        //5 发送消息 第二个参数代表超时时间
        SendResult result = producer.send(message, 10000);
        System.out.println(result);

        //6 关闭生产者
        producer.shutdown();

    }
}

4 案例

接下来我们模拟一种场景: 下单成功之后,向下单用户发送短信。设计图如下:
在这里插入图片描述

订单微服务发送消息

1 在 shop-order 中添加rocketmq的依赖

        <!-- rocketmq -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
        </dependency>

2 添加配置

rocketmq:
  name-server: 192.168.109.131:9876 #rocketMQ服务的地址
  producer:
    group: shop-order # 生产者组

3 编写测试代码

package com.yc.controller;

import com.alibaba.fastjson.JSON;
import com.yc.domain.Order;
import com.yc.domain.Product;
import com.yc.service.OrderService;
import com.yc.service.ProudService;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/14 17:35
 */

@RestController
@Slf4j
public class OrderController02 {

    @Autowired
    private OrderService orderService;
    @Autowired
    private ProudService productService;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;


    //下单
    @RequestMapping("/order/prod/{pid}")
    public Order product(@PathVariable("pid") Integer pid) {
        log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid);
        Product product = productService.findByPid(pid);
        log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));
//下单(创建订单)
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(pid);
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);
        orderService.save(order);
        log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));

        //下单成功之后,将消息放到mq中
        rocketMQTemplate.convertAndSend("order-topic", order);

        return order;
    }


}

4 重启服务访问订单接口 查看rocketmq

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

用户微服务订阅消息

1 修改 shop-user 模块配置

        <!-- rocketmq -->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-spring-boot-starter</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.4.0</version>
        </dependency>

2 修改主类
在这里插入图片描述

package com.yc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc
 * @Date 2024/3/5 12:29
 */

@SpringBootApplication
@EnableDiscoveryClient
public class User01Application {

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

3 添加配置

server:
  port: 8073
spring:
  application:
    name: service-user01
  zipkin:
    base-url: http://127.0.0.1:9411/ #zipkin server的请求地址
    discoveryClientEnabled: false #让nacos把它当成一个URL,而不要当做服务名
  sleuth:
    sampler:
      probability: 1.0 #采样的百分比 0.0 - 1.0
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///hmshop02?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: ****
    password: ****
  #jpa:
   # properties:
     # hibernate:
     #   hbm2ddl:
      #    auto: update
      #  dialect: org.hibernate.dialect.MySQL5InnoDBDialect
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

#rocketmq
rocketmq:
  name-server: 192.168.85.132:9876 #rocketMQ服务的地址


```4 编写消息接收服务

```java
package com.yc.service;

import com.alibaba.fastjson.JSON;
import com.yc.domain.Order;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Service;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service
 * @Date 2024/3/14 18:04
 */
//发送短信的服务
@Slf4j
@Service
@RocketMQMessageListener(consumerGroup = "shop-order02", topic = "order02-topic")
public class SmsService implements RocketMQListener<Order> {
    @Override
    public void onMessage(Order order) {
        log.info("收到一个订单信息{},接下来发送短信", JSON.toJSONString(order));
    }
}

5 启动服务,执行下单操作,观看后台输出
在这里插入图片描述

5 发送不同类型的消息

普通消息

RocketMQ提供三种方式来发送普通消息:可靠同步发送、可靠异步发送和单向发送。

  1. ** 可靠同步发送** 同步发送是指消息发送方发出数据后,会在收到接收方发回响应之后才发下一个数据包的通讯方
    式。
    此种方式应用场景非常广泛,例如重要通知邮件、报名短信通知、营销短信系统等。
  2. ** 可靠异步发送** 异步发送是指发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。发送
    方通过回调接口接收服务器响应,并对响应结果进行处理。
    异步发送一般用于链路耗时较长,对 RT 响应时间较为敏感的业务场景,例如用户视频上传后通知
    启动转码服务,转码完成后通知推送转码结果等。
  3. ** 单向发送 **单向发送是指发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不
    等待应答。
    适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。

依赖

     <!--依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>

测试

package com.itheima.test;

import com.itheima.OrderApplication;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = OrderApplication.class)
public class MessageTypeTest {

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    //同步消息
    @Test
    public void testSyncSend() {
        //参数一: topic:tag
        //参数二: 消息体
        //参数三: 超时时间
        SendResult result =
                rocketMQTemplate.syncSend("test-topic-1:tag", "这是一条同步消息", 10000);
        System.out.println(result);
    }

    //异步消息
    @Test
    public void testAsyncSend() throws InterruptedException {
        //参数一: topic:tag
        //参数二: 消息体
        //参数三: 回调
        rocketMQTemplate.asyncSend("test-topic-1", "这是一条异步消息", new SendCallback() {
            //成功响应的回调
            @Override
            public void onSuccess(SendResult result) {
                System.out.println(result);
            }

            //异常响应的回调
            @Override
            public void onException(Throwable throwable) {
                System.out.println(throwable);
            }
        });
        System.out.println("==================");
        Thread.sleep(300000000);
    }

    //单向消息
    @Test
    public void testOneWay() {
        for (int i = 0; i < 10; i++) {
            rocketMQTemplate.sendOneWay("test-topic-1", "这是一条单向消息");
        }
    }


    //单向顺序消息
    @Test
    public void testOneWayOrderly() {
        for (int i = 0; i < 10; i++) {
            //第三个参数的作用是用来决定这些消息发送到哪个队列的上的
            rocketMQTemplate.sendOneWayOrderly("test-topic-1", "这是一条单向顺序消息","xx");
        }
    }
}

三种发送方式的对比

发送方式发送 TPS发送结果反馈可靠性
同步发送不丢失
异步发送不丢失
单向发送最快可能丢失
顺序消息

顺序消息是消息队列提供的一种严格按照顺序来发布和消费的消息类型。

在这里插入图片描述

  //单向顺序消息
    @Test
    public void testOneWayOrderly() {
        for (int i = 0; i < 10; i++) {
            //第三个参数的作用是用来决定这些消息发送到哪个队列的上的
            rocketMQTemplate.sendOneWayOrderly("test-topic-1", "这是一条单向消息","xx");
        }
    }
事务消息

RocketMQ提供了事务消息,通过事务消息就能达到分布式事务的最终一致。
在这里插入图片描述

事务消息交互流程:

两个概念:

  • 半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了RocketMQ服务端,但是服务
    端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的
    消息即半事务消息。
  • 消息回查:由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,
    RocketMQ服务端通过扫描发现某条消息长期处于“半事务消息”时,需要主动向消息生产者询问该
    消息的最终状态(Commit 或是 Rollback),该询问过程即消息回查。

事务消息发送步骤:

  1. 发送方将半事务消息发送至RocketMQ服务端
  2. RocketMQ服务端将消息持久化之后,向发送方返回Ack确认消息已经发送成功,此时消息为半事
    务消息
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit 或是 Rollback),服务端收到
    Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到 Rollback 状
    态则删除半事务消息,订阅方将不会接受该消息。

事务消息回查步骤:

  1. 在断网或者是应用重启的特殊情况下,上述步骤4提交的二次确认最终未到达服务端,经过固定时
    间后服务端将对该消息发起消息回查。
  2. 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  3. 发送方根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息
    进行操作。

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

package com.yc.domain;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.io.Serializable;
import java.util.Date;

//消息事物状态记录
@Entity(name = "shop_txlog")
@Data
public class TxLog implements Serializable {

    @Id
    private String txLogId;
    private String content;
    private Date date;
}

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

package com.yc.dao;

import com.yc.domain.TxLog;
import org.springframework.data.jpa.repository.JpaRepository;

public interface TxLogDao extends JpaRepository<TxLog, String> {
}

**OrderServiceImpl4 **

package com.yc.service.impl;

import com.yc.dao.OrderDao;
import com.yc.dao.TxLogDao;
import com.yc.domain.Order;
import com.yc.domain.TxLog;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.UUID;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/14 18:37
 */
@Service
public class OrderServiceImpl4 {
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private TxLogDao txLogDao;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    public void createOrderBefore(Order order) {
        String txId = UUID.randomUUID().toString();
        //发送半事务消息
        rocketMQTemplate.sendMessageInTransaction(
                "tx_producer_group",
                "tx_topic",
                MessageBuilder.withPayload(order).setHeader("txId",
                        txId).build(),
                order
        );
    }

    //本地事物
    @Transactional
    public void createOrder(String txId, Order order) {
        //本地事物代码
        orderDao.save(order);
        //记录日志到数据库,回查使用
        TxLog txLog = new TxLog();
        txLog.setTxLogId(txId);
        txLog.setContent("事物测试");
        txLog.setDate(new Date());
        txLogDao.save(txLog);
    }

}

**OrderServiceImpl4Listener **

package com.yc.service.impl;

import com.yc.dao.TxLogDao;
import com.yc.domain.Order;
import com.yc.domain.TxLog;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/14 18:38
 */
public class OrderServiceImpl4Listener implements RocketMQLocalTransactionListener {

    @Autowired
    private TxLogDao txLogDao;
    @Autowired
    private OrderServiceImpl4 orderServiceImpl4;

    //执行本地事物
    @Override
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg,
                                                                 Object arg) {
        try {
            //本地事物
            orderServiceImpl4.createOrder((String) msg.getHeaders().get("txId"),
                    (Order) arg);
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    //消息回查
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        //查询日志记录
        TxLog txLog = txLogDao.findById((String) msg.getHeaders().get("txId")).get();
        if (txLog != null) {
            return RocketMQLocalTransactionState.COMMIT;
        } else {
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

6 消息消费要注意的细节

@RocketMQMessageListener(
consumerGroup = "shop",//消费者分组
topic = "order-topic",//要消费的主题
consumeMode = ConsumeMode.CONCURRENTLY, //消费模式:无序和有序
messageModel = MessageModel.CLUSTERING, //消息模式:广播和集群,默认是集群
)
public class SmsService implements RocketMQListener<Order> {}

RocketMQ支持两种消息模式:

  • 广播消费: 每个消费者实例都会收到消息,也就是一条消息可以被每个消费者实例处理;
  • 集群消费: 一条消息只能被一个消费者实例消费

十 SMS–短信服务

阿里云 、腾讯云、华为云等会提供demo

十一 Nacos Config–服务配置

1 服务配置中心介绍

首先我们来看一下,微服务架构下关于配置文件的一些问题:

  1. 配置文件相对分散。在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散
    在各个微服务中,不好统一配置和管理
  2. 配置文件无法区分环境。微服务项目可能会有多个环境,例如:测试环境、预发布环境、生产环
    境。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动
    维护,这比较困难
  3. 配置文件无法实时更新。我们修改了配置文件之后,必须重新启动微服务才能使配置生效,这对一
    个正在运行的项目来说是非常不友好的。

基于上面这些问题,我们就需要配置中心的加入来解决这些问题。
配置中心的思路是:

  • 首先把项目中各种配置全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。
  • 当各个服务需要获取配置的时候,就来配置中心的接口拉取自己的配置。
  • 当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动
    态更新。

当加入了服务配置中心之后,我们的系统架构图会变成下面这样:
在这里插入图片描述

2 Nacos Config入门

使用nacos作为配置中心,其实就是将nacos当做一个服务端,将各个微服务看成是客户端,我们
将各个微服务的配置文件统一存放在nacos上,然后各个微服务从nacos上拉取配置即可。
接下来我们以商品微服务为例,学习nacos config的使用。
1 搭建nacos环境【使用现有的nacos环境即可】
2 在微服务中引入nacos的依赖

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

3 在微服务中添加nacos config的配置
注意:不能使用原来的application.yml作为配置文件,而是新建一个bootstrap.yml作为配置文件

配置文件优先级(由高到低):
bootstrap.properties -> bootstrap.yml -> application.properties -> application.yml

在这里插入图片描述

spring:
  application:
    name: service-product01
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.93.1:8848
  profiles:
    active: dev # 环境标识

4 在nacos中添加配置

点击配置列表,点击右边+号,新建配置。在新建配置过程中,要注意下面的细节:
1Data ID不能随便写,要跟配置文件中的对应,对应关系如图所示
2)配置文件格式要跟配置文件的格式对应,且目前仅仅支持YAMLProperties
3)配置内容按照上面选定的格式书写

在这里插入图片描述

spring:
  application:
    name: service-product01
  cloud:
    nacos:
      config:
        server-addr: 192.168.93.1:8848 #nacos中心地址
        file-extension: yaml # 配置文件格式
  profiles:
    active: dev # 环境标识

5 注释本地的application.yml中的内容, 启动程序进行测试
如果依旧可以成功访问程序,说明我们nacos的配置中心功能已经实现

3 Nacos Config深入

配置动态刷新

在入门案例中,我们实现了配置的远程存放,但是此时如果修改了配置,我们的程序是无法读取到
的,因此,我们需要开启配置的动态刷新功能

在nacos中的service-product-dev.yaml配置项中添加下面配置:

config:
	appName: product

方式一: 硬编码方式

@RestController
public class NacosConfigController {
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping("/nacos-config-test1")
public String nacosConfingTest1() {
return
applicationContext.getEnvironment().getProperty("config.appName");
}
}

方式二: 注解方式(推荐)
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

package com.yc.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/15 21:12
 */
@RestController
//@RefreshScope //只需要在需要动态读取配置的类上添加此注解就可以
@RefreshScope
public class NacosConfigController {

    @Value("${spring.application.name}")
    private String name;

    @Value("${config.appName}")
    private String appName;

    //2 注解方式
    @GetMapping("/nacos-config-test2")
    public String nacosConfingTest2() {
        System.out.println("-----"+appName);
        return appName;
    }

    /**
     * 方法一:硬编码方式
     */
    @Autowired
    private ConfigurableApplicationContext applicationContext;

    @GetMapping("/nacos-config-test1")
    public String nacosConfingTest1() {
        System.out.println("-----"+applicationContext.getEnvironment().getProperty("config.appName"));
        return applicationContext.getEnvironment().getProperty("config.appName");
    }

}

配置共享

当配置越来越多的时候,我们就发现有很多配置是重复的,这时候就考虑可不可以将公共配置文件
提取出来,然后实现共享呢?当然是可以的。接下来我们就来探讨如何实现这一功能。

1 同一个微服务的不同环境之间共享配置

如果想在同一个微服务的不同环境之间实现配置共享,其实很简单。
只需要提取一个以 spring.application.name 命名的配置文件,然后将其所有环境的公共配置放在里
面即可。

1 新建一个名为service-product01.yaml配置存放商品微服务的公共配置,配置内容是原来的application.yml内容
在这里插入图片描述

在这里插入图片描述
2 新建一个名为service-product01-test.yaml配置存放测试环境的配置
在这里插入图片描述

3 新建一个名为service-product01-dev.yaml配置存放开发环境的配置
在这里插入图片描述

4 添加测试方法

    @Value("${config.env}")
    private String env;

    //3 同一微服务的不同环境下共享配置
    @GetMapping("/nacos-config-test3")
    public String nacosConfingTest3() {
        System.out.println("----" + env);
        return env;
    }

5 通过修改bootstrap.yml中的配置 进行多次访问,观察结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 不同微服务中间共享配置

不同为服务之间实现配置共享的原理类似于文件引入,就是定义一个公共配置,然后在当前配置中引

1 在nacos中定义一个DataID为all-service.yaml的配置,用于所有微服务共享
在这里插入图片描述
2 修改原本yaml配置内容 ,自己独有的内容 如:

server:
	port: 8081

3 修改bootstrap.yaml

spring:
	application:
		name: service-product
	cloud:
		nacos:
			config:
				server-addr: 127.0.0.1:8848 #nacos中心地址
				file-extension: yaml # 配置文件格式
				shared-dataids: all-service.yaml # 配置要引入的配置
				refreshable-dataids: all-service.yaml # 配置要实现动态配置刷新的配置
	profiles:
		active: dev # 环境标识

4 启动商品微服务进行测试

4 nacos的几个概念

命名空间(Namespace)
命名空间可用于进行不同环境的配置隔离。一般一个环境划分到一个命名空间
配置分组(Group)
配置分组用于将不同的服务可以归类到同一分组。一般将一个项目的配置分到一组
配置集(Data ID)
在系统中,一个配置文件通常就是一个配置集。一般微服务的配置就是一个配置集
在这里插入图片描述

命名空间(Namespace)

在这里插入图片描述
在这里插入图片描述
引入不同的命名空间在这里插入图片描述

配置分组(Group)

在这里插入图片描述

十二 Seata–分布式事务

1 分布式事务基础

事务

事务指的就是一个操作单元,在这个操作单元中的所有操作最终要保持一致的行为,要么所有操作
都成功,要么所有的操作都被撤销。简单地说,事务提供一种“要么什么都不做,要么做全套”机制。

本地事物

本地事物其实可以认为是数据库提供的事务机制。说到数据库事务就不得不说,数据库事务中的四
大特性:

  • A:原子性(Atomicity),一个事务中的所有操作,要么全部完成,要么全部不完成
  • C:一致性(Consistency),在一个事务执行之前和执行之后数据库都必须处于一致性状态
  • I:隔离性(Isolation),在并发环境中,当不同的事务同时操作相同的数据时,事务之间互不影响
  • D:持久性(Durability),指的是只要事务成功结束,它对数据库所做的更新就必须永久的保存下来

数据库事务在实现时会将一次事务涉及的所有操作全部纳入到一个不可分割的执行单元,该执行单
元中的所有操作要么都成功,要么都失败,只要其中任一操作执行失败,都将导致整个事务的回滚

分布式事务

分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布
式系统的不同节点之上。
简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同
的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

分布式事务的场景
  • 单体系统访问多个数据库
    一个服务需要调用多个数据库实例完成数据的增删改操作
    在这里插入图片描述
  1. 多个微服务访问同一个数据库
    多个服务需要调用一个数据库实例完成数据的增删改操作
    在这里插入图片描述

  2. 多个微服务访问多个数据库
    多个服务需要调用一个数据库实例完成数据的增删改操作
    在这里插入图片描述

2 分布式事务解决方案

全局事务

全局事务基于DTP模型实现。DTP是由X/Open组织提出的一种分布式事务模型——X/Open
Distributed Transaction Processing Reference Model。它规定了要实现分布式事务,需要三种角色:

  1. AP: Application 应用系统 (微服务)
  2. TM: Transaction Manager 事务管理器 (全局事务管理)
  3. RM: Resource Manager 资源管理器 (数据库)

整个事务分成两个阶段:
阶段一: 表决阶段,所有参与者都将本事务执行预提交,并将能否成功的信息反馈发给协调者。
阶段二: 执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地执行提交或者回
滚。
在这里插入图片描述

优点
提高了数据一致性的概率,实现成本较低
缺点
单点问题: 事务协调者宕机
同步阻塞: 延迟了提交时间,加长了资源阻塞时间
数据不一致: 提交第二阶段,依然存在commit结果未知的情况,有可能导致数据不一致

可靠消息服务

基于可靠消息服务的方案是通过消息中间件保证上、下游应用数据操作的一致性。假设有A和B两个
系统,分别可以处理任务A和任务B。此时存在一个业务流程,需要将任务A和任务B在同一个事务中处
理。就可以使用消息中间件来实现这种分布式事务。
在这里插入图片描述

第一步: 消息由系统A投递到中间件

  1. 在系统A处理任务A前,首先向消息中间件发送一条消息
  2. 消息中间件收到后将该条消息持久化,但并不投递。持久化成功后,向A回复一个确认应答
  3. 系统A收到确认应答后,则可以开始处理任务A
  4. 任务A处理完成后,向消息中间件发送Commit或者Rollback请求。该请求发送完成后,对系统A而
    言,该事务的处理过程就结束了
  5. 如果消息中间件收到Commit,则向B系统投递消息;如果收到Rollback,则直接丢弃消息。但是
    如果消息中间件收不到Commit和Rollback指令,那么就要依靠"超时询问机制"。
    超时询问机制
    系统A除了实现正常的业务流程外,还需提供一个事务询问的接口,供消息中间件调
    用。当消息中间件收到发布消息便开始计时,如果到了超时没收到确认指令,就会主动调用
    系统A提供的事务询问接口询问该系统目前的状态。该接口会返回三种结果,中间件根据三
    种结果做出不同反应:
    提交:将该消息投递给系统B
    回滚:直接将条消息丢弃
    处理中:继续等待

第二步: 消息由中间件投递到系统B
消息中间件向下游系统投递完消息后便进入阻塞等待状态,下游系统便立即进行任务的处理,任务
处理完成后便向消息中间件返回应答。
如果消息中间件收到确认应答后便认为该事务处理完毕
如果消息中间件在等待确认应答超时之后就会重新投递,直到下游消费者返回消费成功响应为止。
一般消息中间件可以设置消息重试的次数和时间间隔,如果最终还是不能成功投递,则需要手工干
预。这里之所以使用人工干预,而不是使用让A系统回滚,主要是考虑到整个系统设计的复杂度问

基于可靠消息服务的分布式事务,前半部分使用异步,注重性能;后半部分使用同步,注重开发成本。

最大努力通知

最大努力通知也被称为定期校对,其实是对第二种解决方案的进一步优化。它引入了本地消息表来
记录错误消息,然后加入失败消息的定期校对功能,来进一步保证消息会被下游系统消费
在这里插入图片描述

第一步: 消息由系统A投递到中间件

  1. 处理业务的同一事务中,向本地消息表中写入一条记录
  2. 准备专门的消息发送者不断地发送本地消息表中的消息到消息中间件,如果发送失败则重试
    ** 第二步: 消息由中间件投递到系统B**
  3. 消息中间件收到消息后负责将该消息同步投递给相应的下游系统,并触发下游系统的任务执行
  4. 当下游系统处理成功后,向消息中间件反馈确认应答,消息中间件便可以将该条消息删除,从而该
    事务完成
  5. 对于投递失败的消息,利用重试机制进行重试,对于重试失败的,写入错误消息表
  6. 消息中间件需要提供失败消息的查询接口,下游系统会定期查询失败消息,并将其消费
    这种方式的优缺点:
    优点: 一种非常经典的实现,实现了最终一致性。
    缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理
TCC事务

TCC即为Try Confirm Cancel,它属于补偿型分布式事务。TCC实现分布式事务一共有三个步骤:

  1. Try:尝试待执行的业务
    这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源
  2. Confirm:确认执行业务
    确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。通常情况下,采用TCC
    则认为 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。若Confirm阶段真的
    出错了,需引入重试机制或人工处理。
  3. Cancel:取消待执行的业务
    取消Try阶段预留的业务资源。通常情况下,采用TCC则认为Cancel阶段也是一定成功的。若
    Cancel阶段真的出错了,需引入重试机制或人工处理。

在这里插入图片描述

TCC两阶段提交与XA两阶段提交的区别是:
XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
TCC事务的优缺点:
优点:把数据库层的二阶段提交上提到了应用层来实现,规避了数据库层的2PC性能低下问题。
缺点:TCC的Try、Confirm和Cancel操作功能需业务提供,开发成本高

3 Seata介绍

2019 年 1 月,阿里巴巴中间件团队发起了开源项目 Fescar(Fast & EaSy Commit And
Rollback),其愿景是让分布式事务的使用像本地事务的使用一样,简单和高效,并逐步解决开发者们
遇到的分布式事务方面的所有难题。后来更名为 Seata,意为:Simple Extensible Autonomous
Transaction Architecture,是一套分布式事务解决方案。
Seata的设计目标是对业务无侵入,因此从业务无侵入的2PC方案着手,在传统2PC的基础上演进。
它把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分
支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个关系数据
库的本地事务
在这里插入图片描述

在这里插入图片描述Seata主要由三个重要组件组成:

  1. TC:Transaction Coordinator 事务协调器,管理全局的分支事务的状态,用于全局性事务的提交
    和回滚。
  2. TM:Transaction Manager 事务管理器,用于开启、提交或者回滚全局事务。
  3. RM:Resource Manager 资源管理器,用于分支事务上的资源管理,向TC注册分支事务,上报分
    支事务的状态,接受TC的命令来提交或者回滚分支事务。

在这里插入图片描述
Seata的执行流程如下:

  1. A服务的TM向TC申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID
  2. A服务的RM向TC注册分支事务,并及其纳入XID对应全局事务的管辖
  3. A服务执行分支事务,向数据库做操作
  4. A服务开始远程调用B服务,此时XID会在微服务的调用链上传播
  5. B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖
  6. B服务执行分支事务,向数据库做操作
  7. 全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚
  8. TC协调其管辖之下的所有分支事务, 决定是否回滚
    Seata实现2PC与传统2PC的差别:
  9. 架构层次方面,传统2PC方案的 RM 实际上是在数据库层,RM本质上就是数据库自身,通过XA协
    议实现,而 Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。
  10. 两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback,事务性资源的锁都要保
    持到Phase2完成才释放。而Seata的做法是在Phase1 就将本地事务提交,这样就可以省去Phase2
    持锁的时间,整体提高效率。

4 Seata实现分布式事务控制

本示例通过Seata中间件实现分布式事务,模拟电商中的下单和扣库存的过程
我们通过订单微服务执行下单操作,然后由订单微服务调用商品微服务扣除库存
在这里插入图片描述

案例

为了方便先取消nacos的config配置,使用本地配置
容错类也取消掉

1 order微服务

在这里插入图片描述

controller

package com.yc.controller;

import com.yc.domain.Order;
import com.yc.service.impl.OrderServiceImpl05;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author yc
 *
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.controller
 * @Date 2024/3/15 22:59
 */
@RestController
@Slf4j
public class OrderController05 {
    @Autowired
    private OrderServiceImpl05 orderService;
    //下单
    @RequestMapping("/order/prod/{pid}")
    public Order order(@PathVariable("pid") Integer pid) {
        log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid);
        return orderService.createOrder(pid);
    }
}

OrderService

package com.yc.service.impl;

import com.alibaba.fastjson.JSON;
import com.yc.dao.OrderDao;
import com.yc.domain.Order;
import com.yc.domain.Product;
import com.yc.service.ProudService;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service.impl
 * @Date 2024/3/15 23:00
 */

@Service
@Slf4j
public class OrderServiceImpl05 {
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private ProudService productService;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

//    @GlobalTransactional
    public Order createOrder(Integer pid) {
//1 调用商品微服务,查询商品信息
        Product product = productService.findByPid(pid);
        log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));
//2 下单(创建订单)
        Order order = new Order();
        order.setUid(1);
        order.setUsername("测试用户");
        order.setPid(pid);
        order.setPname(product.getPname());
        order.setPprice(product.getPprice());
        order.setNumber(1);
        orderDao.save(order);
        log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));
//3 扣库存
        productService.reduceInventory(pid, order.getNumber());
//4 向mq中投递一个下单成功的消息
        rocketMQTemplate.convertAndSend("order-topic", order);
        return order;
    }
}

ProductService
添加完方法会报错,所有报错的容错处理全部注释,或者对新方法进行容错

package com.yc.service;

import com.yc.domain.Product;
import com.yc.service.fallback.ProductServiceFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * @Author yc
 * @PackageName springcloud-alibaba-02
 * @Package com.yc.service
 * @Date 2024/3/5 17:54
 */
//value用于指定调用nacos下哪个微服务
//fallback 指定当调用出现问题之后,要进入到哪个类中的同名方法之下执行备用逻辑
//@FeignClient("service-product01")//声明调用的提供者的name
//@FeignClient(value = "service-product01", fallback = ProductServiceFallBack.class)
//@FeignClient(value = "service-product01", fallbackFactory = ProductServiceFallBackFactory.class)
@FeignClient(value = "service-product01")
public interface ProudService {

    //指定调用提供者的哪个方法
    //@FeignClient+@GetMapping 就是一个完整的请求路径 http://serviceproduct/product/{pid}
    @GetMapping(value = "/product/{pid}")
    Product findByPid(@PathVariable("pid") Integer pid);

    //减库存
    @RequestMapping("/product/reduceInventory")
    void reduceInventory(@RequestParam("pid") Integer pid, @RequestParam("num") int num);

}

2 Product微服务

在这里插入图片描述

controller

 //减少库存
    @RequestMapping("/product/reduceInventory")
    public void reduceInventory(Integer pid, int num) {
        productService.reduceInventory(pid, num);
    }

service

  @Override
    public void reduceInventory(Integer pid, int num) {
        Product product = productDao.findById(pid).get();
        product.setStock(product.getStock() - num);//减库存
        productDao.save(product);
    }
3 异常模拟

在ProductServiceImpl的代码中模拟一个异常, 然后调用下单接口

    @Override
    public void reduceInventory(Integer pid, int num) {
        Product product = productDao.findById(pid).get();
        if (product.getStock() < num) {
            throw new RuntimeException("库存不足");
        }
        int i = 1 / 0;
        product.setStock(product.getStock() - num);//减库存
        productDao.save(product);
    }

5 启动Seata

下载seata

https://github.com/apache/incubator-seata/releases
选择自己的版本下载

初始化数据库脚本

在这里插入图片描述

修改配置文件

将下载得到的压缩包进行解压,进入conf目录,调整下面的配置文件:
在这里插入图片描述
打开application.yml和application.example.yml
application.yml需要修改的内容,application.example.yml都有示例,然后改为自己的ip端口、账号密码和连接
在这里插入图片描述

启动seata服务

双击 然后浏览器查看nacos
并访问seata 服务
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6 使用Seata实现事务控制

1 添加依赖

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

DataSourceProxyConfig
Seata 是通过代理数据源实现事务分支的,所以需要配置 io.seata.rm.datasource.DataSourceProxy 的
Bean,且是 @Primary默认的数据源,否则事务不会回滚,无法实现分布式事务

@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}

registry.conf

registry {
    type = "nacos"
    nacos {
        serverAddr = "localhost"
        namespace = "public"
        cluster = "default"
    }
}

config {
    type = "nacos"
    nacos {
        serverAddr = "localhost"
        namespace = "public"
        cluster = "default"
    }
}

bootstrap.yaml

spring:
  application:
    name: service-order02
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848 #nacos中心地址
#        shared-dataids: service-product.yaml # 配置要引入的配置
#        refreshable-dataids: service-product.yaml # 配置要实现动态配置刷新的配置
        namespace: public
        group: SEATA_GROUP
    alibaba:
      seata:
        tx-service-group: order-service

在order微服务开启全局事务
在这里插入图片描述

7 seata运行流程分析

要点说明:
1、每个RM使用DataSourceProxy连接数据库,其目的是使用ConnectionProxy,使用数据源和数据连
接代理的目的就是在第一阶段将undo_log和业务数据放在一个本地事务提交,这样就保存了只要有业务
操作就一定有undo_log。
2、在第一阶段undo_log中存放了数据修改前和修改后的值,为事务回滚作好准备,所以第一阶段完成
就已经将分支事务提交,也就释放了锁资源。
3、TM开启全局事务开始,将XID全局事务id放在事务上下文中,通过feign调用也将XID传入下游分支
事务,每个分支事务将自己的Branch ID分支事务ID与XID关联。
4、第二阶段全局事务提交,TC会通知各各分支参与者提交分支事务,在第一阶段就已经提交了分支事
务,这里各各参与者只需要删除undo_log即可,并且可以异步执行,第二阶段很快可以完成。
5、第二阶段全局事务回滚,TC会通知各各分支参与者回滚分支事务,通过 XID 和 Branch ID 找到相应
的回滚日志,通过回滚日志生成反向的 SQL 并执行,以完成分支事务回滚到之前的状态,如果回滚失
败则会重试回滚操作。

十三 Dubbo–rpc通信

1 介绍

Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC分布式服务框架,致力于提供高性能和透明化的
RPC远程服务调用方案,以及SOA服务治理方案。
Spring-cloud-alibaba-dubbo 是基于SpringCloudAlibaba技术栈对dubbo技术的一种封装,目的在
于实现基于RPC的服务调用。

2 实现

提供统一业务api
public interface ProductService {
Product findByPid(Integer pid);
}
提供服务提供者

1 添加依赖

<!--dubbo-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

2 添加dubbo配置

dubbo:
scan:
base-packages: com.itheima.service.impl # 开启包扫描
protocols:
dubbo:
name: dubbo # 服务协议
port: -1 # 服务端口
registry:
address: spring-cloud://localhost # 注册中心

3 编写并暴露服务

//暴露服务:注意这里使用的是dubbo提供的注解@Service,而不是Spring的
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductDao productDao;
@Override
public Product findByPid(Integer pid) {
return productDao.findById(pid).get();
}
}
提供服务消费者

1 添加依赖

<!--dubbo-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>

2 添加dubbo配置

dubbo:
registry:
address: spring-cloud://localhost # 注册中心
cloud:
subscribed-services: service-product # 订阅的提供者名称

3 引用服务

@RestController
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
//引用服务
@Reference
private ProductService productService;
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable Integer pid) {
log.info("接收到{}号商品的下单请求,接下来调用商品微服务查询此商品信息", pid);
//调用商品微服务,查询商品信息
Product product = productService.findByPid(pid);
log.info("查询到{}号商品的信息,内容是:{}", pid, JSON.toJSONString(product));
//下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderService.createOrder(order);
log.info("创建订单成功,订单信息为{}", JSON.toJSONString(order));
return order;
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值