SpringCloud系列之服务消费者Ribbon(三)

服务注册常用的两个组件,前面的文章都介绍的比较详细了,今天要交流的是服务调用的方式,也就是我们常说的负载均衡,简单而言就是通过一种算法,给用户指定的请求分配到一个指定的服务来出来用户的请求,这个在很多组件和框架中都是有的,比如LVS,Ngixn,MyCat,Dubbo等,而且它们之间有很多的算法都是类似的,因为对于负载均衡而言,常用的算法就那几种,比如 随机,轮询,加权轮询,最近最少用,最少访问数等,对于实现的形式主要有两大类,一类是在服务端处理的,一类是在客户端处理的,而我们今天要介绍的springcloud的负载均衡也是类似的,它的实现功能是ribbon和fegion,今天先讲解第一个ribbon,下一篇文章会讲解fegion,概括一下本篇内容的大纲

& 概括ribbon

& 介绍负载均衡的类型

& 介绍普通的ribbon

& 介绍自定义ribbon

& 介绍不依赖eureka的ribbon

一:Spring Cloud Ribbon

   Spring Cloud Ribbon 是一个客户端负载均衡的组件,主要提供客户侧的软件负载均衡算法

    负载均衡就是分发请求流量到不同的服务器,负载均衡分为两种:

服务端负载:服务器端负载均衡是对客户透明的,用户请求到LB服务器,真正的Application服务器是由LB服务器分发控制的,目前的实现有软件(ngnix,HA Proxy等)和硬件(F5等).

客户端负载:是客户端软件的一部分,客户端获知到可用的服务器列表按一定的均衡策略,分发请求.

客户端软负载核心:

    • 服务发现,发现依赖服务的列表
    • 服务选择规则,在多个服务中如何选择一个有效服务
    • 服务监听,检测失效的服务,高效剔除失效服务

二:Ribbon中的负载均衡实现

        AbstractLoadBalancerRule:负载均衡策略的抽象类,在该抽象类中定义了负载均衡器ILoadBalancer对象,该对象能够在具体实现选择服务策略时,获取到一些负载均衡器中维护的信息来作为分配依据,并以此设计一些算法来实现针对特定场景的高效策略。

RandomRule:随机策略,从服务实例清单中随机选择一个服务实例。获得可用实例列表upList和所有实例列表allList,并通过rand.nextInt(serverCount)函数来获取一个随机数,并将该随机数作为upList的索引值来返回具体实例。

RoundRobinRule:轮询策略,按照线性轮询的方式依次选择每个服务实例。通过AtomicInteger nextServerCyclicCounter对象实现,每次进行实例选择时通过调用incrementAndGetModulo函数实现递增。

RetryRule:重试策略,具备重试机制的实例选择。内部定义了RoundRobinRule,并实现了对RoundRobinRule进行反复尝试的策略,若期间能够选择到具体的服务实例就返回,若选择不到就根据设置的尝试结束时间为阈值,当超过该阈值后就返回null。

WeightedResponseTimeRule:权重策略,根据实例的运行情况来计算权重,并根据权重来挑选实例,以达到更优的分配效果。通过定时任务为每个服务进行权重计算,平均响应时间小的权重区间(总平均响应时间-实例平均响应时间)就大,实力选择根据权重范围随机选择,落在哪个区间则选择哪个实例。

BestAvailableRule:最佳策略,通过遍历负载均衡器中维护的所有服务实例,会过滤掉故障的实例,并找出并发请求数最小的一个,选出最空闲的实例。

AvailabilityFilteringRule:可用过滤策略:先过滤出故障的或并发请求大于阈值一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选出一个。

ZoneAvoidanceRule:区域感知策略:使用主过滤条件(区域负载器,选择最优区域)对所有实例过滤并返回过滤后的实例清单,依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤,判断最小过滤数(默认1)和最小过滤百分比(默认0),满足条件则使用RoundRobinRule选择实例。

三:代码演示普通的ribbon

ok,继续在之前的项目上新建立一个模块叫user_server_consumer_ribbon,整体模块结构如下图所示:


首先看一下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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud</artifactId>
        <groupId>com.suning.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user_server_consumer_ribbon</artifactId>


    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</artifactId>
        </dependency>

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

    </dependencies>

</project>

