(架构设计)观察者模式+redis队列实现不同项目之间数据的交互

一,简介
最近做一个项目,主要功能是根据一些关键词去百度爬取一些数据,如pv,uv等等有价值的数据,或者对应的URL等百度排名。
我们小组主要负责的是前端的功能,此前端非WEB前端,我们主要将用户导入的数据进行封转,然后转换为protobuf的传输格式对象,
最后保存到redis的队列中。
而另一个小组(由技术总监那边负责的)则是负责爬虫的业务,他会定时扫描redis队列的数据进行数据的抓取。

所以,会有两个java工程来协同工作。其中对接的桥梁就是redis的队列。
我们这边的项目会引入技术总监那边的一个jar,这个jar包的主要功能将数据提交到Redis队列,
我们需要传递过去的参数有队列名称,和list集合数据。(list中的集合是protobuf的传输对象)

那边的工程就会去扫描队列里面的数据,从队列lpop一个数据,直到队列里面的数据读完,然后开始调用回调的方法。
我们这边会去注册handler来响应那边的回调。(这里使用到了观察者模式)

二,流程
keyword,url是需要爬取的数据。
1,kw获取数据,对数据进行清洗的工作,然后保存到数据库,数据库里面的对应关系是taskid - > keyword,url [一个任务下面会有n个关键词,url连接]
2,定时器定时扫描未开始执行的任务,然后从根据任务的id获取对应下的所有keyword,url,封装为protobuf对象,并添加到集合中,最后将list扔到队列。
3,注册队列,只有被注册过的队列名,ce那边才会去扫描该队列下的数据。
4,注册handler,处理ce那边的回调。
5,ce那边处理redis的数据,知道数据没有了,就会调用我们注册的handler,将任务的id发给我们。
6,我们根据这个id再继续获取该任务id下面的所有keyword和url,然后根据他们的主键来获取爬虫抓取回来的数据。

三,代码的实现
两个项目的对接的是redis数据库,kw负责将前端的数据放到redis,ce取redis的数据,这个时候kw是生产者,ce是消费者。
【ce抓取到数据保存到数据库,然后kw去爬虫数据库里面取数据,这个时候ce是生产者,kw是消费者】

如何实现上述的过程,其实是由技术总监提供的jar中的TaskDirector类来实现的。

public class TaskDirector {
    private static final String QUEUE_TASK_SPLIT = "_";
    private static final String QUEUE_TASK_LIST_END = "_task";
    private static final String QUEUE_TASK_LIST_SUBMITED_END = "_submited";
    private static final String QUEUE_TASK_LIST_COMPLETED_END = "_completed";
    private static QueueClient client;
    private static List<String> watchList = null;
    private static ScheduledExecutorService executor;
    private static ITaskCompleteHandler handler;
    private static InitialLock lock = new InitialLock();

    public TaskDirector() {
    }

    public static void intialize(final String hosts) {
        lock.doInit(new Runnable() {
            public void run() {
                TaskDirector.client = QueueManager.getClient(hosts, "");
                TaskDirector.executor = Executors.newScheduledThreadPool(20);
                TaskDirector.watchList = new ArrayList(30);
            }
        });
    }

    public static void registerWatchQueue(final String queueName) {
        try {
            if(watchList.contains(queueName)) {
                return;
            }

            watchList.add(queueName);
            executor.scheduleAtFixedRate(new Runnable() {
                public void run() {
                    try {
                        TaskDirector.doWatchQueue(queueName);
                    } catch (Exception var2) {
                        ;
                    }

                }
            }, 10L, 60L, TimeUnit.SECONDS);
        } catch (Exception var2) {
            ;
        }

    }

    public static void registerHandler(ITaskCompleteHandler h) {
        handler = h;
    }

    public static void submitTaskDirect(String queueName, CrawlingTask task) {
        if(task != null && StringUtils.isNotEmpty(task.getTaskId()) && task.getProtos() != null && task.getProtos().size() > 0) {
            try {
                Iterator var3 = task.getProtos().iterator();

                while(var3.hasNext()) {
                    ProtoEntity p = (ProtoEntity)var3.next();
                    byte[] data = p.toByteArray();
                    client.enqueue(queueName, data);
                }
            } catch (Exception var5) {
                ;
            }
        }

    }

