微服務(wù)之間的調(diào)用(Ribbon與Feign)

概述

在前面的文章中愕秫,我們講了使用Eureka作為服務(wù)注冊中心宣决,在服務(wù)啟動(dòng)后,各個(gè)微服務(wù)會(huì)將自己注冊到Eureka server月弛。那么服務(wù)之間是如何調(diào)用?又是如何進(jìn)行負(fù)載均衡的呢科盛?本文講講服務(wù)之間調(diào)用及負(fù)載均衡Ribbon帽衙。

目前,在Spring cloud 中服務(wù)之間通過restful方式調(diào)用有兩種方式

  • restTemplate+Ribbon
  • feign

從實(shí)踐上看贞绵,采用feign的方式更優(yōu)雅(feign內(nèi)部也使用了ribbon做負(fù)載均衡)厉萝。

本文使用如下的項(xiàng)目實(shí)例來分別演示這兩種寫法。

  • hello服務(wù),讀取數(shù)據(jù)庫谴垫,返回信息
  • world服務(wù)章母,返回信息
  • helloworld服務(wù),調(diào)用了hello服務(wù)和world服務(wù)(restTemplate+Ribbon方式)
  • helloworldfeign服務(wù)翩剪,調(diào)用了hello服務(wù)和world服務(wù)(feign方式)
項(xiàng)目之間關(guān)系.png

本文項(xiàng)目代碼:springcloud-demo
如何理解客戶端Ribbon
zuul也有負(fù)載均衡的功能乳怎,它是針對外部請求做負(fù)載,那客戶端ribbon的負(fù)載均衡又是怎么一回事前弯?

客戶端ribbon的負(fù)載均衡蚪缀,解決的是服務(wù)發(fā)起方(在Eureka注冊的服務(wù))對被調(diào)用的服務(wù)的負(fù)載,比如我們查詢商品服務(wù)要調(diào)用顯示庫存和商品明細(xì)服務(wù)博杖,通過商品服務(wù)的接口將兩個(gè)服務(wù)組合椿胯,可以減少外部應(yīng)用的請求筷登,比如手機(jī)App發(fā)起一次請求即可剃根,可以節(jié)省網(wǎng)絡(luò)帶寬,也更省電前方。

ribbon是對服務(wù)之間調(diào)用做負(fù)載狈醉,是服務(wù)之間的負(fù)載均衡,zuul是可以對外部請求做負(fù)載均衡惠险。


Ribbon負(fù)載均衡.png

hello服務(wù)與world服務(wù)

hello服務(wù)
Git項(xiàng)目代碼:hello
pom.xml

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>        
        <!--connect the db-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>1.5.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.31</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>1.5.6.RELEASE</version>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Dalston.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

啟動(dòng)類HelloApplication:

package com.example.hello;

import com.example.hello.model.Info;
import com.example.hello.repository.InfoRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;


@EnableDiscoveryClient
@SpringBootApplication
public class HelloApplication {

    private static final Logger log= LoggerFactory.getLogger(HelloApplication.class);
    public static final String KEY="Database";
    public static final String VALUE="MYSQL FROM BILLJIANG";

    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }

    @Bean
    public CommandLineRunner initDatabase(InfoRepository repository){
        return (args) -> {
            repository.save(new Info(KEY,VALUE));
        };
    }
}


入口類HelloController:

package com.example.hello.controller;

import com.example.hello.HelloApplication;
import com.example.hello.model.HelloMessage;
import com.example.hello.model.Info;
import com.example.hello.repository.InfoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


@RestController
public class HelloController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private InfoRepository infoRepository;

    @GetMapping("/")
    public String home(){
        return "hello";
    }

    @GetMapping("/message")
    public HelloMessage getMessage() {
        HelloMessage helloMessage = new HelloMessage();
        helloMessage.setName(getLocalInstanceInfo());
        helloMessage.setMessage(getInfoFromDatabase());
        return helloMessage;
    }


    private String getLocalInstanceInfo() {
        ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance();
        return serviceInstance.getServiceId() + ":" + serviceInstance.getHost() + ":" + serviceInstance.getPort();
    }


    private String getInfoFromDatabase() {
        List<Info> infoList = infoRepository.findByName(HelloApplication.KEY);
        for (Info info : infoList) {
            return info.toString();
        }
        return "(no database info)";
    }
}

配置文件application.yml:


#隨機(jī)端口
server:
  port: 0

#服務(wù)注冊中心
eureka:
  client:
    service-url:
      default-zone: http://localhost:8761/eureka
  instance:
    instance-id: ${spring.application.name}:${spring.application.instance_id:${random.value}}

#數(shù)據(jù)源
spring:
  application:
    name: hello
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/${{MYSQL_DATABASE}:foodb}
    username: ${{MYSQL_USERNAME}:root}
    password: ${{MYSQL_PASSWORD}:billjiang}
    testWhileIdle: true
    validationQuery: SELECT 1
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
      properties:
        hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect
  zipkin:
    base-url: http://127.0.0.1:9411

其他類略苗傅,請參照源碼:

world
world服務(wù)與hello服務(wù)類似,不過沒有連接數(shù)據(jù)庫
項(xiàng)目代碼:world

helloworld調(diào)用hello服務(wù)和world服務(wù)

采用restTemplate+Ribbon調(diào)用服務(wù)班巩。
項(xiàng)目代碼:helloworld

啟動(dòng)類HelloworldApplication:
配置restTemplate的Bean

package com.example.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableDiscoveryClient
public class HelloworldApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloworldApplication.class, args);
    }

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

入口類HelloworldController:

package com.example.helloworld.controller;