接下来看一下controller的代码,其实相比之前而言 只不过添加了一个方法,为了进行测试

package com.server.consumer.ribbon.controller;

import com.server.consumer.ribbon.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
* @Author 18011618
* @Date 16:12 2018/7/9
* @Function
*/
@RestController
public class UserRobbinController {

  @Autowired
  private RestTemplate restTemplate;

  @Value("${user.userServicePath}")
  private String userServicePath;

  @GetMapping("/ribbon/{id}")
  public User findById(@PathVariable Long id) {
    //简化http模版请求
    return this.restTemplate.getForObject(this.userServicePath+"/findUser/" + id, User.class);
  }

  @GetMapping("/ribbon/showInfo")//测试负载均衡的方法
  public String showInfo(@RequestParam String message){
    return restTemplate.getForObject(this.userServicePath+"/getInfo?name="+message,String.class);
  }
}
 

这里仍然还是需要注入@RestTemplate,接下来看main类

package com.server.consumer.ribbon;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author 18011618
 * @Description
 * @Date 9:54 2018/7/10
 * @Modify By
 */
@EnableDiscoveryClient
@SpringBootApplication
@EnableEurekaClient     //注册到Erukeapublic class ServerConsumerRibbonApplication {
    @Bean
    @LoadBalanced   //启动负载均衡
    public RestTemplate restTemplate() {
        return new RestTemplate();//封装http的请求的模版
    }
    public static void main(String[] args) {
        SpringApplication.run(ServerConsumerRibbonApplication.class,args);
    }
}
 

这里主要多了一个注解@LoadBalanced,ribbon的负载均衡主要就是靠它来实现的,接下来看一下配置文件的内容:

spring:
  application:
    name: user-service-consumer-ribbon  #服务应用名称
server:
  port: 7902 #服务端口号
user: 
  userServicePath: http://service-provider-user/ #服务提供者的url
eureka:
  client:
    healthcheck:
      enabled: true #启动服务健康状态的检查
    serviceUrl:
      defaultZone:  http://admin:admin@localhost:8080/eureka #注册服务组件的url
  instance:
    prefer-ip-address: true #使用ip前缀

和之前也是没有多少差别,唯一的差别就是红色标注的地方,服务提供者不再写死了,而是写注册在服务上的应用名称来调用..,

消费者写好了,接下来完成服务提供者,新建一个模块叫user_server_provider,整体代码结构如下所示:


看一下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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud</artifactId>
        <groupId>com.suning.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>user_server_provider</artifactId>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--集成JPA-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <!--启动WEB-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--集成H2DTATABASE-->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--集成EUREKA-SERVER-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!--系统自省和监控的集成功能-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>


</project>

看一下controller的内容:

package com.suning.provider.controller;

import com.suning.provider.bean.User;
import com.suning.provider.respoitory.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author 18011618
 * @Description 用户提供服务的controller
 * @Date 9:51 2018/7/9
 * @Modify By
 */
@RestController
public class UserController {

    @Value("${server.port}")
    String port;



    @Autowired
    private UserRepository userRepository; //数据访问层接口

    @GetMapping("/findUser/{id}")
    public User findUserById(@PathVariable long id){
        return this.userRepository.findOne(id);
    }


    @GetMapping("/getInfo")
    public String getServerPort(@RequestParam String name){
        return name+",this from server port is:"+port;
    }
}


红色标注的就是新添加的方法,用来测试负载均衡的,接下来看一下main方法类:

package com.suning.provider;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Author 18011618
 * @Description
 * @Date 9:55 2018/7/9
 * @Modify By
 */
@SpringBootApplication      //服务启动是以springboot方式
@EnableAutoConfiguration        //自动检查相关配置的依赖
@EnableEurekaClient         //当前服务注册到eurekaserverpublic class UserServerProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServerProviderApplication.class,args);
    }
}
 

最后看一下对应的配置文件内容

server:
  port: 7900  #配置服务端口号
spring:
  jpa:
    generate-ddl: false
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:
    platform: h2
    schema: classpath:schema.sql
    data: classpath:data.sql
  application:
    name: service-provider-user #配置springjpa的支持
logging:
  level:
    root: INFO
    org.hibernate: INFO
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.hibernate.type.descriptor.sql.BasicExtractor: TRACE #设置日志格式
    com.itmuch: DEBUG
