Sfoglia il codice sorgente

更新了数据库部分的文档

jackfrued 6 anni fa
parent
commit
1f67d39f8d

+ 533 - 3
Day36-40/NoSQL入门.md

@@ -2,14 +2,544 @@
 
 ### NoSQL概述
 
+如今,大多数的计算机系统(包括服务器、PC、移动设备等)都会产生庞大的数据量。其实,早在2012年的时候,全世界每天产生的数据量就达到了2.5EB(艾字节,$$1EB\approx10^{18}B$$)。这些数据有很大一部分是由关系型数据库来存储和管理的。 早在1970年,E.F.Codd发表了论述关系型数据库的著名论文“*A relational model of data for large shared data banks*”,这篇文章奠定了关系型数据库的基础并在接下来的数十年时间内产生了深远的影响。实践证明,关系型数据库是实现数据持久化最为重要的方式,它也是大多数应用在选择持久化方案时的首选技术。
 
+NoSQL是一项全新的数据库革命性运动,虽然它的历史可以追溯到1998年,但是NoSQL真正深入人心并得到广泛的应用是在进入大数据时候以后,业界普遍认为NoSQL是更适合大数据存储的技术方案,这才使得NoSQL的发展达到了前所未有的高度。2012年《纽约时报》的一篇专栏中写到,大数据时代已经降临,在商业、经济及其他领域中,决策将不再基于经验和直觉而是基于数据和分析而作出。事实上,在天文学、气象学、基因组学、生物学、社会学、互联网搜索引擎、金融、医疗、社交网络、电子商务等诸多领域,由于数据过于密集和庞大,在数据的分析和处理上也遇到了前所未有的限制和阻碍,这一切都使得对大数据处理技术的研究被提升到了新的高度,也使得各种NoSQL的技术方案进入到了公众的视野。
+
+NoSQL数据库按照其存储类型可以大致分为以下几类:
+
+| 类型       | 部分代表                            | 特点                                                         |
+| ---------- | ----------------------------------- | ------------------------------------------------------------ |
+| 列族数据库 | HBase<br>Cassandra<br>Hypertable    | 顾名思义是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的I/O优势,适合于批量数据处理和即时查询。 |
+| 文档数据库 | MongoDB<br>CouchDB<br>ElasticSearch | 文档数据库一般用类JSON格式存储数据,存储的内容是文档型的。这样也就有机会对某些字段建立索引,实现关系数据库的某些功能,但不提供对参照完整性和分布事务的支持。 |
+| KV数据库   | DynamoDB<br>Redis<br>LevelDB        | 可以通过key快速查询到其value,有基于内存和基于磁盘两种实现方案。 |
+| 图数据库   | Neo4J<br>FlockDB<br>JanusGraph      | 使用图结构进行语义查询的数据库,它使用节点、边和属性来表示和存储数据。图数据库从设计上,就可以简单快速的检索难以在关系系统中建模的复杂层次结构。 |
+| 对象数据库 | db4o<br>Versant                     | 通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。 |
 
 ### Redis概述
 
