基於akka與scala實現一個簡單rpc框架
一、RPC簡介
RPC,即 Remote Procedure Call(遠端過程呼叫),說得通俗一點就是:呼叫遠端計算機上的服務,就像呼叫本地服務一樣。
RPC 可基於 HTTP 或 TCP 協議,Web Service 就是基於 HTTP 協議的 RPC,它具有良好的跨平臺性,但其效能卻不如基於 TCP 協議的 RPC。會兩方面會直接影響 RPC 的效能,一是傳輸方式,二是序列化。
眾所周知,TCP 是傳輸層協議,HTTP 是應用層協議,而傳輸層較應用層更加底層,在資料傳輸方面,越底層越快,因此,在一般情況下,TCP 一定比 HTTP 快。就序列化而言,Java 提供了預設的序列化方式,但在高併發的情況下,這種方式將會帶來一些效能上的瓶頸,於是市面上出現了一系列優秀的序列化框架從而提供更高效的效能。
我們需要將服務部署在分散式環境下的不同節點上,通過服務註冊的方式,讓客戶端來自動發現當前可用的服務,並呼叫這些服務。這需要一種服務登錄檔(Service Registry)的元件,讓它來註冊分散式環境下所有的服務地址(包括:主機名與埠號)。
二、程式碼框架
三、程式碼實現
該小專案包含四個檔案:
1.WorkerInfo,用於儲存Worker的資訊,此次儲存Worker的上一次心跳時間
package com.zxl.rpc
class WorkerInfo(val id: String, val memory: Int, val cores: Int) {
// TODO 上一次心跳
var lastHeartbeatTime: Long = _
}
2.RemoteMessage,實現序列化並定義Master與Worker之間傳送資訊的型別package com.zxl.rpc
/**
* 用於實現序列化 網路傳輸
*/
trait RemoteMessage extends Serializable
// Worker -> Master
case class RegisterWorker(id: String, memory: Int, cores: Int) extends RemoteMessage
case class Heartbeat(id: String) extends RemoteMessage
// Master -> Worker
case class RegisteredWorker(masterUrl: String) extends RemoteMessage
// Worker -> self
case object SendHeartbeat
// Master -> self
case object CheckTimeOutWorker
3.Worker,與Master進行訊息互動package com.zxl.rpc
import java.util.UUID
import akka.actor.{Props, ActorSystem, Actor, ActorSelection}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class Worker(val masterHost: String, val masterPort: Int, val memory: Int, val cores: Int) extends Actor {
// 與master連線的物件
var master : ActorSelection = _
// 每個worker的id
val workerId = UUID.randomUUID().toString
// 傳送心跳的時間間隔
val HEART_INTERVAL = 10000
override def preStart(): Unit = {
// 建立連線
// 在Master啟動時會列印下面的那個協議, 可以先用這個做一個標誌, 連線哪個master
// 繼承actor後會有一個context, 可以通過它來連線
// 需要有/user, Master要和master那邊建立的名字保持一致
master = context.actorSelection(s"akka.tcp://MasterSystem@$masterHost:$masterPort/user/Master")
// 向Master傳送註冊訊息
master ! RegisterWorker(workerId, memory, cores)
}
override def receive: Receive = {
case RegisteredWorker(masterUrl) => {
println(masterUrl)
// 啟動定時器傳送心跳
import context.dispatcher
// 多長時間後執行 單位,多長時間執行一次 單位, 訊息的接受者(直接給master發不好, 先給自己傳送訊息, 以後可以做下判斷, 什麼情況下再傳送訊息), 資訊
context.system.scheduler.schedule(0 millis, HEART_INTERVAL millis, self, SendHeartbeat)
}
case SendHeartbeat => {
println("send heartbeat to master")
// 對master傳送心跳資訊,傳送當前worker的id
master ! Heartbeat(workerId)
}
}
}
object Worker {
def main(args: Array[String]) {
val host = args(0)
val port = args(1).toInt
val masterHost = args(2)
val masterPort = args(3).toInt
// 分配的記憶體大小
val memory = args(4).toInt
// 分配的處理器核數
val cores = args(5).toInt
// 準備配置
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin
val config = ConfigFactory.parseString(configStr)
// ActorSystem老大,輔助建立和監控下面的Actor,它是單例的
val actorSystem = ActorSystem("WorkerSystem", config)
actorSystem.actorOf(Props(new Worker(masterHost, masterPort, memory, cores)), "Worker")
actorSystem.awaitTermination()
}
}
4.Master,接收Worker的訊息並做回應package com.zxl.rpc
import akka.actor.{Props, ActorSystem, Actor}
import com.typesafe.config.ConfigFactory
import scala.collection.mutable
import scala.concurrent.duration._
class Master(val host: String, val port: Int) extends Actor {
// 儲存(workerId,WorkerInfo)
val idToWorker = new mutable.HashMap[String, WorkerInfo]()
// 儲存wokerInfo
// 使用set刪除快, 也可用linkList
val workers = new mutable.HashSet[WorkerInfo]()
// 超時檢查的間隔
val CHECK_INTERVAL = 15000
override def preStart(): Unit = {
println("preStart invoked")
// 匯入隱式轉換
import context.dispatcher
// 使用timer太low了, 可以使用akka的, 使用定時器, 要匯入這個包
// 定時檢查worker的心跳時間是否超時
context.system.scheduler.schedule(0 millis, CHECK_INTERVAL millis, self, CheckTimeOutWorker)
}
// 用於接收訊息
override def receive: Receive = {
case RegisterWorker(id, memory, cores) => {
// 判斷一下,是不是已經註冊過
if(!idToWorker.contains(id)) {
// 把Worker的資訊封裝起來儲存到記憶體當中
val workerInfo = new WorkerInfo(id, memory, cores)
idToWorker(id) = workerInfo
workers += workerInfo
// 通知worker註冊 並將master的地址返回給worker
sender ! RegisteredWorker(s"akka.tcp://MasterSystem@$host:$port/user/Master")
}
}
case Heartbeat(id) => {
if(idToWorker.contains(id)) {
val workerInfo = idToWorker(id)
// 報活
val currentTime = System.currentTimeMillis()
// 更新已收到id的worker的上一次心跳時間
workerInfo.lastHeartbeatTime = currentTime
}
}
case CheckTimeOutWorker => {
val currentTime = System.currentTimeMillis()
// 獲取過時的worker
val toRemove = workers.filter(x => currentTime - x.lastHeartbeatTime > CHECK_INTERVAL)
// 將超時的worker從兩個集合中去掉
for(w <- toRemove) {
workers -= w
idToWorker -= w.id
}
println(workers.size)
}
}
}
object Master {
def main(args: Array[String]) {
val host = args(0)
val port = args(1).toInt
// 準備配置
val configStr =
s"""
|akka.actor.provider = "akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname = "$host"
|akka.remote.netty.tcp.port = "$port"
""".stripMargin
val config = ConfigFactory.parseString(configStr)
// ActorSystem老大,輔助建立和監控下面的Actor,它是單例的
val actorSystem = ActorSystem("MasterSystem", config)
// 建立Actor
val master = actorSystem.actorOf(Props(new Master(host, port)), "Master")
master ! "hello"
actorSystem.awaitTermination()
}
}
相關文章
- 從零實現一個RPC框架系列文章(二):11個類實現簡單RPCRPC框架
- 基於vue實現一個簡單的MVVM框架(原始碼分析)VueMVVM框架原始碼
- 徒手擼一個簡單的RPC框架RPC框架
- 動手實現一個簡單的 rpc 框架到入門 grpc (下)RPC框架
- 動手實現一個簡單的 rpc 框架到入門 grpc(上)RPC框架
- 動手實現一個簡單的 rpc 框架到入門 grpc (上)RPC框架
- Golang快速實現一個簡單RPC服務GolangRPC
- 自己用 Netty 實現一個簡單的 RPCNettyRPC
- 造個輪子之基於 Netty 實現自己的 RPC 框架NettyRPC框架
- 手把手教你基於Netty實現一個基礎的RPC框架(通俗易懂)NettyRPC框架
- akka-grpc - 基於akka-http和akka-streams的scala gRPC開發工具RPCHTTP
- Go 實現簡易 RPC 框架GoRPC框架
- 從零開始實現簡單 RPC 框架 1:RPC 框架的結構和設計RPC框架
- 從零開始實現一個RPC框架(一)RPC框架
- 使用Netty和動態代理實現一個簡單的RPCNettyRPC
- SimpleRAG:基於WPF與Semantic Kernel實現的一個簡單的RAG應用
- 基於ES5`defineProperty` 實現簡單的 Mvvm框架MVVM框架
- 基於 Twirp RPC 的簡易 JSON Api Gateway 實現RPCJSONAPIGateway
- 從零開始實現一個RPC框架(四)RPC框架
- 從零開始實現一個RPC框架(零)RPC框架
- 從零開始實現一個RPC框架(二)RPC框架
- 從零開始實現一個RPC框架(五)RPC框架
- 從零開始實現一個RPC框架(三)RPC框架
- 從零開始實現簡單 RPC 框架 4:註冊中心RPC框架
- netty 實現簡單的rpc呼叫NettyRPC
- 大廚小鮮——基於Netty自己動手實現RPC框架NettyRPC框架
- 從零開始實現一個IDL+RPC框架RPC框架
- 從零開始實現一個分散式RPC框架分散式RPC框架
- 關於一個最簡單的數獨解題實現與疑惑一
- Java使用Netty實現簡單的RPCJavaNettyRPC
- 關於SSM框架的一個簡單DemoSSM框架
- 通過Dapr實現一個簡單的基於.net的微服務電商系統(二十)——Saga框架實現思路分享微服務框架
- 基於元件化開發,一個簡單的Android專案框架元件化Android框架
- 從零開始實現簡單 RPC 框架 3:配置匯流排 URLRPC框架
- 從零開始實現簡單 RPC 框架 2:擴充套件利器 SPIRPC框架套件
- 基於netty手寫RPC框架NettyRPC框架
- 基於 Hyperf+ SQL Server 實現的一個簡單資料庫 curdSQLServer資料庫
- 實現一個簡單的基於 WebAssembly 的圖片處理應用Web
- 動手造輪子:實現一個簡單的 AOP 框架框架