本篇代碼存放於:githubjava
服務發現架構一般具備下面 4 個概念:git
下圖展現了這 4 個概念的流程,以及在服務發現模式實現中一般發生的狀況:github
一般服務實例都只向一個服務發現實例註冊,服務發現實例之間再經過數據傳輸,讓每一個服務實例註冊到全部的服務發現實例中。
服務在向服務發現實例註冊後,這個服務就能被服務消費者調用了。服務消費者可使用多種模型來"發現"服務。算法
以下圖所示:spring
在這個模型中,當服務消費者須要調用一個服務時:json
(1)聯繫服務發現層,獲取所請求服務的全部服務實例,而後放到本地緩存中。bootstrap
(2)每次調用該服務時,服務消費者從緩存中取出一個服務實例的位置,一般這個'取出'使用簡單的複製均衡算法,如「輪詢」,「隨機",以確保服務調用分佈在全部實例之間。緩存
(3)客戶端將按期與服務發現層進行通訊,並刷新服務實例的緩存。服務器
(4)若是在調用服務的過程當中,服務調用失敗,那麼本地緩存將從服務發現層中刷新數據,再次嘗試。架構
<!-- more -->
使用 spring cloud 和 Netflix Eureka 搭建服務發現實例。
eurekasvr POM 主要配置以下:
<!-- 其餘依賴省略 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency>
applicaiton.yml 配置以下:
server: port: 8761 eureka: client: #不註冊本身 register-with-eureka: false #不在本地緩存註冊表信息 fetch-registry: false server: #接受請求前的等待實際,開發模式下不要開啓 #wait-time-in-ms-when-sync-empty: 5
最後在啓動類上加入註釋@SpringBootApplication
便可啓動服務中心。服務中心管理頁面:http://localhost:8761
這裏咱們編寫一個新服務註冊到服務中心,organizationservice:組織服務。並將上一篇的兩個服務:confsvr:配置中心服務,licensingservice:受權服務註冊到服務中心。
首先修改 POM 文件:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
而後修改配置文件 application.yml:
server: port: 8888 eureka: instance: #註冊服務的IP,而不是服務器名 prefer-ip-address: true client: #向eureka註冊服務 register-with-eureka: true #拉取註冊表的本地副本 fetch-registry: true service-url: #Eureka服務的位置(若是有多個註冊中心,使用,分隔) defaultZone: http://localhost:8761/eureka/ spring: profiles: # 使用文件系統來存儲配置信息,須要設置爲native active: native application: name: confsvr cloud: config: server: native: # 使用文件來存放配置文件,爲每一個應用程序提供用逗號分隔的文件夾列表 searchLocations: file:///D:/configFolder/licensingservice,file:///D:/configFolder/organizationservice
最後在啓動類加入註解@EnableDiscoveryClient
,啓動便可在 eureka 管理頁面發現。
首先修改 POM
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
而後修改配置文件 bootstrap.yml
spring: application: #指定名稱,以便spring cloud config客戶端知道查找哪一個配置 name: licensingservice profiles: #指定環境 active: dev cloud: config: #設爲true便會自動獲取從配置中心獲取配置文件 enabled: true eureka: instance: prefer-ip-address: true client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8761/eureka/
最後在啓動類加入註解@EnableDiscoveryClient
,啓動便可在 eureka 管理頁面發現本服務實例。
首先在文件夾file:///D:/configFolder/organizationservice下建立兩個配置文件:organizationservice.yml,organizationservice-dev.yml,內容分別爲:
#organizationservice-dev.yml server: port: 10012
#organizationservice.yml spring: application: name: organizationservice
主要 POM 配置以下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
而後修改配置文件,bootstrap.yml
spring: application: #指定名稱,以便spring cloud config客戶端知道查找哪一個配置 name: organizationservice profiles: #指定環境 active: dev cloud: config: enabled: true eureka: instance: prefer-ip-address: true client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://localhost:8761/eureka/
最後在啓動類加入註解@EnableDiscoveryClient
,啓動。
如今已經有兩個註冊服務了,如今來讓許可證服務調用組織服務,獲取組織信息。首先在 organizationservice 服務中的 controller 包中加入一個 controller 類,讓它可以響應請求:
//OrganizationController.java @RestController public class OrganizationController { @GetMapping(value = "/organization/{orgId}") public Object getOrganizationInfo(@PathVariable("orgId") String orgId) { Map<String, String> data = new HashMap<>(2); data.put("id", orgId); data.put("name", orgId + "公司"); return data; } }
接下來讓許可證服務經過 Eureka 來找到組織服務的實際位置,而後調用該接口。爲了達成目的,咱們將要學習使用 3 個不一樣的 Spring/Netflix 客戶端庫,服務消費者可使用它們來和 Ribbon 進行交互。從最低級別到最高級別,這些庫包含了不一樣的與 Ribbon 進行交互的抽象封裝層次:
該工具提供了對 Ribbon 和 Ribbon 中緩存的註冊服務最低層次的訪問,能夠查詢經過 Eureka 註冊的全部服務以及這些服務對應的 URL。
首先在 licensingservice 的啓動類中加入@EnableDiscoveryClient
註解來啓用 DiscoveryClient 和 Ribbon 庫。
而後在 service 包下建立 OrganizationService.java
@Service public class OrganizationService { private static final String SERVICE_NAME = "organizationservice"; private DiscoveryClient discoveryClient; @Autowired public OrganizationService(DiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; } /** * 使用Spring DiscoveryClient查詢 * * @param id * @return */ public Organization getOrganization(String id) { RestTemplate restTemplate = new RestTemplate(); List<ServiceInstance> instances = discoveryClient.getInstances(SERVICE_NAME); if (instances.size() == 0) { throw new RuntimeException("無可用的服務"); } String serviceUri = String.format("%s/organization/%s", instances.get(0).getUri().toString(), id); ResponseEntity<Organization> responseEntity = restTemplate.exchange(serviceUri, HttpMethod.GET , null, Organization.class, id); return responseEntity.getBody(); } }
接着在 controller 包中新建 LicensingController.java
@RestController public class LicensingController { private OrganizationService organizationService; @Autowired public LicensingController(OrganizationService organizationService) { this.organizationService = organizationService; } @GetMapping("/licensing/{orgId}") public Licensing getLicensing(@PathVariable("orgId") String orgId) { Licensing licensing = new Licensing(); licensing.setValid(false); licensing.setOrganization(organizationService.getOrganization(orgId)); return licensing; } }
啓動全部項目,訪問localhost:10011/licensing/12,能夠看到返回以下結果:
{ "organization": { "id": "12", "name": "12公司" }, "valid": false }
在實際開發中,基本上是用不到這個的,除非是爲了查詢 Ribbon 以獲取某個服務的全部實例信息,纔會直接使用。若是直接使用它存在如下兩個問題:
這種方法是較爲經常使用的微服務通訊機制之一。要啓動該功能,須要使用 Spring Cloud 註解@LoadBanced 來定義 RestTemplate bean 的構造方法。方便起見直接在啓動類中定義 bean:
#LicensingserviceApplication.java @SpringBootApplication @EnableDiscoveryClient //使用不帶Ribbon功能的Spring RestTemplate,其餘狀況下可刪除 public class LicensingserviceApplication { /** * 使用帶有Ribbon 功能的Spring RestTemplate,其餘狀況可刪除 */ @LoadBalanced @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(LicensingserviceApplication.class, args); } }
接着 service 包下增長一個類:OrganizationByRibbonService.java
@Component public class OrganizationByRibbonService { private RestTemplate restTemplate; @Autowired public OrganizationByRibbonService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } public Organization getOrganizationWithRibbon(String id) { ResponseEntity<Organization> responseEntity = restTemplate.exchange("http://organizationservice/organization/{id}", HttpMethod.GET, null, Organization.class, id); return responseEntity.getBody(); } }
最後就是在 LicensingController.js 中加一個訪問路徑:
//不要忘記注入OrganizationByRibbonService服務 @GetMapping("/licensingByRibbon/{orgId}") public Licensing getLicensingByRibbon(@PathVariable("orgId") String orgId) { Licensing licensing = new Licensing(); licensing.setValid(false); licensing.setOrganization(organizationService.getOrganization(orgId)); return licensing; } }
訪問localhost:10011/licensingByRibbon/113,便可看到結果。
Feign 客戶端是 Spring 啓用 Ribbon 的 RestTemplate 類的替代方案。開發人員只需定義一個接口,而後使用 Spring 註解來標註接口,便可調用目標服務。除了編寫接口定義無需編寫其餘輔助代碼。
首先啓動類上加一個@EnableFeignClients
註解啓用 feign 客戶端。而後在 POM 中加入 Feign 的依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
而後在 client 包下新建 OrganizationFeignClient.java
@FeignClient("organizationservice")//使用FeignClient註解指定目標服務 public interface OrganizationFeignClient { /** * 獲取組織信息 * * @param orgId 組織id * @return Organization */ @RequestMapping(method = RequestMethod.GET, value = "/organization/{orgId}", consumes = "application/json") Organization getOrganization(@PathVariable("orgId") String orgId); }
最後修改LicensingController.java
,加入一個路由調用 Feign。
//注入OrganizationFeignClient,使用構造注入 @GetMapping("/licensingByFeign/{orgId}") public Licensing getLicensingByFeign(@PathVariable("orgId") String orgId) { Licensing licensing = new Licensing(); licensing.setValid(false); licensing.setOrganization(organizationFeignClient.getOrganization(orgId)); return licensing; }
訪問localhost:10011/licensingByFeign/11313,便可看到結果。
這一節磨磨蹭蹭寫了好幾天,雖然例子很簡單,可是相信應該是可以看懂的。因爲篇幅緣由代碼沒有所有貼上,想要查看完整代碼,能夠訪問這個連接:點擊跳轉。