-1. Redis的安装和启动
-2. Redis常用命令
-3. Python访问Redis
+Redis是一种基于键值对的NoSQL数据库,它提供了对多种数据类型(字符串、哈希、列表、集合、有序集合、位图等)的支持,能够满足很多应用场景的需求。Redis将数据放在内存中,因此读写性能是非常惊人的。与此同时,Redis也提供了持久化机制,能够将内存中的数据保存到硬盘上,在发生意外状况时数据也不会丢掉。此外,Redis还支持键过期、地理信息运算、发布订阅、事务、管道、Lua脚本扩展等功能,总而言之,Redis的功能和性能都非常强大,如果项目中要实现高速缓存和消息队列这样的服务,直接交给Redis就可以了。目前,国内外很多著名的企业和商业项目都使用了Redis,包括:Twitter、Github、StackOverflow、新浪微博、百度、优酷土豆、美团、小米、唯品会等。
+
+
+#### Redis简介
+
+2008年,一个名为Salvatore Sanfilippo的程序员为他开发的LLOOGG项目定制了专属的数据库(因为之前他无论怎样优化MySQL,系统性能已经无法再提升了),这项工作的成果就是Redis的初始版本。后来他将Redis的代码放到了全球最大的代码托管平台[Github](<https://github.com/antirez/redis>),从那以后,Redis引发了大量开发者的好评和关注,继而有数百人参与了Redis的开发和维护,这使得Redis的功能越来越强大和性能越来越好。
+
+Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性能的key-value存储系统,与其他的key-value存储系统相比,Redis有以下一些特点(也是优点):
+
+- Redis的读写性能极高,并且有丰富的特性(发布/订阅、事务、通知等)。
+- Redis支持数据的持久化(RDB和AOF两种方式),可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
+- Redis支持多种数据类型,包括:string、hash、list、set,zset、bitmap、hyperloglog等。
+- Redis支持主从复制(实现读写分析)以及哨兵模式(监控master是否宕机并自动调整配置)。
+- Redis支持分布式集群,可以很容易的通过水平扩展来提升系统的整体性能。
+- Redis基于TCP提供的可靠传输服务进行通信,很多编程语言都提供了Redis客户端支持。
+
+#### Redis的应用场景
+
+1. 高速缓存  - 将不常变化但又经常被访问的热点数据放到Redis数据库中,可以大大降低关系型数据库的压力,从而提升系统的响应性能。
+2. 排行榜 - 很多网站都有排行榜功能,利用Redis中的列表和有序集合可以非常方便的构造各种排行榜系统。
+3. 商品秒杀/投票点赞 - Redis提供了对计数操作的支持,网站上常见的秒杀、点赞等功能都可以利用Redis的计数器通过+1或-1的操作来实现,从而避免了使用关系型数据的`update`操作。
+4. 分布式锁 - 利用Redis可以跨多台服务器实现分布式锁(类似于线程锁,但是能够被多台机器上的多个线程或进程共享)的功能,用于实现一个阻塞式操作。
+5. 消息队列 - 消息队列和高速缓存一样,是一个大型网站不可缺少的基础服务,可以实现业务解耦和非实时业务削峰等特性,这些我们都会在后面的项目中为大家展示。
+
+#### Redis的安装和配置
+
+可以使用Linux系统的包管理工具(如yum)来安装Redis,也可以通过在Redis的[官方网站](https://redis.io/)下载Redis的源代码,解压缩解归档之后通过make工具对源代码进行构建并安装,在更新这篇文档时,Redis官方提供的最新稳定版本是[Redis 5.0.4](http://download.redis.io/releases/redis-5.0.4.tar.gz)。
+
+```Shell
+wget http://download.redis.io/releases/redis-5.0.4.tar.gz
+gunzip redis-5.0.4.tar.gz
+tar -xvf redis-5.0.4.tar
+cd redis-5.0.4
+make && make install
+```
+
+在redis源代码目录下有一个名为redis.conf的配置文件,我们可以先查看一下该文件。
+
+```Shell
+vim redis.conf
+```
+
+配置将Redis服务绑定到指定的IP地址和端口。
+
+![](./res/redis-bind-and-port.png)
+
+配置底层有多少个数据库。
+
+![](./res/redis-databases.png)
+
+配置Redis的持久化机制 - RDB。
+
+![](./res/redis-rdb-1.png)
+
+![](./res/redis-rdb-3.png)
+
+配置Redis的持久化机制 - AOF。
+
+![](./res/redis-aof.png)
+
+配置访问Redis服务器的验证口令。
+
+![](./res/redis-security.png)
+
+配置Redis的主从复制,通过主从复制可以实现读写分离。
+
+![](./res/redis-replication.png)
+
+配置慢查询。
+
+![](./res/redis-slow-logs.png)
+
+上面这些内容就是Redis的基本配置,如果你对上面的内容感到困惑也没有关系,先把Redis用起来再回头去推敲这些内容就行了。如果想找一些参考书,[《Redis开发与运维》](https://item.jd.com/12121730.html)是一本不错的入门读物,而[《Redis实战》](https://item.jd.com/11791607.html)是不错的进阶读物。
+
+#### Redis的服务器和客户端
+
+接下来启动Redis服务器,下面的方式将以默认的配置启动Redis服务。
+
+```Shell
+redis-server
+```
+
+如果希望修改Redis的配置(如端口、认证口令、持久化方式等),可以通过下面两种方式。
+
+方式一:通过参数指定认证口令和AOF持久化方式。
+
+```Shell
+redis-server --requirepass 1qaz2wsx --appendonly yes
+```
+
+方式二:通过指定的配置文件来修改Redis的配置。
+
+```Shell
+redis-server /root/redis-5.0.4/redis.conf
+```
+
+下面我们使用第一种方式来启动Redis并将其置于后台运行,将Redis产生的输出重定向到名为redis.log的文件中。
+
+```Shell
+redis-server --requirepass 1qaz2wsx > redis.log &
+```
+
+可以通过ps或者netstat来检查Redis服务器是否启动成功。
+
+```Shell
+ps -ef | grep redis-server
+netstat -nap | grep redis-server
+```
+
+接下来,我们尝试用Redis客户端去连接服务器。
+
+```Shell
+redis-cli
+127.0.0.1:6379> auth 1qaz2wsx
+OK
+127.0.0.1:6379> ping
+PONG
+127.0.0.1:6379>
+```
+
+Redis有着非常丰富的数据类型,也有很多的命令来操作这些数据,具体的内容可以查看[Redis命令参考](http://redisdoc.com/),在这个网站上,除了Redis的命令参考,还有Redis的详细文档,其中包括了通知、事务、主从复制、持久化、哨兵、集群等内容。
+
+![](./res/redis-data-types.png)
+
+> 说明:上面的插图来自付磊和张益军先生编著的《Redis开发与运维》一书。
+
+```Shell
+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"
+```
+
+#### 在Python程序中使用Redis
+
+可以使用pip安装redis模块。redis模块的核心是名为Redis的类,该类的对象代表一个Redis客户端,通过该客户端可以向Redis服务器发送命令并获取执行的结果。上面我们在Redis客户端中使用的命令基本上就是Redis对象可以接收的消息,所以如果了解了Redis的命令就可以在Python中玩转Redis。
+
+```Shell
+pip3 install redis
+python3
+```
+
+```Python
+>>> 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简介
+
+MongoDB是2009年问世的一个面向文档的数据库管理系统,由C++语言编写,旨在为Web应用提供可扩展的高性能数据存储解决方案。虽然在划分类别的时候后,MongoDB被认为是NoSQL的产品,但是它更像一个介于关系数据库和非关系数据库之间的产品,在非关系数据库中它功能最丰富,最像关系数据库。
+
+MongoDB将数据存储为一个文档,一个文档由一系列的“键值对”组成,其文档类似于JSON对象,但是MongoDB对JSON进行了二进制处理(能够更快的定位key和value),因此其文档的存储格式称为BSON。关于JSON和BSON的差别大家可以看看MongoDB官方网站的文章[《JSON and BSON》](https://www.mongodb.com/json-and-bson)。
+
+目前,MongoDB已经提供了对Windows、MacOS、Linux、Solaris等多个平台的支持,而且也提供了多种开发语言的驱动程序,Python当然是其中之一。
+
+#### MongoDB的安装和配置
+
+可以从MongoDB的[官方下载链接](https://www.mongodb.com/download-center#community)下载MongoDB,官方为Windows系统提供了一个Installer程序,而Linux和MacOS则提供了压缩文件。下面简单说一下Linux系统如何安装和配置MongoDB。
+
+```Shell
+wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.6.5.tgz
+gunzip mongodb-linux-x86_64-amazon-3.6.5.tgz
+mkdir mongodb-3.6.5
+tar -xvf mongodb-linux-x86_64-amazon-3.6.5.tar --strip-components 1 -C mongodb-3.6.5/
+export PATH=$PATH:~/mongodb-3.6.5/bin
+mkdir -p /data/db
+mongod --bind_ip 172.18.61.250
+
+2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] MongoDB starting : pid=1163 port=27017 dbpath=/data/db 64-bit host=iZwz97tbgo9lkabnat2lo8Z
+2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] db version v3.6.5
+2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] git version: a20ecd3e3a174162052ff99913bc2ca9a839d618
+2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] OpenSSL version: OpenSSL 1.0.0-fips29 Mar 2010
+...
+2018-06-03T18:03:28.945+0800 I NETWORK  [initandlisten] waiting for connections on port 27017
+```
+
+> 说明:上面的操作中,export命令是设置PATH环境变量,这样可以在任意路径下执行mongod来启动MongoDB服务器。MongoDB默认保存数据的路径是/data/db目录,为此要提前创建该目录。此外,在使用mongod启动MongoDB服务器时,--bind_ip参数用来将服务绑定到指定的IP地址,也可以用--port参数来指定端口,默认端口为27017。
+
+#### MongoDB基本概念
+
+我们通过与关系型数据库进行对照的方式来说明MongoDB中的一些概念。
+
+| SQL         | MongoDB     | 解释(SQL/MongoDB)    |
+| ----------- | ----------- | ---------------------- |
+| database    | database    | 数据库/数据库          |
+| table       | collection  | 二维表/集合            |
+| row         | document    | 记录(行)/文档        |
+| column      | field       | 字段(列)/域          |
+| index       | index       | 索引/索引              |
+| table joins | ---         | 表连接/嵌套文档        |
+| primary key | primary key | 主键/主键(`_id`字段) |
+
+#### 通过Shell操作MongoDB
+
+启动服务器后可以使用交互式环境跟服务器通信,如下所示。
+
+```shell
+mongo --host 172.18.61.250
+
+MongoDB shell version v3.6.5
+connecting to: mongodb://172.18.61.250:27017/
+...
+> 
+```
+
+1. 查看、创建和删除数据库。
+
+   ```JavaScript
+   > // 显示所有数据库
+   > show dbs
+   admin   0.000GB
+   config  0.000GB
+   local   0.000GB
+   > // 创建并切换到school数据库
+   > use school
+   switched to db school
+   > // 删除当前数据库
+   > db.dropDatabase()
+   { "ok" : 1 }
+   >
+   ```
+
+2. 创建、删除和查看集合。
+
+   ```JavaScript
+   > // 创建并切换到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中插入文档时如果集合不存在会自动创建集合,所以也可以按照下面的方式通过创建文档来创建集合。
+
+3. 文档的CRUD操作。
+
+   ```JavaScript
+   > // 向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
+   }
+   > 
+   ```
+
+使用MongoDB可以非常方便的配置数据复制,通过冗余数据来实现数据的高可用以及灾难恢复,也可以通过数据分片来应对数据量迅速增长的需求。关于MongoDB更多的操作可以查阅[官方文档](https://mongodb-documentation.readthedocs.io/en/latest/) ,同时推荐大家阅读Kristina Chodorow写的[《MongoDB权威指南》](http://www.ituring.com.cn/book/1172)。
+
+####在Python程序中操作MongoDB
+
+可以通过pip安装pymongo来实现对MongoDB的操作。
+
+```Shell
+pip3 install pymongo
+python3
+```
+
+```Python
+>>> from pymongo import MongoClient
+>>> client = MongoClient('mongodb://120.77.222.217: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
+姓名: 白元芳
+性别: 男
+>>> 
+```
 
+关于PyMongo更多的知识可以通过它的[官方文档](https://api.mongodb.com/python/current/tutorial.html)进行了解。

BIN
Day36-40/res/IMG_0358.PNG


BIN
Day36-40/res/IMG_0360.png


BIN
Day36-40/res/IMG_0361.png


BIN
Day36-40/res/IMG_0362.png


BIN
Day36-40/res/IMG_0363.png


BIN
Day36-40/res/IMG_0364.png


BIN
Day36-40/res/IMG_0365.png


BIN
Day36-40/res/IMG_0366.png


BIN
Day36-40/res/redis-aof.png


BIN
Day36-40/res/redis-bind-and-port.png


BIN
Day36-40/res/redis-data-type.png


BIN
Day36-40/res/redis-data-types.png


BIN
Day36-40/res/redis-databases.png


BIN
Day36-40/res/redis-hash.png


BIN
Day36-40/res/redis-list.png


BIN
Day36-40/res/redis-rdb-1.png


BIN
Day36-40/res/redis-rdb-3.png


BIN
Day36-40/res/redis-replication.png


BIN
Day36-40/res/redis-security.png


BIN
Day36-40/res/redis-set.png


BIN
Day36-40/res/redis-slow-logs.png


BIN
Day36-40/res/redis-string.png


BIN
Day36-40/res/redis-zset.png


+ 2 - 2
Day36-40/关系型数据库MySQL.md

@@ -27,7 +27,7 @@
    - [DB2](https://www.ibm.com/analytics/us/en/db2/) - IBM公司开发的、主要运行于Unix(包括IBM自家的[AIX](https://zh.wikipedia.org/wiki/AIX))、Linux、以及Windows服务器版等系统的关系数据库产品。DB2历史悠久且被认为是最早使用SQL的数据库产品,它拥有较为强大的商业智能功能。
    - [SQL Server](https://www.microsoft.com/en-us/sql-server/) - 由Microsoft开发和推广的关系型数据库产品,最初适用于中小企业的数据管理,但是近年来它的应用范围有所扩展,部分大企业甚至是跨国公司也开始基于它来构建自己的数据管理系统。
    - [MySQL](https://www.mysql.com/) - MySQL是开放源代码的,任何人都可以在GPL(General Public License)的许可下下载并根据个性化的需要对其进行修改。MySQL因为其速度、可靠性和适应性而备受关注。
-   - [PostgreSQL]() - 在BSD许可证下发行的开源代码的关系数据库产品。
+   - [PostgreSQL]() - 在BSD许可证下发行的开源代码的关系数据库产品。
 
 ### MySQL简介
 
@@ -108,7 +108,7 @@
      mysql -u root
      ```
 
-     修改超级管理员(root)的访问口令为hello_world_123。
+     修改超级管理员(root)的访问口令为i_LOVE_macos_123。
 
      ```SQL
      use mysql;

+ 10 - 67
Day41-55/03.静态资源和Ajax请求.md

@@ -1,55 +1,13 @@
 ## Django实战(03) - 静态资源和Ajax请求
 
-基于前面两个章节讲解的知识,我们已经可以使用Django框架来实现Web应用的开发了。接下来我们就尝试实现一个投票应用,具体的需求是用户进入系统首先来到“登录页”;登录成功后可以查看到“学科介绍”页面,该页面显示了一个学校所开设的所有学科;通过点击某个学科,可以进入“讲师详情”页面,该页面展示了该学科所有讲师的详细情况,可以在该页面上给讲师点击“好评”或“差评”;对于未注册的用户,可以在登录页点击“新用户注册”进入“注册页”完成用户注册,注册成功或失败都会获得相应的提示信息,注册成功后会返回“登录页”
+基于前面两个章节讲解的知识,我们已经可以使用Django框架来实现Web应用的开发了。接下来我们就尝试实现一个投票应用,具体的需求是用户进入应用首先查看到“学科介绍”页面,该页面显示了一个学校所开设的所有学科;通过点击某个学科,可以进入“老师介绍”页面,该页面展示了该学科所有老师的详细情况,可以在该页面上给老师点击“好评”或“差评”,但是会先跳转到“登录页”要求用户登录,登录成功才能投票;对于未注册的用户,可以在“登录页”点击“新用户注册”进入“注册页”完成用户注册,注册成功后会跳转到“登录页”,注册失败会获得相应的提示信息
 
 ### 准备工作
 
-由于之前已经详细的讲解了如何创建Django项目以及项目的相关配置,因此我们略过这部分内容,唯一需要说明的是,我们将项目命名为hellodjango,在项目下创建了一个名为demo的应用。从“学科介绍”和“讲师详情”页面的需求,我们可以首先分析出两个业务实体,一个是学科,一个是讲师,二者之前是一对多关联。因此,我们首先修改应用demo下的models.py文件来定义数据模型。
+由于之前已经详细的讲解了如何创建Django项目以及项目的相关配置,因此我们略过这部分内容,唯一需要说明的是,从上面对投票应用需求的描述中我们可以分析出三个业务实体:学科、老师和用户。学科和老师之间通常是一对多关联关系(一个学科有多个老师,一个老师通常只属于一个学科),用户因为要给老师投票,所以跟老师之间是多对多关联关系(一个用户可以给多个老师投票,一个老师也可以收到多个用户的投票)。首先修改应用下的models.py文件来定义数据模型,先给出学科和老师的模型。
 
 ```Python
-from django.db import models
-from django.db.models import PROTECT
-
-
-class Subject(models.Model):
-    no = models.AutoField(primary_key=True, db_column='sno', verbose_name='编号')
-    name = models.CharField(max_length=50, db_column='sname', verbose_name='学科名称')
-    intro = models.CharField(max_length=511, db_column='sintro', verbose_name='学科介绍')
-
-    def __str__(self):
-        return self.name
-
-    class Meta(object):
-        db_table = 'tb_subject'
-        verbose_name = '学科'
-        verbose_name_plural = '学科'
-
-
-class Teacher(models.Model):
-    no = models.AutoField(primary_key=True, db_column='tno', verbose_name='编号')
-    name = models.CharField(max_length=20, db_column='tname', verbose_name='姓名')
-    intro = models.CharField(max_length=1023, db_column='tintro', verbose_name='简介')
-    motto = models.CharField(max_length=255, db_column='tmotto', verbose_name='教学理念')
-    photo = models.CharField(max_length=511, db_column='tphoto', verbose_name='照片', null=True, blank=True)
-    subject = models.ForeignKey(Subject, db_column='sno', on_delete=PROTECT, related_name='+', verbose_name='所属学科')
-    manager = models.BooleanField(default=False, db_column='tmanager', verbose_name='是否主管')
-    good_count = models.IntegerField(default=0, db_column='tgcount', verbose_name='好评数')
-    bad_count = models.IntegerField(default=0, db_column='tbcount', verbose_name='差评数')
-
-    @property
-    def gcount(self):
-        return f'{self.good_count}' \
-            if self.good_count <= 999 else '999+'
-
-    @property
-    def bcount(self):
-        return f'{self.bad_count}' \
-            if self.bad_count <= 999 else '999+'
-
-    class Meta(object):
-        db_table = 'tb_teacher'
-        verbose_name = '讲师'
-        verbose_name_plural = '讲师'
+
 ```
 
 模型定义完成后,可以通过“生成迁移”和“执行迁移”来完成关系型数据库中二维表的创建,当然这需要提前启动数据库服务器并创建好对应的数据库,同时我们在项目中已经安装了PyMySQL而且完成了相应的配置,这些内容此处不再赘述。
@@ -61,25 +19,10 @@ class Teacher(models.Model):
 ...
 ```
 
-完成模型迁移之后,我们可以通过下面的SQL语句来添加学科和师的数据。
+完成模型迁移之后,我们可以通过下面的SQL语句来添加学科和师的数据。
 
 ```SQL
-INSERT INTO `tb_subject` 
-    (`sname`, `sintro`)
-VALUES
-	('Python教学部', 'Python是一种面向对象的解释型计算机程序设计语言,由荷兰人Guido van Rossum于1989年发明,第一个公开发行版发行于1991年。' ),
-	('JavaEE教学部', 'Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。' ),
-	('HTML5教学部', 'HTML5 将成为 HTML、XHTML 以及 HTML DOM 的新标准。' );
-
-INSERT INTO `tb_teacher` 
-    (`tname`, `tintro`, `tmotto`, `tphoto`, `tmanager`, `sno`)
-VALUES
-	('骆昊', '10年以上软硬件产品和系统设计、研发、架构和管理经验,2003年毕业于四川大学,四川大学Java技术俱乐部创始人,四川省优秀大学毕业生,在四川省网络通信技术重点实验室工作期间,参与了2项国家自然科学基金项目、1项中国科学院中长期研究项目和多项四川省科技攻关项目,在国际会议和国内顶级期刊上发表多篇论文(1篇被SCI收录,3篇被EI收录),大规模网络性能测量系统DMC-TS的设计者和开发者,perf-TTCN语言的发明者。国内最大程序员社区CSDN的博客专家,在Github上参与和维护了多个高质量开源项目,精通C/C++、Java、Python、R、Swift、JavaScript等编程语言,擅长OOAD、系统架构、算法设计、协议分析和网络测量,主持和参与过电子政务系统、KPI考核系统、P2P借贷平台等产品的研发,一直践行“用知识创造快乐”的教学理念,善于总结,乐于分享。', '教育是让受教育者体会用知识创造快乐的过程', 'images/ken.png', 1, 1),
-	('余小美', '5年以上移动互联网项目开发经验和教学经验,曾担任上市游戏公司高级软件研发工程师和移动端(iOS)技术负责人,参了多个企业级应用和游戏类应用的移动端开发和后台服务器开发,拥有丰富的开发经验和项目管理经验,以个人开发者和协作开发者的身份在苹果的AppStore上发布过多款App。精通Python、C、Objective-C、Swift等开发语言,熟悉iOS原生App开发、RESTful接口设计以及基于Cocos2d-x的游戏开发。授课条理清晰、细致入微,性格活泼开朗、有较强的亲和力,教学过程注重理论和实践的结合,在学员中有良好的口碑。', '每天叫醒你的不是闹钟而是梦想', 'images/linus.png', 0, 1),
-	('肖小帅', '10年以上互联网和移动互联网产品设计、研发、技术架构和项目管理经验,曾在中国移动、symbio、ajinga.com、万达信息等公司担任架构师、项目经理、技术总监等职务,长期为苹果、保时捷、耐克、沃尔玛等国际客户以及国内的政府机构提供信息化服务,主导的项目曾获得“世界科技先锋”称号,个人作品“许愿吧”曾在腾讯应用市场生活类App排名前3,拥有百万级用户群体,运营的公众号“卵石坊”是国内知名的智能穿戴设备平台。精通Python、C++、Java、Ruby、JavaScript等开发语言,主导和参与了20多个企业级项目(含国家级重大项目和互联网创新项目),涉及的领域包括政务、社交、电信、卫生和金融,有极为丰富的项目实战经验。授课深入浅出、条理清晰,善于调动学员的学习热情并帮助学员理清思路和方法。', '世上没有绝望的处境,只有对处境绝望的人', 'images/dennis.png', 0, 1),
-	('王大帅', '5年以上Python开发经验,先后参与了O2O商城、CRM系统、CMS平台、ERP系统等项目的设计与研发,曾在全国最大最专业的汽车领域相关服务网站担任Python高级研发工程师、项目经理等职务,擅长基于Python、Java、PHP等开发语言的企业级应用开发,全程参与了多个企业级应用从需求到上线所涉及的各种工作,精通Django、Flask等框架,熟悉基于微服务的企业级项目开发,拥有丰富的项目实战经验。善于用浅显易懂的方式在课堂上传授知识点,在授课过程中经常穿插企业开发的实际案例并分析其中的重点和难点,通过这种互动性极强的教学模式帮助学员找到解决问题的办法并提升学员的综合素质。', '不要给我说什么底层原理、框架内核!老夫敲代码就是一把梭!复制!黏贴!拿起键盘就是干!', NULL, 0, 1),
-	('何大富', '5年以上JavaEE项目开发和教学经验,参与过人力资源管理系统、电子教育产品在线商城、平安好医生App、平安好车主App等项目的设计与研发。擅长Java语言、面向对象编程、JavaEE框架、Web前端开发、数据库编程和Android应用开发,对新技术有着浓厚的兴趣和钻研精神,对微服务架构、虚拟化技术、区块链、边缘计算等领域都有自己独到的认识和见解,有丰富的项目经验和教学经验。授课时注重学习方法的引导,提倡以项目为导向的实战型教学,同时也注重基础知识的掌握和底层原理的理解,课堂氛围轻松幽默,能够把枯燥乏味的知识变成生动有趣的案例,帮助学员更快更好的掌握技术的要领,从事JavaEE教学工作以来,获得了学生潮水般的好评。', '每天撸代码,生活乐无边!', 'images/andrew.png', 0, 2),
-	('吴小富', '毕业于西南交通大学,高级软件研发工程师,10年以上的开发和培训经验。曾就职于华为赛门铁克科技有限公司,负责公司内部ERP系统的研发,参与和主导过多个大型门户网站、电子商务网站、电子政务系统以及多个企业级Web项目的设计和开发,同时负责过多门企业内训课程的研发与讲授,有着非常丰富的JavaEE项目开发经验和Web前端开发经验,精通C/C++、Java、PHP、JavaScript等开发语言,能够使用多种技术进行全栈开发。授课经验丰富、思路清晰、富有激情,对知识点的讲解由浅入深、深入浅出,能够通过实际开发的场景引导学员思考业务并理解相关技术,善于将多年的项目实战经验和企业内训经验融入课堂,通过理论联系实际的方式帮助学员迅速提升就业能力。', '人生的道路在态度的岔口一分为二', NULL, 1, 3);
+
 ```
 
 接下来,我们就可以修改views.py文件,通过编写视图函数先实现“学科介绍”页面。
@@ -87,10 +30,10 @@ VALUES
 ```Python
 def show_subjects(request):
     ctx = {'subjects_list': Subject.objects.all()}
-    return render(request, 'demo/subject.html', ctx)
+    return render(request, 'subject.html', ctx)
 ```
 
-至此,我们还需要一个模板页,模板的配置以及模板页中模板语言的用法在之前已经进行过简要的介绍,如果不熟悉可以看看下面的代码,相信学会编写模板页并熟练的使用模板语言并不是一件困难的事情。
+至此,我们还需要一个模板页,模板的配置以及模板页中模板语言的用法在之前已经进行过简要的介绍,如果不熟悉可以看看下面的代码,相信并不是一件困难的事情。
 
 ```HTML
 <!DOCTYPE html>
@@ -151,7 +94,7 @@ def show_teachers(request, no):
     return render(request, 'demo/teacher.html', ctx)
 ```
 
-接下来我们可以定制“师详情”的模板页。
+接下来我们可以定制“师详情”的模板页。
 
 ```HTML
 <!DOCTYPE html>
@@ -214,7 +157,7 @@ def show_teachers(request, no):
 </html>
 ```
 
-请注意上面的模板页面,我们在第2行和`<img>`标签中使用了加载静态资源的模板指令,通过加载静态资源的指令我们可以显示师的头像。当然,我们还得创建放置静态资源的文件夹并在项目的配置文件中指明静态资源文件夹的所在以及静态资源的URL。
+请注意上面的模板页面,我们在第2行和`<img>`标签中使用了加载静态资源的模板指令,通过加载静态资源的指令我们可以显示师的头像。当然,我们还得创建放置静态资源的文件夹并在项目的配置文件中指明静态资源文件夹的所在以及静态资源的URL。
 
 ```Shell
 (venv)$ mkdir static
@@ -241,7 +184,7 @@ STATIC_URL = '/static/'
 
 接下来就可以实现“好评”和“差评”的功能了,很明显如果能够在不刷新页面的情况下实现这两个功能会带来更好的用户体验,因此我们考虑使用[Ajax](https://zh.wikipedia.org/wiki/AJAX)来实现“好评”和“差评”。
 
-首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL,跟上面一样我们在URL中使用了占位符语法来绑定师的编号。
+首先修改项目的urls.py文件,为“好评”和“差评”功能映射对应的URL,跟上面一样我们在URL中使用了占位符语法来绑定师的编号。
 
 ```Python
 from django.contrib import admin

+ 4 - 437
Day66-75/03.存储数据.md

@@ -1,441 +1,12 @@
 ## 存储数据
 
-### 数据缓存
-
-通过[《网络数据采集和解析》](./02.数据采集和解析.md)一文,我们已经知道了如何从指定的页面中抓取数据,以及如何保存抓取的结果,但是我们没有考虑过这么一种情况,就是我们可能需要从已经抓取过的页面中提取出更多的数据,重新去下载这些页面对于规模不大的网站倒是问题也不大,但是如果能够把这些页面缓存起来,对应用的性能会有明显的改善。
-
-### 使用NoSQL
-
-#### Redis简介
-
-Redis是REmote DIctionary Server的缩写,它是一个用ANSI C编写的高性能的key-value存储系统,与其他的key-value存储系统相比,Redis有以下一些特点(也是优点):
-
-- Redis的读写性能极高,并且有丰富的特性(发布/订阅、事务、通知等)。
-- Redis支持数据的持久化(RDB和AOF两种方式),可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
-- Redis不仅仅支持简单的key-value类型的数据,同时还提供hash、list、set,zset、hyperloglog、geo等数据类型。
-- Redis支持主从复制(实现读写分析)以及哨兵模式(监控master是否宕机并调整配置)。
-
-#### Redis的安装和配置
-
-可以使用Linux系统的包管理工具(如yum)来安装Redis,也可以通过在Redis的[官方网站](https://redis.io/)下载Redis的[源代码](http://download.redis.io/releases/redis-3.2.11.tar.gz)解压缩解归档之后进行构件安装。
-
-```Shell
-wget http://download.redis.io/releases/redis-3.2.11.tar.gz
-gunzip redis-3.2.11.tar.gz
-tar -xvf redis-3.2.11.tar
-cd redis-3.2.11
-make && make install
-```
-
-接下来我们将redis-3.2.11目录下的redis.conf配置文件复制到用户主目录下并修改配置文件(如果你对配置文件不是很有把握就不要直接修改而是先复制一份再修改这个副本)。
-
-```Shell
-cd ..
-cp redis-3.2.11/redis.conf redis.conf
-vim redis.conf
-```
-
-配置将Redis服务绑定到指定的IP地址和端口。
-
-![](./res/redis-bind.png)
-
-![](./res/redis-port.png)
-
-配置底层有多少个数据库。
-
-![](./res/redis-database.png)
-
-配置Redis的持久化机制 - RDB。
-
-![](./res/redis-save.png)
-
-![](./res/redis-rdb.png)
-
-配置Redis的持久化机制 - AOF。
-
-![](./res/redis-aof.png)
-
-配置访问Redis服务器的验证口令。
-
-![](./res/redis-security.png)
-
-配置Redis的主从复制,通过主从复制可以实现读写分离。
-
-![](./res/redis-replication.png)
-
-配置慢查询日志。
-
-![](./res/redis-slow-log.png)
-
-这样我们就完成了Redis的基本配置,如果对上面的东西感到困惑,可以先系统的了解一下Redis,[《Redis开发与运维》](https://item.jd.com/12121730.html)是一本不错的入门读物,而[《Redis实战》](https://item.jd.com/11791607.html)是不错的进阶读物。
-
-#### Redis的服务器和客户端
-
-接下来启动Redis服务器,可以将服务器放在后台去运行。
-
-```Shell
-redis-server redis.conf &
-
-           _.-``__ ''-._
-      _.-``    `.  `_.  ''-._           Redis 3.2.11 (00000000/0) 64 bit
-  .-`` .-```.  ```\/    _.,_ ''-._
- (    '      ,       .-`  | `,    )     Running in standalone mode
- |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
- |    `-._   `._    /     _.-'    |     PID: 12345
-  `-._    `-._  `-./  _.-'    _.-'
- |`-._`-._    `-.__.-'    _.-'_.-'|
- |    `-._`-._        _.-'_.-'    |           http://redis.io
-  `-._    `-._`-.__.-'_.-'    _.-'
- |`-._`-._    `-.__.-'    _.-'_.-'|
- |    `-._`-._        _.-'_.-'    |
-  `-._    `-._`-.__.-'_.-'    _.-'
-      `-._    `-.__.-'    _.-'
-          `-._        _.-'
-              `-.__.-'
-```
-
-接下来,我们尝试用Redis客户端去连接服务器。
-
-```Shell
-redis-cli -h 172.18.61.250 -p 6379
-172.18.61.250:6379> auth 1qaz2wsx
-OK
-172.18.61.250:6379> ping
-PONG
-172.18.61.250:6379>
-```
-
-Redis有着非常丰富的数据类型,也有很多的命令来操作这些数据,具体的内容可以查看[Redis命令参考](http://redisdoc.com/),在这个网站上,除了Redis的命令参考,还有Redis的详细文档,其中包括了通知、事务、主从复制、持久化、哨兵、集群等内容。
-
-```Shell
-172.18.61.250:6379> set username admin
-OK
-172.18.61.250:6379> get username
-"admin"
-172.18.61.250:6379> hset student1 name hao
-(integer) 0
-172.18.61.250:6379> hset student1 age 38
-(integer) 1
-172.18.61.250:6379> hset student1 gender male
-(integer) 1
-172.18.61.250:6379> hgetall student1
-1) "name"
-2) "hao"
-3) "age"
-4) "38"
-5) "gender"
-6) "male"
-172.18.61.250:6379> lpush num 1 2 3 4 5
-(integer) 5
-172.18.61.250:6379> lrange num 0 -1
-1) "5"
-2) "4"
-3) "3"
-4) "2"
-5) "1"
-172.18.61.250:6379> sadd fruits apple banana orange apple grape grape
-(integer) 4
-172.18.61.250:6379> scard fruits
-(integer) 4
-172.18.61.250:6379> smembers fruits
-1) "grape"
-2) "orange"
-3) "banana"
-4) "apple"
-172.18.61.250:6379> zadd scores 90 zhao 78 qian 66 sun 95 lee
-(integer) 4
-172.18.61.250:6379> zrange scores 0 -1
-1) "sun"
-2) "qian"
-3) "zhao"
-4) "lee"
-172.18.61.250:6379> zrevrange scores 0 -1
-1) "lee"
-2) "zhao"
-3) "qian"
-4) "sun"
-```
-
-#### 在Python程序中使用Redis
+### 存储海量数据
 
-可以使用pip安装redis模块。redis模块的核心是名为Redis的类,该类的对象代表一个Redis客户端,通过该客户端可以向Redis服务器发送命令并获取执行的结果。上面我们在Redis客户端中使用的命令基本上就是Redis对象可以接收的消息,所以如果了解了Redis的命令就可以在Python中玩转Redis
+数据持久化的首选方案应该是关系型数据库,关系型数据库的产品很多,包括:Oracle、MySQL、SQLServer、PostgreSQL等。如果要存储海量的低价值数据,文档数据库也是不错的选择,MongoDB是文档数据库中的佼佼者,之前我们已经讲解过MongDB的相关知识,在此不再进行赘述。
 
-```Shell
-pip3 install redis
-python3
-```
-
-```Python
->>> 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》](https://www.mongodb.com/json-and-bson)。
-
-目前,MongoDB已经提供了对Windows、MacOS、Linux、Solaris等多个平台的支持,而且也提供了多种开发语言的驱动程序,Python当然是其中之一。
-
-#### MongoDB的安装和配置
-
-可以从MongoDB的[官方下载链接](https://www.mongodb.com/download-center#community)下载MongoDB,官方为Windows系统提供了一个Installer程序,而Linux和MacOS则提供了压缩文件。下面简单说一下Linux系统如何安装和配置MongoDB。
-
-```Shell
-wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.6.5.tgz
-gunzip mongodb-linux-x86_64-amazon-3.6.5.tgz
-mkdir mongodb-3.6.5
-tar -xvf mongodb-linux-x86_64-amazon-3.6.5.tar --strip-components 1 -C mongodb-3.6.5/
-export PATH=$PATH:~/mongodb-3.6.5/bin
-mkdir -p /data/db
-mongod --bind_ip 172.18.61.250
-
-2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] MongoDB starting : pid=1163 port=27017 dbpath=/data/db 64-bit host=iZwz97tbgo9lkabnat2lo8Z
-2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] db version v3.6.5
-2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] git version: a20ecd3e3a174162052ff99913bc2ca9a839d618
-2018-06-03T18:03:28.232+0800 I CONTROL  [initandlisten] OpenSSL version: OpenSSL 1.0.0-fips29 Mar 2010
-...
-2018-06-03T18:03:28.945+0800 I NETWORK  [initandlisten] waiting for connections on port 27017
-```
-
-> 说明:上面的操作中,export命令是设置PATH环境变量,这样可以在任意路径下执行mongod来启动MongoDB服务器。MongoDB默认保存数据的路径是/data/db目录,为此要提前创建该目录。此外,在使用mongod启动MongoDB服务器时,—bind_ip参数用来将服务绑定到指定的IP地址,也可以用—port参数来指定端口,默认端口为27017。
-
-#### MongoDB基本概念
-
-我们通过与关系型数据库进行对照的方式来说明MongoDB中的一些概念。
-
-| SQL         | MongoDB     | 解释(SQL/MongoDB)    |
-| ----------- | ----------- | ---------------------- |
-| database    | database    | 数据库/数据库          |
-| table       | collection  | 二维表/集合            |
-| row         | document    | 记录(行)/文档        |
-| column      | field       | 字段(列)/域          |
-| index       | index       | 索引/索引              |
-| table joins | ---         | 表连接/嵌套文档        |
-| primary key | primary key | 主键/主键(`_id`字段) |
-
-#### 通过Shell操作MongoDB
-
-启动服务器后可以使用交互式环境跟服务器通信,如下所示。
-
-```shell
-mongo --host 172.18.61.250
-
-MongoDB shell version v3.6.5
-connecting to: mongodb://172.18.61.250:27017/
-...
-> 
-```
-
-1. 查看、创建和删除数据库。
-
-   ```JavaScript
-   > // 显示所有数据库
-   > show dbs
-   admin   0.000GB
-   config  0.000GB
-   local   0.000GB
-   > // 创建并切换到school数据库
-   > use school
-   switched to db school
-   > // 删除当前数据库
-   > db.dropDatabase()
-   { "ok" : 1 }
-   >
-   ```
-
-2. 创建、删除和查看集合。
-
-   ```JavaScript
-   > // 创建并切换到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中插入文档时如果集合不存在会自动创建集合,所以也可以按照下面的方式通过创建文档来创建集合。
-
-3. 文档的CRUD操作。
-
-   ```JavaScript
-   > // 向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
-   }
-   > 
-   ```
-
-使用MongoDB可以非常方便的配置数据复制,通过冗余数据来实现数据的高可用以及灾难恢复,也可以通过数据分片来应对数据量迅速增长的需求。关于MongoDB更多的操作可以查阅[官方文档](https://mongodb-documentation.readthedocs.io/en/latest/) ,同时推荐大家阅读Kristina Chodorow写的[《MongoDB权威指南》](http://www.ituring.com.cn/book/1172)。
-
-####在Python程序中操作MongoDB
-
-可以通过pip安装pymongo来实现对MongoDB的操作。
-
-```Shell
-pip3 install pymongo
-python3
-```
-
-```Python
->>> from pymongo import MongoClient
->>> client = MongoClient('mongodb://120.77.222.217: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
-姓名: 白元芳
-性别: 男
->>> 
-```
+### 数据缓存
 
-关于PyMongo更多的知识可以通过它的[官方文档](https://api.mongodb.com/python/current/tutorial.html)进行了解
+通过[《网络数据采集和解析》](./02.数据采集和解析.md)一文,我们已经知道了如何从指定的页面中抓取数据,以及如何保存抓取的结果,但是我们没有考虑过这么一种情况,就是我们可能需要从已经抓取过的页面中提取出更多的数据,重新去下载这些页面对于规模不大的网站倒是问题也不大,但是如果能够把这些页面缓存起来,对应用的性能会有明显的改善。可以使用Redis来提供高速缓存服务,关于Redis的知识,我们在[《NoSQL入门》](../Day36-40/NoSQL入门.md)一文中已经做过简要的介绍。
 
 ### 实例 - 缓存知乎发现上的链接和页面代码
 
@@ -493,7 +64,3 @@ if __name__ == '__main__':
 
 
 
-
-
-
-

BIN
Day66-75/res/redis-aof.png


BIN
Day66-75/res/redis-bind.png


BIN
Day66-75/res/redis-database.png


BIN
Day66-75/res/redis-port.png


BIN
Day66-75/res/redis-rdb.png


BIN
Day66-75/res/redis-replication.png


BIN
Day66-75/res/redis-security.png


BIN
Day66-75/res/redis-slow-log.png