Ribbon实战

  RibbonNetflix开发的客户端负载均衡器,为Ribbon配置服务提供者地址列表后,Ribbon就可以基于某种负载均衡策略算法让服务消费者去请求提供者。Ribbon默认为我们提供了很多负载均衡算法,例如轮询、随机等,也可以实现自定义负载均衡算法。

一、Ribbon的组成

官网首页:github.com/Netflix/rib…

  • ribbon-core:核心的通用性代码

  • ribbon-eureka:基于eureka封装的模块,能快速集成eureka

  • ribbon-examples:学习示例

  • ribbon-httpclient:基于Apache HttpClient封装的rest客户端,集成了负载均衡模块

  • ribbon-loadbalancer:负载均衡算法模块

  • ribbon-transport:基于netty实现多协议的支持,比如httptcpudp

1. 负载均衡形式

负载均衡的目的就是使集群中服务器的负载保持在稳定高效的状态,从而提供整个系统的请求处理能力。通常分为客户端负载均衡服务端负载均衡

(1)客户端负载均衡

在客户端负载均衡中,所有的客户端节点都有一份自己要访问的服务端地址列表,这些服务端地址列表都是从服务注册中心拉取的

spring-cloud-all.drawio.png

(2)服务端负载均衡

在服务端负载均衡中,在客户端和服务端中间使用代理,比如Nginx。但客户端节点只知道服务代理地址,服务代理则知道所有服务端的地址

spring-cloud-all-服务端负载均衡.drawio.png

总结: 客户端负载均衡和服务端负载均衡最大的区别在于 服务端地址列表的存储位置,以及负载算法在哪里

二、负载均衡算法

1. RoundRobinRule

轮询策略,Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮,若最终还没有找到,则返回 null

2. RandomRule

随机策略,从所有可用的 provider 中随机选择一个

3. RetryRule

重试策略,先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。

4. BestAvailableRule

最低并发策略,会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。逐个找服务,如果断路器打开,则忽略

6. AvailabilityFilteringRule

可用过滤策略,会先过滤掉多次访问故障而处于熔断状态的服务、过滤并发的连接数量超过阀值得服务,然后对剩余的服务列表按照轮询策略进行访问

7. ZoneAvoidanceRule

区域权衡策略,复合判断 Server 所在区域的性能和 Server 的可用性,轮询选择服务器

8. WeightedResponseTimeRule

响应时间加权策略,据平均响应时间计算所有的服务的权重,响应时间越快服务权重越大,容易被选中的概率就越高。刚启动时,如果统计信息不中,则使用 RoundRobinRule,等统计的信息足够了会自动的切换到 WeightedResponseTimeRule 。响应时间长,权重低,被选择的概率低

三、Ribbon 负载均衡搭建

3.1 服务提供方

  • pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>2.3.10.RELEASE</version>

        <relativePath/>

    </parent>



    <groupId>com.example</groupId>

    <artifactId>user-provider</artifactId>
    <version>0.0.1-SNAPSHOT</version>




    <properties>
        <java.version>8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>



        <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

        </dependency>



        <dependency>

            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
  • application.properties 配置
server.port=8081
#
# 
eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/
#
# 客户端在注册中心中的名称
eureka.instance.instance-id=user-provider-8081
#
# 设置当前 client 每5秒向 server 发送一次心跳,默认 30s
eureka.instance.lease-renewal-interval-in-seconds=5
#
# 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒
eureka.instance.lease-expiration-duration-in-seconds=90
#
# 当前服务名称
spring.application.name=user-provider
#
# 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到server
eureka.instance.prefer-ip-address=true
#
# eureka 服务名,默认值 unknown;如果没有配置,则取 spring.application.name
#eureka.instance.appname=user-provider
#
# 实例的虚拟主机名称,默认值 unknown;如果没有配置,则取 spring.application.name
#eureka.instance.virtual-host-name=user-provider
#
# 对外开放所有监控端点
management.endpoints.web.exposure.include=*
#
# 是否将自己注册到其他Eureka Server,默认为true
eureka.client.register-with-eureka=true
#
# 是否从eureka server获取注册信息, 需要
eureka.client.fetch-registry=true
  • 代码实现
