📘 手搓 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 设计
🧠 配置中心的核心目标
配置中心要解决的问题是:
-
支持多环境/多应用配置隔离(命名空间 namespace)
-
支持多个配置项组织(dataId + group)
-
支持动态读取/发布配置
-
线程安全高并发访问
-
支持长轮询或事件通知(可扩展)
📦 最小功能版本的结构设计
✅ 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"

1846

被折叠的 条评论
为什么被折叠?



