作者: whooyun发表于: 2025-10-29 18:14
核心思想:
在数据库中新增一个表,用来存储dataCenterId和workId,dataCenterId和workId需要在web应用启动时就进行生成,并插入到数据库表中,并通过定时任务自动包活
表设计:
CREATE TABLE worker_node (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
host_name VARCHAR(100) NOT NULL, -- 主机名(可选)
port INT NOT NULL, -- 端口(可选)
ip VARCHAR(50) NOT NULL, -- IP 地址(可选)
status TINYINT DEFAULT 1, -- 1=活跃, 0=过期
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
modified_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- 关键:唯一索引确保 (datacenter_id, worker_id) 全局唯一
UNIQUE KEY uk_datacenter_worker (datacenter_id, worker_id),
datacenter_id INT NOT NULL DEFAULT 1,
worker_id INT NOT NULL -- 实际分配的 workerId (0~31)
);
mybatis-flex的实现
@Component
public class SnowflakeWorkerIdInitializer {
@Autowired
private WorkerNodeMapper workerNodeMapper; // MyBatis-Flex Mapper
@PostConstruct
public void initSnowflakeGenerator() {
// 1. 生成当前实例唯一标识(如 UUID + 时间戳)
String instanceId = UUID.randomUUID().toString();
String ip = getLocalIp();
String hostname = getHostname();
int port = 8080; // 或从配置读取
// 2. 尝试插入新记录,由数据库分配 workerId
int maxRetries = 32; // workerId 范围 0~31
for (int i = 0; i < maxRetries; i++) {
try {
// 插入时尝试分配 workerId = i
WorkerNode node = new WorkerNode();
node.setHostName(hostname);
node.setPort(port);
node.setIp(ip);
node.setDatacenterId(1);
node.setWorkerId(i);
workerNodeMapper.insert(node); // 成功插入 = 分配成功
// 3. 设置全局雪花生成器
SnowflakeKeyConfig config = new SnowflakeKeyConfig();
config.setDatacenterId(1L);
config.setWorkerId((long) i);
FlexGlobalConfiguration.getDefaultConfig()
.setSnowflakeKeyGenerator(new FlexSnowflakeKeyGenerator(config));
log.info("成功分配 workerId: {}, instance: {}", i, instanceId);
return;
} catch (Exception e) {
if (e.getMessage().contains("Duplicate entry") ||
e instanceof DuplicateKeyException) {
// workerId=i 已被占用,继续尝试 i+1
continue;
} else {
throw new RuntimeException("注册 workerId 失败", e);
}
}
}
throw new IllegalStateException("无法分配可用的 workerId(0~31 已耗尽)");
}
private String getLocalIp() {
try {
return InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
return "127.0.0.1";
}
}
private String getHostname() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (Exception e) {
return "unknown";
}
}
}