Eureka & Ribbon实现服务调用和负载均衡

Ribbon是什么

Spring cloud Ribbon是基于Netflix Ribbon实现的一套客户端服务调用以及负载均衡的工具。

Ribbon + Eureka + RestTemplate

Eureka已经集成了Ribbon
Eureka集成了Ribbon

Ribbon在工作时分成三步:

  1. 优先选择在同一个区域内负载较少的Eureka Server(当Eureka Server是集群的时候)
  2. 从选择的Eureka Server中获取服务注册列表(服务发现),并将信息缓存起来
  3. 根据用户配置的负载均衡策略,从第二步获取到的服务注册列表中选择一个地址,利用RestTemplate远程调用服务
Ribbon的负载均衡策略

Ribbon和Nginx都可以用来实现负载均衡的功能,那么它们有什么区别呢
Nginx是集中式的负载均衡,即服务器负载均衡,客户端所有请求都会交给Nginx,然后由它实现请求转发,比如浏览器发过来的各种请求都会经过Nginx这第一道大门,Nginx解析请求地址,然后根据一定的映射规则将请求转发
Ribbon是进程内的负载均衡,即本地负载均衡(浏览器先请求该服务,被Nginx转发到该服务,该服务才能继续运行它的逻辑),根据它的负载均衡策略来决定调用同一个服务下的哪个服务接口
即浏览器的请求→Nginx→某微服务→根据负载均衡策略去调用另一个微服务

Ribbon自带的负载均衡策略

所有策略都要继承AbstractLoadBalancerRule抽象类,实现choose方法
Ribbon自带的负载均衡策略

常见几种策略的解释

策略说明
RoundRobinRule轮询
RandomRule随机
RetryRule先按照轮询策略获取服务,如果获取失败则在指定时间内会进行重试,获取可用的服务
WeightedResponseTimeRule对轮询策略的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRule会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
AvailablilityFilteringRule先过滤掉故障实例,再选择并发较小的实例
ZoneAvoidanceRule默认规则,复合判断server所在区域的性能和server的可用性选择服务器

使用默认的轮询策略

eureka客户端

pom.xml

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

application.yaml

server:
  port: 80
spring:
  application:
    name: cloud-ribbon-order #注册到eureka服务端时使用的微服务名称
eureka:
  client:
    register-with-eureka: true #将本应用注册到eureka服务端
    fetch-registry: true #从eureka服务端获取服务注册列表
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka #eureka服务端地址

启动类

@SpringBootApplication
@EnableEurekaClient //开启eureka客户端
@EnableDiscoveryClient //开启服务发现功能
public class ApplicationContextMain80 {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationContextMain80.class,args);
    }
}

配置类

@Configuration
public class CustomizeConfiguration {

	//配置远程调用工具
    @Bean
    @LoadBalanced //开启负载均衡功能(默认用的轮询策略)
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

@RestController
public class OrderController {

    @Autowired
    RestTemplate restTemplate;


    public static final String url = "http://CLOUD-RIBBON-PAYMENT"; //将调用的微服务的地址,CLOUD-RIBBON-PAYMENT是将访问的微服务名称

    @GetMapping("/ribbon/order")
    public String getPaymentById() {
		//远程调用
        return restTemplate.getForObject(url + "/ribbon/payment", String.class, (Object) null);
    }
}

替换负载均衡策略

以替换成随机策略为例,有两种方法:配置文件,配置类

配置文件
application.xml增加如下负载均衡策略,可以实现对特定微服务的负载均衡配置,比如配置支付服务使用的负载均衡策略为随机策略,那么当需要访问支付服务时就会使用随机策略,而访问其他服务则依然使用默认的策略

CLOUD-RIBBON-PAYMENT: #要实现特定负载均衡配置的微服务名称
  ribbon:
    NFLoadBalancerRuleClassName: com.fl.demo.lb.MyIRule #配置的特定负载均衡策略

配置类

分两步:1.写一个配置类 2.在启动类上加@RibbonClient注解

写配置类,注意,该配置类不能放在启动类所在的包或其子包下,否则将失去针对某一微服务的特定配置策略功能,所有微服务都会受到策略修改的影响

@Configuration
public class RandomRuleConfiguration {

	//配置一个随机策略
    @Bean
    public MyIRule randomRule(){
        return new RandomRule();
    }
}

在启动类上加@RibbonClient注解,其中name表示要修改负载均衡策略的微服务名称,configuration填写的是上面定义的配置类,这样就将特定的微服务和特定的策略绑定,调用其他微服务时依然使用的是默认的负载均衡策略

@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@RibbonClient(name = "CLOUD-RIBBON-PAYMENT", configuration = RandomRuleConfiguration.class)
public class ApplicationContextMain80 {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationContextMain80.class,args);
    }
}

自定义负载均衡策略

我们先来看看自带的轮询负载均衡策略是怎么写的

