目标:

    搭建一个远程的phantomjs服务器,提供高可用服务,支持并发。

  原料:

    1、docker环境、docker-compose环境

    2、phantomjs镜像: docker.io/wernight/phantomjs

    3、haproxy镜像:haproxy:latest

  docker-compose 项目目录结构

  phantomjs/

    haproxy/

      haproxy.cfg

    docker-compose.yml

  配置文件内容

  docker-compose.yml 配置

version: ""
services:
phantomjs1:
image: docker.io/wernight/phantomjs
ports:
- ""
command: phantomjs --webdriver= --cookies-file=/cookies.txt
restart: always
# 内存限制 单位 bytes 大B
mem_limit:
expose:
- "" phantomjs2:
image: docker.io/wernight/phantomjs
ports:
- ""
command: phantomjs --webdriver= --cookies-file=/cookies.txt
restart: always
# 内存限制 单位 bytes 大B
mem_limit:
expose:
- "" haproxy:
image: haproxy:latest
volumes:
- ./haproxy:/haproxy-override
- ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
links:
- phantomjs1
- phantomjs2
restart: always
ports:
- "8910:8910"
- "8911:8911"
expose:
- ""
- ""

  haproxy.cfg 配置内容

global
log 127.0.0.1 local0
log 127.0.0.1 local1 notice defaults
log global
mode http
#option httplog
option dontlognull
# option redispatch # 后端挂掉 则重定向别的机器
retries # 连续5次检查失败 则判定不可用
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
timeout check 20s # 超时20s才判定服务不可用 listen stats
bind 0.0.0.0:
stats enable
stats uri / frontend balancer
bind 0.0.0.0:
mode http
default_backend phantomjs_backends backend phantomjs_backends
mode http
option forwardfor
#balance source
balance hdr(Cookie) # 必须用这个 爬虫方面做了hook
#balance url_param sessionId check_post
server phantomjs1 phantomjs1: check inter # 增加check检查间隔10s
server phantomjs2 phantomjs2: check inter
option httpchk GET /status
http-check expect status

  配置完毕

  在phantomjs目录下执行

    启动命令  docker-compose up -d

    停止命令  docker-compose stop

  使用phantoms服务:

    http://机器ip:8910

  查看集群状态

    http://机器ip:8911

  

  下面天坑:

    一个一个看:

      1、python selenium远程连接phantomjs服务时使用的http链接不支持类似cookie、session之类的会话机制,

    而phantomjs由于使用了haproxy做负载均衡,haproxy默认是轮询后端服务器处理请求,每次请求都会定向到不同的

    后端服务器。所以selenium在第一次请求发起新建phantomjs session的命令,获取了 phantomjs sessionId

    之后,再次使用sessionId来操作phantomjs的时候,由于请求被发送到了不同的后端服务器,导致无法找到相应

    sessionId的资源,所以根本无法使用。而haproxy其他的负载均衡策略基本也都不可用。

      先明确一下我想达到的效果:

        1)第一次请求(新建phantomjs session)是随机分配,并且均匀分布的

        2)后续请求除非服务器挂掉,否则不能更改服务器(挂掉没办法,本次操作肯定中断了,得重新开始)

      下面逐个分析一下haproxy的负载均衡策略:

        1)roundrobin  默认轮询   不可用

        2)static-rr  根据权重  不可用(权重这个东西并不能保证绝对不换机器)

        3)leastconn 最少连接 呵呵

        4)source  对来源ip做hash  不可用(除非我的来源ip均匀分布,并且请求频率均匀分布,要不然

              肯定负载肯定会集中分布在某几台机器上)

        5)uri  对请求的url?前的部分或全部做hash 不可用(每次进行的操作都差不多,访问的api并不均匀分布)

        6)url_param  根据指定的GET参数(或POST参数)做hash  不可用 (第一次请求的时候木有sessionId 。。。)

        7)hdr(name) 根据指定的header(如user-agent)做hash  不可用 (selenium请求无状态 每个 + 每次 请求

              的header都一毛一样,还不让修改,不过我最终选的还是这个,后面会介绍如何修改

        8)rdp-cookie(name)  根据cookie来选择 不可用 ( selenium请求无状态

      下面是放大招的时刻:

        经过上面的分析,貌似没啥办法了,不过经过我苦思冥想,埋头研究selenium源码,终于发现了一个可以在不修改源码

      的情况下修改每次远程调用phantomjs api服务时发送请求的header的方法。废话不多说,上代码:

# coding:utf8
from selenium.webdriver.remote import remote_connection
# hook
import base64 class MyRemoteConnection(object):
@classmethod
def get_remote_connection_headers(cls, parsed_url, keep_alive=False):
"""
Get headers for remote request. :Args:
- parsed_url - The parsed url
- keep_alive (Boolean) - Is this a keep-alive connection (default: False)
""" headers = {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8',
'User-Agent': 'Python http auth'
} if parsed_url.username:
base64string = base64.b64encode('{0.username}:{0.password}'.format(parsed_url).encode())
headers.update({
'Authorization': 'Basic {}'.format(base64string.decode())
}) if keep_alive:
headers.update({
'Connection': 'keep-alive'
})
# 下面这几行是我加的 重点在于keep_alive的非严格限制 以及可以在创建
# remote driver是传递
headers.update({
"Cookie": keep_alive,
})
return headers # 覆盖selenium包中的对应方法
remote_connection.RemoteConnection.get_remote_connection_headers = MyRemoteConnection.get_remote_connection_headers

      原理:

        selenium.webdriver.remote.remote_connection中有个类 RemoteConnection的get_remote_connection_headers

      方法控制每次调用api时使用的header,并且还接受一个参数 keep_alive,更重要的是 keep_alive参数在创建remote

      driver的时候可以传递,更更重要的是这个keep_alive 参数无论在哪里都只检查bool值,而不是具体值,所以我们可以把

      它作为一个唯一标识符,来放到header中,并在haproxy中做对应值的检查,只要生成keep_alive的算法是均匀分布的,就

      完美满足了我的要求。

        于是我选择了header中的Cookie值,在代码运行前动态hook这个方法,将keep_alive放入header中的Cookie值,然

      后在创建webdriver对象的时候生成一个唯一的keep_alive值传递进去,见代码:

# coding:utf8
import time
import hashlib
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium import webdriver desired_capabilities = DesiredCapabilities.PHANTOMJS.copy()
browser = webdriver.Remote(
command_executor='http://localhost:8910',
desired_capabilities=desired_capabilities,
# 被hook 作为 唯一标示
keep_alive="{}".format(hashlib.md5("{}".format(time.time()).encode()).hexdigest())
)

      2、phantomjs内存无限制的话,跑着跑着docker宿主机就挂了,所以需要在docker-compose配置文件中对每个phantomjs容器

    增加内存限制、以及自动容器挂掉重启。

      3、haproxy会对容器健康情况做检查,这里需要对检查间隔和检查方法都做一下调整,放点水,要不然phantomjs容器很快就会内存

    爆掉,然后重启,然后你的连接断开,然后你的程序一多半时间都在尝试连接中度过。

       为啥内存会爆掉呢?还得说说haproxy负载均衡的问题,由于phantomjs服务在负载高的情况下响应速度比较慢,如果你对容器健康

    情况的检查很严格,那么就会经常把一个webdriver的请求发送到其他的服务器上,因为原来的服务器经常貌似不可用,于是原来的服务器

    上就会残留很多没有被关闭的session,一直在吃内存。。。,于是很快内存就不够了,然后容器挂掉,重启,然后同样的一幕继续上演。。

        针对这个情况,对haproxy做了如下配置:

        1)禁用转发 机器挂掉也不换机器 就这么任性的等机器恢复

          # option redispatch  # 后端挂掉 则重定向请求到别的服务器

        2)增加健康检查失败判定不可用的次数

          retries 5 # 失败5次后才判定此服务器不可用

        3)增加健康检查超时时间

          timeout check 20s

        4)增加健康检查间隔

          server phantomjs1 phantomjs1:8910 check inter 10000 # 检查间隔设置为10S

        5)使用 /status 接口来判定服务是否正常

          option httpchk GET /status

          http-check except status 200

  总结:

    坑 1 很重要,解决了集群可用性问题

    坑 2 3 也很重要,解决了集群稳定性问题

    最后,不要忘记对 docker-compose 增加一个定时重启的任务,我是用的crontab 每20分钟重启一次,要不然机器的负载蹭蹭的涨、内

  存哗哗的掉,一会就等着收服务器的尸吧。

  

  后续估计还得增加一下自动发现功能,毕竟不能老是自己去修改配置文件来增加phantomjs服务器的数量。。。

  PS:

    苦逼爬虫。。  为了提高效率忙活了两天,结果还好,成功提升了将近10倍效率 :)

    说一下服务器配置:

      腾讯云  8核 32G内存 20M带宽 普通云主机

      然后我启动了12个phantomjs容器。。。

      然后服务器负载在14、15、16左右,虽然挺高的,但还好,能用了

  如果有大神有更好的办法,欢迎交流

  参考:

    HAproxy指南之haproxy配置详解1(理论篇)

    关于haproxy负载均衡的算法整理

    负载均衡软件HAProxy有哪些优点?

    项目详解4—haproxy 反向代理负载均衡

    HAProxy, session sticky and balance algorithm

    HOW TO LOAD BALANCE AN HTTP SERVER (USING WITH HAPROXY OR POUND)

    Compose file version 2 reference  

  转载请注明来源。。。  我咋这么自觉呢

