Василий Ременюк «Курс молодого подрывника»

50
a million bots can't be wrong @remeniuk, Viaden Media #ScalaSBP, 18-05-2012

Upload: e-legion

Post on 06-May-2015

7.107 views

Category:

Technology


0 download

DESCRIPTION

В своем докладе, на примере фреймвормка для нагрузочного тестирования многопользовательской онлайн-игры, Василий рассказал, как, следуя 4-ем простым советам, создать эффективную, асинхронную систему, используя модель актеров и Akka 2.0, уложиться в отведенные сроки, и, при этом, регулярно спать по-ночам больше 4 часов.

TRANSCRIPT

Page 1: Василий Ременюк «Курс молодого подрывника»

a million bots can't be wrong @remeniuk, Viaden Media #ScalaSBP, 18-05-2012

Page 2: Василий Ременюк «Курс молодого подрывника»

In Viaden our aim is to make the best poker ever

Page 3: Василий Ременюк «Курс молодого подрывника»

we know that performance tests should be

the first-class citizens

Page 4: Василий Ременюк «Курс молодого подрывника»

and kill 2 birds with one stone, using bots for testing

Page 5: Василий Ременюк «Курс молодого подрывника»

 #1 we can emulate 50k players using just one medium EC2 instance #2 bots are interactive, so client teams can use them in development, and QA for testing

Page 6: Василий Ременюк «Курс молодого подрывника»

everyone knows Akka, huh?

Page 7: Василий Ременюк «Курс молодого подрывника»

why Scala and Akka is a perfect choice for making bots?

actors are !(interactive)straightforward remotingsimple scalability/clustering~30 minutes to make a DSL and CLI with Scala and SBT

Page 8: Василий Ременюк «Курс молодого подрывника»

..., but, I have to warn you ...

Page 9: Василий Ременюк «Курс молодого подрывника»

4 dead simple tips for keeping your sanity

when you do asynch with Akka 2.0

Page 10: Василий Ременюк «Курс молодого подрывника»

tip #1: live fast, die young

Page 11: Василий Ременюк «Курс молодого подрывника»

typical approach in Akka 1.x

lobby

desk

botlogin lin

k

tourney

Page 12: Василий Ременюк «Курс молодого подрывника»

Akka 1.x: actors have a long lifecycle

lobby

desk

bot

botplay game

link

linkun

link

tourney

Page 13: Василий Ременюк «Курс молодого подрывника»

lobby

desk

tourney

bot

bot

botplay tournament linkun

link

Akka 1.x: actors have a long lifecycle

Page 14: Василий Ременюк «Курс молодого подрывника»

in Akka 2.0 the world has changed

actors

paths

props

newsupervision

Page 15: Василий Ременюк «Курс молодого подрывника»

now, you're forced to do "the right thing" (c)

lobby

tournament desk

desk

IdleBot

DeskBot

DeskBot

Page 16: Василий Ременюк «Курс молодого подрывника»

all actors are supervised

lobby

desk login

Page 17: Василий Ременюк «Курс молодого подрывника»

easy come

lobby

desk

IdleBotplay game

Page 18: Василий Ременюк «Курс молодого подрывника»

easy go (when supervisor to be changed)

lobby

desk

IdleBotDeskBot

dies

borns

Page 19: Василий Ременюк «Курс молодого подрывника»

class Lobby extends Actor {

case Login(username, password) => context.actorOf(Props(new IdlePokerBot(...)))

case JoinAnyDesk(props) => findDesk ! JoinDesk(props) } class Desk extends Actor {

case JoinDesk(botProps)=> context.actorOf(Props(new DeskPokerBot(botProps)))

} class IdlePokerBot(props: BotProps) extends Actor {

case PlayNow => context.parent ! JoinAnyDesk(props); context.stop(self)

}

Page 20: Василий Ременюк «Курс молодого подрывника»

Props Pattern - "the soul" of an actor

IdleBot

DeskBot

BotProps

BotProps

Props remains alive between actor "reincarnations"

Page 21: Василий Ременюк «Курс молодого подрывника»

