四、SpringCloud-服务治理-3

服务治理

什么是服务治理

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

服务注册:在服务治理框架中,都会构建⼀个注册中⼼,每个服务单元向注册中⼼登记⾃⼰提供服务的详细信息。 并在注册中⼼形成⼀张服务的清单,服务注册中⼼需要以⼼跳的⽅式去监测清单中的服务是否可⽤,如果不可⽤, 需要在服务清单中剔除不可⽤的服务。

服务发现:服务调⽤⽅向服务注册中⼼咨询服务,并获取所有服务的实例清单,实现对具体服务实例的访问。

image-20230824115525611

通过上⾯的调⽤图会发现,除了微服务,还有⼀个组件是服务注册中⼼,它是微服务架构⾮常重要的⼀个组件,在微服务架构⾥主要起到了协调者的⼀个作⽤

注册中⼼⼀般包含如下⼏个功能:

  1. 服务发现:
  • 服务注册:保存服务提供者和服务调⽤者的信息。
  • 服务订阅:服务调⽤者订阅服务提供者的信息,注册中⼼向订阅者推送提供者的信息。
  1. 服务配置:
  • 配置订阅:服务提供者和服务调⽤者订阅微服务相关的配置。
  • 配置下发:主动将配置推送给服务提供者和服务调⽤者。
  1. 服务健康检测

    检测服务提供者的健康情况,如果发现异常,执⾏服务剔除。

常⻅的注册中⼼

Zookeeper

zookeeper 是⼀个分布式服务框架,是 Apache Hadoop 的⼀个⼦项⽬,它主要是⽤来解决分布式应⽤中经常遇 到的⼀些数据管理问题,如:统⼀命名服务、状态同步服务、集群管理、分布式应⽤配置项的管理等。

Eureka

Eureka 是 Springcloud Netflix 中的重要组件,主要作⽤就是做服务注册和发现。但是现在已经闭源。

Consul

Consul 是基于 GO 语⾔开发的开源⼯具,主要⾯向分布式,服务化的系统提供服务注册、服务发现和配置管理的 功能。 Consul 的功能都很实⽤,其中包括:服务注册/发现、健康检查、Key/Value 存储、多数据中⼼和分布式⼀ 致性保证等特性。 Consul 本身只是⼀个⼆进制的可执⾏⽂件,所以安装和部署都⾮常简单,只需要从官⽹下载 后,在执⾏对应的启动脚本即可。

Nacos

Nacos 是⼀个更易于构建云原⽣应⽤的动态服务发现、配置管理和服务管理平台。它是 Spring Cloud Alibaba 组件之⼀,负责服务注册发现和服务配置,可以这样认为 nacos = eureka + config 。

Nacos 概述

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

Nacos 的环境搭建

下载

第一种:

从 SpringCloud Alibaba 官⽹上下载:

image-20230824120258329

最好用迅雷下载,浏览器一般很慢!

第二种:
image-20230824122000244

用wget命令的这种方式下载

部署到 Linux

上传到 Linux 中,解压。

在linux中新建Springcloud文件夹,再新建nacos文件夹

解压命令

tar zxvf nacos-server-2.2.3.tar.gz

1、搭建数据库环境

创建数据库 nacos_config ,将下载⽂件中的 sql 中的表创建到 nacos_config数据库中。

image-20230824122310703

打开所对应的文件,新建数据库

image-20230824122600007

image-20230824122447750

create database nacos_config;
use nacos_config;

将所有的内容复制到中执行Navicat

然后创建⼀个⽤户账号: nacos 密码是: nacos 。赋予对 nacos 数据库的所有权限。

#创建nacos⽤户
CREATE USER nacos IDENTIFIED BY 'nacos';
#授予nacos⽤户对nacos数据库所有权限并可以远程访问
grant all on nacos.* TO 'nacos'@'%';
#刷新权限
FLUSH PRIVILEGES;

image-20230824123635159

Nacos 环境配置

在配置 nacos 服务环境前,需搭建 JDK 环境,如未搭建请⾃⾏解决。

⾸先:修改 conf ⽂件夹下的 application.properties ⽂件:

image-20230824123307462

如果出现问题

image-20230824123525444

最后:修改 bin ⽂件夹下的启动项 start.sh ⽂件:

image-20230824123840332

启动和关闭

