使用akka構建高并發程序_如何使用Akka Cluster創建簡單的應用程序

使用akka構建高并發程序

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.

如果您閱讀了我以前關于Scalachain的故事,您可能會注意到它遠非分布式系統。 它缺少與其他節點正確配合使用的所有功能。 此外,由單個節點組成的區塊鏈是無用的。 基于這個原因,我認為是時候解決這個問題了。

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提供支持,為什么不趁此機會與Akka Cluster一起玩呢? 我創建了一個簡單的項目來完善Akka Cluster ,在這個故事中,我將分享我的經驗。 我們將創建一個由三個節點組成的群集,使用群集感知路由器來平衡它們之間的負載。 一切都將在Docker容器中運行,并且我們將使用docker-compose進行輕松部署。

Ok, Let’s roll! ?

好吧,滾吧! ?

Akka Cluster快速入門 (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為分布式應用程序的創建提供了強大的支持。 最佳用例是在分布式環境中擁有要復制N次的節點時。 這意味著所有N個節點都是運行相同代碼的對等點。 Akka群集使您可以立即發現同一群集中的成員。 使用群集感知路由器,可以在不同節點中的參與者之間平衡消息。 還可以選擇平衡策略,使負載平衡成為小菜一碟!

Actually you can chose between two types of routers:

實際上,您可以在兩種類型的路由器之間進行選擇:

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.

組路由器 -將消息發送到的參與者-稱為路由-使用其參與者路徑指定。 路由器共享在群集中創建的路由。 在此示例中,我們將使用組路由器。

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.

池路由器 -路由是由路由器創建和部署的,因此它們是角色層次結構中的子級。 路由器之間不共享路由。 這對于主副本方案是理想的,因為每個路由器都是主副本,并且路由副本。

This is just the tip of the iceberg, so I invite you to read the official documentation for more insights.

這只是冰山一角,因此,我邀請您閱讀官方文檔以獲取更多見解。

數學計算的集群 (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.

讓我們描述一個用例場景。 假設設計一個按要求執行數學計算的系統。 該系統在線部署,因此它需要一個REST API來接收計算請求。 內部處理器處理這些請求,執行計算并返回結果。

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!

現在,處理器只能計算斐波那契數 。 我們決定使用節點集群來在節點之間分配負載并提高性能。 Akka Cluster將處理節點之間的群集動態和負載平衡。 好的聽起來不錯!

演員等級 (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.

首先,我們需要定義參與者的層次結構。 該系統可以分為三個功能部分: 業務邏輯集群管理節點本身。 還有服務器,但它不是演員,我們將在以后進行處理。

Business logic

商業邏輯

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.

該應用程序應該進行數學計算。 我們可以定義一個簡單的Processor actor來管理所有計算任務。 我們支持的每個計算都可以在特定的actor中實現,它將成為Processor的子代。 這樣,應用程序是模塊化的,并且易于擴展和維護。 現在, Processor的唯一孩子將是ProcessorFibonacci actor。 我想您可以猜到它的任務是什么。 這應該足以開始。

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.

要管理集群,我們需要一個ClusterManager 。 聽起來很簡單,對吧? 這個參與者處理與集群有關的所有事情,例如在被詢問時返回其成員。 記錄集群內部發生的情況將很有用,因此我們定義了一個ClusterListener actor。 這是ClusterManager的子級,并訂閱記錄它們的集群事件。

Node

節點

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是我們層次結構的根源。 這是我們與API通信的系統的入口點。 ProcessorClusterManager是其子級,以及ProcessorRouter actor。 這是系統的負載平衡器,可在Processor之間分配負載。 我們將其配置為群集感知路由器,因此每個ProcessorRouter都可以將消息發送到每個節點上的Processor

演員執行 (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.

是時候實施我們的演員了! 首先,我們實現與系統業務邏輯相關的參與者。 然后,我們移至用于集群管理的參與者,最后移至根參與者( 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!

該參與者執行斐波那契數的計算。 它收到一條Compute消息,其中包含要計算的數字和要答復的actor的引用。 參考很重要,因為可以有不同的請求方。 請記住,我們正在分布式環境中工作!

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函數將計算結果。 我們將其包裝在ProcessorResponse對象中,以提供有關執行計算的節點的信息。 這對于以后查看循環策略的作用將很有用。

The result is then sent to the actor we should reply to. Easy-peasy.

然后將結果發送給我們應該答復的演員。 十分簡單。

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角色負責管理特定的子處理器,例如斐波那契。 它應該實例化子處理器并將請求轉發給它們。 現在,我們只有一個子處理器,因此Processor會收到一種消息: ComputeFibonacci 。 該消息包含要計算的斐波那契數。 接收到后,要計算的數字連同sender()的引用一起發送到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

集群監聽器

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.

我們想記錄有關集群中發生的情況的有用信息。 如果需要,這可以幫助我們調試系統。 這是ClusterListener actor的目的。 在啟動之前,它會訂閱群集的事件消息。 MemberUp對諸如MemberUpUnreachableMemberMemberRemoved類的消息做出React,記錄相應的事件。 停止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.

負責集群管理的ClusterManagerClusterManager 。 它創建ClusterListener actor,并根據請求提供集群成員的列表。 可以擴展它以添加更多功能,但是現在就足夠了。

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.

處理器之間的負載平衡由ProcessorRouter 。 它是由Node actor創建的,但是這次所有必需的信息都在系統的配置中提供。

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文件中的相關部分。

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 。 在該屬性內,我們可以配置路由器的行為:

  • router: this is the policy for the load balancing of messages. I chose the round-robin-group, but there are many others.

    router :這是消息負載平衡的策略。 我選擇了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 :這是將接收路由器處理的消息的參與者的路徑。 我們說的是: “收到消息后,尋找與這些路徑相對應的參與者。 根據政策選擇一個,然后將消息轉發給它。” 由于我們使用的是群集感知路由器,因此路由可以位于群集的任何節點上。

  • cluster.enabled: are we operating in a cluster? The answer is on, of course!

    cluster.enabled :我們是否在集群中運行? 答案是on的,當然!

  • cluster.allow-local-routees: here we are allowing the router to choose a routee in its node.

    cluster.allow-local-routees :在這里,我們允許路由器在其節點中選擇一個路由。

Using this configuration we can create a router to load balance the work among our processors.

使用此配置,我們可以創建一個路由器來負載均衡處理器之間的工作。

Node

節點

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層次結構的根是Node 。 它創建子actor(即ClusterManagerProcessorProcessorRouter ),并將消息轉發到正確的子actor。 這里沒什么復雜的。

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)}
}

服務器和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.

我們集群的每個節點都運行一臺能夠接收請求的服務器。 Server創建我們的參與者系統,并通過application.conf文件進行配置。

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為服務器本身和REST API供電,公開了三個簡單的端點。 這些端點是在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

第一個是/health ,用于檢查節點的運行狀況。 如果節點已啟動并正在運行,它將以200 OK響應

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端點以集群的當前活動成員作為響應。

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端點,用于請求n的斐波那契數。

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.

它以包含結果的ProcessorResponse以及進行計算的節點的ID進行響應。

集群配置 (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.

一旦有了所有參與者,就需要配置系統以使其作為集群運行! application.conf文件是神奇的地方。 我將對其進行拆分以更好地呈現它,但是您可以在此處找到完整的文件。

Let’s start defining some useful variables.

讓我們開始定義一些有用的變量。

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.

在這里,我們僅定義節點的IP和端口,種子以及群集名稱。 我們設置一個默認值,如果指定了新值,則將其覆蓋。 群集的配置如下。

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之上構建的,因此我們需要對其進行正確配置。 首先,我們指定將要使用Akka Cluster,即provider = "cluster" 。 然后,我們將cluster.ipcluster.port綁定到netty Web框架的hostnameport

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.

集群需要一些種子節點作為其入口點。 我們將它們設置在seed-nodes數組中,格式為akka.tcp://"{clustering.cluster.name}"@"{clustering.seed-ip}":”${clustering.seed-port}” 。 現在,我們有一個種子節點,但以后可能會添加更多。

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屬性將成員設置為在一段時間內無法訪問后變為down。 如官方文檔中所述,只能在開發過程中使用它。

Ok, the cluster is configured, we can move to the next step: Dockerization and deployment!

好了,集群已經配置好了,我們可以繼續下一步: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.

要創建我們節點的Docker容器,我們可以使用sbt-native-packager 。 它的安裝很容易:將addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15")project/文件夾中的plugin.sbt文件中。 這個驚人的工具有一個用于創建Docker容器的插件。 它允許我們在build.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… ?

設置好插件后,我們可以運行命令sbt docker:publishLocal創建sbt docker:publishLocal 。 運行命令并品嘗魔術……?

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.

我們有節點的Docker映像,現在我們需要部署它并檢查一切正常。 最簡單的方法是創建一個docker-compose文件,該文件將產生一個種子和幾個其他節點。

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: 2552

I won’t spend time going through it, since it is quite simple.

因為它很簡單,所以我不會花時間去研究它。

讓我們運行它! (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! ?

是時候測試我們的工作了! 一旦運行了docker-compose up命令,我們將建立一個由三個節點組成的集群并正在運行。 seed將在端口:8000響應請求,而node1node2在端口:8001:8002響應。 嘗試一下各種端點。 您將看到,遵循循環策略,每次都會由不同的節點來計算對斐波那契數的請求。 很好,我們為我們的工作感到自豪,可以出去喝杯啤酒慶祝一下! ?

結論 (Conclusion)

We are done here! We learned a lot of things in these ten minutes:

我們在這里完成! 在這十分鐘中,我們學到了很多東西:

  • What Akka Cluster is and what can do for us.

    什么是Akka集群,什么可以為我們做。
  • How to create a distributed application with it.

    如何使用它創建分布式應用程序。
  • How to configure a Group Router for load-balancing in the cluster.

    如何在群集中配置組路由器以實現負載平衡。
  • How to Dockerize everything and deploy it using docker-compose.

    如何對所有內容進行Docker化并使用docker-compose進行部署。

You can find the complete application in my GitHub repo. Feel free to contribute or play with it as you like! ?

您可以在我的GitHub存儲庫中找到完整的應用程序。 隨意貢獻或隨心所欲玩! ?

See you! ?

再見! ?

翻譯自: https://www.freecodecamp.org/news/how-to-make-a-simple-application-with-akka-cluster-506e20a725cf/

使用akka構建高并發程序

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/392078.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/392078.shtml
英文地址,請注明出處:http://en.pswp.cn/news/392078.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

pandas之數值計算與統計

數值計算與統計 對于DataFrame來說,求和、最大、最小、平均等統計方法,默認是按列進行統計,即axis 0,如果添加參數axis 1則會按照行進行統計。 如果存在空值,在統計時默認會忽略空值,如果添加參數skipna …

python自動化數據報告_如何:使用Python將實時數據自動化到您的網站

python自動化數據報告This tutorial will be helpful for people who have a website that hosts live data on a cloud service but are unsure how to completely automate the updating of the live data so the website becomes hassle free. For example: I host a websit…

一顆站在技術邊緣的土豆

2012年開始上專業課,2013年打了一年游戲,年底專業課忘光了,但是蒙混過關沒掛科,2014年7月份畢業,對這個社會充滿向往。2014年9月份——方正代理商做網絡安全公司。2015年3月份跳槽到一家vmware代理商公司。2016年6月&a…

leetcode 839. 相似字符串組(并查集)

如果交換字符串 X 中的兩個不同位置的字母,使得它和字符串 Y 相等,那么稱 X 和 Y 兩個字符串相似。如果這兩個字符串本身是相等的,那它們也是相似的。 例如,“tars” 和 “rats” 是相似的 (交換 0 與 2 的位置); “r…

android intent參數是上次的結果,【Android】7.0 Intent向下一個活動傳遞數據、返回數據給上一個活動...

1.0 可以利用Intent吧數據傳遞給上一個活動,新建一個叫“hellotest01”的項目。新建活動FirstActivity,勾選“Generate Layout File”和“Launcher Activity”。image修改AndroidMainifest.xml中的內容:android:name".FirstActivity&quo…

實習一年算工作一年嗎?_經過一年的努力,我如何找到軟件工程工作

實習一年算工作一年嗎?by Andrew Ngo通過安德魯恩戈 經過一年的努力,我如何找到軟件工程工作 (How I landed a software engineering job after a year of hard work) Many of us think the path to becoming a software engineer requires years of education an…

學習深度學習需要哪些知識_您想了解的有關深度學習的所有知識

學習深度學習需要哪些知識有關深層學習的FAU講義 (FAU LECTURE NOTES ON DEEP LEARNING) Corona was a huge challenge for many of us and affected our lives in a variety of ways. I have been teaching a class on Deep Learning at Friedrich-Alexander-University Erlan…

參加開發競賽遇到的問題【總結】

等比賽完就寫。 轉載于:https://www.cnblogs.com/jiangyuanjia/p/11261978.html

html5--3.16 button元素

html5--3.16 button元素 學習要點 掌握button元素的使用button元素 用來建立一個按鈕從功能上來說,與input元素建立的按鈕相同button元素是雙標簽,其內部可以配置圖片與文字,進行更復雜的樣式設計不僅可以在表單中使用,還可以在其…

如何注冊鴻蒙id,鴻蒙系統真機調試證書 和 設備ID獲取

鴻蒙系統真機調試創建項目創建項目創建應用創建鴻蒙應用(注意,測試階段需要發郵件申請即可)關聯應用項目進入關聯 添加引用準備調試使用的 p12 和證書請求 csr使用以下命令// 別名"test"可以修改,但必須前后一致,密碼請自行修改key…

Java—實現 IOC 功能的簡單 Spring 框架

編寫一個實現 IOC 功能的簡單 Spring 框架,包含對象注冊、對象管理、及暴 露給外部獲取對象的功能,并編寫測試程序。擴展注冊器的方式,要求采用 XML 和 txt 文件。 源代碼 package myspring;import java.lang.reflect.Method; import java.…

讀zepto核心源碼學習JS筆記(3)--zepto.init()

上篇已經講解了zepto.init()的幾種情況,這篇就繼續記錄這幾種情況下的具體分析. 1. 首先是第一種情況,selector為空 既然是反向分析,那我們先看看這句話的代碼; if (!selector) return zepto.Z() 這里的返回值為zepto.Z();那我們繼續往上找zepto.Z()函數 zepto.Z function(dom…

css flexbox模型_Flexbox和CSS Grid之間的主要區別

css flexbox模型by Shaira Williams由莎拉威廉姆斯(Shaira Williams) Flexbox和CSS Grid之間的主要區別 (The main differences between Flexbox and CSS Grid) Dimensions define the primary demarcation between Flexbox and CSS Grid. Flexbox was designed specifically …

置信區間估計 預測區間估計_估計,預測和預測

置信區間估計 預測區間估計Estimation implies finding the optimal parameter using historical data whereas prediction uses the data to compute the random value of the unseen data.估計意味著使用歷史數據找到最佳參數,而預測則使用該數據來計算未見數據的…

鴻蒙系統還會推出嗎,華為明年所有自研設備都升級鴻蒙系統,還會推出基于鴻蒙系統的新機...

不負期許,華為鴻蒙OS手機版如期而至。今日(12月15日),鴻蒙OS 2.0手機開發者Beta版本正式上線,支持運行安卓應用,P40、Mate 30系列可申請公測。國內媒體報道稱,華為消費者業務軟件部副總裁楊海松表示,按照目…

C#中將DLL文件打包到EXE文件

1:在工程目錄增加dll目錄,然后將dll文件復制到此目錄,例如: 2:增加引用,定位到工程的dll目錄,選中要增加的dll文件 3:修改dll文件夾下面的dll文件屬性 選中嵌入式資源,不…

PopupMenu控件的使用

1、用PopupMenu控件能進行右鍵菜單的實現,它的實現還需要綁定到barManager控件上,在barManager的Customize中添加右鍵所需要顯示的功能。 2、PopupMenu屬性欄中綁定Manager為barManager; 3、窗體加載事件中創建 this.popupMenu1.AddItems(new…

Java—動態代理

動態代理利用了JDK API,動態地在內存中構建代理對象,從而實現對目標對象的代理功能。動態代理又被稱為JDK代理或接口代理。 靜態代理與動態代理的區別主要在: 靜態代理在編譯時就已經實現,編譯完成后代理類是一個實際的class文件…

Oracle VM Virtual Box的安裝

安裝Oracle VM Virtual Box安裝擴展插件 選擇"管理"?"全局設定" 在設置對話框中,選擇"擴展" 選擇"添加包" 找到"Oracle_VM_VirtualBox_Extension_Pack-4.1.18-78361",點擊"打開" 5&#x…

python 移動平均線_Python中的SMA(短期移動平均線)

python 移動平均線With the evolution of technology rapidly evolving, so do strategies in the stock market. In this post, I’ll go over how I created a SMA(Short Moving Average) strategy.隨著技術的飛速發展,股票市場的策略也在不斷發展。 在本文中&…