微服务之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的接口。

相关推荐
小坏讲微服务10 小时前
Docker-compose 搭建Maven私服部署
java·spring boot·后端·docker·微服务·容器·maven
陈果然DeepVersion13 小时前
Java大厂面试真题:Spring Boot+微服务+AI智能客服三轮技术拷问实录(六)
java·spring boot·redis·微服务·面试题·rag·ai智能客服
Wang's Blog15 小时前
Nestjs框架: 微服务项目工程结构优化与构建方案
微服务·云原生·架构·nestjs
回家路上绕了弯19 小时前
内容平台核心工程:最热帖子排行实现与用户互动三元组存储查询
后端·微服务
_Walli_20 小时前
k8s集群搭建(七)-------- 微服务间的调用
微服务·容器·kubernetes
装不满的克莱因瓶20 小时前
【Java架构师】各个微服务之间有哪些调用方式?
java·开发语言·微服务·架构·dubbo·restful·springcloud
陈果然DeepVersion1 天前
Java大厂面试真题:Spring Boot+Kafka+AI智能客服场景全流程解析(十一)
java·spring boot·微服务·ai·kafka·面试题·rag
pan3035074791 天前
GRPC详解
微服务·grpc
喵个咪2 天前
开箱即用的GO后台管理系统 Kratos Admin - 站内信
后端·微服务·go