    public static void submitTask(String queueName, CrawlingTask task) {
        boolean hasWatched = false;
        Iterator var4 = watchList.iterator();

        while(var4.hasNext()) {
            String p = (String)var4.next();
            if(p.equals(queueName)) {
                hasWatched = true;
            }
        }

        if(hasWatched) {
            if(task != null && StringUtils.isNotEmpty(task.getTaskId()) && task.getProtos() != null && task.getProtos().size() > 0) {
                try {
                    var4 = task.getProtos().iterator();

                    while(var4.hasNext()) {
                        ProtoEntity p1 = (ProtoEntity)var4.next();
                        byte[] data = p1.toByteArray();
                        client.enqueueX(queueName, getTaskQueueName(queueName, task.getTaskId()), data);
                    }

                    client.enqueueX(queueName, getTaskListName(queueName), task.toByteArray());
                    client.enqueueX(queueName, getTaskListSubmitedName(queueName), formatBytes(task.getTaskId()));
                } catch (Exception var6) {
                    ;
                }
            }

        }
    }

    public static void cancelTask(String queueName, CrawlingTask task) {
        List tasksOrdered = taskList(queueName, false);

        for(int i = 0; i < tasksOrdered.size(); ++i) {
            if(((CrawlingTask)tasksOrdered.get(i)).equals(task.getTaskId())) {
                if(i == tasksOrdered.size() - 1) {
                    client.clearQueue(queueName);
                }

                client.clearQueue(getTaskQueueName(queueName, task.getTaskId()));
                client.queueTrimX(queueName, getTaskListName(queueName), (long)(i + 1), (long)(i - (tasksOrdered.size() - 1)));
                if(handler != null) {
                    handler.onTaskCancelled(queueName, task);
                }
                break;
            }
        }

    }

    public static List<CrawlingTask> upgradeTask(String queueName, CrawlingTask task) {
        List tasksOrdered = taskList(queueName, false);

        for(int i = 0; i < tasksOrdered.size() - 1; ++i) {
            if(((CrawlingTask)tasksOrdered.get(i)).getTaskId().equals(task.getTaskId())) {
                client.queueTrimX(queueName, getTaskListName(queueName), (long)(i + 1), (long)(i - (tasksOrdered.size() - 1)));
                client.queueInsertX(queueName, getTaskListName(queueName), ((CrawlingTask)tasksOrdered.get(i + 1)).toByteArray(), ((CrawlingTask)tasksOrdered.get(i)).toByteArray());
                break;
            }
        }

        return taskList(queueName, false);
    }

    public static List<CrawlingTask> taskList(String queueName, boolean needTaskLen) {
        List taskList = client.queueAllX(queueName, getTaskListName(queueName));
        ArrayList tasksOrdered = new ArrayList();
        if(taskList != null) {
            Iterator var5 = taskList.iterator();

            while(var5.hasNext()) {
                byte[] bytes = (byte[])var5.next();
                CrawlingTask task = new CrawlingTask();

                try {
                    ProtoEntity.parseFrom(task, bytes);
                    if(needTaskLen) {
                        task.setTaskLen(taskLen(queueName, task.getTaskId()));
                    }

                    tasksOrdered.add(task);
                } catch (Exception var8) {
                    var8.printStackTrace();
                }
            }
        }

        return tasksOrdered;
    }

    public static Long queueLen(String queueName) {
        Long len = client.queueLen(queueName);
        List queue = client.queueAllX(queueName, getTaskListName(queueName));
        CrawlingTask task;
        if(queue != null) {
            for(Iterator var4 = queue.iterator(); var4.hasNext(); len = Long.valueOf(len.longValue() + client.queueLenX(queueName, getTaskQueueName(queueName, task.getTaskId())).longValue())) {
                byte[] bytes = (byte[])var4.next();
                task = new CrawlingTask();

                try {
                    ProtoEntity.parseFrom(task, bytes);
                } catch (Exception var7) {
                    var7.printStackTrace();
                }
            }
        }

        return len;
    }

