分布式并发计数,以视频站点播放数统计为例(本质是{vid->count}映射关系),内容提要:
- Upsert+$INC解决并发计数
- findAndModify解决写时返回结果
- JAVA实现:findAndModify+upsert+$INC三剑客
- 谢绝ObjectId,用vid直接做_id
(1)Upsert+$INC解决并发计数
第一点:第一次update的时候,提示“ok”;但是查询的时候,发现提示“ok”其实依然失败了。这点表现了MongoDB的fire-and-forget特性,默认情况下MongoDB不执行getLastError(),只管发送写请求,不等待写请求的响应,也就是不管能否写成功。听说MongoDB并发控制策略上用了个“DB级别的全局锁(不是表级,关系型一般了不起是全表锁定,MongoDB却是DB级的)”,难道为了弥补这个影响写性能的缺陷,来了个“fire-and-forget”,这样全局锁对客户来说就的确没有等待的影响了,这是不是太投机了呀?!
第二点:第二次更新操作,多了第三个参数“true”,表示upsert标记(upsert=update+insert),语义是:如果第一个参数Query文档匹配到了,则执行update;如果没匹配到,则insert。同时第二个参数Modifier文档使用了$inc,表示原子累加操作。这两个特性的完美结合,非常适用于“互联网的PV实时统计或视频网站的播放数实时统计”。如果这个逻辑,用关系型实现,那代码要复杂许多。
(2)findAndModify解决写时返回结果
上面说了,“upsert标记+$inc修饰器”完美组合就能轻松搞定“分布式并发计数器”的应用场景(比如:PV统计,视频站点的VV统计)。但是我们以VV统计为例,一个真正实时计数器在执行写操作的时候,往往还需要同时返回更新后的VV数,也就是说写操作的同时应该伴随读操作。显然“upsert标记+$inc修饰器”组合还无法满足需求?那么MongoDB 还会不会有新特性呢?很遗憾,MongoDB的确可以有这种场景的操作“findAndModify”,但是“findAndModify”有两个遗憾(据《MongoDB权威指南》介绍):
(1) 丢失upsert特性: 可以返回更新后,或者更新前的文档的状态,但是不具备upsert标记。也就是Query必须匹配上,否则无法更新,也无法插入。
(2) 性能慢:findAndModify据《MongoDB权威指南》介绍,它的时间开销=find一次+update一次+getLastError一次,三者顺序执行所需要的时间。之前我们说,MongoDB很可能是因为“fire-and-forget”,不等待响应(响应需要通过getLastError指令获得),来规避全局锁的开销,现在findAndModify的的确确需要返回结果,所以这个开销是免不了的。
这么看来,findAndModify有点鸡肋?!《MongoDB权威指南》中介绍findAndModify的时候,举了一个例子,大概可以理解为“分布式任务队列”(分布式任务队列也是架构中常用的模型,理念上是MQ,或者AOP,适合“异步事件”的场景),用findAndModify比用乐观锁解决race condition的问题上要方便些。先不管findAndModify是否鸡肋,先看看findAndModify的API吧: http://www.mongodb.org/display/DOCS/findAndModify+Command 。
MongoDB 1.3+ supports a "find, modify, and return" command. This command can be used to atomically modify a document (at most one) and return it. Note that, by default, the document returned will not include the modifications made on the update.
If you don't need to return the document, you can use Update (which can affect multiple documents, as well).
官方建议:如果你打算用findAndModify,而不用update,那么一个前提条件是:你想知道更新后的结果(文档状态,不仅仅是getLastError信息)。如果你并不想知道更新后的结果,那么你更应该选择update操作。
Oh, my god,看到上面这幅图,我只能说MongoDB很年轻,因为年轻有很多鸡肋式的不足,因为年轻更有日新月异的成长。
(3)JAVA实现:findAndModify+upsert+$INC三剑客
我们简单来段JAVA代码,开发包是:
https://github.com/mongodb/mongo-java-driver/blame/master/src/main/com/mongodb/DBCollection.java
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
public static void main(String[] args) throws Exception {
String host = "XXX";
int port = XXX;
String dbname = "XXX";
String username = "XXX";
String passwd = "XXX";
Mongo mongo = new Mongo(host, port);
DB db = mongo.getDB(dbname);
boolean authed = db.authenticate(username, passwd.toCharArray());
System.out.println("认证:"+authed);
System.out.println("DB下所有集合:"+db.getCollectionNames());
//CH3: 获取DB
String collectionName = "vv_stat";
DBCollection collection = db.getCollection(collectionName);
//public DBObject findAndModify(DBObject query, DBObject fields, DBObject sort, boolean remove, DBObject update, boolean returnNew, boolean upsert)
//REFER: https://github.com/mongodb/mongo-java-driver/blame/master/src/main/com/mongodb/DBCollection.java
String vid = "10086";
int count = 38;
BasicDBObject query = new BasicDBObject().append("vid", vid);
BasicDBObject fields = new BasicDBObject().append("vid", 1).append("count", 1);
BasicDBObject sort = new BasicDBObject().append("vid", 1);
boolean remove = false;
BasicDBObject update = new BasicDBObject().append("$inc", new BasicDBObject("count",count));
boolean returnNew = true;
boolean upsert = true;
DBObject r = collection.findAndModify(query,fields,sort,remove,update,returnNew,upsert);
System.out.println("统计结果:"+r);
}
输出:
统计结果:{ "_id" : { "$oid" : "5029dc79bc7ee3893a74cffe"} , "count" : 38 , "vid" : "10086"}
很完美了!至少功能上是完全符合我们的需求了,性能的问题暂不理会。
(4)谢绝ObjectId,用vid直接作为_id
上面的输出{ "_id" : { "$oid" : "5029dc79bc7ee3893a74cffe"} , "count" : 38 , "vid" : "10086"},我们看到插入的文档除了vid和count,还有_id,因为每个Doc都必须有_id,而且它是唯一索引。播放数统计就是从vid到count映射关系,数据量大了,为了提高查询效率,我们要对vid做索引,而且是唯一索引,那为什么要浪费原有的_id呢?其实_id天然就是NoSQL特性之Key/Value的友好支持。于是,我们应该把vid存储在_id上。
BasicDBObject query = new BasicDBObject().append("_id", vid);
BasicDBObject fields = new BasicDBObject().append("_id", 1).append("count", 1);
BasicDBObject sort = new BasicDBObject().append("_id", 1);
boolean remove = false;
BasicDBObject update = new BasicDBObject().append("$inc", new BasicDBObject("count",count));
boolean returnNew = true;
boolean upsert = true;
DBObject r = collection.findAndModify(query,fields,sort,remove,update,returnNew,upsert);
System.out.println("统计结果:"+r);
输出:“统计结果:{ "_id" : "10086" , "count" : 38}”
- 大小: 39.3 KB
- 大小: 12.1 KB
分享到:
相关推荐
分布式数据库面试专题系列:Redis+MongoDB
分布式数据库面试专题系列:Memcached+Redis+MongoDB.zip
NoSQL 数据库中 MongoDB 模块的演示和来源安装以下是运行演示所需的不同系统的安装过程。通过从 Github 克隆来安装存储库: 运行以下脚本: sudo yum -y install gitcdgit clone ...
分布式存储数据库MongoDB教程
大数据高并发实战架构(Mongodb实现)、网站大访问量、mysql索引优化、反向代理
MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。
使用scrapy,redis,mongodb实现的一个分布式网络爬虫[整理].pdf
hyperf mongodb pool composer require yumufeng/hyperf-mongodb config 在/config/autoload目录里面创建文件 mongodb.php 添加以下内容 return [ 'default' => [ 'username' => env('MONGODB_USERNAME', ''), '...
可以用于毕业设计(项目源码+项目说明)目前在window10/11测试环境一切正常,用于演示的图片和部署教程说明都在压缩包里
安装:mongod --dbpath=D:\mongodb\db --logpath=D:\mongodb\log\mongo.log= --install 卸载:mongod.exe --remove 最近准备把空闲时间都发在mongodb的研究上,因此将有一系列的文章记录这个过程。 直接从官网...
file-demo用nodejs+mongodb实现文件管理的demo使用express框架需要先运行npm install
mongodb游乐场 :books: 学习和探索MongoDB。 注意:这是在macOS上开发的。 指示 作为先决条件,您必须安装Mongo。 有关选项,请参见“部分。 启动MongoDB服务器 使用来自佐治亚州(GA)的邮政编码测试数据加载...
1,创建logs文件夹,在下面创建log日志文件:C:\MongoDB\Server\3.4\data\logs\mongo.log 2、按照这个路径创建文件夹:C:\MongoDB\Server\3.4\data\db 3.配置环境变量 2.以管理员方式开启cmd 3.进入mongodb的bin...
mongodb中文API及分布式分片实例详解。
Spring集成MongoDB官方指定jar包:spring-data-mongodb-1.4.1.RELEASE.jar
奥尔良提供者的MongoDb实现。 这包括 成员资格( IMembershipTable和IGatewayListProvider ) 提醒( IReminderTable ) 存储( IStoragePRovider ) 安装 install-package Orleans.Providers.MongoDB 建立 安装...
使用C#,MongoDb实现存储库模式。 请使用链接查找更多信息:
因为MongoDB的文档有数组字段,所以可以简单的将计算总和分成两种: 1,统计符合条件的所有文档的某个字段的总和; 2,统计每个文档的数组字段里面的各个数据值的和。这两种情况都可以通过$sum表达式来完成。 以上两...
命令行: mongod --dbpath "D:\java\mongodb\data" --logpath "D:\java\mongodb\log\mongod.log" --install -serviceName "MongoDB" golang安装、配置 npm设置代理 proxy、配置国内源 Windows执行: npm install ...