Scala и Akka
Степан Каменцев
Распределенная обработка
миллионов документов на
План
1. Анализ текстовых коллекций
2. Коротко об Akka
3. Шаблон Distributed Workers
4. Примеры реализации
Что мы делаем?
В начале был
хаос!
структурируем
тегируем
собираем статистику
реализуем поиск
представляем
инструменты для
анализа
Как мы это делаем?
Мотивация перехода на
Akka
type Receive = PartialFunction[Any, Unit]
trait Actor {
def receive: Receive = ???
...
}
class TimeActor extends Actor {
def receive = {
case "Который час?" =>
sender ! System.currentTimeMillis()
}
}
object SomeApp extends App {
val system = ActorSystem("someSystem")
val path = "akka.tcp://sys@host:2552/user/time"
val timeActor = system.actorSelection(path)
timeActor ? "Который час?" onSuccess {
case time => println(time)
}
}
Ссылки
1. http://akka.io
2. http://typesafe.com/activator/templates#filter:
akka
Distributed workers
Распределенные
рабы
Требования к системе
1. Динамическое добавление рабочих узлов
2. Динамическое добавление клиентов
3. Отсутствие дополнительных настроек
4. Возможность использовать тысячи узлов
5. При падении сети, мастера, рабочего
узла данные не должны теряться
object WorkerToMaster {
case object RegisterWorker
}
object MasterToWorker {
case object WorkerRegistered
}
class Worker(master: ActorRef) extends Actor {
import context.dispatcher
context.system.scheduler.scheduleOnce(
0.seconds, self, RegisterWorker)
def receive = registration
def registration: Receive = {
case RegisterWorker =>
context.setReceiveTimeout(5.seconds)
context.become(waitForRegistration)
master ! RegisterWorker
}
}
class Worker(master: ActorRef) extends Actor {
import context.dispatcher
...
...
def waitForRegistration: Receive = {
case WorkerRegistered =>
context.setReceiveTimeout(Duration.Undefined)
context.become(idle)
case ReceiveTimeout =>
master ! RegisterWorker
}
}
class Master extends Actor {
private object clients {...}
private object workers {
val workerToStatus =
mutable.Map.empty[ActorRef, WorkerStatus]
}
def receive = {
case RegisterWorker =>
val worker = sender()
worker ! WorkerRegistered
if (clients.isTaskAvailable) {
worker ! TasksIsReady
}
workers.workerToStatus(worker) = Idle
}}
Ну, граждане алкоголики, хулиганы,
тунеядцы... Кто хочет сегодня поработать?
Песчаный карьер - два человека.
Я!
class Worker(master: ActorRef) extends Actor {
private def taskExecutorPropsFor(task: Task) = Props(
task match {
case _: MystemTask => classOf[MystemExecutor]
case _: NgramTask => classOf[NgramExecutor]
// ...
})
def idle: Receive = {
case task: Task =>
context.become(working)
val taskExecutor =
context.actorOf(taskExecutorPropsFor(task))
taskExecutor ! task
currentExecutor = Some(taskExecutor)
}}
Отказоустойчивость
class Worker(master: ActorRef) extends Actor {
private var currentExecutor: Option[ActorRef] = None
private def interrupt() = currentExecutor.foreach { ref =>
ref ! PoisonPill
currentExecutor = None
master ! TaskFailed
}
override def supervisorStrategy = OneForOneStrategy() {
//...
case _: Exception =>
interrupt()
context.become(idle)
Restart
}}
class Master extends Actor {
override def preStart() {
context.system.eventStream.subscribe(
self, classOf[OnDisassociated])
}
def receive = {
case OnDisassociated(localAddress, remoteAddress, true) =>
for (cl <- clients.all
if cl.path.address == remoteAddress)
clients.release(cl)
for (wr <- workers.all
if wr.path.address == remoteAddress)
workers.release(wr)
}
}
Ссылки
1. http://typesafe.com/activator/template/akka-
distributed-workers
2. http://letitcrash.
com/post/29044669086/balancing-workload-
across-nodes-with-akka-2
Примеры реализации
Распределенное
построение словаря
Функции этапа
1. Токенизация и лемматизация
2. Выделение устоявшихся словосочетаний
3. Статистика (сколько раз, в каких
документах и т.д.)
object DictionaryClient {
case class DictionaryTask(
chunkIdx: Int,
databaseConfig: DBConfig
) extends Task
case class DictionaryResult(
chunkIdx: Int,
lemmas: Map[String, Int],
dictionaryBits: java.util.BitSet,
lemmasCount: Map[Int, Int],
lemmasDocCount: Map[Int, Int]
)
}
Распределенное
построение пространства
Функции этапа
1. Построение матрицы лемм и документов
2. Сингулярное разложение (Singular Vector
Decomposition)
3. Проекция документов на пространство
object SVDClient {
case class SVDTask(chunkIdx: Int,
databaseConfig: DBConfig) extends Task
case class SVDResult(chunkIdx: Int,
// chunk U * S array
data: Array[Byte])
}
object ProjectClient {
case class ProjectTask(chunkIdx: Int,
databaseConfig: DBConfig) extends Task
case object ProjectResult
}
Ссылки
1. http://nlp.fi.muni.
cz/~xrehurek/ecir2011/rehurek_ecir2011.pdf
Кластеризация и
тегирование
1. Выделение групп (кластеров) документов
2. Тегирование документов и кластеров
3. Построение графов лемм
object CBCClient {
case class CBCTask(delta: Double,
mergeThresh: Double,
committeeThresh: Double,
residueThresh: Double,
databaseConfig: DBConfig) extends Task
case object CBCResult(centroids: Array[Array[Double]],
quality: Array[Double])
}
object KeywordsClient {
case class KeywordsTask(chunkIdx: Int,
databaseConfig: DBConfig) extends Task
case object KeywordsResult
}
Ссылки
1. http://www.patrickpantel.
com/download/papers/2003/cbc.pdf
Вопросы?
email: skamentsev@naumen.ru
skype: stepan.kamentsev

DUMP-2015: «Распределенная обработка миллионов документов на Scala и Akka» Степан Каменцев, Naumen