    public static void killQueue(String queueName) {
        List queue = client.queueAllX(queueName, getTaskListName(queueName));
        CrawlingTask task;
        if(queue != null) {
            for(Iterator var3 = queue.iterator(); var3.hasNext(); client.clearQueueX(queueName, getTaskQueueName(queueName, task.getTaskId()))) {
                byte[] bytes = (byte[])var3.next();
                task = new CrawlingTask();

                try {
                    ProtoEntity.parseFrom(task, bytes);
                } catch (Exception var6) {
                    var6.printStackTrace();
                }
            }
        }

        client.clearQueueX(queueName, getTaskListName(queueName));
        client.clearQueue(queueName);
    }

    private static void doWatchQueue(String queueName) {
        Long len = client.queueLen(queueName);
        if(len.longValue() == 0L) {
            byte[] b = client.queueHeadX(queueName, getTaskListName(queueName));
            if(b != null) {
                CrawlingTask task = new CrawlingTask();

                try {
                    ProtoEntity.parseFrom(task, b);
                } catch (Exception var5) {
                    var5.printStackTrace();
                }

                Long lenX = client.queueLenX(queueName, getTaskQueueName(queueName, task.getTaskId()));
                if(lenX.longValue() == 0L) {
                    client.dequeueNoBlockX(queueName, getTaskListName(queueName));
                    if(handler != null) {
                        handler.onTaskCompleted(queueName, task);
                    }

                    client.enqueueX(queueName, getTaskListCompletedName(queueName), formatBytes(task.getTaskId()));
                } else {
                    client.renameX(getTaskQueueName(queueName, task.getTaskId()), queueName);
                }
            }
        }

    }

    private static Long taskLen(String queueName, String taskId) {
        Long len = client.queueLenX(queueName, getTaskQueueName(queueName, taskId));
        if(len.longValue() == 0L) {
            len = client.queueLen(queueName);
        }

        return len;
    }

    private static byte[] formatBytes(String taskId) {
        return String.format("%s(%s)", new Object[]{taskId, DateUtils.formatDate(new Date(), "yyyy-MM-dd HH:mm:ss")}).getBytes();
    }

    private static String getTaskListName(String queueName) {
        return queueName + "_task";
    }

    private static String getTaskListSubmitedName(String queueName) {
        return queueName + "_submited";
    }

    private static String getTaskListCompletedName(String queueName) {
        return queueName + "_completed";
    }

    private static String getTaskQueueName(String queueName, String taskId) {
        return queueName + "_" + taskId;
    }
}

通过分析这个类,我们就可以知道两个项目之间是如何进行协作的。
(1)初始化数据

    public static void intialize(final String hosts) {
        lock.doInit(new Runnable() {
            public void run() {
                TaskDirector.client = QueueManager.getClient(hosts, "");
                TaskDirector.executor = Executors.newScheduledThreadPool(20);
                TaskDirector.watchList = new ArrayList(30);
            }
        });
    }

该方法主要是对redis的客户端进行初始化,线程池的初始化,队列数量集合的初始化。
其中使用InitialLock类来做初始化,
1,利用开启一个线程来处理一些比较耗时操作,如何redis的初始化,线程池的初始化。【然而,我测试过,其实那里的代码不会用太久的时间】
2,使用双重校验锁机制来确定该代码只会被执行一次。

    public void doInit(Runnable initer) {
        if(!this.inited) {
            Object var2 = this.syncRoot;
            synchronized(this.syncRoot) {
                if(!this.inited) {
                    initer.run();
                    this.inited = true;
                }
            }
        }
    }

(2)注册redis队列

public static void registerWatchQueue(final String queueName) {
    try {
        if(watchList.contains(queueName)) {
            return;
        }

        watchList.add(queueName);
        executor.scheduleAtFixedRate(new Runnable() {
            public void run() {
                try {
                    TaskDirector.doWatchQueue(queueName);
                } catch (Exception var2) {
                    ;
                }

            }
        }, 10L, 60L, TimeUnit.SECONDS);
    } catch (Exception var2) {
        ;
    }

}

