2. About me:
• Software Engineer at
• Working in the internet advertisement
industry
• I try to create distributed, highly performant
systems that will run on the JVM
• Programming in Scala since ~2011
5. What we already know?
• Working on huge monolithic projects is hard
• Complexity and interdependencies become
incomprehensible at some point
• We all fell in our guts that breaking it down
is a way to go
• Single responsibility principle but for
services
6. The plan
1. Create multiple git repositories
2. Create/copy/move code around repos
3. Pour it all with a fair share of
REST/HTTP
4. Extract common code/models into libs
5. Run it!
12. Paying for the lunch
• Easy to develop
• Hard to operate
• More fine-grained services boost both
traits
13. Things to address
• A lot of stuff from “Fallacies of distributed computing"
• Tracing request across different services
• Service discovery and tracing service topology
• Message serialization
• Distributing load across nodes
• Measuring performance and finding bottlenecks is not easy
• Interface contracts can introduce hidden coupling
• Duplication of effort
14.
15. • Historically we were a .Net shop
• Think spring-like approach
• Moved to API first approach (although we were
running some kind of μ-service approach to some
extend)
• Splitting one of the business features into small-
chunks using whatever tech stack we want so we
went akka/akka-http
16. • We didn’t want to use akka clustering
• Recreating utils e.g. request retry
• Establishing new project = work upfront before creating
any business value e.g. metrics
• Every project had it’s own structure - mental context
switching
• REST is good but other protocols might be better
• Fighting the “spray DSL” and marshallers 😀
17. • Creating json is annoying and automatic derivation from
classes was useless most of the time
• We had to think how to properly version our apis and hold
backward compatibility
• We had situations where multiple services needed to be
deployed for the solution to work (hidden monolith)
• How do I run this service locally?
18.
19. What is it?
• Protocol agnostic RPC system for JVM
• Based on Netty
• Allows for building servers and clients
• Core platform on which Twitter is build
• "Your Server as a Function" by Marius
Eriksen
21. Future
• NOT the Scala one unfortunately but very close
• Some api differences
- import com.twitter.util.Future
- Future.value == Future.successful
- Future.exception == Future.failed
- Future.collect == Future.sequence
• Usual monadic composition with flatMap and map
• There is also twitter’s Try, Duration and a bunch of others…
22. Service
• Basic building block in Finagle
• Just a function that returns a Future
• Servers implement services to which Finagle dispatches incoming requests
• Finagle furnishes clients with instances of Service representing either virtual or
concrete remote servers
abstract class Service[-Req, +Rep] extends (Req =>
Future[Rep])
23. Example local service (1)
import com.twitter.finagle.Service
import com.twitter.util.Future
val lengthService = Service.mk[String, Int] { req =>
Future.value(req.length)
}
lengthService("Hello TSUG").foreach(println)
24. import com.twitter.finagle.http
import com.twitter.finagle.Service
import com.twitter.util.Future
import com.twitter.finagle.Http
val service = new Service[http.Request, http.Response] {
def apply(req: http.Request): Future[http.Response] =
Future.value(
http.Response(req.version, http.Status.Ok)
)
}
val server = Http.serve(":8080", service)
Example http server (2)
28. Filter
• Again a function that returns a Future
• Filters are always attached to Services altering their
behavior
• Used for application agnostic concerns such as: timeouts,
retry policies, service statistics, and authentication
abstract class Filter[-ReqIn, +RepOut, +ReqOut, -RepIn]
extends ((ReqIn, Service[ReqOut, RepIn]) => Future[RepOut])
29. Filter
• Filters just like any other functions
compose
• There is a andThen method that chains
different filters with each other and in the
end with service
30. Local service filter
import com.twitter.finagle.{Filter, Service}
import com.twitter.util.Future
val someStringMetrics = Service.mk[String,Int] { req =>
Future.value(req.length)
}
val evenMetricsFilter = new Filter[Float, Boolean, String, Int] {
override def apply(input: Float,
stringMetricsService: Service[String, Int]): Future[Boolean] = {
stringMetricsService(input.toString).map(_ % 2 == 0)
}
}
(evenMetricsFilter andThen someStringMetrics)(1.234f)
31. import com.twitter.finagle.{Http,Service, Filter}
import com.twitter.finagle.http.{Request,Response}
import com.twitter.finagle.service.TimeoutFilter
import com.twitter.util.MockTimer
import com.twitter.conversions.time._
val httpClient: Service[Request, Response] =
Http.client.newService("twitter.com")
val timeoutFilter: Filter[Request, Response, Request, Response] =
new TimeoutFilter[Request, Response](30.seconds, new MockTimer)
val httpClientWithTimeout: Service[Request, Response] =
timeoutFilter andThen httpClient
Filter example http client
WARNING
This is only a showcase, timeouts for http clients are best configured by Http.client object
How to implement
this?
33. Cool client filters
(or wannabe-filters)
• MonitorFilter - handling exceptions
• StatsFilter - exposing metrics
• RetryFilter - retying calls with configured budget and a back-off policy
• TimeoutFilter - mentioned earlier, for different aspects like idle time, request
time, connection time ect.
• LoadBalancing (not really a filter) - distributed load across nodes, just pass
multiple host values
• FailFast (not really a filter) - mark node as dead for a certain period of time
34. • Unfortunately not all functionality can be
expressed with Filters
• Some things are a bit hairy - e.g. request
cancelation
• Beneath implementations is non-trivial but
comprehensible
• But the good part is that it all works out to the
box for free with almost none configuration!
35. There is more in Finagle to
explore:
• Thrift protocol
• Mux - multiplexing RPC
• ZooKeeper support
• Network location naming
37. Twitter Server
• A “template” for finagle-based server
• Flags
• Logging
• Metrics
• Admin interface
38. Flags (1)
• Cmd line arguments passed to the
application
• A remedy to “How do I start this thing
and what are the options?”
• Smart with type inference and parsing
capabilities
39. Flags (2)
val addr = flag("bind", new InetSocketAddress(0), "Bind address")
val durations = flag("alarms", (1.second, 5.second), "2 alarm durations")
$ java -jar target/myserver-1.0.0-SNAPSHOT.jar -help
AdvancedServer
-alarm_durations='1.seconds,5.seconds': 2 alarm durations
-help='false': Show this help
-admin.port=':9990': Admin http server port
-bind=':0': Network interface to use
-log.level='INFO': Log level
-log.output='/dev/stderr': Output file
-what='hello': String to return
Option for fail fast when missing a flag
40. Logging
• Twitter server provides a logging trait
• It can be configured with standard flags
• Can be tuned on-the-fly
47. What is it?
• Finatra is build atop Finagle and Twitter
Server
• Finatra does all the heavy-lifting that one
needs to do when working only with
Finagle
48. Finatra features
• Dependency injection using Guice
• JSON handling with Jackson
• Mustache support
• Routing
• Integrates it all together nicely
50. Unified error reporting
➜ finagle-spike git:(master) ✗ curl -v 127.0.0.1:2001/recommend/32/wsf
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 2001 (#0)
> GET /recommend/32/wsf HTTP/1.1
> Host: 127.0.0.1:2001
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Server: Finatra
< Date: Wed, 30 Mar 2016 07:05:53 +00:00
< Content-Length: 79
<
* Connection #0 to host 127.0.0.1 left intact
{"errors":["cookie_id: 'wsf' is not a valid int","x-auth: header is required"]}%
51. JSON Support
• Build on jackson-module-scala
• Support for case classes (de)serlialization
• Integrated with Joda
• Error accumulation (instead of fail-fast)
• Integration with routing
• Most of the cases just return object
52. Customizing Json
class Server extends HttpServer {
override def jacksonModule = CustomJacksonModule
...
}
object CustomJacksonModule extends FinatraJacksonModule {
override val additionalJacksonModules = Seq(
new SimpleModule {
addSerializer(LocalDateParser)
})
override val serializationInclusion = Include.NON_EMPTY
override val propertyNamingStrategy = CamelCasePropertyNamingStrategy
override def additionalMapperConfiguration(mapper: ObjectMapper) {
mapper.configure(Feature.WRITE_NUMBERS_AS_STRINGS, true)
}
}
53. Validation
import com.twitter.finatra.validation._
import org.joda.time.DateTime
case class GroupRequest(@NotEmpty name: String,
description: Option[String],
tweetIds: Set[Long],
dates: Dates) {
@MethodValidation
def validateName = {
ValidationResult.validate(
name.startsWith("grp-"),
"name must start with 'grp-'")
}
}
case class Dates(@PastTime start: DateTime,
@PastTime end: DateTime)
Also checked at routing time
54. Testing (1)
• Based on ScalaTest
• Feature testing (both black box and white box)
- looking at external interface of our service
• Integration tests - only a subset of modules is
instantiated and tested
• Finatra does not provide anything for unit
testing
55. Testing (2)
• Smart JSON diff
• Integration with DI
• Easy mocking
• Embedded http server with our service
Name - host -grateful
Finagle foundation of Twitter’s services
Finagle - ease pain of distributed computing
micorservices commond ground
challanges/benefits
People that raised their hand twice, are interesting
Using old tools -> bad
Guily of charge of doing this
The same happens when you create services from scratch
when I do a http post to some endpoint…bla bla bla.
Run first services locally, the second one ect.
Both are hard, finding and repairing that may involve many places where you need to make changes
first time see - laugh hard
single incomprehensible into multiple incomprehensible
Funny thing is that simple languages thrive in micro services as you don’t need that much of a advanced constructs as single parts are small and should be exchangeable within two weeks
Some of those could be addressed by framework your using
The fallacies are
The network is reliable.
Latency is zero.
Bandwidth is infinite.
The network is secure.
Topology doesn't change.
There is one administrator.
Transport cost is zero.
The network is homogeneous.
Big company - kilo-services
Clustering - means closing inside technology.
Don’t bend your model to the needs of your framework
Adding field, removing field, making it optional
Protocol angostic means it can be http but also memcache and mysql
Finagle is low level
Function that returns a A => Future
Metoda mk vs apply + new
We use the same base abstract building block for modeling both ends of the distributed
So we can create utils that will work on both
External traffic reverse proxy
Future is immutable and one way flow.
Network location naming - different protocols like DNS or ZK, with Dtab (delegation table) rewriting of the urls. We can delegate the request graph to rewrite some url to point to dev server
Mux is a session-layer protocol, maximize bandwidth, avoid head-of-line blocking (response can get back out-of-order)