升級Spring Cloud最新版後,有個重要的元件被棄用了!

語言: CN / TW / HK

前不久,我把 Mall微服務版本全面升級了 ,在通過Gateway閘道器呼叫其他服務的時候,出現了 Service Unavailable 的問題。排查原因時發現作為負載均衡元件的Ribbon被棄用了,作為Netflix開源的一個元件,Ribbon早已進入維護狀態。現在推薦使用的是Loadbalancer,今天我們就來聊聊Loadbalancer的使用!

SpringCloud實戰電商專案 mall-swarm (8.8k+star)地址: https://github.com/macrozheng/mall-swarm

LoadBalancer簡介

LoadBalancer是Spring Cloud官方提供的負載均衡元件,可用於替代Ribbon。其使用方式與Ribbon基本相容,可以從Ribbon進行平滑過渡。

使用

下面介紹下LoadBalancer的基本使用,我們將使用Nacos作為註冊中心,通過 nacos-loadbalancer-servicenacos-user-service 兩個服務間的相互呼叫來進行演示。

負載均衡

我們將使用RestTemplate來演示下LoadBalancer的負載均衡功能。

  • 首先在 nacos-loadbalancer-service 模組的 pom.xml 檔案中新增LoadBalancer相關依賴;
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
  • 然後建立Java配置類,用於配置 RestTemplate ,同時使用 @LoadBalanced 註解賦予其負載均衡能力;
/**
 * RestTemplate相關配置
 * Created by macro on 2019/8/29.
 */
@Configuration
public class RestTemplateConfig {

    @Bean
    @ConfigurationProperties(prefix = "rest.template.config")
    public HttpComponentsClientHttpRequestFactory customHttpRequestFactory() {
        return new HttpComponentsClientHttpRequestFactory();
    }

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate(customHttpRequestFactory());
    }
}
  • application.yml 中可以使用自定義配置對RestTemplate的呼叫超時進行配置;
rest:
  template:
    config: # RestTemplate呼叫超時配置
      connectTimeout: 5000
      readTimeout: 5000
  • 然後在Controller中使用RestTemplate進行遠端呼叫;
/**
 * Created by macro on 2019/8/29.
 */
@RestController
@RequestMapping("/user")
public class UserLoadBalancerController {
    @Autowired
    private RestTemplate restTemplate;
    @Value("${service-url.nacos-user-service}")
    private String userServiceUrl;

    @GetMapping("/{id}")
    public CommonResult getUser(@PathVariable Long id) {
        return restTemplate.getForObject(userServiceUrl + "/user/{1}", CommonResult.class, id);
    }

    @GetMapping("/getByUsername")
    public CommonResult getByUsername(@RequestParam String username) {
        return restTemplate.getForObject(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
    }

    @GetMapping("/getEntityByUsername")
    public CommonResult getEntityByUsername(@RequestParam String username) {
        ResponseEntity<CommonResult> entity = restTemplate.getForEntity(userServiceUrl + "/user/getByUsername?username={1}", CommonResult.class, username);
        if (entity.getStatusCode().is2xxSuccessful()) {
            return entity.getBody();
        } else {
            return new CommonResult("操作失敗", 500);
        }
    }

    @PostMapping("/create")
    public CommonResult create(@RequestBody User user) {
        return restTemplate.postForObject(userServiceUrl + "/user/create", user, CommonResult.class);
    }

    @PostMapping("/update")
    public CommonResult update(@RequestBody User user) {
        return restTemplate.postForObject(userServiceUrl + "/user/update", user, CommonResult.class);
    }

    @PostMapping("/delete/{id}")
    public CommonResult delete(@PathVariable Long id) {
        return restTemplate.postForObject(userServiceUrl + "/user/delete/{1}", null, CommonResult.class, id);
    }
}
  • nacos-user-service 中我們已經實現了這些介面,可以提供給 nacos-loadbalancer-service 服務進行遠端呼叫;

  • 然後啟動一個 nacos-loadbalancer-service ,和兩個 nacos-user-service ,此時Nacos中會顯示如下服務;

  • 此時通過 nacos-loadbalancer-service 呼叫介面進行測試,會發現兩個 nacos-user-service 交替列印日誌資訊,使用的是輪詢策略,訪問地址: http://localhost:8308/user/1

宣告式服務呼叫

當然LoadBalancer除了使用RestTemplate來進行遠端呼叫,還可以使用OpenFeign來進行宣告式服務呼叫,下面我們就來介紹下。

  • 首先 nacos-loadbalancer-service 模組的 pom.xml 檔案中新增OpenFeign的相關依賴;
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 然後在OpenFeign的客戶端介面中宣告好需要呼叫的服務介面以及呼叫方式;
/**
 * Created by macro on 2019/9/5.
 */
@FeignClient(value = "nacos-user-service")
public interface UserService {
    @PostMapping("/user/create")
    CommonResult create(@RequestBody User user);

