SpringCloud(五):客户端负载均衡器—Ribbon
(编写不易,转载请注明:http://shihlei.iteye.com/blog/2404997)
一 概述
负载均衡,简单说即将 “负载” 按照一定策略分摊到不同的执行单元中执行。
服务的负载均衡目前主要实现策略:
1)服务器端反向代理:硬负载 F5, 软负载 LVS,Nginx;可以透明接入,但要独立搭建高可用架构。
2)客户单端负载均衡器:lib形式提供,可以根据需要灵活控制策略,但需要客户端集成。
Ribbon 属于客户端负载均衡器,主要应用于服务消费者,支持RestTemplate,Feign等请求技术集成,支持从配置文件或从Eureka重拉取Server实例列表,其他需要原生开发。
负载均衡算法:
随机,轮询,响应时间加权等等
git:https://github.com/Netflix/ribbon
本文规划:
1)Ribbon 原生使用及自定义rule。
2)SpringCloud RestClient集成Ribbon及简要说明实现机制。
二 Ribbon demo
1) 原生demo
(1)添加依赖
<dependency> <groupId>com.netflix.ribbon</groupId> <artifactId>ribbon-loadbalancer</artifactId> <version>2.2.5</version> </dependency>
(2)demo
package x.demo.netflix.ribbon; import java.util.Arrays; import java.util.List; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.IPing; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.LoadBalancerBuilder; import com.netflix.loadbalancer.NoOpPing; import com.netflix.loadbalancer.RoundRobinRule; import com.netflix.loadbalancer.Server; /** * LoadBalancerDemo */ public class LoadBalancerDemo { public static void main(String[] args) { // 负载均衡规则 IRule rule = new RoundRobinRule(); // ping 规则 IPing ping = new NoOpPing(); // 添加Server 列表 List<Server> serverList = Arrays.asList( new Server("server1"), new Server("server2"), new Server("server3"), new Server("server4"), new Server("server5") ); //初始化LoadBalancer ILoadBalancer loadBalancer = LoadBalancerBuilder .newBuilder() .withRule(rule) .withPing(ping) .buildFixedServerListLoadBalancer(serverList); // 简单查看效果 for (int i = 0; i < 10; i++) { System.out.println(loadBalancer.chooseServer(null)); } } }
2)Ribbon 结果简单分析
Ribbon 源码比较简单,结构也比较清晰,很方便看,主要体系:LoadBalancer,Rule,Ping 可以简单看看。
(1)LoadBalancer 体系:
【1】 接口级别:
a)ILoadBalancer:定义LoadBalancer的基本行为,主要是从ServerList中根据策略选择一个Server实例进行请求。
核心方法:
- addServers:向LoadBalancer增加Server实例。
- chooseServer:通过某种策略,从ServerList选择一个Server实例。
- markServerDown:通知LoadBalancer某个Server实例已Down,下次请求可以忽略。
- getReachableServers:获取可用的Server实例列表。
- getAllServers:获取所有Server实例列表。
b)AbstractLoadBalancer:抽象LoadBalancer的一般操作。
【2】实现级别:
c)BaseLoadBalancer:基础实现,把实现依赖抽象成如下几大块,做用户定制和扩展,
主要依赖:
- IClientConfig:提供配置信息,如ping 超时时间等。
- IRule:提供Server实例选择策略。
- IPing:提供验证实例是否可用策略。
d)DynamicServerListLoadBalancer:可动态添加ServerList(yml文件等),并提供Filter过滤能力
e) ZoneAwareLoadBalancer:提供Zone 负载均衡
(2)Rule体系
【1】接口级别:
a)IRule:提供Server选择策略
核心方法:Server choose(in Object key)
b)AbstractLoadBalancerRule:抽象Rule的一般操作,主要维护一个ILoadBalancer实例,提供get,set方法,完成绑定,以便获得SeverList供选择使用
【2】实现级别:
d)RandomRule
e)RoundRobinRule(及ClientConfigEnabledRoundRobinRule),
f)WeightedResponseTimeRule(返回时间权重规则)
g)BestAvailableRule(最低并发规则)
【3】其他包装类:
h)RetryRule:支持重试的规则
(3)Ping体系
【1】接口级别:
a)IPing:提供判读Sever实例是否可用的策略
核心方法:boolean isAlive(Server server)
b)AbstractLoadBalancerPing:主要维护一个ILoadBalancer实例,提供get,set方法,完成绑定,以便获得SeverList以便尝试时使用
【2】实现级别:
c)NoOpPing:无任何操作,永远返回true,有效。
d)DummyPing:也是永远返回true。
e)NIWSDiscoveryPing:基于发现的Ping规则
3)自定义一个Rule实现只使用第一个服务器
package x.demo.springcloud.webfront.service.impl.ribbon; import java.util.List; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.Server; /** * 只请求第一台server * */ public class FirstServerRule extends AbstractLoadBalancerRule { @Override public Server choose(Object key) { List<Server> servers = getLoadBalancer().getReachableServers(); return servers.get(0); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { } }
三 SpringCloud Ribben + RestClient
1)概述
SpringCloud 对 LoadBalancer 进行了高层抽象,用于多种Http客户端获得负载均衡的能力。Ribbon作为一种基础实现,通过SpringCloud AutoConfiguration机制集成。
注:demo 默认使用《SpringCloud文章系列》的 “microservice-time” 时间微服务,作为服务提供者,为简化项目复杂度,禁用Eureka 服务发现能力。
具体项目环境可参见《SpringCloud(一): SpringBoot 创建简单的微服务》
本文主要改造微服务客户端项目:
spring-cloud-webfront:服务调用者,调用“时间”微服务,返回当前时间。
2)集成使用Demo
(1)添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </dependency>
(2)RestClient 集成 Ribbon
只需要通过在@Configuration中添加创建RestTemplate的Bean,并使用@LoadBalanced进行注解即可
package x.demo.springcloud.webfront.service.impl.ribbon; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * RestTemplateRibbon * */ @Configuration public class RestTemplateRibbonConfiguration { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
(3)配置服务器信息
a)yml 文件配置方式
spring: profiles: Standalone application: name: webfront server: port: 20001 # 自定义配置 timeMisroService: v1: uri: http://microservice-time/time/v1 # Ribbon 负载均衡配置 # 取消eureka 依赖 ribbon: eureka: enabled: false # microservice-time 相对于虚拟主机,RestTemplate 必须使用microservice-time 访问 才能走复杂均衡策略 microservice-time: ribbon: # LoadBalancer NFLoadBalancerClassName: com.netflix.loadbalancer.DynamicServerListLoadBalancer # 由于禁用了Ribbon Eureka获取服务ip端口,需要手动提供 listOfServers: 127.0.0.1:10001,127.0.0.1:10002 # 负载均衡策略 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # ping策略 NFLoadBalancerPingClassName: com.netflix.loadbalancer.NoOpPing
注:
microservice-time.ribbon 是针对 microservice-time 的 ribbon配置,microservice-time 相当于虚拟主机,使用时host需要使用 microservice-time 作为虚拟主机,如地址http://microservice-time/time/v1,Ribbon根据策略获取实际的IP和端口,这里是通过microservice-time.ribbon.listOfServers获取的
其他定制项:
- NFLoadBalancerClassName: 默认 DefaultClientConfigImpl
- NFLoadBalancerPingClassName: 默认 DummyPing
- NFLoadBalancerRuleClassName: 默认 ZoneAvoidanceRule
- NIWSServerListClassName: 默认 ConfigurationBasedServerList
- NIWSServerListFilterClassName: 默认 ZonePreferenceServerListFilter
b)程序配置方式:
package x.demo.springcloud.webfront.service.impl.ribbon; import java.util.Arrays; import java.util.List; import com.netflix.loadbalancer.IPing; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.NoOpPing; import com.netflix.loadbalancer.RandomRule; import com.netflix.loadbalancer.Server; import com.netflix.loadbalancer.ServerList; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.context.annotation.Bean; /** * Ribbon 负载均衡配置 * <p> * 特别注意: * 这里是针对 name = "microservice-time" 的服务进行的配置,所以不能 添加 @Configuration ,否则接入容器,会全局生效 * */ @RibbonClient(name = "microservice-time", configuration = TimeMicroServiceConfiguration.class) public class TimeMicroServiceConfiguration { @Bean public IRule rule() { return new RandomRule(); } @Bean public IPing ping() { return new NoOpPing(); } @Bean public ServerList<Server> serverList() { List<Server> servers = Arrays.asList(new Server("127.0.0.1", 10001), new Server("127.0.0.1", 10002)); ServerList<Server> serverList = new ServerList<Server>() { @Override public List<Server> getInitialListOfServers() { return servers; } @Override public List<Server> getUpdatedListOfServers() { return servers; } }; return serverList; } }
(4)service 层
package x.demo.springcloud.webfront.service; public interface TimeService { /** * 获取当前时间 * @return 当前时间,格式:yyyy-MM-dd HH:mm:ss */ String now(); } package x.demo.springcloud.webfront.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import x.demo.springcloud.webfront.service.TimeService; @Service public class TimeServiceRestClientImpl implements TimeService { @Value("${timeMisroService.v1.uri}") private String timeMicroServiceV1Uri; @Autowired private RestTemplate restTemplate; /** * 获取当前时间 * * @return 当前时间,格式:yyyy-MM-dd HH:mm:ss */ @Override public String now() { String url = timeMicroServiceV1Uri + "/now"; ProtocolResult<String> result = restTemplate.getForObject(url, ProtocolResult.class); return result.getBody(); } }
(5)启动类
package x.demo.springcloud.webfront; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringCloudWebfrontApplication { public static void main(String[] args) { SpringApplication.run(SpringCloudWebfrontApplication.class, args); } }
(6)查看结果(略)
(7)SpringCloud Ribbon + FeignClien:
四 SpringCloud LoadBalancer 源码分析
1)概述
(1)LoadBalancerClient体系
a)ServiceInstanceChooser:Service实例选择器接口
核心方法:ServiceInstance choose(String serviceId) :根据serviceId 选择一个 Service 实例。
b)LoadBalancerClient:负载均衡客户端接口,具有选择服务实例,执行请求的能力,继承自 ServiceInstanceChooser;
核心方法:
执行请求方法:
T execute(String serviceId, LoadBalancerRequest<T> request)
T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)
重建请求链接方法:
URI reconstructURI(ServiceInstance instance, URI original);
c)RibbonLoadBalancerClient:基于Ribbon实现的LoadBalancerClient,内部依赖 Ribbon的ILoadBalancer体系
(2) ClientHttpRequestInterceptor体系:
a)RestTemplate:Rest请求客户端,提供Rest服务访问能力
b)ClientHttpRequestInterceptor:RestTemplate提供的拦截器,可以在请求之根据需要进行拦截,一般用实现Header头改写,添加Token等通用的面向切面编程。
c)RetryLoadBalancerInterceptor:实现了ClientHttpRequestInterceptor,拦截Http请求,依赖 LoadBalancerClient 选择Service实例,重新发起请求。实现负载均衡。
(3)AutoConfiguration体系:
a)LoadBalancerAutoConfiguration:配置负载均衡客户端(di主要通过这个进行配置)如果是 RestTemplate 的形式,则在restTemplate 中通过添加RetryLoadBalancerInterceptor; 用于拦截请求。
b)RibbonAutoConfiguration:查看spring.factories文件,拉起Ribbon的默认配置及组装点
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
推荐阅读
-
SpringCloud客户端的负载均衡Ribbon的实现
-
spring cloud 之 客户端负载均衡Ribbon深入理解
-
SpringCloud客户端的负载均衡Ribbon的实现
-
spring cloud 之 客户端负载均衡Ribbon深入理解
-
详解spring cloud中使用Ribbon实现客户端的软负载均衡
-
Spring Cloud Ribbon实现客户端负载均衡的方法
-
详解spring cloud中使用Ribbon实现客户端的软负载均衡
-
Spring Cloud Ribbon实现客户端负载均衡的方法
-
Spring Cloud Ribbon实现客户端负载均衡的示例
-
SpringCloud学习系列之二 ----- 服务消费者(Feign)和负载均衡(Ribbon)