一、熔断降级
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
服务调用现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
二、熔断&降级区分
2.1 服务降级举例
下单接口调用远程更新产品销量接口,当某一天更新产品销量接口挂了,下单服务会在本地提供一个降级接口来记录xx订单xx产品需要更新销量,这条记录可以写入对应的记录数据表或者消息中间件中,然后定时扫描来完成产品销量更新的任务,这就是服务降级。
2.2 服务熔断举例
远程更新产品销量接口暂时已经挂了,如果下单接口每次下单时还是去请求网络调用远程更新产品销量接口,然后再走本地降级接口,这样会浪费系统资源,如果用熔断功能,可以设定30秒内加积分接口失败5次就熔断的规则,后面执行下单接口就不会再网络请求远程更新产品销量接口了,而是直接调用本地的降级接口,记录补偿更新产品销量日志。过了30秒后再尝试网络请求远程更新产品销量接口(更新产品销量接口可能会在未来某个时间修复启动)
3.3 关系
熔断和降级一般是联系在一起的,熔断时需要调用降级接口。Spring Cloud Alibaba用Sentinel来做服务限流、熔断、降级
三、实现[order-service]调用[product-service]更新产品销量功能
3.1 module [product-service]
3.1.1 配置maven
[product-service] pom.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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ac-mall-cloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.ac</groupId>
<artifactId>product-service</artifactId>
<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-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
</project>
3.1.2 编写更新产品销量代码
新建com.ac.product
包,创建ProductApplication、ModulePrePath、ProductApi 类
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class);
}
}
public class ModulePrePath {
public static final String API = "api";
}
@RestController
@RequestMapping(ModulePrePath.API+"/products")
public class ProductApi {
@PutMapping("/{productId}")
public void updateSales(@PathVariable String productId){
System.out.println("商品:"+productId+"更新销量数");
}
}
3.1.3 配置文件
新建application.yml配置文件,配置端口为8030,完整配置如下
server:
port: 8030
spring:
application:
name: product-service
cloud:
nacos:
discovery:
server-addr: 47.105.146.74:8848
sentinel:
transport:
dashboard: 127.0.0.1:8888
[product-service]项目完整结构如下
[product-service]项目完整结构3.2 module [gateway-service] 添加 [product-service] 路由
打开[gateway-service] application.yml配置文件,添加[product-service] 路由
spring:
gateway:
routes:
- id: product-service-api
uri: lb://product-service
predicates:
- Path=/products/**
[gateway-service] application.yml完整配置如下
server:
port: 7010
spring:
application:
name: gateway-service
redis:
database: 0
host: 39.108.250.186
port: 6379
password: xxxx
jedis:
pool:
max-active: 500 #连接池的最大数据库连接数。设为0表示无限制
max-idle: 20 #最大空闲数
max-wait: -1
min-idle: 5
timeout: 1000
cloud:
nacos:
discovery:
server-addr: 47.105.146.74:8848
gateway:
routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
- id: user-service-api # 当前路由的标识, 要求唯一
uri: lb://user-service # lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
predicates: # 断言(就是路由转发要满足的条件)
- Path=/users/** # 当请求路径满足Path指定的规则时,才进行路由转发
#filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
#- StripPrefix=1 # 转发之前去掉1层路径
- id: order-service-api
uri: lb://order-service
predicates:
- Path=/orders/**
- id: product-service-api
uri: lb://product-service
predicates:
- Path=/products/**
- id: auth-service-api
uri: lb://auth-service
predicates:
- Path=/auth/**
- id: jianshu_route
uri: https://www.jianshu.com/
predicates:
- Query=url,jianshu
3.3 [order-service]下单接口添加更新产品销量逻辑
3.3.1 创建feign接口ProductServiceClient
在feign包下创建ProductServiceClient类
@FeignClient("product-service")
public interface ProductServiceClient {
/**
* 更新产品销量
* @param productId
*/
@PutMapping(ModulePrePath.API+"/products/{productId}")
void updateSales(@PathVariable("productId") String productId);
}
ProductServiceClient接口
3.3.2 更新下单接口
编辑OrderServiceImpl类,在下单方法里加入更新产品销量逻辑
package com.ac.order.service.impl;
import com.ac.order.dao.OrderDao;
import com.ac.order.dto.UserDto;
import com.ac.order.entity.Order;
import com.ac.order.feign.ProductServiceClient;
import com.ac.order.feign.UserServiceClient;
import com.ac.order.service.IOrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.UUID;
/**
* @author Alan Chen
* @description
* @date 2020/10/15
*/
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
OrderDao orderDao;
@Autowired
RestTemplate restTemplate;
@Autowired
UserServiceClient userServiceClient;
@Autowired
ProductServiceClient productServiceClient;
//final static String USER_SERVICE_URL="http://127.0.0.1:8010/users/{userId}";
final static String USER_SERVICE_URL="http://user-service/users/{userId}"; //用服务名来替换IP
public Order makeOrder(String productId, String userId) {
/**
* RestTemplate是java创造出来的,在java能够访问到网络资源的包是java.net.URLConnenction/Socket
* RestTemplate是对URLConnenction的封装
* apache--HttpClient 也是对URLConnenction/HttpURLConnenction的封装
* oKHttp 也封装了URLConnenction
* netty/rpc/grpc/thirt/tomcat
*/
// 1、根据用户ID调用用户服务接口数据,查询用户的名字
//UserDto userDto = restTemplate.getForObject(USER_SERVICE_URL,UserDto.class,userId);
//换成OpenFeign
UserDto userDto = userServiceClient.getUser(userId);
String userName=userDto.getUserName();
// 2、生成订单
Order order = new Order();
order.setId(UUID.randomUUID().toString());
order.setCreateTime(new Date());
order.setPriceFen(1600L);
order.setUserId(userId);
order.setUserName(userName);
order.setProductId(productId);
order.setOrderNo(UUID.randomUUID().toString());
// 3、保存数据库
orderDao.insert(order);
// 4、更新产品销量
productServiceClient.updateSales(productId);
return order;
}
}
更新产品销量
3.4 启动微服务访问下单接口
依次启动[user-service]、[order-service]、[auth-service]、[gateway-service]、[product-service]服务,通过[gateway-service]访问[order-service]的下单接口
访问下单接口 产品服务控制台接口访问成功,同时产品服务控制台也打印了对应的成功信息
3.5 模拟[product-service]掉线
手动关闭[product-service],模拟[product-service]掉线,再次访问下单接口
下单接口失败我们看到下单接口访问失败了,提示的信息就是无法连接到[product-service]的更新产品销量接口
四、 [order-service]提供降级接口,记录更新产品销量日志
4.1 Feign整合Sentinel实现降级处理
4.1.1 添加Feign整合Sentinel配置
编辑[order-service] application.yml配置文件,添加Feign整合Sentinel配置
feign:
sentinel:
#为feign整合Sentinel
enabled: true
[order-service] application.yml 完整配置如下
server:
port: 8020
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 47.105.146.74:8848
sentinel:
transport:
dashboard: 127.0.0.1:8888
feign:
sentinel:
#为feign整合Sentinel
enabled: true
4.1.2 创建降级接口ProductFeignClientFallbackFactory
新建包com.ac.order.fallback
并创建降级接口ProductFeignClientFallbackFactory
package com.ac.order.fallback;
import com.ac.order.feign.ProductServiceClient;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
/**
* @author Alan Chen
* @description
* @date 2021/4/10
*/
@Component
public class ProductFeignClientFallbackFactory implements FallbackFactory<ProductServiceClient> {
public ProductServiceClient create(Throwable throwable) {
return new ProductServiceClient() {
public void updateSales(String productId) {
System.out.println("调用产品更新销量接口失败,记录日志(记录到数据库或消息中间件),产品:"+productId+"需要更新销量");
}
};
}
}
4.1.3 配置降级接口
编辑ProductServiceClient类,在@FeignClient注解中添加降级接口配置
@FeignClient(name="product-service",fallbackFactory = ProductFeignClientFallbackFactory.class)
package com.ac.order.feign;
import com.ac.order.constant.ModulePrePath;
import com.ac.order.fallback.ProductFeignClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
/**
* @author Alan Chen
* @description 产品接口
* @date 2021/4/10
*/
@FeignClient(name="product-service",fallbackFactory = ProductFeignClientFallbackFactory.class)
public interface ProductServiceClient {
/**
* 更新产品销量
* @param productId
*/
@PutMapping(ModulePrePath.API+"/products/{productId}")
void updateSales(@PathVariable("productId") String productId);
}
4.1.4 测试降级接口
重启[order-service]服务,注意[product-service]不要启动,再次调用下单接口
下单接口访问成功 降级接口我们看到的结果是,下单接口访问成功了,同时[order-service]控制台打印了降级接口的内容
网友评论