mongodb mapreduce主要方法详解

mongodb在分组统计用途比较大,相当于mysql里面的group by,但比group by 功能更强大。

db.collection.mapReduce( //要操作的目标集合
	<map>,//映射函数 (生成键值对序列,作为 reduce 函数参数)
	<reduce>,//统计函数
	{
		out: <collection>,//统计结果存放集合 (不指定则使用临时集合,在客户端断开后自动删除)。必须要小于BSON文档的大小(16M)限制
		query: <document>,//目标记录过滤。
		sort: <document>,//目标记录排序。
		limit: <number>,//限制目标记录数量。
		finalize: <function>,//最终处理函数 (对 reduce 返回结果进行最终整理后存入结果集合)。
		scope: <document>,//向 map、reduce、finalize 导入外部变量。
		jsMode: <boolean>,//从BSON转化为JSON,执行Map过程,将JSON转化为BOSN,从BSON转化为JSON,执行Reduce过程,将JSON转化为BSON,注意,jsMode 受到JSON堆大小和独立主键最大500KB的限制。因此,对于较大的任务jsMode并不适用,在这种情况下会转变为通常的模式
		verbose: <boolean>,//显示详细的时间统计信息。
		bypassDocumentValidation: <boolean>
	}
)

数据准备

db.emp.insert({level:"t1", age:24, name:'Tom'});
db.emp.insert({level:"t2", age:22, name:'Jacky'});
db.emp.insert({level:"t3", age:26, name:'Lily'});
db.emp.insert({level:"t2", age:29, name:'Tony'});
db.emp.insert({level:"t3", age:21, name:'Harry'});
db.emp.insert({level:"t4", age:35, name:'Vincent'});
db.emp.insert({level:"t2", age:25, name:'Bill'});
db.emp.insert({level:"t3", age:25, name:'Bruce'});
db.emp.insert({level:"t1", age:25, name:'tencent'});
db.emp.insert({level:"t4", age:25, name:'baidu'});
map
map函数必须调用 emit(key, value) 返回键值对。map里面有如下限制和要求:
1:In the map function, reference the current document as this within the function.
this为当前处理的文档
2:The map function should not access the database for any reason.
不应该访问任何非this的其它集合,事实上也没办法访问,用db变量访问会直接报错
3:The map function should be pure, or have no impact outside of the function (i.e. side effects.)
不能的影响其它功能
4:A single emit can only hold half of MongoDB’s maximum BSON document size.
emit的容量只能试文档容量的一半,也是8MB,理论上讲分组后不会存在这么大的文档。
5:The map function may optionally call emit(key,value) any number of times to create an output document associating key with value.

reduce
通过map运算后,key为分组字段,其value的集合(即数组)会传给reduce函数执行
特别注意:若value集合长度小于2,则该分组不会传到reduce函数执行,任何需要在reduce里面对key就行统计操作的,需要特别注意,有可能会以后key。上面的示例数据,可以删掉最后两条看看效果。t1和t4并不会再reduce里面出现。

finalize
利用 finalize() 我们可以对 reduce() 的结果做进一步处理。处理完毕后要返回给存储,可以自定义结果。

案例
公司职员有t1,t2,t3,t4不同的职位等级,有职员名字和年龄。
现在需要统计不同的职位的人员的个数

m = function () {
	total = total +1
	// if(this.age==25){
		emit(this.level, 1);
	// }
	print("total in map:"+total)
}
r = function (key, values) {
	total = total +1
	var x = 0;
	print(separator)
	print(key+separator+values)
	// print(values)
	values.forEach(function (v) {
		x += v;
		// print(v);
	});
	print("total in reduce:"+total)
	return x;
}
res = db.runCommand({
	mapreduce:"emp",
	map:m,
	reduce:r,
	// query:{"age":25},
	// sort:{"age":-1},
	// limit:2,
	// verbose:true,
	finalize:function(key, reducedValue) {
		total = total +1
		print(key+separator+reducedValue)
		print("total in finalize:"+total)
		return reducedValue;
		// return {level:key,count:reducedValue};
	},
	scope:{separator:"\t",total:0},
	out:"emp_result"
});

其中打印的日志过程为:
2016-12-12T20:38:05.536+0800 I - [conn160] total in map:1
2016-12-12T20:38:05.536+0800 I - [conn160] total in map:2
2016-12-12T20:38:05.536+0800 I - [conn160] total in map:3
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:4
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:5
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:6
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:7
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:8
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:9
2016-12-12T20:38:05.537+0800 I - [conn160] total in map:10
2016-12-12T20:38:05.537+0800 I - [conn160]
2016-12-12T20:38:05.537+0800 I - [conn160] t1 1,1
2016-12-12T20:38:05.537+0800 I - [conn160] total in reduce:11
2016-12-12T20:38:05.537+0800 I - [conn160]
2016-12-12T20:38:05.537+0800 I - [conn160] t2 1,1,1
2016-12-12T20:38:05.538+0800 I - [conn160] total in reduce:12
2016-12-12T20:38:05.538+0800 I - [conn160]
2016-12-12T20:38:05.538+0800 I - [conn160] t3 1,1,1
2016-12-12T20:38:05.538+0800 I - [conn160] total in reduce:13
2016-12-12T20:38:05.538+0800 I - [conn160]
2016-12-12T20:38:05.538+0800 I - [conn160] t4 1,1
2016-12-12T20:38:05.538+0800 I - [conn160] total in reduce:14
2016-12-12T20:38:05.539+0800 I - [conn160] t1 2
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:15
2016-12-12T20:38:05.539+0800 I - [conn160] t2 3
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:16
2016-12-12T20:38:05.539+0800 I - [conn160] t3 3
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:17
2016-12-12T20:38:05.539+0800 I - [conn160] t4 2
2016-12-12T20:38:05.539+0800 I - [conn160] total in finalize:18
统计的结果为:

