1.Mongo新特性
首先,我们必须知道的是,Mongo的事务的在版本4.0时的新特性,也就是说,如果Mongo的版本是在4.0之前的,那么是不支持事务的,因此,我们的Mongo需要是4.0以上的版本。
2.基于会话的事务
其次,Mongo的事务是比较有意思的,Mongo要开启事务支持,需要搭建副本集(Replica Sets)。而且,Mongo的事务是和会话(Session)相关联的,一个会话同一时刻只能开启一个事务操作,当一个会话断开的时候,这个会话中的事务也会结束。
3.事务相关命令
Mongo中的事务相关命令如下:
//开启一个会话
sess = db.getMongo().startSession()
//开启事务
sess.startTransaction()
//回滚
sess.abortTransaction()
//提交
sess.commitTransaction()
//结束会话
sess.endSession()
二、搭建Mongo副本集
以下搭建副本集以Centos为例,并在同一个机子上进行搭建
1. 安装MongoDB
首先在MongoDB官网下载对应系统和版本的MongoDB包,如:
[root@vm1 ~]# wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-4.4.10.tgz
解压tgz包到/usr/local/目录下,并给目录重命名一下
[root@vm1 ~]# tar -zxvf mongodb-linux-x86_64-rhel70-4.4.10.tgz -C /usr/local/
[root@vm1 ~]# mv /usr/local/mongodb-linux-x86_64-rhel70-4.4.10/ /usr/local/mongodb-4.4.10/
2. 环境变量配置
# 修改环境变量
[root@vm1 ~]# vi /etc/profile
export PATH=$PATH:/usr/local/mongodb-4.4.10/bin
# 使环境变量生效
[root@vm1 ~]# source /etc/profile
3. 创建副本集目录
3.1 创建主节点相关目录
[root@vm1 ~]# cd /usr/local/mongodb-4.4.10/
[root@vm1 mongodb-4.4.10]# mkdir primary
[root@vm1 mongodb-4.4.10]# cd primary
[root@vm1 primary]# mkdir data logs config pid
3.2 创建副节点相关目录
[root@vm1 primary]# cd ..
[root@vm1 mongodb-4.4.10]# mkdir secondary
[root@vm1 mongodb-4.4.10]# cd secondary
[root@vm1 secondary]# mkdir data logs config pid
3.3 创建仲裁节点相关目录
[root@vm1 secondary]# cd ..
[root@vm1 mongodb-4.4.10]# mkdir arbiter
[root@vm1 mongodb-4.4.10]# cd arbiter
[root@vm1 arbiter]# mkdir data logs config pid
4. 创建副本集认证的key文件
[root@vm1 arbiter]# cd ..
[root@vm1 mongodb-4.4.10]# openssl rand -base64 90 -out mongo.keyfile
[root@vm1 mongodb-4.4.10]# chmod 400 mongo.keyfile
5 修改MongoDB配置文件
5.1 主节点配置文件
[root@vm1 mongodb-4.4.10]# cd primary/
[root@vm1 primary]# vi config/mongod.conf
添加内容如下:
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb-4.4.10/primary/logs/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod
dbPath: "/usr/local/mongodb-4.4.10/primary/data"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/usr/local/mongodb-4.4.10/primary/pid/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: 0.0.0.0
#bindIp
#绑定的端口
port: 27017
replication:
#副本集的名称
replSetName: "my-rs"
security:
#副本集密钥文件
keyFile: "/usr/local/mongodb-4.4.10/mongo.keyfile"
#副本集认证方式
clusterAuthMode: "keyFile"
5.2 副节点配置文件
[root@vm1 primary]# cd ../secondary/
[root@vm1 secondary]# vi config/mongod.conf
添加内容如下:
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb-4.4.10/secondary/logs/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod
dbPath: "/usr/local/mongodb-4.4.10/secondary/data"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/usr/local/mongodb-4.4.10/secondary/pid/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: 0.0.0.0
#bindIp
#绑定的端口
port: 27018
replication:
#副本集的名称
replSetName: "my-rs"
security:
#副本集密钥文件
keyFile: "/usr/local/mongodb-4.4.10/mongo.keyfile"
#副本集认证方式
clusterAuthMode: "keyFile"
5.3 仲裁节点配置文件
[root@vm1 secondary]# cd ../arbiter/
[root@vm1 arbiter]# vi config/mongod.conf
添加内容如下:
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/usr/local/mongodb-4.4.10/arbiter/logs/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod
dbPath: "/usr/local/mongodb-4.4.10/arbiter/data"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/usr/local/mongodb-4.4.10/arbiter/pid/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: 0.0.0.0
#bindIp
#绑定的端口
port: 27019
replication:
#副本集的名称
replSetName: "my-rs"
security:
#副本集密钥文件
keyFile: "/usr/local/mongodb-4.4.10/mongo.keyfile"
#副本集认证方式
clusterAuthMode: "keyFile"
6. 使用Systemd对Mongo服务进行管理
6.1 主节点配置
[root@vm1 arbiter]# vi /usr/lib/systemd/system/mongo-primary.service
添加内容如下
[Unit]
Description=mongodb primary
[Service]
ExecStart=/usr/local/mongodb-4.4.10/bin/mongod -f /usr/local/mongodb-4.4.10/primary/config/mongod.conf
ExecStop=/bin/kill -INT $MAINPID
PIDFile=/usr/local/mongodb-4.4.10/primary/pid/mongod.pid
[Install]
WantedBy=multi-user.target
添加为开机启动
[root@vm1 arbiter]# systemctl enable mongo-primary
6.2 副节点配置
[root@vm1 arbiter]# vi /usr/lib/systemd/system/mongo-secondary.service
添加内容如下
[Unit]
Description=mongodb secondary
[Service]
ExecStart=/usr/local/mongodb-4.4.10/bin/mongod -f /usr/local/mongodb-4.4.10/secondary/config/mongod.conf
ExecStop=/bin/kill -INT $MAINPID
PIDFile=/usr/local/mongodb-4.4.10/secondary/pid/mongod.pid
[Install]
WantedBy=multi-user.target
添加为开机启动
[root@vm1 arbiter]# systemctl enable mongo-secondary
6.3 仲裁节点配置
[root@vm1 arbiter]# vi /usr/lib/systemd/system/mongo-arbiter.service
添加内容如下
[Unit]
Description=mongodb arbiter
[Service]
ExecStart=/usr/local/mongodb-4.4.10/bin/mongod -f /usr/local/mongodb-4.4.10/arbiter/config/mongod.conf
ExecStop=/bin/kill -INT $MAINPID
PIDFile=/usr/local/mongodb-4.4.10/arbiter/pid/mongod.pid
[Install]
WantedBy=multi-user.target
添加为开机启动
[root@vm1 arbiter]# systemctl enable mongo-arbiter
7. 启动MongoDB服务
7.1 启动节点
[root@vm1 arbiter]# systemctl start mongo-primary mongo-secondary mongo-arbiter
7.2 查看节点状态
[root@vm1 arbiter]# systemctl status mongo-primary mongo-secondary mongo-arbiter
8. 创建用户,并初始化副本集
[root@vm1 mongodb-4.4.10]# mongo --port 27017
> use admin
> db.createUser({user:"root", pwd:"root", roles:["root"]})
> config={
"_id": "my-rs",
"members": [
{
"_id": 0,
"host": "127.0.0.1:27017",
"priority": 2
},
{
"_id": 1,
"host": "127.0.0.1:27018",
"priority": 1
},
{
"_id": 2,
"host": "127.0.0.1:27019",
"arbiterOnly": true
}
]
}
> rs.initiate(config)
> rs.status()
三、在Go代码中实现Mongo事务
1. 下载驱动包
直接执行下面的命令获取Golang的官方Mongo驱动包
go get -u go.mongodb.org/mongo-driver/mongo
2. 代码例子
使用事务时,特别要注意该用哪个context,在进行任何的db操作,不管是插入还是更新,都必须使用函数参数的那个sessionCtx,否则事务是无法生效的。另外在进行提交和回滚的时候,需要使用context.Background(),这是因为提交和回滚不应被context超时等因素影响。
package main
import (
"context"
"fmt"
"github.com/beego/beego/v2/core/logs"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"time"
)
//mongo连接参数
const (
user = "root"
pwd = "root"
hosts = "127.0.0.1:27017,127.0.0.1:27018,127.0.0.1:27019"
mongoOpt = "replicaSet=my-rs"
auth = "admin"
timeout = time.Duration(3000) * time.Millisecond
)
//mongo文档结构体
type student struct {
Name string `bson:"name"`
Gender string `bson:"gender"`
Age int `bson:"age"`
}
func main() {
//设置连接参数
uri := fmt.Sprintf("mongodb://%s:%s@%s/%s?%s",
user, pwd, hosts, mongoOpt, auth)
opt := options.Client().ApplyURI(uri).SetSocketTimeout(timeout)
//创建一个context上下文
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
//获得一个mongo client
client, err := mongo.Connect(ctx, opt)
if err != nil {
logs.Error("connect mongo failed, err:%s", err.Error())
return
}
//ping一下mongo
err = client.Ping(ctx, nil)
if err != nil {
logs.Error("ping mongo failed, err:%s", err.Error())
return
}
//设定连接的数据库和集合
database := "school"
collection := "student"
//构造插入的数据
students := []interface{}{
student{
Name: "Michael",
Gender: "Male",
Age: 21,
},
student{
Name: "Alice",
Gender: "Female",
Age: 19,
},
}
//在会话中使用mongo
if err = client.UseSession(ctx, func(sessionContext mongo.SessionContext) error {
//开启事务
if err := sessionContext.StartTransaction(); err != nil {
return err
}
//插入数据
if _, err := client.Database(database).Collection(collection).InsertMany(sessionContext, students); err != nil {
if err := sessionContext.AbortTransaction(context.Background()); err != nil {
//回滚事务
logs.Error("mongo transaction rollback failed, %s", err.Error())
return err
}
return err
}
//提交事务
return sessionContext.CommitTransaction(context.Background())
}); err != nil {
logs.Error("insert failed, err:%s", err.Error())
}
}