    @GetMapping("/user/{id}")
    CommonResult<User> getUser(@PathVariable Long id);

    @GetMapping("/user/getByUsername")
    CommonResult<User> getByUsername(@RequestParam String username);

    @PostMapping("/user/update")
    CommonResult update(@RequestBody User user);

    @PostMapping("/user/delete/{id}")
    CommonResult delete(@PathVariable Long id);
}
  • 再在Controller中使用OpenFeign的客戶端介面來呼叫遠端服務;
/**
 * Created by macro on 2019/8/29.
 */
@RestController
@RequestMapping("/userFeign")
public class UserFeignController {
    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public CommonResult getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }

    @GetMapping("/getByUsername")
    public CommonResult getByUsername(@RequestParam String username) {
        return userService.getByUsername(username);
    }

    @PostMapping("/create")
    public CommonResult create(@RequestBody User user) {
        return userService.create(user);
    }

    @PostMapping("/update")
    public CommonResult update(@RequestBody User user) {
        return userService.update(user);
    }

    @PostMapping("/delete/{id}")
    public CommonResult delete(@PathVariable Long id) {
        return userService.delete(id);
    }
}
  • 如果你想設定下OpenFeign的超時配置的話,可以在 application.yml 中新增如下內容;
feign:
  client:
    config:
      default: # Feign呼叫超時配置
        connectTimeout: 5000
        readTimeout: 5000

服務例項快取

LoadBalancer為了提高效能,不會在每次請求時去獲取例項列表,而是將服務例項列表進行了本地快取。

預設的快取時間為 35s ,為了減少服務不可用還會被選擇的可能性,我們可以進行如下配置。

spring:
  cloud:
    loadbalancer:
      cache: # 負載均衡快取配置
        enabled: true # 開啟快取
        ttl: 5s # 設定快取時間
        capacity: 256 # 設定快取大小

HTTP請求轉換

如果你想在每次遠端呼叫中傳入自定義的請求頭的話,可以試試 LoadBalancerRequestTransformer ,通過它可以對原始請求進行一定的轉換。

  • 首先我們需要配置好 LoadBalancerRequestTransformer 的Bean例項,這裡我們將 ServiceInstanceinstanceId 放入到請求頭 X-InstanceId 中;
/**
 * LoadBalancer相關配置
 * Created by macro on 2022/7/26.
 */
@Configuration
public class LoadBalancerConfig {
    @Bean
    public LoadBalancerRequestTransformer transformer() {
        return new LoadBalancerRequestTransformer() {
            @Override
            public HttpRequest transformRequest(HttpRequest request, ServiceInstance instance) {
                return new HttpRequestWrapper(request) {
                    @Override
                    public HttpHeaders getHeaders() {
                        HttpHeaders headers = new HttpHeaders();
                        headers.putAll(super.getHeaders());
                        headers.add("X-InstanceId", instance.getInstanceId());
                        return headers;
                    }
                };
            }
        };
    }
}
  • 然後修改 nacos-user-service 中的程式碼,列印獲取到的請求頭 X-InstanceId 的資訊;
/**
 * Created by macro on 2019/8/29.
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @GetMapping("/{id}")
    public CommonResult<User> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        LOGGER.info("根據id獲取使用者資訊,使用者名稱稱為:{}", user.getUsername());
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String instanceId = request.getHeader("X-InstanceId");
        if (StrUtil.isNotEmpty(instanceId)) {
            LOGGER.info("獲取到自定義請求頭:X-InstanceId={}", instanceId);
        }
        return new CommonResult<>(user);
    }
}
  • 接下來訪問介面進行測試, nacos-user-service 控制檯將列印如下日誌,發現自定義請求頭已經成功傳遞了,訪問地址: http://localhost:8308/user/1
2022-07-26 15:05:19.920  INFO 14344 --- [nio-8206-exec-5] c.macro.cloud.controller.UserController  : 根據id獲取使用者資訊,使用者名稱稱為:macro
2022-07-26 15:05:19.921  INFO 14344 --- [nio-8206-exec-5] c.macro.cloud.controller.UserController  : 獲取到自定義請求頭:X-InstanceId=192.168.3.227#8206#DEFAULT#[email protected]@nacos-user-service

總結

今天通過對LoadBalancer的一波實踐我們可以發現,使用LoadBalancer和Ribbon的區別其實並不大,主要是一些配置方式的相同。如果你之前使用過Ribbon的話,基本上可以無縫切換到LoadBalancer。

參考資料

官方文件: https://docs.spring.io/spring...

專案原始碼地址

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