/* 1 */
{
"_id" : "t1",
"value" : 2
}

/* 2 */
{
"_id" : "t2",
"value" : 3
}

/* 3 */
{
"_id" : "t3",
"value" : 3
}

/* 4 */
{
"_id" : "t4",
"value" : 2
}

通过例子可以总结为(我自己的理解,有可能是错的):

1.大致的执行过程为map->过滤条件->reduce->限制条件->finalize
2.query不是必须的,可以在map里面也可以进行过滤
3.通过print可以打印,在日志里面查看打印结果
4.通过日志(drop applock.tmp.mr.emp_59)可以看出,中间的临时结果会被删除
5.scope的变量是全局变量,可以在map,reduce,finalize中间共享

思考:单一职责原则(Single Responsibility Principle)

一个类应该有且只有一个变化的原因,我在这里要提出一些不一样的意见,如果完全按照这个原则来设计程序,那么会是这样的:
1.一个类只有一个方法,并且这个方法非常简单,没有多余的功能。一个项目会有非常多的类。
2.由于每个类都只有一个方法,那么总有一个类去调用我们之前定义好的类,这个时候,这个类被定义为了更大的职责。
3.面向对象里面的继承,貌似在某种程度上违反了SRP这个原则

思考:敏捷软件开发宣言和遵循的12条准则

