SunXinFei / sunxinfei.github.io

前后端技术相关笔记,已迁移到 Issues 中
https://github.com/SunXinFei/sunxinfei.github.io/issues
32 stars 3 forks source link

Docker + Node + Pm2 + Redis + Puppeteer #20

Open SunXinFei opened 5 years ago

SunXinFei commented 5 years ago

centOS7安装node+pm2+chrome步骤

安装node
sudo wget https://nodejs.org/dist/v10.16.0/node-v10.16.0-linux-x64.tar.xz
sudo xz -d node-v10.16.0-linux-x64.tar.xz 
sudo tar -xf node-v10.16.0-linux-x64.tar
命令指向
sudo ln -s  /usr/local/sin_mobile/node-v10.16.0-linux-x64/bin/node /usr/bin/node
sudo ln -s /usr/local/sin_mobile/node-v10.16.0-linux-x64/bin/npm /usr/bin/npm
安装pm2
sudo npm install -g pm2
sudo ln -s /usr/local/sin_mobile/node-v10.16.0-linux-x64/bin/pm2 /usr/local/bin/
下载chrome安装包
sudo wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm
安装chrome安装包
sudo yum localinstall google-chrome-stable_current_x86_64.rpm或sudo rpm -ivh google-chrome-stable_current_x86_64.rpm

Selenium在MAC中的环境搭建

Selenium由于不再迭代,现在主流爬虫都使用的--Puppeteer 由于mac中自带python,那么我们可以避开python的安装。

  1. 运行sudo easy_install pip 安装pip;
  2. 运行sudo pip install selenium 安装selenium;
  3. 安装与运行机器的chrome浏览器版本号相同的chromedriver brew install chromedriver; 注意:如果运行该命令报错:则改为如下命令去执行:
    brew tap caskroom/cask
    brew cask install chromedriver

    新建test.py,粘贴下面的内容;保存之后使用python test.py执行,即可得到运行结果与爬虫截图。

    
    #-*-  coding:utf-8 -*-
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    #要想调用键盘按键操作需要引入keys包
    from selenium.webdriver.common.keys import Keys
    import time
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    driver = webdriver.Chrome(chrome_options=chrome_options)
    driver.set_window_size(1366, 768)
    driver.get("http://www.baidu.com")
    # 获取页面名为wraper的id标签的文本内容
    data = driver.find_element_by_id('wrapper').text
    #打印数据内容
    print(data)
    print driver.title
    #生成页面快照并保存
    driver.save_screenshot("baidu.png")
    #获取当前页面Cookie
    print(driver.get_cookies())
    #ctrl+a全选输入框内容
    driver.find_element_by_id('kw').send_keys(Keys.CONTROL, 'a')
    #ctrl+x剪切输入框内容
    driver.find_element_by_id('kw').send_keys(Keys.CONTROL, 'x')

输入框重新输入内容

driver.find_element_by_id('kw').send_keys(u'百度')

模拟Enter回车键

driver.find_element_by_id('su').send_keys(Keys.RETURN) time.sleep(5)

清空输入框内容

driver.find_element_by_id('kw').clear()

生成新的页面快照

driver.save_screenshot('test.png')

获取当前url

print(driver.current_url) driver.close()

SunXinFei commented 5 years ago

Docker部署方式

【issue】 上面全局安装这种会造成全局环境污染,这里我们使用docker部署相关服务。 Docker中一些概念这里就不再赘述了,重要概念提一下分别是镜像和容器, 镜像可以用远端下载的也可以自己生成,容器是运行起来的环境,可以从主机进入容器内部运行命令。

常用docker命令: service docker restart #docker服务启动 service docker stop #docker服务停止 docker images #查看已有镜像 docker ps #查看活动中的容器 docker ps -a #查看所有容器 docker rm 容器id/容器名称 #移除容器,注:需要先stop docker stop 容器id/容器名称 docker restart 容器id/容器名称 docker exec -it 容器id/容器名称 bash #进入容器内,执行命令 docker exec -it 容器id/容器名称 命令 # 不需进入容器,执行命令 docker logs 容器id/容器名称# 查看docker日志 docker run 参数 镜像名称 生成容器,-it进入容器内,--restart=always-v 主机路径: 容器路径 挂载路径到主机, -p 80:3200 主机80映射给容器内3200端口,--name crawler-node 容器名称,-d 后台运行。

