微服务之OpenFeign、hystrix熔断降级、loadbalancer负载均衡

微服务学习顺序:
微服务之Nacos
微服务之OpenFeign
微服务之网关

简介

微服务架构中使用OpenFeign进行服务间的相互调用,OpenFeign提供了一种简洁的方式来定义和处理服务间的调用。OpenFeign作为一个声明式的、模块化的HTTP客户端,通过「接口」的定义和「注解」的使用,简化了微服务之间的通信调用。

OpenFeign 能自动根据服务名找到目标服务的网络地址,实现跨服务的 HTTP 通信。

案例openfeign

子项目bill-7780准备接口

此处项目在我的另一篇文章中的项目的基础上进行创建:微服务之Nacos

在子项目bill-7780的SmbmsBillService中添加根据供应商id查询接口方法

java 复制代码
package com.lp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.lp.entity.SmbmsBill;
import com.lp.utils.ResultAJAX;

public interface SmbmsBillService extends IService<SmbmsBill> {
    public ResultAJAX findList();

    public ResultAJAX findListByProId(Integer id);

}

实现该接口方法

java 复制代码
package com.lp.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lp.entity.SmbmsBill;
import com.lp.mapper.SmbmsBillMapper;
import com.lp.service.SmbmsBillService;
import com.lp.utils.ResultAJAX;
import org.springframework.stereotype.Service;

@Service
public class SmbmsBillServiceImpl extends ServiceImpl<SmbmsBillMapper, SmbmsBill> implements SmbmsBillService {
    @Override
    public ResultAJAX findList() {
        return ResultAJAX.success(this.list());
    }

    @Override
    public ResultAJAX findListByProId(Integer id) {
        return ResultAJAX.success(this.list(new QueryWrapper<SmbmsBill>().eq("providerId",id)));
    }
}

创建一个api包,用于专门对外提供接口。创建BillServiceAPI,此时只是一个普通的controller。

java 复制代码
package com.lp.api;

import com.lp.service.SmbmsBillService;
import com.lp.utils.ResultAJAX;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/billApi")
public class BillServiceAPI {
    @Autowired
    private SmbmsBillService smbmsBillService;

    @GetMapping("/listByProId")
    public ResultAJAX listByProId(@RequestParam("id") Integer id){ //必须添加@RequestParam注解
        System.out.println("id==="+id);
        return smbmsBillService.findListByProId(id);
    }

}

子项目provider-7790调用接口

以前分布式中如果我们想在provider-7790中调用bill-7780中的接口,需要在provider-7790的pom文件中中引入bill-7780。

但现在项目provider-7790和项目bill-7780应该是互相独立没有关联的(比如这两个项目分别部署在不同的电脑或服务器上),是没有办法在provider-7790的pom文件中引入bill-7780,并调用bill-7780提供的接口。

这时就需要用到远程调用了,用子项目provider-7790调用上面bill-7780中创建的BillServiceAPI中的接口方法。

修改application.yml文件,注册中心端口配置改为8850

yml 复制代码
server:
  port: 7790
spring:
  application:
    name: provider-7790 #往注册中心放的服务名称,一般与项目名保持一致(服务名称不能重复)
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8850
        username: nacos
        password: nacos

OpenFeign 是 Spring Cloud 的组件,而非 Spring Cloud Alibaba 的组件。按步骤(1、2、3)在父级项目cloud-alibaba-test的pom文件引入OpenFeign依赖。

xml 复制代码
 <?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>cloud-alibaba-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cloud-alibaba-test</name>
    <description>cloud-alibaba-test</description>

    <!--指定子模块-->
    <modules>
        <module>bill-7780</module>
        <module>provider-7790</module>
        <module>cloud-common</module>
    </modules>
    <!--指定父级项目打包方式-->
    <packaging>pom</packaging>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.6</spring-boot.version>
        <!--1. 指定Spring Cloud版本-->
        <spring-cloud.version>2021.0.5</spring-cloud.version>
        <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
    </properties>
    <dependencies>
        <!--3. 引入OpenFeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--2. 引入Spring Cloud依赖管理(核心) -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <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>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.example.cloudalibabatest.CloudAlibabaTestApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

在父级项目cloud-alibaba-test引入负载均衡loadbalancer依赖(无论是否用到负载均衡,若想使用OpenFeign就必须引入loadbalancer依赖)

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>

在子项目provider-7790的pom文件中引入cloud-common公共内容(为了使用公共返回类ResultAJAX)

