SpringCloud标准版的服务注册可以用
eureka、consul或zookeeper
,配置中心用config或bus
,现在Spring Cloud Alibaba
用Nacos
搞定这些,简言之就是注册中心加配置中心。Nacos可以代替eureka做服务注册中心。
Gitee上的Spring-Cloud-Alibaba/nacos-discovery-example
Nacos是什么
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos 的关键特性包括:
-
服务发现和服务健康监测
Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。
Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。
-
动态配置服务
动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。
配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。
-
动态 DNS 服务
动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。
Nacos 提供了一些简单的 DNS APIs (暂未实现) 帮助您管理服务的关联域名和可用的 IP:PORT 列表.
-
服务及其元数据管理
Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。
前提
您需要先下载 Nacos 并启动 Nacos server。操作步骤参见 Nacos 快速入门。我使用的是windows单机版,进入bin目录下执行以下命令启动nacos-startup.bat
。
管理页面:访问
ip:8848/nacos
,看到如下画面就启动成功了,默认账号密码都是nacos。服务注册与发现
本项目演示如何使用 Nacos Discovery Starter 完成 Spring Cloud 应用的服务注册与发现。
服务提供者
- 新建基于
Nacos
的服务提供者
新建module
,Spring boot的脚手架starter使用阿里云https://start.aliyun.com/
。
-
Dependiencies
选择如下图所示:
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,发现他使用的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
注解开启服务注册与发现功能。
- 启动 Nacos Server
-
首先需要获取 Nacos Server,支持直接下载和源码构建两种方式。
- 直接下载:Nacos Server 下载页
- 源码构建:进入 Nacos Github 项目页面,将代码 git clone 到本地自行编译打包,参考此文档。推荐使用源码构建方式以获取最新版本
-
启动 Server,进入解压后文件夹或编译打包好的文件夹,找到如下相对文件夹 nacos/bin,并对照操作系统实际情况之下如下命令。
- Linux/Unix/Mac 操作系统,执行命令
sh startup.sh -m standalone
- Windows 操作系统,执行命令
cmd startup.cmd
- Linux/Unix/Mac 操作系统,执行命令
- 启动应用
支持 IDE 直接启动和编译打包后启动。
然后看看nacos控制台,可以看到服务名称为service-provider
已经注册到了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 已经默认集成了 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 方法的参数 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 服务发现与注册配置,其中子属性 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
,可以看到返回字符串:绕口令式总结:可见独立的客户端消费者应用通过负载均衡根据Nacos注册的服务名称(而不是ip+端口号)调用服务生效。