服务发现是基于微服务的体系结构的主要宗旨之一。 尝试手动配置每个客户端或某种形式的约定可能很困难并且很脆弱。 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,查询许可证信息,如下图所示: