文章目录
服务治理
什么是服务治理
服务治理是微服务架构中最核⼼最基本的模块。⽤于实现各个微服务的⾃动化注册与发现。
服务注册:在服务治理框架中,都会构建⼀个注册中⼼,每个服务单元向注册中⼼登记⾃⼰提供服务的详细信息。 并在注册中⼼形成⼀张服务的清单,服务注册中⼼需要以⼼跳的⽅式去监测清单中的服务是否可⽤,如果不可⽤, 需要在服务清单中剔除不可⽤的服务。
服务发现:服务调⽤⽅向服务注册中⼼咨询服务,并获取所有服务的实例清单,实现对具体服务实例的访问。
通过上⾯的调⽤图会发现,除了微服务,还有⼀个组件是服务注册中⼼,它是微服务架构⾮常重要的⼀个组件,在微服务架构⾥主要起到了协调者的⼀个作⽤。
注册中⼼⼀般包含如下⼏个功能:
- 服务发现:
- 服务注册:保存服务提供者和服务调⽤者的信息。
- 服务订阅:服务调⽤者订阅服务提供者的信息,注册中⼼向订阅者推送提供者的信息。
- 服务配置:
- 配置订阅:服务提供者和服务调⽤者订阅微服务相关的配置。
- 配置下发:主动将配置推送给服务提供者和服务调⽤者。
-
服务健康检测
检测服务提供者的健康情况,如果发现异常,执⾏服务剔除。
常⻅的注册中⼼
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 官⽹上下载:
最好用迅雷下载,浏览器一般很慢!
第二种:
用wget命令的这种方式下载
部署到 Linux
上传到 Linux 中,解压。
在linux中新建Springcloud文件夹,再新建nacos文件夹
解压命令
tar zxvf nacos-server-2.2.3.tar.gz
1、搭建数据库环境
创建数据库 nacos_config ,将下载⽂件中的 sql 中的表创建到 nacos_config数据库中。
打开所对应的文件,新建数据库
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;
Nacos 环境配置
在配置 nacos 服务环境前,需搭建 JDK 环境,如未搭建请⾃⾏解决。
⾸先:修改 conf ⽂件夹下的 application.properties ⽂件:
如果出现问题
最后:修改 bin ⽂件夹下的启动项 start.sh ⽂件:
启动和关闭
#启动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
启动演示
关闭演示
查看⽇志演示
访问
在浏览器中访问:http://IP:8848/nacos
账号密码: nacos
创建微服务项⽬
我们创建⼏个微服务的项⽬,将这些项⽬注册到服务治理中⼼。
创建命名空间
创建⽗项⽬
⽗项⽬是⼀个资源管理项⽬,主要⼯作是管理依赖。
<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>
创建⼦项⽬
⼦项⽬继承⽗项⽬,也会引⽤⽗项⽬的配置。
搭建项目
依赖管理
<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>
配置文件
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);
}
}
最终效果
注册成功!
服务消费
微服务的⽬的就是将项⽬拆分成多个⼦项⽬,每个⼦项⽬各⾃处理独⽴的内容,那么势必会出现,每个⼦项⽬之间的调⽤问题。在微服务中如何处理的呢?
- 基本⽅式
- Ribbon ⽅式
- feign ⽅式
其中第⼀种和第⼆种都是依赖中⾃带的,⽽第三种⽅式需要引⼊额外的依赖。
1、基本⽅式
通过服务的发现来获取到被调⽤的微服务实例,然后通过 Restful ⽅式来调⽤。
创建消费微服务
没有什么特别的依赖需要引⼊,正常的写法。
搭建项目
配置文件(没有什么特别的依赖需要引⼊,正常的写法)
<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 控制台中就可以看到:
浏览器访问
负载均衡
通俗的讲,负载均衡就是将负载(⼯作任务,访问请求)进⾏分摊到多个操作单元(服务器,组件)上进⾏执⾏。根据负载均衡发⽣位置的不同,⼀般分为服务端负载均衡和客户端负载均衡。
服务端负载均衡指的是发⽣在服务提供者⼀⽅,⽐如常⻅的 nginx 负载均衡⽽客户端负载均衡指的是发⽣在服务请求的⼀⽅,也就是在发送请求之前已经选好了由哪个实例处理请求。
我们在微服务调⽤关系中⼀般会选择客户端负载均衡,也就是在服务调⽤的⼀⽅来决定服务由哪个提供者执⾏。
我们这⾥模拟了负载均衡的轮询和随机⽅式:
轮询⽅式
@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);
}
}
修改一处,方便查看调用哪个端口号
@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
修改不同的端口号
随机⽅式
@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
2、Ribbon ⽅式
基本⽅式需要单独获取实例,并且在多实例时访问较为复杂,那么有没有⼀种简单⽅式呢? Ribbon 是 Spring Cloud 的⼀个组件, 它可以让我们使⽤⼀个注解就能轻松的搞定负载均衡。
需要注意的是,负载均衡需要引⼊ @LoadBalanced 注解。 这时候将服务的提供者启动多次,会发现:
这时候将服务的提供者启动多次,会发现:
访问消费者的控制类:
搭建项目
引入依赖
<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();
}
}
测试结果
注意:默认是轮询方式
Ribbon ⽀持的负载均衡策略
Ribbon 内置了多种负载均衡策略,内部负载均衡的顶级接⼝为 com.netflix.loadbalancer.IRule
具体的负载策略如下图所示:
我们可以通过修改配置来调整 Ribbon 的负载均衡策略,具体代码如下:
service-product: # 调⽤的提供者的名称
ribbon:
nFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
案例
此案例(购物车案例)基于订单查询后的信息
1、创建公共模块
搭建项目
导入依赖
<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、创建订单微服务模块
搭建项目
导入依赖
<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、创建用户微服务模块
搭建项目
导入依赖
<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、测试结果
启动两项目
APiPost测试
成功拿到用户信息!
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
搭建项目
导入依赖
<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");
}
}
添加
测试结果
1
2
3
案例2
前期准备
1、在公共模块准备一个数据源(配置多数据源)
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包
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;
}
搭建项目
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);
}
}
测试结果
启动两项目
成功拿到数据!
案例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包
//第二种
@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);
}
启动类
测试1
第一次
从数据库成功拿到数据
第二次
后端控制台
说明缓存起作用了
1、删除
删除成功
后端控制台
查看数据库,数据库中的数据也成功删除
再次查询,发现没有删除
此时,出现问题,两个项目的数据库没有同步,所以查询结果就会出现错误。
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中的缓存
查询要删除的对象
执行删除,删除成功
再次查询,没有7934,说明删除成功,项目之间数据同步