#启动nacos
[root@weilai ~]# cd /opt/soft/springcloud/nacos/nacos/bin
[root@weilai bin]# sh startup.sh
#关闭nacos
sh shutdown.sh
# 查看⽇志
cat /opt/soft/springcloud/nacos/nacos/logs/start.out

启动演示

image-20230824124027119

关闭演示

image-20230824124217165

查看⽇志演示

image-20230824211000098

访问

在浏览器中访问:http://IP:8848/nacos

image-20230824124124517

账号密码: nacos

image-20230824121310226

创建微服务项⽬

我们创建⼏个微服务的项⽬,将这些项⽬注册到服务治理中⼼。

创建命名空间

image-20230824151111137

image-20230824151142147

创建⽗项⽬

⽗项⽬是⼀个资源管理项⽬,主要⼯作是管理依赖。

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<spring.boot.version>2.6.6</spring.boot.version>
<spring.cloud.alibaba.version>2021.0.4.0</spring.cloud.alibaba.version>
<spring.cloud.version>2021.0.4</spring.cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<!--SpringBoot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringCloud依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--SpringCloud alibaba依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建⼦项⽬

⼦项⽬继承⽗项⽬,也会引⽤⽗项⽬的配置。

搭建项目

image-20230824175230953

依赖管理

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引⼊服务的注册和发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>

配置文件

image-20230824174411705

server:
  port: 8081
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.65.3:8848  # 配置Nacos Server地址
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce		# 配置Nacos Server地址
  application:
    name: example-provider		# 配置Nacos Server地址

controller包

@RestController
@RequestMapping("/provider")
public class ProviderController {

    @GetMapping("/a")
    public String a() {
        return "This is ProviderController`a!";
    }
}

启动项⽬

普通的 SpringBoot 项⽬启动,只需引⼊ @EnableDiscoveryClient 注解即可。

@SpringBootApplication	 # 表示这是一个Spring Boot应用,并启用Spring Cloud框架提供的一些特性
@EnableDiscoveryClient  # 表示启用服务发现和服务调用功能
public class ExampleProducerApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleProducerApplication.class, args);
 }
}

最终效果

image-20230824151858975

注册成功!

服务消费

微服务的⽬的就是将项⽬拆分成多个⼦项⽬,每个⼦项⽬各⾃处理独⽴的内容,那么势必会出现,每个⼦项⽬之间的调⽤问题。在微服务中如何处理的呢?

  • 基本⽅式
  • Ribbon ⽅式
  • feign ⽅式

其中第⼀种和第⼆种都是依赖中⾃带的,⽽第三种⽅式需要引⼊额外的依赖。

1、基本⽅式

通过服务的发现来获取到被调⽤的微服务实例,然后通过 Restful ⽅式来调⽤。

创建消费微服务

没有什么特别的依赖需要引⼊,正常的写法。

搭建项目

image-20231014113241280

配置文件(没有什么特别的依赖需要引⼊,正常的写法)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

controller包

@RestController
@RequestMapping("/provider")
public class ProviderController {

    @GetMapping("/a")
    public String a() {
        return "This is ProviderController`a!";
    }
}

启动类

引⼊ RestTemplate 模版类,注⼊到了 IoC 容器中。

