SpringCloud 02 —— 负载均衡之Ribbon

负载均衡概念

略,应该都懂不知道的可以自行搜索...

Ribbon 介绍

Ribbon是Netflix下的负载均衡项目,通过Spring Cloud的封装的工具Spring Cloud Ribbon,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。主要提供了以下特性:

  • 负载均衡器,可支持插拔式的负载均衡原则
  • 对多种协议提供支持,例如HTTP、TCP、UDP等
  • 集成了负载均衡功能的客户端

它不需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要

Ribbon主要由4个子模块组成

  • ribbon-core:ribbon的核心,主要包含负载均衡器、负载均衡接口、客户端接口、内置负载均衡实现API
  • ribbon-httpclient:为负载均衡提供了REST客户端
  • ribbon-loadbalancer:负载均衡模块,可以独立使用,也可和其它模块一起使用
  • ribbon:在 ribbon 模块和 Hystrix 基础之上,集成了 负载均衡、容错处理、缓存/批处理的API

Ribbon 负载均衡器主键

Ribbon 的负载均衡器主要与集群中的各个服务进行通信,负载均衡器主要提供已下功能:

  • 维护服务器的IP、DNS的信息
  • 根据特定的逻辑在服务列表中循环

为了实现负载均衡的基础功能,RIbbon的负载均衡器提供了已下三大模块:

  • Rule: Ribbon提供的逻辑主键,决定了服从服务器列表中返回那个服务器实例
  • Ping: 主要使用定时器来确保服务器的网络可以连接
  • ServerList: 服务器列表,可以静态的配置服务器列表,也可以动态指定服务器列表

Ribbon提供的负载均衡算法

  • RoundRobinRule(轮询算法)
  • RandomRule(随机算法)
  • AvailabilityFilteringRule():会先过滤由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问
  • WeightedResponseTimeRule():根据平均响应的时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够会切换到WeightedResponseTimeRule
  • RetryRule():先按照RoundRobinRule的策略获取服务,如果获取失败则在制定时间内进行重试,获取可用的服务。
  • BestAviableRule():会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

Ribbon 服务实体

这里我们将创建一个负载均衡连接两个web服务。

先建服务端ribbon-server提供一个RESTFUL的接口,然后在创建Ribbon客户端调动,并将负载的效果显示出来。

新建模块后导入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <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>

创建启动类

@EnableDiscoveryClient
@SpringBootApplication
public class RibbonServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(RibbonServerApplication.class, args);
    }
}

添加application.yml配置

server:
  port: 8773
spring:
  application:
    name: ribbon-server
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka

这里为了更好的演示RESTFUL接口,和前面一样创建一些示例(可以把前面eureka创建的直接拿来用)

User实体对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    /**
     * 主键Id
     */
    private long id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 昵称
     */
    private String rolename;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 备注
     */
    private String remark;
}

UserService 接口

public interface UserService {

    /**
     * 模拟数据库获取所有用户
     * @return
     */
    List<User> getUsers();

    /**
     * 模拟数据库根据id获取用户
     * @return
     */
    User getUserById(long id);
}

实现类

@Service
public class UserServiceImpl implements UserService {

    @Override
    public List<User> getUsers() {
        return initUser();
    }

    @Override
    public User getUserById(long id) {
        List<User> userList = getUsers().stream().filter(user -> user.getId() == id).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(userList)) {
            return new User(0, null, null, null, "用户不存在!");
        }
        return userList.get(0);
    }

    /**
     * 模拟数据库初始化数据
     *
     * @return
     */
    private List<User> initUser() {

        List<User> userList = new ArrayList<>();
        User user1 = new User(1, "007", "詹姆斯邦德", "zms@gmail.com", "12345");
        User user2 = new User(2, "9527", "华安", "h_a@gmail.com", "321");
        User user3 = new User(3, "47", "代号47", "47@gmail.com", "请扫描头顶二维码");

        userList.add(user1);
        userList.add(user2);
        userList.add(user3);

        return userList;
    }
}

RESTFUL 接口

@RestController
@RequestMapping("user")
public class RibbonController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id:\\d+}")
    public User getUserById(@PathVariable Long id, HttpServletRequest req){
        String url = req.getRequestURL().toString();
        User user = userService.getUserById(id);
        user.setRemark(user.getRemark()+":提供服务的是:"+url);
        return user;
    }
}

然后我们启动项目验证提供的服务是否可用,后面再创建负载客户端。

首先启动前面8761端口的eureka服务器,再启动ribbon server。

访问127.0.0.1:8761,看到已经注册:

接口验证正常

然后在启动方式中设置并行运行,然后修改端口为8774之后再启动。

server:
  port: 8774
spring:
  application:
    name: ribbon-server
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka

再次查看Eureka控制台,两个均已注册

Ribbon客户端

在前面两个服务体创建好了之后,新建ribbon-client模块。导入依赖

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

新建启动类

@EnableDiscoveryClient
@SpringBootApplication
public class RibbonClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(RibbonClientApplication.class, args);
    }
}

创建配置

server:
  port: 8772
spring:
  application:
    name: ribbon-client
eureka:
  client:
    service-url:
      defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka

因为服务端是接口返回的User 类,所以接收端也需要同样的User类.把上面的User类复制粘贴过来

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    /**
     * 主键Id