一、分布式系統
分布式系統是指將多個獨立的計算節點通過網絡連接,協同完成同一目標的系統架構。其核心特征是:
- 多個獨立節點:每個節點都是一個可獨立運行的服務實例
- 網絡通信:節點間通過網絡協議(如HTTP、RPC)交換數據
- 協同工作:共同完成統一的業務目標(如處理請求、存儲數據)
二、分布式環境下 Spring Boot 項目的部署方法
(一) Docker多節點部署
- 實現方式:將同一個Spring Boot JAR包構建為Docker鏡像,然后在多個服務器(物理機/虛擬機)上啟動多個容器實例。
- 示例場景:
服務器A:運行Docker容器實例1(端口8081) 服務器B:運行Docker容器實例2(端口8082) 服務器C:運行Docker容器實例3(端口8083)
- 關鍵配置:
- 每個容器需分配唯一的雪花算法參數(
dataCenterId
和machineId
) - 通過Nginx等負載均衡器將請求分發到不同容器
- 示例雪花算法配置(動態獲取容器ID):
// 從Docker環境變量獲取節點標識(需在docker run時通過-e參數傳入) long dataCenterId = Long.parseLong(System.getenv("DATACENTER_ID")); long machineId = Long.parseLong(System.getenv("MACHINE_ID"));
- 每個容器需分配唯一的雪花算法參數(
(二)微服務架構下的多服務節點
- 場景:一個大型系統拆分為多個獨立服務,每個服務部署為分布式節點
- 用戶服務(user-service):部署3個節點 - 訂單服務(order-service):部署5個節點 - 商品服務(product-service):部署2個節點
- 雪花算法應用:
- 每個服務集群分配獨立的
dataCenterId
(如用戶服務為1,訂單服務為2) - 每個服務節點分配唯一的
machineId
(如節點1、節點2)
- 每個服務集群分配獨立的
三、分布式環境下雪花算法的使用問題
(一) 節點ID分配問題
- 挑戰:如何確保分布式節點的
dataCenterId
和machineId
唯一 - 解決方案:
- 配置文件靜態分配:適用于節點固定的場景(如手動編輯
application.yml
)snowflake:data-center-id: 1 # 數據中心ID(0-31)machine-id: 2 # 機器ID(0-31)
- 分布式協調服務動態分配:適用于動態擴縮容場景(如ZooKeeper)
// 通過ZooKeeper獲取唯一節點ID String nodePath = zkClient.createEphemeralSequential("/snowflake/nodes/", "node-"); long machineId = Long.parseLong(nodePath.substring(nodePath.lastIndexOf("-") + 1));
- 配置文件靜態分配:適用于節點固定的場景(如手動編輯
(二)時鐘回撥問題
- 場景:某節點因硬件故障或時區調整導致系統時間回退
- 解決方案:
- 雪花算法已內置時鐘回撥處理(如示例中的
waitNextMillis
方法) - 更嚴格的方案:結合Redis等分布式鎖,在時鐘回撥時阻塞生成ID
明白了!我將修改雪花算法的實現,使其適應你描述的分布式用戶服務場景。關鍵是要為每個節點分配唯一的dataCenterId
和machineId
。
- 雪花算法已內置時鐘回撥處理(如示例中的
四、示例:雪花算法實現
SnowflakeIdGenerator
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** 分布式環境下的雪花算法ID生成器* 支持從配置文件或環境變量讀取dataCenterId和machineId*/
@Component
public class SnowflakeIdGenerator {// 起始時間戳 (2020-01-01)private final long startTimeStamp = 1577836800000L;// 各部分占用的位數private final long dataCenterIdBits = 5L;private final long machineIdBits = 5L;private final long sequenceBits = 12L;// 各部分的最大值private final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); // 31private final long maxMachineId = -1L ^ (-1L << machineIdBits); // 31private final long maxSequence = -1L ^ (-1L << sequenceBits); // 4095// 各部分向左的位移private final long machineIdShift = sequenceBits;private final long dataCenterIdShift = sequenceBits + machineIdBits;private final long timestampShift = sequenceBits + machineIdBits + dataCenterIdBits;private final long dataCenterId;private final long machineId;private long sequence = 0L;private long lastTimestamp = -1L;/*** 構造函數,從配置中讀取dataCenterId和machineId*/public SnowflakeIdGenerator(@Value("${snowflake.datacenter-id:1}") long dataCenterId,@Value("${snowflake.machine-id:1}") long machineId) {// 檢查ID是否合法if (dataCenterId > maxDataCenterId || dataCenterId < 0) {throw new IllegalArgumentException("DataCenter ID must be between 0 and " + maxDataCenterId);}if (machineId > maxMachineId || machineId < 0) {throw new IllegalArgumentException("Machine ID must be between 0 and " + maxMachineId);}this.dataCenterId = dataCenterId;this.machineId = machineId;System.out.printf("初始化雪花算法ID生成器: datacenterId=%d, machineId=%d%n", dataCenterId, machineId);}// 生成ID的核心方法保持不變public synchronized long nextId() {long currentTimestamp = System.currentTimeMillis();// 處理時鐘回撥if (currentTimestamp < lastTimestamp) {throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - currentTimestamp) + " milliseconds");}if (currentTimestamp == lastTimestamp) {sequence = (sequence + 1) & maxSequence;if (sequence == 0) {// 當前毫秒內序列號已用完,等待下一毫秒currentTimestamp = waitNextMillis(lastTimestamp);}} else {// 不同毫秒,重置序列號sequence = 0L;}lastTimestamp = currentTimestamp;// 按位或組合生成IDreturn ((currentTimestamp - startTimeStamp) << timestampShift) |(dataCenterId << dataCenterIdShift) |(machineId << machineIdShift) |sequence;}private long waitNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;}
}
User
@Data
public class User {private Long id;private String username;private String createTime;private String updateTime;
}
UserController
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@PostMappingpublic User createUser(@RequestBody User user) {return userService.createUser(user);}@GetMapping("/{id}")public User getUser(@PathVariable Long id) {return userService.getUserById(id);}
}
UserService
public interface UserService {/*** 創建用戶* @param user 用戶信息* @return 創建后的用戶對象(包含生成的ID)*/User createUser(User user);/*** 根據ID獲取用戶* @param id 用戶ID* @return 用戶對象*/User getUserById(Long id);
}
UserServiceImpl
createUser方法:
- 調用idGenerator.nextId()生成全局唯一 ID
- 設置用戶的創建時間和更新時間
- 調用 MyBatis 的 Mapper方法將用戶信息插入數據庫
確保了在分布式環境下,即使多個用戶服務節點同時處理創建用戶請求,生成的 ID 也不會沖突。
@Service
@Transactional
public class UserServiceImpl implements UserService {@Autowiredprivate SnowflakeIdGenerator idGenerator;@Autowiredprivate UserMapper userMapper;@Overridepublic User createUser(User user) {// 生成全局唯一IDlong userId = idGenerator.nextId();user.setId(userId);// 設置創建時間和更新時間LocalDateTime now = LocalDateTime.now();user.setCreateTime(now);user.setUpdateTime(now);// 插入數據庫userMapper.insert(user);return user;}@Overridepublic User getUserById(Long id) {return userMapper.selectById(id);}
}
配置文件
application-node1.yml
server:port: 8081spring:datasource:url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=UTCusername: rootpassword: yourpassworddriver-class-name: com.mysql.cj.jdbc.Driver# 雪花算法配置
snowflake:datacenter-id: 1 # 數據中心IDmachine-id: 1 # 機器ID (節點A)
application-node2.yml
server:port: 8082spring:datasource:url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=UTCusername: rootpassword: yourpassworddriver-class-name: com.mysql.cj.jdbc.Driver# 雪花算法配置
snowflake:datacenter-id: 1 # 數據中心IDmachine-id: 2 # 機器ID (節點B)
application-node3.yml
server:port: 8083spring:datasource:url: jdbc:mysql://localhost:3306/user_db?useSSL=false&serverTimezone=UTCusername: rootpassword: yourpassworddriver-class-name: com.mysql.cj.jdbc.Driver# 雪花算法配置
snowflake:datacenter-id: 1 # 數據中心IDmachine-id: 3 # 機器ID (節點C)