前不久,我把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. */@Configurationpublic 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
  • 接下来通过测试接口调用近程服务,发现能够失常调用,拜访地址:http://localhost:8308/userFei...

服务实例缓存

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. */@Configurationpublic 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获取用户信息,用户名称为:macro2022-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#DEFAULT_GROUP@@nacos-user-service

总结

明天通过对LoadBalancer的一波实际咱们能够发现,应用LoadBalancer和Ribbon的区别其实并不大,次要是一些配置形式的雷同。如果你之前应用过Ribbon的话,基本上能够无缝切换到LoadBalancer。

参考资料

官网文档:https://docs.spring.io/spring...

我的项目源码地址

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