Spring Cloud Microservices Tutorial: Implementing Nacos Service Registration & Discovery

This tutorial walks through setting up Nacos for service registration and discovery in a Spring Cloud microservice project, covering environment preparation, configuring user-service and order-service, verifying registration, demonstrating load balancing, and troubleshooting common issues.

Coder Trainee
Coder Trainee
Coder Trainee
Spring Cloud Microservices Tutorial: Implementing Nacos Service Registration & Discovery

Goal of This Session

The aim is to add the first core component of a microservice architecture—Nacos service registration and discovery—to the project skeleton built in the previous episode.

Why Service Registration & Discovery?

In a monolithic app a call is a simple local method invocation, e.g. userService.getUser(id). In a microservice architecture the caller must know the address of the target service, handle multiple instances, and detect service failures. Nacos solves these problems by providing:

Service registration : each service reports its address to Nacos on startup.

Service discovery : callers query Nacos to obtain the target address.

Health check : Nacos periodically verifies that a service instance is alive.

Load balancing : when multiple instances exist, Nacos distributes requests automatically.

Environment Preparation

Start Nacos with Docker Compose:

# Use Docker Compose to start Nacos
docker-compose up -d

The docker-compose.yml defines a single Nacos container in standalone mode and exposes ports 8848 (HTTP) and 9848 (gRPC, required by Nacos 2.x):

version: '3.8'
services:
  nacos:
    image: nacos/nacos-server:v2.3.2
    container_name: nacos-teaching
    environment:
      MODE: standalone
    ports:
      - "8848:8848"
      - "9848:9848" # gRPC port (required for newer versions)

Access the console at http://localhost:8848/nacos with username/password nacos/nacos.

user-service (User Service)

Core Configuration

server:
  port: 8081
spring:
  application:
    name: user-service # service name (important!)
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

Bootstrap Class

@SpringBootApplication
@EnableDiscoveryClient // Enable registration & discovery
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

REST Endpoint

@RestController
@RequestMapping("/api/user")
public class UserController {
    @Value("${server.port}")
    private String port;

    @GetMapping("/{id}")
    public Map<String, Object> getUser(@PathVariable Long id) {
        return Map.of(
            "id", id,
            "name", "用户-" + id,
            "from", "user-service:" + port
        );
    }
}

order-service (Order Service)

Core Configuration

server:
  port: 8082
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

Bootstrap Class (with Load‑Balanced WebClient)

@SpringBootApplication
@EnableDiscoveryClient
public class OrderServiceApplication {
    @Bean
    @LoadBalanced // Enable load balancing, allow service‑name calls
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

Order Controller (calls user‑service by name)

@RestController
@RequestMapping("/api/order")
public class OrderController {
    @Autowired
    private WebClient.Builder webClientBuilder;
    @Value("${server.port}")
    private String port;

    @GetMapping("/{id}")
    public Mono<Map<String, Object>> getOrder(@PathVariable Long id) {
        // Simulated order data
        Map<String, Object> order = Map.of(
            "orderId", id,
            "productName", "Spring Cloud 教学课程",
            "userId", 1L,
            "from", "order-service:" + port
        );
        // Call user-service using its service name
        return webClientBuilder.build()
            .get()
            .uri("http://user-service/api/user/1")
            .retrieve()
            .bodyToMono(Map.class)
            .map(user -> {
                Map<String, Object> result = new HashMap<>(order);
                result.put("userInfo", user);
                return result;
            });
    }
}

Verification Steps

1. Start Services

# Start Nacos
docker-compose up -d

# Start user-service
cd user-service
mvn spring-boot:run

# Start order-service in a new terminal
cd order-service
mvn spring-boot:run

2. Verify Registration

Open the Nacos console ( http://localhost:8848/nacos) and look under Service Management → Service List . You should see both user-service and order-service with a healthy instance count of 1.

3. Test Call

curl http://localhost:8082/api/order/1

Expected JSON response:

{
  "orderId":1,
  "productName":"Spring Cloud 教学课程",
  "userId":1,
  "from":"order-service:8082",
  "userInfo":{
    "id":1,
    "name":"用户-1",
    "from":"user-service:8081"
  }
}

Load‑Balancing Demonstration

Run Two Instances of user‑service

# Instance 1 (default 8081)
java -jar user-service/target/user-service.jar

# Instance 2 (different port)
java -jar user-service/target/user-service.jar --server.port=8085

Observe Load‑Balancing

Repeatedly call order-service and watch the userInfo.from field alternate between user-service:8081 and user-service:8085:

for i in {1..10}; do
  curl -s http://localhost:8082/api/order/1 | grep -o '"from":"[^"]*"'
 done
# Output will show alternating service instances

Common Issues

Issue 1 – Service Not Registered

Check the value of spring.cloud.nacos.discovery.server-addr in the configuration.

Ensure the Nacos container is running: docker ps | grep nacos.

Verify that port 9848 (gRPC) is exposed for Nacos 2.x.

Issue 2 – UnknownHostException When Calling

Cause: the @LoadBalanced annotation was missing on the WebClient.Builder (or RestTemplate) bean.

Fix: add @LoadBalanced to the bean definition.

Issue 3 – Nacos 2.x Requires gRPC Port

Cause: the required gRPC port 9848 was not mapped.

Fix: expose the port in docker-compose.yml (e.g., "9848:9848").

Code Retrieval

The complete code for this episode is packaged in the repository referenced in the first episode. The subdirectory 02 contains the source for this session.

Next Episode Preview

Spring Cloud Microservices Tutorial – Episode 3: OpenFeign Service Calls, covering RestTemplate/WebClient vs OpenFeign, Feign interface definition, timeout & retry configuration, and a unified interceptor for token propagation.

I'm 老 J, see you next time.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

microservicesload balancingservice discoverynacosspring-bootSpring Cloud
Coder Trainee
Written by

Coder Trainee

Experienced in Java and Python, we share and learn together. For submissions or collaborations, DM us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.