docker+phantomjs+haproxy 搭建phantomjs集群的更多相关文章

  1. docker容器中搭建kafka集群环境

    Kafka集群管理.状态保存是通过zookeeper实现,所以先要搭建zookeeper集群 zookeeper集群搭建 一.软件环境: zookeeper集群需要超过半数的的node存活才能对外服务 ...

  2. Haproxy搭建web集群

    目录: 一.常见的web集群调度器 二.Haproxy应用分析 三.Haproxy调度算法原理 四.Haproxy特性 五.Haproxy搭建 Web 群集 一.常见的web集群调度器 目前常见的we ...

  3. 用 HAproxy 搭建 RabbitMQ 集群

    构建参考: [ Rabbitmq cluster setup with HAproxy ] [ python demo ] RabbitMQ Cluster 遇到的问题 python pika 作为c ...

  4. RabbitMQ:Docker环境下搭建rabbitmq集群

    RabbitMQ作为专业级消息队列:如何在微服务框架下搭建 使用组件 文档: https://github.com/bijukunjummen/docker-rabbitmq-cluster 下载镜像 ...

  5. docker使用Dockerfile搭建spark集群

    1.创建Dockerfile文件,内容如下 # 基础镜像,包括jdk FROM openjdk:8u131-jre-alpine #作者 LABEL maintainer "tony@163 ...

  6. Docker环境下的前后端分离项目部署与运维(六)搭建MySQL集群

    单节点数据库的弊病 大型互联网程序用户群体庞大,所以架构必须要特殊设计 单节点的数据库无法满足性能上的要求 单节点的数据库没有冗余设计,无法满足高可用 单节点MySQL的性能瓶领颈 2016年春节微信 ...

  7. docker搭建mysql集群

    目录 一.集群方案 二.安装PXC集群 三.Haproxy负载均衡 四.访问测试 五.节点宕机或重启 六.参考 一.集群方案 1.Replication 速度快,但仅能保证弱一致性,适用于保存价值不高 ...

  8. docker搭建Hadoop集群

    一个分布式系统基础架构,由Apache基金会所开发. 用户可以在不了解分布式底层细节的情况下,开发分布式程序.充分利用集群的威力高速运算和存储. 首先搭建Docker环境,Docker版本大于1.3. ...

  9. Docker 搭建 etcd 集群

    阅读目录: 主机安装 集群搭建 API 操作 API 说明和 etcdctl 命令说明 etcd 是 CoreOS 团队发起的一个开源项目(Go 语言,其实很多这类项目都是 Go 语言实现的,只能说很 ...

随机推荐

  1. IIS 域名 带参数 设置重定向

    IIS里面设置重定向后,经常会出现,从百度快照里直接打不开的情况. 可以在IIS里面设置重定向的时候,把参数加上,格式如下: http://www.***.com%S%Q

  2. laravel 门面的介绍和使用

    #上文讲述了laravel中怎么用容器依赖注入类的示例.其实在服务提供者上面在封装一层静态调用,这就是门面.静态调用门面,返回了容器中注册的别名和实例. #下面是测试的示例 #先创建要操作的类 < ...

  3. python基础之Day5

    一.基本概念 为什么要有数据: 计算机能够像人一样识别现实生活中的状态是因为计算机事先将数据存到了记忆中 为什么要分类型: 满足现实世界不同状态的需要 二.数据类型(研究定义,作用,常见操作) 1.整 ...

  4. [Robot Framework] 如何在Setup中用Run Keywords执行多个带参数的关键字

    参考文档:http://www.howtobuildsoftware.com/index.php/how-do/bZ7q/robotframework-setup-teardown-robot-fra ...

  5. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  6. Minimum Window Substring LT76

    Given a string S and a string T, find the minimum window in S which will contain all the characters ...

  7. java mail 读取邮件列表,

    // 准备连接服务器的会话信息 Properties props = new Properties(); props.setProperty("mail.store.protocol&quo ...

  8. c#中将字符串转换成带2位小数的浮点数

    今天遇到一个展示酒店价格的需求,觉得是要显示成“¥0.00”样式的,就做个小随笔,将字符串装换成带2位小数的浮点数 代码如下 "; string amount = string.Empty; ...

  9. C语言基础第四次作业

    题目7-2,九九乘法表 1.实验代码: #include<stdio.h> int main() { int N, i, j, q; scanf("%d",&N ...

  10. Devexpress VCL Build v2015 vol 15.1.2发布

    2015年马上过半年了.终于第一个大版出来了. What's New in 15.1.2 (VCL Product Line)   New Major Features in 15.1 What's ...