在微服務專案中,如果我們想實現服務間呼叫,一般會選擇Feign。之前介紹過一款HTTP客戶端工具Retrofit
,配合SpringBoot非常好用!其實Retrofit
不僅支援普通的HTTP呼叫,還能支援微服務間的呼叫,負載均衡和熔斷限流都能實現。今天我們來介紹下Retrofit
在Spring Cloud Alibaba下的使用,希望對大家有所幫助!
SpringBoot實戰電商專案mall(50k+star)地址:https://github.com/macrozheng/mall
前置知識
本文主要介紹Retrofit在Spring Cloud Alibaba下的使用,需要用到Nacos和Sentinel,對這些技術不太熟悉的朋友可以先參考下之前的文章。
- Spring Cloud Alibaba:Nacos 作為註冊中心和配置中心使用
- Spring Cloud Alibaba:Sentinel實現熔斷與限流
- 還在用HttpUtil?試試這款優雅的HTTP客戶端工具吧,跟SpringBoot絕配!
搭建
在使用之前我們需要先搭建Nacos和Sentinel,再準備一個被呼叫的服務,使用之前的nacos-user-service
即可。
- 首先從官網下載Nacos,這裡下載的是
nacos-server-1.3.0.zip
檔案,下載地址:https://github.com/alibaba/na...
- 解壓安裝包到指定目錄,直接執行
bin
目錄下的startup.cmd
,執行成功後訪問Nacos,賬號密碼均為nacos
,訪問地址:http://localhost:8848/nacos
- 接下來從官網下載Sentinel,這裡下載的是
sentinel-dashboard-1.6.3.jar
檔案,下載地址:https://github.com/alibaba/Se...
- 下載完成後輸入如下命令執行Sentinel控制檯;
java -jar sentinel-dashboard-1.6.3.jar
- Sentinel控制檯預設執行在
8080
埠上,登入賬號密碼均為sentinel
,通過如下地址可以進行訪問:http://localhost:8080
- 接下來啟動
nacos-user-service
服務,該服務中包含了對User物件的CRUD操作介面,啟動成功後它將會在Nacos中註冊。
/**
* Created by macro on 2019/8/29.
*/
@RestController
@RequestMapping("/user")
public class UserController {
private Logger LOGGER = LoggerFactory.getLogger(this.getClass());
@Autowired
private UserService userService;
@PostMapping("/create")
public CommonResult create(@RequestBody User user) {
userService.create(user);
return new CommonResult("操作成功", 200);
}
@GetMapping("/{id}")
public CommonResult<User> getUser(@PathVariable Long id) {
User user = userService.getUser(id);
LOGGER.info("根據id獲取使用者資訊,使用者名稱稱為:{}",user.getUsername());
return new CommonResult<>(user);
}
@GetMapping("/getUserByIds")
public CommonResult<List<User>> getUserByIds(@RequestParam List<Long> ids) {
List<User> userList= userService.getUserByIds(ids);
LOGGER.info("根據ids獲取使用者資訊,使用者列表為:{}",userList);
return new CommonResult<>(userList);
}
@GetMapping("/getByUsername")
public CommonResult<User> getByUsername(@RequestParam String username) {
User user = userService.getByUsername(username);
return new CommonResult<>(user);
}
@PostMapping("/update")
public CommonResult update(@RequestBody User user) {
userService.update(user);
return new CommonResult("操作成功", 200);
}
@PostMapping("/delete/{id}")
public CommonResult delete(@PathVariable Long id) {
userService.delete(id);
return new CommonResult("操作成功", 200);
}
}
使用
接下來我們來介紹下Retrofit的基本使用,包括服務間呼叫、服務限流和熔斷降級。
整合與配置
- 首先在
pom.xml
中新增Nacos、Sentinel和Retrofit相關依賴;
<dependencies>
<!--Nacos註冊中心依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--Sentinel依賴-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--Retrofit依賴-->
<dependency>
<groupId>com.github.lianjiatech</groupId>
<artifactId>retrofit-spring-boot-starter</artifactId>
<version>2.2.18</version>
</dependency>
</dependencies>
- 然後在
application.yml
中對Nacos、Sentinel和Retrofit進行配置,Retrofit配置下日誌和開啟熔斷降級即可;
server:
port: 8402
spring:
application:
name: nacos-retrofit-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
sentinel:
transport:
dashboard: localhost:8080 #配置sentinel dashboard地址
port: 8719
retrofit:
log:
# 啟用日誌列印
enable: true
# 日誌列印攔截器
logging-interceptor: com.github.lianjiatech.retrofit.spring.boot.interceptor.DefaultLoggingInterceptor
# 全域性日誌列印級別
global-log-level: info
# 全域性日誌列印策略
global-log-strategy: body
# 熔斷降級配置
degrade:
# 是否啟用熔斷降級
enable: true
# 熔斷降級實現方式
degrade-type: sentinel
# 熔斷資源名稱解析器
resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser
- 再新增一個Retrofit的Java配置,配置好選擇服務例項的Bean即可。
/**
* Retrofit相關配置
* Created by macro on 2022/1/26.
*/
@Configuration
public class RetrofitConfig {
@Bean
@Autowired
public ServiceInstanceChooser serviceInstanceChooser(LoadBalancerClient loadBalancerClient) {
return new SpringCloudServiceInstanceChooser(loadBalancerClient);
}
}
服務間呼叫
- 使用Retrofit實現微服務間呼叫非常簡單,直接使用
@RetrofitClient
註解,通過設定serviceId
為需要呼叫服務的ID即可;
/**
* 定義Http介面,用於呼叫遠端的User服務
* Created by macro on 2019/9/5.
*/
@RetrofitClient(serviceId = "nacos-user-service", fallback = UserFallbackService.class)
public interface UserService {
@POST("/user/create")
CommonResult create(@Body User user);
@GET("/user/{id}")
CommonResult<User> getUser(@Path("id") Long id);
@GET("/user/getByUsername")
CommonResult<User> getByUsername(@Query("username") String username);
@POST("/user/update")
CommonResult update(@Body User user);
@POST("/user/delete/{id}")
CommonResult delete(@Path("id") Long id);
}
- 我們可以啟動2個
nacos-user-service
服務和1個nacos-retrofit-service
服務,此時Nacos註冊中心顯示如下;
- 然後通過Swagger進行測試,呼叫下獲取使用者詳情的介面,發現可以成功返回遠端資料,訪問地址:http://localhost:8402/swagger...
- 檢視
nacos-retrofit-service
服務列印的日誌,兩個例項的請求呼叫交替列印,我們可以發現Retrofit通過配置serviceId
即可實現微服務間呼叫和負載均衡。
服務限流
- Retrofit的限流功能基本依賴Sentinel,和直接使用Sentinel並無區別,我們建立一個測試類
RateLimitController
來試下它的限流功能;
/**
* 限流功能
* Created by macro on 2019/11/7.
*/
@Api(tags = "RateLimitController",description = "限流功能")
@RestController
@RequestMapping("/rateLimit")
public class RateLimitController {
@ApiOperation("按資源名稱限流,需要指定限流處理邏輯")
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult("按資源名稱限流", 200);
}
@ApiOperation("按URL限流,有預設的限流處理邏輯")
@GetMapping("/byUrl")
@SentinelResource(value = "byUrl",blockHandler = "handleException")
public CommonResult byUrl() {
return new CommonResult("按url限流", 200);
}
@ApiOperation("自定義通用的限流處理邏輯")
@GetMapping("/customBlockHandler")
@SentinelResource(value = "customBlockHandler", blockHandler = "handleException",blockHandlerClass = CustomBlockHandler.class)
public CommonResult blockHandler() {
return new CommonResult("限流成功", 200);
}
public CommonResult handleException(BlockException exception){
return new CommonResult(exception.getClass().getCanonicalName(),200);
}
}
- 接下來在Sentinel控制檯建立一個根據
資源名稱
進行限流的規則;
- 之後我們以較快速度訪問該介面時,就會觸發限流,返回如下資訊。
熔斷降級
- Retrofit的熔斷降級功能也基本依賴於Sentinel,我們建立一個測試類
CircleBreakerController
來試下它的熔斷降級功能;
/**
* 熔斷降級
* Created by macro on 2019/11/7.
*/
@Api(tags = "CircleBreakerController",description = "熔斷降級")
@RestController
@RequestMapping("/breaker")
public class CircleBreakerController {
private Logger LOGGER = LoggerFactory.getLogger(CircleBreakerController.class);
@Autowired
private UserService userService;
@ApiOperation("熔斷降級")
@RequestMapping(value = "/fallback/{id}",method = RequestMethod.GET)
@SentinelResource(value = "fallback",fallback = "handleFallback")
public CommonResult fallback(@PathVariable Long id) {
return userService.getUser(id);
}
@ApiOperation("忽略異常進行熔斷降級")
@RequestMapping(value = "/fallbackException/{id}",method = RequestMethod.GET)
@SentinelResource(value = "fallbackException",fallback = "handleFallback2", exceptionsToIgnore = {NullPointerException.class})
public CommonResult fallbackException(@PathVariable Long id) {
if (id == 1) {
throw new IndexOutOfBoundsException();
} else if (id == 2) {
throw new NullPointerException();
}
return userService.getUser(id);
}
public CommonResult handleFallback(Long id) {
User defaultUser = new User(-1L, "defaultUser", "123456");
return new CommonResult<>(defaultUser,"服務降級返回",200);
}
public CommonResult handleFallback2(@PathVariable Long id, Throwable e) {
LOGGER.error("handleFallback2 id:{},throwable class:{}", id, e.getClass());
User defaultUser = new User(-2L, "defaultUser2", "123456");
return new CommonResult<>(defaultUser,"服務降級返回",200);
}
}
- 由於我們並沒有在
nacos-user-service
中定義id為4
的使用者,呼叫過程中會產生異常,所以訪問如下介面會返回服務降級結果,返回我們預設的使用者資訊。
總結
Retrofit給了我們除Feign和Dubbo之外的第三種微服務間呼叫選擇,使用起來還是非常方便的。記得之前在使用Feign的過程中,實現方的Controller經常要抽出一個介面來,方便呼叫方來實現呼叫,介面實現方和呼叫方的耦合度很高。如果當時使用的是Retrofit的話,這種情況會大大改善。總的來說,Retrofit給我們提供了更加優雅的HTTP呼叫方式,不僅是在單體應用中,在微服務應用中也一樣!
參考資料
官方文件:https://github.com/LianjiaTec...