public class User implements Serializable {




    private String idCard;

    private String username;




    public User() {

    }








    public User(String idCard, String username) {

        this.idCard = idCard;

        this.username = username;

    }




    public String getUsername() {

        return username;

    }



    public void setUsername(String username) {

        this.username = username;

    }





    public String getIdCard() {

        return idCard;

    }





    public void setIdCard(String idCard) {

        this.idCard = idCard;

    }


    @Override
    public String toString() {
        return "User{" +
                "idCard='" + idCard + ''' +
                ", username='" + username + ''' +
                '}';
    }
}


@SpringBootApplication

@EnableDiscoveryClient

public class UserServiceProviderApplication {




    public static void main(String[] args) {

        SpringApplication.run(UserServiceProviderApplication.class, args);
    }








}

@RestController

@RequestMapping("/user")
public class UserController {
    private final static Log log = LogFactory.getLog(UserController.class);



    @GetMapping("/get")
    public User getUser() {
        User user = new User("110", "大宝");
        log.info("UserProvider result : " + user);
        return user;
    }

}

3.2 服务消费方

  • pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>2.3.10.RELEASE</version>

        <relativePath/>

    </parent>



    <groupId>com.example</groupId>

    <artifactId>web-consumer</artifactId>
    <version>0.0.1-SNAPSHOT</version>






    <properties>
        <java.version>8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>




    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>


    <dependencies>
        <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>



        <dependency>

            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

    </dependencies>



    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • application.properties 配置
server.port=7071
# 注册中心地址
eureka.client.service-url.defaultZone=http://admin:123@ek1.com:7901/eureka/,http://admin:123@ek2.com:7902/eureka/
#
# 客户端在注册中心中的名称
eureka.instance.instance-id=web-consumer-7071
#
# 设置当前 client 每5秒向 server 发送一次心跳,默认 30s
eureka.instance.lease-renewal-interval-in-seconds=5
#
# 表示eureka server至上一次收到client的心跳之后,等待下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认为90秒
eureka.instance.lease-expiration-duration-in-seconds=90
#
# 当前服务对外暴露的名称
spring.application.name=web-consumer
#
# 表示将自己的ip注册到Eureka Server上。不配置,表示将操作系统的 hostname 注册到 server
eureka.instance.prefer-ip-address=true
#
# 是否将自己注册到其他Eureka Server
eureka.client.register-with-eureka=true
#
# 是否从eureka server获取注册信息
eureka.client.fetch-registry=true
#
# 表示eureka client间隔多久去拉取服务注册信息,默认为30秒,如果要迅速获取服务注册状态,可以缩小该值,比如5秒
eureka.client.registry-fetch-interval-seconds=5
#
# 设置 ribbon 和服务注册中心Eureka一起工作,ribbon 可以从服务注册中心获取服务端的地址信息
ribbon.eureka.enabled=true
  • 代码实现
public class User implements Serializable {




    private String idCard;

    private String username;




    public User() {

    }








    public User(String idCard, String username) {

        this.idCard = idCard;

        this.username = username;

    }




    public String getUsername() {

        return username;

    }



    public void setUsername(String username) {

        this.username = username;

    }





    public String getIdCard() {

        return idCard;

    }





    public void setIdCard(String idCard) {

        this.idCard = idCard;

    }

}
public class ResultBody<T> implements Serializable {



    private boolean status;




    // 响应码
    private String code;

    // 响应描述信息
    private String message;

    // 响应数据
    private T data;



    public ResultBody() {
    }


    private ResultBody(T data) {
        this.data = data;
    }

    private ResultBody(String code, String msg) {
        this.code = code;
        this.message = msg;
    }





    public static <T> ResultBody<T> success() {
        ResultBody<T> result = new ResultBody<>();
        result.setCode("1");
        result.setStatus(Boolean.TRUE);
        result.setMessage("成功");
        return result;
    }


