前面已经对nginx日志进行了分析处理,多个go程同时insert数据到MySQL,这对MySQL来说无疑是一次高并发
// 起5个go程,解析nginx的mq,并存入到mysql
go worker("worker1", mq, db)
go worker("worker2", mq, db)
go worker("worker3", mq, db)
go worker("worker4", mq, db)
go worker("worker5", mq, db)
针对这种高并发、海量的数据,存储到MongoDB是一个不错的选择
实践出真理,这里只改了前面代码的存储部分
// 将异常的nginx日志写入logs_info_exception表
db.Create(&infoException)
// 异常访问,存MySQL改成存MongoDB
// 将异常的nginx日志写入mongodb集合
collection.InsertOne(context.TODO(), infoException)
// 将正常的nginx日志写入logs_info表
db.Create(&info)
// 正常访问,存MySQL改成存MongoDB
// 将正常的nginx日志写入mongodb集合
collection.InsertOne(context.TODO(), info)
完整代码
package main
import (
"bufio"
"context"
"fmt"
"io"
"log"
"os"
"regexp"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type LogsInfo struct {
Ip string
Time int64
Method string
Path string
Protocol string
Status int
Size int
Referer string
UserAgent string
}
type LogsInfoException struct {
Ip string
Time int64
Other string
}
func connMongo() *mongo.Collection {
// 设置客户端连接配置, mongodb://用户名:密码@host:port/数据库
clientOptions := options.Client().ApplyURI("mongodb://root:root123@localhost:27017/admin")
// 连接到MongoDB
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
log.Fatal(err)
}
// 检查连接
err = client.Ping(context.TODO(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Connected to MongoDB!")
// 指定获取要操作的数据集
return client.Database("test").Collection("nginx_log")
}
func main() {
start := time.Now()
// 连接mongodb
collection := connMongo()
mq := make(chan string, 50) // 存放消息,用来给多个go程消费
signal := make(chan bool, 2) // 读取文件结束标志
// 注塑主进程,防止主进程exit
forever := make(chan bool)
// 起5个go程,解析nginx的mq,并存入到mongodb
go worker("worker1", mq, collection)
go worker("worker2", mq, collection)
go worker("worker3", mq, collection)
go worker("worker4", mq, collection)
go worker("worker5", mq, collection)
// 起一个go程,用来读取nginx
go task(mq, signal)
// 主进程退出检测
go func(signal chan bool, mq chan string, forever chan bool) {
for {
if len(signal) == 1 && len(mq) == 0 {
forever <- true
}
}
}(signal, mq, forever)
// 优雅退出
<-forever
fmt.Println("cost:", time.Since(start).Seconds(), "s")
}
// task
// read log file
// 日志文件一般都比较大,很难一次性读取到内存中
// 这里是一行一行读取,并将每一行string放入mq通道中
func task(mq chan string, signal chan bool) error {
// read log file
filePath := "log/access.log"
f, err := os.Open(filePath)
defer f.Close()
if err != nil {
log.Panic(err)
return err
}
buf := bufio.NewReader(f)
count := 0
for {
if count > 1000 { // 加个条件方便测试数据量
signal <- true
return nil
}
line, _, err := buf.ReadLine()
if err != nil {
if err == io.EOF {
signal <- true
return nil
}
signal <- true
return err
}
body := strings.TrimSpace(string(line))
mq <- body
log.Println("task: ", count)
count += 1
// 每次读取后,都检查一下mq通道的容量,如果容量达到一般,则sleep,将时间片交给其他go程
// 达到容量的阈值,以及sleep的时间,要根据自己的电脑配置计算的出
// 比如,处理10000个mq,就可以大概计算出处理1个mq所需要的的时间
if len(mq) > 25 {
time.Sleep(10 * time.Millisecond)
}
}
}
// consumer
// parse and write
func worker(workerName string, mq chan string, collection *mongo.Collection) {
count := 0
for d := range mq {
parse(d, collection)
count++
log.Println(workerName, ": ", count)
}
}
// parse and write
func parse(str string, collection *mongo.Collection) {
// 每行nginx格式:ip 访问时间 访问方式 访问路径 协议 状态码 数据大小 referer user-agent
re := `^([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}) - - \[(.*)\] "([^\s]+) ([^\s]+) ([^\s]+?)" ([\d]{3}) ([\d]{1,9}) "([^"]*?)" "([^"]*?)"`
reg := regexp.MustCompile(re)
parseInfo := reg.FindStringSubmatch(str)
// 匹配不到正常的格式,那么这条访问记录很可能有问题
// 异常nginx处理
if len(parseInfo) == 0 {
re1 := `^([\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}) - - \[(.*)\] (.*)`
reg1 := regexp.MustCompile(re1)
parseInfo1 := reg1.FindStringSubmatch(str)
if len(parseInfo1) == 0 {
return
}
t1, _ := time.Parse("02/Jan/2006:15:04:05 -0700", parseInfo1[2])
infoException := LogsInfoException{
Ip: parseInfo1[1],
Time: t1.Unix(),
Other: parseInfo1[3],
}
// 将异常的nginx日志写入mongodb集合
collection.InsertOne(context.TODO(), infoException)
return
}
t, _ := time.Parse("02/Jan/2006:15:04:05 -0700", parseInfo[2])
status, _ := strconv.Atoi(parseInfo[6])
size, _ := strconv.Atoi(parseInfo[7])
//
info := LogsInfo{
Ip: parseInfo[1],
Time: t.Unix(),
Method: parseInfo[3],
Path: parseInfo[4],
Protocol: parseInfo[5],
Status: status,
Size: size,
Referer: parseInfo[8],
UserAgent: parseInfo[9],
}
// 将正常的nginx日志写入mongodb集合
collection.InsertOne(context.TODO(), info)
}
有兴趣的可以进行MongoDB VS MySQL,从读写速度、连接数等维度对比分析,自由发挥了
项目代码已放码云