Go 包选择与对比

Table of Contents

1 日志

可能是 Go 尚年轻,行业对 Go 的应用领域还比较宽泛,也可能是开源的缘故,Go package 普遍造轮子的现象比较明显。

对于日志包也是如此,没有像 google/glog 和 log4plus 之于 C++ 这么权威。尝试了一些的 Go logging package,结论如下:

1.1 标准库

功能较少,不同级别的日志区分不开,小的测试项目可以取代 fmt 来 debug。

1.2 google/glog

google 类似 C++ 日志版本的 Go 版本,性能较好,使用也比较简单。

  • 优点:代码很短,性能好,易用性较好,稳定;K8s 内置的就是 glog,不过最近好像要基于 glog 做二次开发 klog
  • 缺点:已经四五年没更新了···,日志级别区分的比较乱,需要二次封装

1.3 sirupsen/logrus

结构化的,可插拔的日志系统。

  • 优点:业内评价较高,易用性好,定制化也很强,人性化。

1.4 uber-go/zap

Uber 开源的日志系统。

  • 优点:性能 非常
  • 缺点:可能是设计就是为了设计之初就是为了高性能,定制性很强,就是非常不人性化,以至于不知道怎么定制···,好在有这个 sandipb/zap-examples

性能相比: zap > glog > logrus > log ,值得一提的是,zap 是远大于 glog 和 logrus 的。

1.5 结论

  • 高性能的系统,推荐使用 zap;
  • 业务系统推荐使用 logrus,glog 的日志级别( V() )很容易误导开发人员,个人体验并不是很好。

2 Redis

redis go clients: https://redis.io/clients#go

推荐两个:

  • go-redis/redis:对 Redis 命令进行了封装,易用性也好,支持集群
  • gomodule/redigo:不支持集群,更接近原生的 Redis 的使用方法(Print-like API),并且提供了常用基本类型的转换方法,统一的结果处理方法。用习惯了觉得非常简单

各有优劣势,看个人喜好和业务场景:

  1. 轻度依赖缓存,量也不是很大的情况下,推荐使用 redigo,感觉上比 go-redis 要方便一些
  2. 重度依赖缓存,量很大,使用 go-redis,特性支持的好,主要对 Cluster 方便容量扩展

3 Mongo

MongoDB 官方在 2018 年的 2 月 14 日发布了 MongoDB Go Driver Alpha 版本,四天前发布了第一个正式版本,算是很晚了。

以前社区主要维护的是 gopkg.in/mgo.v2 已经七年了,用的人比较多,但是目前已经不在维护了(不维护的主要原因是作者已经不再使用 MongoDB,并且维护一个好的项目太耗费时间而没带来什么好处)。

如何选择是比较难的,官方的 mongo-go-driver 只有 1.0 版本,另外一个虽然成熟稳定,但是不再维护,不再维护意味着 Bug 不再修,更重要的是 MongoDB 版本还在更新,很多特性的支持不足甚至会出现不兼容的情况。

mongo-go-drivermgo.v2 都用过,一点建议:

  • 目前依旧建议使用 mgo.v2 企业不比社区,稳定性第一位,而且 MongoDB 版本的升级不一定会导致业务使用的 MongoDB 升级
  • 可以持续观望 mongo-go-driver,官方支持肯定靠谱,怕的是不兼容的更新,再等个一年左右多发几个正式版再说
  • 就使用体验而言 mongo-go-driver 比 mgo.v2 要好一些, bson.Dbson.M 好用一些,就是文档范例不够完善,不知道怎么用,以前用的时候,不得已只能翻他自己的测试用例。有个小项目用的是 Beta 版本,看到后几次版本发布,欲哭无泪(各种不兼容更新),还不知道咋搞呢···

3.1 mongo-go-driver 构建索引报错:

(BadValue) Invalid field specified for createIndexes command: maxTimeMS

代码如下:

func yieldIndexModel(key string, unique bool, order int) mongo.IndexModel {
    keys := bsonx.Doc{{Key: key, Value: bsonx.Int32(int32(order))}}
    index := mongo.IndexModel{
        Keys: keys,
    }
    if unique {
        index.Options = options.Index().SetUnique(true)
    }
    return index
}

func (sc *StorageClient) BuildIndex(name string, key string, unique bool) error {
    db, err := sc.GetDatabase()
    if err != nil {
        return err
    }

    collection := db.Collection(name)
    opts := options.CreateIndexes().SetMaxTime(20 * time.Second) // set max build time
    indexModel := yieldIndexModel(key, unique, ASCENDING)
    indexName, err := collection.Indexes().CreateOne(context.Background(), indexModel, opts)

    // ...
}

去掉 SetMaxTime 之后就正常了。

有一个 issue 说明这个问题, maxTimeMS 只适合只读操作,是一个遗留选项,应该是不同的版本支持不同,这也就印证了在我 macOS 本地是可以的,在 server 端却是不行的。

3.2 mgo.v2 中的连接池

mgo.Session 的所有方法是并发安全的,支持多个协程同时访问。但是并不意味着可以并行的创建和它们。

正确的使用方法是:只初始化一个 session,然后调用 Session.Copy() 或者 Session.Clone() 创建一个副本。

Session 会自动的管理一个连接池,甚至可以管理多个服务器节点,如果只使用一个 Session 的话,会跳过连接池带来的便利。调用 Session.Clone() 会从连接池中拿出一个连接, Session.Close() 并没有关闭连接,而是把它放回连接池,准备为另外一个会话服务。

Copy() vs Clone()

Clone works just like Copy, but also reuses the same socket as the original session, in case it had already reserved one due to its consistency guarantees. This behavior ensures that writes performed in the old session are necessarily observed when using the new session, as long as it was a strong or monotonic session. That said, it also means that long operations may cause other goroutines using the original session to wait.

似乎 Clone() 更好一些?

参考:

Date: 2019-03-14 17:50:00

Author: JerryZhang