美文网首页
spring cloud实战(2) -- 服务调用及负载均衡Ri

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

作者: Cal_15ed | 来源:发表于2019-01-20 16:54 被阅读0次

    上一篇文章介绍了服务如何注册到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<SysUser> 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;
        }
    
    }
    

    接着为消费者服务添加以下依赖.

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

    服务消费者同样需要在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<ClientHttpRequestInterceptor> 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<ClientHttpRequestInterceptor> 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> T execute(String serviceId, LoadBalancerRequest<T> 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) -- 服务调用及负载均衡Ri

          本文链接:https://www.haomeiwen.com/subject/wgexjqtx.html