【Go - 超实用,3行代码实现个自增器】
场景
自增器的作用是生成一个唯一的递增序列号。这在一些需要生成自增id的场景十分有用,比如自增的订单号,任务号,序列号。
要点
- 全局统一:在整个服务体系下,多个服务或者进程,都统一调用这个自增器,来获取自增ID。
- 严格自增:避免竞争,写冲突造成写覆盖等,导致不严格自增
实现
根据上面要点,需要跨服务进程可以访问,且保障严格自增。综上考虑, 依赖MonogoDB来实现这个自增器,以下是代码实现,
代码
mongodb-conn.go
package main
import (
"context"
"os"
"sync"
mongo "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var (
singletonMongoClient *mongo.Client
once sync.Once
)
var MONGODB_URI = os.Getenv("MONGODB_URI")
var MONGODB_DATABASE = os.Getenv("MONGODB_DATABASE")
func getSingletonMongoClient() *mongo.Client {
once.Do(func() {
// 创建连接到 MongoDB 的客户端
uri := MONGODB_URI
client, err := mongo.Connect(context.TODO(), options.Client().
ApplyURI(uri))
if err != nil {
panic(err)
}
singletonMongoClient = client
})
return singletonMongoClient
}
func GetMongoConn() *mongo.Database {
return getSingletonMongoClient().Database(MONGODB_DATABASE)
}
main.go
package main
import (
"context"
bson "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
type UniqSeq struct {
Key string `bson:"key"`
Seq int64 `bson:"seq"`
}
func GetNextSeq(key string) (int64, error) {
conn := GetMongoConn()
var collectionName = "uniqseqz"
collection := conn.Collection(collectionName)
filter := bson.M{"key": key}
update := bson.M{"$inc": bson.M{"seq": 1}}
opts := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)
var result UniqSeq
err := collection.FindOneAndUpdate(context.Background(), filter, update, opts).Decode(&result)
if err != nil {
return 0, err
}
return result.Seq, nil
}
func main() {
// Test
key := "T"
seq, err := GetNextSeq(key)
if err != nil {
panic(err)
}
println(seq)
}
这里的核心就在下面三行
// 这里是借助了MongoDB $inc 和 upsert特性
// 先按key过滤,找出对应的document,然后upsert,没有就插入一条,有就加一。
// 以此来完成了自增
filter := bson.M{"key": key}
update := bson.M{"$inc": bson.M{"seq": 1}}
opts := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After)
关于竞争,
// 这个操作调用的时候,MongoDB Server端会加锁执行,保障操作的原子性,
// 对于调用方可以看以下一行是原子性的,不用担心写冲突或者写覆盖。
var result UniqSeq
err := collection.FindOneAndUpdate(context.Background(), filter, update, opts).Decode(&result)
运行
最后可以带入运行一下,
MONGODB_URI="${YOUR_MONGODB_URL}" MONGODB_DATABASE="${YOUR_MONGODB_DATABASE}" go run mongodb-conn.go main.go