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

来源:互联网 发布:数据分析 培训 编辑:程序博客网 时间:2024/06/05 01:03

一,简介
最近做一个项目,主要功能是根据一些关键词去百度爬取一些数据,如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 队列的帮助类,定义一些队列名称。
0 0
原创粉丝点击