OpenHarmony Go 环境适配方案,手把手实现 三方库snowflake 雪花算法 ID 生成
欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_go_cgo
本文讲解鸿蒙PC基于musl库、应用沙箱与二进制强制签名机制,不原生支持Go语言,通用Linux编译产物无法直接运行。需借助社区Harmonybrew包管理器搭建开发环境:纯Go开发安装go与ohos-sdk,依托SDK实现编译自动签名;CGO跨语言开发需额外安装llvm-gcc-compat补齐cc编译命令,编译时手动开启CGO参数。搭配CodeArts IDE可完成全流程开发,同时需提前处理软件冲突、使用原生终端规避环境报错。
搭建环境的话,可以参考以下文章:OpenHarmony 鸿蒙 PC + CodeArts IDE 实现 Go开发完整开发环境搭建指南
一、snowflake 库介绍
1. 库作用
github.com/bwmarrin/snowflake 是 Go 实现的雪花算法ID生成库,雪花ID是分布式全局唯一ID,多用于订单、用户、消息主键,优势:
- 全局不重复,分布式多服务并发无冲突;
- ID自带时间戳,可直接解析生成时间;
- 64位数字,排序有序,数据库索引性能好;
- 可配置机器ID、进程ID区分不同服务器/实例。
2. 安装命令
go get github.com/bwmarrin/snowflake

3. 无报错完整可运行代码
package main
import (
"fmt"
"sync"
"time"
"github.com/bwmarrin/snowflake"
)
// 全局雪花节点(项目开发推荐全局单例,只初始化一次)
var snowNode *snowflake.Node
var once sync.Once
// InitSnowflake 初始化雪花生成器,单例执行只创建一次
// machineId:机器ID,集群每台机器使用 0~1023 唯一数字
func InitSnowflake(machineId int64) error {
var err error
once.Do(func() {
// 创建雪花节点
snowNode, err = snowflake.NewNode(machineId)
})
return err
}
// GetSnowID 获取int64类型雪花ID(数据库主键存储用)
func GetSnowID() int64 {
return snowNode.Generate().Int64()
}
// GetSnowIDStr 获取字符串雪花ID(接口返回前端,避免大数精度丢失)
func GetSnowIDStr() string {
return snowNode.Generate().String()
}
// ParseIDInfo 解析雪花ID内部全部信息
func ParseIDInfo(id int64) {
snowID := snowflake.ParseInt64(id)
fmt.Println("===== 雪花ID解析详情 =====")
fmt.Printf("原始ID数值: %d\n", id)
ms := snowID.Time()
// 毫秒时间戳转time.Time
createTime := time.UnixMilli(ms)
fmt.Printf("生成时间戳(ms): %d\n", ms)
fmt.Printf("格式化创建时间: %s\n", createTime.Format("2006-01-02 15:04:05"))
fmt.Printf("机器编号(NodeID): %d\n", snowID.Node())
fmt.Printf("毫秒内自增序列号(Step): %d\n", snowID.Step())
fmt.Println("==========================")
}
// 并发测试函数:多协程批量生成ID,验证无重复
func concurrentTest(wg *sync.WaitGroup, ch chan<- int64) {
defer wg.Done()
for i := 0; i < 500; i++ {
id := GetSnowID()
ch <- id
}
}
func main() {
// 1. 初始化雪花生成器,机器ID设为1,分布式环境每台机器修改唯一值
err := InitSnowflake(1)
if err != nil {
fmt.Printf("雪花算法初始化失败,错误:%v\n", err)
return
}
fmt.Println("雪花ID生成器初始化完成!\n")
// 2. 基础用法:生成两种格式ID
idNum := GetSnowID()
idStr := GetSnowIDStr()
fmt.Printf("数字型雪花ID(存数据库): %d\n", idNum)
fmt.Printf("字符串雪花ID(接口返回): %s\n\n", idStr)
// 3. 解析ID内置时间、机器、序列号信息
ParseIDInfo(idNum)
// 4. 单循环批量生成ID
fmt.Println("单次循环批量生成10个ID:")
for i := 0; i < 10; i++ {
fmt.Printf("%d ", GetSnowID())
}
fmt.Println("\n")
// 5. 高并发多协程测试,校验ID全局唯一
const goroutineNum = 10
const totalCount = goroutineNum * 500
idChan := make(chan int64, totalCount)
var wg sync.WaitGroup
fmt.Printf("开启%d个协程,总共生成%d个ID,并发唯一性测试\n", goroutineNum, totalCount)
for i := 0; i < goroutineNum; i++ {
wg.Add(1)
go concurrentTest(&wg, idChan)
}
wg.Wait()
close(idChan)
// 去重校验
idMap := make(map[int64]bool)
duplicateFlag := false
for val := range idChan {
if idMap[val] {
fmt.Printf("发现重复ID:%d\n", val)
duplicateFlag = true
}
idMap[val] = true
}
if !duplicateFlag {
fmt.Printf("并发校验完成:%d 条ID全部唯一,无重复!\n", totalCount)
}
// 6. 业务模拟:生成订单ID、消息ID
orderId := GetSnowIDStr()
msgId := GetSnowID()
fmt.Printf("\n模拟业务订单ID:%s\n", orderId)
fmt.Printf("模拟消息主键ID:%d\n", msgId)
}