import com.example.helloworld.model.HelloMessage;
import com.example.helloworld.model.HelloworldMessage;
import com.example.helloworld.model.WorldMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * @author billjiang 475572229@qq.com
 * @create 17-8-22
 */
@RestController
public class HelloworldController {

    private static final Logger log = LoggerFactory.getLogger(HelloworldController.class);

    private static final String HELLO_SERVICE_NAME = "hello";

    private static final String WORLD_SERVICE_NAME = "world";

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/")
    public String home() {
        return "hello world";
    }

    @GetMapping("/message")
    public HelloworldMessage getMessage() {
        HelloMessage hello = getMessageFromHelloService();
        WorldMessage world = getMessageFromWorldService();
        HelloworldMessage helloworld = new HelloworldMessage();
        helloworld.setHello(hello);
        helloworld.setWord(world);
        log.debug("Result helloworld message:{}", helloworld);
        return helloworld;
    }

    private HelloMessage getMessageFromHelloService() {
        HelloMessage hello = restTemplate.getForObject("http://hello/message", HelloMessage.class);
        log.debug("From hello service : {}.", hello);
        return hello;
    }

    private WorldMessage getMessageFromWorldService() {
        WorldMessage world = restTemplate.getForObject("http://world/message", WorldMessage.class);
        log.debug("From world service : {}.", world);
        return world;
    }


}

測試:

helloworldfeign調(diào)用hello服務(wù)和world服務(wù)

feign方式調(diào)用服務(wù)渣慕,項(xiàng)目代碼參考:helloworldfeign
pom.xml引入feignx

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

配置啟動(dòng)類注解:

package com.example.helloworldfeign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class HelloworldFeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloworldFeignApplication.class, args);
    }
}

被調(diào)用接口HelloService

package com.example.helloworldfeign.service;

import com.example.helloworldfeign.model.HelloMessage;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author billjiang 475572229@qq.com
 * @create 17-8-23
 */
@FeignClient(value="hello")
public interface HelloService {

    @GetMapping("/message")
    HelloMessage hello();


}

被調(diào)用接口WorldService

package com.example.helloworldfeign.service;

import com.example.helloworldfeign.model.WorldMessage;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * @author billjiang 475572229@qq.com
 * @create 17-8-23
 */
@FeignClient(value="world")
public interface WorldService {

    @GetMapping("/message")
    WorldMessage world();

}

入口類HelloworldController:

package com.example.helloworldfeign.controller;

import com.example.helloworldfeign.model.HelloMessage;
import com.example.helloworldfeign.model.HelloworldMessage;
import com.example.helloworldfeign.model.WorldMessage;
import com.example.helloworldfeign.service.HelloService;
import com.example.helloworldfeign.service.WorldService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author billjiang 475572229@qq.com
 * @create 17-8-23
 */
@RestController
public class HelloworldController {

    private static final Logger log = LoggerFactory.getLogger(HelloworldController.class);

    private static final String HELLO_SERVICE_NAME = "hello";

    private static final String WORLD_SERVICE_NAME = "world";

    @Autowired
    private HelloService helloService;

    @Autowired
    private WorldService worldService;

    @GetMapping("/")
    public String home() {
        return "hello world";
    }

    @GetMapping("/message")
    public HelloworldMessage getMessage() {
        HelloMessage hello = helloService.hello();
        WorldMessage world = worldService.world();
        HelloworldMessage helloworld = new HelloworldMessage();
        helloworld.setHello(hello);
        helloworld.setWord(world);
        log.debug("Result helloworld message:{}", helloworld);
        return helloworld;
    }

}

為了更好的地查看效果,hello服務(wù)和world服務(wù)可以啟動(dòng)多個(gè)實(shí)例抱慌,并把調(diào)用實(shí)例的ip和端口輸出來逊桦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抑进,隨后出現(xiàn)的幾起案子强经,更是在濱河造成了極大的恐慌,老刑警劉巖寺渗,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匿情,死亡現(xiàn)場離奇詭異,居然都是意外死亡信殊,警方通過查閱死者的電腦和手機(jī)炬称,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涡拘,“玉大人转砖,你說我怎么就攤上這事。” “怎么了府蔗?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵晋控,是天一觀的道長。 經(jīng)常有香客問我姓赤,道長赡译,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任不铆,我火速辦了婚禮蝌焚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘誓斥。我一直安慰自己只洒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布劳坑。 她就那樣靜靜地躺著毕谴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪距芬。 梳的紋絲不亂的頭發(fā)上涝开,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機(jī)與錄音框仔,去河邊找鬼舀武。 笑死,一個(gè)胖子當(dāng)著我的面吹牛离斩,可吹牛的內(nèi)容都是我干的银舱。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼跛梗,長吁一口氣:“原來是場噩夢啊……” “哼寻馏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茄袖,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤操软,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后宪祥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聂薪,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年蝗羊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了藏澳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耀找,死狀恐怖翔悠,靈堂內(nèi)的尸體忽然破棺而出业崖,到底是詐尸還是另有隱情,我是刑警寧澤蓄愁,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布双炕,位于F島的核電站,受9級特大地震影響撮抓,放射性物質(zhì)發(fā)生泄漏妇斤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一丹拯、第九天 我趴在偏房一處隱蔽的房頂上張望站超。 院中可真熱鬧,春花似錦乖酬、人聲如沸死相。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽算撮。三九已至,卻和暖如春施掏,著一層夾襖步出監(jiān)牢的瞬間钮惠,已是汗流浹背茅糜。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工七芭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔑赘。 一個(gè)月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓狸驳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親缩赛。 傳聞我的和親對象是個(gè)殘疾皇子耙箍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容