安装docker-nginx并配置

SunXinFei commented 5 years ago

Dockerfile

Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

FROM <基础镜像名称>

基于某一个基础镜像进行拉取

COPY <源路径1>... <目标路径>

复制指令,从上下文目录中复制文件或者目录到容器里指定路径 <目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。

RUN <命令行命令>

用于执行后面跟着的命令行命令,用于构建镜像阶段 注意:以 && 符号连接命令,这样执行后,只会创建 1 层镜像,比不加&&符号体积要小很多。

CMD ["<可执行文件或命令>","","",...]

类似于 RUN 指令,用于运行程序,但二者运行的时间点不同: 1. CMD 在docker run 时运行。2. RUN 是在 docker build。 注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。

用Dockerfile构建镜像

进入Dockerfile所在文件夹,运行docker build -t 新镜像名称 . 注意:最后的 . 代表本次执行的上下文路径,默认上下文路径就是 Dockerfile 所在的位置

docker-compose.yml

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

其实主要就是避免了过于复杂的docker run命令参数,且可以依次运行出多个容器。

version: '3'
services:
    xxx_nginx:
        container_name: xxx_nginx
        network_mode: host
        image: 镜像名称/地址
        volumes:
            -  宿主机地址:容器内地址

用compose创建容器

docker-compose up -d -d参数表示后台运行

SunXinFei commented 4 years ago

Docker 空间使用分析与清理

【issue】 docker的空间由于一段时间运行发现占满了磁盘,故作出清理

