(架构设计)观察者模式+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的操作还有数据回调的操作。
- TaskQuartz 定时器类,这个类会定时扫描任务 通过调用CrawDirector的submitTask方法将数据提交。
- TaskCallBack 回调类,这个类会去注册爬虫队列,并注册Handler,实现数据的回调。
- QueueClientHelper 队列的帮助类,定义一些队列名称。
- (架构设计)观察者模式+redis队列实现不同项目之间数据的交互
- 利用Observer模式解决不同模块之间的交互
- 不同的sqlserver 实例之间的数据交互
- 设计模式——行为型设计模之借助观察者模式实现模块之间的解耦
- MFC中两个不同窗口之间的数据交互
- java不同对象之间的数据交互(通用)
- HTTP接口不同项目网页之间数据交互跨域以及打开的窗口无法跳出关掉的问题
- 架构、框架、设计模式之间的关系
- 架构、框架、设计模式之间的关系
- 架构、框架、设计模式之间的关系
- 实现设计模式:观察者模式
- 将Unity3D项目导出到Android工程中二次开发并实现之间的数据交互
- 不同系统不同语言之间的交互
- android不同线程之间数据交互
- 接口设计实现不同线程之间数据传递
- CI项目设计Redis队列
- C++设计模式实现--观察者
- C++设计模式实现--观察者
- Ubuntu 彻底删除 MYSQL 然后重装 MYSQL
- 最理想的互联网医疗
- java 堆(heap)、栈(stack)和方法区(method)
- jq操作select
- android支付宝的接入流程
- (架构设计)观察者模式+redis队列实现不同项目之间数据的交互
- Android 最全面面试整理
- 通讯录的内容提供者(查询手机通讯录的数据)
- Calendar 类的应用
- hls 网络上的m3u8视频源地址(可用的)
- [译]AngularJS1.3.0 开发者指南(四) -- 控制器
- noi-7546-统计数字字符个数
- Cordova-----3、Cordova使用插件
- Java的文件相关操作