掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
今天,我們就使用Sentinel實現(xiàn)接口的限流,并使用Feign整合Sentinel實現(xiàn)服務容錯的功能,讓小伙伴們體驗下微服務使用了服務容錯功能的效果。因為我們整個專欄的內(nèi)容僅僅圍繞著SpringCloud Alibaba技術(shù)棧展開,所以,這里我們使用的服務容錯組件是阿里開源的Sentinel。

讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領域值得信任、有價值的長期合作伙伴,公司提供的服務項目有:域名注冊、網(wǎng)絡空間、營銷軟件、網(wǎng)站建設、鄂溫克網(wǎng)站維護、網(wǎng)站推廣。
當然,能夠?qū)崿F(xiàn)服務容錯功能的組件不僅僅有Sentinel,比如:Hystrix和Resilience4J也能夠?qū)崿F(xiàn)服務容錯的目的,關于Hystrix和Resilience4J不是本專欄的重點,冰河就不再贅述了,小伙伴們可以自行了解。
隨著微服務的流行,服務和服務之間的穩(wěn)定性變得越來越重要。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統(tǒng)負載保護等多個維度保護服務的穩(wěn)定性。
Sentinel 分為兩個部分:
注意:上述內(nèi)容來自Sentinel官方文檔,鏈接地址為:
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
在微服務項目中整合Sentinel是非常簡單的,只需要在項目的pom.xml文件中引入Sentinel的依賴即可,不過在使用Sentinel時,需要安裝Sentinel的控制臺。
Sentinel 提供一個輕量級的控制臺, 它提供機器發(fā)現(xiàn)、單機資源實時監(jiān)控以及規(guī)則管理等功能。
(1)到鏈接
https://github.com/alibaba/Sentinel/releases
下載Sentinel控制臺,如下所示,我這里下載的Sentinel控制臺是1.8.4版本。
(2)Sentinel控制臺下載完成后,在本地啟動Sentinel控制臺,如下所示。
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888
-Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar
小伙伴們?nèi)绻朐贑entOS服務器上以后臺進程方式啟動Sentinel控制臺,可以使用如下命令
nohup java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.4.jar >> /dev/null &
啟動后在瀏覽器中輸入 http://localhost:8888 訪問Sentinel控制臺,如下所示。
輸入默認的用戶名sentinel和密碼sentinel,登錄Sentinel控制臺,如下所示。
至此,Sentinel控制臺下載并啟動成功。
(1)在訂單微服務的shop-order的pom.xml文件中添加Sentinel的相關依賴,如下所示。
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
(2)在訂單微服務的shop-order的application.yml文中加入Sentinel相關的配置,如下所示。
spring:
cloud:
sentinel:
transport:
port: 9999 #指定和Sentinel控制臺交互的端口,任意指定一個未使用的端口即可
dashboard: 127.0.0.1:8888 #Sentinel控制臺服務地址
(3)為了讓大家直觀的感受到Sentinel的功能,這里我們先在訂單微服務的io.binghe.shop.order.controller.OrderController類中新增一個測試接口,如下所示。
@GetMapping(value = "/test_sentinel")
public String testSentinel(){
log.info("測試Sentinel");
return "sentinel";
}
(4)啟動訂單微服務,在瀏覽器中輸入http://localhost:8080/order/test_sentinel訪問在訂單微服務中新增的接口,如下所示。
(5)刷新Sentinel頁面,會發(fā)現(xiàn)已經(jīng)顯示了訂單微服務的菜單,如下所示。
注意:直接啟動訂單微服務和Sentinel,會發(fā)現(xiàn)Sentinel中沒有訂單微服務的數(shù)據(jù),因為Sentinel是懶加載機制,所以需要訪問一下接口,再去訪問Sentinel 就有數(shù)據(jù)了。
至此,訂單微服務成功集成了Sentinel。
這里,我們使用Sentinel為http://localhost:8080/order/test_sentinel接口限流,步驟如下所示。
(1)在Sentinel控制臺找到server-order下的簇點鏈路菜單,如下所示。
(2)在簇點鏈路列表中找到/test_sentinel,在右側(cè)的操作中選擇流控,如下所示。
點擊流控按鈕會顯示 新增流控規(guī)則 的彈出框,如下所示。
這里,我們在單機閾值后直接填寫1,如下所示。
配置好之后點擊新增按鈕。上述配置表示http://localhost:8080/order/test_sentinel接口的QPS為1,每秒訪問1次。如果每秒訪問的次數(shù)超過1次,則會被Sentinel限流。
(3)在瀏覽器上不斷刷新http://localhost:8080/order/test_sentinel接口,當每秒中訪問的次數(shù)超過1次時,會被Sentinel限流,如下所示。
在提交訂單的接口 http://localhost:8080/order/submit_order上實現(xiàn)限流,步驟如下。
(1)首先訪問下提交訂單的接口 http://localhost:8080/order/submit_order,使得Sentinel中能夠捕獲到提交訂單的接口,并點擊操作中的流控按鈕,如下所示。
這里的注意點還是:直接啟動訂單微服務和Sentinel,會發(fā)現(xiàn)Sentinel中沒有訂單微服務的數(shù)據(jù),因為Sentinel是懶加載機制,所以需要訪問一下接口,再去訪問Sentinel 就有數(shù)據(jù)了。
(2)在新增流控規(guī)則顯示框中的QPS單機閾值設置為1,點擊新增按鈕,如下所示。
(3)在瀏覽器中不斷刷新 http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1 使得每秒訪問的頻率超過1次,會被Sentinel限流,如下所示。
至此,項目中集成了Sentinel并使用Sentinel實現(xiàn)了接口的限流。
我們之前在項目中集成了Sentinel,并使用Sentinel實現(xiàn)了限流,如果訂單微服務的下游服務,比如用戶微服務和商品微服務出現(xiàn)故障,無法訪問時,那訂單微服務該如何實現(xiàn)服務容錯呢?使用Sentinel就可以輕松實現(xiàn)。
(1)在訂單微服務的shop-order的pom.xml文件中添加Sentinel的相關依賴,如下所示。
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
注意:這一步是為了整個案例的完整性加上的,如果小伙伴們按照文章實現(xiàn)了項目整合Sentinel,并在訂單微服務的shop-order的pom.xml文件中添加了上述配置,則可忽略此步驟。
(2)在訂單微服務的application.yml文件中添加如下配置開啟Feign對Sentinel的支持。
feign:
sentinel:
enabled: true
(1)需要在訂單微服務shop-order中,為遠程調(diào)用接口實現(xiàn)容錯方法。這里,先為用戶微服務實現(xiàn)容錯。在訂單微服務中新建io.binghe.shop.order.Feign.fallback 包,并在 io.binghe.shop.order.Feign.fallback包下創(chuàng)建UserServiceFallBack類實現(xiàn)UserService接口,用于調(diào)用用戶微服務的容錯類,如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 用戶服務容錯類
*/
@Component
public class UserServiceFallBack implements UserService {
@Override
public User getUser(Long uid) {
User user = new User();
user.setId(-1L);
return user;
}
}
注意:容錯類需要實現(xiàn)一個被容錯的接口,并實現(xiàn)這個接口的方法。
接下來,在訂單微服務的io.binghe.shop.order.Feign.UserService接口上的@FeignClient注解上指定容錯類,如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 調(diào)用用戶微服務的接口
*/
@FeignClient(value = "server-user", fallback = UserServiceFallBack.class)
public interface UserService {
@GetMapping(value = "/user/get/{uid}")
User getUser(@PathVariable("uid") Long uid);
}
(2)在訂單微服務中的 io.binghe.shop.order.Feign.fallback包下創(chuàng)建ProductServiceFallBack類實現(xiàn)ProductService接口,用于調(diào)用商品微服務的容錯類,如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 商品微服務的容錯類
*/
@Component
public class ProductServiceFallBack implements ProductService {
@Override
public Product getProduct(Long pid) {
Product product = new Product();
product.setId(-1L);
return product;
}
@Override
public ResultupdateCount(Long pid, Integer count) {
Resultresult = new Result<>();
result.setCode(1001);
result.setCodeMsg("觸發(fā)了容錯邏輯");
return result;
}
}
接下來,在訂單微服務的io.binghe.shop.order.fegin.ProductService接口的@FeignClient注解上指定容錯類,如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 調(diào)用商品微服務的接口
*/
@FeignClient(value = "server-product", fallback = ProductServiceFallBack.class)
public interface ProductService {
/**
* 獲取商品信息
*/
@GetMapping(value = "/product/get/{pid}")
Product getProduct(@PathVariable("pid") Long pid);
/**
* 更新庫存數(shù)量
*/
@GetMapping(value = "/product/update_count/{pid}/{count}")
ResultupdateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);
}
(3)修改訂單微服務的業(yè)務實現(xiàn)類中提交訂單的業(yè)務方法,這里修改的方法位于io.binghe.shop.order.service.impl.OrderServiceV6Impl類中,同時需要將類上的@Service注解中指定bean的名稱為orderServiceV6。
@Slf4j
@Service("orderServiceV6")
public class OrderServiceV6Impl implements OrderService {
//省略所有代碼
}
在提交訂單的業(yè)務方法中,修改前的代碼片段如下所示。
User user = userService.getUser(orderParams.getUserId());
if (user == null){
throw new RuntimeException("未獲取到用戶信息: " + JSONObject.toJSONString(orderParams));
}
Product product = productService.getProduct(orderParams.getProductId());
if (product == null){
throw new RuntimeException("未獲取到商品信息: " + JSONObject.toJSONString(orderParams));
}
//#####################省略N行代碼##########################
Resultresult = productService.updateCount(orderParams.getProductId(), orderParams.getCount());
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("庫存扣減失敗");
}
修改后的代碼片段如下所示。
User user = userService.getUser(orderParams.getUserId());
if (user == null){
throw new RuntimeException("未獲取到用戶信息: " + JSONObject.toJSONString(orderParams));
}
if (user.getId() == -1){
throw new RuntimeException("觸發(fā)了用戶微服務的容錯邏輯: " + JSONObject.toJSONString(orderParams));
}
Product product = productService.getProduct(orderParams.getProductId());
if (product == null){
throw new RuntimeException("未獲取到商品信息: " + JSONObject.toJSONString(orderParams));
}
if (product.getId() == -1){
throw new RuntimeException("觸發(fā)了商品微服務的容錯邏輯: " + JSONObject.toJSONString(orderParams));
}
//#####################省略N行代碼##########################
Resultresult = productService.updateCount(orderParams.getProductId(), orderParams.getCount());
if (result.getCode() == 1001){
throw new RuntimeException("觸發(fā)了商品微服務的容錯邏輯: " + JSONObject.toJSONString(orderParams));
}
if (result.getCode() != HttpCode.SUCCESS){
throw new RuntimeException("庫存扣減失敗");
}
可以看到,修改后的提交訂單的業(yè)務方法主要增加了服務容錯的判斷邏輯。
(4)在io.binghe.shop.order.controller.OrderController中注入bean名稱為orderServiceV6的OrderService對象,如下所示。
@Autowired
@Qualifier(value = "orderServiceV6")
private OrderService orderService;
至此,我們在項目中使用Sentinel實現(xiàn)了服務容錯的功能。
(1)停掉所有的商品微服務(也就是只啟動用戶微服務和訂單微服務),在瀏覽器中訪問http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,結(jié)果如下所示。
返回的原始數(shù)據(jù)如下所示。
{"code":500,"codeMsg":"執(zhí)行失敗","data":"觸發(fā)了商品微服務的容錯邏輯: {\"count\":1,\"empty\":false,\"productId\":1001,\"userId\":1001}"}說明停掉所有的商品微服務后,觸發(fā)了商品微服務的容錯邏輯。
(2)停掉所有的用戶微服務(也就是只啟動商品微服務和訂單微服務)在瀏覽器中訪問http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,結(jié)果如下所示。
返回的原始數(shù)據(jù)如下所示。
{"code":500,"codeMsg":"執(zhí)行失敗","data":"觸發(fā)了用戶微服務的容錯邏輯: {\"count\":1,\"empty\":false,\"productId\":1001,\"userId\":1001}"}(3)停掉所有的用戶微服務和商品微服務(也就是只啟動訂單微服務),在瀏覽器中訪問http://localhost:8080/order/submit_order?userId=1001&productId=1001&count=1,結(jié)果如下所示。
返回的原始數(shù)據(jù)如下所示。
{"code":500,"codeMsg":"執(zhí)行失敗","data":"觸發(fā)了用戶微服務的容錯邏輯: {\"count\":1,\"empty\":false,\"productId\":1001,\"userId\":1001}"}說明項目集成Sentinel成功實現(xiàn)了服務的容錯功能。
如果想要在訂單微服務中獲取到容錯時的具體信息時,可以按照如下方式實現(xiàn)容錯方案。
(1)在訂單微服務shop-order中新建io.binghe.shop.order.fegin.fallback.factory包,在io.binghe.shop.order.fegin.fallback.factory包中新建UserServiceFallBackFactory類,并實現(xiàn)FallbackFactory接口,F(xiàn)allbackFactory接口的泛型指定為UserService,源碼如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 用戶微服務容錯Factory
*/
@Component
public class UserServiceFallBackFactory implements FallbackFactory{
@Override
public UserService create(Throwable cause) {
return new UserService() {
@Override
public User getUser(Long uid) {
User user = new User();
user.setId(-1L);
return user;
}
};
}
}
(2)在訂單微服務的 io.binghe.shop.order.fegin.UserService 接口上的@FeignClient注解上指定fallbackFactory屬性,如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 調(diào)用用戶微服務的接口
*/
//@FeignClient(value = "server-user", fallback = UserServiceFallBack.class)
@FeignClient(value = "server-user", fallbackFactory = UserServiceFallBackFactory.class)
public interface UserService {
@GetMapping(value = "/user/get/{uid}")
User getUser(@PathVariable("uid") Long uid);
}
(3)在io.binghe.shop.order.fegin.fallback.factory包中新建ProductServiceFallBackFactory類,并實現(xiàn)FallbackFactory接口,F(xiàn)allbackFactory接口的泛型指定為ProductService,源碼如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 商品微服務容錯Factory
*/
@Component
public class ProductServiceFallBackFactory implements FallbackFactory{
@Override
public ProductService create(Throwable cause) {
return new ProductService() {
@Override
public Product getProduct(Long pid) {
Product product = new Product();
product.setId(-1L);
return product;
}
@Override
public ResultupdateCount(Long pid, Integer count) {
Resultresult = new Result<>();
result.setCode(1001);
result.setCodeMsg("觸發(fā)了容錯邏輯");
return result;
}
};
}
}
(4)在訂單微服務的 io.binghe.shop.order.fegin.ProductService 接口上的@FeignClient注解上指定fallbackFactory屬性,如下所示。
/**
* @author binghe (公眾號:冰河技術(shù))
* @version 1.0.0
* @description 調(diào)用商品微服務的接口
*/
//@FeignClient(value = "server-product", fallback = ProductServiceFallBack.class)
@FeignClient(value = "server-product", fallbackFactory = ProductServiceFallBackFactory.class)
public interface ProductService {
/**
* 獲取商品信息
*/
@GetMapping(value = "/product/get/{pid}")
Product getProduct(@PathVariable("pid") Long pid);
/**
* 更新庫存數(shù)量
*/
@GetMapping(value = "/product/update_count/{pid}/{count}")
ResultupdateCount(@PathVariable("pid") Long pid, @PathVariable("count") Integer count);
}
與“Feign整合Sentinel實現(xiàn)容錯-測試服務容錯”中的測試方法相同,這里不再贅述。
至此,使用Sentinel實現(xiàn)限流和容錯的功能就完成了。
最后,需要注意的是:使用Sentinel實現(xiàn)服務容錯時,fallback和fallbackFactory只能使用其中一種方式實現(xiàn)服務容錯,二者不能同時使用。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流