分布式并发计数,以视频站点播放数统计为例(本质是{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的Redis Cluster是如何实现分布式存储的,MongoDB如何进行Sharding,以及Memcached如何与应用程序集成以提高效率。 理解这些数据库的核心概念、操作和最佳实践,对于任何希望在分布式存储和数据库领域...
分布式数据库在现代软件架构中扮演着至关重要的角色,特别是在处理大数据量、高并发场景时。本专题将聚焦于两个常见的分布式数据库系统——Redis和MongoDB,深入探讨它们的设计原理、应用场景以及面试中常被问到的...
**JSP图片计数器**是一种在Web应用中常见的技术,用于实时统计页面访问量或者用户行为次数。基于Eclipse实现的图片计数器利用JavaServer Pages(JSP)技术,能够动态生成带有访问计数的图片。这种计数器不仅能够记录...
1. **连接统计**:监控MongoDB服务器的当前连接数,这有助于识别服务器是否承受过大的负载。 2. **操作计数器**:展示MongoDB的各种操作(如读写操作)的计数,可以反映出数据库的活动水平。 3. **内存使用**:监控...
- 对于高并发场景,考虑使用分布式锁来避免计数冲突。 6. **可视化展示**: - 设计美观且直观的用户界面,用HTML/CSS/JavaScript展示计数结果。 - 可以使用图表库如D3.js或ECharts来创建动态的统计图表,展示...
- 分布式系统:通过负载均衡和分布式计算,提高统计处理能力。 - 缓存策略:使用Redis等缓存系统,减轻数据库压力,提升响应速度。 8. 安全性: - 防止刷数据:设定合理的访问频率限制,检测并屏蔽异常访问行为...
3. 高并发处理:Redis的原子操作和发布/订阅功能可以帮助处理高并发场景,比如实现计数器、限流或者分布式锁。 4. 数据备份与恢复:了解如何备份MongoDB和Redis的数据,以及在系统故障时如何快速恢复,对于系统的...
在构建动态网站时,计数器是一个常见的功能,它能够记录页面的访问量,为用户提供实时的访问统计信息。本文将深入探讨如何使用Java来实现这样的动态计数器,并解析其实现过程的关键技术。 首先,计数器的核心是数据...
可以采用分布式协调服务(如Zookeeper)来维护全局计数器,或者利用分布式缓存(如Memcached)进行数据共享。 9. **监控与报警**:设置监控系统,实时查看在线人数的变化,当达到预设阈值时触发报警,以便及时调整...
Java高级工程师面试题涵盖了许多关键领域,旨在评估候选人在编程、设计模式、并发处理、内存管理、性能优化、框架理解及应用等方面的专业能力。以下是一些可能出现在面试中的主题和相关知识点: 1. **Java基础知识*...
- 内存区域:堆、栈、方法区、程序计数器、本地方法栈。 - 垃圾回收:GC算法、垃圾收集器、内存调优。 6. **网络编程**: - TCP/IP协议:三次握手、四次挥手、拥塞控制、TCP与UDP区别。 - HTTP/HTTPS:请求响应...
Redis支持多种数据结构,如字符串、哈希、列表、集合和有序集合,这使得它在实现如计数器、队列、发布/订阅系统等功能时非常灵活。在Node.js项目中,Redis常用于实现会话管理、缓存管理和实时数据分析。 **MongoDB*...
- 分布式计数:对于高并发场景,可以采用分布式计数算法如Hadoop MapReduce或者Google的Bigtable分布式计数器,来处理大量点赞请求。 - 系统监控与报警:通过监控系统性能指标,如CPU使用率、内存占用、网络延迟...
- **分布式ID生成**:Snowflake、UUID、MongoDB的ObjectID等。 10. **项目经验与问题解决能力** - **项目架构设计**:微服务、SOA、MVC等架构模式。 - **异常排查与性能调优**:日志分析、监控工具的使用。 - *...
Java作为一门广泛使用的编程语言,其面试题涵盖了多个核心领域,包括数据结构与算法、Java基础知识、多线程与并发、Linux使用与问题分析、框架、数据库相关、网络协议和网络编程、微服务以及JVM。以下是这些领域的...
- HBase、MongoDB和Cassandra是分布式NoSQL数据库,适合大数据场景。 9. 设计模式: - 设计模式是解决软件设计中常见问题的经验总结,如单例、工厂、观察者、装饰者等23种经典设计模式。 10. 数据结构与算法: ...
- 内存模型:包括堆内存、栈内存、方法区、本地方法栈和程序计数器。 - 垃圾回收:GC的基本原理,如新生代、老年代、Minor GC和Full GC的区别。 - 调优:JVM参数调整,如-Xms, -Xmx, -XX:NewRatio等。 2. **多...
连接数:大量的连接请求会消耗资源并可能影响性能。 **4. 用于数据库恢复的重要文件** - **知识点概述:** - 日志文件是数据库恢复过程中的关键组件,它记录了所有对数据库所做的修改。 - **选项解析:** - A....