微服务学习顺序:
微服务之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的接口。