这几天丁磊:求快是创业者的误区,过于强调时间窗口是伪命题(http://www.managershare.com/post/310461?from=groupmessage),这篇文章被大家转载了很多,他的核心观点为:
1.很多创业者都是打着创新的幌子做很多贪婪的事情,
2.只求做第一个吃螃蟹的人,往往不顾自己实际发展状况,
3.求快不求精,看见竞争对手做什么自己也按耐不住
这也引起了我对敏捷开发的思考,估计互联网的创业公司都在 用敏捷开发,那么,敏捷开发固有的原则和丁磊说的在某些方面略有冲突。下面我就敏捷开发的宣言和准则做一些思辨的过程:

2001年,17位编程大师共同发布《敏捷软件开发宣言》:
“人”以及“人与人的互动” 胜于 “过程”和”工具”
Individuals and interactions over processes and tools
#赞同,过程和工具毕竟是人去设计的,并不具有人类发达的思维和表达能力,敏捷突出快和沟通。
可运行的软件 胜于 面面俱到的文档
Working software over comprehensive documentation
#不是很赞同,文档很重要,但是否需要面面俱到,其实是因行业而异,比喻金融、医疗、高精密的行业的开发,他们需要的不仅是能运行的软件,而且是需要能精准稳定运行的软件,没有面面俱到的文档,恐怕这些都很难得到保障。
客户合作 胜于 合同谈判
Customer collaboration over contract negotiation
#不知道这个是在说什么
响应变化 胜于 遵循计划
Responding to change over following a plan
#非常赞同,是人都会犯错,区别在于犯错的大小和时机不同而已,大大小小这么公司,这么多产品经理,他们的需求是否正确,这些都得打一个问号,我一直认为,产品经理比研发重要的多,一定要是一个十分聪明,善于沟通,略懂技术研发,如果能对行业动态保持高度的敏锐的嗅觉,那我觉得这个产品成功的概率就比较大了。所以在迭代的过程中,知错就改是一个好的心态和应有的技能。

遵循的12条准则:
1.我们最优先要做的是通过尽早的、持续的交付有价值的软件来使客户满意。
#有价值很重要,持续为辅
2.即使到了开发的后期,也欢迎改变需求,敏捷过程利用变化来为客户创造竞争优势。
#能正确的变化
3.经常性地交付可以工作的软件,交付的间隔可以从几个星期到几个月,交付的时间间隔越短越好。
#保留意见,交付的东西有意义才行,丁磊也说道了这个问题
4.在整个项目开发期间,业务人员和开发人员必须天天都在一起工作。
#天天在一起但是没有有效的沟通也没用
5.围绕被激励起来的个体来构建项目。给他们提供所需的环境和支持,并且信任他们能够完成工作。
#非常赞同
6.在团队内部,最具有效果并且富有效率的传递信息的方法,就是面对面的交流。
#赞同,但是一定要带有自己成熟的思考和别人去交流,并不是浪费别人的时间。
7.工作的软件是首要的进度度量标准。
8.敏捷过程提倡可持续的开发速度。责任人、开发者和用户应该能够保持一个长期的、恒定的开发速度。
#赞同,有松有紧,有节奏,但是不要把人都作为机器来运转就可以了
9.不断地关注优秀的技能和好的设计会增强敏捷能力。
#赞同,开发的过程中,遇到了问题要搞明白,同时也要专注相关的技术。
10.简单--使未完成的工作最大化的艺术---是根本的。
#赞同,敏捷是快速开发,在这个过程中,需求以及架构都不是稳定的,所以快、准、稳很重要。
11.最好的构架、需求和设计出自于自组织的团队。
12.每隔一定时间,团队会在如何才能更有效地工作方面进行反省,然后相应地对自己的行为进行调整。
#赞同,但是这个总结一定是非常Open的,放下所有的防备、等级、角色其实是非常困难。很多时候都是形式主义,因为很多因素往往可能是该表达的没有表达出来,有时候甚至是矫枉过正。都甚至都认为这个环节在中国并不适用,水土不服。

使用maxmind离线数据得到国家位置信息(python)

maxmind提供在线和离线的数据地理位置服务,http://dev.maxmind.com/geoip/,有收费的也有免费的,我这里的需求相对简单,只用根据IP得到国家即可,这里没有经过nginx,是后续扫描从一个存储好的日志里面根据IP得到国家的信息。

当然有很多实现的版本,这里讲如何利用python去实现,https://github.com/maxmind/GeoIP2-python

在虚拟环境里面按照geoip2,目前使用的python环境是2.6

pip install geoip2

下载免费的IP数据包,http://dev.maxmind.com/geoip/geoip2/geolite2/,我这里使用的是country级别的,有需要用到city粒度的可以使用city这个包,下载好后放到一个路径下面,程序里面会引用,简单的代码为:

import geoip2.database
reader = geoip2.database.Reader('/opt/GeoLite2-Country.mmdb')
response = reader.country('183.14.31.175')
print(response)

得到的结果为:

{
	'traits': {
		'ip_address': '183.14.31.175'
		},
	'country': {
		'geoname_id': 1814991,
		'iso_code': 'CN',
		'names': {
			'r': '\u041a\u0438\u0442\u0430\u0439', 
			'fr': 'Chine', 
			'en': 'China', 
			'de': 'China', 
			'zh-CN': '\u4e2d\u56fd', 
			'pt-BR': 'China', 
			'ja': '\u4e2d\u56fd', 
			'es': 'China'
			}
		}, 
	'continent': {
		'geoname_id': 6255147, 
		'code': 'AS', 
		'names': {
			'r': '\u0410\u0437\u0438\u044f', 
			'fr': 'Asie', 
			'en': 'Asia', 
			'de': 'Asien', 
			'zh-CN': '\u4e9a\u6d32', 
			'pt-BR': '\xc1sia', 
			'ja': '\u30a2\u30b8\u30a2', 
			'es': 'Asia'
			}
		}, 
	'registered_country': {
		'geoname_id': 1814991, 
		'iso_code': 'CN', 
		'names': {
			'r': '\u041a\u0438\u0442\u0430\u0439', 
			'fr': 'Chine', 
			'en': 'China', 
			'de': 'China', 
			'zh-CN': '\u4e2d\u56fd', 
			'pt-BR': 'China', 
			'ja': '\u4e2d\u56fd', 
			'es': 'China'
			}
		}
}

返回的结果里面废话挺多的,直接拿到country的iso_code即可。

 

mysql常见错误

错误一:mysql(mariadb): unknown variable 'character_set_server=utf8'
在mariadb里面设置字符编码的时候,client和server还不太一样,下如下这样设置就可以了

[client]
default-character-set=utf8
[mysqld]
character_set_server=utf8
 

错误二:MySQL ERROR 1005: Can't create table (errno: 150)
1、外键的引用类型不一样,如主键是int外键是char
2、找不到主表中引用的列
3、主键和外键的字符编码不一致,也可能存储引擎不一样

错误三:The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
在导出数据到目录的时候会遇到上面的错误,具体的语句为:
SELECT * FROM ciika INTO OUTFILE '/tmp/ciika.txt';
可以修改my.ini或者直接导出到mysql的目录
mysql> SELECT @@GLOBAL.secure_file_priv;
+---------------------------+
| @@GLOBAL.secure_file_priv |
+---------------------------+
| /var/lib/mysql-files/ |
+---------------------------+
修改语句为:SELECT * FROM ciika INTO OUTFILE '/var/lib/mysql-files/ciika.txt';

错误四:SQLyog连接数据库报错plugin caching_sha2_password could not be loaded
ALTER USER 'root'@'localhost' IDENTIFIED BY 'password' PASSWORD EXPIRE NEVER; #修改加密规则 

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password'; #更新一下用户的密码 

FLUSH PRIVILEGES; #刷新权限

上面三个都需要执行,亲测可用