//这里继承了AbstractLoadBalancerRule抽象类
public class RoundRobinRule extends AbstractLoadBalancerRule {
    private AtomicInteger nextServerCyclicCounter; //记录访问次数
    private static final boolean AVAILABLE_ONLY_SERVERS = true;
    private static final boolean ALL_SERVERS = false;
    private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

    public RoundRobinRule() {
    	//初始化访问次数为0
        this.nextServerCyclicCounter = new AtomicInteger(0);
    }

    public RoundRobinRule(ILoadBalancer lb) {
        this();
        this.setLoadBalancer(lb);
    }

	//业务逻辑方法
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;//选择的实例
            int count = 0;//获取实例失败后的重试次数

            while(true) {
            	//如果当前还没有获取到实例并且重试次数没有超过10次,则尝试获取实例
                if (server == null && count++ < 10) {
                	//获取所调用微服务的所有可用实例
                    List<Server> reachableServers = lb.getReachableServers();
                    //获取所调用微服务的所有实例
                    List<Server> allServers = lb.getAllServers();
                    //所有可用实例个数
                    int upCount = reachableServers.size();
                    //所有实例个数
                    int serverCount = allServers.size();
                    //如果可用实例和所有实例个数均不为0,则尝试获取实例
                    if (upCount != 0 && serverCount != 0) {
                    	//根据某一算法获取一个下标,参数为所有实例的个数
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        //根据下标从所有实例中获取实例
                        server = (Server)allServers.get(nextServerIndex);
                        //若获取的实例为空(可能是高并发引起),则当前线程释放cpu,稍等一会再尝试获取
                        if (server == null) {
                            Thread.yield();
                        } else {
                        	//若获取到的实例可用并且已经做好了服务的准备,则将实例返回
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }
							//获取到的实例不可用或是实例没有准备好服务,则将实例置为null,重新尝试获取实例
                            server = null;
                        }
                        continue;
                    }

					//服务注册列表中没有可用实例,则直接返回null
                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

				//如果重试了10次依然获取不到可用的实例,则日志记录报错信息
                if (count >= 10) {
                    log.warn("No available alive servers after 10 tries from load balancer: " + lb);
                }

                return server;
            }
        }
    }

	//获取下标的算法
	//算法:(当前访问次数+1)% 服务实例的数量得到一个下标,实现轮询的效果
    private int incrementAndGetModulo(int modulo) {
        int current;//当前访问次数
        int next;//下标
        do {
            current = this.nextServerCyclicCounter.get();//获取当前访问次数
            next = (current + 1) % modulo;//计算得到下标
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next));//cas尝试把访问次数更新为下标

        return next;
    }

	//需要实现的方法
    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }
}

总结一下几点:

  1. 需要继承AbstractLoadBalancerRule抽象类,并实现public Server choose(Object key)public void initWithNiwsConfig(IClientConfig clientConfig)方法,后者先用空实现就行
  2. public Server choose(ILoadBalancer lb, Object key)private int incrementAndGetModulo(int modulo)方法承担了主要的业务逻辑,前者是获取实例,包括空判断,重试等,后者是获取下标的计算逻辑,前者调用了后者
  3. public Server choose(Object key)方法调用了public Server choose(ILoadBalancer lb, Object key)方法

依据这三点我们可以模仿着自定义一个策略:将轮询的一次换一个实例替换成三次换一个实例,即每个实例需要访问三次后才能轮到下一个实例

public class MyIRule extends AbstractLoadBalancerRule{

    AtomicInteger count = new AtomicInteger(0);//访问次数

	//该方法保持空实现就行
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    public MyIRule(){}

    public MyIRule(ILoadBalancer iLoadBalancer){
        setLoadBalancer(iLoadBalancer);
    }

	//需要实现的方法
    @Override
    public Server choose(Object o) {
        return this.choose(this.getLoadBalancer(), key);
    }

	//简单地根据下标获取实例,不考虑重试以及实例是否可用的问题
	public Server choose(ILoadBalancer lb, Object key){
		if(loadBalancer == null)return null;
        List<Server> allServers = loadBalancer.getAllServers();
        if(allServers == null || allServers.size() == 0)return null;
        return allServers.get(incrementAndGetModulo(allServers.size()));
	}

	//计算获得下标
	//算法:假设现在有两个实例,则sum=2*3=6,那么(访问次数+1)%sum的结果是0~5轮询,我们规定,结果为0~2的就访问第一个实例,3~5访问第二个实例,为了满足这个需求,我们只需要将该结果除以3即可获得下标
    private int incrementAndGetModulo(int modulo){
        int current;
        int next;
        int sum = 3 * modulo;
        int result;
        do{
            current = count.get();
            next = (current + 1) % sum;
            result = next / 3;
        }while(!count.compareAndSet(current, next));

        return result;
    }
}

接下来我们需要把自定义的策略配置给相应的微服务使之生效,如何配置请参考上面提到的“替换负载均衡策略”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值