再見(jiàn) Feign!推薦一款微服務(wù)間調(diào)用神器,跟 SpringCloud 絕配

在微服務(wù)項(xiàng)目中,如果我們想實(shí)現(xiàn)服務(wù)間調(diào)用,一般會(huì)選擇Feign。之前介紹過(guò)一款HTTP客戶端工具Retrofit,配合SpringBoot非常好用!其實(shí)Retrofit不僅支持普通的HTTP調(diào)用,還能支持微服務(wù)間的調(diào)用,負(fù)載均衡和熔斷限流都能實(shí)現(xiàn)。今天我們來(lái)介紹下Retrofit在Spring Cloud Alibaba下的使用,希望對(duì)大家有所幫助!

前置知識(shí)

本文主要介紹Retrofit在Spring Cloud Alibaba下的使用,需要用到Nacos和Sentinel,對(duì)這些技術(shù)不太熟悉的朋友可以先參考下之前的文章。

  • Spring Cloud Alibaba:Nacos 作為注冊(cè)中心和配置中心使用
  • Spring Cloud Alibaba:Sentinel實(shí)現(xiàn)熔斷與限流
  • 還在用HttpUtil?試試這款優(yōu)雅的HTTP客戶端工具吧,跟SpringBoot絕配!

搭建

在使用之前我們需要先搭建Nacos和Sentinel,再準(zhǔn)備一個(gè)被調(diào)用的服務(wù),使用之前的nacos-user-service即可。

image.png
  • 解壓安裝包到指定目錄,直接運(yùn)行bin目錄下的startup.cmd,運(yùn)行成功后訪問(wèn)Nacos,賬號(hào)密碼均為nacos,訪問(wèn)地址:http://localhost:8848/nacos
image.png
image.png
  • 下載完成后輸入如下命令運(yùn)行Sentinel控制臺(tái);
java -jar sentinel-dashboard-1.6.3.jar

  • Sentinel控制臺(tái)默認(rèn)運(yùn)行在8080端口上,登錄賬號(hào)密碼均為sentinel,通過(guò)如下地址可以進(jìn)行訪問(wèn):http://localhost:8080
image.png
  • 接下來(lái)啟動(dòng)nacos-user-service服務(wù),該服務(wù)中包含了對(duì)User對(duì)象的CRUD操作接口,啟動(dòng)成功后它將會(huì)在Nacos中注冊(cè)。
/**
 * 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("根據(jù)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("根據(jù)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);
    }
}

使用

接下來(lái)我們來(lái)介紹下Retrofit的基本使用,包括服務(wù)間調(diào)用、服務(wù)限流和熔斷降級(jí)。

集成與配置

  • 首先在pom.xml中添加Nacos、Sentinel和Retrofit相關(guān)依賴;
<dependencies>
    <!--Nacos注冊(cè)中心依賴-->
     <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中對(duì)Nacos、Sentinel和Retrofit進(jìn)行配置,Retrofit配置下日志和開(kāi)啟熔斷降級(jí)即可;
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
    # 全局日志打印級(jí)別
    global-log-level: info
    # 全局日志打印策略
    global-log-strategy: body
  # 熔斷降級(jí)配置
  degrade:
    # 是否啟用熔斷降級(jí)
    enable: true
    # 熔斷降級(jí)實(shí)現(xiàn)方式
    degrade-type: sentinel
    # 熔斷資源名稱解析器
    resource-name-parser: com.github.lianjiatech.retrofit.spring.boot.degrade.DefaultResourceNameParser

  • 再添加一個(gè)Retrofit的Java配置,配置好選擇服務(wù)實(shí)例的Bean即可。
/**
 * Retrofit相關(guān)配置
 * Created by macro on 2022/1/26.
 */
@Configuration
public class RetrofitConfig {

    @Bean
    @Autowired
    public ServiceInstanceChooser serviceInstanceChooser(LoadBalancerClient loadBalancerClient) {
        return new SpringCloudServiceInstanceChooser(loadBalancerClient);
    }
}

服務(wù)間調(diào)用

  • 使用Retrofit實(shí)現(xiàn)微服務(wù)間調(diào)用非常簡(jiǎn)單,直接使用@RetrofitClient注解,通過(guò)設(shè)置serviceId為需要調(diào)用服務(wù)的ID即可;