    public static <T> ResultBody<T> success(T data) {
        ResultBody<T> result = new ResultBody<>();
        result.setCode("1");
        result.setStatus(Boolean.TRUE);
        result.setMessage("成功");
        result.setData(data);
        return result;
    }



    public static <T> ResultBody<T> error(String code, String message) {
        ResultBody<T> result = new ResultBody<>(code, message);
        result.setStatus(Boolean.FALSE);
        return result;
    }

    public boolean isStatus() {
        return status;
    }



    public void setStatus(boolean status) {
        this.status = status;
    }

    public String getCode() {
        return code;
    }


    public void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
@Configuration
public class RestTemplateConfig {

    // 开启消费者的负载均衡,默认负载均衡策略是:轮询; @LoadBalanced 注解的作用就是标识 RestTemplate 为 LoadBalancerClient 客户端
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
        RestTemplate build = restTemplateBuilder.build();
        return build;
    }


}
@SpringBootApplication

@EnableDiscoveryClient

public class WebConsumerApplication {




    public static void main(String[] args) {

        SpringApplication.run(WebConsumerApplication.class, args);
    }








}

@RestController

@RequestMapping("/web")
public class UserProviderController {




    private final static Logger log = LoggerFactory.getLogger(UserProviderController.class);

    private final static String PROVIDER_SERVICE_APPLICATION_NAME = "user-provider";





    @Resource
    private EurekaClient client;
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private LoadBalancerClient loadBalancerClient;



    /**
     * RestTemplate + LoadBalancerClient 方式实现负载均衡
     */
    @GetMapping("/balancer-client")
    public ResultBody getUserByLoadBalancerClient() {




        ServiceInstance server = loadBalancerClient.choose(PROVIDER_SERVICE_APPLICATION_NAME);
        String url = server.getUri() + "/user/get";

        RestTemplate restTemplate = new RestTemplateBuilder().build();
        User res = restTemplate.getForObject(url, User.class);

        return ResultBody.success(res);
    }

    /**
     * RestTemplate + @LoadBalanced 方式实现负载均衡
     */
    @GetMapping("/get")
    public ResultBody getUserByRestTemplate() {
        // 服务提供者的应用名称,当使用  RestTemplate +  @LoadBalanced 实现客户端的负载均衡时,这的请求路径并不是ip+端口号
        String url = "http://" + PROVIDER_SERVICE_APPLICATION_NAME + "/user/get";


        User res = restTemplate.getForObject(url, User.class);
        return ResultBody.success(res);
    }



    /**
     * EurekaClient + ip + 端口 方式直连请求,这种方式没有走负载均衡
     */
    @GetMapping("/client")
    public Object ekClient() {
        InstanceInfo info = client.getNextServerFromEureka(PROVIDER_SERVICE_APPLICATION_NAME, false);
        if (InstanceInfo.InstanceStatus.UP.equals(info.getStatus())) {
            String url = "http://" + info.getHostName() + ":" + info.getPort() + "/user/get";
            log.info("服务端接口地址url : " + url);



            RestTemplate restTemplate = new RestTemplate();
            User res = restTemplate.getForObject(url, User.class);
            return ResultBody.success(res);
        }
        return ResultBody.error("-1", "失败");
    }

}

总结:

  1. 消费者请求服务提供者有多种调用方式
    (1)EurekaClient + ip + 端口 的方式,该种方式相当于直连,没有走ribbon负载均衡
    (2)RestTemplate + @LoadBalanced注解 方式实现负载均衡
     (3)  RestTemplate + LoadBalancerClient 方式实现负载均衡

  2. 使用 ribbon 做客户端负载均衡时,使用RestTemplate + @LoadBalanced注解的方式请求服务提供者时url不能使用端口+ip,应该将端口+ip替换成服务提供者的spring.application.name属性值

  3. Spring Cloud 2020 版本之后,spring-cloud-starter-netflix-eureka-client 中不再有ribbonjar,如果使用高版本,需要单独引入

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

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYui8bgn' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片