博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Microservice with SpringCloud
阅读量:6860 次
发布时间:2019-06-26

本文共 25863 字,大约阅读时间需要 86 分钟。

hot3.png

Microservice with SpringCloud 博客分类: 架构 spring 微服务

Microservice 其實不是很好管理,可想而知會有非常多路由、組態、監控等問題要搞,但是如果你團隊都是用Java的話,基本上 SpringCloud 提供非常多組件,讓你使用一些簡單設定檔跟 Annotation 就可以搞定 Discovery、Synchronize Settings、Proxy、LoadBalance、Realtime Dashboards、LogAnalyzer 等機制,例如下圖。

架構圖

選擇組件的話到這邊勾選需要的組件後下載專案即可

這次練習選擇使用 SpringBoot1.3 & Gradle

 

使用 SpringBoot 建立 RestAPI

先建立一個 reservation-service 專案

使用到的組件如下

Web JPA Config Client Eureka Discovery Zipkin Stream Redis H2 Actuator
Rest Repositories - - - - - - -

首先先把下面這幾個依賴註解起來,因為暫時用不到

build.gradle

dependencies {    /*    compile('org.springframework.cloud:spring-cloud-starter-config')    compile('org.springframework.cloud:spring-cloud-starter-eureka')    compile('org.springframework.cloud:spring-cloud-starter-zipkin')    compile('org.springframework.cloud:spring-cloud-starter-stream-redis')    compile('org.springframework.boot:spring-boot-starter-actuator')    */    compile('org.springframework.boot:spring-boot-starter-data-jpa')    compile('org.springframework.boot:spring-boot-starter-data-rest')    compile('org.springframework.boot:spring-boot-starter-web')    runtime('com.h2database:h2')    testCompile('org.springframework.boot:spring-boot-starter-test') }

application.properties

server.port=8025

ReservationServiceApplication.java