case class BotProperties(id: Long, login: String, script: Seq[BotEvent], aggressiveness: Int, sessionId: Protocol.SessionId) class IdlePokerBot(val botProperties: BotProperties) extends Bot[Poker] class DeskPokerBot(val botProperties: BotProperties) extends Bot[Poker]

Page 22: Василий Ременюк «Курс молодого подрывника»

tip #2: think beyond

Page 23: Василий Ременюк «Курс молодого подрывника»

when you know, who's supervising, life's simple

akka://gpdev/user/lobby/player1234 akka://gpdev/user/lobby/desk1/player1234 akka://gpdev/user/lobby/tournament1/desk1/player1234

Page 24: Василий Ременюк «Курс молодого подрывника»

ActorRegistry, actor UUID

but what should I do, now, when I don't know, where to look for my bot?

were removed from Akka

Bad news

Page 25: Василий Ременюк «Курс молодого подрывника»

you can make your own registry (using Extensions, backed with a distributed data structure)...

Page 26: Василий Ременюк «Курс молодого подрывника»

or, use the full power of location transparency

lobby

desk

IdleBot

DeskBot

ProjectionManager

Projectionvar location:

ActorPath

/lobby/desk123/player123/projection/player123

Page 27: Василий Ременюк «Курс молодого подрывника»

class Projection(var container: ActorPath) extends Actor { def receive = { case newContainer: ActorPath => container = newContainer case msg => context.actorFor(container.child(self.path.name)) ! msg } } class ProjectionManager extends Actor { def receive = { case Add(actor) => context.actorOf(Props(new

Projection(actor.path.parent)), actor.path.name) case Forward(msg, path) => context.actorFor(path) ! msg } } projectionManager ! Add(actorRef)projectionManager ! Forward("ping", "actor1")

Page 28: Василий Ременюк «Курс молодого подрывника»
Page 29: Василий Ременюк «Курс молодого подрывника»

class Projection(var container: ActorPath) extends Actor { def receive = { case newContainer: ActorPath => container = newContainer case msg => context.actorFor(container.child(self.path.name)) ! msg } } class ProjectionManager extends Actor { def receive = { case Add(actor) => context.actorOf(Props(new

Projection(actor.path.parent)), actor.path.name) case Forward(msg, path) => context.actorFor(path) ! msg } } projectionManager ! Add(actorRef)system.actorFor(projectionManager.path.child("actor" + i)) ! "ping"

Page 30: Василий Ременюк «Курс молодого подрывника»
Page 31: Василий Ременюк «Курс молодого подрывника»

class ProjectionManager extends Actor { def receive = { case Add(actor) => context.actorOf(Props(new

Projection(actor.path.parent)), actor.path.name) case Forward(msg, path) => context.actorSelection("../*/" + path) ! msg } } val projectionManager = system.actorOf(Props[ProjectionManagerRoutee]

.withRouter(RoundRobinRouter(resizer = Some(DefaultResizer(lowerBound = 10, upperBound = 20)))), "projection")

projectionManager ! Add(actorRef)projectionManager ! Forward("ping", "actor1")

Page 32: Василий Ременюк «Курс молодого подрывника»
Page 33: Василий Ременюк «Курс молодого подрывника»

case class CustomRouter(n: Int, routerDispatcher: String = DefaultDispatcherId, supervisorStrategy: SupervisorStrategy = defaultStrategy) extends RouterConfig { def createRoute(props: Props, provider: RouteeProvider) = { provider.registerRoutees((1 to n).map(i => provider.context.actorOf(Props[ProjectionManager], i.toString))) def destination(sender: ActorRef, path: String) = List(Destination(sender, provider.routees(abs(path.hashCode) % n))) { case m@(sender, Add(actor)) ⇒ destination(sender, actor.path.name) case m@(sender, Forward(_, name)) ⇒ destination(sender, name) } } }

Page 34: Василий Ременюк «Курс молодого подрывника»
Page 35: Василий Ременюк «Курс молодого подрывника»

tip #3: don't do anything stupid

Page 36: Василий Ременюк «Курс молодого подрывника»