@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudExampleConsumerApplication {
    @Bean
    RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

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

业务类

我们使⽤接⼝-实现类⽅式书写。这⾥着重书写实现类代码:

@Slf4j
@Component
public class ConsumerServiceImpl implements ConsumerService {

    @Resource
    private DiscoveryClient client;

    @Resource
    private RestTemplate restTemplate;



    @Override
    public String a() {
        // 获取到指定微服务名称的所有实例
        List<ServiceInstance> instances = client.getInstances("example-provider");
        // 获取到其中的一个
        ServiceInstance instance = instances.get(0);
        // 获取uri
        String uri = instance.getUri().toString() + "/provider/a";
        log.info("uri:{}", uri);
        // 调用指定uri
        String result = restTemplate.getForObject(uri, String.class);
        return result;
    }
}

启动

启动服务的提供者和服务的消费者,然后在 nacos 控制台中就可以看到:

image-20230824161655762

浏览器访问

image-20230824161311278

负载均衡

通俗的讲,负载均衡就是将负载(⼯作任务,访问请求)进⾏分摊到多个操作单元(服务器,组件)上进⾏执⾏。根据负载均衡发⽣位置的不同,⼀般分为服务端负载均衡和客户端负载均衡。

服务端负载均衡指的是发⽣在服务提供者⼀⽅,⽐如常⻅的 nginx 负载均衡⽽客户端负载均衡指的是发⽣在服务请求的⼀⽅,也就是在发送请求之前已经选好了由哪个实例处理请求。

image-20230824161815493

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

我们这⾥模拟了负载均衡的轮询和随机⽅式:

轮询⽅式
@Slf4j
@Component
public class ConsumerServiceImpl implements ConsumerService {

    @Resource
    private DiscoveryClient client;

    @Resource
    private RestTemplate restTemplate;

    private int i = 0;


//    @Override
//    public String a() {
//        // 获取到指定微服务名称的所有实例
//        List<ServiceInstance> instances = client.getInstances("example-provider");
//        // 获取到其中的一个
//        ServiceInstance instance = instances.get(0);
//        // 获取uri
//        String uri = instance.getUri().toString() + "/provider/a";
//        log.info("uri:{}", uri);
//        // 调用指定uri
//        String result = restTemplate.getForObject(uri, String.class);
//        return result;
//    }

    //轮询方式
    public String a() {
        List<ServiceInstance> instances = client.getInstances("example-provider");
        // 获取到其中的一个
        ServiceInstance instance = instances.get(i);
        if (i == instances.size() - 1) {
            i = 0;
        } else {
            i++;
        }
        // 获取uri
        String uri = instance.getUri().toString() + "/provider/a";
        log.info("uri:{}", uri);
        // 调用指定uri
        return restTemplate.getForObject(uri, String.class);
    }
}

修改一处,方便查看调用哪个端口号

image-20230824181323688

@RestController
@RequestMapping("/provider")
public class ProviderController {
    @Value("${server.port}")
    private Integer port;


    @GetMapping("/a")
    public String a() {
        return "This is ProviderController`a!port:" + port;
    }
}

启动三次SpringCloudExampleProviderApplication

修改不同的端口号

image-20230824181529936

image-20230824182059785

image-20230824182320196

image-20230824182552697

随机⽅式
@Slf4j
@Component
public class ConsumerServiceImpl implements ConsumerService {

    @Resource
    private DiscoveryClient client;

    @Resource
    private RestTemplate restTemplate;

    private int i = 0;


//    @Override
//    public String a() {
//        // 获取到指定微服务名称的所有实例
//        List<ServiceInstance> instances = client.getInstances("example-provider");
//        // 获取到其中的一个
//        ServiceInstance instance = instances.get(0);
//        // 获取uri
//        String uri = instance.getUri().toString() + "/provider/a";
//        log.info("uri:{}", uri);
//        // 调用指定uri
//        String result = restTemplate.getForObject(uri, String.class);
//        return result;
//    }

    //轮询方式
//    public String a() {
//        List<ServiceInstance> instances = client.getInstances("example-provider");
//        // 获取到其中的一个
//        ServiceInstance instance = instances.get(i);
//        if (i == instances.size() - 1) {
//            i = 0;
//        } else {
//            i++;
//        }
//        // 获取uri
//        String uri = instance.getUri().toString() + "/provider/a";
//        log.info("uri:{}", uri);
//        // 调用指定uri
//        return restTemplate.getForObject(uri, String.class);
//    }

    // 随机
    public String a() {
        List<ServiceInstance> instances = client.getInstances("example-provider");
        // 获取到其中的一个
        ServiceInstance instance = instances.get((int) (Math.random() * instances.size()));
        // 获取uri
        String uri = instance.getUri().toString() + "/provider/a";
        log.info("uri:{}", uri);
        // 调用指定uri
        return restTemplate.getForObject(uri, String.class);
    }
}

重新启动SpringCloudExampleConsumerApplication

image-20230824183310858

2、Ribbon ⽅式

基本⽅式需要单独获取实例,并且在多实例时访问较为复杂,那么有没有⼀种简单⽅式呢? Ribbon 是 Spring Cloud 的⼀个组件, 它可以让我们使⽤⼀个注解就能轻松的搞定负载均衡。

需要注意的是,负载均衡需要引⼊ @LoadBalanced 注解。 这时候将服务的提供者启动多次,会发现:

这时候将服务的提供者启动多次,会发现:

image-20230824185229193

访问消费者的控制类:

image-20230824185349159

搭建项目

image-20230824194319714

引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

配置文件

server:
  port: 8073
spring:
  application:
    name: example-consumer-ribbon
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848

启动类

@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudExampleConsumerRibbonApplication {

    @Bean
    //负载均衡
    @LoadBalanced
    RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

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

service包

public interface ConsumerService {
    String a();
}
@Service
@Slf4j
public class ConsumerServiceImpl implements ConsumerService {

    @Resource
    private RestTemplate restTemplate;

    @Override
    public String a() {
        String url = "http://example-provider/provider/a";
        return restTemplate.getForObject(url, String.class);
    }
}

controller包

@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    @Resource
    private ConsumerService service;

    @GetMapping("/a")
    public String a() {
        return service.a();
    }
}

测试结果

注意:默认是轮询方式

image-20230824192600456

Ribbon ⽀持的负载均衡策略

Ribbon 内置了多种负载均衡策略,内部负载均衡的顶级接⼝为 com.netflix.loadbalancer.IRule

具体的负载策略如下图所示:

image-20230824192722570

我们可以通过修改配置来调整 Ribbon 的负载均衡策略,具体代码如下:

service-product: # 调⽤的提供者的名称
 ribbon:
 nFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
案例

此案例(购物车案例)基于订单查询后的信息

image-20230824194822739

1、创建公共模块

搭建项目

image-20230824194155680

导入依赖

 <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.6.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.32</version>
        </dependency>
    </dependencies>

bean包

@TableName(value = "`order`")
@Data
public class Order implements Serializable {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String createdTime;
    private String subject;
    private String uid;
    private Double totalPrice;
    private Integer state;
    private String recipientAddress;
    private String recipientPhone;
    private String recipientName;
    @TableField(exist = false)
    private JsonResult user;
}
@Data
public class User implements Serializable {
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    private String username;
    private String password;
    private String nickname;
}

util包

@Data
@NoArgsConstructor
@AllArgsConstructor
public class JsonResult<T> implements Serializable {
    private Boolean success;
    private String error;
    private Integer code;
    private T data;
}
public class ResultTool {

    public static JsonResult success() {
        return new JsonResult(true, null, 200, null);
    }

    public static JsonResult success(Object data) {
        return new JsonResult(true, null, 200, data);
    }

    public static JsonResult fail(String msg) {
        return new JsonResult(false, msg, 500, null);
    }

    public static JsonResult fail(int code, String msg) {
        return new JsonResult(false, msg, code, null);
    }
}

配置文件(application-sql.yml)

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shoppingcart
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

注意:此时需要两个微服务——>订单微服务和用户微服务

​ 订单微服务(消费者)

​ 用户微服务(提供者)

2、创建订单微服务模块

搭建项目

image-20230824210714145

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>spring-cloud-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

配置文件

server:
  port: 8001
spring:
  application:
    name: case01-order
  profiles:
    active: sql #引入其他yml文件 application-sql.yml
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848

mapper包

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}

