spring cloud实战(2) -- 服务调用及负载均衡Ribbon

上一篇文章介绍了服务如何注册到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进行测试结果.


调用acc2服务
调用acc1服务

出现这样的结果就是服务调用成功了.在进行多次测试后会发现acc1和acc2服务会被轮流调用,这就是Ribbon负载均衡的效果.

负载均衡原理

Ribbon是如何进行负载均衡的呢?
Ribbon实现的关键点是为ribbon定制的RestTemplate,Ribbon利用了RestTemplate的拦截器机制,在拦截器中实现Ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。RestTemplate中有一个属性是List interceptors,如果interceptors里面的拦截器数据不为空,在RestTemplate进行http请求时,这个请求就会被拦截器拦截.
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里面根据跳转查看具体代码!

你可能感兴趣的:(spring cloud实战(2) -- 服务调用及负载均衡Ribbon)