二、代码逐段说明
snowflake.NewNode(1)
创建生成器节点,参数为机器ID,集群中每台服务必须使用 0~1023 唯一数字,多机避免ID重复;创建失败会返回err,必须捕获。node.Generate()
生成一条全局唯一雪花ID,返回snowflake.ID类型。- 常用方法
.Int64():转为int64数字,存入数据库主键;.String():转字符串,接口返回防止前端大数精度丢失;.Time():提取ID内置的创建时间;.Node():获取当前机器编号;.Step():同一毫秒内自增序列号,保证同毫秒多条数据不重复。
一、头部包导入
package main
标记这是可执行程序,不是第三方库,能直接编译运行。
import (
"fmt"
"sync"
"time"
"github.com/bwmarrin/snowflake"
)
fmt:控制台打印输出;sync:提供同步锁sync.Once、等待组sync.WaitGroup,用于协程并发控制;time:时间转换,把雪花ID里的毫秒时间戳格式化成年月日时分秒;snowflake:第三方雪花ID生成库,用来生成分布式全局唯一ID。
二、全局变量(生产项目标准单例设计)
var snowNode *snowflake.Node
var once sync.Once
snowNode:雪花ID生成器全局实例,整个项目只创建一个;once sync.Once:Go内置一次性执行锁,保证InitSnowflake函数全局只会初始化一次,避免多次创建生成器浪费资源。
三、初始化函数 InitSnowflake
func InitSnowflake(machineId int64) error {
var err error
once.Do(func() {
snowNode, err = snowflake.NewNode(machineId)
})
return err
}
- 参数
machineId:机器编号,取值范围0~1023;分布式集群每台服务器必须填不同数字,防止ID重复; once.Do():无论代码调用多少次初始化,内部创建节点逻辑只执行一次;snowflake.NewNode(machineId):创建雪花ID生成节点,失败会返回错误,外部捕获即可。
四、两个获取ID工具函数(适配两种业务场景)
1. GetSnowID() 返回 int64 数字ID
func GetSnowID() int64 {
return snowNode.Generate().Int64()
}
- 作用:返回纯数字int64类型ID;
- 使用场景:MySQL数据库主键、数字存储字段。
2. GetSnowIDStr() 返回字符串ID
func GetSnowIDStr() string {
return snowNode.Generate().String()
}
- 作用:把数字ID转字符串;
- 使用场景:HTTP接口返回给前端JS,JS数字超过2^53会丢失精度,用字符串可以避免该问题。
五、ParseIDInfo 雪花ID解析工具函数
func ParseIDInfo(id int64) {
snowID := snowflake.ParseInt64(id)
fmt.Println("===== 雪花ID解析详情 =====")
fmt.Printf("原始ID数值: %d\n", id)
ms := snowID.Time()
createTime := time.UnixMilli(ms)
fmt.Printf("生成时间戳(ms): %d\n", ms)
fmt.Printf("格式化创建时间: %s\n", createTime.Format("2006-01-02 15:04:05"))
fmt.Printf("机器编号(NodeID): %d\n", snowID.Node())
fmt.Printf("毫秒内自增序列号(Step): %d\n", snowID.Step())
fmt.Println("==========================")
}
snowflake.ParseInt64(id):传入雪花数字ID,反向拆解ID内部存储的信息;snowID.Time():拿到ID内置的毫秒时间戳(int64);time.UnixMilli(ms):毫秒时间戳转为Go标准time.Time对象,才能调用Format格式化;- 输出4个核心信息:原始ID、生成毫秒时间戳、可读格式化时间、机器编号、同一毫秒内自增序号;
- 业务用途:根据订单/消息ID直接查到创建时间,不用额外查数据库时间字段。
六、concurrentTest 并发测试协程函数
func concurrentTest(wg *sync.WaitGroup, ch chan<- int64) {
defer wg.Done()
for i := 0; i < 500; i++ {
id := GetSnowID()
ch <- id
}
}
wg *sync.WaitGroup:协程等待组,每个协程执行完调用wg.Done(),主函数等待所有协程全部执行完毕;ch chan<- int64:通道,用来收集所有协程生成的ID;- 循环500次:单个协程生成500条唯一ID,推入通道。
七、main 主函数(完整功能演示入口)
1. 初始化雪花生成器
err := InitSnowflake(1)
if err != nil {
fmt.Printf("雪花算法初始化失败,错误:%v\n", err)
return
}
fmt.Println("雪花ID生成器初始化完成!\n")
传入机器ID=1;分布式部署时每台机器更换唯一ID,初始化失败直接退出程序。
2. 两种格式ID基础使用
idNum := GetSnowID()
idStr := GetSnowIDStr()
fmt.Printf("数字型雪花ID(存数据库): %d\n", idNum)
fmt.Printf("字符串雪花ID(接口返回): %s\n\n", idStr)
分别拿到数据库存储用数字ID、前端返回用字符串ID并打印。
3. 解析ID内部信息
ParseIDInfo(idNum)
调用解析函数,打印该ID对应的创建时间、机器号、序列号。
4. 单循环批量生成ID
fmt.Println("单次循环批量生成10个ID:")
for i := 0; i < 10; i++ {
fmt.Printf("%d ", GetSnowID())
}
fmt.Println("\n")
串行批量生成10个ID,直观看到ID有序递增。
5. 高并发多协程唯一性测试(核心验证雪花算法特性)
const goroutineNum = 10
const totalCount = goroutineNum * 500
idChan := make(chan int64, totalCount)
var wg sync.WaitGroup
fmt.Printf("开启%d个协程,总共生成%d个ID,并发唯一性测试\n", goroutineNum, totalCount)
for i := 0; i < goroutineNum; i++ {
wg.Add(1)
go concurrentTest(&wg, idChan)
}
wg.Wait()
close(idChan)
- 定义10个协程,每个协程生成500条ID,合计5000条;
wg.Add(1)每开启一个协程等待计数+1;wg.Wait()阻塞主程序,直到所有协程执行完毕;- 关闭通道,停止读取。
6. map去重校验,验证ID全局不重复
idMap := make(map[int64]bool)
duplicateFlag := false
for val := range idChan {
if idMap[val] {
fmt.Printf("发现重复ID:%d\n", val)
duplicateFlag = true
}
idMap[val] = true
}
if !duplicateFlag {
fmt.Printf("并发校验完成:%d 条ID全部唯一,无重复!\n", totalCount)
}
- 使用map存储已经生成过的ID;
- 如果map中已存在当前ID,标记出现重复并打印;
- 全部遍历完成无重复则输出校验通过,证明雪花算法并发下ID全局唯一。
7. 模拟真实业务场景
orderId := GetSnowIDStr()
msgId := GetSnowID()
fmt.Printf("\n模拟业务订单ID:%s\n", orderId)
fmt.Printf("模拟消息主键ID:%d\n", msgId)
模拟电商订单号(字符串给前端)、聊天消息数据库主键(数字存入MySQL)。
整体代码设计亮点
- 单例全局管理:整个项目只初始化一次雪花生成器,符合线上项目规范;
- 双格式ID封装:同时兼容数据库存储、前端接口两种场景;
- ID反向解析工具:支持通过ID溯源创建时间,方便日志、订单排查;
- 并发完整性测试:模拟线上高并发场景,验证雪花ID无重复核心特性;
- 完整错误捕获,无未处理异常,编译零报错;
- 代码可直接复制到鸿蒙PC,编译+签名后运行。
鸿蒙PC运行命令
go get github.com/bwmarrin/snowflake
go build -o snow main.go
chmod +x snow
ohos-signpost snow
./snow
四、业务使用场景
- MySQL主键替代自增ID:分布式多服务部署时自增ID会冲突,雪花ID全局唯一;
- 订单号、流水号、消息ID、用户唯一标识;
- 可通过ID反查数据创建时间,无需额外存创建时间字段做统计;
- 按ID排序等价于按创建时间排序,分页查询更友好。
五、鸿蒙PC运行注意事项
鸿蒙不能直接go run,编译签名后执行:
go build -o snow main.go
chmod +x snow
ohos-signpost snow
./snow
更多推荐




所有评论(0)