1、基礎知識
微服務:微服務是系統架構上的一種設計風格,它的主旨是將一個本來獨立的系統拆分紅多個小型服務,
這些小型服務都在各自獨立的進程中運行,服務之間經過基於HTTP的RESTful API進行通訊協做。
與單系統區別:
單體系統在初期能夠很是方便地進行開發與使用,可是隨着系統的發展,維護成本會變得愈來愈大,且難以控制。
爲解決龐大臃腫以後產生的難以維護的問題,微服務架構誕生了並被你們所關注。咱們將系統功能模塊拆分紅多個不一樣的服務,
這些服務都可以獨立部署和擴展。
如何實施微服務:
(1)運維的新挑戰
進程多,維護難度增大
(2)接口的一致性
拆分了服務,業務邏輯依賴並不會消除,從單體應用中的代碼依賴變爲了服務間的通訊依賴。
一旦原有接口進行了一些修改,那麼交互雙方也須要協調這樣的改變來進行發佈,以保證原有接口的正確調用。
(3)分佈式的複雜性:
單體->多個獨立微服務,服務之間只能經過通訊來進行協做,會帶來一系列問題,如:網絡延遲、分佈式事務、異步消息等。
微服務九大特性:
(1)服務組件化
組件,是一個能夠獨立更換和升級的單元,獨立且能夠更換升級而不影響其餘單元。
在微服務架構中,須要咱們對服務進行組件化分解。
(2)按業務組織團隊
當決定如何劃分微服務時,一般也意味着咱們要開始對團隊進行從新規劃與組織。按以往的方式,咱們每每會從技術的層面將團隊劃分爲多個,
好比DBA、運維、後端、前端、設計師等。
在實施微服務架構時,須要採用不一樣的團隊分割方法。
(3)作「產品」的態度
在實施微服務架構的團隊中,每一個小團隊都應該以作產品的方式,對其產品的整個生命週期負責。而不是以項目的模式,以完成開發與交付
並將成果交接給維護都爲最終目標。
(4)智能端點與啞管道
在單體應用中,組件間直接經過函數調用的方式進行交互協做。而在微服務架構中,因爲服務還在一個進程中,組件間的通訊模式發生了改變,若僅僅將本來
在進程內的方法調用改爲RPC方式的調用,會致使微服務之間產生煩瑣的通訊,使得系統表現更爲槽糕,因此咱們須要更粗粒度的通訊協議。
第一種:RESTful API或輕量級的消息發送協議,第二種:經過在輕量級消息總線上傳遞消息,相似RabbitMQ等一些提供可靠異步交換的中間件。
(5)去中心化治理
在實施微服務架構時,經過採用輕量級的契約定義接口,使得咱們對於服務自己的具體技術平臺再也不那麼敏感,這樣整個微服務架構系統中的各個組件就能針對其
不一樣的業務特色選擇不一樣的平臺,終於不會出現殺雞用牛刀等的尷尬。
(6)去中心化管理數據
咱們在實施微服務架構時,都但願讓每個服務來管理其自有的數據庫,這就是數據管理的去中心化。
(7)基礎設施自動化
自動化測試:每次部署前的強心劑,盡能夠地得到對正在運行的軟件的信心。
自動化部署:解放煩瑣枯燥的重複操做以及對多環境的配置管理。
(8)容錯設計
單體服務中,存在一掛全持,而在微服務,服務獨立運行,不影響其它服務。
在微服務架構中,快速檢測出故障源並儘量地自動恢復是必須被設計和考慮的。
每一個服務中實現監控和日誌記錄的組件,好比服務狀態,斷路器狀態,吞吐量、網絡延遲等關鍵數據的儀表盤等。
(9)演進式設計
經過上面的幾點特徵,咱們已經可以體會到,要實施一個完善的微服務架構,須要考慮的設計與成本並不小,對於沒有足夠經驗的團隊來講,甚至要比單體應用
付出更多的代價。
項目初期,以單體系統的方式來設計和實施,系統量初期並不會很大,構架和維護成本都不高。隨着系統的發展或者業務的須要,架構師會將一些常常
變更或是有必定時間效應的內容進行微服務處理,並逐漸將原來在單體系統中多變的模塊逐步拆分出來,而穩定不太變化的模塊就造成一個核心微服務存在於整個
架構之中。
爲何選擇Spring Cloud:
服務治理:
Dubbo、DubboX、Netflix的Eureka、Apache的Consul等.
分佈式配置管理:
百度的Disconf、Netflix的Archaius、360的QConf、Spring Cloud 的Config、淘寶的Diamond等
批量任務:
噹噹網的Elastic-Job、LinkedIn的Azkaban、Spring Cloud的Task等
服務跟蹤:
京東的Hydra、Spring Cloud的Sleuth、Twitter的Zipkin等
......
Spring Cloud簡介
Spring Cloud是一個基於Spring Boot實現的微服務架構開發工具。它爲微服務架構中涉及的配置管理、服務治理、斷路器,智能路由、微代理、控制總線、全局鎖、
決策競選、分佈式會話和集羣狀態管理等操做提供了一種簡單的開發方式。
Spring Cloud包含了多個子項目:
(1)Spring Cloud Config:
配置管理工具,支持使用Git存儲配置內容,可使用它實現應用配置外部化存儲,並支持客戶端配置信息刷新、加密/解密配置內容等。
(2)Spring Cloud Netflix:
核心組件,對多個Netflix OSS開源套件進行整合。
Eureka:服務治理組件,包含服務註冊中心,服務註冊與發現機制的實現
Hystrix:容錯管理組件,實現斷路器模式,幫助服務依賴中出現的延遲和爲故障提供強大的容錯能力。
Ribbon:客戶端負載均衡的服務調用組件。
Feign:基於Ribbon和Hystrix的聲明式服務調用組件。
Zuul:網關組件,提供智能路由、訪問過濾等功能。
Archaius:外部化配置組件。
(3)Spring Cloud Bus:
事件、消息總線,用於傳播集羣中的狀態變化或事件,以觸發後續的處理,好比用來動態刷新配置等。
(4)Spring Cloud Cluster:
針對ZooKeeper、Redis、Hazelcast、Consul的選舉算法和通用狀態模式的實現
(5)Spring Cloud Cloudfoundry:
與Privotal Cloudfoundry的整合支持。
(6)Spring Cloud Consul:
服務發現與配置管理工具。
(7)Spring Cloud Stream:
經過Redis、Rabbit或者kafka實現的消費微服務,能夠經過簡單的聲明式模型來發送和接收消息。
(8)Spring Cloud AWS:
用於簡化整合Amazon Web Service的組件。
(9)Spring Cloud Security:
安全工具包,提供在Zuul代理中對Oauth2客戶端請求的中繼器。
(10)Spring Cloud Sleuth:
基於Zookeeper的服務發現與配置管理組件。
(11)Spring Cloud Starters:
Spring Cloud的基礎組件,它是基於Spring Boot風格項目的基礎依賴模塊。
(12)Spring Cloud CLI:
用於在Groovy中快速建立Spring Cloud應用的Spring Boot CLI插件。
......前端
2、微服務構建:Spring Bootjava
目標: 如何構建Spring Boot項目 如何實現RESTful API接口 如何實現多環境的Spring Boot應用配置 深刻理解Spring Boot配置的啓動機制 Spring Boot應用的監控與管理 Spring屬性加載順序: (1)在命令中傳入的參數 (2)SPRING_APPLICATION_JSON中的屬性。SPRING_APPLICATION_JSON是以JSON格式配置在系統環境變量中的內容。 (3)java:comp/env中的JNDI屬性 (4)Java的系統屬性,能夠經過System.getProperties()得到的內容 (5)操做系統的環境變量 (6)經過random.*配置的隨機屬性 (7)位於當前應用jar包以外,針對不一樣${profile}環境的配置文件內容,例如application-{profile}.properties或是YAML定義的配置文件。 (8)位於當前應用jar包以內,針對不一樣${profile}環境的配置文件內容,例如application-{profile}.properties或是YAML定義的配置文件。 (9)位於當前應用jar包以外的application.properties和YAML配置內容。 (10)位於當前應用jar包以內的application.properties和YAML配置內容。 (11)在@Configurtion註解修改的類中,經過@PropertySource註解定義的屬性。 (12)應用默認屬性,使用SpringApplication.setDefaultProperties定義的內容。 原生端點: (1)應用配置類 獲取應用程序中加載的應用配置、環境配置、自動化配置報告等與Spring Boot應用密切相關的配置類信息。 /autoconfig:// 獲取應用自動化配置報告 { "positiveMatchers": { // 條件匹配成功的 "EndpointWebMvcAutoConfiguration": [ { "condition": "OnClassCondition", "message": "@ConditionOnClass classes found: javax.servlet.Servlet,org.springframework.web.servlet.DispatcherServlet" }, { "condition": "OnWebApplicationCondition", "message": "found web application StandardServletEnvironment" } ], ... }, "negativeMatchers": { // 條件匹配不成功的 "HealthIndicatorAutoConfiguration.DataSourcesHealthIndicatorConfiguration":[ { "condition": "OnClassCondition", "message": "required @ConditionalOnClass classes not found: org.springframework.jdbc.core.JdbcTemplate" } ], ... } } /beans: // 該端點用來獲取應用上下文建立的全部Bean [ { "context": "hello:dev:8881", "parent": null, "beans": [ { "bean": "org.xx.xx.DispatcherServletAutoConfiguration$DispatcherServletConfiguration", "scope": "singleton", "type": "org.springframework.boot.autoconfig.web.xx$$CGLIB$$xx", "resource": "null", "dependences": [ "serverProperties", "spring.mvc.CONFIGURATION_PROPERTIES", "multipartConfigElement" ] }, { "bean": "DispatcherServlet", "scope": "singleton", "type": "org.springframework.boot.autoconfig.web.DispatcherServlet", "resource": "class path resource [org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration$DispatcherServletConfiguration.class]", "dependences": [] } ] } ] /configprops: // 該端點用來獲取應用中的屬性信息報告 { "configuractionPropertiesReportEndpoint": { "prefix": "endpoints.configprops", "properties": { "id": "configprops", "sensitive": true, "enabled": true } }, ... } /env: // 該端點與/configprops不一樣,它用來獲取應用全部可用的環境屬性報告。 { "profiles": [ "dev" ], "server.ports": { "localhost.server.port": 8881 }, "servletContextInitParams": { }, "systemProperties": { "idea.version": "2016.1.3", ... }, "systemEnviroment": { "configsetroot": "C:\\WINDOWS\\ConfigSetRoot", ... }, "applicationConfig": [classpath:/application-dev.properties]{ "server.port": "8881" }, "applicationConfig": [classpath:/application.properties]{ "server.port": "8885", "spring.profiles.active": "dev", "info.app.name": "spring-boot-hello", "spring.application.name": "hello" } } /mappings: // SpringMVC映射器關係 { "/webjars/**": { "bean": "resourceHandlerMapping" }, "/**": { "bean": "resourceHandlerMapping" }, "/**/favicon.ico": { "bean": "faviconHandlerMapping" }, "{[hello]}": { "bean": "requestMappingHandlerMapping", "method": "public java.lang.String com.didispace.web.HelloController.index()" }, "{[/mappings || /mappings.json],methods=[GET],produres=[application/json]}": { "bean": "endpointHandlerMapper", "method": "public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointWebMvcAdapter.invoke()" }, ... } /info: // 返回自定義信息 application.produres |-- info.app.name=spring-boot-hello |-- info.app.version=v1.0.0 { "app": { "name": "spring-boot-hello", "version": "v1.0.0" } } (2)度量指標類 獲取應用程序運行過程當中用於監控的度量指標,好比內存信息、線程池信息、HTTP請求統計等。 /metrics: // 內存信息、線程信息、垃圾回收信息 { "mem": 541305, "mem.free": 317864, "processors": 8, "instance.uptime": 33376471, "uptime": 333853352, "gauge.*": ... // HTTP性能指標之一,延遲量 "counter.*": ... // HTTP性能指標之一,增長量、減小量 ... } /health: // 各種健康指標信息 // 檢查器 DiskSpaceHealthIndicator // 低磁盤空間檢測 DataSourceHealthIndicator // 檢測DataSource鏈接是否可用 MongoHealthIndicator // 檢測Mongo數據庫是否可用 RabbitHealthIndicator // 檢測Rabbit服務器是否可用 RedisHealthIndicator // 檢測Redis服務器是否可用 SolrHealthIndicator // 檢測Solr服務器是否可用 自動實現檢測器: |-- @Component public class RocketMQHealthIndicator implements HealthIndicator { @Override public Health health() { int errorCode = check(); if (errorCode != 0) { return Health.down().withDetail("Error Code", errorCode).build(); } return Health.up().build(); } private int check() { // 對監控對象檢測操做 } } -> { "rocketMQ": { "status": "UP" } } /dump: // 線程信息 java.lang.management.ThreadMXBean的dumpAllThreads方法返回所含同步的活動線程詳情。 /trace: // HTTP跟蹤信息 org.springframework.boot.actuate.trace.InMemoryTranceRepository [ { "timestamp": 1482570000000, "info": { "method": "GET", "path": "/metricts/mem/", "headers": { "request": { "host": "localhost:8081", "connection": "keep-alive", "cache-control": "no-cache", "user-agent": "Mozilla/5.0(Windows NT 10.0; WOW64), ... }, "response": { "X-Application-Context": "hello:dev:8881", "Content-Type": "application/json;charset=UTF-8", "Transfer-Encoding": "chunked", "Date": "Sat, 24 Dec 2016 09:00:22 GMT", "status": "200" } } } } ] (3)操做控制類 提供了對應用的關閉等操做類功能。 /shutdown endpoint.shutdown.enabled=true
3、服務治理:Spring Cloud Eurakeweb
Spring Cloud Netflix |-- Spring Cloud Eurake 目標: 構建服務註冊中心 服務註冊與服務發現 Eurake的基礎架構 Eurake的服務治理機制 Eurake的配置 1、構建服務註冊中心 (1)服務註冊: 在服務治理框架中,一般會構建一個註冊中心,每一個服務單元向註冊中心登記本身提供的服務,將主機端口號、版本號、通訊協議等信息。 服務器A:192.168.0.100:8000、192.168.0.101:8000 服務器B:192.168.0.100:9000、192.168.0.101:9000、192.168.0.102:9000 (2)服務發現: 因爲服務治理框架下運做,服務間的調用再也不經過指定具體的實例地址來實現,而是經過向服務名發起請求調用實現。 調用方:向服務註冊中心諮詢服務,並獲取全部服務的實例清單,以實現對具體服務實例的訪問。 例: 服務C->服務A, |-- 服務C -> 向服務註冊中心諮詢服務 |-- 將服務器A的位置清單返回-> 服務C |-- 192.168.0.100:8000、192.168.0.101:8000 |-- 經過某種策略取出一個位置來進行服務調用 -> 調用服務192.168.0.101:8000 Eurake服務端:服務註冊中心, Eurake客戶端:處理服務註冊與發現 (3)搭建服務註冊中心 |-- demo: |-- 服務端: |-- @EnableEurekaServer @SpringBootApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } } |-- application.properties |-- server.port=1111 |-- eureka.instance.hostname=localhost |-- eureka.client.register-with-eureka=false // 因爲該應用爲註冊中心,因此設置爲false |-- eureka.client.fetch-registry=false // 因爲註冊中心的職責是維護服務實例,它並不須要去檢索服務,因此也設置爲false |-- eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/ |-- 客戶端: |-- pom.xml spring-boot-starter-web spring-boot-starter-test spring-cloud-starter-eureka spring-cloud-dependences |-- @RestController { Logger logger; @Autowire private DiscoveryClient client; @RequestMapping(value = "/hello", method = RequestMethod.GET) { instance = client.getLocalServiceInstance(); logger.info("/hello,host: " + instance.getHost() + ", service_id:" + instance.getServiceId()); return "Hello World"; } } |-- @EnableDiscoveryClient @SpringBootApplication public class HelloApplication { public static void main(String[] args) { SpringApplication.run(HelloApplication.class, args); } } |-- application.properties |-- spring.application.name=hello-service |-- eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ -> http://localhost:8080/hello -> com.didispace.web.HelloController : /hello, host: PC-201602152056 service-id: hello-service (4)高可用註冊中心 |-- application-peer1.properties |-- spring.application.name=eureka-server |-- service.port=1111 |-- eureka.instance.hostname=peer1 |-- eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/ |-- application-peer1.properties |-- spring.application.name=eureka-server |-- service.port=1112 |-- eureka.instance.hostname=peer2 |-- eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/ |-- /etc/hosts 127.0.0.1 peer1 127.0.0.1 peer2 |-- 啓動命令: java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer1 java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer2 |-- 訪問地址: http://localhost:1111/ |-- 節點1111 |-- 節點1112 http://localhost:1112/ |-- 節點1111 |-- 節點1112 |-- 集羣 |-- application.properties spring.application.name=hello-service eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:2222/eureka/ |-- 啓動服務後 http://localhost:1111/、http://localhost:1112/ -> hello-service被註冊到了peer1和peer2 若是想以ip地址的形式訪問,須要在配置文件中配置參數 |-- eureka.instance.prefer-ip-address=true,默認爲false 2、服務註冊與服務發現 (1)目標: 構建消費端,用來發現服務及消費服務。 (2)關鍵名詞解析 服務發現-> Eureka的客戶端 服務消費-> Ribbon Ribbon:是一個基於HTTP和TCP的客戶端負載均衡器,經過ribbonServerList服務端列表去輪詢訪問以達到均衡。 (3)構建簡單事例 |-- 啓動服務: $ java -jar hello-service-1.0.0.jar --server.port=8081 $ java -jar hello-service-1.0.0.jar --server.port=8082 |-- Eureka面板 Application AMIs Availablity Zones Status HELLO-SERVICE n/a(2) (2) UP(2)-PC-201602152056:hello-service:80801,PC-201602152057:8082 |-- 建立服務消費者 |-- pom.xml groupId: spring-boot-starter-web spring-cloud-starter-eureka spring-cloud-starter-ribbon spring-cloud-denpendencies |-- @EnableDiscoveryClient @SpringBootApplication ConsumterApplication { // 主類 @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } main { SpringApplication.run(ConsumterApplication.class, args); } } |-- @RestController ConsumterController { @Autowire RestTemplate restTemplate; @RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET) public String helloConsumer() { return restTemplate.getForEntity("http://HELL-SERVICE/hello", String.class).getBody(); } } |-- application.properties spring.application.name=ribbon-consumer server.port=9000 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ |-- 啓動ribbon-consumer Eureka面板 Application AMIs Availablity Zones Status HELLO-SERVICE n/a(2) (2) UP(2)-PC-201602152056:hello-service:80801,PC-201602152057:8082 RIBBON-CONSUMER n/a(1) (1) UP(1)-PC-201602152056:ribbon-consumer:9000 3、Eureka詳解 (1)基礎架構 服務註冊中心:eurake-server 服務提供者:提供服務的應用 服務消費者:Ribbon來實現服務消費,或其它方式 (2)服務治理機制 服務提供者: A、服務註冊: 服務提供者->註冊到Eure Server上(同時帶上了自身服務的一些元數據信息) -> Eure Server存儲一個雙層結構Map中, |-- 第一層key:服務名 |-- 第二層key:具體服務的實例名 在服務註冊時,須要確認一下eureka.client.register-with-eureka=true參數是否正確,若設置爲false將不會啓動註冊操做 B、服務同步: 服務註冊A<--->服務註冊B, 服務提供者->發送註冊請求->服務註冊中心 服務註冊中心-> 轉發給集羣中相連的其它註冊中心,從而實現註冊中心的服務同步。 C、服務續約: 經過心跳包來持續告訴Eureka Server還活着。兩個重要屬性: eureka.instance.lease-renewal-interval-in-seconds=30 // 調用間隔時間 eureka.instance.lease-renewal-expiration-in-seconds=90 // 服務失效時間 服務消費者: A、獲取服務: 啓動服務消費者—>發送Rest請求到->註冊中心->服務清單(只讀,每隔30秒更新一次) 緩存屬性: eureka.client.registry-fetch-interval-in-seconds=30(默認爲30,單位秒) B、服務調用: 經過上一步獲取了服務清單->Ribbon輪詢方式選取服務調用(實現客戶端負載均衡) Region:一個Region包含多個Zone Zone:每一個服務客戶端被註冊到一個Zone中,因此每一個客戶端對應一個Region和Zone(優先訪問同處一個Zone的服務提供方) C、服務下線: 服務實例正常關閉操做-> 觸發一個服務下線的REST請求給Eureka Server -> Eureka Server將服務狀態設置了DOWN 服務註冊中心: A、失效剔除: Eurake Server在啓動時會建立一個定時任務,默認每隔一段時間(默認60秒)將當前清單中超時(默認爲90秒)沒有續約的服務剔除出去。 B、自我保護: 服務註冊到Eureka Server以後,會將當前的實例註冊信息保存起來,讓這些實例不會過時,保護期間若實例出了問題,客戶端很容易拿到 實際已經不存在的服務實例。 4、源碼分析 DiscoveryClient 做用: 向Eureka Server註冊服務實例 向Eureka Server服務租約 當服務關閉期間,向Eureka Server取消租約 查詢Eureka Server中的服務實例列表 Eureka Client還須要配置一個Eureka Server的URL列表. @link public static Map<String, List<String>> getServiceUrlsMapFromConfig(EurekaClientConfig clientConfig, String instanceZone, boolean preferSameZone) { orderedUrls = new LinkedHashMap<>(); String region = getRegion(clientConfig); String[] availZones = clientConfig.getAvailablityZones(clientCofig.getRegion()); if (availZones == null || availZones.length == 0) { availZones = new String[1]; availZones[0] = DEFAULT_ZONE; } ... myZoneOffset = getZoneOffset(instanceZone, preferSameZone, availZones); String zone = availZones[myZoneOffset]; List<String> serviceUrls = clientConfig.getEurekaServerServiceUrls(zone); if (serviceUrls != null) { orderedUrls.put(zone, serviceUrls); } ... return orderedUrls; } |-- public static String getRegion(EurekaClientConfig clientConfig) { String region = clientConfig.getRegion(); if (region == null) { region = DEFAULT_ZONE; } region = region.trim().toLowerCase(); return region; } |-- public String[] getAvailablityZones(String region) { String value = this.availablityZones.get(region); if (value == null) { value = DEFAULT_ZONE; } return value.split(","); } |-- 服務註冊 private void initScheduledTasks() { ... if (clientConfig.shouldRegisterWithEureka()) { // 註冊 instatnceInfoReplicator = new InstanceInfoReplicator( this, instanceInfo, clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2 ); ... instatnceInfoReplicator.start(clientCofig.getInstanceInfoReplicationIntervalSeconds()); // 開啓一個線程 } else { logger.info("Not registering with Eureka server per configuration"); } } |-- run () { try { discoverClient.refreshInstanceInfo(); Long dirtyTimestamp = instanceInfo.isDirtyWithTime(); if (dirtyTimestamp != null) { discoverClient.register(); // 觸發調用註冊的地方 instanceInfo.unsetIsDirty(dirtyTimestamp); } } catch() { logger.warn(...); } finally { Future next = scheduler.schedule(this, replicationIntervalSeconds, TimeUnit.SECONDS); scheduledPeriodicRef.set(next); } } |-- register() { EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch() { logger.warn(...); throw e; } if (logger.isInfoEnabled()) { logger.info("{} - registration status: {}", PREFIX + appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == 204; } |-- 服務獲取與服務續約 private void initScheduledTasks() { ... if (clientConfig.shouldFetchRegistry()) { // 服務獲取 ... } if (clientConfig.shouldRegisterWithEureka()) { // 服務續約(與服務註冊共用一個if) ... } } |-- 服務註冊中心處理 5、配置詳解: (1)服務註冊類配置(eureka.client爲前綴) 指定註冊中心: eureka.client.serviceUrl=http://localhost:8761/eureka/ 默認:key=defaultZone,value=http://localhost:8761/eureka/ 單機修改端口地址: eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ 高可用的註冊服務中心: eureka.client.serviceUrl.defaultZone=http://pee1:1111/eureka/,http://peer2:1111/eureka/ 增長安全驗證: http://<username>:<password>@localhost:1111/eureka username:安全檢驗信息的用戶名 password:該用戶的密碼 其它配置: eureka.client.*=xx (2)服務實例類配置(eureka.instance爲前綴) 元數據: 包換服務名稱、實例名稱、實例IP、實例端口等 eureka.instance.<properties>=<value> eureka.instance.metadataMap.<key>=<value> eureka.instance.metadataMap.zone=shanghai 實例名配置:(主機名做爲配置值) 默認命名擴展: ${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}} eureka.instance.instanceId=${spring.application.name}:${random.int} 端點配置: |-- homePageUrl 應用主頁的url |-- statusPageUrl 狀態頁的url /info 端點 |-- healthCheckUrl 健康檢查的url /health 端點 // 加前綴 management.context-path=/hello eureka.instance.statusPageUrlPath=${management.context-path}/info eureka.instance.healthCheckUrlPath=${management.context-path}/health // 修改原始路徑 endpoints.info.path=/appInfo endpoints.health.path=/checkHealth eureka.instance.statusPageUrlPath=/${endpoints.info.path} eureka.instance.healthCheckUrlPath=/${endpoints.health.path} // 絕對路徑 eureka.instance.statusPageUrlPath=https://${eureka.instance.hostname}/info eureka.instance.healthCheckUrlPath=https://${eureka.instance.hostname}/health eureka.instance.homePageUrlPath=https://${eureka.instance.hostname}/ 健康檢測:(不是依靠/health來實現的,而是經過客戶端心跳的方式來保持服務實例的存活) 心跳包能有效檢查客戶端進程是否正常運做,卻沒法保證服務端可以正常提供服務。大多數服務依賴第三方,如:數據庫、緩存、消息代理等。 此時委託給/health端點,以實現更加全面的健康狀態維護。 步驟: |-- pom.xml 引入spring-boot-starter-actuator模塊依賴 |-- application.propertie eureka.client.healthcheck.enabled=true // 如作了特殊處理,須要從新配置 |-- 其它配置 eureka.instance.*=xx 6、跨平臺支持: (1)通訊協議: 默認使用Jersey和XStream配合JSON做爲Server和Client之間的通訊協議 Jersey: 是JAX-RS的參考實現,主要包括三部分: 核心服務器: JSR311中標準化和API標準,REST通訊 核心客戶端: Jersey客戶端API與REST通訊 集成: 提供spring、Guice、Apache Abdera庫 XStream: 是用來將對象序列化成XML(JSON)或反序列化爲對象的一個JAVA類庫。 JAX-RS: @Path @GET\@PUT\@POST\@DELETE @Produces @Consumes @PathParam\@QueryParam\@HeaderParam\@CookieParam\@MatrixParam\@FormParam
4、客戶端負載均衡算法
spring cloud ribbon調用: 服務提供者只須要啓動多個服務實例並註冊到一個註冊中心或是多個相關聯的服務註冊中心。 服務消費者直接經過調用被@LoadBalanced註解修飾過的RestTemplate來實現面向服務的接口調用。 (1)RestTemplate詳解 GET請求: A、getForEntity函數: RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://USER-SERVICE/user?name={1}", String.class, "didi"); String body = responseEntity.getBody(); getForEntity(String url, Class responseType, Object... urlVarliables) |-- url 請求地址 |-- responseType 響應體body的包裝類型 |-- urlVarliables 爲url的綁定參數 getForEntity(String url, Class responseType, Map urlVarliables) |-- urlVarliables 爲url的綁定參數 Map<String, String> params = new HashMap<>(); params.put("name", "didi"); responseEntity = restTemplate.getForEntity("http://USER-SERVICE/user?name={name}", String.class, params); getForEntity(URI url, Class responseType) |-- URI url 用URI代替了url和urlVarliables uriComponents = UriComponentsBuilder.fromUriString( "http://USER-SERVICE/user?name={name}") .build() .expand("dodo") .encode(); uril = uriComponents.toUri(); responseEntity = restTemplate.getForEntity(uri, String.class); B、getForObject函數:(getForEntity函數的進一步封裝) RestTemplate restTemplate = new RestTemplate(); result = restTemplate.getForObject(uri, String.class); result = restTemplate.getForObject(uri, User.class); getForObject(String url, Class responseType, Object... urlVarliables) |-- url 請求地址 |-- responseType 響應體body的包裝類型 |-- urlVarliables 爲url的綁定參數 getForObject(String url, Class responseType, Map urlVarliables) |-- urlVarliables 爲url的綁定參數 getForObject(URI url, Class responseType) |-- URI url 用URI代替了url和urlVarliables POST請求: A、postEntity函數: RestTemplate restTemplate = new RestTemplate(); ResponseEntity<String> responseEntity = restTemplate.postEntity("http://USER-SERVICE/user?name={1}", String.class, "didi"); String body = responseEntity.getBody(); |-- postEntity(String url, Object request, Class responseType, Object... urlVarliables) |-- postEntity(String url, Object request, Class responseType, Map urlVarliables) |-- postEntity(URI url, Object request, Class responseType) |-- request 普通對象|HttpEntity對象 B、postForObject(): |-- postForObject(String url, Object request, Class responseType, Object... urlVarliables) |-- postForObject(String url, Object request, Class responseType, Map urlVarliables) |-- postForObject(URI url, Object request, Class responseType) C、postForLocation(): |-- postForLocation(String url, Object request, Object... urlVarliables) |-- postForLocation(String url, Object request, Map urlVarliables) |-- postForLocation(URI url, Object request) User user = new User("didi", 40); URI responseURI = restTemplate.postForLocation("http://USER-SERVICE/user", user); PUT請求: RestTemplate restTemplate = new RestTemplate(); restTemplate.put("http://USER-SERVICE/user/{1}", user, id); |-- put(String url, Class responseType, Object... urlVarliables) |-- put(String url, Class responseType, Map urlVarliables) |-- put(URI url, Class responseType) DELETE請求: RestTemplate restTemplate = new RestTemplate(); Long id = 1000L; restTemplate.delete("http://USER-SERVICE/user/{1}", id); |-- delete(String url, Object... urlVarliables) |-- delete(String url, Map urlVarliables) |-- delete(URI url) 源碼分析: LoadBalancerClient |-- ServerInstance choose(String serviceId) // 根據服務名稱挑選一個對應服務實例 |-- <T> T execute(String serviceId, LoadBalancerRequest<T> request) throw IOException // 使用從負載均衡器中挑選出來的服務實例來執行請求內容 |-- URI reconstructURI(ServiceInstance instance, URI original) // 爲系統構建一個合適的host:port形式的URI LoadBalancerAutoConfiguration 條件: |-- @ConditionalOnClass(RestTemplate.class) |-- @ConditionalOnClass(LoadBalancerClient.class) 作了三件事情: |-- 建立一個LoadBalancerInterceptor的bean,用於實現對客戶端發起請求時進行攔截,以實現負載均衡。 |-- 建立一個RestTemplateCustomizer的bean,用於給RestTemplate增長LoadBalancerInterceptor攔截器。 |-- 維護一個被@LoadBalanced註解修飾的列表,並在這裏初始化,經過調用RestTemplateCustomizer的實例給須要 客戶端負載均衡的RestTemplate增長LoadBalancerInterceptor攔截器 LoadBalancerInterceptor private LoadBalancerClient loadBalancer; |-- // 注入 LoadBalancerInterceptor(LoadBalancerClient loadBalancer) { this.loadBalancer = loadBalancer; } |-- intercept(request, body, execution) { return this.loadBalancer.execute(serviceName, new LoadBalancerRequest<ClientHttpResponse>() { @Override public ClientHttpResponse apply(ServiceInstance instance) throw Exception { ServiceRequestWrapper serviceRequest = new ServiceRequestWrapper(request, instance); return execution.execute(serviceRequest, body); } }); } private class ServiceRequestWrapper extends HttpRequestWrapper { private final ServiceInstance instance; ServiceRequestWrapper(request, instance) { super(request); this.instance = instance; } @Override public URI getURI() { uri = LoadBalancerInterceptor.this.loadBalancer.reconstructURI( this.instance, getRequest().getURI()); return uri; } } RibbonLoadedBalancerClient |-- <T> T execute(String serviceId, LoadBalancerRequest<T> request) throw IOException { loadBalancer = getLoadBancer(serviceId); server = getServer(loadBalancer); if (server == null) throw e; ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); context = this.clientFactory.getLoadBancerContext(serviceId); statsRecorder = new RibbonStatsRecorder(context, server); try { T returnVal = request.apply(ribbonServer); statsRecorder.recordStats(returnVal); return returnVal; } catch(e) { throw e; } return null; } |-- getServer(loadBalancer) { if (loadBalancer == null) return null; return loadBalancer.chooseServer("default"); } |-- ILoadBalancer { public void addServers(List<Server> newServers); public Server chooseServer(Object key); public void markServerDown(Server server); // 用來通知標識負載均衡中某個具體實例已經中止服務,否則負載均衡器在下一次獲取實例清單前都會認爲服務實例均是正常服務的。 public List<Server> getReachableServers(); // 獲取當前正常服務列表 public List<Server> getAllServers(); } ZoneAwareLoadBalancer |-- public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping) { balancer = LoadBalancerBuilder.newBuilder() .withClientConfig(config).withRule(rule).withPing(ping) .withServerListFilter(serverListFilter).withDynamicServerList(serverList) .buildDynamicServerListLoadBalancer(); return balancer; } ServerInstance |-- String getServiceId() |-- String getHost() |-- int getPort() |-- boolean isSecure() |-- URI getUri() |-- Map<String, String> getMetadata() RibbonServer implements ServerInstance { RibbonServer(String serviceId, Server server) { this(serviceId, server, false, Collection.<String,String> emptryMap()); } RibbonServer(String serviceId, Server server, boolean secure, Map<String, String> metadata) { this.serverId = serviceId; this.server = server; this.secure = secure; this.metadata = metadata; } // setter、getter... } apply(final ServiceInstance instance) -> LoadBalancerClient.reconstructURI() // 重構URI來訪問 -> execution.execute(serviceRequest, body) -> InterceptingClient.InterceptingRequestExecution.execute() { ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); ... return delegate.execute(); } -> request.getURI() -> 調用ServiceRequestWrapper.getURI() -> RibbonLoadedBalancerClient.reconstructURI(ServiceInstance instance, URI original) { serviceId = instance.getServiceId(); context = this.clientFactory.getLoadBancerContext(serviceId); server = new Server(instance.getHost(), instance.getPort()); secure = isSecure(server, serviceId); URI uri = original; if (secure) { uri = UriComponentsBuilder.fromUri(uri),schema("https").build().toUri(); } return context.reconstructURIWithServer(server, uri); } |-- SpringClientFactory clientFactory // 用來建立客戶端負載均衡器的工廠類,該工廠類爲每個不一樣名的Ribbon客戶端生成不一樣的spring上下文 |-- RibbonLoadedBalancerContext context // LoadedBalancerContext子類,該類用於存儲被負載均衡器使用上下文內容和API操做 LoadBalancerContext implements IClientConfigAware { ... public URI reconstructURIWithServer(Server server, URI original) { host = server.getHost(); port = server.getPort(); if (host.equals(original.getHost()) && port = original.getPort()) { return original; } String schema = original.getSchema(); if (schema == null) { schema = deriveSchemaAndPortFromPartialUri(original).first(); } try { sb = new StringBuilder(); sb.append(schema).append("://"); if (!String.isNullOrEmpty(original.getRawUserInfo())) { sb.append(original.getRawUserInfo()).append("@"); } sb.append(host); if (port >= 0) { sb.append(":").append(port); } if (!String.isNullOrEmpty(original.getRawQuery())) { sb.append("?").append(original.getRawQuery()); } if (!String.isNullOrEmpty(original.getRawFragment())) { sb.append("#").append(original.getRawFragment()); } URI newURI = new URI(sb.toString()); return newURI; } catch(e) { throw e; } ... } } 負載均衡器: ILoadBalancer |-- AbstractLoadBalancer |-- ServerGroup // 服務實例 ALL:全部服務實例 STATUS_UP:正常服務實例 STATUS_NOT_UP:中止服務實例 |-- @Override Server chooseServer() { return chooseServer(null); } |-- abstract List<Server> getServerList(ServerGroup serverGroup) |-- abstract LoadBancerStats getLoadBancerStats() |-- BaseLoadBalancer 定義了兩個存儲服務實例Server對象的列表: |-- @Monitor(name = PREFIX + "AllServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> allServerList = Collections.synchronizedList(new ArrayList<Server>()); |-- @Monitor(name = PREFIX + "UpServerList", type = DataSourceType.INFORMATIONAL) protected volatile List<Server> upServerList = Collections.synchronizedList(new ArrayList<Server>()); 定義了以前咱們提到的用來存儲負載均衡器各服務實例屬性和統計信息的LoadBalancerStats對象 |-- LoadBalancerStats 定義了檢查服務是否正常服務的IPing對象 |-- IPing 定義了檢查服務實例操做的執行策略對象IPingStrategy |-- IPingStrategy |-- SerialPingStrategy implements IPingStrategy { @Override public boolean[] pingServer(IPing ping, Server[] servers) { int numCandidates = servers.length; boolean[] results = new Boolean[numCandidates]; if (logger.isDebugEnabled()) { logger.debug(...); } for (int i=0; i < numCandidates; i++) { results[i] = false; try { results[i] = ping.isAlive(server[i]); } catch (Throwable t) { logger.error(...); } } return results; } } 定義了負載均衡處理規則:(BaseLoadBalancer中的chooseServer(Object key)委託給了RoundRobinRule)-> rule.choose(key) |-- IRule |-- RoundRobinRule implements IRule { ... @Override Server choose(key) { ... } } 啓動ping任務: |-- BaseLoadBalancer() { // 啓動一個用於定時檢查Server是否健康的任務 } 實現了ILoadBalancer接口定義的負載均衡器應具有如下一系列基本操做。 |-- addServers(List newServers) { if (isNotEmpty(newServers)) { try { newList = new ArrayList<Server>(); newList.addAll(allServerList); newList.addAll(newServers); setServersList(newList); } catch (e) { logger.error(e); } } } |-- chooseServer(Object key) // 挑選一個具體的服務實例 |-- markServerDown(Server server) // 標記某個服務實例暫停服務 { if (server == null) return; if (!server.isAlive()) return; server.setAlive(false); notifyServerStatusChangeListener(singleton(server)); } |-- getReachableServers() // 獲取可用服務實例列表 { return Collections.unmodifiableList(upServerList); } |-- getAllServers() // 返回全部的服務實例清單 { return Collections.unmodifiableList(allServerList); } |-- DynamicServerListLoadBalancer |-- ServerList interface ServerList<T extends Server> { public List<T> getInitialListOfServers(); public List<T> getUpdatedListOfServers(); } |-- DomainExtractingServerList implements ServerList<DiscoveryEnabledServer> { // 委託給了DiscoveryEnabledNIWServerList private ServerList<DiscoveryEnabledServer> list; private IClientConfig clientConfig; private boolean approximateZoneFromHostname; DomainExtractingServerList(ServerList<DiscoveryEnabledServer> list, IClientConfig clientConfig, boolean approximateZoneFromHostname) { this.list = list; this.clientConfig = clientConfig; this.approximateZoneFromHostname = approximateZoneFromHostname; } @Override public List<DiscoveryEnabledServer> getInitialListOfServers() { servers = setZones(this.list.getInitialListOfServers()); return servers; } @Override public List<DiscoveryEnabledServer> getUpdatedListOfServers() { servers = setZones(this.list.getUpdatedListOfServers()); return servers; } ... } |-- DiscoveryEnabledNIWServerList implements DiscoveryEnabledServer{ @Override public List<DiscoveryEnabledServer> getInitialListOfServers() { return obtainServersViaDiscovery(); } @Override public List<DiscoveryEnabledServer> getUpdatedListOfServers() { return obtainServersViaDiscovery(); } private List<DiscoveryEnabledServer> obtainServersViaDiscovery() { if (eurekaClientProvider == null || eurekaClientProvider.get() == null) { return new ArrayList<DiscoveryEnabledServer>(); } serverList = new ArrayList<DiscoveryEnabledServer>(); eurekaClient = eurekaClientProvider.get(); if (vipAddresses != null) { for (String vipAddress : vipAddresses.split(",")) { listOfInstanceInfo = eurekaClient.getInstanceByVipAddress( vipAddress, isSecure, targetRegion); for (InstanceInfo ii : listOfInstanceInfo) { if (ii.getStatus().equals(InstanceStatus.UP)) { ... DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr); des.setZone(DiscoveryClient.getZone(ii)); serverList.add(des); } } } if (serverList.size() > 0 && prioritizeVipAddressBasedServers) { break; } } return serverList; } } |-- List<DiscoveryEnabledServer> setZones(List<DiscoveryEnabledServer> servers) { // 設置id、zone、isAliveFlag、readyToServer等信息 List<DiscoveryEnabledServer> result = new ArrayList<>(); isSecure = this.clientConfig.getPropertyAsBoolean(CommonClientConfigKey.IsSecure, Boolean.TRUE); shouldUseIpAddr = this.clientConfig.getPropertyAsBoolean(CommonClientConfigKey.UseIPAddrFofServer, Boolean.FALSE); for (DiscoveryEnabledServer server : servers) { result.add(new DomainExtractingServer(server, isSecure, shouldUseIpAddr, this.approximateZoneFromHostname)); } return result; } |-- ServerListUpdater interface ServerListUpdater { public interface UpdateAction { void doUpdate(); } // 啓動服務器 void start(UpdateAction updateAction); void stop(); String getLastUpdate(); // 獲取上一次更新到如今的時間間隔,單位爲毫秒 long getDurationSinceLastUpdateMs(); // 獲取錯過的週期數 int getNumberMissedCycles(); // 獲取核心線程數 int getCoreThreads(); } |-- PollingServerListUpdater implements ServerListUpdater // 動態服務列表更新的默認策略(負載均衡器默認實現就是它) { @Override public synchronized void start(final UpdateAction updateAction) { if (isActive.compareAndSet(false, true)) { final wrapperRunnable = new Runnable() { @Override public void run() { if (!isActive.get()) { if (scheduledFuture != null) { scheduledFuture.cancel(true); } return; } try { updateAction.doUpdate(); lastUpdated = System.currentTimeMillis(); } catch (e) { logger.warn(...); } } } scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay( wrapperRunnable, initialDelayMs, refreshIntervalMs, TimeUnit.MILLISECONDS ); } else { logger.info("Already active, no-op"); } } } |-- EurekaNotificationServerListUpdater // 經過Eureka的事件監聽器來驅動服務列表的更新操做 |-- ServerListFilter |-- AbstractServerListFilter<T extends Server> implements ServerListFilter<T> { volatile LoadBalancerStats stats; setLoadBalancerStats(LoadBalancerStats stats) { this.stats = stats; } LoadBalancerStats getLoadBalancerStats() { return stats; } } |-- ZoneAffinityServerListFilter extends AbstractServerListFilter { ... List<T> getFilteredListOfServers(List<T> servers) { if (isNotEmpty(servers)) { filteredServers = Lists.newArrayList(Iterables.filter( servers, this.zonAffinityPredicate.getServerOnlyPredicate())); if (shouldEnableZoneAffinity(filteredServers)) { return filteredServers; } else if (zonAffinity) { overrideCounter.increment(); } } return servers; } } |-- shouldEnableZoneAffinity(filteredServers) // 根據複雜算法篩選同區域實例指標(實例數量、斷路器斷開數、活動請求數、實例平均負載等) |-- blackOutServerPercentage:故障實例百分比(斷路器斷開數/實例數量)>=0.8 |-- activeRequestsPerServer:實例平均負載>=0.6 |-- availableServers:可用實例數量(實例數量-斷路器斷開數量)<2 { ... } |-- DefaultNIWSServerListFilter |-- ServerListSubsetFilter:適用於大規模服務器集羣(上百或更多)的系統 |-- ... |-- ZoneAwareLoadBalancer 待續。。。