Spring Cloud-Ribbon负载均衡

简述

Spring Cloud Netflix Ribbon是Spring Cloud Netflix子项目的核心组件之一,主要给服务间调用及API网关转发提供负载均衡的功能。

Ribbon简介

在微服务架构中,每个服务都会部署多个节点,当有服务调用该服务时,如何保证负载均衡是个不得不考虑的问题。负载均衡可以增加系统的可用性和扩展性,当我们使用RestTemplate来调用其他服务时,Ribbon可以很方便的实现负载均衡的功能。

RestTemplate的使用

RestTemplate是一个HTTP客户端,使用它我们可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法。

GET请求方法

public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) 

public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)

public <T> T getForObject(URI url, Class<T> responseType)

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)

public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)

public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType)

getForObject方法
返回对象为响应体封装对象。
示例如下:

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

getForEntity方法
返回对象为ResponseEntity对象,包含响应头、响应状态码、响应体等。
示例如下:

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

POST请求方法

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)

public <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables)

public <T> T postForObject(URI url, @Nullable Object request, Class<T> responseType)

public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)

public <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Map<String, ?> uriVariables) 

public <T> ResponseEntity<T> postForEntity(URI url, @Nullable Object request, Class<T> responseType)

方法与GET请求方法类似。

postForObject方法示例:

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

postForEntity方法示例:

@PostMapping("/insert")
public Result insert(@RequestBody User user) {
    return restTemplate.postForEntity(userServiceUrl + "/user/insert", user, Result.class).getBody();
}

PUT请求方法

public void put(String url, @Nullable Object request, Object... uriVariables) 

public void put(String url, @Nullable Object request, Map<String, ?> uriVariables)

public void put(URI url, @Nullable Object request)

PUT方法示例:

@PutMapping("/update")
public Result update(@RequestBody User user) {
    restTemplate.put(userServiceUrl + "/user/update", user);
    return Result.isSuccess();
}

DELETE请求方法

public void delete(String url, Object... uriVariables)

public void delete(String url, Map<String, ?> uriVariables)

public void delete(URI url)

DELETE方法示例:

@DeleteMapping("/delete/{id}")
public Result delete(@PathVariable Long id) {
    restTemplate.delete(userServiceUrl + "/user/delete/{id}", id);
    return Result.isSuccess();
}

创建user-service模块(被调用方)

创建user-service模块,用于给ribbon服务提供调用。

pom.xml添加对应依赖

如下所示:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

application.yml配置端口及注册中心地址

如下所示:

# 指定服务端口
server:
  port: 8201
# 指定服务名称
spring:
  application:
    name: user-service
# 指定注册地址
eureka:
  client:
    # 指定是否从注册中心获取服务
    fetch-registry: true
    # 指定是否将服务注册到注册中心
    register-with-eureka: true
    service-url:
      # 配置注册中心地址
      defaultZone: http://localhost:8001/eureka/

创建基类及接口类

User类:

public class User {

    private Long id;
    private String username;
    private String password;
    // setter和getter方法省略
}

Result类:

public class Result<T> {

    private String message;
    private int code;
    private T data;

    public Result(){}

    public Result(String message, int code) {
        this.message = message;
        this.code = code;
    }

    public Result(String message, int code, T data) {
        this.message = message;
        this.code = code;
        this.data = data;
    }

    public static Result isSuccess() {
        return new Result("操作成功", 200);
    }

    public Result isSuccess(T data) {
        return new Result("操作成功", 200, data);
    }
    //setter和getter方法省略
}

UserController类:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/insert")
    public Result insert(@RequestBody User user) {
        userService.insert(user);
        return Result.isSuccess();
    }

    @GetMapping("/get/{id}")
    public Result getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return new Result().isSuccess(user);
    }

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

    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable Long id) {
        userService.delete(id);
        return Result.isSuccess();
    }
}

UserService接口类:

public interface UserService {
    public int insert(User user);
    public User getUser(Long id);
    public List<User> listUser(User user);
    public int update(User user);
    public int delete(Long id);
}

UserServiceImpl实现类:

@Service
public class UserServiceImpl implements UserService {

    private List<User> userList = new ArrayList<>();

    Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Override
    public int insert(User user) {
        logger.info("新增成功...");
        userList.add(user);
        return 1;
    }

    @Override
    public User getUser(Long id) {
        logger.info("查询用户...");
        for (User user : userList) {
            if (user.getId().longValue() == id) {
                return user;
            }
        }
        return null;
    }

    @Override
    public List<User> listUser(User user) {
        logger.info("查询用户列表...");
        return userList;
    }

    @Override
    public int update(User user) {
        for (User dbUser : userList) {
            if (user.getId().longValue() == dbUser.getId()) {
                userList.remove(dbUser);
                userList.add(user);
                break;
            }
        }
        logger.info("更新成功...");
        return 1;
    }

    @Override
    public int delete(Long id) {
        for (User user : userList) {
            if (user.getId().longValue() == id) {
                userList.remove(user);
            }
        }
        logger.info("删除成功...");
        return 1;
    }
}

创建ribbon-service模块(调用方)

pom.xml添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

注意:由于eureka-client3.0版本默认集成了ribbon,故无需单独引入ribbon依赖;若eureka-client版本为3.0以下,则还需引入netflix-ribbon依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

application.yml配置端口及注册中心地址

如下所示:

# 指定服务端口
server:
  port: 8301
# 指定服务名称
spring:
  application:
    name: ribbon-service
