SpringCloud標準版的服務注冊可以用
eureka、consul或zookeeper,配置中心用config或bus,現(xiàn)在Spring Cloud Alibaba用Nacos搞定這些,簡言之就是注冊中心加配置中心。Nacos可以代替eureka做服務注冊中心。
Gitee上的Spring-Cloud-Alibaba/nacos-discovery-example

Nacos是什么
Nacos 致力于幫助您發(fā)現(xiàn)、配置和管理微服務。Nacos 提供了一組簡單易用的特性集,幫助您快速實現(xiàn)動態(tài)服務發(fā)現(xiàn)、服務配置、服務元數(shù)據(jù)及流量管理。
Nacos 的關鍵特性包括:
-
服務發(fā)現(xiàn)和服務健康監(jiān)測
Nacos 支持基于 DNS 和基于 RPC 的服務發(fā)現(xiàn)。服務提供者使用 原生SDK、OpenAPI、或一個獨立的Agent TODO注冊 Service 后,服務消費者可以使用DNS TODO 或HTTP&API查找和發(fā)現(xiàn)服務。
Nacos 提供對服務的實時的健康檢查,阻止向不健康的主機或服務實例發(fā)送請求。Nacos 支持傳輸層 (PING 或 TCP)和應用層 (如 HTTP、MySQL、用戶自定義)的健康檢查。 對于復雜的云環(huán)境和網(wǎng)絡拓撲環(huán)境中(如 VPC、邊緣網(wǎng)絡等)服務的健康檢查,Nacos 提供了 agent 上報模式和服務端主動檢測2種健康檢查模式。Nacos 還提供了統(tǒng)一的健康檢查儀表盤,幫助您根據(jù)健康狀態(tài)管理服務的可用性及流量。
-
動態(tài)配置服務
動態(tài)配置服務可以讓您以中心化、外部化和動態(tài)化的方式管理所有環(huán)境的應用配置和服務配置。
動態(tài)配置消除了配置變更時重新部署應用和服務的需要,讓配置管理變得更加高效和敏捷。
配置中心化管理讓實現(xiàn)無狀態(tài)服務變得更簡單,讓服務按需彈性擴展變得更容易。
Nacos 提供了一個簡潔易用的UI (控制臺樣例 Demo) 幫助您管理所有的服務和應用的配置。Nacos 還提供包括配置版本跟蹤、金絲雀發(fā)布、一鍵回滾配置以及客戶端配置更新狀態(tài)跟蹤在內(nèi)的一系列開箱即用的配置管理特性,幫助您更安全地在生產(chǎn)環(huán)境中管理配置變更和降低配置變更帶來的風險。
-
動態(tài) DNS 服務
動態(tài) DNS 服務支持權(quán)重路由,讓您更容易地實現(xiàn)中間層負載均衡、更靈活的路由策略、流量控制以及數(shù)據(jù)中心內(nèi)網(wǎng)的簡單DNS解析服務。動態(tài)DNS服務還能讓您更容易地實現(xiàn)以 DNS 協(xié)議為基礎的服務發(fā)現(xiàn),以幫助您消除耦合到廠商私有服務發(fā)現(xiàn) API 上的風險。
Nacos 提供了一些簡單的 DNS APIs (暫未實現(xiàn)) 幫助您管理服務的關聯(lián)域名和可用的 IP:PORT 列表.
-
服務及其元數(shù)據(jù)管理
Nacos 能讓您從微服務平臺建設的視角管理數(shù)據(jù)中心的所有服務及元數(shù)據(jù),包括管理服務的描述、生命周期、服務的靜態(tài)依賴分析、服務的健康狀態(tài)、服務的流量管理、路由及安全策略、服務的 SLA 以及最首要的 metrics 統(tǒng)計數(shù)據(jù)。
前提
您需要先下載 Nacos 并啟動 Nacos server。操作步驟參見 Nacos 快速入門。我使用的是windows單機版,進入bin目錄下執(zhí)行以下命令啟動nacos-startup.bat。

