還在用Feign?推薦一款微服務間呼叫神器,跟SpringCloud絕配!

macrozheng發表於2022-02-15
在微服務專案中,如果我們想實現服務間呼叫,一般會選擇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,對這些技術不太熟悉的朋友可以先參考下之前的文章。

搭建

在使用之前我們需要先搭建Nacos和Sentinel,再準備一個被呼叫的服務,使用之前的nacos-user-service即可。

  • 解壓安裝包到指定目錄,直接執行bin目錄下的startup.cmd,執行成功後訪問Nacos,賬號密碼均為nacos,訪問地址:http://localhost:8848/nacos

  • 下載完成後輸入如下命令執行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...

專案原始碼地址

https://github.com/macrozheng...

相關文章