本文目录导读:

我来为你提供一个完整的Spring Cloud Nacos服务发现案例,包含服务提供者和服务消费者。
项目结构
nacos-discovery-demo
├── pom.xml (父POM)
├── user-service (服务提供者)
└── order-service (服务消费者)
父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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>nacos-discovery-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
</properties>
<modules>
<module>user-service</module>
<module>order-service</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
</project>
服务提供者 (user-service)
1 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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>nacos-discovery-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>user-service</artifactId>
<name>user-service</name>
<description>用户服务提供者</description>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Nacos Config (可选) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Actuator 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
2 主启动类
package com.example.userservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
System.out.println("用户服务启动成功!");
}
}
3 配置文件 application.yml
server:
port: 8081
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
# 命名空间ID(可选,默认public)
namespace: public
# 集群名称(可选)
# cluster-name: DEFAULT
# 权重(默认1.0)
weight: 1.0
# 是否注册服务(默认true)
register-enabled: true
# 是否心跳检测
heart-beat-interval: 5000
heart-beat-timeout: 15000
ip-delete-timeout: 30000
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
group: DEFAULT_GROUP
# 支持多个配置
shared-configs:
- data-id: common.yaml
group: DEFAULT_GROUP
refresh: true
# 日志配置
logging:
level:
com.alibaba.nacos: warn
com.example: debug
# 管理端点
management:
endpoints:
web:
exposure:
include: '*'
4 实体类
package com.example.userservice.entity;
public class User {
private Long id;
private String username;
private String email;
private Integer age;
public User() {}
public User(Long id, String username, String email, Integer age) {
this.id = id;
this.username = username;
this.email = email;
this.age = age;
}
// Getter和Setter方法
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}
5 Controller
package com.example.userservice.controller;
import com.example.userservice.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
@RequestMapping("/api/users")
@RefreshScope
public class UserController {
private static final Logger log = LoggerFactory.getLogger(UserController.class);
@Value("${server.port}")
private String serverPort;
@Value("${test.config:默认配置}") // 从Nacos配置中心读取
private String testConfig;
// 模拟数据库
private static final Map<Long, User> userMap = new ConcurrentHashMap<>();
static {
userMap.put(1L, new User(1L, "张三", "zhangsan@example.com", 25));
userMap.put(2L, new User(2L, "李四", "lisi@example.com", 28));
userMap.put(3L, new User(3L, "王五", "wangwu@example.com", 32));
}
@GetMapping
public List<User> getAllUsers() {
List<User> users = new ArrayList<>(userMap.values());
log.info("从端口 {} 获取用户列表,数量:{}", serverPort, users.size());
return users;
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
User user = userMap.get(id);
log.info("从端口 {} 获取用户:{}", serverPort, user);
return user;
}
@PostMapping
public User createUser(@RequestBody User user) {
user.setId(System.currentTimeMillis());
userMap.put(user.getId(), user);
log.info("从端口 {} 创建用户:{}", serverPort, user);
return user;
}
@GetMapping("/info")
public String getServerInfo() {
return "当前服务实例端口:" + serverPort + ",配置信息:" + testConfig;
}
}
5b 负载均衡测试接口
package com.example.userservice.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/health")
public class HealthController {
@Value("${server.port}")
private String serverPort;
@GetMapping
public String health() {
return "User Service instance on port " + serverPort + " is healthy!";
}
}
服务消费者 (order-service)
1 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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>nacos-discovery-demo</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>order-service</artifactId>
<name>order-service</name>
<description>订单服务消费者</description>
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- OpenFeign 服务调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- LoadBalancer 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Actuator 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
2 主启动类
package com.example.orderservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
System.out.println("订单服务启动成功!");
}
}
3 配置文件 application.yml
server:
port: 8082
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
register-enabled: true
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
loadbalancer:
nacos:
enabled: true # 使用Nacos负载均衡策略
# Ribbon负载均衡配置(可选)
ribbon:
nacos:
enabled: true
ConnectTimeout: 5000
ReadTimeout: 5000
# Feign配置
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic
logging:
level:
com.example.orderservice.client: debug
4 使用OpenFeign的定义
package com.example.orderservice.client;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(name = "user-service") // 通过服务名调用
public interface UserClient {
@GetMapping("/api/users/{id}")
Object getUserById(@PathVariable Long id);
@GetMapping("/api/users")
Object getAllUsers();
@GetMapping("/health")
String getHealth();
}
5 使用RestTemplate + @LoadBalanced
package com.example.orderservice.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 开启负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
6 Service层
package com.example.orderservice.service;
import com.example.orderservice.client.UserClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
@Service
public class OrderService {
@Autowired
private UserClient userClient;
@Autowired
private RestTemplate restTemplate;
/**
* 通过Feign调用服务
*/
public Object getUserByFeign(Long userId) {
return userClient.getUserById(userId);
}
/**
* 通过RestTemplate调用服务
*/
public Object getUserByRestTemplate(Long userId) {
String url = "http://user-service/api/users/" + userId;
return restTemplate.getForObject(url, Object.class);
}
/**
* 获取所有用户(Feign)
*/
public Object getAllUsersByFeign() {
return userClient.getAllUsers();
}
/**
* 获取所有用户(RestTemplate)
*/
public Object getAllUsersByRestTemplate() {
String url = "http://user-service/api/users";
return restTemplate.getForObject(url, Object.class);
}
/**
* 获取用户服务健康状态
*/
public String getUserServiceHealth() {
return userClient.getHealth();
}
}
7 Controller
package com.example.orderservice.controller;
import com.example.orderservice.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private static final Logger log = LoggerFactory.getLogger(OrderController.class);
@Autowired
private OrderService orderService;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/users/{userId}")
public Map<String, Object> getUserInfo(@PathVariable Long userId) {
Map<String, Object> result = new HashMap<>();
// 通过Feign调用
Object feignData = orderService.getUserByFeign(userId);
// 通过RestTemplate调用
Object restTemplateData = orderService.getUserByRestTemplate(userId);
result.put("feignData", feignData);
result.put("restTemplateData", restTemplateData);
return result;
}
@GetMapping("/users/all")
public Map<String, Object> getAllUsers() {
Map<String, Object> result = new HashMap<>();
result.put("feignAllUsers", orderService.getAllUsersByFeign());
result.put("restTemplateAllUsers", orderService.getAllUsersByRestTemplate());
return result;
}
@GetMapping("/discovery")
public List<ServiceInstance> discoverServices() {
return discoveryClient.getInstances("user-service");
}
@GetMapping("/health")
public String getUserServiceHealth() {
return orderService.getUserServiceHealth();
}
}
8 服务发现控制器
package com.example.orderservice.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/services")
public class ServiceDiscoveryController {
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping
public Map<String, Object> getAllServices() {
Map<String, Object> result = new HashMap<>();
List<String> services = discoveryClient.getServices();
result.put("services", services);
for (String service : services) {
List<ServiceInstance> instances = discoveryClient.getInstances(service);
List<Map<String, Object>> instanceInfo = new ArrayList<>();
for (ServiceInstance instance : instances) {
Map<String, Object> info = new HashMap<>();
info.put("serviceId", instance.getServiceId());
info.put("host", instance.getHost());
info.put("port", instance.getPort());
info.put("uri", instance.getUri());
info.put("instanceId", instance.getInstanceId());
info.put("metadata", instance.getMetadata());
instanceInfo.add(info);
}
result.put(service, instanceInfo);
}
return result;
}
@GetMapping("/{serviceName}")
public List<ServiceInstance> getServiceInstances(@PathVariable String serviceName) {
return discoveryClient.getInstances(serviceName);
}
}
启动和测试
1 环境准备
下载并启动Nacos Server
# Linux/Mac sh startup.sh -m standalone # Windows startup.cmd -m standalone
访问Nacos控制台
- 地址:http://127.0.0.1:8848/nacos
- 默认账号密码:nacos/nacos
2 启动服务
# 启动用户服务 cd user-service mvn spring-boot:run # 启动订单服务 cd order-service mvn spring-boot:run
3 测试API
# 1. 测试服务发现 curl http://localhost:8082/services # 2. 测试通过Feign调用用户服务 curl http://localhost:8082/api/orders/users/1 # 3. 测试通过RestTemplate调用用户服务 curl "http://localhost:8082/api/orders/users/all" # 4. 获取用户服务健康状态 curl http://localhost:8082/api/orders/health # 5. 直接调用用户服务 curl http://localhost:8081/api/users/1
4 负载均衡测试
启用多个user-service实例测试负载均衡:
# 启动多个用户服务实例(不同端口)
mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8083
mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8084
# 多次调用观察负载均衡效果
for i in {1..5}; do
curl http://localhost:8082/api/orders/health
echo ""
done
Nacos控制台配置(可选)
1 配置中心配置
在Nacos控制台创建数据ID:order-service.yaml
# 订单服务配置 order: timeout: 3000 auto-confirm: true # 自定义配置 test: config: 这是从Nacos配置中心读取的配置
2 配置项说明
# 服务发现配置
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos服务器地址
namespace: public # 命名空间
group: DEFAULT_GROUP # 分组
weight: 1.0 # 权重,范围0-10000
register-enabled: true # 是否注册
ip: 127.0.0.1 # 指定IP(可选)
port: 8081 # 指定端口(可选)
metadata: # 元数据
version: 1.0
region: cn-hangzhou
常见配置示例
1 集群环境配置
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848,127.0.0.2:8848,127.0.0.3:8848
namespace: prod-namespace
cluster-name: HANGZHOU
2 多环境配置
# bootstrap.yml
spring:
profiles:
active: dev
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
namespace: ${spring.profiles.active}
注意事项
- 版本兼容性:注意Spring Boot、Spring Cloud和Spring Cloud Alibaba的版本对应关系
- 服务命名:服务名只能使用小写字母、数字和连字符
- 防火墙配置:确保8848端口可以访问
- 集群环境:生产环境建议使用集群部署
- 安全性:可以配置Nacos的认证和权限控制
这个案例展示了Nacos服务发现的核心功能,包括服务注册、发现、调用和负载均衡,你可以根据实际需求进行扩展和调整。