Disruptor 实践:整合到现有的爬虫框架

2024-01-08 22:58

本文主要是介绍Disruptor 实践:整合到现有的爬虫框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

640?wx_fmt=jpeg


一. Disruptor

Disruptor 是一个高性能的异步处理框架。

Disruptor 是 LMAX 在线交易平台的关键组成部分,LMAX平台使用该框架对订单处理速度能达到600万TPS,除金融领域之外,其他一般的应用中都可以用到Disruptor,它可以带来显著的性能提升。其实 Disruptor 与其说是一个框架,不如说是一种设计思路,这个设计思路对于存在“并发、缓冲区、生产者—消费者模型、事务处理”这些元素的程序来说,Disruptor提出了一种大幅提升性能(TPS)的方案。

二. 实践

NetDiscovery 是基于 Vert.x、RxJava 2 等框架实现的爬虫框架。

NetDiscovery 默认的消息队列采用 JDK 的 ConcurrentLinkedQueue,由于爬虫框架各个组件都可以被替换,所以下面基于 Disruptor 实现爬虫的 Queue。

2.1 事件的封装

将爬虫的 request 封装成一个 RequestEvent,该事件会在 Disruptor 中传输。

 
  1. import com.cv4j.netdiscovery.core.domain.Request;

  2. import lombok.Data;

  3. /**

  4. * Created by tony on 2018/9/1.

  5. */

  6. @Data

  7. public class RequestEvent {

  8.    private Request request;

  9.    public String toString() {

  10.        return request.toString();

  11.    }

  12. }

2.2 发布事件

下面编写事件的发布,从 RingBuffer 中获取下一个可写入事件的序号,将爬虫要请求的 request 设置到 RequestEvent 事件中,最后将事件提交到 RingBuffer。

 
  1. import com.cv4j.netdiscovery.core.domain.Request;

  2. import com.lmax.disruptor.RingBuffer;

  3. import java.util.concurrent.atomic.AtomicInteger;

  4. /**

  5. * Created by tony on 2018/9/2.

  6. */

  7. public class Producer {

  8.    private final RingBuffer<RequestEvent> ringBuffer;

  9.    private AtomicInteger count = new AtomicInteger(0); // 计数器

  10.    public Producer(RingBuffer<RequestEvent> ringBuffer) {

  11.        this.ringBuffer = ringBuffer;

  12.    }

  13.    public void pushData(Request request){

  14.        long sequence = ringBuffer.next();

  15.        try{

  16.            RequestEvent event = ringBuffer.get(sequence);

  17.            event.setRequest(request);

  18.        }finally {

  19.            ringBuffer.publish(sequence);

  20.            count.incrementAndGet();

  21.        }

  22.    }

  23.    /**

  24.     * 发送到队列中到Request的数量

  25.     * @return

  26.     */

  27.    public int getCount() {

  28.        return count.get();

  29.    }

  30. }

2.3 消费事件

RequestEvent 设置了 request 之后,消费者需要处理具体的事件。下面的 Consumer 仅仅是记录消费者的线程名称以及 request。真正的“消费”还是需要从 DisruptorQueue 的 poll() 中获取 request ,然后在 Spider 中进行“消费”。

 
  1. import com.lmax.disruptor.WorkHandler;

  2. import lombok.extern.slf4j.Slf4j;

  3. import java.util.concurrent.atomic.AtomicInteger;

  4. /**

  5. * Created by tony on 2018/9/2.

  6. */

  7. @Slf4j

  8. public class Consumer implements WorkHandler<RequestEvent> {

  9.    @Override

  10.    public void onEvent(RequestEvent requestEvent) throws Exception {

  11.        log.info("consumer:" + Thread.currentThread().getName() + " requestEvent: value=" + requestEvent.toString());

  12.    }

  13. }

2.4 DisruptorQueue 的实现

Disruptor 支持单生产者单消费者、多生产者、多消费者、分组等方式。

在 NetDiscovery 中采用多生产者多消费者。

在 RingBuffer 创建时,ProducerType 使用 MULTI 类型表示多生产者。创建 RingBuffer 采用了 YieldingWaitStrategy 。YieldingWaitStrategy 是一种WaitStrategy,不同的 WaitStrategy 会有不同的性能。

YieldingWaitStrategy 性能是最好的,适合用于低延迟的系统。在要求极高性能且事件处理线数小于CPU逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性。

 
  1.        ringBuffer = RingBuffer.create(ProducerType.MULTI,

  2.                new EventFactory<RequestEvent>() {

  3.                    @Override

  4.                    public RequestEvent newInstance() {

  5.                        return new RequestEvent();

  6.                    }

  7.                },

  8.                ringBufferSize ,

  9.                new YieldingWaitStrategy());

EventProcessor 用于处理 Disruptor 中的事件。

EventProcessor 的实现类包括:BatchEventProcessor 用于单线程批量处理事件,WorkProcessor 用于多线程处理事件。

WorkerPool 管理着一组 WorkProcessor。创建完 ringBuffer 之后,创建 workerPool:

 
  1.        SequenceBarrier barriers = ringBuffer.newBarrier();

  2.        for (int i = 0; i < consumers.length; i++) {

  3.            consumers[i] = new Consumer();

  4.        }

  5.        workerPool = new WorkerPool<RequestEvent>(ringBuffer,

  6.                        barriers,

  7.                        new EventExceptionHandler(),

  8.                        consumers);

启动 workerPool:

 
  1.        ringBuffer.addGatingSequences(workerPool.getWorkerSequences());

  2.        workerPool.start(Executors.newFixedThreadPool(threadNum));

最后是 DisruptorQueue 完整的代码:

 
  1. import com.cv4j.netdiscovery.core.domain.Request;

  2. import com.cv4j.netdiscovery.core.queue.AbstractQueue;

  3. import com.lmax.disruptor.*;

  4. import com.lmax.disruptor.dsl.ProducerType;

  5. import lombok.extern.slf4j.Slf4j;

  6. import java.util.concurrent.Executors;

  7. import java.util.concurrent.atomic.AtomicInteger;

  8. /**

  9. * Created by tony on 2018/9/1.

  10. */

  11. @Slf4j

  12. public class DisruptorQueue extends AbstractQueue {

  13.    private RingBuffer<RequestEvent> ringBuffer;

  14.    private Consumer[] consumers = null;

  15.    private Producer producer = null;

  16.    private WorkerPool<RequestEvent> workerPool = null;

  17.    private int ringBufferSize = 1024*1024; // RingBuffer 大小,必须是 2 的 N 次方

  18.    private AtomicInteger consumerCount = new AtomicInteger(0);

  19.    private static final int CONSUME_NUM = 2;

  20.    private static final int THREAD_NUM = 4;

  21.    public DisruptorQueue() {

  22.        this(CONSUME_NUM,THREAD_NUM);

  23.    }

  24.    public DisruptorQueue(int consumerNum,int threadNum) {

  25.        consumers = new Consumer[consumerNum];

  26.        //创建ringBuffer

  27.        ringBuffer = RingBuffer.create(ProducerType.MULTI,

  28.                new EventFactory<RequestEvent>() {

  29.                    @Override

  30.                    public RequestEvent newInstance() {

  31.                        return new RequestEvent();

  32.                    }

  33.                },

  34.                ringBufferSize ,

  35.                new YieldingWaitStrategy());

  36.        SequenceBarrier barriers = ringBuffer.newBarrier();

  37.        for (int i = 0; i < consumers.length; i++) {

  38.            consumers[i] = new Consumer();

  39.        }

  40.        workerPool = new WorkerPool<RequestEvent>(ringBuffer,

  41.                        barriers,

  42.                        new EventExceptionHandler(),

  43.                        consumers);

  44.        ringBuffer.addGatingSequences(workerPool.getWorkerSequences());

  45.        workerPool.start(Executors.newFixedThreadPool(threadNum));

  46.        producer = new Producer(ringBuffer);

  47.    }

  48.    @Override

  49.    protected void pushWhenNoDuplicate(Request request) {

  50.        producer.pushData(request);

  51.        try {

  52.            Thread.sleep(100);

  53.        } catch (InterruptedException e) {

  54.            e.printStackTrace();

  55.        }

  56.    }

  57.    @Override

  58.    public Request poll(String spiderName) {

  59.        Request request = ringBuffer.get(ringBuffer.getCursor() - producer.getCount() +1).getRequest();

  60.        ringBuffer.next();

  61.        consumerCount.incrementAndGet();

  62.        return request;

  63.    }

  64.    @Override

  65.    public int getLeftRequests(String spiderName) {

  66.        return producer.getCount()-consumerCount.get();

  67.    }

  68.    public int getTotalRequests(String spiderName) {

  69.        return super.getTotalRequests(spiderName);

  70.    }

  71.    static class EventExceptionHandler implements ExceptionHandler {

  72.        public void handleEventException(Throwable ex, long sequence, Object event) {

  73.            log.debug("handleEventException:" + ex);

  74.        }

  75.        public void handleOnStartException(Throwable ex) {

  76.            log.debug("handleOnStartException:" + ex);

  77.        }

  78.        public void handleOnShutdownException(Throwable ex) {

  79.            log.debug("handleOnShutdownException:" + ex);

  80.        }

  81.    }

  82. }

其中,pushWhenNoDuplicate() 是将 request 发送到 ringBuffer 中。poll() 是从 ringBuffer 中取出对应的 request ,用于爬虫进行网络请求、解析请求等处理。

总结:

爬虫框架 github 地址:https://github.com/fengzhizi715/NetDiscovery

上述代码是比较经典的 Disruptor 多生产者多消费者的代码,亦可作为样板代码使用。

最后,在爬虫框架是面向接口编程的,所以替换其中的任意组件都比较方便。

该系列的相关文章:

从API到DSL —— 使用 Kotlin 特性为爬虫框架进一步封装

使用Kotlin Coroutines简单改造原有的爬虫框架

