使用数据库自带的事务功能在业务中是十分重要的!
1. 单节点 mongo 事务如何配置
mongo 较新版本中已经支持事务了,但是前提条件是 你的 mongo模式必须为“副本集模式” 或者 “分片模式”,鉴于操作成本问题,这里只提 副本集模式。
对于 go 语言开发的小伙伴,我推荐用官方包:go.mongodb.org/mongo-driver/mongo
这有两个点要提醒一下:
要使用mongo的自带事务,需要配置成 副本集或者分片模式。
在非事务时mongo,插入数据时会自动建立表结构,但是使用事务中如何相关表不存在,就会报错。所有事务使用时建议先建立好对应的表结构。
配置单节点副本集模式
其实很简单,在多节点即一般为三节点架构的mongo数据库配置副本集时,一般我们需要三个服务器分配配置不同的节点。但对于只用一个节点的mongo数据库服务器时,我们只想配一个节点的副本集模式(虽然这种单节点架构很不安全!)
首先找到你的 mongod.conf 这个 mongo 对应的配置文件。在里面更改配置内容如下:
systemLog:
destination: file
path: /usr/local/var/log/mongodb/mongo.log
logAppend: true
storage:
dbPath: /usr/local/var/mongodb
net:
bindIp: 127.0.0.1
# 就是添加下方的内容
replication:
replSetName: rs0 # 这个 replSetName 必须所有节点都得一致
然后我们重启mongo,运行如下命令(记得一定要重启,让mongo重新读取新修改的配置文件):
重启 mongo 带上配置文件位置
mongod -f /usr/local/etc/mongod.conf
运行 mongo 关键字进入mongo执行环境,再执行如下命令即可:
rs.initiate()
rs.slaveOk()
然后当你再次进入mongo后你会看见如下:
---
Enable MongoDB's free cloud-based monitoring service, which will then receive and display
metrics about your deployment (disk utilization, CPU, operation statistics, etc).
The monitoring data will be available on a MongoDB website with a unique URL accessible to you
and anyone you share the URL with. MongoDB may use this information to make product
improvements and to suggest MongoDB products and deployment options to you.
To enable free monitoring, run the following command: db.enableFreeMonitoring()
To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
rs0:PRIMARY>
运行查看副本集状态命令:rs.status() 如下所示:
部分显示内如:
"members" : [
{
"_id" : 0,
"name" : "127.0.0.1:27017",
"ip" : "127.0.0.1",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY", // 这里表示该节点为主节点了
"uptime" : 78645,
"optime" : {
"ts" : Timestamp(1579066457, 2),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2020-01-15T05:34:17Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1578987825, 1),
"electionDate" : ISODate("2020-01-14T07:43:45Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
}
],
"ok" : 1, // 1 表示这个副本集节点是正常成功的
"$clusterTime" : {
"clusterTime" : Timestamp(1579066457, 2),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1579066457, 2)
这样单节点副本集配置就完成了,你可以开始写关于事务的业务代码了。
事务代码 demo 展示
当上述单节点副本集配置完成,接下来就是具体代码的实现而已:
func main(){
connectString := "mongodb://127.0.0.1/test"
dbUrl, err := url.Parse(connectString)
if err != nil {
panic(err)
}
//认证参数设置,否则连不上
opts := &options.ClientOptions{}
opts.SetAuth(options.Credential{
AuthMechanism:"SCRAM-SHA-1",
AuthSource:"test",
Username:"test",
Password:"123456"})
client, err = mongo.Connect(context.Background(), connectString,opts)
if err != nil {
panic(err)
}
db := client.Database(dbUrl.Path[1:])
ctx := context.Background()
defer db.Client().Disconnect(ctx)
col := db.Collection("test")
//先在事务外写一条id为“111”的记录
_,err = col.InsertOne(ctx, bson.M{"_id": "111", "name": "ddd", "age": 50})
if err != nil {
fmt.Println(err)
return
}
//第一个事务:成功执行
db.Client().UseSession(ctx, func(sessionContext mongo.SessionContext) error {
err = sessionContext.StartTransaction()
if err != nil {
fmt.Println(err)
return err
}
//在事务内写一条id为“222”的记录
_, err = col.InsertOne(sessionContext, bson.M{"_id": "222", "name": "ddd", "age": 50})
if err != nil {
fmt.Println(err)
return err
}
//在事务内写一条id为“333”的记录
_, err = col.InsertOne(sessionContext, bson.M{"_id": "333", "name": "ddd", "age": 50})
if err != nil {
sessionContext.AbortTransaction(sessionContext)
return err
}else {
sessionContext.CommitTransaction(sessionContext)
}
return nil
})
//第二个事务:执行失败,事务没提交,因最后插入了一条重复id "111",
err = db.Client().UseSession(ctx, func(sessionContext mongo.SessionContext) error {
err := sessionContext.StartTransaction()
if err != nil {
fmt.Println(err)
return err
}
//在事务内写一条id为“222”的记录
_, err = col.InsertOne(sessionContext, bson.M{"_id": "444", "name": "ddd", "age": 50})
if err != nil {
fmt.Println(err)
return err
}
//写重复id
_, err = col.InsertOne(sessionContext, bson.M{"_id": "111", "name": "ddd", "age": 50})
if err != nil {
sessionContext.AbortTransaction(sessionContext)
return err
}
sessionContext.CommitTransaction(sessionContext)
return nil
})
}
多节点 副本集(复制集)
副本集的作用:
- MongoDB 复制集的主要意义就在于 => 实现服务的高可用
- 它的现实依赖于两个方面的功能:
- 数据写入时将数据迅速复制达到另外一个独立节点中
- 在接受写入的节点发生故障时自动选举出一个新的替代节点
- 在实现高可用的同时,复制集实现了其他几个附加作用:
- 数据分发:将数据从一个区域复制到另外一个区域,减少另外一个区域的延迟
- 读写分离:不同类型的压力分别在不同节点执行。比如主节点写入,副节点读取
- 异地容灾:在数据中心故障时快速切换到异地。
典型副本集架构
一般典型的架构是三个及其以上节点组成,其中有:
- 一个主节点(PRIMARY): 接受写入操作和选举时投票
- 两个(多个)从节点(SECONDARY): 复制主节点上的新数据和选举时进行投票
数据复制过程
- 当一个修改操作,到达主节点时,这种对数据的操作将被记录下来,这些记录称为 oplog
- 从节点通过在主节点上打开一个 tailable 游标不断的获取新进入主节点的 oplog,并在自己的数据上回放,以此保持跟主节点的数据一致。
通过选举完成故障恢复
- 具有投票权的节点(最多7个)两两之间相互发送心跳;
- 当 5 次心跳都未收到时判断为该节点失联了;
- 如果失联的是主节点,从节点就会发起选举,选出新的主节点;
- 如果失联的是从节点则不会选举
- 选举基于 RAFT 一致性算法 实现,选举成功的必要条件是大多数投票节点是存活的;(如果总节点7个那么应该要有4个节点是存货的才能选举)
- 副本集中最多可以有 50 个节点, 但具有投票权的最多只有7个。
开始搭建配置我们的三节点副本集吧
我用的是 MacOS , linux 也可行的。
首先请自行安装mongo,并设置好环境变量等。
创建数据目录
mkdir -p /data/db{1,2,3}
配置文件
三个不同的数据库存放路径:
/data/db1
/data/db2
/data/db3
三个不同的对应的日志文件路径:
/data/db1/mongod.log
/data/db2/mongod.log
/data/db3/mongod.log
分别建立 三个对应的配置文件
/data/db1/mongod.conf
/data/db2/mongod.conf
/data/db3/mongod.conf
配置文件模板如下:
systemLog:
destination: file
path: /data/db1/mongod.log
logAppend: true
storage:
dbPath:/data/db1
net:
bindIp: 0.0.0.0
port: 27017
replication:
replSetName: rs0 # 这个 replSetName 必须所有节点都得一致
processManagement:
fork: true
启动 MongoDB 进程:
mongod -f /data/db1/mongod.conf mongod -f /data/db2/mongod.conf mongod -f /data/db3/mongod.conf
最后就是配置复制集:
# mongo --port 27017
> rs.initiate({
_id: "rs0",
members: [{
_id: 0,
host: "localhost27017"
},{
_id: 1,
host: "localhost27018"
},{
_id: 2,
host: "localhost27019"
}]
})
你可以试着验证
主节点写入:
# mongo localhost:27017
> db.test.insert({a:1})
> db.test.insert({a:2})
从节点读取:
# mongo localhost:27018
// 这里默认丛节点没有开启读取功能所以要开启
> rs.slaverOk()
> db.test.find()