Java实现服务注册发现案例

wen java案例 2

本文目录导读:

Java实现服务注册发现案例

  1. 项目结构
  2. 父POM配置
  3. Eureka Server(注册中心)
  4. User Service(服务提供者)
  5. Order Service(服务消费者)
  6. 测试步骤
  7. 多实例测试
  8. 核心概念说明

我将为您提供一个完整的Java服务注册与发现案例,使用Spring Cloud Netflix Eureka作为注册中心。

项目结构

service-registry-demo
├── pom.xml (父POM)
├── eureka-server    (注册中心)
├── 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>service-registry-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <modules>
        <module>eureka-server</module>
        <module>user-service</module>
        <module>order-service</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
        <relativePath/>
    </parent>
    <properties>
        <java.version>11</java.version>
        <spring-cloud.version>2021.0.8</spring-cloud.version>
    </properties>
    <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>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

Eureka Server(注册中心)

eureka-server/pom.xml

<?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>service-registry-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>eureka-server</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

EurekaServerApplication.java

package com.example.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

application.yml

server:
  port: 8761
spring:
  application:
    name: eureka-server
eureka:
  instance:
    hostname: localhost
  client:
    # 注册中心本身不注册自己
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
  server:
    # 关闭自我保护模式(开发环境)
    enable-self-preservation: false
    eviction-interval-timer-in-ms: 5000

User Service(服务提供者)

user-service/pom.xml

<?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>service-registry-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>user-service</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
</project>

UserServiceApplication.java

package com.example.user;
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);
    }
}

UserController.java

package com.example.user.controller;
import com.example.user.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@RestController
@RequestMapping("/api/users")
public class UserController {
    @Value("${server.port}")
    private String serverPort;
    @Resource
    private DiscoveryClient discoveryClient;
    // 模拟数据库
    private static final Map<Long, User> USER_DB = new ConcurrentHashMap<>();
    static {
        USER_DB.put(1L, new User(1L, "张三", "zhangsan@example.com"));
        USER_DB.put(2L, new User(2L, "李四", "lisi@example.com"));
        USER_DB.put(3L, new User(3L, "王五", "wangwu@example.com"));
    }
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        log.info("从端口 {} 获取用户信息, id: {}", serverPort, id);
        User user = USER_DB.get(id);
        if (user != null) {
            user.setServicePort(serverPort);
        }
        return user;
    }
    @GetMapping("/list")
    public List<User> getAllUsers() {
        log.info("从端口 {} 获取所有用户", serverPort);
        return USER_DB.values().stream()
                .peek(user -> user.setServicePort(serverPort))
                .toList();
    }
    @PostMapping
    public User createUser(@RequestBody User user) {
        log.info("从端口 {} 创建用户: {}", serverPort, user);
        user.setId((long) (USER_DB.size() + 1));
        user.setServicePort(serverPort);
        USER_DB.put(user.getId(), user);
        return user;
    }
    /**
     * 获取服务发现信息
     */
    @GetMapping("/service-info")
    public Map<String, Object> getServiceInfo() {
        Map<String, Object> info = new HashMap<>();
        info.put("serviceName", "USER-SERVICE");
        info.put("port", serverPort);
        List<ServiceInstance> instances = discoveryClient.getInstances("USER-SERVICE");
        info.put("instances", instances.stream()
                .map(instance -> Map.of(
                    "serviceId", instance.getServiceId(),
                    "host", instance.getHost(),
                    "port", instance.getPort(),
                    "uri", instance.getUri().toString()
                ))
                .toList());
        return info;
    }
}

User.java

package com.example.user.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private String email;
    private String servicePort;
}

application.yml

server:
  port: 8081
spring:
  application:
    name: user-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    register-with-eureka: true
    fetch-registry: true
  instance:
    instance-id: user-service-${spring.cloud.client.ip-address}:${server.port}
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5    # 心跳时间
    lease-expiration-duration-in-seconds: 10 # 过期时间
management:
  endpoints:
    web:
      exposure:
        include: '*'

Order Service(服务消费者)

order-service/pom.xml

<?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>service-registry-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>order-service</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
    </dependencies>
</project>

OrderServiceApplication.java

package com.example.order;
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.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
@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();
    }
    /**
     * WebClient 负载均衡配置
     */
    @Bean
    @LoadBalanced
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

OrderController.java

