Spring 架構提供了一組用于在 Java 生态中建立微服務的庫。它們是 Spring Cloud 項目的一部分。今天我将向您展示如何使用 Spring Boot 和以下技術建立簡單的微服務支手架:
- Zuul – 提供動态路由、監控、彈性、安全性等的網關服務
- Ribbon– 用戶端負載均衡器
- Feign – 聲明式 REST 用戶端
- Eureka – 服務注冊和發現
- Sleuth – 通過日志進行分布式跟蹤
- Zipkin – 具有請求可視化的分布式跟蹤系統。
本文介紹spring cloud微服務快速入門執行個體的應用程式架構以下圖所示。用戶端調用customer-service内部可用的端點,該端點通過 Zuul 網關存儲基本客戶資料。此端點與 account-service 互動以收集有關由 account-service 中的端點提供服務的客戶帳戶的資訊。每個服務在 Eureka 上注冊自己,并使用 spring-cloud-sleuth 将其日志發送到 Zipkin。

這是一個
account-service
控制器。我們通過 id, 使用
findByCustomer方法來擷取客戶帳戶
。
@RestController
public class Api {
private List<Account> accounts;
protected Logger logger = Logger.getLogger(Api.class.getName());
public Api() {
accounts = new ArrayList<>();
accounts.add(new Account(1, 1, "111111"));
accounts.add(new Account(2, 2, "222222"));
accounts.add(new Account(3, 3, "333333"));
accounts.add(new Account(4, 4, "444444"));
accounts.add(new Account(5, 1, "555555"));
accounts.add(new Account(6, 2, "666666"));
accounts.add(new Account(7, 2, "777777"));
}
@RequestMapping("/accounts/{number}")
public Account findByNumber(@PathVariable("number") String number) {
logger.info(String.format("Account.findByNumber(%s)", number));
return accounts.stream().filter(it -> it.getNumber().equals(number)).findFirst().get();
}
@RequestMapping("/accounts/customer/{customer}")
public List<Account> findByCustomer(@PathVariable("customer") Integer customerId) {
logger.info(String.format("Account.findByCustomer(%s)", customerId));
return accounts.stream().filter(it -> it.getCustomerId().intValue()==customerId.intValue()).collect(Collectors.toList());
}
@RequestMapping("/accounts")
public List<Account> findAll() {
logger.info("Account.findAll()");
return accounts;
}
}
這是
customer-service
控制器。有一個findById方法可以與
account-service
使用
Feign
用戶端進行互動。
@RestController
public class Api {
@Autowired
private AccountClient accountClient;
protected Logger logger = Logger.getLogger(Api.class.getName());
private List<Customer> customers;
public Api() {
customers = new ArrayList<>();
customers.add(new Customer(1, "12345", "Adam Kowalski", CustomerType.INDIVIDUAL));
customers.add(new Customer(2, "12346", "Anna Malinowska", CustomerType.INDIVIDUAL));
customers.add(new Customer(3, "12347", "Paweł Michalski", CustomerType.INDIVIDUAL));
customers.add(new Customer(4, "12348", "Karolina Lewandowska", CustomerType.INDIVIDUAL));
}
@RequestMapping("/customers/pesel/{pesel}")
public Customer findByPesel(@PathVariable("pesel") String pesel) {
logger.info(String.format("Customer.findByPesel(%s)", pesel));
return customers.stream().filter(it -> it.getPesel().equals(pesel)).findFirst().get();
}
@RequestMapping("/customers")
public List<Customer> findAll() {
logger.info("Customer.findAll()");
return customers;
}
@RequestMapping("/customers/{id}")
public Customer findById(@PathVariable("id") Integer id) {
logger.info(String.format("Customer.findById(%s)", id));
Customer customer = customers.stream().filter(it -> it.getId().intValue()==id.intValue()).findFirst().get();
List<Account> accounts = accountClient.getAccounts(id);
customer.setAccounts(accounts);
return customer;
}
}
下面是 Feign 用戶端與
account-service
.
@FeignClient("account-service")
public interface AccountClient {
@RequestMapping(method = RequestMethod.GET, value = "/accounts/customer/{customerId}")
List<Account> getAccounts(@PathVariable("customerId") Integer customerId);
}
為了能夠使用 Feign 用戶端,我們隻需要在我們的主類中啟用它。
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
請注意customer-service中application.yml裡面配置資訊。需要啟用ribbon負載平衡器。我還建議在 Eureka 用戶端上設定租約更新和到期,以便在我們的服務關閉時從發現服務中取消注冊。
server:
port: ${PORT:3333}
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka/
instance:
leaseRenewalIntervalInSeconds: 1
leaseExpirationDurationInSeconds: 2
ribbon:
eureka:
enabled: true
我們已經實作并配置了兩個微服務。Eureka server為我們的提供注冊及發現服務功能,是以我們必須建立并運作基于 Eureka server的發現服務。建立此服務非常簡單,我們隻需要導入一個依賴項
spring-cloud-starter-eureka-server
并使用
@EnableEurekaServer
注釋在應用程式主類中啟用它。以下是 application.yml 檔案中 Eureka 伺服器的配置:
server:
port: ${PORT:8761}
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
server:
enableSelfPreservation: false
運作 discovery-service 後,我們看可以通過8761端口通路此控制台。現在讓我們在 application.yml 配置檔案中設定的預設端口上運作我們的兩個微服務,并使用-DPORT VM參數在另一個端口上運作它們的更多兩個執行個體,例如端口2223上的帳戶服務和端口 3334 上的客戶服務。現在我們來看看 Eureka 監控控制台。我們有兩個在2222、2223端口上運作的帳戶服務執行個體和兩個在3333、3334端口上運作的客戶服務執行個體。
我們在
eureka
上為每個微服務注冊了兩個執行個體。但是我們需要向外界隐藏我們的系統複雜性。在一個端口上應該隻有一個 IP 位址可供入站用戶端使用。這就是為什麼我們需要 API 網關——Zuul原因。Zuul 将根據其代理配置将我們的請求轉發到特定的微服務。此類請求也将由R
ibbon
用戶端進行負載均衡。要啟用 Zuul 網關依賴項,
spring-cloud-starter-zuul
應在 pom.xml 中添加并
@EnableZuulProxy
在主類中添加注釋。這是 application.yml 中為我們的服務設定的 Zuul 配置。
server:
port: 8765
zuul:
prefix: /api
routes:
account:
path: /account/**
serviceId: account-service
customer:
path: /customer/**
serviceId: customer-service
...
就像我們看到的 Zuul 被配置為在其預設端口8765下可用;它轉發來自
/api/account/
路徑請求給
account-service處理,
來自
/api/customer/
的請求給
customer-service處理
。當 URL http://localhost:8765/api/customer/customers/1被多次調用時,我們将看到它在每個微服務的兩個執行個體之間會進行用戶端的負載均衡。此外,當我們關閉一個微服務執行個體時,我們可以檢視它是否已從 Eureka 伺服器登出。
在文章的第二部分,我将介紹如何使用 Spring Cloud Sleuth、Zipkin 和 ELK。