service包

public interface OrderService extends IService<Order> {

    JsonResult find();

}
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
    @Resource
    private RestTemplate restTemplate;

    @Override
    public JsonResult find() {
        List<Order> list = list();
        list.forEach(e -> {
            String uri = "http://case01-user/user/" + e.getUid();
            JsonResult result = restTemplate.getForObject(uri, JsonResult.class);
            e.setUser(result);
        });
        return ResultTool.success(list);
    }
}

controller包

@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource
    private OrderService service;

    @GetMapping
    public JsonResult find() {
        return service.find();
    }
}

启动类

@SpringBootApplication
@EnableDiscoveryClient
public class SpringCloudCase01OrderApplication {

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

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

3、创建用户微服务模块

搭建项目

image-20230824210745698

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>spring-cloud-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

配置文件

server:
  port: 8011
spring:
  application:
    name: case01-user
  profiles:
    active: sql #引入其他yml文件 application-sql.yml
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848

mapper包

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

service包

public interface UserService extends IService<User> {

    JsonResult findById(String id);
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    public JsonResult findById(String id) {
        return ResultTool.success(baseMapper.selectById(id));
    }
}

controller包

@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService service;
    // http://localhost:8011/user/{id}
    @GetMapping("/{id}")
    public JsonResult find(@PathVariable("id") String id) {
        return service.findById(id);
    }
}

启动类

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

4、测试结果

启动两项目

image-20230824210510822

APiPost测试

image-20230824210437626

成功拿到用户信息!

3、Feign ⽅式

Feign 是 Spring Cloud 提供的⼀个声明式的伪 Http 客户端, 它使得调⽤远程服务就像调⽤本地服务⼀样简 单,只需要创建⼀个接⼝并添加⼀个注解即可。

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

引⼊依赖

<dependencies>
<!--引⼊openfeign依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
 <!--负载均衡的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>

业务类

@FeignClient(value = "example-producer")
public interface CustomerService {
@GetMapping("/producer/a")
String a();
}

启动类

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

@FeignClient 注解

由于 SpringCloud 采⽤分布式微服务架构,难免在各个⼦模块存在模块⽅法互相调⽤的情况。⽐如 service-admin 服务要调⽤ service-card 服务的⽅法。

  • @FeignClient() 注解就是为了解决这个问题的。
  • @FeignClient() 注解的源码要求它必须在 Interface 接⼝上使⽤。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface FeignClient {

@AliasFor("name")
String value() default "";
    
String contextId() default "";
    
@AliasFor("value")
String name() default "";
    
@Deprecated
String qualifier() default "";
    
String[] qualifiers() default {};
    
String url() default "";
    
boolean decode404() default false;
    
Class<?>[] configuration() default {};
    
Class<?> fallback() default void.class;
    
Class<?> fallbackFactory() default void.class;
    
String path() default "";
    
boolean primary() default true;
}
  • value : 服务名
  • name : 指定 FeignClient 的名称,如果项⽬使⽤了 Ribbon , name 属性会作为微服务的名称,⽤于服务 发现
  • url : ⼀般⽤于调试,可以⼿动指定 @FeignClient 调⽤的地址
  • decode404 :当发⽣http 404错误时,如果该字段位 true ,会调⽤ decoder 进⾏解码,否则抛出 FeignException
  • configuration : Feign 配置类,可以⾃定义 Feign 的 Encoder 、 Decoder 、 LogLevel 、 Contract
  • fallback : 定义容错的处理类,当调⽤远程接⼝失败或超时时,会调⽤对应接⼝的容错逻辑, fallback 指 定的类必须实现 @FeignClient 标记的接⼝
  • fallbackFactory : ⼯⼚类,⽤于⽣成 fallback 类示例,通过这个属性我们可以实现每个接⼝通⽤的容错 逻辑,减少重复的代码
  • path : 定义当前 FeignClient 的统⼀前缀

此外还要求服务的启动类要有 @EnableFeignClients 注解才能使 Fegin ⽣效。

案例1

搭建项目

image-20230825121157435

导入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

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

配置文件

server:
  port: 8075
spring:
  application:
    name: example-consumer-feign
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848

启动类

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

service包

public interface FeignService {
    String a();

    String b(String name);

    String c(String name);
}
@Service
public class FeignServiceImpl implements FeignService {
    @Resource
    private ProviderFeign feign;

    @Override
    public String a() {
        return feign.a();
    }

    @Override
    public String b(String name) {
        return feign.b(name);
    }

    @Override
    public String c(String name) {
        return feign.c(name);
    }
}

controller包

@RequestMapping("/feign")
@RestController
public class FeignController {


    @Resource
    private FeignService service;

    @GetMapping("/a")
    public String a() {
        return service.a();
    }

    @GetMapping("/b")
    public String b() {
        return service.b("admin");
    }

    @GetMapping("/c")
    public String c() {
        return service.c("china");
    }
}

添加

image-20230825120745877

测试结果

1image-20230825121053690

2

image-20230825121034587

3

image-20230825121008991

案例2

前期准备

1、在公共模块准备一个数据源(配置多数据源)

image-20230827150649536

image-20230827150729696

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2、书写bean包

image-20230827150911633

Demp

@Data
public class Dept implements Serializable {
    @TableId(type = IdType.AUTO)
    private Integer deptNo;
    private String dname;
    private String loc;
    @TableField(exist = false)
    private Set<Emp> emps;
}

Emp

@TableName("emp")
@Data
public class Emp implements Serializable {
    @TableId(value = "empno", type = IdType.AUTO)
    private Integer empNo; // 自动---emp_no
    private String ename;
    private String job;
    private Integer mgr;
    private String hireDate;
    private Double sal;
    private Double comm;
    private Integer deptNo;
    @TableLogic(delval = "0", value = "1")
    private Integer state;
}

搭建项目

image-20230827151152782

case02-dept

配置文件

server:
  port: 8015
spring:
  application:
    name: case02-dept
  profiles:
    active: test #引入其他yml文件 application-bookstore.yml
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848

mapper包

