有关 MongoDB 是什么,MongoDB 如何用,如何发挥最大优势的相关问题,欢迎大家交流探讨。
和时间一起做 MongoDB 的朋友
我是在 2010 年的一期程序员杂志上开始接触 MongoDB 数据库和 Nosql 的概念,当时感觉很新奇,并不明白具体的用途和优势,直到 2013 年才有机会真正的使用和了解。
初识 MongoDB
当时的环境是 Windows 平台与 C#,在一个基于内容的网站首页功能开发中,最初希望能够提高列表的响应速度,这样一个契机,有机会把 MongoDB 应用到具体项目中。一句话概括
“听说 MongoDB 快,所以开始用 MongoDB 做数据库,用小功能做验证。
整个业务的小部分功能数据存储使用 MongoDB,其余大部分功能数据存储使用 SqlServer。从 MongoDB 里取列表数据,可以理解为数据的预加载,也可以理解为数据的缓存。
除了速度快之外,另外一个感受是 MongoDB 的日志量很大。云计算公有平台的概念逐渐提出,RDS 等云数据库就是当时那个阶段的产品概念,记得当时我想在云平台上找一款 MongoDB 云数据库,找不到。现在在各个平台的云产品中,MongoDB 应该是标配了。
进阶使用
物联网领域
随后同样是工作的机会,我把 MongoDB 的使用扩展到了物联网领域,用于存储不同产品的差异性属性,属性无法统一,还存在着需求的反复变化,MongoDB 正好有宽表的概念和集合按需存储的提倡。
图1-智趣健康feed
图 1 是一款智能硬件 APP 的效果图,简单的展开来讲,基于智能硬件的产品连接硬件,用户以及业务系统,涉及到硬件信息(即源数据收集),使用者(用户信息),业务处理(社交聚合,边缘计算处理,日统计,地理位置)等。
图2-产品功能模型
图 2 是产品的一些功能对象属性
我们可以看到,随着产品的侧重点,业务发展阶段不同,业务对象的属性是多变和不确定的。
这种场景正是 MongoDB 的嵌套模型和模式自由的用武之地。对于社交 APP 的 Feed 流查询,时序数据的采集和统计都可以友好的支持。
在可控的范围之内,这里的可控是说 MongoDB 本身的存储规则,例如单文档最大存储限制。
使用者在组织产品功能,开发实现业务系统时,不需要在数据集合的修改和维护上花费太多功夫。
总结下来是以下几点:
1 程序可以自行创建集合,不需要在程序执行前预处理。
2 集合中数据的字段数目不需要保持统一,并且被提倡为按需存储。
3 关联关系借助于嵌套包含模型单集合存储,查询友好,提高程序性能,降低联合查询复杂度。
灵活的数组模型
一个集合中的嵌套,层级,关联使用,免不了提到数组。这里想重点说一下数组模型,在我看来 MongoDB 的数组模型可以 广泛的应用在基于父子结构,组织员工分组等经典的 1 对多业务领域中。
以下是员工分组的一对多数据模型案例
用于企业员工组织架构和工作组的分配管理,包含组信息和员工信息两部分,员工信息是一个数组集合
Data Model
代码语言:javascript复制"createTime": ISODate("2017-08-23T17:15:56.173 08:00"),
"groudId": 10,工作组编号
"users": [ 员工数据节点
{
"name": "wangmm",
"uid": 1240,
"email": "610212129@qq.com",
"status": 1,
"edit": ISODate("2017-08-16T00:00:18.685 08:00"),
"scope": 1,
"desc": "服务人员"
},
{
"name": "lmd",
"uid": 1241,
"email": "yy699@sina.com",
"status": 1,
"edit": ISODate("2017-08-16T00:00:18.685 08:00"),
"scope": 2,
"desc": "管理员"
},
]
我把这种设计模型进行了抽象,适合于总和模式,包含模式等多种业务场景。
除了上文提到的,还可以想到的有
1 每个商圈下的店铺信息集合
2 每个仓库关联的摄像头监控硬件设备集合 ...
基于数组模型,可以做如下几个典型的操作
“使用 和pull 追加,删除数组元素
使用$push 操作符将子元素追加到集合元素末尾,也就是 1:N 的 N。
使用$pull 移除单个子元素
以下是一段参考代码:
代码语言:javascript复制 $where = [
'groupId' => 20
];
$result = static::getCollection()->update(
$where,
['$pull' => ['user' => ['uid' => 309]]],
['multi' => true]
);
return $result > 0;
“使用$unwind 聚合分离数组元素
如果按照组员 Id 查询 如下
代码语言:javascript复制db.getCollection('collectionname').find({'user.uid':519})
返回的结果是整个集合,会包含整个数组,我们可以使用 unwind 操作符解决这个问题,进行精确输出。
$unwind 实现对 1:N 存储的集合实现 1:1 的输出,这样就可以做分页列表,条件查询了。避免了复杂的连接查询和不必须的冗余输出,总是好的。
以下是一段参考代码:
代码语言:javascript复制 $ops = [
[
'$unwind' => '$user',
],
[
'$project' =>
[
'user' => 1,
'groupId' => 1,
'name' => 1,
'_id' => 0,
'uid' => 1,
],
],
[
'$match' =>
[
'user.uid' => $userId,
],
],
];
$collection = self::_aggregateInstance();
return $data = $collection->aggregate($ops);
凌厉的数据聚合
基于基础业务数据的沉淀和收集,我们可以做一些统计分析,运营支持相关的数据操作,MongoDB 中的聚合就是强有力的工具助手。
聚合(Aggregation)提供分组和统计文档的功能。算是 MongoDB 中的进阶使用。关于聚合,网络上还有一些资料,说通过 key reduce 函数实现,这种方式已经被放弃了。官方推荐采用管道实现聚合。
代码语言:javascript复制db.collection.aggregate(pipeline, options)
业务抽象场景如下:
“某个商店按时间维度对订单进行统计,排序等管理
Data Model
代码语言:javascript复制{
"_id" : ObjectId("5a6d08f38919090e847b659b"),
"obtain_time" : ISODate("2018-01-25T16:00:00.000Z"),
"datepark" : "201801261618",
"create_time" : ISODate("2018-01-27T23:19:15.000Z"),
"epay_count" : 15,
"pay_date" : ISODate("2018-01-25T16:00:00.000Z"),
"pre_week_e_count" : 30,
"state" : 2,
"epay_week_rate" : 50.0,
"storeid" : 1618,
"storename" : "解放路SOHO店",
"address" : "解放路66号",
"city_id" : 579
}
原始数据是一天一条数据,业务要求按照店铺做聚合,统计每周,每月的销售订单。
实现方式,式例方法如下
基于日统计的原始数据,聚合字段进行 sum 等操作,group 参数如下
代码语言:javascript复制 /*
* 根据店铺编号聚合订单数
*
* */
public function aggregateStoreEpay($startTime,$endTime,$storeId)
{
$mongo = Mongodb::getInstance();
$ops = [
[
'$match' => [
'state' => ['$eq' => 2],
'storeid'=>['$eq'=>$storeId],
'date_time' => ['$gte' => $conditions["start_time"],
'$lte' => $conditions["end_time"]]
]
],
[
'$group' => [
'_id' => ['storeid' => '$storeid'],
'sum' => ['$sum' => 1],
'date_time' => ['$push' => '$pay_date']
]
],
[
'$sort' => [
'pay_date' => -1
]
],
[
'$limit' => $limit
]
];
$collection = $mongo->dbname->epay_stat;
$data = $collection->aggregate($ops);
return $data['result'];
}
同系统多数据库产生的数据同步问题
在一个技术团队中,当技术决策者决定使用 MongoDB 时,除非是全新的项目,不然大多数属于探索性使用,按功能模块一步一步的迁移调整。即使是全新项目,基础的行业数据,核心业务数据,也难免不和关系型数据库做交互。
对于业务来说,用户需要一个完整的业务场景,而数据会被分散到 MongoDB 和 Mysql 或者(SqlServer 中),也就是 Sql 和 Nosql 共存。
这种情况会出现数据相关问题,我们思考下边的场景:
“查询展示列表页面,数据源分散在不同的数据库
数据源不同,数据的展示涉及到组装和整合。数据展示时数据源从哪里取,是使用时从不同的库同步取还是提前把数据存储到一个统一的数据源,从一处取?
前者有查询的数据性能问题,后者有数据同步的维护延迟问题,如何选择?
在以往的使用过程中,我也遇到过类似的问题,得出的结论是,在开发初期做好规划,整块的数据尽量放到一处,也就是说不要把业务分的太散。
如果已经遇到类似场景,数据源不一致,暴露性能问题是迟早的事,前期将数据同步的延迟控制在业务方可以忍受的范围内,得业务成熟后,最好能够逐步统一到 MongoDB 平台,当然这样研发成本和时间的花销是不可避免的。
小有心得
借用之前收藏的一张图,做一个简短的总结。
图3 MongoDB 应用场景
优势梳理
在我看来,对于互联网业务系统,特别是靠近用户侧的前端应用系统,MongoDB 丰富的数据结构,可以轻松应对多变的需求和复杂的使用场景。
为什么是靠近用户侧,靠近用户侧代表着灵活和多变,特别是近两年中台设计的提出,本质上也是在降低协作和开发成本,推进应用落地的灵活性,为业务赋能。
模式自由的特性并不代表集合无设计,相比较关系型数据库设计,根据现实事物的业务属性映射生成集合的基本字段,基本的设计原则还是要遵循。
集合结构修改调整不需要 DBA 着重参与,减少沟通成本,加快版本更新迭代速率,DBA 们可以把精力投入到数据库运维层面架构设计上,复制集的健壮性,索引优化,数据备份,故障预警等其它方面。
在问题中成长
学习 MongoDb 数据库的基本姿势,边学习,边实践,边参考,边改进,在问题中成长。
在业务的推动下,对一项技术的学习,力求能把技术的优势尽量的发挥出来,本身就是一个磨合,演化,学习的过程。
如果大家关注 MongoDB 官方的一些产品动态,可以发现 MongoDB 自身也在不断的扩展 数据处理,事务支持,云原生等方面的产品形态,版本在不断的升级。相较于个人的成长也是这样的。
更多的使用场景,需要一起探索和实践。