本文目录导读:

我来详细介绍如何开发Java服务发现案例,包含服务注册、发现、健康检查等核心功能。
项目结构
service-discovery-demo/
├── service-registry-center # 注册中心
├── service-provider # 服务提供者
├── service-consumer # 服务消费者
└── common # 公共模块
基础依赖配置
1 Maven父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>service-discovery-demo</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.boot.version>2.7.14</spring.boot.version>
<spring.cloud.version>2021.0.8</spring.cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
自定义注册中心实现
1 注册中心服务端
// ServiceRegistryCenterApplication.java
package com.example.registry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@SpringBootApplication
@RestController
public class ServiceRegistryCenterApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRegistryCenterApplication.class, args);
}
}
// RegistryServerController.java
@RestController
@RequestMapping("/registry")
public class RegistryServerController {
// 服务注册表:serviceName -> List<ServiceInstance>
private final Map<String, Map<String, ServiceInstance>> registry = new ConcurrentHashMap<>();
// 服务注册
@PostMapping("/register")
public Result register(@RequestBody ServiceInstance instance) {
Map<String, ServiceInstance> instances = registry
.computeIfAbsent(instance.getServiceName(), k -> new ConcurrentHashMap<>());
String instanceId = instance.getHost() + ":" + instance.getPort();
instance.setInstanceId(instanceId);
instance.setRegisterTime(System.currentTimeMillis());
instance.setLastHeartbeat(System.currentTimeMillis());
instances.put(instanceId, instance);
return Result.success("注册成功", instance);
}
// 服务发现
@GetMapping("/discover/{serviceName}")
public Result discover(@PathVariable String serviceName) {
Map<String, ServiceInstance> instances = registry.get(serviceName);
if (instances == null || instances.isEmpty()) {
return Result.error("服务不存在");
}
// 过滤掉过期的实例(30秒无心跳)
long now = System.currentTimeMillis();
List<ServiceInstance> activeInstances = instances.values().stream()
.filter(instance -> now - instance.getLastHeartbeat() < 30000)
.collect(Collectors.toList());
if (activeInstances.isEmpty()) {
return Result.error("没有可用的服务实例");
}
// 简单的负载均衡:轮询
ServiceInstance selected = activeInstances.get(
(int)(System.currentTimeMillis() % activeInstances.size())
);
return Result.success("发现成功", selected);
}
// 心跳检测
@PutMapping("/heartbeat/{instanceId}")
public Result heartbeat(@PathVariable String instanceId) {
for (Map<String, ServiceInstance> instances : registry.values()) {
ServiceInstance instance = instances.get(instanceId);
if (instance != null) {
instance.setLastHeartbeat(System.currentTimeMillis());
return Result.success("心跳更新成功");
}
}
return Result.error("实例不存在");
}
// 服务下线
@DeleteMapping("/unregister/{instanceId}")
public Result unregister(@PathVariable String instanceId) {
for (Map<String, ServiceInstance> instances : registry.values()) {
if (instances.remove(instanceId) != null) {
return Result.success("下线成功");
}
}
return Result.error("实例不存在");
}
// 获取所有服务
@GetMapping("/services")
public Result getAllServices() {
Map<String, List<ServiceInstance>> result = new HashMap<>();
registry.forEach((serviceName, instances) -> {
List<ServiceInstance> activeList = instances.values().stream()
.filter(i -> System.currentTimeMillis() - i.getLastHeartbeat() < 30000)
.collect(Collectors.toList());
if (!activeList.isEmpty()) {
result.put(serviceName, activeList);
}
});
return Result.success("查询成功", result);
}
}
2 服务实例和结果对象
// ServiceInstance.java
package com.example.registry.model;
public class ServiceInstance {
private String instanceId;
private String serviceName;
private String host;
private int port;
private String ip;
private Map<String, String> metadata;
private long registerTime;
private long lastHeartbeat;
// getters and setters...
}
// Result.java
package com.example.registry.model;
public class Result<T> {
private int code;
private String message;
private T data;
public static <T> Result<T> success(String message, T data) {
Result<T> result = new Result<>();
result.setCode(200);
result.setMessage(message);
result.setData(data);
return result;
}
public static <T> Result<T> error(String message) {
Result<T> result = new Result<>();
result.setCode(500);
result.setMessage(message);
return result;
}
// getters and setters...
}
服务提供者实现
1 服务提供者客户端
// ServiceProviderApplication.java
package com.example.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class ServiceProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceProviderApplication.class, args);
}
}
// RegistryClient.java
@Component
public class RegistryClient {
@Value("${registry.server.url:http://localhost:8080}")
private String registryUrl;
@Value("${server.port}")
private int serverPort;
@Value("${spring.application.name}")
private String serviceName;
private String instanceId;
private final RestTemplate restTemplate;
public RegistryClient() {
this.restTemplate = new RestTemplate();
}
@PostConstruct
public void register() {
ServiceInstance instance = new ServiceInstance();
instance.setServiceName(serviceName);
instance.setHost(getLocalIp());
instance.setPort(serverPort);
try {
Result result = restTemplate.postForObject(
registryUrl + "/registry/register",
instance,
Result.class
);
if (result.getCode() == 200) {
ServiceInstance registered = (ServiceInstance) result.getData();
this.instanceId = registered.getInstanceId();
log.info("服务注册成功,instanceId: {}", instanceId);
}
} catch (Exception e) {
log.error("服务注册失败", e);
}
}
@PreDestroy
public void unregister() {
if (instanceId != null) {
try {
restTemplate.delete(registryUrl + "/registry/unregister/" + instanceId);
log.info("服务下线成功");
} catch (Exception e) {
log.error("服务下线失败", e);
}
}
}
// 定时心跳
@Scheduled(fixedRate = 10000)
public void sendHeartbeat() {
if (instanceId != null) {
try {
restTemplate.put(registryUrl + "/registry/heartbeat/" + instanceId, null);
} catch (Exception e) {
log.warn("心跳发送失败", e);
}
}
}
private String getLocalIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
return "127.0.0.1";
}
}
}
// UserServiceController.java
@RestController
@RequestMapping("/api/user")
public class UserServiceController {
@GetMapping("/info")
public Result getUserInfo(@RequestParam String userId) {
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("userId", userId);
userInfo.put("name", "张三");
userInfo.put("email", "zhangsan@example.com");
userInfo.put("servicePort", serverPort); // 添加端口信息便于验证
return Result.success("查询成功", userInfo);
}
}
服务消费者实现
1 服务消费者客户端
// ServiceConsumerApplication.java
package com.example.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
public class ServiceConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceConsumerApplication.class, args);
}
}
// ServiceDiscoveryClient.java
@Component
public class ServiceDiscoveryClient {
@Value("${registry.server.url:http://localhost:8080}")
private String registryUrl;
private final RestTemplate restTemplate;
public ServiceDiscoveryClient() {
this.restTemplate = new RestTemplate();
}
public ServiceInstance discoverService(String serviceName) {
try {
ResponseEntity<Result> response = restTemplate.getForEntity(
registryUrl + "/registry/discover/" + serviceName,
Result.class
);
Result result = response.getBody();
if (result != null && result.getCode() == 200) {
Map<String, Object> data = (Map<String, Object>) result.getData();
ServiceInstance instance = new ServiceInstance();
instance.setHost((String) data.get("host"));
instance.setPort((Integer) data.get("port"));
return instance;
}
} catch (Exception e) {
log.error("服务发现失败", e);
}
return null;
}
public String getServiceUrl(String serviceName, String path) {
ServiceInstance instance = discoverService(serviceName);
if (instance != null) {
return String.format("http://%s:%d%s",
instance.getHost(), instance.getPort(), path);
}
return null;
}
}
// UserConsumerController.java
@RestController
@RequestMapping("/api/consumer")
public class UserConsumerController {
@Autowired
private ServiceDiscoveryClient discoveryClient;
@GetMapping("/user/{userId}")
public Result getUserInfo(@PathVariable String userId) {
// 服务发现并调用
ServiceInstance instance = discoveryClient.discoverService("user-service");
if (instance == null) {
return Result.error("服务不可用");
}
String url = String.format("http://%s:%d/api/user/info?userId=%s",
instance.getHost(), instance.getPort(), userId);
try {
RestTemplate restTemplate = new RestTemplate();
Result result = restTemplate.getForObject(url, Result.class);
return result;
} catch (Exception e) {
return Result.error("服务调用失败: " + e.getMessage());
}
}
// 显示所有可用服务
@GetMapping("/services")
public Result getAllServices() {
String registryInfo = discoveryClient.getServiceUrl(
"registry-center", "/registry/services");
// 直接调用注册中心的API
RestTemplate restTemplate = new RestTemplate();
try {
return restTemplate.getForObject(
"http://localhost:8080/registry/services", Result.class);
} catch (Exception e) {
return Result.error("获取服务列表失败");
}
}
}
使用Nacos实现(生产推荐)
1 Nacos服务提供者
<!-- Nacos依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
# application.yml
spring:
application:
name: nacos-provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: public
group: DEFAULT_GROUP
server:
port: 8081
@SpringBootApplication
@EnableDiscoveryClient
public class NacosProviderApplication {
public static void main(String[] args) {
SpringApplication.run(NacosProviderApplication.class, args);
}
}
@RestController
public class ProviderController {
@Value("${server.port}")
private int port;
@GetMapping("/hello")
public String hello(@RequestParam String name) {
return String.format("Hello %s, from port %d", name, port);
}
}
2 Nacos服务消费者
# application.yml
spring:
application:
name: nacos-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
server:
port: 8082
@SpringBootApplication
@EnableDiscoveryClient
public class NacosConsumerApplication {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(NacosConsumerApplication.class, args);
}
}
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/consumer")
public String consume() {
// 使用负载均衡调用服务
String result = restTemplate.getForObject(
"http://nacos-provider/hello?name=World",
String.class
);
return "Consumer received: " + result;
}
@GetMapping("/services")
public List<String> getServices() {
// 获取所有服务
return discoveryClient.getServices();
}
@GetMapping("/instances/{serviceName}")
public List<ServiceInstance> getInstances(
@PathVariable String serviceName) {
// 获取服务的所有实例
return discoveryClient.getInstances(serviceName);
}
}
配置文件
1 自定义注册中心配置
# application-registry.properties server.port=8080 spring.application.name=registry-center
2 服务提供者配置
# application-provider.properties server.port=8081 spring.application.name=user-service registry.server.url=http://localhost:8080
3 服务消费者配置
# application-consumer.properties server.port=8082 spring.application.name=consumer-service registry.server.url=http://localhost:8080
测试和运行
1 启动顺序
# 1. 启动注册中心 mvn spring-boot:run -pl service-registry-center # 2. 启动服务提供者(可以启动多个实例) mvn spring-boot:run -pl service-provider -Dserver.port=8081 mvn spring-boot:run -pl service-provider -Dserver.port=8083 # 3. 启动服务消费者 mvn spring-boot:run -pl service-consumer -Dserver.port=8082
2 测试API
# 1. 查看注册的服务 curl http://localhost:8080/registry/services # 2. 服务发现 curl http://localhost:8080/registry/discover/user-service # 3. 消费者调用 curl http://localhost:8082/api/consumer/user/123 # 4. 查看消费者发现的服务 curl http://localhost:8082/api/consumer/services
高级特性
1 健康检查增强
@Component
public class HealthCheckEndpoint extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
// 检查数据库连接
// 检查外部依赖
// 检查资源使用情况
if (isHealthy()) {
builder.up().withDetail("status", "Service is healthy");
} else {
builder.down().withDetail("error", "Service is unhealthy");
}
}
private boolean isHealthy() {
// 实现健康检查逻辑
return true;
}
}
2 负载均衡策略
@Component
public class CustomLoadBalancer implements LoadBalancer {
private final AtomicInteger position = new AtomicInteger(0);
@Override
public ServiceInstance choose(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return null;
}
// 加权随机
int totalWeight = instances.stream()
.mapToInt(this::getWeight)
.sum();
int random = new Random().nextInt(totalWeight);
int currentWeight = 0;
for (ServiceInstance instance : instances) {
currentWeight += getWeight(instance);
if (random < currentWeight) {
return instance;
}
}
// 默认轮询
int pos = Math.abs(position.incrementAndGet());
return instances.get(pos % instances.size());
}
private int getWeight(ServiceInstance instance) {
// 根据实例的配置或性能指标获取权重
return instance.getMetadata().getOrDefault("weight", "1");
}
}
最佳实践
-
服务命名规范:使用统一的命名格式,如
{业务域}-{服务名} -
健康检查:实现详细的自定义健康检查逻辑
-
熔断降级:结合Hystrix或Sentinel实现熔断
-
配置中心:使用Apollo或Nacos Config管理配置
-
监控告警:集成Prometheus和Grafana监控
-
安全防护:注册中心添加认证和授权机制
-
多环境支持:通过Namespace区分开发、测试、生产
这个案例涵盖了服务发现的核心功能,可以根据实际需求进行扩展和优化。