前不久,我把Mall微服务版本全面升级了 ,在通过Gateway网关调用其他服务的时候,出现了
Service Unavailable
的问题。排查原因时发现作为负载均衡组件的Ribbon被弃用了,作为Netflix开源的一个组件,Ribbon早已进入维护状态。现在推荐使用的是Loadbalancer,今天我们就来聊聊Loadbalancer的使用!
SpringCloud实战电商项目mall-swarm
(8.8k+star)地址:github.com/macrozheng/…
LoadBalancer是Spring Cloud官方提供的负载均衡组件,可用于替代Ribbon。其使用方式与Ribbon基本兼容,可以从Ribbon进行平滑过渡。
下面介绍下LoadBalancer的基本使用,我们将使用Nacos作为注册中心,通过
nacos-loadbalancer-service
和nacos-user-service
两个服务间的相互调用来进行演示。
我们将使用RestTemplate来演示下LoadBalancer的负载均衡功能。
nacos-loadbalancer-service
模块的pom.xml
文件中添加LoadBalancer相关依赖;- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-loadbalancerartifactId>
- dependency>
- 复制代码
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
- 复制代码
- /**
- * 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
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.cloudgroupId>
- <artifactId>spring-cloud-starter-openfeignartifactId>
- dependency>
- 复制代码
- /**
- * 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
getUser(@PathVariable Long id); -
- @GetMapping("/user/getByUsername")
- CommonResult
getByUsername(@RequestParam String username); -
- @PostMapping("/user/update")
- CommonResult update(@RequestBody User user);
-
- @PostMapping("/user/delete/{id}")
- CommonResult delete(@PathVariable Long id);
- }
- 复制代码
- /**
- * 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);
- }
- }
- 复制代码
application.yml
中添加如下内容;- feign:
- client:
- config:
- default: # Feign调用超时配置
- connectTimeout: 5000
- readTimeout: 5000
- 复制代码
LoadBalancer为了提高性能,不会在每次请求时去获取实例列表,而是将服务实例列表进行了本地缓存。
默认的缓存时间为35s
,为了减少服务不可用还会被选择的可能性,我们可以进行如下配置。
- spring:
- cloud:
- loadbalancer:
- cache: # 负载均衡缓存配置
- enabled: true # 开启缓存
- ttl: 5s # 设置缓存时间
- capacity: 256 # 设置缓存大小
- 复制代码
如果你想在每次远程调用中传入自定义的请求头的话,可以试试
LoadBalancerRequestTransformer
,通过它可以对原始请求进行一定的转换。
LoadBalancerRequestTransformer
的Bean实例,这里我们将ServiceInstance
的instanceId
放入到请求头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
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#DEFAULT_GROUP@@nacos-user-service
- 复制代码
今天通过对LoadBalancer的一波实践我们可以发现,使用LoadBalancer和Ribbon的区别其实并不大,主要是一些配置方式的相同。如果你之前使用过Ribbon的话,基本上可以无缝切换到LoadBalancer。
最近面试的小伙伴很多,对此我整理了一份Java面试题手册:基础知识、JavaOOP、Java集合/泛型面试题、
Java异常面试题、Java中的IO与NIO面试题、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、
Memcached、MongoDB、Spring、SpringBoot、SpringCloud、RabbitMQ、Dubbo、MyBatis、ZooKeeper、数据结构、算法、
Elasticsearch、Kafka、微服务、Linux等等。可以分享给大家学习。【持续更新中】领取方式【999】就可以领取资料了