本文目录导读:

在Java中实现服务发现,主要有两种主流方式:使用现成的注册中心(如Nacos、Consul、Eureka) 或 自行开发基于DNS或HTTP的简单方案。
下面我会从最常用的生产级方案(Spring Cloud + Nacos) 和手动实现的简易方案两个角度来给出案例。
生产级方案(Spring Cloud Alibaba + Nacos)
这是目前国内最主流的搭配,功能强大,自带控制台。
环境准备
- 启动Nacos Server:
下载Nacos(https://github.com/alibaba/nacos/releases),解压后运行
bin/startup.cmd -m standalone(单机模式)。 访问http://localhost:8848/nacos,默认账号密码nacos/nacos。
Maven依赖(服务提供方 + 消费方都需要)
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.13</version>
</parent>
<dependencies>
<!-- Spring Cloud Alibaba Nacos Discovery
注意版本对应关系 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.5.0</version>
</dependency>
<!-- Web依赖(用于提供/调用接口) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
服务提供方(Provider)
application.yml
server:
port: 8081
spring:
application:
name: user-service # 服务名,消费方通过这个名字调用
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos地址
启动类
@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现(新版本可省略,加上了更明确)
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
Controller (提供API)
@RestController
@RequestMapping(“/users”)
public class UserController {
@GetMapping(“/{id}”)
public String getUser(@PathVariable String id) {
return “User info for id: ” + id + “ from port: 8081”;
}
}
服务消费方(Consumer)
application.yml
server:
port: 8090
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
使用 RestTemplate + @LoadBalanced (负载均衡调用)
@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
// 注入一个具有负载均衡能力的RestTemplate
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Controller (调用UserService)
@RestController
@RequestMapping(“/order”)
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping(“/user/{userId}”)
public String getOrderUser(@PathVariable String userId) {
// 注意:这里用的是服务名 “user-service”,不是具体的IP和端口
String url = “http://user-service/users/” + userId;
String userInfo = restTemplate.getForObject(url, String.class);
return “Order info, user: ” + userInfo;
}
}
验证效果
- 启动 Provider(8081),启动 Consumer(8090)。
- 访问
http://localhost:8090/order/user/123,应返回Order info, user: User info for id: 123 from port: 8081。 - 打开 Nacos 控制台,可以看到
user-service和order-service都已经注册成功。
手动实现简易服务发现(基于HTTP + 内存注册表)
如果不依赖Spring Cloud,想理解核心原理,可以自己实现一个轻量版。
核心思想:
- 服务注册:启动时,服务方将自己的 IP:Port + 服务名 发送给注册中心。
- 服务发现:消费方从注册中心拉取服务列表,然后根据负载均衡策略选择一个IP调用。
服务注册中心代码(Registry Center)
使用Spring Boot + ConcurrentHashMap
@RestController
@RequestMapping(“/registry”)
@SpringBootApplication
public class RegistryCenterApplication {
// 内存注册表: Key=服务名, Value=IP:Port列表
private static final ConcurrentHashMap<String, List<String>> SERVICE_MAP = new ConcurrentHashMap<>();
public static void main(String[] args) {
SpringApplication.run(RegistryCenterApplication.class, args);
}
// 注册接口
@PostMapping(“/register”)
public String register(@RequestParam String serviceName, @RequestParam String address) {
SERVICE_MAP.computeIfAbsent(serviceName, k -> new CopyOnWriteArrayList<>()).add(address);
System.out.println(“Registered: ” + serviceName + “ -> ” + address);
return “success”;
}
// 服务发现接口(获取所有实例)
@GetMapping(“/discover/{serviceName}”)
public List<String> discover(@PathVariable String serviceName) {
return SERVICE_MAP.getOrDefault(serviceName, Collections.emptyList());
}
// 心跳检测/剔除(简易版略,生产需加定时任务判断节点是否存活)
}
application.yml (端口 8761)
server: port: 8761
服务提供方(UserService)
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
// 注册逻辑
@Bean
public CommandLineRunner registerToCenter(RestTemplate restTemplate) {
return args -> {
String serviceName = “user-service”;
String address = “localhost:8081”; // 本机地址
// 向注册中心注册
restTemplate.postForEntity(
“http://localhost:8761/registry/register?serviceName=” + serviceName + “&address=” + address,
null,
String.class
);
System.out.println(“Registered to registry center.”);
};
}
@RestController
public class UserApi {
@GetMapping(“/users/{id}”)
public String getUser(@PathVariable String id) {
return “User ” + id + “ from manual registry”;
}
}
}
服务消费方(OrderService)
@SpringBootApplication
public class OrderServiceApplication {
@Autowired
private RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
@RestController
public class OrderApi {
@GetMapping(“/invoke”)
public String invokeUserService() {
// 1. 从注册中心发现服务
List<String> instances = restTemplate.getForObject(
“http://localhost:8761/registry/discover/user-service”,
List.class
);
if (instances == null || instances.isEmpty()) {
return “No available service”;
}
// 2. 简单负载均衡:取第一个(生产应使用轮询/随机)
String target = instances.get(0);
// 3. 调用服务
String result = restTemplate.getForObject(“http://” + target + “/users/1”, String.class);
return “Result from discovered service: ” + result;
}
}
}
核心概念对比与总结
| 特性 | 方案一(Nacos/生产级) | 方案二(手动/教学级) |
|---|---|---|
| 注册中心稳定性 | 高,支持集群、持久化、CP+AP | 低,单机,内存存储 |
| 健康检查 | 自动心跳检测、剔除不健康节点 | 需自己实现 |
| 负载均衡 | 内置(Ribbon/LoadBalancer) | 需手动实现轮询/随机 |
| 配置管理 | Nacos自带配置中心,可动态刷新 | 无 |
| 使用场景 | 微服务生产环境 | 理解原理、小项目或学习 |
- 在实际企业Java项目中,强烈推荐使用 Spring Cloud Alibaba + Nacos 或 Spring Cloud + Consul,这些组件已经解决了高可用、健康检查、负载均衡、配置同步等复杂问题。
- 如果想快速理解服务发现的本质,可以自己用
HTTP+ConcurrentHashMap实现一遍流程,你会发现核心就是:一个集中存储服务地址的地方 + 一个用来获取地址的客户端。