xml 复制代码
<dependency>
	<groupId>com.lp</groupId>
	<artifactId>cloud-common</artifactId>
	<version>0.0.1-SNAPSHOT</version>
</dependency>

创建service.api包,并创建远程调用接口(创建api包是用于把这个项目自己的service和远程调用的service区分开来)该接口不需要写实现,它的实现就是在前面子项目bill-7780中创建的BillServiceAPI(需要加注解@FeignClient

java 复制代码
package com.lp.service.api;

import com.lp.utils.ResultAJAX;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;


@FeignClient(name = "bill-7780") //name:为服务提供者的服务名
public interface SmbmsBillService {

    @GetMapping("/billApi/listByProId")//要与BillServiceAPI类中的方法使用的请求方式一致,比如均为@GetMapping或@RequestMapping
    public ResultAJAX listByProId(@RequestParam("id") Integer id); //必须添加@RequestParam注解
}

如果要调用的接口方法很多,就如上面的@GetMapping("/billApi/listByProId"),每次都要写/billApi,很麻烦。可以在@FeignClient注解中添加path属性,如:@FeignClient(name = "bill-7780", path = "billApi"),这样可以简化方法上的如@GetMapping注解中的请求路径的书写:

创建控制器

java 复制代码
package com.lp.controller;

import com.lp.service.api.SmbmsBillService;
import com.lp.utils.ResultAJAX;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/bill")
public class SmbmsBillController {

    @Autowired
    private SmbmsBillService smbmsBillService;

    @GetMapping("/listByProId")
    public ResultAJAX listByProId(Integer id){
        return smbmsBillService.listByProId(id);
    }

}

在启动类添加注解@EnableFeignClients,开启OpenFeign。

java 复制代码
package com.lp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class Provider7790Application {

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

}

启动项目,测试远程访问接口

首先要保证nacos开启,启动启动项目bill-7780和项目provider-7790,浏览器访问http://localhost:7790/bill/listByProId?id=1

会发现我们访问的是provider-7790的接口方法,但内部会远程访问bill-7780的接口方法。

hystrix熔断降级

前面我们是通过provider-7790调用的bill-7780的接口方法,那么可能会出现一种情况就是provider-7790调不到bill-7780中的接口方法(比如说bill-7780服务挂掉了),调不到就会报错,前端页面就会显示500错误,这不是我们想要的。我们想要美化或者说处理一下这个错误,这就需要用到熔断降级了。在调用者(比如provider-7790)中添加降级处理。

在子项目provider-7790的pom文件中引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.9.RELEASE</version>
</dependency>

在配置文件中开启服务降级:

yml 复制代码
server:
  port: 7790
spring:
  application:
    name: provider-7790 #往注册中心放的服务名称,一般与项目名保持一致(服务名称不能重复)
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8850
        username: nacos
        password: nacos
feign:
  circuitbreaker:
    enabled: true # 开启熔断处理

编写前面provider-7790中service.api包下SmbmsBillService的实现,一旦远程调用失败,就会自动执行该实现类下的方法

java 复制代码
package com.lp.service.api.impl;

import com.lp.service.api.SmbmsBillService;
import com.lp.utils.ResultAJAX;
import org.springframework.stereotype.Component;

//降级处理:如果服务提供者挂了,则调用此降级处理类
//要添加@Component注解,不用@Service是因为它是Spring中用于标识业务逻辑层组件的注解(其实能用),而@Component语义更通用,用于标识任意受Spring管理的组件
@Component
public class SmbmsBillServiceImpl implements SmbmsBillService {
    @Override
    public ResultAJAX listByProId(Integer id) {
        return ResultAJAX.error("接口调用失败,服务降级处理。。。");
    }
}

虽然SmbmsBillServiceImpl是SmbmsBillService的实现,但它不是务逻辑层,我们不会用@Service,而是用语义更通用的@Component。

修改SmbmsBillService接口中@FeignClient,使用fallback属性指定服务降级处理

java 复制代码
package com.lp.service.api;

import com.lp.service.api.impl.SmbmsBillServiceImpl;
import com.lp.utils.ResultAJAX;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;


@FeignClient(name = "bill-7780", path = "billApi", fallback = SmbmsBillServiceImpl.class) //name:为服务提供者的服务名
public interface SmbmsBillService {

    @GetMapping("/listByProId")//要与BillServiceAPI类中的方法使用的请求方式一致,比如均为@GetMapping或@RequestMapping
    public ResultAJAX listByProId(@RequestParam("id") Integer id); //必须添加@RequestParam注解
}

启动项目bill-7780和项目provider-7790(需要nacos处在启动状态),浏览器访问http://localhost:7790/bill/listByProId?id=1,访问成功

关闭项目bill-7780,再次访问http://localhost:7790/bill/listByProId?id=1,模拟接口调用失败

会发现降级处理起作用了。

loadbalancer负载均衡

假如消费者(如provider-7790)请求调用提供者(如bill-7780)的提供的接口,一个提供者压力太大,为了均衡,再加一个提供者,在消费者中做负载均衡。

再准备一个提供者

在父级项目cloud-alibaba-test中复制一份bill-7780,命名为bill-7781

引入bill-7781的pom文件

批量修改替换:

点击选中bill-7781项目 -> 点击上方的Edit -> 选中Find -> Replace in Files

进行替换

还有将bill-7780替换为为bill-7781,具体修改结合自身实际情况。

修改bootstrap.yml中配置的端口为7781,服务名与bill-7780中配置的保持一致

yml 复制代码
server:
  port: 7781
spring:
  profiles:
    active: dev
  application:
    name: bill-7780 #与项目bill-7780的服务名保持一致
  # 注册服务
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8850 # nacos注册中心地址,可以配置多个
        username: nacos
        password: nacos
      config:
        server-addr: 127.0.0.1:8850 # nacos配置中心地址
        file-extension: yaml
        group: DEFAULT_GROUP # 配置分组
        namespace: 935f0c39-db1c-4fc0-a997-35cd6f929740 # 命名空间ID

注意:将两个提供者(bill-7780和bill-7781)服务名改为一致端口号不一致,消费者(provider-7790)中调用服务(一个provider两个实例)

修改启动类

java 复制代码
package com.lp;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.lp.mapper")
public class Bill7781Application {

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

}

添加项目bill-7781到父级中,即在父级pom文件引入bill-7781模块

java 复制代码
<modules>
    <module>bill-7780</module>
    <module>bill-7781</module>
    <module>provider-7790</module>
    <module>cloud-common</module>
</modules>

负载均衡轮询

在子项目provider-7790的pom文件中引入依赖

xml 复制代码
<!--客户端负载均衡loadbalancer-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

启动bill-7780项目、bill-7781项目和provider-7790项目

浏览器多次访问http://localhost:7790/bill/listByProId?id=1,会发现控制台中bill-7780和bill-7781的接口被依次调用(轮询)。

负载均衡随机

在项目provider-7790的config包中添加配置类RandomLoadBalancerConfig,实现随机策略

java 复制代码
package com.lp.config;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class RandomLoadBalancerConfig {
    @Bean
    ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment, LoadBalancerClientFactory
            loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(loadBalancerClientFactory
                .getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}

在SmbmsBillService中配置注解

java 复制代码
package com.lp.service.api;

import com.lp.config.RandomLoadBalancerConfig;
import com.lp.service.api.impl.SmbmsBillServiceImpl;
import com.lp.utils.ResultAJAX;
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;


@FeignClient(name = "bill-7780", path = "billApi", fallback = SmbmsBillServiceImpl.class) //name:为服务提供者的服务名
@LoadBalancerClient(name = "bill-7780", configuration = RandomLoadBalancerConfig.class)
public interface SmbmsBillService {

    @GetMapping("/listByProId")//要与BillServiceAPI类中的方法使用的请求方式一致,比如均为@GetMapping或@RequestMapping
    public ResultAJAX listByProId(@RequestParam("id") Integer id); //必须添加@RequestParam注解
}

重启provider-7790,浏览器多次访问http://localhost:7790/bill/listByProId?id=1,会发现随机调用bill-7780和bill-7781的接口。

相关推荐
fanly1117 小时前
Surging AI Agent 完整产品介绍
微服务·microservice
蝎子莱莱爱打怪7 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking8 天前
Java微服务练习方式
java·后端·微服务
米丘11 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质14 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
霸道流氓气质14 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
地瓜伯伯14 天前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
Devin~Y14 天前
大厂 Java 面试实录:从音视频内容社区到 AI RAG 的全链路技术设计
java·spring boot·redis·spring cloud·微服务·kafka·音视频
递归尽头是星辰14 天前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理
就改了15 天前
Windows 环境 SkyWalking 完整实操教程
windows·微服务·skywalking