他山之石,可以攻玉
引言
前两天写了个NodeJs爬虫,很简单那种。就是爬取某个网站的首页解析所有的Url然后再爬取内页。这里就带来了一个问题。众所周知,Js是单线程异步的,并以此为卖点。可是在这个场景就有一个很明显的问题。
比如首页有30个Url它会一次性解析30个Url然后同时去请求,失败率不可谓不高。类似的场景我曾经使用过用数组来模拟栈用于控制并发,然实在不能说很优雅,各种判断,轮训,时间长了再去看不合心意,于是便想到Redis。
这是一个NoSql数据库,经常用来做缓存,因为是存在内存里的效率很高,当然也支持存进硬盘做持久化,我看上它是有一个事务,能否用这个事务来做并发控制呢?就是我来学习它的初衷,毕竟既能解决缓存又可以做并发控制岂不美哉?
初探
- 安装
Redis的安装非常简单,以Linux为例:前往首页选择最新稳定版下载(官网下载地址:https://redis.io/download) $ wget http://download.redis.io/releases/redis-4.0.9.tar.gz $ tar xzf redis-4.0.9.tar.gz $ cd redis-4.0.9 $ make 好了装完了
- 使用
$ src/redis-server //开启本地服务器
$ src/redis-cli //开启本地客户端
redis> set foo bar
OK
redis> get foo
"bar"
以上是官网示例
默认端口:6379 可以通过 --port 来设置
cli 可以通过 -h 指定host地址,-p来指定端口
然后为了方便我们可以。
ln -s /home/pi/redis-4.0.9/src/redis-server /usr/bin/redis-server
ln -s /home/pi/redis-4.0.9/src/redis-cli /usr/bin/redis-cli
//注意一点,默认情况下Redis只能本地访问,设置密码以后才能远端访问。
- 基本概念
数据结构
Redis有五种数据结构:String,Hash,List,Set和zset。
**String(字符串) **
简单的键值对:SET name “Redis” GET name //输出 Redis
** Hash(哈希)**
是个键值集合,存数据的方式,当作Js的对象来理解似乎没有太大问题。
这个估计最常用
HMSET obj name “Redis” state “Studying”
HGGET obj name
//输出Redis
HGET obj
//输出 Redis
//Studying
List(列表)
字符串列表,按照输入顺序排序,类比Js的话,就是使用push和unpush的数组。
我觉得翻译成队列更合适。
lpush runoob redis
lpush runoob mongodb
lpush runoob rabitmq
lrange runoob 0 10
//输出
1) "rabitmq"
2) "mongodb"
3) "redis"
Set(集合)
学术性的说法叫:String的无序集合。
在我看来..相当于先创建一个空数组,然后一个值一个值往里加,与上面的区别似乎是这是个哈系表?。
sadd runoob
sadd runoob redis
sadd runoob mongodb
sadd runoob rabitmq
sadd runoob rabitmq
smembers runoob
//输出
1) "redis"
2) "rabitmq"
3) "mongodb"
zset(有序集合)
自带排序的集合..
学术性的说法:不允许重复的,每一个元素关联一个double类型数的String类型元素集合。每个元素虽然不能重复,但是被用来排序的double值是可以重复的。
通俗一点就是,下标只用来排序且允许重复,每个成员都是字符串且唯一的数组....
zadd runoob 0 redis
zadd runoob 0 mongodb
zadd runoob 0 rabitmq
zadd runoob 0 rabitmq
ZRANGEBYSCORE runoob 0 1000
1) "mongodb"
2) "rabitmq"
3) "redis"
- 基本操作
上面每一种都介绍一下的话,篇幅太长,我重点看看Hash的..其它估计也都差不多,举一反三。只要会增删改查感觉基本的应用就没问题了。
增:HMSET key name1 value1 name2 value2 ....
删:HDEL key [name value] //允许删除整个key 或者只删除key里面的某一个值
改:HMSET key name1 value1 name2 value2 .... //直接覆盖,和Js的对象一样..
查:HMGET key name //获取key下某个字段的值
HGETALL key //获取某key所有的字段和值
一些花样
HEXISTS key name //某Key 对应字段是否存在
HSETNX key field value //安全添加只有字段不存在的时候才会添加
HKEYS key //获取所有的关键字
HVALS key //获取所有的值
5.事务
恩,到重点了,我原本是想用这个做并发控制来着。只看描述似乎是没问题的。
而且事务似乎有Watch这个方法..检测某值如果改变则取消...
emmm..高并发的抢购活动似乎很有用,问题是,我可能需要一个,改变值才去执行的方法。
Redis似乎也有发布订阅....emmmm,太繁琐了..我可能不太需要一个观察者..
实际去阅读文档发现,这个事务执行的是Redis命令...我想想...
读取所有Url
取一部分用事务存入Reds。
再一条一条取出来下载。
下完了再执行这个步骤..
emmmm...我为什么不新建一个数组?
好吧,言归正传。
所谓事务
Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
- 一个事务从开始到执行会经历以下三个阶段:
三个阶段:
1. 开始事务。
2. 命令入队。
3. 执行事务。
注意点:
Redis的命令执行是原子性的,即不成功就失败,失败会回滚状态,不存在中间态。颇有“无胜利,毋宁死”的感觉。
但是事务不一样,虽然Redis的命令是原子性的,但是Redis的事务不是,也就是说,如果一组事务有几个失败了,成功的依旧不会回滚。
例子:
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
2) "C++"
3) "Programming"
- 其它
除了上面这些,Redis还有发布订阅,脚本,等其它知识点,只是暂时似乎用不上,所以,在此略过不表。
在Node.js中使用Redis
Node.js有一个Redis库,直接
npm install redis;
node
var redis = require('redis')
var client = redis.createClient(port, host);
这里就能拿到一个可以执行Redis的对象。
用法示例:
//原命令 hmset key name1 value1 name2 value2...
//在Js中
var mainkey = key;
vae setOBj={
name1:value1,
name2:value2,
...
}
client.hmset(mainKey,setObj,(err,res)=>{
if(err){
console.log(err);
return;
}
console.log(res);//正常这里会返回OK
client.quit(); //单次操作建议退出,不退出超时也会断开。
});
//原命令 hgetall key
var mainid = key;
client.HGETALL(mainid,(err,res)=>{
if(err){
console.log(err);
return;
}
result=res;//这里返回的是对象。
client.quit();
});
值得一提的是,hmset也好,HMSET也罢,它都认识..很方便。
需要注意的是,这里无论写入还是读取都是异步的,写入还好,如果是读取,恐怕需要注意使用async,await,和Promise来做同步操作。
这里我只是举了两个刚用到的列子,其它还有很多,github上写的很详细注意返回数据的时机就可以了。
结语
本以为利用Redis的事务可以做爬虫的并发控制,最后发现,并不能,如果它能在事务中执行下载的话,倒是可以做图片下载的并发控制..
还有待研究,这篇文章作为完整的入门来说并不合格,我仅仅只是发现一个问题,然后去解决,记录下来了解决过程罢了。
Node.js的Redis库暂时没看见中文文档,示例中有一些用的到的地方,看的有点费劲,程序员学好英语还是非常必要的。
关于Node.js中使用Redis等待返回,官网有个示例,不过,我是之后才去看到,所以,我用的自己的方法,但是,这个方法很蠢,一个请求创建了两个Promis,还有待优化。
简单介绍下:在Api中使用Async 创建一个Promis,await,调用一个带Async的函数,函数中掉用Client的方法,这里再来一个Promise,await。
去掉Api中的Async Promise await的话,内层掉用Client的函数会直接返回一个Promise即使没有读到return,推测是Client函数中的方法做的,经过一遍一遍的测试,最后变成了如上所述的结构。
//2018/5/30
修正这一段描述,今天看ES6的入门,这里的Promise是async返回的,猛然想到,如果去掉async是没有promise返回的,当时忽略了这一点。
做完后发现github上的示例有一个推荐写法....emmmmmm...
查文档很重要..好..那么就这样。