eureka:
  client:
    healthcheck:
      enabled: true #启动服务健康状态检查
    serviceUrl:
      defaultZone: http://admin:admin@localhost:8080/eureka #服务注册组件的url--单个节点
      #defaultZone: http://localhost:8761/eureka,http://localhost:8762/eureka,http://localhost:8763/eureka
      #服务高可用HA,多个地址以,号分开
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${spring.cloud.client.ipAddress}:${spring.application.instance_id:${server.port}}
    metadata-map:
      zone: ABC      # eureka可以理解的元数据
      lilizhou: BBC  # 不会影响客户端行为
    lease-renewal-interval-in-seconds: 5

既然要模拟负载均衡,所以肯定需要启动多个服务者的实例,这里通过修改红色标注的端口号,来启动多个本地实例,我这里是修改为7900和7904,这样就启动了两个服务者的实例,然后启动之前项目中的eurkea(server_eureka),这个时候在浏览器端访问

http://localhost:7902/ribbon/showInfo?message=i%20am%20jhp,会交替出现:



从结果中,可以很显然看出来消费者通过负载均衡的方式来调用服务提供者.

四:自定义ribbon实现

有时候在实际业务中,可能需要根据不同的服务来实现不同的负载均衡的策略,来满足特地的业务需求,其实ribbon已经提供了这样的机制,接下来就仔细说一下这种机制的实现方案,一般有两种,一种是通过编码来实现,一种是通过配置文件来实现,下面分开演示。

比如,就拿上面的user_server_provider模块来说,希望能够实现对于service_provider_user,使用随机负载均衡,而除此之外的服务(假设叫service_provider_user2)使用轮询负载均衡,那么这样的一个功能该如何实现呢,下面就分别通过编码和配置文件两种方式来演示。

& 通过编码实现自定义ribbon

ok,新建立一个模块,整体模块结构如下


首选需要定义一个空注解,这个注解不需要任何功能,只是起到标注的作用(排除springboot包扫描的范围,否则自定义的配置是没有效果的)

package com.consumer.ribbon.custom;

/**
 * @Author 18011618
 * @Description 为了指定指定服务采用新定的规则,需要在启动类同级目录下添加类“ExcludeComponentScan.java”:
 * @Date 14:22 2018/7/10
 * @Modify By
 */
public @interface ExcludeFromComponentScan {

}
 

然后再编写一个自定义的Configuration:

package com.consumer.ribbon.custom;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author 18011618
 * @Description 定义自定义负载均衡的算法策略
 * @Date 14:27 2018/7/10
 * @Modify By
 */
@Configuration
@ExcludeFromComponentScan
public class MyConfiguration {
    @Bean
    public IRule ribbonRule(){
        return new RandomRule(); //这里使用随机
    }
}
 

这个配置类的实例,主要定义了一个随机负载均衡实现,然后还加了我们自定义的那个注解。当然当前这个类必须要使用

@Configuration这个注解来进行声明。写一个测试的controller,

package com.consumer.ribbon.custom.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @Author 18011618
 * @Description
 * @Date 11:48 2018/7/10
 * @Modify By
 */
@RestController
public class TestController {
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/test")
    public String test() {
        ServiceInstance serviceInstance = this.loadBalancerClient.choose("service-provider-user");
        System.out.println("访问服务器信息001" + ":" + serviceInstance.getServiceId() + ":" + serviceInstance.getHost() + ":" + serviceInstance.getPort());

        ServiceInstance serviceInstance2 = this.loadBalancerClient.choose("service-provider-user2");
        System.out.println("访问服务器信息002" + ":" + serviceInstance2.getServiceId() + ":" + serviceInstance2.getHost() + ":" + serviceInstance2.getPort());

        return "1";
    }

}
 

通过可以获取到ServiceInstance,包含服务的IP,端口号等信息,最后看一下main方法类的代码:

package com.consumer.ribbon.custom;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.client.RestTemplate;

/**
 * @Author 18011618
 * @Description
 * @Date 11:49 2018/7/10
 * @Modify By
 */
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "service-provider-user",configuration = MyConfiguration.class)
@ComponentScan(excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = ExcludeFromComponentScan.class)})
public class CustomRibbonApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(CustomRibbonApplication.class,args);
    }
}
 