package com.example.order.controller;
import com.example.order.model.Order;
import com.example.order.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    private static final Map<Long, Order> ORDER_DB = new ConcurrentHashMap<>();
    static {
        ORDER_DB.put(1L, new Order(1L, 101L, 200.50, "已支付"));
        ORDER_DB.put(2L, new Order(2L, 102L, 350.00, "待发货"));
        ORDER_DB.put(3L, new Order(3L, 103L, 150.75, "已完成"));
    }
    @Resource
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    @Qualifier("webClientBuilder")
    private WebClient.Builder webClientBuilder;
    /**
     * 获取订单详情(包含用户信息)
     * 使用 RestTemplate 进行服务调用
     */
    @GetMapping("/{id}/with-user")
    public Map<String, Object> getOrderWithUser(@PathVariable Long id) {
        Order order = ORDER_DB.get(id);
        if (order == null) {
            throw new RuntimeException("订单不存在: " + id);
        }
        log.info("获取订单: {},调用用户服务获取用户信息", order);
        // 通过服务名调用用户服务(负载均衡)
        String userServiceUrl = "http://USER-SERVICE/api/users/" + order.getUserId();
        User user = restTemplate.getForObject(userServiceUrl, User.class);
        Map<String, Object> result = new HashMap<>();
        result.put("order", order);
        result.put("user", user);
        return result;
    }
    /**
     * 使用 WebClient 进行响应式调用
     */
    @GetMapping("/{id}/with-user-reactive")
    public Map<String, Object> getOrderWithUserReactive(@PathVariable Long id) throws Exception {
        Order order = ORDER_DB.get(id);
        if (order == null) {
            throw new RuntimeException("订单不存在: " + id);
        }
        // 使用 WebClient 异步调用
        User user = webClientBuilder.build()
                .get()
                .uri("http://USER-SERVICE/api/users/" + order.getUserId())
                .retrieve()
                .bodyToMono(User.class)
                .block();
        Map<String, Object> result = new HashMap<>();
        result.put("order", order);
        result.put("user", user);
        return result;
    }
    /**
     * 发现并获取服务的所有实例
     */
    @GetMapping("/discover-service")
    public Map<String, Object> discoverService() {
        Map<String, Object> result = new HashMap<>();
        // 获取所有服务
        List<String> services = discoveryClient.getServices();
        result.put("services", services);
        // 获取 USER-SERVICE 的所有实例
        List<ServiceInstance> instances = discoveryClient.getInstances("USER-SERVICE");
        result.put("userServiceInstances", instances.stream()
                .map(instance -> Map.of(
                    "instanceId", instance.getInstanceId(),
                    "host", instance.getHost(),
                    "port", instance.getPort(),
                    "uri", instance.getUri().toString()
                ))
                .toList());
        return result;
    }
    /**
     * 负载均衡测试 - 多次调用用户服务
     */
    @GetMapping("/load-balance-test")
    public List<Map<String, Object>> loadBalanceTest(@RequestParam(defaultValue = "5") int count) {
        List<Map<String, Object>> results = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            User user = restTemplate.getForObject("http://USER-SERVICE/api/users/" + (i % 3 + 1), User.class);
            Map<String, Object> item = new HashMap<>();
            item.put("requestId", i + 1);
            item.put("user", user);
            item.put("calledPort", user.getServicePort());
            results.add(item);
        }
        return results;
    }
}

Order.java

package com.example.order.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    private Long id;
    private Long userId;
    private Double amount;
    private String status;
}

User.java (在order-service中定义)

package com.example.order.model;
import lombok.Data;
@Data
public class User {
    private Long id;
    private String name;
    private String email;
    private String servicePort;
}

application.yml

server:
  port: 8082
spring:
  application:
    name: order-service
  cloud:
    loadbalancer:
      ribbon:
        enabled: false
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    register-with-eureka: true
    fetch-registry: true
  instance:
    instance-id: order-service-${spring.cloud.client.ip-address}:${server.port}
    prefer-ip-address: true
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 10

测试步骤

1 启动服务

按照以下顺序启动服务:

# 1. 启动Eureka Server
cd eureka-server
mvn spring-boot:run
# 2. 启动User Service
cd user-service
mvn spring-boot:run
# 3. 启动Order Service
cd order-service
mvn spring-boot:run

2 验证服务注册

访问Eureka管理界面:http://localhost:8761

你应该能看到:

  • USER-SERVICE 注册了一个实例
  • ORDER-SERVICE 注册了一个实例

3 测试接口

# 1. 获取用户信息
curl http://localhost:8081/api/users/1
# 2. 获取订单及用户信息(使用RestTemplate)
curl http://localhost:8082/api/orders/1/with-user
# 3. 获取订单及用户信息(使用WebClient)
curl http://localhost:8082/api/orders/1/with-user-reactive
# 4. 查看服务发现信息
curl http://localhost:8082/api/orders/discover-service
# 5. 负载均衡测试
curl "http://localhost:8082/api/orders/load-balance-test?count=5"

多实例测试

要测试负载均衡,可以启动多个User Service实例:

# 启动第一个实例(默认8081端口)
mvn spring-boot:run
# 启动第二个实例(指定8083端口)
mvn spring-boot:run -Dspring-boot.run.arguments=--server.port=8083

核心概念说明

  1. 服务注册:服务启动时向Eureka Server注册自身信息
  2. 服务发现:服务消费者通过服务名从Eureka Server获取服务实例列表
  3. 心跳机制:服务每5秒向Eureka Server发送心跳保持连接
  4. 负载均衡:Spring Cloud LoadBalancer实现客户端负载均衡
  5. 服务下线:服务优雅退出时向Eureka Server发送注销请求

这个案例完整展示了Spring Cloud服务注册与发现的核心功能,可以作为微服务架构的基础模板使用。

抱歉,评论功能暂时关闭!