服務發現是基於微服務的體系結構的主要宗旨之一。 嘗試手動配置每個客戶端或某種形式的約定可能很困難並且很脆弱。 Eureka是Netflix Service Discovery服務器和客戶端。 可以將服務器配置和部署爲高可用性,每個服務器將有關已註冊服務的狀態複製到其他服務器。
1 構建Eureka服務器
要將Eureka Server包含在您的項目中,請使用組ID爲org.springframework.cloud和工件ID爲的啓動器spring-cloud-starter-netflix-eureka-server。
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
application.yml
#Default port is 8761
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 5
serviceUrl:
defaultZone: http://localhost:8761
EurekaServerApplication.java
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
啓動服務,訪問http://localhost:8761/,查看服務界面,如下圖所示:
2 通過Eureka註冊服務
在MySQL數據庫eagle_eye_local
創建organizations組織表
DROP TABLE IF EXISTS organizations;
CREATE TABLE organizations (
organization_id VARCHAR(100) PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
contact_name TEXT NOT NULL,
contact_email TEXT NOT NULL,
contact_phone TEXT NOT NULL);
INSERT INTO organizations (organization_id, name, contact_name, contact_email, contact_phone)
VALUES ('e254f8c-c442-4ebe-a82a-e2fc1d1ff78a', 'customer-crm-co', 'Mark Balster', '[email protected]', '823-555-1212');
INSERT INTO organizations (organization_id, name, contact_name, contact_email, contact_phone)
VALUES ('442adb6e-fa58-47f3-9ca2-ed1fecdfe86c', 'HR-PowerSuite', 'Doug Drewry','[email protected]', '920-555-1212');
客戶端向Eureka註冊時,它會提供有關其自身的元數據,例如主機,端口,運行狀況指示器URL,主頁和其他詳細信息。Eureka從屬於服務的每個實例接收心跳消息。如果心跳在可配置的時間表上進行故障轉移,則通常會將實例從註冊表中刪除。
創建一個Spring Boot微服務作爲組織服務。
在pom.xml中增加以下依賴jar包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</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-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
application.yml
spring:
application:
name: organizationservice
profiles:
active:
dev
cloud:
config:
enabled: true
server:
port: 8181
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
OrganizationServiceApplication.java
@SpringBootApplication
@EnableEurekaClient
//@EnableFeignClients
public class OrganizationServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrganizationServiceApplication.class, args);
}
}
Organization.java
package com.example.organization.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "organizations")
public class Organization {
@Id
@Column(name = "organization_id", nullable = false)
String id;
@Column(name = "name", nullable = false)
String name;
@Column(name = "contact_name", nullable = false)
String contactName;
@Column(name = "contact_email", nullable = false)
String contactEmail;
@Column(name = "contact_phone", nullable = false)
String contactPhone;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getContactEmail() {
return contactEmail;
}
public void setContactEmail(String contactEmail) {
this.contactEmail = contactEmail;
}
public String getContactPhone() {
return contactPhone;
}
public void setContactPhone(String contactPhone) {
this.contactPhone = contactPhone;
}
}
OrganizationRepository.java
package com.example.organization.repository;
import com.example.organization.model.Organization;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface OrganizationRepository extends CrudRepository<Organization, String> {
public Optional<Organization> findById(String organizationId);
}
OrganizationService.java
package com.example.organization.services;
import com.example.organization.model.Organization;
import com.example.organization.repository.OrganizationRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
import java.util.UUID;
@Service
public class OrganizationService {
@Autowired
private OrganizationRepository orgRepository;
public Optional<Organization> getOrg(String organizationId) {
return orgRepository.findById(organizationId);
}
public void saveOrg(Organization org) {
org.setId(UUID.randomUUID().toString());
orgRepository.save(org);
}
public void updateOrg(Organization org) {
orgRepository.save(org);
}
public void deleteOrg(Organization org) {
orgRepository.deleteById(org.getId());
}
}
OrganizationServiceController.java
package com.example.organization.controllers;
import com.example.organization.model.Organization;
import com.example.organization.services.OrganizationService;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
@RestController
@RequestMapping(value = "organizations")
public class OrganizationServiceController {
@Autowired
private OrganizationService orgService;
@RequestMapping(value = "/{organizationId}", method = RequestMethod.GET)
public Optional<Organization> getOrganization(@PathVariable("organizationId") String organizationId) {
return orgService.getOrg(organizationId);
}
@RequestMapping(value = "/{organizationId}", method = RequestMethod.PUT)
public void updateOrganization(@PathVariable("organizationId") String orgId, @RequestBody Organization org) {
orgService.updateOrg(org);
}
@RequestMapping(value = "/{organizationId}", method = RequestMethod.POST)
public void saveOrganization(@RequestBody Organization org) {
orgService.saveOrg(org);
}
@RequestMapping(value = "/{organizationId}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteOrganization(@PathVariable("orgId") String orgId, @RequestBody Organization org) {
orgService.deleteOrg(org);
}
}
啓動服務,訪問http://localhost:8761/,查看註冊服務,如下圖所示:
訪問http://localhost:8761/eureka/apps/organizationservice,查看註冊服務,如下圖所示:
3 通過Eureka查找服務
我們還可以讓許可證服務調用該組織服務,而不必直接知曉任何組織服務的位置。許可證服務將通過Eureka來查找組織服務的實際位置。 我們將研究3個不同的Spring/Netflix客戶端庫,服務消費者可以使用它們來和Ribbon進行交互。
- Spring DiscoveryClient;
- 啓用了RestTemplate的Spring DiscoveryClient;
- Netflix Feign客戶端。
首先,修改 src/main/java/com/example/licenses/controllers/LicenseServiceController.java以包含許可證服務的新路由。這個新路由允許指定要用於調用服務的客戶端的類型。
@RequestMapping(value="/{licenseId}/{clientType}",method = RequestMethod.GET)
public License getLicensesWithClient( @PathVariable("organizationId") String organizationId,
@PathVariable("licenseId") String licenseId,
@PathVariable("clientType") String clientType) {
return licenseService.getLicense(organizationId,licenseId, clientType);
}
src/main/java/com/thoughtmechanix/licenses/services/LicenseService.java中的LicenseService類添加了一個名爲retrieveOrgInfo()的簡單方法,該方法將根據傳遞到路由的clientType類型進行解析,以用於查找組織服務實例。LicenseService類上的getLicense()方法將使用retrieveOrgInfo()方法從Postgres數據庫中檢索組織數據。
public License getLicense(String organizationId,String licenseId, String clientType) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId);
Organization org = retrieveOrgInfo(organizationId, clientType);
return license
.withOrganizationName( org.getName())
.withContactName( org.getContactName())
.withContactEmail( org.getContactEmail() )
.withContactPhone( org.getContactPhone() )
.withComment(config.getExampleProperty());
}
-
使用Spring DiscoveryClient查找服務實例
要開始使用DiscoveryClient,需要先使用@EnableDiscoveryClient註解來標註src/main/java/com/example/licenses/Application.java中的Application類。
License.java
package com.example.licenses.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
@Entity
@Table(name = "licenses")
public class License{
@Id
@Column(name = "license_id", nullable = false)
private String licenseId;
@Column(name = "organization_id", nullable = false)
private String organizationId;
@Transient
private String organizationName ="";
@Transient
private String contactName ="";
@Transient
private String contactPhone ="";
@Transient
private String contactEmail ="";
@Column(name = "product_name", nullable = false)
private String productName;
@Column(name = "license_type", nullable = false)
private String licenseType;
@Column(name = "license_max", nullable = false)
private Integer licenseMax;
@Column(name = "license_allocated", nullable = false)
private Integer licenseAllocated;
@Column(name="comment")
private String comment;
public Integer getLicenseMax() {
return licenseMax;
}
public void setLicenseMax(Integer licenseMax) {
this.licenseMax = licenseMax;
}
public Integer getLicenseAllocated() {
return licenseAllocated;
}
public void setLicenseAllocated(Integer licenseAllocated) {
this.licenseAllocated = licenseAllocated;
}
public String getLicenseId() {
return licenseId;
}
public void setLicenseId(String licenseId) {
this.licenseId = licenseId;
}
public String getOrganizationId() {
return organizationId;
}
public void setOrganizationId(String organizationId) {
this.organizationId = organizationId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getLicenseType() {
return licenseType;
}
public void setLicenseType(String licenseType) {
this.licenseType = licenseType;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getOrganizationName() {
return organizationName;
}
public void setOrganizationName(String organizationName) {
this.organizationName = organizationName;
}
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getContactPhone() {
return contactPhone;
}
public void setContactPhone(String contactPhone) {
this.contactPhone = contactPhone;
}
public String getContactEmail() {
return contactEmail;
}
public void setContactEmail(String contactEmail) {
this.contactEmail = contactEmail;
}
public License withId(String id){
this.setLicenseId(id);
return this;
}
public License withOrganizationId(String organizationId){
this.setOrganizationId(organizationId);
return this;
}
public License withProductName(String productName){
this.setProductName(productName);
return this;
}
public License withLicenseType(String licenseType){
this.setLicenseType(licenseType);
return this;
}
public License withLicenseMax(Integer licenseMax){
this.setLicenseMax(licenseMax);
return this;
}
public License withLicenseAllocated(Integer licenseAllocated){
this.setLicenseAllocated(licenseAllocated);
return this;
}
public License withComment(String comment){
this.setComment(comment);
return this;
}
public License withOrganizationName(String organizationName){
this.setOrganizationName(organizationName);
return this;
}
public License withContactName(String contactName){
this.setContactName(contactName);
return this;
}
public License withContactPhone(String contactPhone){
this.setContactPhone(contactPhone);
return this;
}
public License withContactEmail(String contactEmail){
this.setContactEmail(contactEmail);
return this;
}
}
LicenseService.java
package com.example.licenses.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.licenses.clients.OrganizationDiscoveryClient;
import com.example.licenses.config.ServiceConfig;
import com.example.licenses.model.License;
import com.example.licenses.model.Organization;
import com.example.licenses.repository.LicenseRepository;
import java.util.List;
import java.util.UUID;
@Service
public class LicenseService {
@Autowired
private LicenseRepository licenseRepository;
@Autowired
OrganizationDiscoveryClient organizationDiscoveryClient;
@Autowired
ServiceConfig config;
public License getLicense(String organizationId, String licenseId) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId);
if (license != null) {
license.withComment(config.getExampleProperty());
}
return license;
}
public List<License> getLicensesByOrg(String organizationId) {
return licenseRepository.findByOrganizationId(organizationId);
}
public void saveLicense(License license) {
license.withId(UUID.randomUUID().toString());
licenseRepository.save(license);
}
public void updateLicense(License license) {
licenseRepository.save(license);
}
public void deleteLicense(String licenseId) {
licenseRepository.deleteById(licenseId);
}
public License getLicense(String organizationId,String licenseId, String clientType) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId);
Organization org = retrieveOrgInfo(organizationId, clientType);
return license
.withOrganizationName( org.getName())
.withContactName( org.getContactName())
.withContactEmail( org.getContactEmail() )
.withContactPhone( org.getContactPhone() )
.withComment(config.getExampleProperty());
}
private Organization retrieveOrgInfo(String organizationId, String clientType){
Organization organization = null;
switch (clientType) {
case "discovery":
System.out.println("I am using the discovery client");
organization = organizationDiscoveryClient.getOrganization(organizationId);
break;
}
return organization;
}
}
LicenseServiceController.java
package com.example.licenses.controllers;
import com.example.licenses.model.License;
import com.example.licenses.services.LicenseService;
import com.example.licenses.config.ServiceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.List;
@RestController
@RequestMapping(value = "organizations/{organizationId}/licenses")
public class LicenseServiceController {
@Autowired
private LicenseService licenseService;
@Autowired
private ServiceConfig serviceConfig;
/**
* 查詢所有對象
*
* @param organizationId
* @return
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<License> getLicenses(@PathVariable("organizationId") String organizationId) {
return licenseService.getLicensesByOrg(organizationId);
}
/**
* 查詢單個對象
*
* @param organizationId
* @param licenseId
* @return
*/
@RequestMapping(value = "/{licenseId}", method = RequestMethod.GET)
public License getLicenses(@PathVariable("organizationId") String organizationId,
@PathVariable("licenseId") String licenseId) {
return licenseService.getLicense(organizationId, licenseId);
}
/**
* 新增對象
*
* @param license
*/
@RequestMapping(value = "/", method = RequestMethod.POST)
public void saveLicenses(@RequestBody License license) {
licenseService.saveLicense(license);
}
/**
* 更新對象
*
* @param licenseId
* @return
*/
@RequestMapping(value = "/", method = RequestMethod.PUT)
public String updateLicenses(@RequestBody License license) {
licenseService.updateLicense(license);
return String.format("This is the put");
}
/**
* 刪除對象
*
* @param licenseId
* @return
*/
@RequestMapping(value = "{licenseId}", method = RequestMethod.DELETE)
// @ResponseStatus(HttpStatus.NO_CONTENT)
public String deleteLicenses(@PathVariable("licenseId") String licenseId) {
licenseService.deleteLicense(licenseId);
return String.format("This is the Delete");
}
@RequestMapping(value="/{licenseId}/{clientType}",method = RequestMethod.GET)
public License getLicensesWithClient( @PathVariable("organizationId") String organizationId,
@PathVariable("licenseId") String licenseId,
@PathVariable("clientType") String clientType) {
return licenseService.getLicense(organizationId,licenseId, clientType);
}
}
OrganizationDiscoveryClient.java
package com.example.licenses.clients;
import com.example.licenses.model.Organization;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@Component
public class OrganizationDiscoveryClient {
@Autowired
private DiscoveryClient discoveryClient;
public Organization getOrganization(String organizationId) {
RestTemplate restTemplate = new RestTemplate();
List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice");
if (instances.size() == 0)
return null;
String serviceUri = String.format("%s/organizations/%s", instances.get(0).getUri().toString(), organizationId);
System.out.println("!!!! SERVICE URI: " + serviceUri);
ResponseEntity<Organization> restExchange = restTemplate.exchange(serviceUri, HttpMethod.GET, null,
Organization.class, organizationId);
return restExchange.getBody();
}
}
啓動服務,訪問http://localhost:8080/organizations/442adb6e-fa58-47f3-9ca2-ed1fecdfe86c/licenses/08dbe05-606e-4dad-9d33-90ef10e334f9/discovery,查詢許可證信息,如下圖所示:
-
使用帶有Ribbon功能的Spring RestTemplate調用服務
要使用帶有Ribbon功能的Spring RestTemplate類,需要使用Spring Cloud註解@LoadBalanced來定義RestTemplate的構造方法。對於許可證服務,可以在src/main/java/com/example/licenses/LicensingServiceApplication.java中找到用於創建RestTemplate bean的方法。
LicenseService.java
package com.example.licenses.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.licenses.clients.OrganizationDiscoveryClient;
import com.example.licenses.clients.OrganizationFeignClient;
import com.example.licenses.clients.OrganizationRestTemplateClient;
import com.example.licenses.config.ServiceConfig;
import com.example.licenses.model.License;
import com.example.licenses.model.Organization;
import com.example.licenses.repository.LicenseRepository;
import java.util.List;
import java.util.UUID;
@Service
public class LicenseService {
@Autowired
private LicenseRepository licenseRepository;
@Autowired
ServiceConfig config;
@Autowired
OrganizationDiscoveryClient organizationDiscoveryClient;
@Autowired
OrganizationRestTemplateClient organizationRestClient;
@Autowired
OrganizationFeignClient organizationFeignClient;
public License getLicense(String organizationId, String licenseId) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId);
if (license != null) {
license.withComment(config.getExampleProperty());
}
return license;
}
public License getLicense(String organizationId,String licenseId, String clientType) {
License license = licenseRepository.findByOrganizationIdAndLicenseId(organizationId, licenseId);
Organization org = retrieveOrgInfo(organizationId, clientType);
return license
.withOrganizationName( org.getName())
.withContactName( org.getContactName())
.withContactEmail( org.getContactEmail() )
.withContactPhone( org.getContactPhone() )
.withComment(config.getExampleProperty());
}
private Organization retrieveOrgInfo(String organizationId, String clientType){
Organization organization = null;
switch (clientType) {
case "discovery":
System.out.println("I am using the discovery client");
organization = organizationDiscoveryClient.getOrganization(organizationId);
break;
case "rest":
System.out.println("I am using the rest client");
organization = organizationRestClient.getOrganization(organizationId);
break;
case "feign":
System.out.println("I am using the feign client");
organization = organizationFeignClient.getOrganization(organizationId);
break;
}
return organization;
}
public List<License> getLicensesByOrg(String organizationId) {
return licenseRepository.findByOrganizationId(organizationId);
}
public void saveLicense(License license) {
license.withId(UUID.randomUUID().toString());
licenseRepository.save(license);
}
public void updateLicense(License license) {
licenseRepository.save(license);
}
public void deleteLicense(String licenseId) {
licenseRepository.deleteById(licenseId);
}
}
LicensingServiceApplication.java
package com.example.licenses;
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.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import java.util.Collections;
import java.util.List;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LicensingServiceApplication {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(LicensingServiceApplication.class, args);
}
}
OrganizationRestTemplateClient.java
package com.example.licenses.clients;
import com.example.licenses.model.Organization;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@Component
public class OrganizationRestTemplateClient {
@Autowired
RestTemplate restTemplate;
public Organization getOrganization(String organizationId){
ResponseEntity<Organization> restExchange =
restTemplate.exchange(
"http://organizationservice/organizations/{organizationId}",
HttpMethod.GET,
null, Organization.class, organizationId);
return restExchange.getBody();
}
}
啓動服務,訪問http://localhost:8080/organizations/442adb6e-fa58-47f3-9ca2-ed1fecdfe86c/licenses/08dbe05-606e-4dad-9d33-90ef10e334f9/rest,查詢許可證信息,如下圖所示:
-
使用Netflix Feign客戶端調用服務
要在許可證服務中允許使用Feign客戶端,需要向許可證服務的src/main/java/com/example/licenses/LicensingServiceApplication.java添加一個新註解@EnableFeignClients。
在pom.xml中增加以下依賴jar包:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
OrganizationFeignClient.java
package com.example.licenses.clients;
import com.example.licenses.model.Organization;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient("organizationservice")
public interface OrganizationFeignClient {
@RequestMapping(method = RequestMethod.GET, value = "organizations/{organizationId}", consumes = "application/json")
Organization getOrganization(@PathVariable("organizationId") String organizationId);
}
啓動服務,訪問http://localhost:8080/organizations/442adb6e-fa58-47f3-9ca2-ed1fecdfe86c/licenses/08dbe05-606e-4dad-9d33-90ef10e334f9/feign,查詢許可證信息,如下圖所示: