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<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里面根据跳转查看具体代码!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容