@Mapper
public interface DeptMapper extends BaseMapper<Dept> {
}

service包

public interface DeptService extends IService<Dept> {

    JsonResult findAll();
}
@Slf4j
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {

    @Resource
    private EmpFeign empFeign;
    
    //第一种
//    @Override
//    public JsonResult findAll() {
//        List<Dept> list = list();
//        list.forEach(e -> {
//            JsonResult<List<Emp>> result = empFeign.findEmpByDeptId(e.getDeptNo());
//            log.info("Result:{}", result);
//            Set<Emp> set = new HashSet<>(result.getData());
//            e.setEmps(set);
//        });
//        return ResultTool.success(list);
//    }
    
    //第二种
    @Override
    public JsonResult findAll() {
        List<Dept> list = list();
        list.forEach(e -> {
            JsonResult result = empFeign.findEmpByDeptId(e.getDeptNo());
            List<Emp> date = (List<Emp>) result.getData();
            Set<Emp> set = new HashSet<>(date);
            e.setEmps(set);
        });
        return ResultTool.success(list);
    }
}

feign包

@FeignClient(value = "case02-emp")
public interface EmpFeign {

    @GetMapping("/emp/{id}")

    JsonResult findEmpByDeptId(@PathVariable("id") int id);

}

controller包

@RestController
@RequestMapping("/dept")
public class DeptController {

    @Resource
    private DeptService service;

    @GetMapping
    public JsonResult find() {
        return service.findAll();
    }
}

启动类

@SpringBootApplication
//启用Feign客户端,从而可以在类中使用Feign来发送HTTP请求
@EnableFeignClients
//服务发现和负载均衡
@EnableDiscoveryClient
public class SpringCloudCase02DeptApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCloudCase02DeptApplication.class, args);
    }
}

case02-emp

配置文件

server:
  port: 8005
spring:
  application:
    name: case02-emp
  profiles:
    active: test #引入其他yml文件 application-bookstore.yml
  cloud:
    nacos:
      discovery:
        namespace: 0e2f3694-d3ef-4a39-8aa6-5622872228ce
        server-addr: 192.168.65.3:8848

mapper包

@Mapper
public interface EmpMapper extends BaseMapper<Emp> {
}

service包

public interface EmpService extends IService<Emp> {

    JsonResult findEmpByDeptNo(int id);

}
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {

    @Override
    public JsonResult findEmpByDeptNo(int id) {
        QueryWrapper<Emp> wrapper = new QueryWrapper<>();
        wrapper.eq("deptNo", id);
        return ResultTool.success(list(wrapper));
    }
}

feign包

controller包

public interface EmpService extends IService<Emp> {

    JsonResult findEmpByDeptNo(int id);

    JsonResult removeEmp(int empNo, int deptNo);
}

启动类

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

测试结果

启动两项目

image-20230827154536598

成功拿到数据!

案例3

基于案例2的基础上,加入redis

导入依赖

<!--        导入redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置文件

spring:
  redis:
    host: 192.168.65.3

1、错误演示

case02-emp

service包

public interface EmpService extends IService<Emp> {

    JsonResult findEmpByDeptNo(int id);

    JsonResult removeEmp(int empNo,int deptNo);

}
@Service
public class EmpServiceImpl extends ServiceImpl<EmpMapper, Emp> implements EmpService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //用于根据部门编号查找员工信息
    public JsonResult findEmpByDeptNo(int id) {
        //从Redis中获取数据
        String s = stringRedisTemplate.opsForValue().get("findEmpByDeptNo:" + id);
        if (s == null) {//如果数据不存在,则执行下面的代码
            //QueryWrapper对象可以用于生成SQL语句,用于查询数据库中的数据
            QueryWrapper<Emp> wrapper = new QueryWrapper();
            wrapper.eq("deptNo", id);
            //用于解析数据
            List<Emp> list = list(wrapper);
            //将查询结果存储到Redis中
            /*
            stringRedisTemplate.opsForValue().set方法表示对Redis中的value类型的数据进行操作,"findEmpByDeptNo:" + id表示Redis中的key,
            list表示查询结果,使用JSON.stringify方法将查询结果转换为字符串类型,存储到Redis中。
             */
            stringRedisTemplate.opsForValue().set("findEmpByDeptNo:" + id, JSONArray.toJSONString(list));
            return ResultTool.success(list);
        }
        //用于解析Redis中存储的查询结果
        List<Emp> list = JSONArray.parseArray(s, Emp.class);
        return ResultTool.success(list);
    }

    //用于删除员工信息
    public JsonResult removeEmp(int empNo, int deptNo) {
        //如果删除成功,则执行下面的代码
        int row = getBaseMapper().deleteById(empNo);
        if (row > 0) {
            //从Redis中删除查询结果
            stringRedisTemplate.delete("findEmpByDeptNo:" + deptNo);
        }
        return ResultTool.success();
    }

