-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
73 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
--- | ||
layout: post | ||
title: "dhtcrawler的进程模型经验" | ||
categories: [erlang, module] | ||
tags: [erlang, dht, p2p, magnet, dhtcrawler] | ||
comments: true | ||
keywords: [erlang, dht, p2p, magnet, dhtcrawler] | ||
description: | ||
--- | ||
|
||
距离写[dhtcrawler](http://codemacro.com/2013/07/02/dhtcrawler2/)已经有半年时间。半年前就想总结点心得经验,但最后写出来的并没有表达出我特别有感慨的地方。最近又被人问到这方面的经验问题,才静下心来思考整理了下。 | ||
|
||
我的经验是关于在写一个网络项目时所涉及到的架构(或者说是模型)。 | ||
|
||
在dhtcrawler中,一个主要的问题是:程序在网络中需要尽可能快尽可能多地收集请求,然后程序需要尽可能快地加工处理这些信息。本质上就这么简单,我觉得很多网络系统面临的都可能是类似的问题。 | ||
|
||
详细点说,dhtcrawler高峰期每天会收到2000万的DHT协议请求,收到这些请求后,dhtcrawler需要对这些请求做处理,包括:合并相同的请求;从外部网站请求下载种子文件;新增/更新种子信息到数据库;建立种子sphinx索引等。在实际运行期间,高峰期每天能新录入14万个种子。 | ||
|
||
那么如何架构这个系统来让处理速度尽可能地快呢?首先,毫无疑问这个系统是多线程/多进程,甚至是分布式的。写一个多线程程序学几个API谁都会,但是如何组织这些线程以让系统最优则是一个较困难的问题。根据dhtcrawler的经验,我简单总结了以下几种模型/架构: | ||
<!-- more --> | ||
## 简单模型 | ||
|
||
约定一个线程/进程为worker。那么简单模型就是每一个worker都包含了完整的处理逻辑,从收到请求,到把该请求处理完毕。 | ||
|
||
Req -> Worker -> Process -> O | ||
|
||
当然,我们可以给系统配置若干个Worker,以求最大化效率。例子中,Req的来源是非常快非常多的,而 Process过程相对而言则非常慢,涉及到各种IO操作(从外部网站下载种子,写入数据库等)。 | ||
|
||
这个模型的整体效率完全受限于Process的过程。如果Req的来源速度还不是稳定的,那么Process的速度将严重影响系统的吞吐性。 | ||
|
||
当然这个模型的优点就是特别简单,咋并发系统中简单有利于维护和调试。 | ||
|
||
## 粗粒度分离模型 | ||
|
||
分离模型指的是把Req的获取过程和处理过程分离开来。也就是合理地将系统中慢的部分和快的部分分离。然后两者之间通过一些数据共享方式来交互。 | ||
|
||
Req -> ReqWorker -> Pool | ||
Pool -> ReqProcessor -> O | ||
|
||
这个时候,ReqWorker可以以尽可能快的速度收集Req,不用受限于ReqProcessor的处理速度。 | ||
|
||
这个Pool的实现有很多方式。这种模型有点类似于线程/进程间的交互,典型的生产者消费者问题。在需要同步的实现中,Pool可能需要写的比较精巧。 | ||
|
||
Pool可以放置在内存中。也就是ReqWorker把收到的请求稍作加工就放到内存中。这里的稍作加工可以是一段时间里的重复数据合并。ReqProcessor则可以以一定策略从这个内存中取得Req。这个策略可以是以一定时间间隔,或者基于ReqWorker的通知。 | ||
|
||
在erlang中,可以以一个单独的进程来维护这个Pool。那么这里就是通过erlang的进程来实现数据的同步。本质上也是基于erlang进程的mailbox机制。这个维护Pool的进程逻辑足够简单,可以快速响应ReqWorker的Req压入,以及ReqProcessor的Req取出。 | ||
|
||
**在用erlang的过程中,很多时候就是在平衡这种(actor)[http://en.wikipedia.org/wiki/Actor_model]进程模式中各种进程间的协调程度。**平衡不好会导致两种情况:a)进程mailbox暴涨最后内存耗尽;b)消费者进程请求资源超时。 | ||
|
||
Pool被放置在内存中时,本身也可能有问题。例如数据量过大,无论是直接基于OS的程序还是基于erlang/jvm等虚拟机的程序,都可能在这个时候出现问题。并且,把数据放置在内存中也可能由于程序不稳定导致数据丢失。 | ||
|
||
dhtcrawler中把很多中间数据放置在数据库中。当然这里是个权衡问题。更复杂的系统里我相信就可以加入内存数据库之类的系统。 | ||
|
||
使用了分离模型之后,还是可以配置每种进程的数量,但是这里的问题在于很难平衡每种进程所配置的比例,以最大化使用CPU内存之类的资源。 | ||
|
||
## 细粒度分离模型 | ||
|
||
异步程序编写起来始终比同步程序更困难。在异步系统中需要加入各种例如事件、消息等机制。一个简单的逻辑可能会分散到程序的不同地方。对于资源的管理,错误的排除,性能的调优都带来了困难。 | ||
|
||
细粒度分离模型同粗粒度模型一样,只不过对进程种类的划分粒度更细。在erlang这种使用进程来组织程序合情合理的语言中,就可以做到每一种进程仅仅只做一种事情,就像函数设计原则一样,功能单一。 | ||
|
||
以dhtcrawler为例,整个系统可以划分为如下若干种进程: | ||
|
||
* 请求收集,用于收集请求,涉及到网络操作和数据库操作 | ||
* 请求分类,将请求按是否需要从外部网站下载种子分类,仅涉及到数据库操作 | ||
* 种子下载,从外部网站下载种子 | ||
* 种子索引,建立sphinx索引 | ||
|
||
部分简单的进程其代码实现量不到千行。在erlang的进程中也是简单的几个消息,维护起来非常容易。 | ||
|
||
从上面的这种模型中,进程之间全部通过数据库做交互,那就很自然地可以发展为分布式系统。数据库再通过集群之类的技术,可以较高地提升系统的吞吐量。 | ||
|
||
|