package com.example;import java.util.Arrays;import java.util.Collection;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import org.springframework.boot.CommandLineRunner;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.repository.query.Param;import org.springframework.data.rest.core.annotation.RepositoryRestResource;import org.springframework.data.rest.core.annotation.RestResource;@SpringBootApplicationpublic class ReservationServiceApplication {        //起動的時候預先塞測試資料    @Bean    CommandLineRunner runner(ReservationRepository rr){        return args -> {            Arrays.asList("Dr. rod,Dr. Syer,Juergen,ALL THE COMMUNITY,Josh".split(","))            .forEach( x -> rr.save(new Reservation(x)));;            rr.findAll().forEach( System.out::println);        };    }    public static void main(String[] args) {        SpringApplication.run(ReservationServiceApplication.class, args);    }}//這個註解是把你的Repository直接變成 RESTful API@RepositoryRestResourceinterface ReservationRepository extends JpaRepository
{    @RestResource(path = "by-name")    Collection
 findByReservationName( @Param("rn") String rn);}@Entityclass Reservation{    @Id    @GeneratedValue    private Long id;    private String reservationName;    public Reservation(){}    public Reservation(String reservationName) {        this.reservationName = reservationName;    }    public Long getId() {        return id;    }    public String getReservationName() {        return reservationName;    }    @Override    public String toString() {        StringBuilder sb = new StringBuilder("Reservation{");        sb.append("id=").append(id);        sb.append(", reservationName='").append(reservationName).append("'}");        return sb.toString();    }}

這是產生器幫我們建立的測試範本ReservationServiceApplicationTests.java

package com.example;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.test.context.web.WebAppConfiguration;import org.springframework.boot.test.SpringApplicationConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@SpringApplicationConfiguration(classes = ReservationServiceApplication.class)@WebAppConfigurationpublic class ReservationServiceApplicationTests {    @Test    public void contextLoads() {    }}

接著使用GET 向  取得資料,得到這樣的結果

 

{  "_embedded": {    "reservations": [      {        "reservationName": "Dr. rod",        "_links": {          "self": {            "href": "http://localhost:8025/reservations/1"          },          "reservation": {            "href": "http://localhost:8025/reservations/1"          }        }      },      {        "reservationName": "Dr. Syer",        "_links": {          "self": {            "href": "http://localhost:8025/reservations/2"          },          "reservation": {            "href": "http://localhost:8025/reservations/2"          }        }      },      {        "reservationName": "Juergen",        "_links": {          "self": {            "href": "http://localhost:8025/reservations/3"          },          "reservation": {            "href": "http://localhost:8025/reservations/3"          }        }      },      {        "reservationName": "ALL THE COMMUNITY",        "_links": {          "self": {            "href": "http://localhost:8025/reservations/4"          },          "reservation": {            "href": "http://localhost:8025/reservations/4"          }        }      },      {        "reservationName": "Josh",        "_links": {          "self": {            "href": "http://localhost:8025/reservations/5"          },          "reservation": {            "href": "http://localhost:8025/reservations/5"          }        }      }    ]  },  "_links": {    "self": {      "href": "http://localhost:8025/reservations"    },    "profile": {      "href": "http://localhost:8025/profile/reservations"    },    "search": {      "href": "http://localhost:8025/reservations/search"    }  },  "page": {    "size": 20,    "totalElements": 5,    "totalPages": 1,    "number": 0  }}

簡單幾行就可以把資料庫轉成 RESTful API 主要是靠這個 @RepositoryRestResource

 

建立統一管理的 Config Server

 

建立一個 config-server 專案

使用到的組件如下

Config Server

主要依賴是 spring-cloud-config-server

build.gradle

dependencies {    compile('org.springframework.cloud:spring-cloud-config-server')    testCompile('org.springframework.boot:spring-boot-starter-test') }

application.properties

spring.cloud.config.server.git.uri=D:/springcloud/config-repo  server.port=8888
  • spring.cloud.config.server.git.uri 設定應用起動時從哪裡讀取設定檔,可以從Github,也可以從本地Git檔案

D:/springcloud/config-repo資料夾放的檔案

application.yml

server.port: ${PORT:${SERVER_PORT:0}}info.id: ${spring.application.name}debug: truespring.sleuth.log.json.enabled: truelogging.pattern.console: "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([trace=%X{X-Trace-Id:-},span=%X{X-Span-Id:-}]){yellow} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex"

reservation-service.properties

server.port=${PORT:8000}  message=HELLO world!  spring.cloud.stream.bindings.input=reservations

起動程式

ConfigServerApplication.java

package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.config.server.EnableConfigServer; @EnableConfigServer @SpringBootApplication public class ConfigServerApplication {     public static void main(String[] args) {         SpringApplication.run(ConfigServerApplication.class, args);     } }

記得加上@EnableConfigServer就可以啟動ConfigServer的功能了

起動 Config-Server 後可以訪問  就可以取得設定相關資料

 

{  "name": "reservation-service",  "profiles": [    "master"  ],  "label": null,  "version": "b017cbcb47700df4ffd7e824614532dd18128040",  "propertySources": [    {      "name": "D:/springcloud/config-repo/reservation-service.properties",      "source": {        "server.port": "${PORT:8000}",        "spring.cloud.stream.bindings.input": "reservations",        "message": "HELLO world!"      }    },    {      "name": "D:/springcloud/config-repo/application.yml",      "source": {        "server.port": "${PORT:${SERVER_PORT:0}}",        "info.id": "${spring.application.name}",        "debug": true,        "spring.sleuth.log.json.enabled": true,        "logging.pattern.console": "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([trace=%X{X-Trace-Id:-},span=%X{X-Span-Id:-}]){yellow} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wex"      }    }  ]}

 

修改 reservation-service

把原先得設定檔改成依靠 Config-Server 提供的

把原先註解掉的依賴加回去 spring-cloud-starter-config 跟 spring-boot-starter-actuator

build.gradle

dependencies {    compile('org.springframework.cloud:spring-cloud-starter-config')    /*     compile('org.springframework.cloud:spring-cloud-starter-eureka')     compile('org.springframework.cloud:spring-cloud-starter-zipkin')     compile('org.springframework.cloud:spring-cloud-starter-stream-redis')     */    compile('org.springframework.boot:spring-boot-starter-actuator')    compile('org.springframework.boot:spring-boot-starter-data-jpa')    compile('org.springframework.boot:spring-boot-starter-data-rest')    compile('org.springframework.boot:spring-boot-starter-web')    runtime('com.h2database:h2')    testCompile('org.springframework.boot:spring-boot-starter-test') }

記得更新一下依賴

把原本的application.properties重新命名為bootstrap.properties並改成以下內容

bootstrap.properties

spring.application.name=reservation-service  spring.cloud.config.uri=http://localhost:8888
  • spring.application.name 應用自己的名稱,到時候可以從介面上看到,也必須對應到設定檔的名稱

  • spring.cloud.config.uri Config-Server的位置

增加個控制器可以顯示從Condif-Server得到的資料

 

@RefreshScope @RestController class MessageRestControler{     @Value("${message}")     private String message;     @RequestMapping("/message")     String message(){         return this.message;     } }

只要啟動後可以在  取得 HELLO world! 的資料

注意 Port 變了喔,因為一開始就從 Config-Server 取得 reservation-service.properties 的內容,也取得了 message=HELLO world! 的內容來呈現。

加上 @RefreshScope 用意是當設定檔有變更時,你可以透過 URL 來觸發更新

 

curl -X POST 'http://localhost:8000/refresh'

但是要怎麼隨時保持同步請看另外一篇研究

 

 

新增自動發現服務

 

建立一個 eureka-server 專案

使用到的組件如下

Config Client Eureka Server

新增 bootstrap.properties 然後把不需要的 application.properties 刪除,因為組態檔我們使用 Config Server 提供的

bootstrap.properties

spring.application.name=eureka-service  spring.cloud.config.uri=http://localhost:8888

新增 eureka-service.properties 到 Config-Server 設定檔路徑底下

eureka-service.properties

server.port=${PORT:8761}  eureka.client.register-with-eureka=false eureka.client.fetch-registry=false  #eureka.client.enabled=false eureka.instance.hostname=localhost  #eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

其實 hostname 理論上是可以不用加的,但是會出現以下的錯誤訊息,不過網路上是說這沒關係是沒有Client的錯誤

 

2015-11-09 14:53:30.685 ERROR 21880 --- [trace=,span=] [nio-8761-exec-1] c.n.eureka.resources.StatusResource : Could not determine if the replica is available java.lang.NullPointerException: null    at com.netflix.eureka.resources.StatusResource.isReplicaAvailable(StatusResource.java:87)    at com.netflix.eureka.resources.StatusResource.getStatusInfo(StatusResource.java:67)    at org.springframework.cloud.netflix.eureka.server.EurekaController.status(EurekaController.java:68)

主程式部份

EurekaServerApplication.java

package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class EurekaServerApplication {     public static void main(String[] args) {         SpringApplication.run(EurekaServerApplication.class, args);     } }

起動後就可以從  觀察到目前有哪些服務

 

修改 reservation-service 新增自動發現的客戶端

把 spring-cloud-starter-eureka 依賴加回去

build.gradle

dependencies {    compile('org.springframework.cloud:spring-cloud-starter-config')    compile('org.springframework.cloud:spring-cloud-starter-eureka')    /*     compile('org.springframework.cloud:spring-cloud-starter-zipkin')     compile('org.springframework.cloud:spring-cloud-starter-stream-redis')     */    compile('org.springframework.boot:spring-boot-starter-actuator')    compile('org.springframework.boot:spring-boot-starter-data-jpa')    compile('org.springframework.boot:spring-boot-starter-data-rest')    compile('org.springframework.boot:spring-boot-starter-web')    runtime('com.h2database:h2')    testCompile('org.springframework.boot:spring-boot-starter-test') }

記得更新依賴後在起動類別加上 @EnableDiscoveryClient

 

@EnableDiscoveryClient@SpringBootApplicationpublic class ReservationServiceApplication {        //起動的時候預先塞測試資料    @Bean    CommandLineRunner runner(ReservationRepository rr){        return args -> {            Arrays.asList("Dr. rod,Dr. Syer,Juergen,ALL THE COMMUNITY,Josh".split(","))            .forEach( x -> rr.save(new Reservation(x)));;            rr.findAll().forEach( System.out::println);        };    }    public static void main(String[] args) {        SpringApplication.run(ReservationServiceApplication.class, args);    }}

起動後再回去  觀察,就可以發現在 DS Replicas Instances currently registered with Eureka 列表中多了一個 RESERVATION-SERVICE 的應用名稱

 

新增 Proxy 機制

 

建立一個 reservation-client 專案

使用到的組件如下

HATEOAS Config Client Eureka Discovery Zuul Hystrix Zipkin Stream Redis Actuator

先暫時將 zipkin 跟 hateoas 註解起來練習 Proxy 機制

build.gradle

dependencies {    compile('org.springframework.boot:spring-boot-starter-actuator')    compile('org.springframework.cloud:spring-cloud-starter-config')    compile('org.springframework.cloud:spring-cloud-starter-eureka')    compile('org.springframework.cloud:spring-cloud-starter-hystrix')    /*     compile('org.springframework.boot:spring-boot-starter-hateoas')     compile('org.springframework.cloud:spring-cloud-starter-zipkin')     */    compile('org.springframework.cloud:spring-cloud-starter-stream-redis')    compile('org.springframework.cloud:spring-cloud-starter-zuul')    testCompile('org.springframework.boot:spring-boot-starter-test') }

把範例的 application.properties 刪掉並新增 bootstrap.properties

bootstrap.properties

spring.application.name=reservation-client  spring.cloud.config.uri=http://localhost:8888

起動程式

ReservationClientApplication.java

package com.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.zuul.EnableZuulProxy;@EnableZuulProxy@EnableDiscoveryClient@SpringBootApplicationpublic class ReservationClientApplication {    public static void main(String[] args) {        SpringApplication.run(ReservationClientApplication.class, args);    }}

在原先的範例程式上增加 @EnableZuulProxy 跟 @EnableDiscoveryClient

起動後即可  取得原本 reservation-service 上的資料如下,可以很明顯的得出來多了一層 reservation-service 代理路徑

 

{  "_embedded": {    "reservations": [      {        "reservationName": "Dr. rod",        "_links": {          "self": {            "href": "http://localhost:8050/reservation-service/reservations/1"          },          "reservation": {            "href": "http://localhost:8050/reservation-service/reservations/1"          }        }      },      {        "reservationName": "Dr. Syer",        "_links": {          "self": {            "href": "http://localhost:8050/reservation-service/reservations/2"          },          "reservation": {            "href": "http://localhost:8050/reservation-service/reservations/2"          }        }      },      {        "reservationName": "Juergen",        "_links": {          "self": {            "href": "http://localhost:8050/reservation-service/reservations/3"          },          "reservation": {            "href": "http://localhost:8050/reservation-service/reservations/3"          }        }      },      {        "reservationName": "ALL THE COMMUNITY",        "_links": {          "self": {            "href": "http://localhost:8050/reservation-service/reservations/4"          },          "reservation": {            "href": "http://localhost:8050/reservation-service/reservations/4"          }        }      },      {        "reservationName": "Josh",        "_links": {          "self": {            "href": "http://localhost:8050/reservation-service/reservations/5"          },          "reservation": {            "href": "http://localhost:8050/reservation-service/reservations/5"          }        }      }    ]  },  "_links": {    "self": {      "href": "http://localhost:8050/reservation-service/reservations"    },    "profile": {      "href": "http://localhost:8050/reservation-service/profile/reservations"    },    "search": {      "href": "http://localhost:8050/reservation-service/reservations/search"    }  },  "page": {    "size": 20,    "totalElements": 5,    "totalPages": 1,    "number": 0  }}

 

增加 LoadBalance 機制

 

修改 reservation-client 專案

先把依賴 hateoas 加回去

build.gradle

dependencies {    compile('org.springframework.boot:spring-boot-starter-actuator')    compile('org.springframework.cloud:spring-cloud-starter-config')    compile('org.springframework.cloud:spring-cloud-starter-eureka')    compile('org.springframework.cloud:spring-cloud-starter-hystrix')    compile('org.springframework.boot:spring-boot-starter-hateoas')    /*     compile('org.springframework.cloud:spring-cloud-starter-zipkin')     */    compile('org.springframework.cloud:spring-cloud-starter-stream-redis')    compile('org.springframework.cloud:spring-cloud-starter-zuul')    testCompile('org.springframework.boot:spring-boot-starter-test') }

然後新增一個控制器

 

@RestController@RequestMapping("/reservations")class ReservationApiGatewayRestController{    @Autowired    @LoadBalanced    private RestTemplate restTemplate;    @RequestMapping("names")    public Collection
 getReservationNames(){        ParameterizedTypeReference
> ptr =                 new ParameterizedTypeReference
>(){};                ResponseEntity
> responseEntity =                         this.restTemplate.exchange("http://reservation-service/reservations",                                 HttpMethod.GET, null, ptr);                                Collection
 nameList = responseEntity                        .getBody()                        .getContent()                        .stream()                        .map(Reservation::getReservationName)                        .collect(Collectors.toList());                return nameList;    }}

這邊其實猜得出來它的使用方式,然後下面的部分是Java8的語法喔。

再啟動後從  嘗試抓取資料,結果是可以取得預期的姓名清單

 

[ "Dr. rod", "Dr. Syer", "Juergen", "ALL THE COMMUNITY", "Josh" ]

 

透過 redis 來轉發請求

 

修改 reservation-client 專案

先修改 build.gradle 把 spring-cloud-starter-stream-redis 加進來

build.gradle

dependencies {    compile('org.springframework.cloud:spring-cloud-starter-config')    compile('org.springframework.cloud:spring-cloud-starter-eureka')    /*     compile('org.springframework.cloud:spring-cloud-starter-zipkin')     */    compile('org.springframework.cloud:spring-cloud-starter-stream-redis')    compile('org.springframework.boot:spring-boot-starter-actuator')    compile('org.springframework.boot:spring-boot-starter-data-jpa')    compile('org.springframework.boot:spring-boot-starter-data-rest')    compile('org.springframework.boot:spring-boot-starter-web')    runtime('com.h2database:h2')    testCompile('org.springframework.boot:spring-boot-starter-test') }

主程式上方新增 @EnableBinding (Source.class)

 

@EnableZuulProxy@EnableBinding (Source.class)@EnableDiscoveryClient@SpringBootApplicationpublic class ReservationClientApplication {    public static void main(String[] args) {        SpringApplication.run(ReservationClientApplication.class, args);    }}

在 Controller 中加上

 

@Autowired @Output(Source.OUTPUT) private MessageChannel messageChannel; @RequestMapping( method = RequestMethod.POST) public void write(@RequestBody Reservation r){     this.messageChannel.send(MessageBuilder.withPayload(r.getReservationName()).build()); }

 

修改 reservation-service

修改 build.gradle 把 spring-cloud-starter-stream-redis 加進來

build.gradle

dependencies {    compile('org.springframework.cloud:spring-cloud-starter-config')    compile('org.springframework.cloud:spring-cloud-starter-eureka')    /*     compile('org.springframework.cloud:spring-cloud-starter-zipkin')     */    compile('org.springframework.cloud:spring-cloud-starter-stream-redis')    compile('org.springframework.boot:spring-boot-starter-actuator')    compile('org.springframework.boot:spring-boot-starter-data-jpa')    compile('org.springframework.boot:spring-boot-starter-data-rest')    compile('org.springframework.boot:spring-boot-starter-web')    runtime('com.h2database:h2')    testCompile('org.springframework.boot:spring-boot-starter-test') }

然後在主程式加上 @EnableBinding(Sink.class)

 

@EnableBinding(Sink.class)@EnableDiscoveryClient@SpringBootApplicationpublic class ReservationServiceApplication {        //起動的時候預先塞測試資料    @Bean    CommandLineRunner runner(ReservationRepository rr){        return args -> {            Arrays.asList("Dr. rod,Dr. Syer,Juergen,ALL THE COMMUNITY,Josh".split(","))            .forEach( x -> rr.save(new Reservation(x)));;            rr.findAll().forEach( System.out::println);        };    }    public static void main(String[] args) {        SpringApplication.run(ReservationServiceApplication.class, args);    }}

再增加一個訊息接入點

 

    @MessageEndpointclass MessageReservationReceiver{    @Autowired    private ReservationRepository reservationRepository;        @ServiceActivator(inputChannel = Sink.INPUT)    public void acceptReservation(String rn){        this.reservationRepository.save(new Reservation(rn));    }}

然後再回到 Config-Server 的設定檔資料夾加上 spring.redis.host ,因為是透過 redis 來收送所以當然是要給位置才能用

application.yml

spring.redis.host: "localhost"

這邊測試而已,還要裝個 redis 就太大費周章了,直接用 Docker 來跑吧

Vagrantfile.proxy

VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|    config.vm.box = "ubuntu/trusty64"  config.vm.provision "docker"  config.vm.provision "shell", inline:    "ps aux | grep 'sshd:' | awk '{print $2}' | xargs kill"    config.vm.provider :virtualbox do |vb|    vb.name = "redis"    vb.gui = $vm_gui    vb.memory = $vm_memory    vb.cpus = $vm_cpus  end   config.vm.network :forwarded_port, guest: 6379, host: 6379    config.ssh.username = "vagrant"   config.ssh.password = "vagrant" end

Vagrantfile

# -*- mode: ruby -*-# vi: set ft=ruby :# Specify Vagrant version and Vagrant API version#Vagrant.require_version ">= 1.6.0"VAGRANTFILE_API_VERSION = "2"ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'$vm_gui = false$vm_memory = 2048$vm_cpus = 2Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|  config.vm.synced_folder ".", "/vagrant", disabled: true    config.vm.define "redis" do |v|    v.vm.provider "docker" do |d|      d.name = "redis"      d.image = "redis"      d.ports = ["6379:6379"]      d.vagrant_vagrantfile = "./Vagrantfile.proxy"    end  endend

兩個檔案放同一個資料夾後接著執行

 

vagrant up redis --provider=docker

好啦,程式跟環境都好了,接著把程式都叫起來,接著透過 POST 新增資料從 reservation-client -> redis -> reservation-service 寫入資料庫

 

curl -X POST -H "Accept: application/json" -H "Content-Type: application/json" -d '{
"reservationName":"Red Johnson"}' 'http://localhost:8050/reservations'

再重新查看  就可以看到多了 Red Johnson 的名字

為什麼要這樣做?我覺得是為了突發量,有時就算你的應用可以水平拓展,但是來不及拓也是GG...

 

增加服務中斷時的回覆訊息

有時候還是會有意外,但是出現問題如果跑出奇怪的錯有可能讓前端措手不及,或是稍微偽裝一下,可以讓客戶端無感異常

 

修改 reservation-client 專案

起 動程式增加 @EnableCircuitBreaker ,然後在需要此功能的方法上增加 @HystrixCommand(fallbackMethod = "getReservationNamesFallback") 當失敗時他就會使用你指定的方法 getReservationNamesFallback 來回覆前端

 

@EnableZuulProxy@EnableBinding(Source.class)@EnableCircuitBreaker@EnableDiscoveryClient@SpringBootApplicationpublic class ReservationClientApplication {    public static void main(String[] args) {        SpringApplication.run(ReservationClientApplication.class, args);    }}@RestController@RequestMapping("/reservations")class ReservationApiGatewayRestController{    @Autowired    @LoadBalanced    private RestTemplate restTemplate;        @Autowired    @Output(Source.OUTPUT)    private MessageChannel messageChannel;        @RequestMapping( method = RequestMethod.POST)    public void write(@RequestBody Reservation r){        this.messageChannel.send(MessageBuilder.withPayload(r.getReservationName()).build());    }        public Collection
 getReservationNamesFallback(){        return Collections.emptyList();    }        @HystrixCommand(fallbackMethod = "getReservationNamesFallback")    @RequestMapping("names")    public Collection
 getReservationNames(){        ParameterizedTypeReference
> ptr =                 new ParameterizedTypeReference
>(){};                ResponseEntity
> responseEntity =                         this.restTemplate.exchange("http://reservation-service/reservations",                                 HttpMethod.GET, null, ptr);                                Collection
 nameList = responseEntity                        .getBody()                        .getContent()                        .stream()                        .map(Reservation::getReservationName)                        .collect(Collectors.toList());                return nameList;    }}

reservation-client 起動後,把 reservation-service 關掉,這麼一來通常應用程式就會發生異常回傳 500 之類的,但是你可以試著呼叫 ,你可以發現你只是得到一個空集合,不影響你的前端程式

 

[]

 

即時監控

 

建立一個 hystrix-dashboard 專案

使用到的組件如下

Config Client Eureka Discovery Hystrix Dashboard

一樣移除 application.properties,因為主要設定我們現在都依靠 Config-Server 的提供,再新增bootstrap.properties

bootstrap.properties

spring.application.name=hystrix-dashboard  spring.cloud.config.uri=http://localhost:8888

然後主程式啟用 @EnableHystrixDashboard

HystrixDashboardApplication.java

package com.example;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;@EnableHystrixDashboard@SpringBootApplicationpublic class HystrixDashboardApplication {    public static void main(String[] args) {        SpringApplication.run(HystrixDashboardApplication.class, args);    }}

然後在 Config-Server 設定檔資料夾中新增

hystrix-dashboard.properties

server.port=${PORT:8010}

然後起動 hystrix-dashboard ,接著訪問

你可以看到我們 reservation-client 一直在吐資料

然後把上面網址貼在下面網頁中間欄位

然後按下 Monitor Stream 按鈕,你就可以看到一個監控的介面

當後端執行成功或是失敗你都可以即時的發現到
成功狀態
失敗狀態

 

Log 收集

有時知道錯在哪一個環節,但是沒有記錄還是很難找問題, spring-cloud-starter-zipkin 就可以記錄每一個 service 之間的資料傳遞,官網 

這東西 twitter 做的,裝起來應該也是很煩人,改天再試看看,再研究一下怎麼用 Docker 頂一下

先說程式部分

把 reservation-service 、 reservation-client 原先註解掉的依賴 spring-cloud-starter-zipkin 加回去
然後這兩個專案內註冊 @Bean ,程式端就完成了

 

@Bean AlwaysSampler alwaysSampler(){ return new AlwaysSampler(); }http://my.oschina.net/xliangbo/blog/608910

转载于:https://my.oschina.net/xiaominmin/blog/1599119

你可能感兴趣的文章
云趋势下的Windows平台:生存并快乐着
查看>>
不要再在JavaScript中写 CSS了
查看>>
Gartner:云安全进入高速发展期
查看>>
云存储能否成为数据安全灵药?几个角度全方位剖析
查看>>
未来几年SDN将进一步提升云服务利润率
查看>>
手把手教你用 Python 和 Scikit-Learn 实现垃圾邮件过滤
查看>>
Hinton亲自讲解迄今未发表工作:胶囊理论的核心概念到底是什么?
查看>>
公开课总结发布《云数据库实现原理和海量运维方法》
查看>>
预告:如何完成从学术科研到产业创新的华丽转身?| 硬创公开课
查看>>
《C++语言入门经典》一2.3 数据的输入与输出
查看>>
阿里云ECS通过docker配置MySQL--MGR
查看>>
光伏业需要一次国内“双反”
查看>>
小微企业都在用的一体化管理解决方案
查看>>
Sql Server 2008 为开发带来的新特性
查看>>
Realm为Node.js发布对象数据库
查看>>
农民别再愁!人工智能帮你诊断作物疾病
查看>>
物联网行业将掀起新一轮并购潮 步入整合期
查看>>
夏日炎炎 构筑安防线 这些知识你Get到了吗?
查看>>
《C语言程序设计:问题与求解方法》——2.4节C语言源程序的次要组成成分:编译预处理命令、注释和声明...
查看>>
业绩不佳引裁员 雅虎2016有点烦
查看>>