美文网首页
Golang MongoDB

Golang MongoDB

作者: 合肥黑 | 来源:发表于2019-04-03 17:58 被阅读0次

    我们选用 MongoDB 作为网站的数据库系统,它是一个开源的 NoSQL 数据库,相比MySQL 那样的关系型数据库,它更为轻巧、灵活,非常适合在数据规模很大、事务性不强的场合下使用。

    一、NoSQL

    参考
    NoSQL 还是 SQL ?这一篇讲清楚
    NoSQL经典详解
    什么是 NoSQL 呢?为了解释清楚,首先让我们来介绍几个概念。在传统的数据库中,数据库的格式是由表(table)、行(row)、字段(field)组成的。表有固定的结构,规定了每行有哪些字段,在创建时被定义,之后修改很困难。行的格式是相同的,由若干个固定的字段组成。每个表可能有若干个字段作为索引(index),这其中有的是主键(primary key),用于约束表中的数据,还有唯一键(unique key),确保字段中不存放重复数据。表和表之间可能还有相互的约束,称为外键(foreign key)。对数据库的每次查询都要以行为单位,复杂的查询包括嵌套查询、连接查询和交叉表查询。拥有这些功能的数据库被称为关系型数据库,关系型数据库通常使用一种叫做 SQL(Structured Query Language)的查询语言作为接口,因此又称为 SQL 数据库。典型的 SQL 数据库有 MySQL、Oracle、Microsoft SQL Server、PostgreSQL、SQLite,等等。

    NoSQL 是 1998 年被提出的,它曾经是一个轻量、开源、不提供SQL功能的关系数据库。但现在 NoSQL 被认为是 Not Only SQL 的简称,主要指非关系型、分布式、不提供 ACID 的数据库系统。正如它的名称所暗示的,NoSQL 设计初衷并不是为了取代 SQL 数据库的,而是作为一个补充,它和 SQL 数据库有着各自不同的适应领域。NoSQL 不像 SQL 数据库一样都有着统一的架构和接口,不同的 NoSQL 数据库系统从里到外可能完全不同。

    各个数据之间存在关联是关系型数据库得名的主要原因,为了进行join处理,关系型数据库不得不把数据存储在同一个服务器内,这不利于数据的分散,这也是关系型数据库并不擅长大数据量的写入处理的原因。相反NoSQL数据库原本就不支持Join处理,各个数据都是独立设计的,很容易把数据分散在多个服务器上,故减少了每个服务器上的数据量,即使要处理大量数据的写入,也变得更加容易,数据的读入操作也很容易。例如:谷歌和Facebook每天为他们的用户收集万亿比特的数据。这些数据的存储不需要固定的模式,无需多余的操作就可以横向扩展。

    image.png
    1.HBase(列存储)

    两大用途:

    • 特别适用于简单数据写入(如“消息类”应用)和海量、结构简单数据的查询(如“详单类”应用)。特别地,适合稀疏表。(个人觉得存个网页内容是极好极好的)
    • 作为MapReduce的后台数据源,以支撑离线分析型应用。

    场景:Facebook的消息类应用,包括Messages、Chats、Emails和SMS系统,用的都是HBase;淘宝的WEB版阿里旺旺,后台是HBase;小米的米聊用的也是HBase;移动某省公司的手机详单查询系统。(单次分析,只能scan全表或者一个范围内的)

    2.MongoDB
    • 是一个介于关系型和非关系型之间的一个产品吧,类SQL语言,支持索引
    • MongoDb在类SQL语句操作方面目前比HBase具备更多一些优势,有二级索引,支持相比于HBase更复杂的集合查找等。
    • BSON的数据结构使得处理文档型数据更为直接。支持复杂的数据结构
    • MongoDb也支持mapreduce,但由于HBase跟Hadoop的结合更为紧密,Mongo在数据分片等mapreduce必须的属性上不如HBase这么直接,需要额外处理。
    3.RedisRedis
    • 为内存型KV系统,处理的数据量要小于HBase与MongoDB
    • Redis很适合用来做缓存,但除此之外,它实际上还可以在一些“读写分离”的场景下作为“读库”来用,特别是用来存放Hadoop或Spark的分析结果。
    • Redis的读写性能在100,000ops/s左右,时延一般为10~70微妙左右;而HBase的单机读写性能一般不会超过1,000ops/s,时延则在1~5毫秒之间。
    • Redis的魅力还在于它不像HBase只支持简单的字符串,他还支持集合set,有序集合zset和哈希hash
    二、知乎 NoSql是一种语言,还是一种概念?

    空口说比较无趣,我举一个现实的例子。我曾就职的一个公司有一个系统要是对用户给出推荐的广告。流程很简单,根据cookie查找用户属性,然后有专门的系统给出推荐列表。

    我们只讨论这个根据cookie查找用户属性。这个功能用sql可以做吗?当然可以,但是为什么要用sql?

    • 1、这个特性永远永远是1对1的查找,不需要任何sum,count等功能;
    • 2、这个特性永远永远是从cookie搜索属性,不需要根据属性来查找,所以where也用不上(更别说join了);
    • 3、数据只有一个更新者,基于hadoop的机器学习程序,所以锁也不是必须的;
    • 4、这个业务甚至不介意脏数据,如果因为同步等原因造成读到的事旧的属性,也不过就是拿昨天的数据给他推广告罢了。

    这个业务真正关心的是什么?

    • 时延一定要小,上游对整个广告过程只给了500ms,你还需要去竞价,需要去选择广告,还要预留网络时延。
    • 数据量大,中国的网民数量是比较多的,几亿条记录是下限。
    • 并发要高,每当网民打开一个网页,有几个广告位就会发生几次广告竞价。
    • 最后,很不幸这个业务毛利率很低,要是在软件或者硬件是花费过多有可能亏本的。

    所以这个特性我相信业界都是使用key-value数据库,redis放得下就用redis(以前没有集群,单机放不下得想其他办法)。在抛弃掉sql不必要的束缚后,kv数据库可以在同样的软硬件成本下,实现更低的时延和更高的吞吐量。

    我的高中数学老师曾经说过一句话,“你一定要掌握一个问题的通解,因为在考试的时候你无法保证可以在有限的时间内找到特解”;而我的大学老师(好像是信号处理)说过另外一句话“如果你没有找到某个问题的特解,那么说明你还不够了解这个问题”。

    sql是一个很好的通解,这个框里面你可以放任何东西,而且它的一切都是可以预期的。但是当行业不断发展了之后,大家开始对业务上的一些问题了解越来越深入,而且这些业务的量也大到了你愿意单独为它搭一套系统。此时,不同的nosql作为不同特定问题的特解出现就自然而然了。所以nosql不可能取代sql,但它本身的发展也是不可逆的。另外给一个建议,如果你的系统访问量用一个单机mysql就可以搞定,那么还是继续用mysql吧,别本末倒置。

    三、MongoDB简介

    参考
    知乎 怎样学 MongoDB?
    MongoDB 极简实践入门

    MongoDB 是一个对象数据库,它没有表、行等概念,也没有固定的模式和结构,所有的数据以文档的形式存储。所谓文档就是一个关联数组式的对象,它的内部由属性组成,一个属性对应的值可能是一个数、字符串、日期、数组,甚至是一个嵌套的文档。下面是一个MongoDB 文档的示例:

    { "_id" : ObjectId( "4f7fe8432b4a1077a7c551e8" ),
    "uid" : 2004,
    "username" : "byvoid",
    "net9" : { "nickname" : "BYVoid",
    "surname" : "Kuo",
    "givenname" : "Carbo",
    "fullname" : "Carbo Kuo",
    "emails" : [ "byvoid@byvoid.com", "byvoid.kcp@gmail.com" ],
    "website" : "http://www.byvoid.com",
    "address" : "Zijing 2#, Tsinghua University" }
    }
    

    上面文档中 uid 是一个整数属性, username 是字符串属性, _id 是文档对象的标识符,格式为特定的 ObjectId 。 net9 是一个嵌套的文档,其内部结构与一般文档无异。从格式来看文档好像 JSON,没错,MongoDB 的数据格式就是 JSON,因此与 JavaScript 的亲和性很强。在 Mongodb 中对数据的操作都是以文档为单位的,当然我们也可以修改文档的部分属性。对于查询操作,我们只需要指定文档的任何一个属性,就可在数据库中将满足条件的所有文档筛选出来。为了加快查询,MongoDB 也对文档实现了索引,这一点和 SQL 数据库一样。

    四、MongoDB安装

    先是看了一下windows下MongoDB的安装及配置,然后自己下载了安装包,才发现是4.0.9版本的,差别比较大。比较重要一点是:

    从 MongoDB 4.0 开始,默认情况下,你可以在安装期间配置和启动 MongoDB 作为服务,并在成功安装后启动 MongoDB 服务。也就是说,MongoDB 4.0 已经不需要像以前版本那样输入一堆命令行来将 MongoDB 配置成 Windows 服务来自动运行了,方便了很多。
    Win10 安装配置 MongoDB 4.0 踩坑记

    在3中的许多配置(如 设置dbpath、logpath、安装服务等),在4中都可以省去。​​也就是说,在MongoDB4.0.0中,只要安装好了,基本不用配置就可以用了。由于之前不知道这些,而且安装配置的教程都是参照MongoDB3的,所以走了许多弯路。

    参考如下,开始安装
    Mongodb最新版本安装(4.0以上)
    mongoDB的使用学习(一)mongoDB4.0.6的下载安装配置

    1.设置service name和配置路径
    image.png
    • 如果你选择不将 MongoDB 配置为服务,请取消选中 Install MongoD as a Service。

    • 如果你选择将 MongoDB 配置为服务,则可以:

      • 指定以下列用户之一运行服务:
        • 网络服务用户;即 Windows 内置的 Windows 用户帐户
        • 本地或域用户:
        • 对于现有本地用户帐户,Account Domain 指定为 .,并为该用户指定 Account Name 和 Account Password。
        • 对于现有域用户,请为该用户指定 Account Domain,Account Name 和 Account Password。
    • 指定 Service Name。如果你已拥有具有指定名称的服务,则必须选择其他名称。

      • 指定 Data Directory(数据保存目录),对应于 --dbpath。如果该目录不存在,安装程序将创建该目录并为服务用户设置访问权限。
      • 指定 Log Directory(日志保存目录),该目录对应于 --logpath。如果该目录不存在,安装程序将创建该目录并为服务用户设置访问权限。
    2.MongoDB Compass是个可视化工具,如果勾选安装的话是在线下载安装的,据说有的人安装一整晚都没装好就是因为这个。我这里是取消勾选的,因为之后我会装别的工具使用
    image.png
    3.简单使用

    我这里全部默认设置,一路next,然后点完finish,就没了……
    呃,只能自己打开cmd,然后在C:\Program Files\MongoDB\Server\4.0\bin路径下(MongoDB配置环境变量,可以任意路径运行mongo命令
    )运行mongo命令发现还是安装成功了。在这个路径下打开mongod.cfg,能看到配置,比如data和log的位置。使用浏览器打开http://127.0.0.1:27017/:It looks like you are trying to access MongoDB over HTTP on the native driver port.

    使用show dbs看一下:

    > show dbs
    admin   0.000GB
    config  0.000GB
    local   0.000GB
    > show collections
    movie
    >
    

    show dbs 显示所有数据库的名称和存储情况
    use xxx 转到xxx数据库,如果没有该数据库,那就建立该数据库
    db.createCollection('author')创建集合;我们试着往我们的数据库里添加一个集合(collection),MongoDB里的集合和SQL里面的表格是类似的
    更多命令参考
    https://www.mongodb.org.cn/tutorial/
    http://www.runoob.com/mongodb/mongodb-tutorial.html

    4.可视化工具

    参考知乎 MongoDB上有哪些比较好的GUI工具?

    如果你是Mongo的企业版用户,不如尝试MongoDB的官方GUI:MongoDB Compass

    我最终选了RoboMongo,现在已经改名为robo 3t。以下参考MongoDB可视化工具--Robo 3T 使用教程

    image.png
    下载后一路next,然后默认连接就能看到数据了。
    五、在go中使用
    1. Mgo 驱动

    地址: https://godoc.org/labix.org/v2/mgohttps://github.com/go-mgo/mgo
    地址: https://github.com/globalsign/mgo
    文档: https://godoc.org/github.com/globalsign/mgo
    说明:上面第一个地址,是 mgo 的原地址,目前作者已经停止维护。第二个地址是基于原作者的社区维护版本,也是作者推荐的方案之一。

    2. 官方驱动

    地址: https://github.com/mongodb/mongo-go-driver
    文档:https://godoc.org/github.com/mongodb/mongo-go-driver/mongo
    参考:
    MongoDB官方推出的Go驱动库“mongo-go-driver”快速教程

    截止到2019.5.24,官方驱动达到2648 star,已经超过mgo驱动了,建议使用官方版本。

    3.使用Mgo 驱动实例

    参考
    Go实战--golang中使用MongoDB(mgo)
    三、go语言操作 mongodb mgo --go语言学习笔记

    package main
    
    import (
        "fmt"
        "log"
    
        "gopkg.in/mgo.v2"
        "gopkg.in/mgo.v2/bson"
    )
    
    type Person struct {
        Name  string
        Phone string
    }
    
    func main() {
        //连接
        session, err := mgo.Dial("localhost:27017")
        if err != nil {
            panic(err)
        }
        defer session.Close()
    
        // Optional. Switch the session to a monotonic behavior.
        session.SetMode(mgo.Monotonic, true)
    
        //通过Database.C()方法切换集合(Collection)
        //func (db Database) C(name string) *Collection
        c := session.DB("test").C("people")
    
        //插入
        //func (c *Collection) Insert(docs ...interface{}) error
        err = c.Insert(&Person{"superWang", "13478808311"},
            &Person{"David", "15040268074"})
        if err != nil {
            log.Fatal(err)
        }
    
        result := Person{}
        //查询
        //func (c Collection) Find(query interface{}) Query
        err = c.Find(bson.M{"name": "superWang"}).One(&result)
        if err != nil {
            log.Fatal(err)
        }
    
        fmt.Println("Name:", result.Name)
        fmt.Println("Phone:", result.Phone)
    }
    ----------------------
    Name: superWang
    Phone: 13478808311
    

    可以看到上面的Go文件插入了两个数据,在Cmd里可以验证下(关于查询,参考MongoDB系列一(查询).):

    > use test
    switched to db test
    > show collections
    people
    > db.people.find()
    { "_id" : ObjectId("5ce799b3c6bfff8dd9776a25"), "name" : "superWang", "phone" : "13478808311" }
    { "_id" : ObjectId("5ce799b3c6bfff8dd9776a26"), "name" : "David", "phone" : "15040268074" }
    db.people.find({name:'superWang'})
    { "_id" : ObjectId("5ce799b3c6bfff8dd9776a25"), "name" : "superWang", "phone" : "13478808311" }
    
    Robo3T中查看
    (1)插入

    注意insert插入的Person结构体,自动变成小写属性写入了mongo。再看一个例子:

    type User struct {
        Id_       bson.ObjectId `bson:"_id"`
        Name      string        `bson:"name"`
        Age       int           `bson:"age"`
        JoinedAt   time.Time     `bson:"joined_at"`
        Interests []string      `bson:"interests"`
    }
    

    通过bson:”name”这种方式(也可以省略bson部分,只写"name")可以定义MongoDB中集合的字段名,如果不定义,mgo自动把struct的字段名首字母小写作为集合的字段名。如果不需要获得id_,Id_可以不定义,在插入的时候会自动生成。

    err = c.Insert(&User{
        Id_:       bson.NewObjectId(),
        Name:      "Jimmy Kuu",
        Age:       33,
        JoinedAt:  time.Now(),
        Interests: []string{"Develop", "Movie"},
    })
    

    上面可以插入自己生成id的数据,注意如果没有bson:"_id"这个标记,数据里会出现id__id两个属性,因为mongo直接把"Id_"变成小写的"id_"了。这里通过bson.NewObjectId()来创建新的ObjectId,如果创建完需要用到的话,放在一个变量中即可,一般在Web开发中可以作为参数跳转到其他页面。

    注:插入也可以使用c.Insert(bson.M{"name":"cuixx"})这种方式。

    (2)查询

    通过func (c *Collection) Find(query interface{}) *Query来进行查询,返回的Query struct可以有附加各种条件来进行过滤。通过Query.All()可以获得所有结果,通过Query.One()可以获得一个结果,注意如果没有数据或者数量超过一个,One()会报错。条件用bson.M{key: value},注意key必须用MongoDB中的字段名,而不是struct的字段名。

    //可以通过id来查询
    id := "5204af979955496907000001"
    objectId := bson.ObjectIdHex(id)
    
    user := new(User)
    c.Find(bson.M{"_id": objectId}).One(&user)
    
    //更简单的方式是直接用FindId()方法:
    c.FindId(objectId).One(&user)
    
    (3)更新

    c.Update(bson.M{"_id":objectId},bson.M{"$set":bson.M{"name":"cuixu"}}),注意修改单个或多个字段需要通过$set操作符号,否则集合会被替换。

    (4)字段增加值
    c.Update(bson.M{"_id": objectId,
        bson.M{"$inc": bson.M{
            "age": -1,
        }})
    
    (5)从数组中增加一个元素
    c.Update(bson.M{"_id": objectId,
        bson.M{"$push": bson.M{
            "interests": "Golang",
        }})
    
    (6)从数组中删除一个元素
    c.Update(bson.M{"_id": objectId,
        bson.M{"$pull": bson.M{
            "interests": "Golang",
        }})
    
    (7)删除
    c.Remove(bson.M{"name": "Jimmy Kuu"})
    
    (8)Upsert,UpsertId

    如果数据存在就更新,否则就新增一条记录:func (c *Collection) Upsert(selector interface{}, update interface{}) (info *ChangeInfo, err error)

    selector := bson.M{"key": "max"}
    data := bson.M{"$set": bson.M{"value": 30}}
    changeInfo, err := getDB().C("config").Upsert(selector, data)
    

    还有func (c *Collection) UpsertId(id interface{}, update interface{}) (info *ChangeInfo, err error)也是类似的,确定根据ID来查找,比如info, err := collection.Upsert(bson.M{"_id": id}, update)

    相关文章

      网友评论

          本文标题:Golang MongoDB

          本文链接:https://www.haomeiwen.com/subject/imhdwftx.html