上一篇文章介绍了服务如何注册到Consul,这篇主要介绍服务之间如何调用以及如何进行服务调用的负载均衡.
Ribbon服务调用
Ribbon是Netflix提供的Spring Cloud Netflix里的服务调用及负载均衡的中间件.Ribbon工作时会根据请求的地址去LoadBalance的机器列表里面寻找机器,根据某种负载均衡算法区选择连接哪个机器.
下面先介绍如何利用Ribbon进行服务调用.将上一篇文章里的auth-account服务在不同端口启动2次,分别在bootstrap.yml里通过设置spring.cloud.consul.discovery.instance-id为acc1,acc2为其赋予不同的id.这是因为Consul默认将服务通过ip与服务名进行辨别,同一机器下就需要手动进行设置id,否则只能注册一个服务.auth-account有以下方法.
@RestController
public class UserController {
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public SysUser getUserById(@PathVariable Long id) {
List list = new ArrayList<>();
SysUser user1 = new SysUser();
user1.setId(1L);
user1.setUserName("admin1");//acc1下值为1,acc2则为2
user1.setUserPassword("123");
list.add(user1);
SysUser user2 = new SysUser();
user2.setId(1001L);
user2.setUserName("test1");//acc1下值为1,acc2则为2
user2.setUserPassword("456");
list.add(user2);
return id.intValue() < list.size() ? list.get(id.intValue()) : null;
}
}
接着为消费者服务添加以下依赖.
org.springframework.cloud
spring-cloud-starter-consul-discovery
org.springframework.cloud
spring-cloud-starter-ribbon
服务消费者同样需要在Consul进行注册,这样才能进行服务调用.接着为了启用Ribbon需要在Spring配置文件里配置RestTemplate的Bean.
@EnableDiscoveryClient
@SpringBootApplication
public class AuthCenterApplication {
@Bean
@LoadBalanced //必须有此注解才能将其他服务链接解释成具体地址,同时开启负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(AuthCenterApplication.class, args);
}
}
RestTemplate是Spring提供的用来发起网络访问的java类,在配置完成后可以通过以下方式进行服务调用.
@RestController
public class CenterController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public SysUser getUserById(@PathVariable Long id) {
//这里的地址是服务在consul注册的服务名称,不是服务id
return restTemplate.getForObject("http://auth-account/user/" + id, SysUser.class);
}
}
消费者服务启动后使用Postman进行测试结果.
出现这样的结果就是服务调用成功了.在进行多次测试后会发现acc1和acc2服务会被轮流调用,这就是Ribbon负载均衡的效果.
负载均衡原理
Ribbon是如何进行负载均衡的呢?
Ribbon实现的关键点是为ribbon定制的RestTemplate,Ribbon利用了RestTemplate的拦截器机制,在拦截器中实现Ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。RestTemplate中有一个属性是List
Ribbon定义了一个LoadBalancerInterceptor拦截器进行拦截请求,然后实现负载均衡调用。拦截器定义在org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration.LoadBalancerInterceptorConfig里面,下面是具体代码
@Configuration
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
static class LoadBalancerInterceptorConfig {
LoadBalancerInterceptorConfig() {
}
@Bean
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
//此处实例化拦截器
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List list = new ArrayList(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
在上面RestTemplate的bean注册的时候发现必须要有@LoadBalanced注解,这是通过注解标明了此RestTemplate对象需要此拦截器,下面看看拦截器的代码实现.
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
//此处为拦截方法
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
再具体看看loadBalancer.execute方法是如何进行负载均衡的.
public T execute(String serviceId, LoadBalancerRequest request, Object hint) throws IOException {
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
//此处getServer就是负载均衡的实现点
Server server = this.getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
上面的getServer方法就是具体的负载均衡实现,里面进行具体服务调用机器的选择.代码也十分的简单.
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
上面就是根据ILoadBalance配置进行负载均衡器chooseServer的选择,默认为default.
具体代码如下.
public Server chooseServer(Object key) {
if (this.counter == null) {
this.counter = this.createCounter();
}
this.counter.increment();
if (this.rule == null) {
return null;
} else {
try {
//此处即为根据配置选择负载均衡规则
return this.rule.choose(key);
} catch (Exception var3) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", new Object[]{this.name, key, var3});
return null;
}
}
}
在rule.choose处进行负载均衡规则的加载,Ribbon提供很多种类的负载均衡规则,比如根据随机数进行服务机器选择等等.
以上就是Ribbon负载均衡实现的简单介绍,有兴趣的可以自己去ide里面根据跳转查看具体代码!