如今,大多数的计算机系统(包括服务器、PC、移动设备等)都会产生庞大的数据量。其实,早在2012年的时候,全世界每天产生的数据量就达到了2.5EB(艾字节)。这些数据有很大一部分是由关系型数据库来存储和管理的。实践证明,关系型数据库是实现数据持久化最为重要的方式,它也是大多数应用在选择持久化方案时的首选技术。
NoSQL 是一项全新的数据库革命性运动,虽然它的历史可以追溯到1998年,但是NoSQL真正深入人心并得到广泛的应用是在进入大数据时候以后,业界普遍认为NoSQL是更适合大数据存储的技术方案,这才使得NoSQL的发展达到了前所未有的高度。2012年《纽约时报》的一篇专栏中写到,大数据时代已经降临,在商业、经济及其他领域中,决策将不再基于经验和直觉而是基于数据和分析而作出。事实上,在天文学、气象学、基因组学、生物学、社会学、互联网搜索引擎、金融、医疗、社交网络、电子商务等诸多领域,由于数据过于密集和庞大,在数据的分析和处理上也遇到了前所未有的限制和阻碍,这一切都使得对大数据处理技术的研究被提升到了新的高度,也使得各种NoSQL的技术方案进入到了公众的视野。
NoSQL数据库按照其存储类型可以大致分为以下几类:
类型 | 部分代表 | 特点 |
---|---|---|
列族数据库 | HBase Cassandra Hypertable | 顾名思义是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的I/O优势,适合于批量数据处理和即时查询。 |
文档数据库 | MongoDB CouchDB ElasticSearch | 文档数据库一般用类JSON格式存储数据,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能,但不提供对参照完整性和分布事务的支持。 |
KV数据库 | DynamoDB Redis LevelDB | 可以通过key快速查询到其value,有基于内存和基于磁盘两种实现方案。 |
图数据库 | Neo4J FlockDB JanusGraph | 使用图结构进行语义查询的数据库,它使用节点、边和属性来表示和存储数据。图数据库从设计上,就可以简单快速的检索难以在关系系统中建模的复杂层次结构。 |
对象数据库 | db4o Versant | 通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。 |
想了解更多的NoSQL数据库,可以访问http://nosql-database.org/。
Redis 是一种基于键值对的NoSQL数据库,它提供了对多种数据类型(字符串、哈希、列表、集合、有序集合、位图等)的支持,能够满足很多应用场景的需求。Redis将数据放在内存中,因此读写性能是非常惊人的。与此同时,Redis也提供了持久化机制,能够将内存中的数据保存到硬盘上,在发生意外状况时数据也不会丢掉。此外,Redis还支持键过期、地理信息运算、发布订阅、事务、管道、Lua脚本扩展等功能,总而言之,Redis的功能和性能都非常强大,如果项目中要实现高速缓存和消息队列这样的服务,直接交给Redis就可以了。目前,国内外很多著名的企业和商业项目都使用了Redis,包括:Twitter、Github、StackOverflow、新浪微博、百度、优酷土豆、美团、小米、唯品会等。
2008年,一个名为Salvatore Sanfilippo的程序员为他开发的LLOOGG项目定制了专属的数据库(因为之前他无论怎样优化MySQL,系统性能已经无法再提升了),这项工作的成果就是Redis的初始版本。后来他将Redis的代码放到了全球最大的代码托管平台Github,从那以后,Redis引发了大量开发者的好评和关注,继而有数百人参与了Redis的开发和维护,这使得Redis的功能越来越强大和性能越来越好。
Redis是 remote dictionary server 的缩写,它是一个用 ANSI C 编写的高性能的key-value存储系统,与其他的key-value存储系统相比,Redis有以下一些特点(也是优点):
update
操作。可以通过在Redis的 官方网站 下载 Redis 的源代码,解压缩解归档之后通过 make 工具对源代码进行构建并安装。
wget http://101.44.1.120/files/318700000890F623/download.redis.io/releases/redis-5.0.8.tar.gz tar -zxvf redis-5.0.8.tar.gz cd redis-5.0.8 sudo make && sudo make install
在 redis 源代码目录下有一个名为redis.conf的配置文件,我们可以先查看一下该文件: vim redis.conf
配置将 Redis 服务绑定到指定的IP地址和端口。
bind 127.0.0.1 port 6379
设置后台运行 (以守护进程方式运行)
daemonize yes
设置日志级别, 可选值: (debug
: 调试, verbose
: 详细, notice
: 通知, warning
: 警告)
loglevel warning
配置数据库的数量, 默认为 16 个
databases 16
配置数据写入规则
save 900 1 # 900 秒 (15 分钟) 内修改过 1 个 key, , 写入一次数据库 save 300 10 # 300 秒 (5 分钟) 内修改过 10 个 key, 写入一次数据库 save 60 10000 # 60 秒 (1 分钟) 内修改过 10000 个 key, 写入一次数据库
配置Redis的持久化机制 - RDB。
rdbcompression yes # 压缩 RDB 文件 rdbchecksum yes # 对 RDB 文件进行校验 dbfilename dump.rdb # RDB 数据库文件的文件名 dir /var/local/redis # RDB 文件保存的目录
配置Redis的持久化机制 - AOF。
appendonly no appendfilename "appendonly.aof"
配置Redis的主从复制,通过主从复制可以实现读写分离。
# Master-Replica replication. Use replicaof to make a Redis instance a copy of # another Redis server. A few things to understand ASAP about Redis replication.\ # # +------------------+ +---------------+ # | Master | ---> | Replica | # | (receive writes) | | (exact copy) | # +------------------+ +---------------+ # # 1) Redis replication is asynchronous, but you can configure a master to # stop accepting writes if it appears to be not connected with at least # a given number of replicas. # 2) Redis replicas are able to perform a partial resynchronization with the # master if the replication link is lost for a relatively small amount of # time. You may want to configure the replication backlog size (see the next # sections of this file) with a sensible value depending on your needs. # 3) Replication is automatic and does not need user intervention. After a # network partition replicas automatically try to reconnect to masters # and resynchronize with them. # replicaof 主机IP地址 主机端口
配置慢查询。
slowlog-log-slower-than 10000 # 一次操作超过 10000 毫秒被视作一次慢查询 slowlog-max-len 128 # 最多纪录 128 次满查询
Redis
的服务器和客户端接下来启动 Redis
服务器,下面的方式将以指定的配置文件启动Redis
服务。
redis-server redis.conf
接下来用Redis
客户端去连接服务器。
redis-cli -h localhost -p 6379
Redis
有着非常丰富的数据类型,也有很多的命令来操作这些数据,具体的内容可以查看Redis命令参考,在这个网站上,除了Redis
的命令参考,还有Redis
的详细文档,其中包括了通知、事务、主从复制、持久化、哨兵、集群等内容。
127.0.0.1:6379> set username admin OK 127.0.0.1:6379> get username "admin" 127.0.0.1:6379> set password "123456" ex 300 OK 127.0.0.1:6379> get password "123456" 127.0.0.1:6379> ttl username (integer) -1 127.0.0.1:6379> ttl password (integer) 286 127.0.0.1:6379> hset stu1 name hao (integer) 0 127.0.0.1:6379> hset stu1 age 38 (integer) 1 127.0.0.1:6379> hset stu1 gender male (integer) 1 127.0.0.1:6379> hgetall stu1 1) "name" 2) "hao" 3) "age" 4) "38" 5) "gender" 6) "male" 127.0.0.1:6379> hvals stu1 1) "hao" 2) "38" 3) "male" 127.0.0.1:6379> hmset stu2 name wang age 18 gender female tel 13566778899 OK 127.0.0.1:6379> hgetall stu2 1) "name" 2) "wang" 3) "age" 4) "18" 5) "gender" 6) "female" 7) "tel" 8) "13566778899" 127.0.0.1:6379> lpush nums 1 2 3 4 5 (integer) 5 127.0.0.1:6379> lrange nums 0 -1 1) "5" 2) "4" 3) "3" 4) "2" 5) "1" 127.0.0.1:6379> lpop nums "5" 127.0.0.1:6379> lpop nums "4" 127.0.0.1:6379> rpop nums "1" 127.0.0.1:6379> rpop nums "2" 127.0.0.1:6379> sadd fruits apple banana orange apple grape grape (integer) 4 127.0.0.1:6379> scard fruits (integer) 4 127.0.0.1:6379> smembers fruits 1) "grape" 2) "orange" 3) "banana" 4) "apple" 127.0.0.1:6379> sismember fruits apple (integer) 1 127.0.0.1:6379> sismember fruits durian (integer) 0 127.0.0.1:6379> sadd nums1 1 2 3 4 5 (integer) 5 127.0.0.1:6379> sadd nums2 2 4 6 8 (integer) 4 127.0.0.1:6379> sinter nums1 nums2 1) "2" 2) "4" 127.0.0.1:6379> sunion nums1 nums2 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6" 7) "8" 127.0.0.1:6379> sdiff nums1 nums2 1) "1" 2) "3" 3) "5" 127.0.0.1:6379> zadd topsinger 5234 zhangxy 1978 chenyx 2235 zhoujl 3520 xuezq (integer) 4 127.0.0.1:6379> zrange topsinger 0 -1 withscores 1) "chenyx" 2) "1978" 3) "zhoujl" 4) "2235" 5) "xuezq" 6) "3520" 7) "zhangxy" 8) "5234" 127.0.0.1:6379> zrevrange topsinger 0 -1 1) "zhangxy" 2) "xuezq" 3) "zhoujl" 4) "chenyx" 127.0.0.1:6379> geoadd pois 116.39738549206541 39.90862689286386 tiananmen 116.27172936413572 39.99 135172904494 yiheyuan 117.27766503308104 40.65332064313784 gubeishuizhen (integer) 3 127.0.0.1:6379> geodist pois tiananmen gubeishuizhen km "111.5333" 127.0.0.1:6379> geodist pois tiananmen yiheyuan km "14.1230" 127.0.0.1:6379> georadius pois 116.86499108288572 40.40149669363615 50 km withdist 1) 1) "gubeishuizhen" 2) "44.7408"
Redis
持久化Redis
在运行时,所有的数据都保存在内存里,进程结束以后,会将数据写入到硬盘中。启动时,会读取硬盘里的内容,并将内容全部加载到内存中(会大量的占用内存)。
Redis
的持久化有两种形式:RDB和AOF
默认的持久化方式,是对内存中的数据进行镜像,并以二进制的形式保存到dump.rdb
文件中。会根据配置文件的时间节点对文件进行持久化。
save 900 1 save 300 10 save 60 10000
优点:速度快,直接镜像内存里的数据,文件小。
缺点:数据有可能会丢失,在两次保存间隔内的数据,有可能会丢失。
AOF(Append only file)持久化,将修改的每一条指令记录进appendonly.aof
中,需要修改配置文件,来打开aof
功能。
appendfsync always:每次有新命令追加到aof文件时就执行一个持久化,非常慢但是安全 appendfsync everysec:每秒执行一次持久化,足够快(和使用rdb持久化差不多)并且在故障时只会丢失1秒钟的数据 appendfsync no:从不持久化,将数据交给操作系统来处理。redis处理命令速度加快但是不安全。
优点:适合保存增量数据,数据不丢失。
缺点:文件体积大,恢复时间长
Python
程序中使用Redis
可以使用pip安装redis
模块。redis
模块的核心是名为Redis
的类,该类的对象代表一个Redis
客户端,通过该客户端可以向Redis
服务器发送命令并获取执行的结果。上面我们在Redis
客户端中使用的命令基本上就是Redis
对象可以接收的消息,所以如果了解了Redis
的命令就可以在Python中玩转Redis
。
>>> import redis >>> client = redis.Redis(host='1.2.3.4', port=6379, password='1qaz2wsx') >>> client.set('username', 'admin') True >>> client.hset('student', 'name', 'hao') 1 >>> client.hset('student', 'age', 38) 1 >>> client.keys('*') [b'username', b'student'] >>> client.get('username') b'admin' >>> client.hgetall('student') {b'name': b'hao', b'age': b'38'}
MongoDB
简介MongoDB是2009年问世的一个面向文档的数据库管理系统,由 C++ 语言编写,旨在为Web应用提供可扩展的高性能数据存储解决方案。虽然在划分类别的时候后,MongoDB被认为是NoSQL的产品,但是它更像一个介于关系数据库和非关系数据库之间的产品,在非关系数据库中它功能最丰富,最像关系数据库。
MongoDB将数据存储为一个文档,一个文档由一系列的“键值对”组成,其文档类似于JSON对象,但是MongoDB对JSON进行了二进制处理(能够更快的定位key和value),因此其文档的存储格式称为BSON。关于JSON和BSON的差别大家可以看看MongoDB官方网站的文章《JSON and BSON》。
目前,MongoDB已经提供了对Windows、MacOS、Linux、Solaris等多个平台的支持,而且也提供了多种开发语言的驱动程序,Python当然是其中之一。
可以从MongoDB的官方下载链接下载MongoDB,而Linux和MacOS则提供了压缩文件。也可以使用yum命令直接安装MongoDB服务端和客户端。
sudo yum install mongodb-server # 安装MongoDB服务端 sudo yum install mongodb # 安装MongoDB客户端 sudo mongod -f /etc/mongod.conf # 加载配置项,启动mongodb服务器
说明:上面的操作中,export命令是设置PATH环境变量,这样可以在任意路径下执行
mongod
来启动MongoDB服务器。MongoDB默认保存数据的路径是
/data/db
目录,为此要提前创建该目录。此外,在使用
mongod
启动MongoDB服务器时,--bind_ip
参数用来将服务绑定到指定的IP地址,也可以用–port参数来指定端口,默认端口为27017。
我们通过与关系型数据库进行对照的方式来说明MongoDB中的一些概念。
SQL | MongoDB | 解释(SQL/MongoDB) |
---|---|---|
database | database | 数据库/数据库 |
table | collection | 二维表/集合 |
row | document | 记录(行)/文档 |
column | field | 字段(列)/域 |
index | index | 索引/索引 |
table joins | — | 表连接/嵌套文档 |
primary key | primary key | 主键/主键(_id 字段) |
启动服务器后可以使用交互式环境跟服务器通信,如下所示。
mongo
查看、创建和删除数据库。
> // 显示所有数据库 > show dbs admin 0.000GB config 0.000GB local 0.000GB > // 创建并切换到school数据库 > use school switched to db school > // 删除当前数据库 > db.dropDatabase() { "ok" : 1 } >
创建、删除和查看集合。
> // 创建并切换到school数据库 > use school switched to db school > // 创建colleges集合 > db.createCollection('colleges') { "ok" : 1 } > // 创建students集合 > db.createCollection('students') { "ok" : 1 } > // 查看所有集合 > show collections colleges students > // 删除colleges集合 > db.colleges.drop() true >
说明:在MongoDB中插入文档时如果集合不存在会自动创建集合,所以也可以按照下面的方式通
过创建文档来创建集合。
文档的CRUD操作。
> // 向students集合插入文档 > db.students.insert({stuid: 1001, name: '张三', age: 38}) WriteResult({ "nInserted" : 1 }) > // 向students集合插入文档 > db.students.save({stuid: 1002, name: '王大锤', tel: '13012345678', gender: '男'}) WriteResult({ "nInserted" : 1 }) > // 查看所有文档 > db.students.find() { "_id" : ObjectId("5b13c72e006ad854460ee70b"), "stuid" : 1001, "name" : "张三", "age" : 38 } { "_id" : ObjectId("5b13c790006ad854460ee70c"), "stuid" : 1002, "name" : "王大锤", "tel" : "13012345678", "gender" : "男" } > // 更新stuid为1001的文档 > db.students.update({stuid: 1001}, {'$set': {tel: '13566778899', gender: '男'}}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) > // 插入或更新stuid为1003的文档 > db.students.update({stuid: 1003}, {'$set': {name: '白元芳', tel: '13022223333', gender: '男'}}, upsert=true) WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : ObjectId("5b13c92dd185894d7283efab") }) > db.students.find().pretty() { "_id" : ObjectId("5b13c72e006ad854460ee70b"), "stuid" : 1001, "name" : "张三", "age" : 38, "gender" : "男", "tel" : "13566778899" } { "_id" : ObjectId("5b13c790006ad854460ee70c"), "stuid" : 1002, "name" : "王大锤", "tel" : "13012345678", "gender" : "男" } { "_id" : ObjectId("5b13c92dd185894d7283efab"), "stuid" : 1003, "gender" : "男", "name" : "白元芳", "tel" : "13022223333" } > // 查询stuid大于1001的文档 > db.students.find({stuid: {'$gt': 1001}}).pretty() { "_id" : ObjectId("5b13c790006ad854460ee70c"), "stuid" : 1002, "name" : "王大锤", "tel" : "13012345678", "gender" : "男" } { "_id" : ObjectId("5b13c92dd185894d7283efab"), "stuid" : 1003, "gender" : "男", "name" : "白元芳", "tel" : "13022223333" }
> // 查询stuid大于1001的文档只显示name和tel字段 > db.students.find({stuid: {'$gt': 1001}}, {_id: 0, name: 1, tel: 1}).pretty() { "name" : "王大锤", "tel" : "13012345678" } { "name" : "白元芳", "tel" : "13022223333" } > // 查询name为“张三”或者tel为“13022223333”的文档 > db.students.find({'$or': [{name: '张三'}, {tel: '13022223333'}]}, {_id: 0, name: 1, tel: 1}).pretty() { "name" : "张三", "tel" : "13566778899" } { "name" : "白元芳", "tel" : "13022223333" } > // 查询学生文档跳过第1条文档只查1条文档 > db.students.find().skip(1).limit(1).pretty() { "_id" : ObjectId("5b13c790006ad854460ee70c"), "stuid" : 1002, "name" : "王大锤", "tel" : "13012345678", "gender" : "男" } > // 对查询结果进行排序(1表示升序,-1表示降序) > db.students.find({}, {_id: 0, stuid: 1, name: 1}).sort({stuid: -1}) { "stuid" : 1003, "name" : "白元芳" } { "stuid" : 1002, "name" : "王大锤" } { "stuid" : 1001, "name" : "张三" } > // 在指定的一个或多个字段上创建索引 > db.students.ensureIndex({name: 1}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 } firewall-cmd --zone=public --add-port=3000/tcp --permanent
使用MongoDB可以非常方便的配置数据复制,通过冗余数据来实现数据的高可用以及灾难恢复,也可以通过数据分片来应对数据量迅速增长的需求。关于MongoDB更多的操作可以查阅官方文档 ,同时推荐大家阅读Kristina Chodorow写的《MongoDB权威指南》。
可以通过pip安装pymongo
来实现对MongoDB的操作。
pip3 install pymongo python3 >>> from pymongo import MongoClient >>> client = MongoClient('mongodb://127.0.0.1:27017') >>> db = client.school >>> for student in db.students.find(): ... print('学号:', student['stuid']) ... print('姓名:', student['name']) ... print('电话:', student['tel']) ... 学号: 1001.0 姓名: 张三 电话: 13566778899 学号: 1002.0 姓名: 王大锤 电话: 13012345678 学号: 1003.0 姓名: 白元芳 电话: 13022223333 >>> db.students.find().count() 3 >>> db.students.remove() {'n': 3, 'ok': 1.0} >>> db.students.find().count() 0 >>> coll = db.students >>> from pymongo import ASCENDING >>> coll.create_index([('name', ASCENDING)], unique=True) 'name_1' >>> coll.insert_one({'stuid': int(1001), 'name': '张三', 'gender': True}) <pymongo.results.InsertOneResult object at 0x1050cc6c8> >>> coll.insert_many([{'stuid': int(1002), 'name': '王大锤', 'gender': False}, {'stuid': int(1003), 'name': '白元芳', 'gender': True}]) <pymongo.results.InsertManyResult object at 0x1050cc8c8> >>> for student in coll.find({'gender': True}): ... print('学号:', student['stuid']) ... print('姓名:', student['name']) ... print('性别:', '男' if student['gender'] else '女') ... 学号: 1001 姓名: 张三 性别: 男 学号: 1003 姓名: 白元芳 性别: 男 >>>
redis
注册为win
系统服务redis-server --service-install redis.windows.conf //安装服务 redis-server --service-start //启动服务 redis-server --service-stop //停止服务 redis-server --service-uninstall //卸载服务