• docker+phantomjs+haproxy 搭建phantomjs集群


      目标:

        搭建一个远程的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: "2"
    services:
        phantomjs1:
            image: docker.io/wernight/phantomjs
            ports:
                - "8910"
            command: phantomjs --webdriver=8910 --cookies-file=/cookies.txt
            restart: always
            # 内存限制 单位 bytes 大B
            mem_limit: 2000000000
            expose:
                - "8910"
    
        phantomjs2:
            image: docker.io/wernight/phantomjs
            ports:
                - "8910"
            command: phantomjs --webdriver=8910 --cookies-file=/cookies.txt
            restart: always
            # 内存限制 单位 bytes 大B
            mem_limit: 2000000000
            expose:
                - "8910"
    
        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:
                - "8910"
                - "8911"
    View Code

      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  # 连续5次检查失败 则判定不可用
      timeout connect 5000ms
      timeout client 50000ms
      timeout server 50000ms
      timeout check 20s  # 超时20s才判定服务不可用
    
    listen stats
        bind 0.0.0.0:8911
        stats enable
        stats uri /
    
    frontend balancer
        bind 0.0.0.0:8910
        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 64
        server phantomjs1 phantomjs1:8910 check inter 10000  # 增加check检查间隔10s
        server phantomjs2 phantomjs2:8910 check inter 10000
        option httpchk GET /status
        http-check expect status 200
    View Code

      配置完毕

      在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
    View Code

          原理:

            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())
    )
    View Code

          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  

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

  • 相关阅读:
    firefox, chrome常见插件
    数据库左连接left join、右连接right join、内连接inner join on 及 where条件查询的区别
    Springmvc + mybatis + spring 配置,spring事物
    Android如何连接MySQL数据库
    Android MP3录音实现
    Android RecyclerView的基本使用
    Java输入流之BufferReader和Scanner的用法!
    Android 网络通信框架Volley简介
    your project contains error(s),please fix them before running your application.错误总结
    新建android项目报错,代码中找不到错误
  • 原文地址:https://www.cnblogs.com/dyfblog/p/8818811.html
Copyright © 2020-2023  润新知