使用akka构建高并发程序_如何使用Akka Cluster创建简单的应用程序
使用akka構(gòu)建高并發(fā)程序
If you read my previous story about Scalachain, you probably noticed that it is far from being a distributed system. It lacks all the features to properly work with other nodes. Add to it that a blockchain composed by a single node is useless. For this reason I decided it is time to work on the issue.
如果您閱讀了我以前關(guān)于Scalachain的故事,您可能會(huì)注意到它遠(yuǎn)非分布式系統(tǒng)。 它缺少與其他節(jié)點(diǎn)正確配合使用的所有功能。 此外,由單個(gè)節(jié)點(diǎn)組成的區(qū)塊鏈?zhǔn)菬o(wú)用的。 基于這個(gè)原因,我認(rèn)為是時(shí)候解決這個(gè)問(wèn)題了。
Since Scalachain is powered by Akka, why not take the chance to play with Akka Cluster? I created a simple project to tinker a bit with Akka Cluster, and in this story I’m going to share my learnings. We are going to create a cluster of three nodes, using Cluster Aware Routers to balance the load among them. Everything will run in a Docker container, and we will use docker-compose for an easy deployment.
由于Scalachain由Akka提供支持,為什么不趁此機(jī)會(huì)與Akka Cluster一起玩呢? 我創(chuàng)建了一個(gè)簡(jiǎn)單的項(xiàng)目來(lái)完善Akka Cluster ,在這個(gè)故事中,我將分享我的經(jīng)驗(yàn)。 我們將創(chuàng)建一個(gè)由三個(gè)節(jié)點(diǎn)組成的群集,使用群集感知路由器來(lái)平衡它們之間的負(fù)載。 一切都將在Docker容器中運(yùn)行,并且我們將使用docker-compose進(jìn)行輕松部署。
Ok, Let’s roll! ?
好吧,滾吧! ?
Akka Cluster快速入門(mén) (Quick introduction to Akka Cluster)
Akka Cluster provides great support to the creation of distributed applications. The best use case is when you have a node that you want to replicate N times in a distributed environment. This means that all the N nodes are peers running the same code. Akka Cluster gives you out-of-the-box the discovery of members in the same cluster. Using Cluster Aware Routers it is possible to balance the messages between actors in different nodes. It is also possible to choose the balancing policy, making load-balancing a piece of cake!
Akka Cluster為分布式應(yīng)用程序的創(chuàng)建提供了強(qiáng)大的支持。 最佳用例是在分布式環(huán)境中擁有要復(fù)制N次的節(jié)點(diǎn)時(shí)。 這意味著所有N個(gè)節(jié)點(diǎn)都是運(yùn)行相同代碼的對(duì)等點(diǎn)。 Akka群集使您可以立即發(fā)現(xiàn)同一群集中的成員。 使用群集感知路由器,可以在不同節(jié)點(diǎn)中的參與者之間平衡消息。 還可以選擇平衡策略,使負(fù)載平衡成為小菜一碟!
Actually you can chose between two types of routers:
實(shí)際上,您可以在兩種類(lèi)型的路由器之間進(jìn)行選擇:
Group Router — The actors to send the messages to — called routees — are specified using their actor path. The routers share the routees created in the cluster. We will use a Group Router in this example.
組路由器 -將消息發(fā)送到的參與者-稱(chēng)為路由-使用其參與者路徑指定。 路由器共享在群集中創(chuàng)建的路由。 在此示例中,我們將使用組路由器。
Pool Router — The routees are created and deployed by the router, so they are its children in the actor hierarchy. Routees are not shared between routers. This is ideal for a primary-replica scenario, where each router is the primary and its routees the replicas.
池路由器 -路由是由路由器創(chuàng)建和部署的,因此它們是角色層次結(jié)構(gòu)中的子級(jí)。 路由器之間不共享路由。 這對(duì)于主副本方案是理想的,因?yàn)槊總€(gè)路由器都是主副本,并且路由副本。
This is just the tip of the iceberg, so I invite you to read the official documentation for more insights.
這只是冰山一角,因此,我邀請(qǐng)您閱讀官方文檔以獲取更多見(jiàn)解。
數(shù)學(xué)計(jì)算的集群 (A Cluster for mathematical computations)
Let’s picture a use-case scenario. Suppose to design a system to execute mathematical computations on request. The system is deployed online, so it needs a REST API to receive the computation requests. An internal processor handles these requests, executing the computation and returning the result.
讓我們描述一個(gè)用例場(chǎng)景。 假設(shè)設(shè)計(jì)一個(gè)按要求執(zhí)行數(shù)學(xué)計(jì)算的系統(tǒng)。 該系統(tǒng)在線部署,因此它需要一個(gè)REST API來(lái)接收計(jì)算請(qǐng)求。 內(nèi)部處理器處理這些請(qǐng)求,執(zhí)行計(jì)算并返回結(jié)果。
Right now the processor can only compute the Fibonacci number. We decide to use a cluster of nodes to distribute the load among the nodes and improve performance. Akka Cluster will handle cluster dynamics and load-balancing between nodes. Ok, sounds good!
現(xiàn)在,處理器只能計(jì)算斐波那契數(shù) 。 我們決定使用節(jié)點(diǎn)集群來(lái)在節(jié)點(diǎn)之間分配負(fù)載并提高性能。 Akka Cluster將處理節(jié)點(diǎn)之間的群集動(dòng)態(tài)和負(fù)載平衡。 好的聽(tīng)起來(lái)不錯(cuò)!
演員等級(jí) (Actor hierarchy)
First things first: we need to define our actor hierarchy. The system can be divided in three functional parts: the business logic, the cluster management, and the node itself. There is also the server but it is not an actor, and we will work on that later.
首先,我們需要定義參與者的層次結(jié)構(gòu)。 該系統(tǒng)可以分為三個(gè)功能部分: 業(yè)務(wù)邏輯 , 集群管理和節(jié)點(diǎn)本身。 還有服務(wù)器,但它不是演員,我們將在以后進(jìn)行處理。
Business logic
商業(yè)邏輯
The application should do mathematical computations. We can define a simple Processor actor to manage all the computational tasks. Every computation that we support can be implemented in a specific actor, that will be a child of the Processor one. In this way the application is modular and easier to extend and maintain. Right now the only child of Processor will be the ProcessorFibonacci actor. I suppose you can guess what its task is. This should be enough to start.
該應(yīng)用程序應(yīng)該進(jìn)行數(shù)學(xué)計(jì)算。 我們可以定義一個(gè)簡(jiǎn)單的Processor actor來(lái)管理所有計(jì)算任務(wù)。 我們支持的每個(gè)計(jì)算都可以在特定的actor中實(shí)現(xiàn),它將成為Processor的子代。 這樣,應(yīng)用程序是模塊化的,并且易于擴(kuò)展和維護(hù)。 現(xiàn)在, Processor的唯一孩子將是ProcessorFibonacci actor。 我想您可以猜到它的任務(wù)是什么。 這應(yīng)該足以開(kāi)始。
Cluster management
集群管理
To manage the cluster we need a ClusterManager. Sounds simple, right? This actor handles everything related to the cluster, like returning its members when asked. It would be useful to log what happens inside the cluster, so we define a ClusterListener actor. This is a child of the ClusterManager, and subscribes to cluster events logging them.
要管理集群,我們需要一個(gè)ClusterManager 。 聽(tīng)起來(lái)很簡(jiǎn)單,對(duì)吧? 這個(gè)參與者處理與集群有關(guān)的所有事情,例如在被詢(xún)問(wèn)時(shí)返回其成員。 記錄集群內(nèi)部發(fā)生的情況將很有用,因此我們定義了一個(gè)ClusterListener actor。 這是ClusterManager的子級(jí),并訂閱記錄它們的集群事件。
Node
節(jié)點(diǎn)
The Node actor is the root of our hierarchy. It is the entry point of our system that communicates with the API. The Processor and the ClusterManager are its children, along with the ProcessorRouter actor. This is the load balancer of the system, distributing the load among Processors. We will configure it as a Cluster Aware Router, so every ProcessorRouter can send messages to Processors on every node.
Node actor是我們層次結(jié)構(gòu)的根源。 這是我們與API通信的系統(tǒng)的入口點(diǎn)。 Processor和ClusterManager是其子級(jí),以及ProcessorRouter actor。 這是系統(tǒng)的負(fù)載平衡器,可在Processor之間分配負(fù)載。 我們將其配置為群集感知路由器,因此每個(gè)ProcessorRouter都可以將消息發(fā)送到每個(gè)節(jié)點(diǎn)上的Processor 。
演員執(zhí)行 (Actor Implementation)
Time to implement our actors! Fist we implement the actors related to the business logic of the system. We move then on the actors for the cluster management and the root actor (Node) in the end.
是時(shí)候?qū)嵤┪覀兊难輪T了! 首先,我們實(shí)現(xiàn)與系統(tǒng)業(yè)務(wù)邏輯相關(guān)的參與者。 然后,我們移至用于集群管理的參與者,最后移至根參與者( Node )。
ProcessorFibonacci
處理器斐波那契
This actor executes the computation of the Fibonacci number. It receives a Compute message containing the number to compute and the reference of the actor to reply to. The reference is important, since there can be different requesting actors. Remember that we are working in a distributed environment!
該參與者執(zhí)行斐波那契數(shù)的計(jì)算。 它收到一條Compute消息,其中包含要計(jì)算的數(shù)字和要答復(fù)的actor的引用。 參考很重要,因?yàn)榭梢杂胁煌恼?qǐng)求方。 請(qǐng)記住,我們正在分布式環(huán)境中工作!
Once the Compute message is received, the fibonacci function computes the result. We wrap it in a ProcessorResponse object to provide information on the node that executed the computation. This will be useful later to see the round-robin policy in action.
收到Compute消息后, fibonacci函數(shù)將計(jì)算結(jié)果。 我們將其包裝在ProcessorResponse對(duì)象中,以提供有關(guān)執(zhí)行計(jì)算的節(jié)點(diǎn)的信息。 這對(duì)于以后查看循環(huán)策略的作用將很有用。
The result is then sent to the actor we should reply to. Easy-peasy.
然后將結(jié)果發(fā)送給我們應(yīng)該答復(fù)的演員。 十分簡(jiǎn)單。
object ProcessorFibonacci {sealed trait ProcessorFibonacciMessagecase class Compute(n: Int, replyTo: ActorRef) extends ProcessorFibonacciMessagedef props(nodeId: String) = Props(new ProcessorFibonacci(nodeId))def fibonacci(x: Int): BigInt = {@tailrec def fibHelper(x: Int, prev: BigInt = 0, next: BigInt = 1): BigInt = x match {case 0 => prevcase 1 => nextcase _ => fibHelper(x - 1, next, next + prev)}fibHelper(x)} }class ProcessorFibonacci(nodeId: String) extends Actor {import ProcessorFibonacci._override def receive: Receive = {case Compute(value, replyTo) => {replyTo ! ProcessorResponse(nodeId, fibonacci(value))}} }Processor
處理器
The Processor actor manages the specific sub-processors, like the Fibonacci one. It should instantiate the sub-processors and forward the requests to them. Right now we only have one sub-processor, so the Processor receives one kind of message: ComputeFibonacci. This message contains the Fibonacci number to compute. Once received, the number to compute is sent to a FibonacciProcessor, along with the reference of the sender().
Processor角色負(fù)責(zé)管理特定的子處理器,例如斐波那契。 它應(yīng)該實(shí)例化子處理器并將請(qǐng)求轉(zhuǎn)發(fā)給它們。 現(xiàn)在,我們只有一個(gè)子處理器,因此Processor會(huì)收到一種消息: ComputeFibonacci 。 該消息包含要計(jì)算的斐波那契數(shù)。 接收到后,要計(jì)算的數(shù)字連同sender()的引用一起發(fā)送到FibonacciProcessor 。
object Processor {sealed trait ProcessorMessagecase class ComputeFibonacci(n: Int) extends ProcessorMessagedef props(nodeId: String) = Props(new Processor(nodeId)) }class Processor(nodeId: String) extends Actor {import Processor._val fibonacciProcessor: ActorRef = context.actorOf(ProcessorFibonacci.props(nodeId), "fibonacci")override def receive: Receive = {case ComputeFibonacci(value) => {val replyTo = sender()fibonacciProcessor ! Compute(value, replyTo)}} }ClusterListener
集群監(jiān)聽(tīng)器
We would like to log useful information about what happens in the cluster. This could help us to debug the system if we need to. This is the purpose of the ClusterListener actor. Before starting, it subscribes itself to the event messages of the cluster. The actor reacts to messages like MemberUp, UnreachableMember, or MemberRemoved, logging the corresponding event. When ClusterListener is stopped, it unsubscribes itself from the cluster events.
我們想記錄有關(guān)集群中發(fā)生的情況的有用信息。 如果需要,這可以幫助我們調(diào)試系統(tǒng)。 這是ClusterListener actor的目的。 在啟動(dòng)之前,它會(huì)訂閱群集的事件消息。 MemberUp對(duì)諸如MemberUp , UnreachableMember或MemberRemoved類(lèi)的消息做出React,記錄相應(yīng)的事件。 停止ClusterListener ,它將取消訂閱集群事件。
object ClusterListener {def props(nodeId: String, cluster: Cluster) = Props(new ClusterListener(nodeId, cluster)) }class ClusterListener(nodeId: String, cluster: Cluster) extends Actor with ActorLogging {override def preStart(): Unit = {cluster.subscribe(self, initialStateMode = InitialStateAsEvents,classOf[MemberEvent], classOf[UnreachableMember])}override def postStop(): Unit = cluster.unsubscribe(self)def receive = {case MemberUp(member) =>log.info("Node {} - Member is Up: {}", nodeId, member.address)case UnreachableMember(member) =>log.info(s"Node {} - Member detected as unreachable: {}", nodeId, member)case MemberRemoved(member, previousStatus) =>log.info(s"Node {} - Member is Removed: {} after {}",nodeId, member.address, previousStatus)case _: MemberEvent => // ignore} }ClusterManager
集群管理器
The actor responsible of the management of the cluster is ClusterManager. It creates the ClusterListener actor, and provides the list of cluster members upon request. It could be extended to add more functionalities, but right now this is enough.
負(fù)責(zé)集群管理的ClusterManager是ClusterManager 。 它創(chuàng)建ClusterListener actor,并根據(jù)請(qǐng)求提供集群成員的列表。 可以擴(kuò)展它以添加更多功能,但是現(xiàn)在就足夠了。
object ClusterManager {sealed trait ClusterMessagecase object GetMembers extends ClusterMessagedef props(nodeId: String) = Props(new ClusterManager(nodeId)) }class ClusterManager(nodeId: String) extends Actor with ActorLogging {val cluster: Cluster = Cluster(context.system)val listener: ActorRef = context.actorOf(ClusterListener.props(nodeId, cluster), "clusterListener")override def receive: Receive = {case GetMembers => {sender() ! cluster.state.members.filter(_.status == MemberStatus.up).map(_.address.toString).toList}} }ProcessorRouter
處理器路由器
The load-balancing among processors is handled by the ProcessorRouter. It is created by the Node actor, but this time all the required information are provided in the configuration of the system.
處理器之間的負(fù)載平衡由ProcessorRouter 。 它是由Node actor創(chuàng)建的,但是這次所有必需的信息都在系統(tǒng)的配置中提供。
class Node(nodeId: String) extends Actor {//...val processorRouter: ActorRef = context.actorOf(FromConfig.props(Props.empty), "processorRouter")//... }Let’s analyse the relevant part in the application.conf file.
讓我們分析一下application.conf文件中的相關(guān)部分。
akka {actor {...deployment {/node/processorRouter {router = round-robin-grouproutees.paths = ["/user/node/processor"]cluster {enabled = onallow-local-routees = on}}}}... }The first thing is to specify the path to the router actor, that is /node/processorRouter. Inside that property we can configure the behaviour of the router:
首先要指定路由器角色的路徑,即/node/processorRouter 。 在該屬性?xún)?nèi),我們可以配置路由器的行為:
router: this is the policy for the load balancing of messages. I chose the round-robin-group, but there are many others.
router :這是消息負(fù)載平衡的策略。 我選擇了round-robin-group ,但還有許多其他round-robin-group 。
routees.paths: these are the paths to the actors that will receive the messages handled by the router. We are saying: “When you receive a message, look for the actors corresponding to these paths. Choose one according to the policy and forward the message to it.” Since we are using Cluster Aware Routers, the routees can be on any node of the cluster.
routees.paths :這是將接收路由器處理的消息的參與者的路徑。 我們說(shuō)的是: “收到消息后,尋找與這些路徑相對(duì)應(yīng)的參與者。 根據(jù)政策選擇一個(gè),然后將消息轉(zhuǎn)發(fā)給它。” 由于我們使用的是群集感知路由器,因此路由可以位于群集的任何節(jié)點(diǎn)上。
cluster.enabled: are we operating in a cluster? The answer is on, of course!
cluster.enabled :我們是否在集群中運(yùn)行? 答案是on的,當(dāng)然!
cluster.allow-local-routees: here we are allowing the router to choose a routee in its node.
cluster.allow-local-routees :在這里,我們?cè)试S路由器在其節(jié)點(diǎn)中選擇一個(gè)路由。
Using this configuration we can create a router to load balance the work among our processors.
使用此配置,我們可以創(chuàng)建一個(gè)路由器來(lái)負(fù)載均衡處理器之間的工作。
Node
節(jié)點(diǎn)
The root of our actor hierarchy is the Node. It creates the children actors — ClusterManager, Processor, and ProcessorRouter — and forwards the messages to the right one. Nothing complex here.
Actor層次結(jié)構(gòu)的根是Node 。 它創(chuàng)建子actor(即ClusterManager , Processor和ProcessorRouter ),并將消息轉(zhuǎn)發(fā)到正確的子actor。 這里沒(méi)什么復(fù)雜的。
object Node {sealed trait NodeMessagecase class GetFibonacci(n: Int)case object GetClusterMembersdef props(nodeId: String) = Props(new Node(nodeId)) }class Node(nodeId: String) extends Actor {val processor: ActorRef = context.actorOf(Processor.props(nodeId), "processor")val processorRouter: ActorRef = context.actorOf(FromConfig.props(Props.empty), "processorRouter")val clusterManager: ActorRef = context.actorOf(ClusterManager.props(nodeId), "clusterManager")override def receive: Receive = {case GetClusterMembers => clusterManager forward GetMemberscase GetFibonacci(value) => processorRouter forward ComputeFibonacci(value)} }服務(wù)器和API (Server and API)
Every node of our cluster runs a server able to receive requests. The Server creates our actor system and is configured through the application.conf file.
我們集群的每個(gè)節(jié)點(diǎn)都運(yùn)行一臺(tái)能夠接收請(qǐng)求的服務(wù)器。 Server創(chuàng)建我們的參與者系統(tǒng),并通過(guò)application.conf文件進(jìn)行配置。
object Server extends App with NodeRoutes {implicit val system: ActorSystem = ActorSystem("cluster-playground")implicit val materializer: ActorMaterializer = ActorMaterializer()val config: Config = ConfigFactory.load()val address = config.getString("http.ip")val port = config.getInt("http.port")val nodeId = config.getString("clustering.ip")val node: ActorRef = system.actorOf(Node.props(nodeId), "node")lazy val routes: Route = healthRoute ~ statusRoutes ~ processRoutesHttp().bindAndHandle(routes, address, port)println(s"Node $nodeId is listening at http://$address:$port")Await.result(system.whenTerminated, Duration.Inf)}Akka HTTP powers the server itself and the REST API, exposing three simple endpoints. These endpoints are defined in the NodeRoutes trait.
Akka HTTP為服務(wù)器本身和REST API供電,公開(kāi)了三個(gè)簡(jiǎn)單的端點(diǎn)。 這些端點(diǎn)是在NodeRoutes特性中定義的。
The first one is /health, to check the health of a node. It responds with a 200 OK if the node is up and running
第一個(gè)是/health ,用于檢查節(jié)點(diǎn)的運(yùn)行狀況。 如果節(jié)點(diǎn)已啟動(dòng)并正在運(yùn)行,它將以200 OK響應(yīng)
lazy val healthRoute: Route = pathPrefix("health") {concat(pathEnd {concat(get {complete(StatusCodes.OK)})})}The /status/members endpoint responds with the current active members of the cluster.
/status/members端點(diǎn)以集群的當(dāng)前活動(dòng)成員作為響應(yīng)。
lazy val statusRoutes: Route = pathPrefix("status") {concat(pathPrefix("members") {concat(pathEnd {concat(get {val membersFuture: Future[List[String]] = (node ? GetClusterMembers).mapTo[List[String]]onSuccess(membersFuture) { members =>complete(StatusCodes.OK, members)}})})})}The last (but not the least) is the /process/fibonacci/n endpoint, used to request the Fibonacci number of n.
最后(但并非最不重要)是/process/fibonacci/n端點(diǎn),用于請(qǐng)求n的斐波那契數(shù)。
lazy val processRoutes: Route = pathPrefix("process") {concat(pathPrefix("fibonacci") {concat(path(IntNumber) { n =>pathEnd {concat(get {val processFuture: Future[ProcessorResponse] = (node ? GetFibonacci(n)).mapTo[ProcessorResponse]onSuccess(processFuture) { response =>complete(StatusCodes.OK, response)}})}})})}It responds with a ProcessorResponse containing the result, along with the id of the node where the computation took place.
它以包含結(jié)果的ProcessorResponse以及進(jìn)行計(jì)算的節(jié)點(diǎn)的ID進(jìn)行響應(yīng)。
集群配置 (Cluster Configuration)
Once we have all our actors, we need to configure the system to run as a cluster! The application.conf file is where the magic takes place. I’m going to split it in pieces to present it better, but you can find the complete file here.
一旦有了所有參與者,就需要配置系統(tǒng)以使其作為集群運(yùn)行! application.conf文件是神奇的地方。 我將對(duì)其進(jìn)行拆分以更好地呈現(xiàn)它,但是您可以在此處找到完整的文件。
Let’s start defining some useful variables.
讓我們開(kāi)始定義一些有用的變量。
clustering {ip = "127.0.0.1"ip = ${?CLUSTER_IP}port = 2552port = ${?CLUSTER_PORT}seed-ip = "127.0.0.1"seed-ip = ${?CLUSTER_SEED_IP}seed-port = 2552seed-port = ${?CLUSTER_SEED_PORT}cluster.name = "cluster-playground" }Here we are simply defining the ip and port of the nodes and the seed, as well as the cluster name. We set a default value, then we override it if a new one is specified. The configuration of the cluster is the following.
在這里,我們僅定義節(jié)點(diǎn)的IP和端口,種子以及群集名稱(chēng)。 我們?cè)O(shè)置一個(gè)默認(rèn)值,如果指定了新值,則將其覆蓋。 群集的配置如下。
akka {actor {provider = "cluster".../* router configuration */...}remote {log-remote-lifecycle-events = onnetty.tcp {hostname = ${clustering.ip}port = ${clustering.port}}}cluster {seed-nodes = ["akka.tcp://"${clustering.cluster.name}"@"${clustering.seed-ip}":"${clustering.seed-port}]auto-down-unreachable-after = 10s} } ... /* server vars */ ... /* cluster vars */ }Akka Cluster is build on top of Akka Remoting, so we need to configure it properly. First of all, we specify that we are going to use Akka Cluster saying that provider = "cluster". Then we bind cluster.ip and cluster.port to the hostname and port of the netty web framework.
Akka Cluster是在Akka Remoting之上構(gòu)建的,因此我們需要對(duì)其進(jìn)行正確配置。 首先,我們指定將要使用Akka Cluster,即provider = "cluster" 。 然后,我們將cluster.ip和cluster.port綁定到netty Web框架的hostname和port 。
The cluster requires some seed nodes as its entry points. We set them in the seed-nodes array, in the format akka.tcp://"{clustering.cluster.name}"@"{clustering.seed-ip}":”${clustering.seed-port}”. Right now we have one seed node, but we may add more later.
集群需要一些種子節(jié)點(diǎn)作為其入口點(diǎn)。 我們將它們?cè)O(shè)置在seed-nodes數(shù)組中,格式為akka.tcp://"{clustering.cluster.name}"@"{clustering.seed-ip}":”${clustering.seed-port}” 。 現(xiàn)在,我們有一個(gè)種子節(jié)點(diǎn),但以后可能會(huì)添加更多。
The auto-down-unreachable-after property sets a member as down after it is unreachable for a period of time. This should be used only during development, as explained in the official documentation.
auto-down-unreachable-after屬性將成員設(shè)置為在一段時(shí)間內(nèi)無(wú)法訪問(wèn)后變?yōu)閐own。 如官方文檔中所述,只能在開(kāi)發(fā)過(guò)程中使用它。
Ok, the cluster is configured, we can move to the next step: Dockerization and deployment!
好了,集群已經(jīng)配置好了,我們可以繼續(xù)下一步:Dockerization and Deployment!
Docker化和部署 (Dockerization and deployment)
To create the Docker container of our node we can use sbt-native-packager. Its installation is easy: add addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15") to the plugin.sbt file in the project/ folder. This amazing tool has a plugin for the creation of Docker containers. it allows us to configure the properties of our Dockerfile in the build.sbt file.
要?jiǎng)?chuàng)建我們節(jié)點(diǎn)的Docker容器,我們可以使用sbt-native-packager 。 它的安裝很容易:將addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15")到project/文件夾中的plugin.sbt文件中。 這個(gè)驚人的工具有一個(gè)用于創(chuàng)建Docker容器的插件。 它允許我們?cè)赽uild.sbt文件中配置Dockerfile的屬性。
// other build.sbt propertiesenablePlugins(JavaAppPackaging) enablePlugins(DockerPlugin) enablePlugins(AshScriptPlugin)mainClass in Compile := Some("com.elleflorio.cluster.playground.Server") dockerBaseImage := "java:8-jre-alpine" version in Docker := "latest" dockerExposedPorts := Seq(8000) dockerRepository := Some("elleflorio")Once we have setup the plugin, we can create the docker image running the command sbt docker:publishLocal. Run the command and taste the magic… ?
設(shè)置好插件后,我們可以運(yùn)行命令sbt docker:publishLocal創(chuàng)建sbt docker:publishLocal 。 運(yùn)行命令并品嘗魔術(shù)……?
We have the Docker image of our node, now we need to deploy it and check that everything works fine. The easiest way is to create a docker-compose file that will spawn a seed and a couple of other nodes.
我們有節(jié)點(diǎn)的Docker映像,現(xiàn)在我們需要部署它并檢查一切正常。 最簡(jiǎn)單的方法是創(chuàng)建一個(gè)docker-compose文件,該文件將產(chǎn)生一個(gè)種子和幾個(gè)其他節(jié)點(diǎn)。
version: '3.5'networks:cluster-network:services:seed:networks:- cluster-networkimage: elleflorio/akka-cluster-playgroundports:- '2552:2552'- '8000:8000'environment:SERVER_IP: 0.0.0.0CLUSTER_IP: seedCLUSTER_SEED_IP: seednode1:networks:- cluster-networkimage: elleflorio/akka-cluster-playgroundports:- '8001:8000'environment:SERVER_IP: 0.0.0.0CLUSTER_IP: node1CLUSTER_PORT: 1600CLUSTER_SEED_IP: seedCLUSTER_SEED_PORT: 2552node2:networks:- cluster-networkimage: elleflorio/akka-cluster-playgroundports:- '8002:8000'environment:SERVER_IP: 0.0.0.0CLUSTER_IP: node2CLUSTER_PORT: 1600CLUSTER_SEED_IP: seedCLUSTER_SEED_PORT: 2552I won’t spend time going through it, since it is quite simple.
因?yàn)樗芎?jiǎn)單,所以我不會(huì)花時(shí)間去研究它。
讓我們運(yùn)行它! (Let’s run it!)
Time to test our work! Once we run the docker-compose up command, we will have a cluster of three nodes up and running. The seed will respond to requests at port :8000, while node1 and node2 at port :8001 and :8002. Play a bit with the various endpoints. You will see that the requests for a Fibonacci number will be computed by a different node each time, following a round-robin policy. That’s good, we are proud of our work and can get out for a beer to celebrate! ?
是時(shí)候測(cè)試我們的工作了! 一旦運(yùn)行了docker-compose up命令,我們將建立一個(gè)由三個(gè)節(jié)點(diǎn)組成的集群并正在運(yùn)行。 seed將在端口:8000響應(yīng)請(qǐng)求,而node1和node2在端口:8001和:8002響應(yīng)。 嘗試一下各種端點(diǎn)。 您將看到,遵循循環(huán)策略,每次都會(huì)由不同的節(jié)點(diǎn)來(lái)計(jì)算對(duì)斐波那契數(shù)的請(qǐng)求。 很好,我們?yōu)槲覀兊墓ぷ鞲械阶院?#xff0c;可以出去喝杯啤酒慶祝一下! ?
結(jié)論 (Conclusion)
We are done here! We learned a lot of things in these ten minutes:
我們?cè)谶@里完成! 在這十分鐘中,我們學(xué)到了很多東西:
- What Akka Cluster is and what can do for us. 什么是Akka集群,什么可以為我們做。
- How to create a distributed application with it. 如何使用它創(chuàng)建分布式應(yīng)用程序。
- How to configure a Group Router for load-balancing in the cluster. 如何在群集中配置組路由器以實(shí)現(xiàn)負(fù)載平衡。
- How to Dockerize everything and deploy it using docker-compose. 如何對(duì)所有內(nèi)容進(jìn)行Docker化并使用docker-compose進(jìn)行部署。
You can find the complete application in my GitHub repo. Feel free to contribute or play with it as you like! ?
您可以在我的GitHub存儲(chǔ)庫(kù)中找到完整的應(yīng)用程序。 隨意貢獻(xiàn)或隨心所欲玩! ?
See you! ?
再見(jiàn)! ?
翻譯自: https://www.freecodecamp.org/news/how-to-make-a-simple-application-with-akka-cluster-506e20a725cf/
使用akka構(gòu)建高并發(fā)程序
總結(jié)
以上是生活随笔為你收集整理的使用akka构建高并发程序_如何使用Akka Cluster创建简单的应用程序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 客户旅程_我如何充分利用freeCode
- 下一篇: 梦到头发白了几根