# 指定主机地址
eureka:
  client:
    # 指定是否从注册中心获取服务
    fetch-registry: true
    # 指定是否将服务注册到注册中心
    register-with-eureka: true
    service-url:
      # 配置注册中心地址
      defaultZone: http://localhost:8001/eureka/

service-url:
  user-service: http://user-service

新增RibbonConfig配置类

使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,如下所示:

@Configuration
public class RibbonConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

可以看到,使用Ribbon的负载均衡功能非常简单,和直接使用RestTemplate没什么两样,只需给RestTemplate添加@LoadBalanced注解即可。

添加接口类:

@RestController
@RequestMapping("/user/ribbon")
public class UserRibbonController {

    @Autowired
    private RestTemplate restTemplate;

    @Value("${service-url.user-service}")
    private String userServiceUrl;

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

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

    @GetMapping("/list")
    public List<Result> listUser() {
        return restTemplate.getForObject(userServiceUrl + "/user/list", List.class);
    }

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

    @PutMapping("/update")
    public Result update(@RequestBody User user) {
        restTemplate.put(userServiceUrl + "/user/update", user);
        return Result.isSuccess();
    }

    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable Long id) {
        restTemplate.delete(userServiceUrl + "/user/delete/{id}", id);
        return Result.isSuccess();
    }
}          

负载功能测试

启动eureka-server于8001端口;
启动user-service于8201端口;
启动另外一个user-service为8202端口,复制启动类配置,添加server.port=8202启动即可;

启动完之后,刷新注册中心可查询启动的服务,如下图示:

springcloud-ribbon-01.pngspringcloud-ribbon-01.png

uaa-service接口测试:
使用postman测试CRUD相关接口;

示例-获取用户详情(uaa-service直接访问):
http://localhost:8201/user/100

响应:

{
    "message": "操作成功",
    "code": 200,
    "data": {
        "id": 100,
        "username": "admin",
        "password": "123456"
    }
}

ribbon-service接口测试:
使用postman测试CRUD相关接口;

示例-获取用户详情(ribbon-service调用user-service访问):
http://localhost:8301/user/ribbon/100
响应:

{
    "message": "操作成功",
    "code": 200,
    "data": {
        "id": 100,
        "username": "admin",
        "password": "123456"
    }
}

多次通过ribbon请求上述接口,发现user-service两个控制台,日志交替打印如下所示:
UserServiceApplication控制台日志:

2022-12-07 22:09:40.069  INFO 91332 --- [nio-8201-exec-9] c.w.s.userservice.UserServiceImpl        : 查询用户...
2022-12-07 22:09:43.797  INFO 91332 --- [io-8201-exec-10] c.w.s.userservice.UserServiceImpl        : 查询用户...
2022-12-07 22:09:46.608  INFO 91332 --- [nio-8201-exec-7] c.w.s.userservice.UserServiceImpl        : 查询用户...

UserServiceApplication(1)控制台日志:

2022-12-07 22:09:33.864  INFO 59524 --- [nio-8202-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-12-07 22:09:33.864  INFO 59524 --- [nio-8202-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-12-07 22:09:33.864  INFO 59524 --- [nio-8202-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 0 ms
2022-12-07 22:09:33.881  INFO 59524 --- [nio-8202-exec-2] c.w.s.userservice.UserServiceImpl        : 查询用户...
2022-12-07 22:09:42.296  INFO 59524 --- [nio-8202-exec-4] c.w.s.userservice.UserServiceImpl        : 查询用户...
2022-12-07 22:09:45.333  INFO 59524 --- [nio-8202-exec-3] c.w.s.userservice.UserServiceImpl        : 查询用户...

Ribbon常用配置

全局配置

ribbon:
  ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
  ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
  OkToRetryOnAllOperations: true #对超时请求启用重试机制
  MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
  MaxAutoRetries: 1 # 切换实例后重试最大次数
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

对指定服务配置
与全局配置的区别是:ribbon节点挂载服务名称下面,如下是ribbon-service调用user-service时的单独配置。

user-service:
  ribbon:
    ConnectTimeout: 1000 #服务请求连接超时时间(毫秒)
    ReadTimeout: 3000 #服务请求处理超时时间(毫秒)
    OkToRetryOnAllOperations: true #对超时请求启用重试机制
    MaxAutoRetriesNextServer: 1 #切换重试实例的最大个数
    MaxAutoRetries: 1 # 切换实例后重试最大次数
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #修改负载均衡算法

Ribbon的负载均衡策略

所谓的负载均衡策略,就是当A服务调用B服务时,此时B服务有多个实例,这时A服务以何种方式来选择调用的B实例,ribbon可以选择以下几种负载均衡策略。

  • com.netflix.loadbalancer.RandomRule:随机策略
  • com.netflix.loadbalancer.RoundRobinRule:轮询策略
  • com.netflix.loadbalancer.RetryRule:在轮询策略的基础上添加重试机制;
  • com.netflix.loadbalancer.WeightedResponseTimeRule:对轮询策略的扩展,响应速度越快的示例,选择权重越大,越容易被选择;
  • com.netflix.loadbalancer.BestAvailableRule:选择并发较小的实例;
  • com.netflix.loadbalancer.AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例;
  • com.netflix.loadbalancer.ZoneAwareLoadBalanver:采用双重过滤,同时过滤不是同一区域的实例和故障实例,再选择并发较小的实例;

(完)

最后修改于:2022年12月07日 22:40

添加新评论