1,添加队列名称到list集合,如果添加的集合已经存在,则不保存。
2,开启线程池每隔一分钟调用一次doWatchQueue(queueName)方法。

(3)监控队列情况

 private static void doWatchQueue(String queueName) {
        Long len = client.queueLen(queueName);
        if(len.longValue() == 0L) {
            byte[] b = client.queueHeadX(queueName, getTaskListName(queueName));
            if(b != null) {
                CrawlingTask task = new CrawlingTask();

                try {
                    ProtoEntity.parseFrom(task, b);
                } catch (Exception var5) {
                    var5.printStackTrace();
                }

                Long lenX = client.queueLenX(queueName, getTaskQueueName(queueName, task.getTaskId()));
                if(lenX.longValue() == 0L) {
                    client.dequeueNoBlockX(queueName, getTaskListName(queueName));
                    if(handler != null) {
                        handler.onTaskCompleted(queueName, task);
                    }

                    client.enqueueX(queueName, getTaskListCompletedName(queueName), formatBytes(task.getTaskId()));
                } else {
                    client.renameX(getTaskQueueName(queueName, task.getTaskId()), queueName);
                }
            }
        }

    }

1,这个方法主要是判断队列的长度是否为0,如果是,则说明任务执行完毕,将队列销毁,在submit的队列中记录提交任务的记录,
并在调用对调的函数(这个handler是kw那边需要注册处理的)
2,由于不同的任务提交就会生成一个队列,队列的名称类似于queue_name_id,id是不同的。
而CE那边是只知道定义好的队列名称,后面的id它是不知道的,如queue_name,ce只知道这个名称。所以我们就要将每一个队列进行优先级的排序,第一个取出来的队列,
如果长度不为0,则将这个队列的名称改为queue_name,知道这个queue_name数据处理完毕,我们再处理剩下的队列。

client.renameX(getTaskQueueName(queueName, task.getTaskId()), queueName);

(4)提交任务

public static void submitTask(String queueName, CrawlingTask task) {
    boolean hasWatched = false;
    Iterator var4 = watchList.iterator();

    while(var4.hasNext()) {
        String p = (String)var4.next();
        if(p.equals(queueName)) {
            hasWatched = true;
        }
    }

    if(hasWatched) {
        if(task != null && StringUtils.isNotEmpty(task.getTaskId()) && task.getProtos() != null && task.getProtos().size() > 0) {
            try {
                var4 = task.getProtos().iterator();

                while(var4.hasNext()) {
                    ProtoEntity p1 = (ProtoEntity)var4.next();
                    byte[] data = p1.toByteArray();
                    client.enqueueX(queueName, getTaskQueueName(queueName, task.getTaskId()), data);
                }

                client.enqueueX(queueName, getTaskListName(queueName), task.toByteArray());
                client.enqueueX(queueName, getTaskListSubmitedName(queueName), formatBytes(task.getTaskId()));
            } catch (Exception var6) {
                ;
            }
        }

    }
}

1,提交的任务队列是否存在,且提交的数据的长度是否合法,如果存在且合法则进行下面的操作。
2,将提交的集合数据放到队列,并记录提交信息

(5)注册句柄

public static void registerHandler(ITaskCompleteHandler h) {
    handler = h;
}

1,kw注册这个句柄,只要方法执行完,就回调这个句柄的方法,返回任务id。

上述五个步骤简单的描述数据是如何通过CrawlDirector放到redis队列。
接下来需要有一个类来专门处理数据放到redis的操作还有数据回调的操作。

  1. TaskQuartz 定时器类,这个类会定时扫描任务 通过调用CrawDirector的submitTask方法将数据提交。
  2. TaskCallBack 回调类,这个类会去注册爬虫队列,并注册Handler,实现数据的回调。
  3. QueueClientHelper 队列的帮助类,定义一些队列名称。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值