这块多了几个新注解,没事下面细说一下:

通过这个注解可以为服务的负载均衡进行细粒度的控制,上面的意思是说要为service-provider-user,使用我们自定义的那个配置,而那个自定义的配置,定义了负载均衡是随机的,所以这个服务也就使用了随机负载均衡

这个注解的作用就是排除自定义的配置是不包含在springboot的包扫描范围之内,否则自定义配置会完全失效的.

最后看一下配置文件内容:

spring:
  application:
    name: service-ribbon-custom-test
server:
  port: 8010
eureka:
  client:
    healthcheck:
      enabled: true
    serviceUrl:
      defaultZone:  http://admin:admin@localhost:8080/eureka
  instance:
    prefer-ip-address: true

接下来来验证效果:修改一下服务提供者的名称为让后启动,之后再修改一下端口号,我这里修改的7903和7905,这个时候在运行,然后在浏览器端:http://localhost:8010/test,控制台会交替打印每个服务的调用实例信息:


& 通过配置文件实现自定义ribbon

通过编码的方式实现,感觉是不是有点啰嗦,没有关系如果讨厌编码的话,可以使用另一种方式,那就是通过配置文件的方式,为了方便演示,再重新建立一个模块叫,代码结构如下所示:


代码机会没有动,就是修改了controller里面的打印信息:

package com.consumer.ribbon.custom.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author 18011618
 * @Description
 * @Date 11:48 2018/7/10
 * @Modify By
 */
@RestController
public class TestController {
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @GetMapping("/test")
    public String test() {
        ServiceInstance serviceInstance = this.loadBalancerClient.choose("service-provider-user");
        System.out.println("001" + ":" + serviceInstance.getServiceId() + ":" + serviceInstance.getHost() + ":" + serviceInstance.getPort());

        ServiceInstance serviceInstance2 = this.loadBalancerClient.choose("service-provider-user2");
        System.out.println("002" + ":" + serviceInstance2.getServiceId() + ":" + serviceInstance2.getHost() + ":" + serviceInstance2.getPort());

        return "1";
    }

}

看一下main方法类:

package com.consumer.ribbon.custom;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author 18011618
 * @Description
 * @Date 15:21 2018/7/10
 * @Modify By
 */
@SpringBootApplication
@EnableEurekaClient
public class CustomRibbonFileApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(CustomRibbonFileApplication.class,args);
    }
}
 

相比上面的 简单了很多,那么自定义负载均衡是怎么实现的呢,很显然是在配置文件里面啊,好 那就看一下配置文件

spring:
  application:
    name: service-ribbon-custom-file
server:
  port: 8011
eureka:
  client:
    healthcheck:
      enabled: true
    serviceUrl:
      defaultZone:  http://admin:admin@localhost:8080/eureka
  instance:
    prefer-ip-address: true

#为这个服务指定负载均衡策略
service-provider-user:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

不错就是红色标注的地方,指定服务的名称以及对应的负载均衡策略,好简单啊,OK ,这个时候直接启动,浏览器端访问http://localhost:8011/test,看一下控制台打印的结果


& 不依赖于eureka

正常情况下ribbon是到eureka去获取一个可用的服务的,但是如果万一eureka挂了 还能负载均衡吗?ribbon是支持的,我们可以通过禁止使用eureka,指定服务器列表,它也是可以实现负载均衡。因为代码几乎都是一样的,这里就不再阐述了,看一下配置文件即可

spring:
  application:
    name: service-ribbon-custom-no-eureka
server:
  port: 8012
eureka:
  client:
    healthcheck:
      enabled: true
    serviceUrl:
      defaultZone:  http://admin:admin@localhost:8080/eureka
  instance:
    prefer-ip-address: true

ribbon:
  eureka:
   enabled: false   #禁用eureka
service-provider-user:
  ribbon:
    listOfServers: localhost:7900,localhost:7904  #直接指定服务提供方地址列表

红色标注的就是禁用eureka和指定配置服务提供的地址列表,重新启动再次访问浏览器,结果应该和上面是一样的。到这里关于ribbon的负载均衡使用就讲解完了

版权声明:本文为博主原创文章,未经博主允许不得转载:https://blog.csdn.net/qq_18603599/article/details/80941676

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值