一询刹、熔斷降級(jí)
除了流量控制以外烘跺,對(duì)調(diào)用鏈路中不穩(wěn)定的資源進(jìn)行熔斷降級(jí)也是保障高可用的重要措施之一。一個(gè)服務(wù)常常會(huì)調(diào)用別的模塊返弹,可能是另外的一個(gè)遠(yuǎn)程服務(wù)分瘦、數(shù)據(jù)庫(kù),或者第三方 API 等琉苇。例如嘲玫,支付的時(shí)候,可能需要遠(yuǎn)程調(diào)用銀聯(lián)提供的 API并扇;查詢某個(gè)商品的價(jià)格去团,可能需要進(jìn)行數(shù)據(jù)庫(kù)查詢。然而穷蛹,這個(gè)被依賴服務(wù)的穩(wěn)定性是不能保證的土陪。如果依賴的服務(wù)出現(xiàn)了不穩(wěn)定的情況,請(qǐng)求的響應(yīng)時(shí)間變長(zhǎng)肴熏,那么調(diào)用服務(wù)的方法的響應(yīng)時(shí)間也會(huì)變長(zhǎng)鬼雀,線程會(huì)產(chǎn)生堆積,最終可能耗盡業(yè)務(wù)自身的線程池蛙吏,服務(wù)本身也變得不可用源哩。
現(xiàn)代微服務(wù)架構(gòu)都是分布式的,由非常多的服務(wù)組成鸦做。不同服務(wù)之間相互調(diào)用励烦,組成復(fù)雜的調(diào)用鏈路。以上的問(wèn)題在鏈路調(diào)用中會(huì)產(chǎn)生放大的效果泼诱。復(fù)雜鏈路上的某一環(huán)不穩(wěn)定坛掠,就可能會(huì)層層級(jí)聯(lián),最終導(dǎo)致整個(gè)鏈路都不可用。因此我們需要對(duì)不穩(wěn)定的弱依賴服務(wù)調(diào)用進(jìn)行熔斷降級(jí)屉栓,暫時(shí)切斷不穩(wěn)定調(diào)用舷蒲,避免局部不穩(wěn)定因素導(dǎo)致整體的雪崩。熔斷降級(jí)作為保護(hù)自身的手段友多,通常在客戶端(調(diào)用端)進(jìn)行配置牲平。
二、熔斷&降級(jí)區(qū)分
2.1 服務(wù)降級(jí)舉例
下單接口調(diào)用遠(yuǎn)程更新產(chǎn)品銷量接口夷陋,當(dāng)某一天更新產(chǎn)品銷量接口掛了欠拾,下單服務(wù)會(huì)在本地提供一個(gè)降級(jí)接口來(lái)記錄xx訂單xx產(chǎn)品需要更新銷量,這條記錄可以寫入對(duì)應(yīng)的記錄數(shù)據(jù)表或者消息中間件中骗绕,然后定時(shí)掃描來(lái)完成產(chǎn)品銷量更新的任務(wù)藐窄,這就是服務(wù)降級(jí)。
2.2 服務(wù)熔斷舉例
遠(yuǎn)程更新產(chǎn)品銷量接口暫時(shí)已經(jīng)掛了酬土,如果下單接口每次下單時(shí)還是去請(qǐng)求網(wǎng)絡(luò)調(diào)用遠(yuǎn)程更新產(chǎn)品銷量接口荆忍,然后再走本地降級(jí)接口,這樣會(huì)浪費(fèi)系統(tǒng)資源撤缴,如果用熔斷功能刹枉,可以設(shè)定30秒內(nèi)加積分接口失敗5次就熔斷的規(guī)則,后面執(zhí)行下單接口就不會(huì)再網(wǎng)絡(luò)請(qǐng)求遠(yuǎn)程更新產(chǎn)品銷量接口了屈呕,而是直接調(diào)用本地的降級(jí)接口微宝,記錄補(bǔ)償更新產(chǎn)品銷量日志。過(guò)了30秒后再嘗試網(wǎng)絡(luò)請(qǐng)求遠(yuǎn)程更新產(chǎn)品銷量接口(更新產(chǎn)品銷量接口可能會(huì)在未來(lái)某個(gè)時(shí)間修復(fù)啟動(dòng))
3.3 關(guān)系
熔斷和降級(jí)一般是聯(lián)系在一起的虎眨,熔斷時(shí)需要調(diào)用降級(jí)接口蟋软。Spring Cloud Alibaba用Sentinel來(lái)做服務(wù)限流、熔斷嗽桩、降級(jí)
三岳守、實(shí)現(xiàn)[order-service]調(diào)用[product-service]更新產(chǎn)品銷量功能
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 編寫更新產(chǎn)品銷量代碼
新建com.ac.product
包,創(chuàng)建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+"更新銷量數(shù)");
}
}
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]項(xiàng)目完整結(jié)構(gòu)如下
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 #連接池的最大數(shù)據(jù)庫(kù)連接數(shù)。設(shè)為0表示無(wú)限制
max-idle: 20 #最大空閑數(shù)
max-wait: -1
min-idle: 5
timeout: 1000
cloud:
nacos:
discovery:
server-addr: 47.105.146.74:8848
gateway:
routes: # 路由數(shù)組[路由 就是指定當(dāng)請(qǐng)求滿足什么條件的時(shí)候轉(zhuǎn)到哪個(gè)微服務(wù)]
- id: user-service-api # 當(dāng)前路由的標(biāo)識(shí), 要求唯一
uri: lb://user-service # lb指的是從nacos中按照名稱獲取微服務(wù),并遵循負(fù)載均衡策略
predicates: # 斷言(就是路由轉(zhuǎn)發(fā)要滿足的條件)
- Path=/users/** # 當(dāng)請(qǐng)求路徑滿足Path指定的規(guī)則時(shí),才進(jìn)行路由轉(zhuǎn)發(fā)
#filters: # 過(guò)濾器,請(qǐng)求在傳遞過(guò)程中可以通過(guò)過(guò)濾器對(duì)其進(jìn)行一定的修改
#- StripPrefix=1 # 轉(zhuǎn)發(fā)之前去掉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: http://www.reibang.com/
predicates:
- Query=url,jianshu
3.3 [order-service]下單接口添加更新產(chǎn)品銷量邏輯
3.3.1 創(chuàng)建feign接口ProductServiceClient
在feign包下創(chuàng)建ProductServiceClient類
@FeignClient("product-service")
public interface ProductServiceClient {
/**
* 更新產(chǎn)品銷量
* @param productId
*/
@PutMapping(ModulePrePath.API+"/products/{productId}")
void updateSales(@PathVariable("productId") String productId);
}
3.3.2 更新下單接口
編輯OrderServiceImpl類嫩挤,在下單方法里加入更新產(chǎn)品銷量邏輯
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}"; //用服務(wù)名來(lái)替換IP
public Order makeOrder(String productId, String userId) {
/**
* RestTemplate是java創(chuàng)造出來(lái)的害幅,在java能夠訪問(wèn)到網(wǎng)絡(luò)資源的包是java.net.URLConnenction/Socket
* RestTemplate是對(duì)URLConnenction的封裝
* apache--HttpClient 也是對(duì)URLConnenction/HttpURLConnenction的封裝
* oKHttp 也封裝了URLConnenction
* netty/rpc/grpc/thirt/tomcat
*/
// 1、根據(jù)用戶ID調(diào)用用戶服務(wù)接口數(shù)據(jù)岂昭,查詢用戶的名字
//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、保存數(shù)據(jù)庫(kù)
orderDao.insert(order);
// 4约啊、更新產(chǎn)品銷量
productServiceClient.updateSales(productId);
return order;
}
}
3.4 啟動(dòng)微服務(wù)訪問(wèn)下單接口
依次啟動(dòng)[user-service]邑遏、[order-service]、[auth-service]恰矩、[gateway-service]记盒、[product-service]服務(wù),通過(guò)[gateway-service]訪問(wèn)[order-service]的下單接口
接口訪問(wèn)成功外傅,同時(shí)產(chǎn)品服務(wù)控制臺(tái)也打印了對(duì)應(yīng)的成功信息
3.5 模擬[product-service]掉線
手動(dòng)關(guān)閉[product-service]纪吮,模擬[product-service]掉線,再次訪問(wèn)下單接口
我們看到下單接口訪問(wèn)失敗了萎胰,提示的信息就是無(wú)法連接到[product-service]的更新產(chǎn)品銷量接口
四碾盟、 [order-service]提供降級(jí)接口,記錄更新產(chǎn)品銷量日志
4.1 Feign整合Sentinel實(shí)現(xiàn)降級(jí)處理
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 創(chuàng)建降級(jí)接口ProductFeignClientFallbackFactory
新建包com.ac.order.fallback
并創(chuàng)建降級(jí)接口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("調(diào)用產(chǎn)品更新銷量接口失敗冰肴,記錄日志(記錄到數(shù)據(jù)庫(kù)或消息中間件),產(chǎn)品:"+productId+"需要更新銷量");
}
};
}
}
4.1.3 配置降級(jí)接口
編輯ProductServiceClient類榔组,在@FeignClient注解中添加降級(jí)接口配置
@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 產(chǎn)品接口
* @date 2021/4/10
*/
@FeignClient(name="product-service",fallbackFactory = ProductFeignClientFallbackFactory.class)
public interface ProductServiceClient {
/**
* 更新產(chǎn)品銷量
* @param productId
*/
@PutMapping(ModulePrePath.API+"/products/{productId}")
void updateSales(@PathVariable("productId") String productId);
}
4.1.4 測(cè)試降級(jí)接口
重啟[order-service]服務(wù)熙尉,注意[product-service]不要啟動(dòng),再次調(diào)用下單接口
我們看到的結(jié)果是搓扯,下單接口訪問(wèn)成功了检痰,同時(shí)[order-service]控制臺(tái)打印了降級(jí)接口的內(nèi)容