controller包

@RestController
@RequestMapping("/emp")
public class EmpController {

    @Resource
    private EmpService service;

    public JsonResult find(@PathVariable("id") int id) {
        return service.findEmpByDeptNo(id);
    }


    @DeleteMapping("/{empNo}/{deptNo}")
    //用于删除员工信息
    public JsonResult remove(@PathVariable("empNo") int empNo, @PathVariable("deptNo") int deptNo) {
        /*调用了EmpService中的removeEmp方法,并将参数传递给该方法,
        用于删除员工信息。最后,该方法返回成功信息*/
        return service.removeEmp(empNo, deptNo);
    }
}

case02-emp

service包

image-20230827162356585

//第二种
@Cacheable(cacheNames = "findAll",key = "0")
public JsonResult findAll() {
    List<Dept> list = list();
    list.forEach(e -> {
        JsonResult result = empFeign.findEmpByDeptId(e.getDeptNo());
        List<Emp> date = (List<Emp>) result.getData();
        Set<Emp> set = new HashSet<>(date);
        e.setEmps(set);
    });
    return ResultTool.success(list);
}

启动类

image-20230827162626671

测试1

image-20230827162739338

第一次

image-20230827162813494

从数据库成功拿到数据

第二次

image-20230827163232185

后端控制台

image-20230827163213328

说明缓存起作用了

1、删除

image-20230827163415316

删除成功

image-20230827163503707

后端控制台

image-20230827163543299

查看数据库,数据库中的数据也成功删除

image-20230827163715075

再次查询,发现没有删除

image-20230827163808504

此时,出现问题,两个项目的数据库没有同步,所以查询结果就会出现错误。

2、正确演示

解决方法:

部门微服务不需要重查,调用员工微服务重查数据,请求员工微服务获取到该部门的员工信息

service包

@Slf4j
@Service
public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements DeptService {

    @Resource
    private EmpFeign empFeign;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //第一种
//    @Override
//    public JsonResult findAll() {
//        List<Dept> list = list();
//        list.forEach(e -> {
//            JsonResult<List<Emp>> result = empFeign.findEmpByDeptId(e.getDeptNo());
//            log.info("Result:{}", result);
//            Set<Emp> set = new HashSet<>(result.getData());
//            e.setEmps(set);
//        });
//        return ResultTool.success(list);
//    }
    
    
        /*
        1、该方法用于查询所有部门及其对应的员工信息。具体的业务逻辑如下:
        2、判断是否存在缓存,如果存在,则直接从缓存中获取数据,否则从数据库中查询数据。
        3、将查询到的数据存入缓存,并将数据转换为JSON格式,存储在Redis中。
        4、遍历每个部门,使用Feign调用员工微服务,获取该部门的员工信息,并将数据存储在部门对象的对应属性中。
        5、返回所有部门及其对应的员工信息。
         */
    public JsonResult findAll() {
        boolean flag = stringRedisTemplate.hasKey("findAllDept");
        List<Dept> list;
        // 本表的同步
        if (!flag) {
            list = list();
            stringRedisTemplate.opsForValue().set("findAllDept", JSONArray.toJSONString(list));
        } else {
            String s = stringRedisTemplate.opsForValue().get("findAllDept");
            list = JSONArray.parseArray(s, Dept.class);
        }
        // 请求员工微服务获取到该部门的员工信息
        list.forEach(e -> {
            JsonResult result = empFeign.findEmpByDeptId(e.getDeptNo());
            List<Emp> data = (List<Emp>) result.getData();
            Set<Emp> set = new HashSet<>(data);
            e.setEmps(set);
        });
        return ResultTool.success(list);
    }
}

测试2

首先去清redis中的缓存

image-20230827165222434

查询要删除的对象

image-20230827165321921

执行删除,删除成功

image-20230827165434376

再次查询,没有7934,说明删除成功,项目之间数据同步

image-20230827165549675

image-20230827165806972

image-20230827165854538

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值