Nacos的手写笔记


📘 手搓 Nacos 实现笔记(简易版服务注册中心)


🧠 1. 设计目标与核心功能

Nacos(服务注册中心)的核心目标:

  • 允许服务 注册(Register)

  • 允许客户端进行 发现(Discover)

  • 支持服务 下线(Deregister)

  • 后续可扩展为:心跳保活、负载均衡、配置中心


🗂️ 2. 核心数据结构设计

Map<String, CopyOnWriteArrayList<ServiceInstance>> registry
  • key 是服务名(如:order-service

  • value 是该服务下的实例列表,使用线程安全的 CopyOnWriteArrayList

  • 保证并发读多写少场景下的性能与安全性


🧱 3. ServiceInstance 实体类

public class ServiceInstance {
    private String serviceName;
    private String ip;
    private int port;

    public String getAddress() {
        return ip + ":" + port;
    }

    // equals/hashCode 需重写,避免重复注册问题
}

🔧 4. ServiceRegistry 注册中心核心逻辑

@Component
public class ServiceRegistry {
    private final Map<String, CopyOnWriteArrayList<ServiceInstance>> registry = new ConcurrentHashMap<>();

    public void register(ServiceInstance instance) {
        registry.computeIfAbsent(instance.getServiceName(), k -> new CopyOnWriteArrayList<>())
                .addIfAbsent(instance);
        logRegistry("注册");
    }

    public void deregister(ServiceInstance instance) {
        List<ServiceInstance> list = registry.get(instance.getServiceName());
        if (list != null) {
            list.removeIf(i -> i.getIp().equals(instance.getIp()) && i.getPort() == instance.getPort());
        }
        logRegistry("注销");
    }

    public List<ServiceInstance> getService(String serviceName) {
        return registry.getOrDefault(serviceName, new CopyOnWriteArrayList<>());
    }

    private void logRegistry(String action) {
        System.out.println("[" + action + "] 当前注册表状态:" + registry);
    }
}
  • 使用 @Component 注入方式管理 ServiceRegistry

  • 日志用于实时监控容器状态


🌐 5. ServiceController 控制器

@RestController
@RequestMapping("/registry")
public class ServiceController {

    @Autowired
    private ServiceRegistry registry;

    @PostMapping("/register")
    public String register(@RequestBody ServiceInstance instance) {
        registry.register(instance);
        return "Registered: " + instance.getAddress();
    }

    @PostMapping("/deregister")
    public String deregister(@RequestBody ServiceInstance instance) {
        registry.deregister(instance);
        return "Deregistered: " + instance.getAddress();
    }

    @GetMapping("/discover/{serviceName}")
    public List<ServiceInstance> discover(@PathVariable String serviceName) {
        return registry.getService(serviceName);
    }
}

🧪 6. 单元测试(MockMvc)

@SpringBootTest
@AutoConfigureMockMvc
public class NacosCoreFunctionTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void testRegisterService() throws Exception {
        Map<String, Object> payload = Map.of(
            "serviceName", "order-service",
            "ip", "192.168.1.10",
            "port", 8081
        );

        mockMvc.perform(post("/registry/register")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(payload)))
                .andExpect(status().isOk());
    }

    @Test
    void testDiscoverService() throws Exception {
        testRegisterService(); // 确保先注册
        mockMvc.perform(get("/registry/discover/order-service"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$[0].ip").value("192.168.1.10"))
                .andExpect(jsonPath("$[0].port").value(8081));
    }
}

💡 7. 为什么要用 CopyOnWriteArrayList?

  • 高并发场景下,读操作远多于写操作

  • 写操作通过拷贝新数组,避免并发冲突

  • 适合服务发现中心这种“读多写少”的业务场景


🧩 8. 扩展点 —— 配置中心实现思路

你说得对,配置中心其实也只是换一套 Map

Map<String /* namespace */, Map<String /* dataId */, String /* content */>> configMap

可以设计以下接口:

  • 发布配置 /config/publish

  • 获取配置 /config/get

  • 删除配置 /config/remove

  • 长轮询监听配置(高级需求)


📌 总结

功能模块关键数据结构线程安全策略接口路径
注册中心Map<String, List<ServiceInstance>>CopyOnWriteArrayList/registry/register
服务发现同上同上/registry/discover/{name}
注销服务同上同上/registry/deregister
配置中心Map<String, Map<String, String>>ConcurrentHashMap/config/*(可扩展)

如果你愿意继续手搓,也可以加上:

  • 服务实例健康检查(心跳机制)

  • 实例过期剔除(定时任务)

  • 服务权重与路由策略

  • 配置中心通知机制(监听器 / LongPolling)


🧩 配置中心的核心 Map 设计

🧠 配置中心的核心目标

配置中心要解决的问题是:

  1. 支持多环境/多应用配置隔离(命名空间 namespace)

  2. 支持多个配置项组织(dataId + group)

  3. 支持动态读取/发布配置

  4. 线程安全高并发访问

  5. 支持长轮询或事件通知(可扩展)


📦 最小功能版本的结构设计

✅ Map 结构(一层命名空间 + 配置键值)

Map<String /* namespace */, Map<String /* dataId */, String /* configContent */>>

示例:

{
  "dev": {
    "application.yaml": "server.port=8080",
    "db.properties": "user=root\npassword=123456"
  },
  "prod": {
    "application.yaml": "server.port=80",
    "db.properties": "user=admin\npassword=prod-secret"
  }
}

好处:

  • namespace 表示环境(或租户、业务线)

  • dataId 表示一个配置文件名或唯一配置标识符

  • String 表示配置文本内容(YAML、Properties、JSON 都行)


🪄 扩展支持 group 的结构设计(推荐方式)

✅ Map 结构(二层命名空间 + 分组 + 配置项)

Map<String /* namespace */, Map<String /* group */, Map<String /* dataId */, String /* config */>>>

示例:

{
  "dev": {
    "DEFAULT_GROUP": {
      "application.yaml": "server.port=8080",
      "redis.yaml": "host=127.0.0.1"
    },
    "DATABASE_GROUP": {
      "db.properties": "user=root\npassword=123456"
    }
  },
  "prod": {
    "DEFAULT_GROUP": {
      "application.yaml": "server.port=80",
      "redis.yaml": "host=redis.prod"
    }
  }
}

查询流程:

用户传入 namespace + group + dataId → 定位到唯一配置项。


🛡️ 线程安全设计建议

使用组合线程安全容器:

ConcurrentHashMap<String, ConcurrentHashMap<String, ConcurrentHashMap<String, String>>>

或者统一封装读写逻辑,避免嵌套层直接暴露。


💡 相关 API 建议设计

动作方法路径描述
发布配置POST/config/publish参数:namespace, group, dataId, content
获取配置GET/config/get参数:namespace, group, dataId
删除配置DELETE/config/remove参数同上
获取某命名空间下所有配置GET/config/list?namespace=dev返回 Map

🧪 示例发布配置 JSON

{
  "namespace": "dev",
  "group": "DEFAULT_GROUP",
  "dataId": "application.yaml",
  "content": "server.port=8080"
}

📌 总结图示(结构)

namespace
  └── group
        └── dataId => config content

例如:

"dev"
  └── "DEFAULT_GROUP"
        └── "application.yaml" => "server.port=8080"

源码jingjiu/nacoshttps://gitee.com/jingjiu11/nacos

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值