分布式事务锁
分布式锁是一种控制分布式系统中并发访问共享资源的机制,它可以保证在同一时间只有一个进程对共享资源进行操作。
分布式锁是一种控制分布式系统中并发访问共享资源的机制,它可以保证在同一时间只有一个进程对共享资源进行操作。
雪花算法(Snowflake)是 Twitter 开源的分布式 ID 生成算法,核心目标是在分布式系统中生成全局唯一、趋势递增的 64 位整数 ID。它解决了传统 UUID 过长、数据库自增 ID 无法分布式部署的问题,广泛应用于微服务、分布式数据库、消息队列等场景。本文将从原理、特性、Go 实现三个维度带你掌握雪花算法。
雪花算法将 64 位整数(int64)拆分为 4 个部分(不同实现的分段可能略有差异,以下是最经典的版本):
| 位段 | 位数 | 作用 |
|---|---|---|
| 符号位 | 1 | 固定为 0(保证 ID 为正数) |
| 时间戳 | 41 | 存储毫秒级时间戳(相对于某个起始时间),可支持约 69 年(2^41/1000/3600/24/365 ≈ 69) |
| 机器/节点 ID | 10 | 标识分布式节点(可拆分为 5 位数据中心 + 5 位机器,支持 1024 个节点) |
| 序列号 | 12 | 同一毫秒内的自增序列(支持每个节点每毫秒生成 4096 个 ID) |
计算验证:1 + 41 + 10 + 12 = 64,刚好占满 int64 位空间。
以下是一个完整、健壮的雪花算法 Go 实现,包含时钟回拨防护、并发安全、可配置节点 ID 等特性:
package main
import (
"errors"
"fmt"
"sync"
"time"
)
// Snowflake 雪花算法生成器
type Snowflake struct {
mu sync.Mutex // 保证并发安全
epoch int64 // 起始时间戳(毫秒)
nodeID int64 // 节点ID
sequence int64 // 毫秒内序列号
lastStamp int64 // 上一次生成ID的时间戳(毫秒)
// 位段掩码与偏移量(根据64位结构定义)
nodeIDBits int64 = 10 // 节点ID位数
sequenceBits int64 = 12 // 序列号位数
nodeIDMax int64 = -1 ^ (-1 << nodeIDBits) // 节点ID最大值(1023)
sequenceMax int64 = -1 ^ (-1 << sequenceBits) // 序列号最大值(4095)
nodeIDShift int64 = sequenceBits // 节点ID偏移量
timeStampShift int64 = sequenceBits + nodeIDBits // 时间戳偏移量
}
// NewSnowflake 创建雪花算法实例
// epoch: 起始时间戳(毫秒),如 1704067200000(2024-01-01 00:00:00)
// nodeID: 节点ID(0-1023)
func NewSnowflake(epoch int64, nodeID int64) (*Snowflake, error) {
// 校验节点ID范围
if nodeID < 0 || nodeID > (-1 ^ (-1 << 10)) {
return nil, errors.New("node ID must be between 0 and 1023")
}
return &Snowflake{
epoch: epoch,
nodeID: nodeID,
sequence: 0,
lastStamp: -1,
}, nil
}
// 获取当前毫秒级时间戳
func (s *Snowflake) now() int64 {
return time.Now().UnixMilli()
}
// NextID 生成下一个唯一ID
func (s *Snowflake) NextID() (int64, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 1. 获取当前时间戳
now := s.now()
// 2. 处理时钟回拨
if now < s.lastStamp {
return 0, fmt.Errorf("clock moved backwards. Refusing to generate ID for %d milliseconds", s.lastStamp-now)
}
// 3. 同一毫秒内,序列号自增
if now == s.lastStamp {
s.sequence = (s.sequence + 1) & s.sequenceMax
// 序列号溢出(同一毫秒生成超过4096个ID)
if s.sequence == 0 {
// 等待到下一毫秒
for now <= s.lastStamp {
now = s.now()
}
}
} else {
// 不同毫秒,重置序列号
s.sequence = 0
}
// 4. 更新上一次时间戳
s.lastStamp = now
// 5. 组合64位ID
// 时间戳部分:(now - epoch) << 22
// 节点ID部分:nodeID << 12
// 序列号部分:sequence
id := ((now - s.epoch) << s.timeStampShift) | (s.nodeID << s.nodeIDShift) | s.sequence
return id, nil
}
// 测试示例
func main() {
// 自定义起始时间(2024-01-01 00:00:00)
epoch := int64(1704067200000)
// 节点ID(假设当前节点为1)
nodeID := int64(1)
// 创建雪花算法实例
sf, err := NewSnowflake(epoch, nodeID)
if err != nil {
panic(err)
}
// 并发生成10个ID测试
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
id, err := sf.NextID()
if err != nil {
fmt.Printf("生成ID失败:%v\n", err)
return
}
fmt.Printf("第%d个ID:%d\n", idx+1, id)
}(i)
}
wg.Wait()
}
mu:互斥锁,保证多 goroutine 下生成 ID 的并发安全;epoch:算法的起始时间戳(自定义,建议设置为项目上线时间),减少时间戳占用的位数;nodeID:节点唯一标识,需在分布式环境中提前分配(如 0-1023);lastStamp:上一次生成 ID 的时间戳,用于检测时钟回拨和同一毫秒的序列号递增;第1个ID:1234567890123456789
第2个ID:1234567890123456790
第3个ID:1234567890123456791
...
第10个ID:1234567890123456798
上述实现中,时钟回拨直接返回错误,实际生产中可优化为:
分布式环境中,节点 ID 需保证唯一,常见分配方式:
✅ 适合:分布式系统全局唯一 ID、订单号、日志 ID、消息 ID 等; ❌ 不适合:需要严格连续的 ID(雪花算法是趋势递增,非严格连续)、需要跨系统长期兼容的 ID(若修改 epoch 或节点 ID 规则会导致 ID 重复)。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 雪花算法 | 高性能、全局唯一、趋势递增 | 依赖时钟、节点 ID 需唯一、时钟回拨风险 |
| UUID | 无需配置、完全唯一 | 过长(128位)、无序、不利于索引 |
| 数据库自增 ID | 简单、严格递增 | 分布式部署困难、性能瓶颈 |
yarn upgrade-interactive --latest
需要手动选择升级的依赖包,按空格键选择,a 键切换所有,i 键反选选择
Go 语言 实现 AES、RSA、国密算法(SM2/SM4) 的完整可运行代码,包含加密、解密、签名、验签核心功能,注释详细,新手也能直接复制使用。
github.com/tjfoc/gmsm,国内主流);base64 编码,方便传输/存储。go get github.com/tjfoc/gmsm
AES 是对称加密(加密解密用同一密钥),推荐用 CBC 模式(比 ECB 更安全,需初始化向量 IV)。
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
)
// PKCS7 补码(AES 要求明文长度为 16 字节倍数)
func pkcs7Padding(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
// PKCS7 解补码
func pkcs7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, fmt.Errorf("加密数据为空")
}
// 取出最后一个字节的值,即为补码长度
padding := int(data[length-1])
return data[:length-padding], nil
}
// AESCBCEncrypt AES-CBC 加密
// key: 密钥(16/24/32 字节,对应 AES-128/AES-192/AES-256)
// iv: 初始化向量(必须 16 字节)
func AESCBCEncrypt(plainText, key, iv []byte) (string, error) {
// 1. 创建 AES 密码块
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// 2. 补码
plainText = pkcs7Padding(plainText, block.BlockSize())
// 3. 创建 CBC 模式
mode := cipher.NewCBCEncrypter(block, iv)
// 4. 加密
cipherText := make([]byte, len(plainText))
mode.CryptBlocks(cipherText, plainText)
// 5. base64 编码返回
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// AESCBCDecrypt AES-CBC 解密
func AESCBCDecrypt(cipherTextBase64 string, key, iv []byte) (string, error) {
// 1. base64 解码
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return "", err
}
// 2. 创建 AES 密码块
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// 3. 创建 CBC 解密模式
mode := cipher.NewCBCDecrypter(block, iv)
// 4. 解密
plainText := make([]byte, len(cipherText))
mode.CryptBlocks(plainText, cipherText)
// 5. 解补码
plainText, err = pkcs7UnPadding(plainText)
if err != nil {
return "", err
}
return string(plainText), nil
}
func main() {
// 测试 AES-CBC
plainText := []byte("Hello, AES Encryption!")
// 密钥(16 字节 = AES-128)
key := []byte("1234567890123456")
// IV(必须 16 字节)
iv := []byte("1234567890123456")
// 加密
cipherText, err := AESCBCEncrypt(plainText, key, iv)
if err != nil {
fmt.Printf("AES 加密失败:%v\n", err)
return
}
fmt.Printf("AES 加密结果:%s\n", cipherText)
// 解密
decryptedText, err := AESCBCDecrypt(cipherText, key, iv)
if err != nil {
fmt.Printf("AES 解密失败:%v\n", err)
return
}
fmt.Printf("AES 解密结果:%s\n", decryptedText)
}
RSA 是非对称加密(公钥加密、私钥解密;私钥签名、公钥验签),适合小数据加密(如密钥传输)。
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"os"
)
// GenerateRSAKey 生成 RSA 密钥对(2048 位)
func GenerateRSAKey() (pubKey []byte, priKey []byte, err error) {
// 1. 生成私钥
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
// 2. 编码私钥(PEM 格式)
privateBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateBlock := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateBytes,
}
priKey = pem.EncodeToMemory(privateBlock)
// 3. 提取公钥
publicKey := &privateKey.PublicKey
// 4. 编码公钥(PEM 格式)
publicBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, nil, err
}
publicBlock := &pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: publicBytes,
}
pubKey = pem.EncodeToMemory(publicBlock)
return pubKey, priKey, nil
}
// RSAEncrypt 公钥加密
func RSAEncrypt(plainText []byte, pubKey []byte) (string, error) {
// 1. 解析公钥
block, _ := pem.Decode(pubKey)
if block == nil {
return "", fmt.Errorf("公钥解析失败")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return "", err
}
publicKey, ok := pubInterface.(*rsa.PublicKey)
if !ok {
return "", fmt.Errorf("公钥类型错误")
}
// 2. 加密(OAEP 填充更安全,PKCS1v15 兼容老系统)
cipherText, err := rsa.EncryptOAEP(
rand.Reader,
publicKey,
nil,
plainText,
nil,
)
if err != nil {
return "", err
}
// 3. base64 编码
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// RSADecrypt 私钥解密
func RSADecrypt(cipherTextBase64 string, priKey []byte) (string, error) {
// 1. base64 解码
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return "", err
}
// 2. 解析私钥
block, _ := pem.Decode(priKey)
if block == nil {
return "", fmt.Errorf("私钥解析失败")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", err
}
// 3. 解密
plainText, err := rsa.DecryptOAEP(
rand.Reader,
privateKey,
nil,
cipherText,
nil,
)
if err != nil {
return "", err
}
return string(plainText), nil
}
func main() {
// 生成 RSA 密钥对
pubKey, priKey, err := GenerateRSAKey()
if err != nil {
fmt.Printf("生成 RSA 密钥失败:%v\n", err)
return
}
fmt.Printf("RSA 公钥:\n%s\n", string(pubKey))
fmt.Printf("RSA 私钥:\n%s\n", string(priKey))
// 测试加密解密
plainText := []byte("Hello, RSA Encryption!")
// 公钥加密
cipherText, err := RSAEncrypt(plainText, pubKey)
if err != nil {
fmt.Printf("RSA 加密失败:%v\n", err)
return
}
fmt.Printf("RSA 加密结果:%s\n", cipherText)
// 私钥解密
decryptedText, err := RSADecrypt(cipherText, priKey)
if err != nil {
fmt.Printf("RSA 解密失败:%v\n", err)
return
}
fmt.Printf("RSA 解密结果:%s\n", decryptedText)
}
国密算法是国家密码局制定的标准,包含:
package main
import (
"encoding/base64"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"github.com/tjfoc/gmsm/sm4"
"github.com/tjfoc/gmsm/x509"
)
// ===================== SM4 对称加密 =====================
// SM4ECBEncrypt SM4-ECB 加密(ECB 模式,无需 IV)
func SM4ECBEncrypt(plainText, key []byte) (string, error) {
// 1. 创建 SM4 密码块
block, err := sm4.NewCipher(key)
if err != nil {
return "", err
}
// 2. 补码(PKCS7,与 AES 通用)
plainText = pkcs7Padding(plainText, block.BlockSize())
// 3. 加密(ECB 模式)
cipherText := make([]byte, len(plainText))
for i := 0; i < len(plainText); i += block.BlockSize() {
block.Encrypt(cipherText[i:i+block.BlockSize()], plainText[i:i+block.BlockSize()])
}
// 4. base64 编码
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// SM4ECBDecrypt SM4-ECB 解密
func SM4ECBDecrypt(cipherTextBase64 string, key []byte) (string, error) {
// 1. base64 解码
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return "", err
}
// 2. 创建 SM4 密码块
block, err := sm4.NewCipher(key)
if err != nil {
return "", err
}
// 3. 解密
plainText := make([]byte, len(cipherText))
for i := 0; i < len(cipherText); i += block.BlockSize() {
block.Decrypt(plainText[i:i+block.BlockSize()], cipherText[i:i+block.BlockSize()])
}
// 4. 解补码
plainText, err = pkcs7UnPadding(plainText)
if err != nil {
return "", err
}
return string(plainText), nil
}
// ===================== SM2 非对称加密 =====================
// GenerateSM2Key 生成 SM2 密钥对
func GenerateSM2Key() (pubKey, priKey []byte, err error) {
// 1. 生成 SM2 私钥
privateKey, err := sm2.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
// 2. 编码私钥(PEM 格式)
privateBytes, err := x509.WritePrivateKeyToPem(privateKey, nil)
if err != nil {
return nil, nil, err
}
// 3. 编码公钥(PEM 格式)
publicBytes, err := x509.WritePublicKeyToPem(&privateKey.PublicKey)
if err != nil {
return nil, nil, err
}
return publicBytes, privateBytes, nil
}
// SM2Encrypt SM2 公钥加密
func SM2Encrypt(plainText []byte, pubKey []byte) (string, error) {
// 1. 解析公钥
publicKey, err := x509.ReadPublicKeyFromPem(pubKey)
if err != nil {
return "", err
}
// 2. 加密(SM2 推荐 C1C3C2 格式)
cipherText, err := sm2.Encrypt(publicKey, plainText, rand.Reader, sm2.C1C3C2)
if err != nil {
return "", err
}
// 3. base64 编码
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// SM2Decrypt SM2 私钥解密
func SM2Decrypt(cipherTextBase64 string, priKey []byte) (string, error) {
// 1. base64 解码
cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
if err != nil {
return "", err
}
// 2. 解析私钥
privateKey, err := x509.ReadPrivateKeyFromPem(priKey, nil)
if err != nil {
return "", err
}
// 3. 解密
plainText, err := sm2.Decrypt(privateKey, cipherText, sm2.C1C3C2)
if err != nil {
return "", err
}
return string(plainText), nil
}
func main() {
// ===================== 测试 SM4 =====================
sm4Key := []byte("1234567890123456") // SM4 密钥必须 16 字节
sm4PlainText := []byte("Hello, SM4 Encryption!")
// SM4 加密
sm4CipherText, err := SM4ECBEncrypt(sm4PlainText, sm4Key)
if err != nil {
fmt.Printf("SM4 加密失败:%v\n", err)
return
}
fmt.Printf("SM4 加密结果:%s\n", sm4CipherText)
// SM4 解密
sm4DecryptedText, err := SM4ECBDecrypt(sm4CipherText, sm4Key)
if err != nil {
fmt.Printf("SM4 解密失败:%v\n", err)
return
}
fmt.Printf("SM4 解密结果:%s\n", sm4DecryptedText)
// ===================== 测试 SM2 =====================
// 生成 SM2 密钥对
sm2PubKey, sm2PriKey, err := GenerateSM2Key()
if err != nil {
fmt.Printf("生成 SM2 密钥失败:%v\n", err)
return
}
sm2PlainText := []byte("Hello, SM2 Encryption!")
// SM2 加密
sm2CipherText, err := SM2Encrypt(sm2PlainText, sm2PubKey)
if err != nil {
fmt.Printf("SM2 加密失败:%v\n", err)
return
}
fmt.Printf("SM2 加密结果:%s\n", sm2CipherText)
// SM2 解密
sm2DecryptedText, err := SM2Decrypt(sm2CipherText, sm2PriKey)
if err != nil {
fmt.Printf("SM2 解密失败:%v\n", err)
return
}
fmt.Printf("SM2 解密结果:%s\n", sm2DecryptedText)
}
tjfoc/gmsm 是国内维护最活跃的国密库,支持 SM2/SM3/SM4/SM9。Hash 冲突解决方法开放定址法 这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式: Hi=(H(key)+di)% m i=1,2,…,n
再哈希法 这种方法是同时构造多个不同的哈希函数: Hi=RH1(key) i=1,2,…,k 当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间
链地址法 这种方法的基本思想是将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况
建立公共溢出区 这种方法的基本思想是:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表