you've tried all the options, system load is fine, only 1/10 of the heap is used, but you still can start not more than 1k bots!?

Page 37: Василий Ременюк «Курс молодого подрывника»

ulimit -n <proper value>

Page 38: Василий Ременюк «Курс молодого подрывника»

your actor is lacking of throughput?       wait before adding poolsshare responsibility!one fine-grained actor is enough in 99% of the cases

Page 39: Василий Ременюк «Курс молодого подрывника»

100-300 threads are serving 300 bots!?

Page 40: Василий Ременюк «Курс молодого подрывника»

spawn futures, backed with standalone [bounded] pools, for blocking operations

class ThirdPartyWrapper extends Actor { case F(x) => sender ! thirdPartyService.f(x) // call to a function that takes a lot of time to // complete } class ThirdPartyWrapper extends Actor { case F(x) => val _sender = sender Future(thirdPartyService.f(x)).map(_sender ! _) // ugly, but safe, and perfectly right}

Page 41: Василий Ременюк «Курс молодого подрывника»

use separate dispatchers

lobby

desk

DeskBot

ProjectionManager

Projection 

projection-manager-dispatcherBalancingDispatcher

projection-dispatcherDispatcher

lobby-dispatcherPinnedDispatcher

container-dispatcherDispatcher

desk-bot-dispatcherDispatcher

Page 42: Василий Ременюк «Курс молодого подрывника»

GOTCHA: Akka successfully bootstraps, even if your dispatcher is not configured, or the config is wrong Always check the logs to make sure that dispatchers are used!

[WARN][gpdev-akka.actor.default-dispatcher-1] [Dispatchers] Dispatcher [bot-system.container-dispatcher] not configured, using default-dispatcher[WARN][gpdev-bot-system.container-dispatcher-1] [PinnedDispatcherConfigurator] PinnedDispatcher [bot-system.lobby-dispatcher] not configured to use ThreadPoolExecutor, falling back to default config. [DEBUG][gpdev-akka.actor.default-dispatcher-24] [akka://gpdev/user/poker/lobby] logged in[DEBUG][gpdev-akka.actor.default-dispatcher-14] [akka://gpdev/user/poker/projeciton/$g/player20013] starting projection...

Page 43: Василий Ременюк «Курс молодого подрывника»

tip #4: analyze that

Page 44: Василий Ременюк «Курс молодого подрывника»

how to measure? Metrics - pushes various collected metrics to GraphiteCarbon and Graphite - gather metrics, and expose them via web interface 

Page 45: Василий Ременюк «Курс молодого подрывника»

object BotMetrics { val loggedInCount = new Counter(Metrics.newCounter(classOf[Lobby[_]], "logged-in-count")) GraphiteReporter.enable(1, TimeUnit.MINUTES, "localhost", 2003) } class Lobby extends Actor { case Login(username, pass) => BotMetrics.loggedInCount += 1 }

1. add logged-in user counter 2. update it3. enable reporting to Graphite4. build a graph in Grtaphite

1.

2.

3.

4.

Page 46: Василий Ременюк «Курс молодого подрывника»

what to measure? - mailbox size1

- throughput- time, before the message is processed (both in actor and future)2

- time to process a message- count of threads- actor pool size- heap size

1 requires implementation of a custom mailbox that can expose mailbox size2 every message should be stuffed with a timestamp

Page 47: Василий Ременюк «Курс молодого подрывника»

how to tune dispatchers? VisualVM - thread timeline shows, if thread polls behind dispatchers are used effectively 

Page 48: Василий Ременюк «Курс молодого подрывника»

don't neglect old good logging  [ERROR][05/06/2012 12:55:43.826] [gpdev-bot-system.desk-bot-dispatcher-7] [akka://gpdev/user/poker/lobby/tournament5382577/desk109129/player20121] unprocessed game event: GameEvent(CHAT,None,None)

Page 49: Василий Ременюк «Курс молодого подрывника»

thanks for listening

Page 50: Василий Ременюк «Курс молодого подрывника»

viaden.com/careers/vacancies.htmlwe're hiring!