管理頁面:訪問
ip:8848/nacos,看到如下畫面就啟動成功了,默認賬號密碼都是nacos。
服務注冊與發(fā)現(xiàn)
本項目演示如何使用 Nacos Discovery Starter 完成 Spring Cloud 應用的服務注冊與發(fā)現(xiàn)。
服務提供者
- 新建基于
Nacos的服務提供者
新建module,Spring boot的腳手架starter使用阿里云https://start.aliyun.com/。
Spring Initializr -
Dependiencies選擇如下圖所示:Nacos Service Discovery依賴
1.我的IDEA 2020.1和Spring Boot 2.2.0版本,如果與我不同,可以參考我的pom文件修改版本:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.pay.cloud.alibaba</groupId>
<artifactId>nacos-discovery-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-discovery-provider</name>
<description>Spring cloud Alibaba nacos-discovery-provider project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
核心是引入如下依賴:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
踩坑:
由于Spring Boot 2.3.0版本太新,使用<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>,會報錯Endpoint ID 'nacos-discovery' contains invalid characters, please migrate to a valid format.。查看Spring-Cloud-Alibaba開源代碼pom.xml,發(fā)現(xiàn)他使用的spring-cloud版本是<spring-cloud-commons.version>2.2.2.RELEASE</spring-cloud-commons.version>,二者的版本對應關系見上篇文章末尾部分。
- application.properties
server.port=18081
spring.application.name=service-provider
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#spring.cloud.nacos.discovery.instance-enabled=true
spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
配置文件中配置 Nacos Server 地址:spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848。
Nacos 服務的名稱會使用:spring.application.name=service-provider配置值。
- 啟動類
package com.pay.cloud.alibaba.nacosdiscoveryprovider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: NacosDiscoveryProviderApplication
* @Description: nacos服務提供者
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/11 16:51
* @Copyright:
*/
@EnableDiscoveryClient
@SpringBootApplication
public class NacosDiscoveryProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosDiscoveryProviderApplication.class, args);
}
@RestController
class EchoController {
@GetMapping("/")
public ResponseEntity index() {
return new ResponseEntity("index error", HttpStatus.INTERNAL_SERVER_ERROR);
}
@GetMapping("/test")
public ResponseEntity test() {
return new ResponseEntity("error", HttpStatus.INTERNAL_SERVER_ERROR);
}
@GetMapping("/sleep")
public String sleep() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "ok";
}
@GetMapping("/echo/{string}")
public String echo(@PathVariable String string) {
return "Guoxiuzhi's Nacos Discovery output:" + string;
}
@GetMapping("/divide")
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
return String.valueOf(a / b);
}
}
}
使用 @EnableDiscoveryClient注解開啟服務注冊與發(fā)現(xiàn)功能。
- 啟動 Nacos Server
-
首先需要獲取 Nacos Server,支持直接下載和源碼構(gòu)建兩種方式。
- 直接下載:Nacos Server 下載頁
- 源碼構(gòu)建:進入 Nacos Github 項目頁面,將代碼 git clone 到本地自行編譯打包,參考此文檔。推薦使用源碼構(gòu)建方式以獲取最新版本
-
啟動 Server,進入解壓后文件夾或編譯打包好的文件夾,找到如下相對文件夾 nacos/bin,并對照操作系統(tǒng)實際情況之下如下命令。
- Linux/Unix/Mac 操作系統(tǒng),執(zhí)行命令
sh startup.sh -m standalone - Windows 操作系統(tǒng),執(zhí)行命令
cmd startup.cmd
- Linux/Unix/Mac 操作系統(tǒng),執(zhí)行命令
- 啟動應用
支持 IDE 直接啟動和編譯打包后啟動。
然后看看nacos控制臺,可以看到服務名稱為service-provider已經(jīng)注冊到了Nacos:
服務注冊截圖
服務消費者
分別使用 RestTemplate 和 FeignClient來消費服務。
- pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.pay.alibaba.nacos</groupId>
<artifactId>nacos-discovery-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>nacos-discovery-consumer</name>
<description>nacos-discovery-consumer project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 代碼
啟動類:
package com.pay.alibaba.nacos.nacosdiscovery.consumer;
import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.UrlCleaner;
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;
/**
* @ClassName: ConsumerApplication
* @Description: 啟動類
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/11 20:15
* @Copyright:
*/
@SpringBootApplication
@EnableDiscoveryClient(autoRegister = true)
@EnableFeignClients
public class ConsumerApplication {
@LoadBalanced
@Bean
@SentinelRestTemplate(urlCleanerClass = UrlCleaner.class, urlCleaner = "clean")
public RestTemplate restTemplate() {
return new RestTemplate();
}
@LoadBalanced
@Bean
@SentinelRestTemplate
public RestTemplate restTemplate1() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
說明:
添加 @LoadBlanced 注解,使得 RestTemplate 接入 Ribbon
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
EchoService
package com.pay.alibaba.nacos.nacosdiscovery.consumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @ClassName: EchoService
* @Description:
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/11 21:11
* @Copyright:
*/
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class,
configuration = FeignConfiguration.class)
public interface EchoService {
@GetMapping("/echo/{str}")
String echo(@PathVariable("str") String str);
@GetMapping("/divide")
String divide(@RequestParam("a") Integer a, @RequestParam("b") Integer b);
default String divide(Integer a) {
return divide(a, 0);
}
@GetMapping("/notFound")
String notFound();
}
class FeignConfiguration {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
}
class EchoServiceFallback implements EchoService {
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
@Override
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
return "divide fallback";
}
@Override
public String notFound() {
return "notFound fallback";
}
}
說明:
FeignClient 已經(jīng)默認集成了 Ribbon ,此處演示如何配置一個 FeignClient。使用 @FeignClient 注解將 EchoService 這個接口包裝成一個 FeignClient,屬性 name 對應服務名 service-provider。
@FeignClient(name = "service-provider")
public interface EchoService {
@GetMapping(value = "/echo/{str}")
String echo(@PathVariable("str") String str);
}
UrlCleaner
package com.pay.alibaba.nacos.nacosdiscovery.consumer.uitil;
/**
* @ClassName: UrlCleaner
* @Description:
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/11 20:18
* @Copyright:
*/
public class UrlCleaner {
public static String clean(String url) {
System.out.println("enter urlCleaner");
if (url.matches(".*/echo/.*")) {
System.out.println("change url");
url = url.replaceAll("/echo/.*", "/echo/{str}");
}
return url;
}
}
TestController
package com.pay.alibaba.nacos.nacosdiscovery.consumer.controller;
import com.pay.alibaba.nacos.nacosdiscovery.consumer.service.EchoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @ClassName: TestController
* @Description:
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/11 21:00
* @Copyright:
*/
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private RestTemplate restTemplate1;
@Autowired
private EchoService echoService;
@Autowired
private DiscoveryClient discoveryClient;
// @PostConstruct
// public void init() {
// restTemplate1.setErrorHandler(new ResponseErrorHandler() {
// @Override
// public boolean hasError(ClientHttpResponse response) throws IOException {
// return false;
// }
//
// @Override
// public void handleError(ClientHttpResponse response) throws IOException {
// System.err.println("handle error");
// }
// });
// }
@GetMapping("/echo-rest/{str}")
public String rest(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/echo/" + str,
String.class);
}
@GetMapping("/index")
public String index() {
return restTemplate1.getForObject("http://service-provider", String.class);
}
@GetMapping("/test")
public String test() {
return restTemplate1.getForObject("http://service-provider/test", String.class);
}
@GetMapping("/sleep")
public String sleep() {
return restTemplate1.getForObject("http://service-provider/sleep", String.class);
}
@GetMapping("/notFound-feign")
public String notFound() {
return echoService.notFound();
}
@GetMapping("/divide-feign")
public String divide(@RequestParam Integer a, @RequestParam Integer b) {
return echoService.divide(a, b);
}
@GetMapping("/divide-feign2")
public String divide(@RequestParam Integer a) {
return echoService.divide(a);
}
@GetMapping("/echo-feign/{str}")
public String feign(@PathVariable String str) {
return echoService.echo(str);
}
@GetMapping("/services/{service}")
public Object client(@PathVariable String service) {
return discoveryClient.getInstances(service);
}
@GetMapping("/services")
public Object services() {
return discoveryClient.getServices();
}
}
echo 方法上的 @RequestMapping 注解將 echo 方法與 URL "/echo/{str}" 相對應,@PathVariable 注解將 URL 路徑中的 {str} 對應成 echo 方法的參數(shù) str。
完成以上配置后,將兩者自動注入到 TestController 中。
@RestController
public class TestController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private EchoService echoService;
@GetMapping(value = "/echo-rest/{str}")
public String rest(@PathVariable String str) {
return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);
}
@GetMapping(value = "/echo-feign/{str}")
public String feign(@PathVariable String str) {
return echoService.echo(str);
}
}
配置必要的配置,在 nacos-discovery-consumer 項目的 /src/main/resources/application.properties 中添加基本配置信息
#################################### common config : ####################################
spring.application.name=nacos-discovery-consumer
# 應用服務web訪問端口
server.port=18080
# ActuatorWeb訪問端口
management.server.port=18081
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# spring cloud access&secret config
# 可以訪問如下地址查看: https://usercenter.console.aliyun.com/#/manage/ak
alibaba.cloud.access-key=****
alibaba.cloud.secret-key=****
#################################### nacosdiscovery config : ####################################
# 微服務引擎控制臺: https://mse.console.aliyun.com
# Nacos幫助文檔: https://nacos.io/zh-cn/docs/concepts.html
# Nacos認證信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服務發(fā)現(xiàn)與注冊配置,其中子屬性 server-addr 指定 Nacos 服務器主機和端口
spring.cloud.nacos.discovery.server-addr=localhost:8848
測試運行
啟動應用,支持 IDE 直接啟動和編譯打包后啟動。
- 啟動
provider項目
采用打包方式啟動2個服務,來驗證consumer請求provider的負載均衡。
java -jar nacos-discovery-provider-0.0.1-SNAPSHOT.jar --server.port=8080 --management.server.port=8081
java -jar nacos-discovery-provider-0.0.1-SNAPSHOT.jar --server.port=8090 --management.server.port=8091
查看Nacos控制臺,可以看到2個provider實例。
服務列表 - 啟動
consumer項目
采用IDEA直接啟動方式,啟動后如上圖同樣看到consumer被注冊進服務列表。
restTemplate方式訪問:http://localhost:18080/echo-rest/郭秀志,可以看到應用的第一個控制臺輸出:
restTemplate方式訪問:http://localhost:18080/echo-rest/loadbalance,可以看到應用的第二個控制臺輸出:

FeignClient方式瀏覽器訪問:http://localhost:18080/echo-feign/loadbalance,可以看到返回字符串:
繞口令式總結(jié):可見獨立的客戶端消費者應用通過負載均衡根據(jù)Nacos注冊的服務名稱(而不是ip+端口號)調(diào)用服務生效。




