我们选用 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每天为他们的用户收集万亿比特的数据。这些数据的存储不需要固定的模式,无需多余的操作就可以横向扩展。
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和配置路径
如果你选择不将 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是个可视化工具,如果勾选安装的话是在线下载安装的,据说有的人安装一整晚都没装好就是因为这个。我这里是取消勾选的,因为之后我会装别的工具使用
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.可视化工具
如果你是Mongo的企业版用户,不如尝试MongoDB的官方GUI:MongoDB Compass
我最终选了RoboMongo,现在已经改名为robo 3t。以下参考MongoDB可视化工具--Robo 3T 使用教程
下载后一路next,然后默认连接就能看到数据了。
五、在go中使用
1. Mgo 驱动
地址: https://godoc.org/labix.org/v2/mgo或https://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" }
(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)