/**
 * 定義Http接口,用于調(diào)用遠(yuǎn)程的User服務(wù)
 * 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);
}

  • 我們可以啟動(dòng)2個(gè)nacos-user-service服務(wù)和1個(gè)nacos-retrofit-service服務(wù),此時(shí)Nacos注冊(cè)中心顯示如下;
image.png
  • 然后通過(guò)Swagger進(jìn)行測(cè)試,調(diào)用下獲取用戶詳情的接口,發(fā)現(xiàn)可以成功返回遠(yuǎn)程數(shù)據(jù),訪問(wèn)地址:http://localhost:8402/swagger-ui/
image.png
  • 查看nacos-retrofit-service服務(wù)打印的日志,兩個(gè)實(shí)例的請(qǐng)求調(diào)用交替打印,我們可以發(fā)現(xiàn)Retrofit通過(guò)配置serviceId即可實(shí)現(xiàn)微服務(wù)間調(diào)用和負(fù)載均衡。
image.png

服務(wù)限流

  • Retrofit的限流功能基本依賴Sentinel,和直接使用Sentinel并無(wú)區(qū)別,我們創(chuàng)建一個(gè)測(cè)試類RateLimitController來(lái)試下它的限流功能;
/**
 * 限流功能
 * 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限流,有默認(rèn)的限流處理邏輯")
    @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);
    }

}

  • 接下來(lái)在Sentinel控制臺(tái)創(chuàng)建一個(gè)根據(jù)資源名稱進(jìn)行限流的規(guī)則;
image.png
  • 之后我們以較快速度訪問(wèn)該接口時(shí),就會(huì)觸發(fā)限流,返回如下信息。
image.png

熔斷降級(jí)

  • Retrofit的熔斷降級(jí)功能也基本依賴于Sentinel,我們創(chuàng)建一個(gè)測(cè)試類CircleBreakerController來(lái)試下它的熔斷降級(jí)功能;
/**
 * 熔斷降級(jí)
 * Created by macro on 2019/11/7.
 */
@Api(tags = "CircleBreakerController",description = "熔斷降級(jí)")
@RestController
@RequestMapping("/breaker")
public class CircleBreakerController {

    private Logger LOGGER = LoggerFactory.getLogger(CircleBreakerController.class);
    @Autowired
    private UserService userService;

    @ApiOperation("熔斷降級(jí)")
    @RequestMapping(value = "/fallback/{id}",method = RequestMethod.GET)
    @SentinelResource(value = "fallback",fallback = "handleFallback")
    public CommonResult fallback(@PathVariable Long id) {
        return userService.getUser(id);
    }

    @ApiOperation("忽略異常進(jìn)行熔斷降級(jí)")
    @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,"服務(wù)降級(jí)返回",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,"服務(wù)降級(jí)返回",200);
    }
}

  • 由于我們并沒(méi)有在nacos-user-service中定義id為4的用戶,調(diào)用過(guò)程中會(huì)產(chǎn)生異常,所以訪問(wèn)如下接口會(huì)返回服務(wù)降級(jí)結(jié)果,返回我們默認(rèn)的用戶信息。
image.png

總結(jié)

Retrofit給了我們除Feign和Dubbo之外的第三種微服務(wù)間調(diào)用選擇,使用起來(lái)還是非常方便的。記得之前在使用Feign的過(guò)程中,實(shí)現(xiàn)方的Controller經(jīng)常要抽出一個(gè)接口來(lái),方便調(diào)用方來(lái)實(shí)現(xiàn)調(diào)用,接口實(shí)現(xiàn)方和調(diào)用方的耦合度很高。如果當(dāng)時(shí)使用的是Retrofit的話,這種情況會(huì)大大改善??偟膩?lái)說(shuō),Retrofit給我們提供了更加優(yōu)雅的HTTP調(diào)用方式,不僅是在單體應(yīng)用中,在微服務(wù)應(yīng)用中也一樣!

參考資料

官方文檔:https://github.com/LianjiaTech/retrofit-spring-boot-starter

項(xiàng)目源碼地址

https://github.com/macrozheng/springcloud-learning

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容