# 查看整体磁盘占用量
[root@localhost ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda6       7.8G  7.6G     0 100% /var
# 也可根据使用的存储驱动的不同,相应目录会有所不同:
[root@node3 docker]# du -h --max-depth=1 |sort
7.5G   ./overlay2   # 这个目录占用了非常高的磁盘磁盘空间
#进入overlay2目录,查看具体子文件夹体积
du -sh *
[root@localhost overlay2]# du -sh *
1.1G    043895ffff36d99286b393c24b69d9936eb73c443c530d83bd5f9bf68f3968f2
684M    067f907d8d0e9986fe5c6a4b1d2de9120e9cb5b333a0f98219b9cfbb0d95e891
44K 067f907d8d0e9986fe5c6a4b1d2de9120e9cb5b333a0f98219b9cfbb0d95e891-init
560M    29ca2111560fa21ded9721a48edaf0664c7796a4e54a651a1127141e5ff2ebb3
396M    4624731b9981661378c4e519e5628e3de5b224e37765967f5e6ee3d37cdfe3e0
111M    509e61fd254b761bd57de30a2c315b8c9e73c2d5fc972049f43634144209678d
162M    6560f3a2dbdc455a01e42517cda4dbd620a2233f4a69f0bdc96ac6f4195a9403
25M 6be5989c62ecfefc9d5f3da75fb5260c05630f5a82bb54881eaee8b16fb74475
166M    76fef89179df7f1b77716922494719718314cc7f5787d5545e813027d9162253
44K 76fef89179df7f1b77716922494719718314cc7f5787d5545e813027d9162253-init
208M    7de7fd55dfcdcd256d35add4c8d9ad6a7c49807aa3f9359f94fb6e3da9f0ca1d
150M    8157ff3137fa6fd0b675d0a09b16b5bdd0c526c6a813fd1c1cedfbaf82c98a0b
36K 8ec926ca8d01cf1a1f5e434a720b69d99359cfb18419adda29ccd2f9e8cfad01
480M    a318592d773dd79e7c6661455b574b6a168c4a9f536a9d32b7594474539eb10b
44K a318592d773dd79e7c6661455b574b6a168c4a9f536a9d32b7594474539eb10b-init
2.6G    aa2fef252df88e4ae0723bcc5280a75865625824b029c813da2f91614c4e479f
44K aa2fef252df88e4ae0723bcc5280a75865625824b029c813da2f91614c4e479f-init
62M aab3fd31db16cb204fd77297c4126842a38631808da6f1c8aa4aec571ca7f7e3
59M acb6bd732166f7c6d155b7a395e1c7699e6ecbba0c0f6da520d7e3641f46de76
36K c2c6ca753469e4aa604dd3974836c471b835996399a542718cab97a24f8f22f2
8.2M    c944d3c8803f09695833aa77fe265eafc476bdbf092e4b130b2bbb5bb691b8ed
54M cdfa3aefc204b33a0d301fd1c11f230ab41dd4f6826e9261c28a060c018a87ba
213M    f1e3485ead1b6cc005e0e0e21676c6a755a3d8b08c4a2460988b328adec841a8
72K f3ecc3546fa8774acad5d6ef95334fbfe5f5b2da08057fa15fdce681b8f26d4c
44K f3ecc3546fa8774acad5d6ef95334fbfe5f5b2da08057fa15fdce681b8f26d4c-init
104K    l
#进行镜像详细分析
[root@localhost overlay2]# docker system df -v
Images space usage:

REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE                SHARED SIZE         UNIQUE SIZE         CONTAINERS
asdasdasdsdasdsaddasds/dsdsdsd/crawler   v0.2                f72977c649ea        7 weeks ago         717.6 MB            0 B                 717.6 MB            1
docker.io/nginx                          latest              719cd2e3ed04        8 weeks ago         109.3 MB            0 B                 109.3 MB            1
nginx/php56                              v0.1                8cd464ac166f        3 months ago        1.235 GB            196.6 MB            1.039 GB            0
docker.io/centos                         centos7.4.1708      9f266d35e02c        4 months ago        196.6 MB            196.6 MB            0 B                 0
Containers space usage:

CONTAINER ID        IMAGE                                         COMMAND                  LOCAL VOLUMES       SIZE                CREATED             STATUS                            NAMES
69b30a75ff6d        asdasdasdsdasdsaddasds/dsdsdsd/crawler:v0.2   "sdsd"                   0                   2.8 GB              6 weeks ago         Up About a minute                 crawler-node
a8a3352218db        docker.io/nginx                               "sdsdsdsdsdsdsdsdsdsd"   0                   2 B                 7 weeks ago         Exited (255) About a minute ago   crawler-nginx

Local Volumes space usage:

VOLUME NAME         LINKS               SIZE
[root@localhost overlay2]# docker ps -a
CONTAINER ID        IMAGE                                         COMMAND                  CREATED             STATUS                       PORTS                    NAMES
69b30a75ff6d        asdasdasdsdasdsaddasds/dsdsdsd/crawler:v0.2   "sdsd"                   6 weeks ago         Up 2 minutes                 0.0.0.0:3200->3200/tcp   crawler-node
a8a3352218db        docker.io/nginx                               "sdsdsdsdsdsdsdsdsds."   7 weeks ago         Exited (255) 2 minutes ago   0.0.0.0:80->80/tcp       crawler-nginx

docker system prune

docker system prune 自动清理说明:

该指令默认会清除所有如下资源:

该指令默认只会清除悬空镜像,未被使用的镜像不会被删除。 添加 -a 或 --all 参数后,可以一并清除所有未使用的镜像和悬空镜像。 可以添加 -f 或 --force 参数用以忽略相关告警确认信息。 指令结尾处会显示总计清理释放的空间大小。

[root@localhost logs]# docker system prune
WARNING! This will remove:
    - all stopped containers
    - all volumes not used by at least one container
    - all networks not used by at least one container
    - all dangling images
Are you sure you want to continue? [y/N] y
Deleted Containers:
6e8dfad222ddfb66ac7dfc7a4f6eb86d902c66e94a1a0740462845fd5be76e31
a8a3352218dbf65b611a62658f7cd64655f5acb3393dbc1a138e7aa7f4d4c658
69b0e7b00f3cf01d1855aded7e38c7753623a5e5f4486f38245c8f497e1cb10e
2b31d6e54bbbaf6f2784f15a392517598bc7ba2763c96b7a1168c94a6654f54f

Total reclaimed space: 1.332 GB

Tips :

不同状态的镜像

  • 已使用镜像(used image): 指所有已被容器(包括已停止的)关联的镜像。即 docker ps -a 看到的所有容器使用的镜像。
  • 未引用镜像(unreferenced image):没有被分配或使用在容器中的镜像,但它有 Tag 信息。
  • 悬空镜像(dangling image):未配置任何 Tag (也就无法被引用)的镜像,所以悬空。这通常是由于镜像 build 的时候没有指定 -t 参数配置 Tag 导致的。
  • 悬空镜像(dangling image)
  • 挂起的卷(dangling Volume)

类似的,dangling=true 的 Volume 表示没有被任何容器引用的卷。

总体瘦身后的docker体积详情

Images space usage:

REPOSITORY                               TAG                 IMAGE ID            CREATED             SIZE                SHARED SIZE         UNIQUE SIZE         CONTAINERS
asdasdasdsdasdsaddasds/dsdsdsd/crawler   v0.2                f72977c649ea        7 weeks ago         717.6 MB            0 B                 717.6 MB            1
docker.io/nginx                          latest              719cd2e3ed04        8 weeks ago         109.3 MB            0 B                 109.3 MB            0

Containers space usage:

CONTAINER ID        IMAGE                                         COMMAND             LOCAL VOLUMES       SIZE                CREATED             STATUS              NAMES
69b30a75ff6d        asdasdasdsdasdsaddasds/dsdsdsd/crawler:v0.2   "bash"              0                   54 MB               6 weeks ago         Up 2 hours          crawler-node

Local Volumes space usage:

VOLUME NAME         LINKS               SIZE

参考文档: Docker 空间使用分析与清理 进程管理器pm2运维小结

SunXinFei commented 4 years ago

Nodejs + Redis

【issue】 项目出现被调用者频繁地调用,为了有效控制执行的速度,将接口修改为调用者调用后,将参数存储到队列中,node服务定时去获取队列,执行完成之后去消耗队列,这里可以是消耗kafka或者存放redis,kafka相对太重,故使用了后者

redis

通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

Redis 优势 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

Redis与其他key-value存储有什么不同? Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。 Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,应为数据量不能大于硬件内存。在内存数据库方面的另一个优点是, 相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。 同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

mac中Redis环境搭建(本地调试使用)

命令 描述
-h 域名 -p 端口号 链接远程redis服务
GET key 获取 key 的值
EXITS key 查看此 key 是否存在
KEYS * 查看所有的 key (线上不允许使用)
FLUSHALLl 消除所有的 key
TTL key 查看该key过期时间
DEL key 删除该key
列表 一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
LLEN key 查看key下的列表长度;key不存在则为0,如果列表为空则会被redis删除该key
LRANGE key start stop 获取列表指定范围内的元素;start=0, end = -1 表示查看列表从开始到结束的值,start=0, end = 0 表示查看列表第一个元素
RPUSH key value1 [value2] 在列表中添加一个或多个值
LPOP key 移出并获取列表的第一个元素;列表为空则返回null
集合 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
SADD key member1 [member2] 向集合添加一个或多个成员;集合不允许重复key,重复添加则返回0
SMEMBERS key 查看key下的集合所有的值
SREM key member1 [member2] 移除集合中一个或多个成员

node中的使用redis

  1. 安装ioredis;redis client的node包有很多,这里放弃使用redis的原因是,get和set其实都是异步调用,ioredis直接就可以使用await/async,而redis虽然支持二次封装,但是略微麻烦了一点点。
  2. node中的redis 是直接支持redis的方法的,所以直接看Redis命令教程,找到符合自己需要并且复杂度不高的方法即可
  3. 在node工程的config中创建redis.js,如下
    const Redis = require('ioredis')
    const options = {
    keyPrefix: 'demo-', //存诸前缀
    db: 0
    }
    const newRedis = new Redis(6379, '127.0.0.1', options);
    module.exports = newRedis
    
    const redis = require("../config/redis") // redis配置文件路径
    let timeId = null;

async function intervalGetRedis() { let targetUrl = await redis.lpop('app-urls-list'); if (targetUrl) {//如果不为null clearInterval(timeId); timeId = null; //同步app-urls-set-collect await redis.srem('app-urls-set-collect', targetUrl); //获取app getAppAttr(targetUrl); } else { if(!timeId){ timeId = setInterval(async () => { console.log('interval'); intervalGetRedis(); }, 1000 60 20);//20分钟 1000 60 20 } } }

intervalGetRedis();

//判读length长度 let redisArrLen = await redis.llen('app-urls-list'); if (redisArrLen > 100000) { //大于10w则清除key await redis.del('app-urls-list'); await redis.del('app-urls-set-collect'); } //sadd如果Set集合已存在该元素则为0 let addRedis = await redis.sadd('app-urls-set-collect', url); if (addRedis !== 0) {//url在队列中不存在 await redis.rpush('app-urls-list', url); }

SunXinFei commented 4 years ago

pm2日志清理

【issue】 在我们的node应用中,实际出现了pm2日志体积过大的情况,默认情况下pm2的日志是一个文件不会被拆分为多个,不方便清理,所以这里我们使用pm2-logrotate进行日志切割。 默认设置即可只需要执行配置这一句参数 pm2 set pm2-logrotate:retain 7

常用的pm2日志命令
pm2 logs  # 显示所有应用程序的日志
pm2 logs [app-name]  # 显示指定应用程序的日志
pm2 logs [--raw]   #Display all processes logs in streaming
pm2 flush              #Empty all log file
pm2 reloadLogs    #Reload all logs
实时监控:
pm2 monit //监控当前所有的进程
pm2 monit 0 //监控批评行编号为0的进程
pm2 monit server.js //监控名称为server.js的进程
SunXinFei commented 4 years ago

Puppeteer/chrome-remote-interface与Docker

【issue】 在node后台是放置在docker中执行的,调用之后,过了一段时间使用top命令发现了很多的僵尸进程chrome没有被回收 image

Puppeteer Troubleshooting

  1. 首先这个里面讲解了puppeteer在docker中安装过程以及运行中的常见问题,其中就包括运行进程崩溃的问题

docker中的/dev/shm默认64m,不足以运行chrome进程开启很多标签页,所以需要通过--shm-size=1gb去重新设置大小

这里要说明一下chrome-launcher的chromeFlags参数已经支持了--disable-dev-shm-usage去禁用,项目issue为https://github.com/GoogleChrome/puppeteer/issues/1834

  1. 在node服务代码中,我们默认的try catch/finally 并不会捕获到page.on('error')的出错的事件,导致浏览器没有被关闭,这里根据puppeteer的issue,有人使用node的fork process进行管理回收未被关闭的chrome,不过更为大多数人认同的是使用dumb-init处理僵尸进程

    我们为什么需要init系统

  2. 通常,当你启动 Docker 容器时,你正在执行的进程将变成 PID 1,给出容器的init系统。 这里有两个常见的问题: 在大多数情况下,信号不会被正确处理。 Linux内核将特殊信号处理应用于作为 PID 1运行的进程。 当进程发送一个普通的Linux系统信号时,内核会首先检查该进程已经注册的任何自定义处理器,否则回退到默认行为。 但是,如果接收信号的过程为 PID 1,则内核将获得特殊处理,如果没有为信号注册处理程序。 换句话说,如果你的进程没有显式处理这些信号,则发送 SIGTERM 将无效。

  3. dumb-init 作为 PID 1运行,像一个简单的init系统。 它启动一个进程,然后将所有接收到的信号代理到一个根进程的根进程。 由于你的实际进程不再是 PID 1,当它接收来自 dumb-init的信号时,将应用默认的信号处理程序。 如果你的流程死了,dumb-init 也会死掉,以便清理它的他可以能仍然保持不变的进程。

  4. 解决后的进程 image

参考文档: Puppeteer Troubleshooting Zombie Process problem instance.close() let zombie process dumb-init dumb-init:一个 Docker 容器初始化系统 深入理解docker信号机制以及dumb-init的使用 dumb-init, 用于Linux容器的最小初始化系统 docker- Specify an init process How to use --init parameter in docker run