为爬虫框架构建Selenium模块、DSL模块(Kotlin实现)

基于Vert.x和RxJava 2构建通用的爬虫框架


关注【Java与Android技术栈】

更多精彩内容请关注扫码

640?wx_fmt=jpeg


这篇关于Disruptor 实践:整合到现有的爬虫框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/585124

相关文章

C++必修:模版的入门到实践

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:C++学习 贝蒂的主页:Betty’s blog 1. 泛型编程 首先让我们来思考一个问题,如何实现一个交换函数? void swap(int& x, int& y){int tmp = x;x = y;y = tmp;} 相信大家很快就能写出上面这段代码,但是如果要求这个交换函数支持字符型

亮相WOT全球技术创新大会,揭秘火山引擎边缘容器技术在泛CDN场景的应用与实践

2024年6月21日-22日,51CTO“WOT全球技术创新大会2024”在北京举办。火山引擎边缘计算架构师李志明受邀参与,以“边缘容器技术在泛CDN场景的应用和实践”为主题,与多位行业资深专家,共同探讨泛CDN行业技术架构以及云原生与边缘计算的发展和展望。 火山引擎边缘计算架构师李志明表示:为更好地解决传统泛CDN类业务运行中的问题,火山引擎边缘容器团队参考行业做法,结合实践经验,打造火山

Python爬虫-贝壳新房

前言 本文是该专栏的第32篇,后面会持续分享python爬虫干货知识,记得关注。 本文以某房网为例,如下图所示,采集对应城市的新房房源数据。具体实现思路和详细逻辑,笔者将在正文结合完整代码进行详细介绍。接下来,跟着笔者直接往下看正文详细内容。(附带完整代码) 正文 地址:aHR0cHM6Ly93aC5mYW5nLmtlLmNvbS9sb3VwYW4v 目标:采集对应城市的

9 个 GraphQL 安全最佳实践

GraphQL 已被最大的平台采用 - Facebook、Twitter、Github、Pinterest、Walmart - 这些大公司不能在安全性上妥协。但是,尽管 GraphQL 可以成为您的 API 的非常安全的选项,但它并不是开箱即用的。事实恰恰相反:即使是最新手的黑客,所有大门都是敞开的。此外,GraphQL 有自己的一套注意事项,因此如果您来自 REST,您可能会错过一些重要步骤!

JavaWeb 学习笔记 spring+jdbc整合开发初步

JdbcTemplate类是Spring的核心类之一,可以在org.springframework.jdbc.core中找到它。JdbcTemplate类在内部已经处理数据库的建立和释放,可以避免一些常见的错误。JdbcTemplate类可直接通过数据源的应用实例化,然后在服务中使用,也可在xml配置中作为JavaBean应用给服务使用直接上一个实例步骤1.xml配置 <?xml version

[分布式网络通讯框架]----Zookeeper客户端基本操作----ls、get、create、set、delete

Zookeeper数据结构 zk客户端常用命令 进入客户端 在bin目录下输入./zkCli.sh 查看根目录下数据ls / 注意:要查看哪一个节点,必须把路径写全 查看节点数据信息 get /第一行代码数据,没有的话表示没有数据 创建节点create /sl 20 /sl为节点的路径,20为节点的数据 注意,不能跨越创建,也就是说,创建sl2的时候,必须确保sl

【服务器08】之【游戏框架】之【加载主角】

首先简单了解一下帧率 FixedUpdate( )   >   Update( )   >   LateUpdate( ) 首先FixedUpdate的设置值 默认一秒运行50次 虽然默认是0.02秒,但FiexedUpdate并不是真的0.02秒调用一次,因为在脚本的生命周期内,FixedUpdate有一个小循环,这个循环也是通过物理时间累计看是不是大于0.02了,然后调用一次。有

Java中的集合框架使用技巧

Java中的集合框架使用技巧 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!今天我们将深入探讨Java中集合框架的使用技巧,这些技巧能够帮助我们更高效地处理数据和优化程序性能。 Java集合框架概述 Java集合框架提供了一组实现了各种集合接口的类和接口,用于存储和操作数据。它包括列表、集合、队列和映射等数据结构,能够满足不

IIS10和Tomcat8整合

在网上找了很久,也试了很多,都没有弄好。后来根据这个博客,做一些小修小改,终于成功了。 我是从里面的IIS与TOMCAT整合那里开始看的。第一步上面要创建一个注册表,我没有创建。我是创建了一个名为“isapi_redirect.properties”的文件,放进tomcat安装目录的conf文件夹里面。里面内容为: # Configuration file for the Jakarta

[分布式网络通讯框架]----ZooKeeper下载以及Linux环境下安装与单机模式部署(附带每一步截图)

首先进入apache官网 点击中间的see all Projects->Project List菜单项进入页面 找到zookeeper,进入 在Zookeeper主页的顶部点击菜单Project->Releases,进入Zookeeper发布版本信息页面,如下图: 找到需要下载的版本 进行下载既可,这里我已经下载过3.4.10,所以以下使用3.4.10进行演示其他的步骤。