上篇我们做了一个WriterActor的例子,主要目的是示范WriterActor如何作为集群分片用persistentActor特性及event-sourcing模式实现CQRS的写功能。既然是集群分片,那么我们就在这篇讲讲WriterActor的部署和测试,因为这个里面还是有些值得注意的地方。下面是一段WriteActor,即集群分片(cluster-sharding)的部署代码:

  1. ClusterSharding(system).start(
  2. typeName = shardName,
  3. entityProps = writerProps,
  4. settings = cpsSettings,
  5. extractEntityId = getPOSId,
  6. extractShardId = getShopId,
  7. allocationStrategy = ClusterSharding(system).defaultShardAllocationStrategy(cpsSettings),
  8. handOffStopMessage = PassivatePOS
  9. )

注意带handOffStopMessage参数的start函数必须同时提供allocationStrategy。这个参数提供了passivation消息类型。

整个集群分片部署代码如下:

  1. object POSRouter extends LogSupport {
  2. def main(args: Array[String]) {
  3. import WriterActor._
  4. import Commands._
  5.  
  6. val argsPat = "(.*):(.*)".r
  7. val (host, port) = args() match {
  8. case argsPat(h, p) => (h, p)
  9. case _ => ("localhost", "")
  10. }
  11.  
  12. val config = ConfigFactory.parseString("akka.remote.netty.tcp.port=\"" + port + "\"")
  13. .withFallback(ConfigFactory.parseString("akka.remote.netty.tcp.hostname=\"" + host + "\""))
  14. //roles can be deployed on this node
  15. .withFallback(ConfigFactory.parseString("akka.cluster.roles = [poswriter]"))
  16. .withFallback(ConfigFactory.load())
  17.  
  18. log.info(s"******* hostname = $host, port = $port *******")
  19.  
  20. val shardName = "POSShard"
  21.  
  22. case class POSMessage(id: Long, cmd: POSCommand) {
  23. def shopId = id.toString.head.toString
  24.  
  25. def posId = id.toString
  26. }
  27.  
  28. val getPOSId: ShardRegion.ExtractEntityId = {
  29. case posCommand: POSMessage => (posCommand.posId, posCommand.cmd)
  30. }
  31. val getShopId: ShardRegion.ExtractShardId = {
  32. case posCommand: POSMessage => posCommand.shopId
  33. }
  34.  
  35. val system = ActorSystem("cloud-pos-server", config)
  36. val role = "poswriter" //role of this shard
  37. val cpsSettings = ClusterShardingSettings(system).withRole(role) //.withPassivateIdleAfter(10 minutes)
  38.  
  39. ClusterSharding(system).start(
  40. typeName = shardName,
  41. entityProps = writerProps,
  42. settings = cpsSettings,
  43. extractEntityId = getPOSId,
  44. extractShardId = getShopId,
  45. allocationStrategy = ClusterSharding(system).defaultShardAllocationStrategy(cpsSettings),
  46. handOffStopMessage = PassivatePOS
  47. )
  48.  
  49. system.actorOf(ClusterMonitor.props, "cps-cluster-monitor")
  50.  
  51. }
  52. }

以上有几个参数需要特别注意:host和port是从main的参数解析出来的如192.168.11.162:2551,代表本节点的host和port。akka.cluster.roles代表本节点支持的角色,这里poswriter是其中之一。而ClusterShardingSettings(system).withRole("poswriter")代表这个分片shard只能在支持poswriter角色的节点上部署。如果搞错了运行时你会发现Sharding无法启动。上面这段程序代表本节点支持poswriter角色。在本节点(输入的IP地址)部署了一个名称为“POSShard”的cluster-sharding,它具备poswriter角色。

如果我在多部机器上运行这段代码,输入当前机器的IP+PORT就代表在这么多台机器上都部署了“POSShard”分片。上面的ClusterMonitor是个集群状态监控actor:

  1. package sdp.cluster.monitor
  2.  
  3. import akka.actor._
  4. import akka.cluster.ClusterEvent._
  5. import akka.cluster._
  6. import sdp.logging.LogSupport
  7.  
  8. object ClusterMonitor {
  9. def props = Props(new ClusterMonitor)
  10. }
  11.  
  12. class ClusterMonitor extends Actor with LogSupport {
  13. val cluster = Cluster(context.system)
  14. override def preStart(): Unit = {
  15. cluster.subscribe(self,initialStateMode = InitialStateAsEvents
  16. ,classOf[MemberEvent],classOf[UnreachableMember]) //订阅集群状态转换信息
  17. super.preStart()
  18. }
  19.  
  20. override def postStop(): Unit = {
  21. cluster.unsubscribe(self) //取消订阅
  22. super.postStop()
  23. }
  24.  
  25. override def receive: Receive = {
  26. case MemberJoined(member) =>
  27. log.info(s"Member is Joining: {${member.address}}")
  28. case MemberUp(member) =>
  29. log.info(s"Member is Up: {${member.address}}")
  30. case MemberLeft(member) =>
  31. log.info(s"Member is Leaving: {${member.address}}")
  32. case MemberExited(member) =>
  33. log.info(s"Member is Exiting: {${member.address}}")
  34. case MemberRemoved(member, previousStatus) =>
  35. log.info(
  36. s"Member is Removed: {${member.address}} after {${previousStatus}")
  37. case UnreachableMember(member) =>
  38. log.info(s"Member detected as unreachable: {${member.address}}")
  39. cluster.down(member.address) //手工驱除,不用auto-down
  40. case _: MemberEvent => // ignore
  41. }
  42.  
  43. }

有了它我们可以监视集群节点连接状态。

好了,现在假设我们在几台机器组成的集群各节点上都部署了“POSShard”分片,那么就设计个客户端来向这个“POSShard”分片发送POSMessage:

  1. case class POSMessage(id: Long, cmd: POSCommand) {
  2. def shopId = id.toString.head.toString
  3.  
  4. def posId = id.toString
  5. }
  6.  
  7. val getPOSId: ShardRegion.ExtractEntityId = {
  8. case posCommand: POSMessage => (posCommand.posId, posCommand.cmd)
  9. }
  10. val getShopId: ShardRegion.ExtractShardId = {
  11. case posCommand: POSMessage => posCommand.shopId
  12. }

这个客户端必须考虑以下几点:它必须在同一个集群,也就是它也是集群其中一个节点,否则无法和其它部署了“POSShard”分片的节点进行信息交流。但它又不能同处与部署了“POSShard”的节点,因为remote的hostname和port已经被占用。所以只能把客户端放在一个没有部署“POSShard”的节点上,然后用ClusterSharding(system).startProxy来启动一个分片中介:

  1. //no shard deployed on this node 2558, use proxy
  2. val posHandler = ClusterSharding(system).startProxy(
  3. typeName = shardName,
  4. role = Some("poswriter"),
  5. extractEntityId = getPOSId,
  6. extractShardId = getShopId
  7. )
  8.  
  9. //val posHandler = ClusterSharding(system).shardRegion(shardName)
  10.  
  11. system.actorOf(POSClient.props(posHandler), "pos-client")

注意这个proxy的role必须是Some("poswriter"),只有这样才能调用其它节点上的”POSShard“,因为它们的角色都是“poswriter”。与WriterActor交互的必须是个actor,因为WriterActor会用sender()返回结果,这个sender()是个ActorRef:

  1. object POSClient {
  2. def props(pos: ActorRef) = Props(new POSClient(pos))
  3. }
  4. class POSClient(posHandler: ActorRef) extends Actor with LogSupport {
  5.  
  6. override def receive: Receive = {
  7. case msg @ POSMessage(_,_) => posHandler ! msg
  8. case resp: POSResponse =>
  9. log.info(s"response from server: $resp")
  10. }
  11. }

我们可用下面的方式来指挥WriterActor:

  1. val posref = system.actorOf(POSClient.props(posHandler), "pos-client")
  2.  
  3. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", apple.code, , ))
  4. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", pineapple.code, , ))
  5. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", banana.code, , ))
  6. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", grape.code, , ))
  7. posref ! POSMessage(,Subtotal)

下面是服务端分片部署源代码:

resources/application.conf

  1. akka.actor.warn-about-java-serializer-usage = off
  2. akka.log-dead-letters-during-shutdown = off
  3. akka.log-dead-letters = off
  4.  
  5. akka {
  6. loglevel = INFO
  7. actor {
  8. provider = "cluster"
  9. }
  10.  
  11. remote {
  12. log-remote-lifecycle-events = off
  13. netty.tcp {
  14. hostname = "127.0.0.1"
  15. port =
  16. }
  17. }
  18.  
  19. cluster {
  20. seed-nodes = [
  21. "akka.tcp://cloud-pos-server@192.168.11.162:2551"]
  22. log-info = off
  23. sharding {
  24. role = "poswriter"
  25. passivate-idle-entity-after = m
  26. }
  27. }
  28.  
  29. persistence {
  30. journal.plugin = "cassandra-journal"
  31. snapshot-store.plugin = "cassandra-snapshot-store"
  32. }
  33.  
  34. }
  35.  
  36. cassandra-journal {
  37. contact-points = ["192.168.11.162"]
  38. }
  39.  
  40. cassandra-snapshot-store {
  41. contact-points = ["192.168.11.162"]
  42. }

POSRouter.scala

  1. package cloud.pos.server
  2.  
  3. import akka.actor._
  4. import akka.cluster.sharding._
  5. import akka.cluster.sharding.ClusterSharding
  6. import com.typesafe.config.ConfigFactory
  7. import sdp.cluster.monitor._
  8. import sdp.logging._
  9.  
  10. object POSRouter extends LogSupport {
  11. def main(args: Array[String]) {
  12. import WriterActor._
  13. import Commands._
  14.  
  15. val argsPat = "(.*):(.*)".r
  16. val (host, port) = args() match {
  17. case argsPat(h, p) => (h, p)
  18. case _ => ("localhost", "")
  19. }
  20.  
  21. val config = ConfigFactory.parseString("akka.remote.netty.tcp.port=\"" + port + "\"")
  22. .withFallback(ConfigFactory.parseString("akka.remote.netty.tcp.hostname=\"" + host + "\""))
  23. //roles can be deployed on this node
  24. .withFallback(ConfigFactory.parseString("akka.cluster.roles = [poswriter]"))
  25. .withFallback(ConfigFactory.load())
  26.  
  27. log.info(s"******* hostname = $host, port = $port *******")
  28.  
  29. val shardName = "POSShard"
  30.  
  31. case class POSMessage(id: Long, cmd: POSCommand) {
  32. def shopId = id.toString.head.toString
  33.  
  34. def posId = id.toString
  35. }
  36.  
  37. val getPOSId: ShardRegion.ExtractEntityId = {
  38. case posCommand: POSMessage => (posCommand.posId, posCommand.cmd)
  39. }
  40. val getShopId: ShardRegion.ExtractShardId = {
  41. case posCommand: POSMessage => posCommand.shopId
  42. }
  43.  
  44. val system = ActorSystem("cloud-pos-server", config)
  45. val role = "poswriter" //role of this shard
  46. val cpsSettings = ClusterShardingSettings(system).withRole(role) //.withPassivateIdleAfter(10 minutes)
  47.  
  48. ClusterSharding(system).start(
  49. typeName = shardName,
  50. entityProps = writerProps,
  51. settings = cpsSettings,
  52. extractEntityId = getPOSId,
  53. extractShardId = getShopId,
  54. allocationStrategy = ClusterSharding(system).defaultShardAllocationStrategy(cpsSettings),
  55. handOffStopMessage = PassivatePOS
  56. )
  57.  
  58. system.actorOf(ClusterMonitor.props, "cps-cluster-monitor")
  59.  
  60. }
  61. }

下面是这个测试项目的源代码:

build.sbt

  1. name := "cloud-pos-client"
  2.  
  3. version := "0.1"
  4.  
  5. scalaVersion := "2.12.8"
  6.  
  7. libraryDependencies := Seq(
  8. "com.typesafe.akka" %% "akka-cluster-sharding" % "2.5.19",
  9. "com.typesafe.akka" %% "akka-persistence" % "2.5.19",
  10. "com.typesafe.akka" %% "akka-persistence-cassandra" % "0.93",
  11. "com.typesafe.akka" %% "akka-persistence-cassandra-launcher" % "0.93" % Test,
  12. "ch.qos.logback" % "logback-classic" % "1.2.3"
  13. )

resources/application.conf

  1. akka.actor.warn-about-java-serializer-usage = off
  2. akka.log-dead-letters-during-shutdown = off
  3. akka.log-dead-letters = off
  4.  
  5. akka {
  6. loglevel = INFO
  7. actor {
  8. provider = "cluster"
  9. }
  10.  
  11. remote {
  12. log-remote-lifecycle-events = off
  13. netty.tcp {
  14. hostname = "192.168.11.162"
  15. port =
  16. }
  17. }
  18.  
  19. cluster {
  20. seed-nodes = [
  21. "akka.tcp://cloud-pos-server@192.168.11.162:2551"]
  22. log-info = off
  23. }
  24.  
  25. }

resources/logback.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <configuration>
  3. <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  4. <encoder>
  5. <Pattern>
  6. %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{} - %msg%n
  7. </Pattern>
  8. </encoder>
  9. </appender>
  10.  
  11. <root level="debug">
  12. <appender-ref ref="STDOUT" />
  13. </root>
  14. </configuration>

ClientDemo.scala

  1. package cloud.pos.client
  2. import akka.actor._
  3. import akka.cluster.sharding.ClusterSharding
  4. import sdp.cluster.monitor._
  5. import sdp.logging._
  6. import Commands._
  7. import States._
  8. import Items._
  9. import akka.cluster.sharding._
  10.  
  11. object POSClientDemo extends LogSupport {
  12. def main(args: Array[String]) {
  13.  
  14. val system = ActorSystem("cloud-pos-server")
  15.  
  16. val shardName = "POSShard"
  17.  
  18. val getPOSId: ShardRegion.ExtractEntityId = {
  19. case posCommand: POSMessage => (posCommand.posId, posCommand.cmd)
  20. }
  21. val getShopId: ShardRegion.ExtractShardId = {
  22. case posCommand: POSMessage => posCommand.shopId
  23. }
  24.  
  25. //no shard deployed on this node 2558, use proxy
  26. val posHandler = ClusterSharding(system).startProxy(
  27. typeName = shardName,
  28. role = Some("poswriter"),
  29. extractEntityId = getPOSId,
  30. extractShardId = getShopId
  31. )
  32.  
  33. //val posHandler = ClusterSharding(system).shardRegion(shardName)
  34.  
  35. system.actorOf(ClusterMonitor.props, "cps-cluster-monitor")
  36.  
  37. val posref = system.actorOf(POSClient.props(posHandler), "pos-client")
  38.  
  39. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", apple.code, , ))
  40. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", pineapple.code, , ))
  41. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", banana.code, , ))
  42. posref ! POSMessage(, LogSales(SALESTYPE.plu, "", grape.code, , ))
  43. posref ! POSMessage(,Subtotal)
  44.  
  45. scala.io.StdIn.readLine()
  46.  
  47. system.terminate()
  48.  
  49. }
  50. }

client/Commands.scala

  1. package cloud.pos.client
  2.  
  3. object Commands {
  4.  
  5. sealed trait POSCommand {}
  6.  
  7. case class LogOn(opr: String, passwd: String) extends POSCommand
  8. case object LogOff extends POSCommand
  9. case class SuperOn(su: String, passwd: String) extends POSCommand
  10. case object SuperOff extends POSCommand
  11. case class MemberOn(cardnum: String, passwd: String) extends POSCommand
  12. case object MemberOff extends POSCommand //remove member status for the voucher
  13. case object RefundOn extends POSCommand
  14. case object RefundOff extends POSCommand
  15. case object VoidOn extends POSCommand
  16. case object VoidOff extends POSCommand
  17. case object VoidAll extends POSCommand
  18. case object Suspend extends POSCommand
  19.  
  20. case class VoucherNum(vnum: Int) extends POSCommand
  21.  
  22. case class LogSales(salesType: Int, dpt: String, code: String, qty: Int, price: Int) extends POSCommand
  23. case object Subtotal extends POSCommand
  24. case class Discount(code: String, percent: Int) extends POSCommand
  25.  
  26. case class OfflinePay(acct: String, num: String, amount: Int) extends POSCommand //settlement 结算支付
  27. //read only command, no event process
  28. case class VCBalance(acct: String, num: String, passwd: String) extends POSCommand
  29. case class VCPay(acct: String, num: String, passwd: String, amount: Int) extends POSCommand
  30. case class AliPay(acct: String, num: String, amount: Int) extends POSCommand
  31. case class WxPay(acct: String, num: String, amount: Int) extends POSCommand
  32.  
  33. // read only command, no update event
  34. case class Plu(itemCode: String) extends POSCommand //read only
  35.  
  36. case class POSMessage(id: Long, cmd: POSCommand) {
  37. def shopId = id.toString.head.toString
  38. def posId = id.toString
  39. }
  40.  
  41. }

client/States.scala

  1. package cloud.pos.client
  2.  
  3. object States {
  4.  
  5. object TXNTYPE {
  6. val sales: Int =
  7. val refund: Int =
  8. val void: Int =
  9. val voided: Int =
  10. val voidall: Int =
  11. val subtotal: Int =
  12. val logon: Int =
  13. val supon: Int = // super user on/off
  14. val suspend: Int =
  15.  
  16. }
  17.  
  18. object SALESTYPE {
  19. val plu: Int =
  20. val dpt: Int =
  21. val cat: Int =
  22. val brd: Int =
  23. val ra: Int =
  24. val sub: Int =
  25. val ttl: Int =
  26. val dsc: Int =
  27. val crd: Int =
  28. }
  29.  
  30. case class TxnItem(
  31. txndate: String = ""
  32. ,txntime: String = ""
  33. ,opr: String = ""//工号
  34. ,num: Int = //销售单号
  35. ,seq: Int = //交易序号
  36. ,txntype: Int = TXNTYPE.sales//交易类型
  37. ,salestype: Int = SALESTYPE.plu //销售类型
  38. ,qty: Int = //交易数量
  39. ,price: Int = //单价(分)
  40. ,amount: Int = //码洋(分)
  41. ,dscamt: Int = //折扣:负值 net实洋 = amount + dscamt
  42. ,member: String = "" //会员卡号
  43. ,code: String = "" //编号(商品、账号...)
  44. ,desc: String = "" //项目名称
  45. ,dpt: String = ""
  46. ,department: String = ""
  47. ,cat: String = ""
  48. ,category: String = ""
  49. ,brd: String = ""
  50. ,brand: String = ""
  51. )
  52.  
  53. case class VchStatus( //操作状态锁留给前端维护
  54. qty: Int = ,
  55. refund: Boolean = false,
  56. void: Boolean = false)
  57.  
  58. case class VchStates(
  59. opr: String = "", //收款员
  60. jseq: BigInt = , //begin journal sequence for read-side replay
  61. num: Int = , //当前单号
  62. seq: Int = , //当前序号
  63. void: Boolean = false, //取消模式
  64. refd: Boolean = false, //退款模式
  65. due: Boolean = true, //当前余额
  66. su: String = "",
  67. mbr: String = ""
  68. )
  69.  
  70. }

client/POSClient.scala

  1. package cloud.pos.client
  2.  
  3. import akka.actor._
  4. import sdp.logging._
  5. import Responses._
  6. import Commands._
  7.  
  8. object POSClient {
  9. def props(pos: ActorRef) = Props(new POSClient(pos))
  10. }
  11. class POSClient(posHandler: ActorRef) extends Actor with LogSupport {
  12.  
  13. override def receive: Receive = {
  14. case msg @ POSMessage(_,_) => posHandler ! msg
  15. case resp: POSResponse =>
  16. log.info(s"response from server: $resp")
  17. }
  18. }

client/Responses.scala

  1. package cloud.pos.client
  2.  
  3. import States._
  4. object Responses {
  5.  
  6. object STATUS {
  7. val OK: Int =
  8. val FAIL: Int = -
  9. }
  10.  
  11. case class POSResponse (sts: Int, msg: String, voucher: VchStates, txnItems: List[TxnItem])
  12. }

client/DataAccess.scala

  1. package cloud.pos.client
  2.  
  3. import java.time.LocalDate
  4. import java.time.format.DateTimeFormatter
  5.  
  6. case class Item(
  7. brd: String
  8. ,dpt: String
  9. ,cat: String
  10. ,code: String
  11. ,name: String
  12. ,price: Int
  13.  
  14. )
  15. object Items {
  16. val apple = Item("","","","", "green apple", )
  17. val grape = Item("","","","", "red grape", )
  18. val orage = Item("","","","", "sunkist orage", )
  19. val banana = Item("","","","", "demon banana", )
  20. val pineapple = Item("","","","", "hainan pineapple", )
  21. val peach = Item("","","","", "xinjiang peach", )
  22.  
  23. val tblItems = List(apple, grape, orage, banana, pineapple, peach)
  24.  
  25. sealed trait QueryItemsResult {}
  26.  
  27. case class QueryItemsOK(items: List[Item]) extends QueryItemsResult
  28.  
  29. case class QueryItemsFail(msg: String) extends QueryItemsResult
  30.  
  31. }
  32.  
  33. object Codes {
  34. case class User(code: String, name: String, passwd: String)
  35. case class Department(code: String, name: String)
  36. case class Category(code: String, name: String)
  37. case class Brand(code: String, name: String)
  38. case class Ra(code: String, name: String)
  39. case class Account(code: String, name: String)
  40. case class Disc(code: String, best: Boolean, aggr: Boolean, group: Boolean)
  41.  
  42. val ras = List(Ra("","Delivery"),Ra("","Cooking"))
  43. val dpts = List(Department("","Fruit"),Department("","Grocery"))
  44. val cats = List(Category("","Fresh Fruit"),Category("","Dry Grocery"))
  45. val brds = List(Brand("","Sunkist"),Brand("","Demon"))
  46. val accts = List(Account("","Cash"),Account("","Value Card"), Account("", "Visa")
  47. ,Account("","Alipay"),Account("","WXPay"))
  48.  
  49. val users = List(User("","Tiger", ""),User("","John", ""),User("","Maria", ""))
  50.  
  51. def getDpt(code: String) = dpts.find(d => d.code == code)
  52. def getCat(code: String) = cats.find(d => d.code == code)
  53. def getBrd(code: String) = brds.find(b => b.code == code)
  54. def getAcct(code: String) = accts.find(a => a.code == code)
  55. def getRa(code: String) = ras.find(a => a.code == code)
  56. }
  57.  
  58. object DAO {
  59. import Items._
  60. import Codes._
  61.  
  62. def getItem(code: String): QueryItemsResult = {
  63. val optItem = tblItems.find(it => it.code == code)
  64. optItem match {
  65. case Some(item) => QueryItemsOK(List(item))
  66. case None => QueryItemsFail("Invalid item code!")
  67. }
  68. }
  69.  
  70. def validateDpt(code: String) = dpts.find(d => d.code == code)
  71. def validateCat(code: String) = cats.find(d => d.code == code)
  72. def validateBrd(code: String) = brds.find(b => b.code == code)
  73. def validateRa(code: String) = ras.find(ac => ac.code == code)
  74. def validateAcct(code: String) = accts.find(ac => ac.code == code)
  75.  
  76. def validateUser(userid: String, passwd: String) = users.find(u => (u.code == userid && u.passwd == passwd))
  77.  
  78. def lastSecOfDateStr(ldate: LocalDate): String = {
  79. ldate.format(DateTimeFormatter.ofPattern( "yyyy-MM-dd"))+" 23:59:59"
  80. }
  81.  
  82. }

logging/Log.scala

  1. package sdp.logging
  2.  
  3. import org.slf4j.Logger
  4.  
  5. /**
  6. * Logger which just wraps org.slf4j.Logger internally.
  7. *
  8. * @param logger logger
  9. */
  10. class Log(logger: Logger) {
  11.  
  12. // use var consciously to enable squeezing later
  13. var isDebugEnabled: Boolean = logger.isDebugEnabled
  14. var isInfoEnabled: Boolean = logger.isInfoEnabled
  15. var isWarnEnabled: Boolean = logger.isWarnEnabled
  16. var isErrorEnabled: Boolean = logger.isErrorEnabled
  17.  
  18. def withLevel(level: Symbol)(msg: => String, e: Throwable = null): Unit = {
  19. level match {
  20. case 'debug | 'DEBUG => debug(msg)
  21. case 'info | 'INFO => info(msg)
  22. case 'warn | 'WARN => warn(msg)
  23. case 'error | 'ERROR => error(msg)
  24. case _ => // nothing to do
  25. }
  26. }
  27.  
  28. def debug(msg: => String): Unit = {
  29. if (isDebugEnabled && logger.isDebugEnabled) {
  30. logger.debug(msg)
  31. }
  32. }
  33.  
  34. def debug(msg: => String, e: Throwable): Unit = {
  35. if (isDebugEnabled && logger.isDebugEnabled) {
  36. logger.debug(msg, e)
  37. }
  38. }
  39.  
  40. def info(msg: => String): Unit = {
  41. if (isInfoEnabled && logger.isInfoEnabled) {
  42. logger.info(msg)
  43. }
  44. }
  45.  
  46. def info(msg: => String, e: Throwable): Unit = {
  47. if (isInfoEnabled && logger.isInfoEnabled) {
  48. logger.info(msg, e)
  49. }
  50. }
  51.  
  52. def warn(msg: => String): Unit = {
  53. if (isWarnEnabled && logger.isWarnEnabled) {
  54. logger.warn(msg)
  55. }
  56. }
  57.  
  58. def warn(msg: => String, e: Throwable): Unit = {
  59. if (isWarnEnabled && logger.isWarnEnabled) {
  60. logger.warn(msg, e)
  61. }
  62. }
  63.  
  64. def error(msg: => String): Unit = {
  65. if (isErrorEnabled && logger.isErrorEnabled) {
  66. logger.error(msg)
  67. }
  68. }
  69.  
  70. def error(msg: => String, e: Throwable): Unit = {
  71. if (isErrorEnabled && logger.isErrorEnabled) {
  72. logger.error(msg, e)
  73. }
  74. }
  75.  
  76. }

logging/LogSupport.scala

  1. package sdp.logging
  2.  
  3. import org.slf4j.LoggerFactory
  4.  
  5. trait LogSupport {
  6.  
  7. /**
  8. * Logger
  9. */
  10. protected val log = new Log(LoggerFactory.getLogger(this.getClass))
  11.  
  12. }

logging/ClusterMonitor.scala

  1. package sdp.cluster.monitor
  2.  
  3. import akka.actor._
  4. import akka.cluster.ClusterEvent._
  5. import akka.cluster._
  6. import sdp.logging.LogSupport
  7.  
  8. object ClusterMonitor {
  9. def props = Props(new ClusterMonitor())
  10. }
  11.  
  12. class ClusterMonitor extends Actor with LogSupport {
  13. val cluster = Cluster(context.system)
  14. override def preStart(): Unit = {
  15. cluster.subscribe(self,initialStateMode = InitialStateAsEvents
  16. ,classOf[MemberEvent],classOf[UnreachableMember]) //订阅集群状态转换信息
  17. super.preStart()
  18. }
  19.  
  20. override def postStop(): Unit = {
  21. cluster.unsubscribe(self) //取消订阅
  22. super.postStop()
  23. }
  24.  
  25. override def receive: Receive = {
  26. case MemberJoined(member) =>
  27. log.info(s"Member is Joining: {${member.address}}")
  28. case MemberUp(member) =>
  29. log.info(s"Member is Up: {${member.address}}")
  30. case MemberLeft(member) =>
  31. log.info(s"Member is Leaving: {${member.address}}")
  32. case MemberExited(member) =>
  33. log.info(s"Member is Exiting: {${member.address}}")
  34. case MemberRemoved(member, previousStatus) =>
  35. log.info(
  36. s"Member is Removed: {${member.address}} after {${previousStatus}")
  37. case UnreachableMember(member) =>
  38. log.info(s"Member detected as unreachable: {${member.address}}")
  39. cluster.down(member.address) //手工驱除,不用auto-down
  40. case _: MemberEvent => // ignore
  41. }
  42.  
  43. }

Akka-CQRS(5)- CQRS Writer Actor 部署和测试的更多相关文章

  1. 大数据学习day17------第三阶段-----scala05------1.Akka RPC通信案例改造和部署在多台机器上 2. 柯里化方法 3. 隐式转换 4 scala的泛型

    1.Akka RPC通信案例改造和部署在多台机器上  1.1 Akka RPC通信案例的改造(主要是把一些参数不写是) Master package com._51doit.akka.rpc impo ...

  2. 以Akka为示例,介绍Actor模型

    许多开发者在创建和维护多线程应用程序时经历过各种各样的问题,他们希望能在一个更高层次的抽象上进行工作,以避免直接和线程与锁打交道.为了帮助这些开发者,Arun Manivannan编写了一系列的博客帖 ...

  3. Akka(2):Actor生命周期管理 - 监控和监视

    在开始讨论Akka中对Actor的生命周期管理前,我们先探讨一下所谓的Actor编程模式.对比起我们习惯的行令式(imperative)编程模式,Actor编程模式更接近现实中的应用场景和功能测试模式 ...

  4. 集群部署及测试SolrCloud-5

    SolrCloud-5.2.1 集群部署及测试   一. 说明 Solr5内置了Jetty服务,所以不用安装部署到Tomcat了,网上部署Tomcat的资料太泛滥了. 部署前的准备工作: 1. 将各主 ...

  5. slurm-16.05.3任务调度系统部署与测试(1)

      1.概述2.同步节点时间3.下载并解压文件4.编译安装munge-0.5.125.配置munge6.编译安装slurm-16.05.37.配置slurm8.配置MySQL数据库环境9.启动slur ...

  6. 消息中间件kafka+zookeeper集群部署、测试与应用

    业务系统中,通常会遇到这些场景:A系统向B系统主动推送一个处理请求:A系统向B系统发送一个业务处理请求,因为某些原因(断电.宕机..),B业务系统挂机了,A系统发起的请求处理失败:前端应用并发量过大, ...

  7. redis3.0集群部署和测试

    redis3.0集群部署和测试 环境介绍 两台Centos7的虚拟机模拟6个节点,A台3个master节点,B台3个slave节点A地址:172.16.81.140B地址:172.16.81.141r ...

  8. LDAP-openldap服务部署和测试(YUM安装)

    1. 概述2. 服务端部署过程2.1 软件包说明2.2 部署过程2.3 配置过程3. 测试4. 生成LDIF格式文件4.1 安装migrationtools工具4.2 用migrationtools生 ...

  9. Nagios图像绘制插件PNP4Nagios部署和测试

    注:本篇博客Nagios版本Nagios-3.5.1 1. 概述2. 关于PNP4Nagios3. 部署PNP4Nagios3.1 下载PNP4Nagios3.2 编译安装3.3 目录文件说明4. 配 ...

随机推荐

  1. [leetcode]61. Rotate List旋转链表

    Given a linked list, rotate the list to the right by k places, where k is non-negative. Example 1: I ...

  2. iOS相关的ARM汇编

    一.iOS汇编1.真机:arm64汇编寄存器指令 堆栈2.模拟器:x86汇编 二.lldb (lldb)register read x0 (lldb)register read w0 (lldb)re ...

  3. 纯css3单选框/复选框美化样式代码

    纯CSS 单/复选框 美化请选择iPhone 型号 iPhone 6s iPhone 6s Plus iPhone 7 iPhone 7 Plus   选择兴趣爱好 女 绘画 摄影 骑行   原理在这 ...

  4. 关于Android UI 优化

    之前项目为了同时兼容tv和手机端的UI,使用了百分比布局来动态计算控件的宽高,这种适配方案只关心屏幕的宽高(分辨率),与屏幕的像素密度无关. 在新的项目里也使用了这种方案.但是由于项目的运行硬件计算能 ...

  5. The Swap

    源程序 swap.cpp* 输入文件 swap.in 输出文件 swap.out 时间限制 1s 空间限制 256MB [问题描述] Alice 得到了一个整数, 她将其视作长度为 n 的字符串 S. ...

  6. Python_格式化字符

    %% 百分号标记 #就是输出一个%%c 字符及其ASCII码%s 字符串 %r 是不管是什么打印出来%d 有符号整数(十进制)%u 无符号整数(十进制)%o 无符号整数(八进制)%x 无符号整数(十六 ...

  7. 《修炼之道:.NET开发要点精讲》读书笔记(一)

    CLR 公共语言运行库 没有CLR的存在,就不能讲该中间件转换成对应操作系统中的机器指令. 程序集是非完全编译的产物,它兼备了源代码和本地代码的特性,是一种介于源代码和本地代码之间的独立存在的一种数据 ...

  8. vue中使用axios

    1.结合vue-axios使用 vue-axios是按照vue插件的方式去写的,那么结合vue-axios就可以使用Vue.use()这个方法import axios from 'axios' imp ...

  9. Cordova配置与WebApp混合开发环境配置

    好久都没来更新随笔了,这阵子比较忙,不过还是在不断的学习当中,今天给大家分享一下Cordova的配置与搭建WebApp混合开发环境的配置. 准备好了吗?让我们一步步来咯!!! 1.配置JDK环境 用的 ...

  10. 适用于Mac 的自动补丁管理软件

    适用于Mac 的自动补丁管理软件 ManageEngine Desktop Central 的功能越来越神奇.系统管理员现在可以使用 Desktop Central 管理异构网络.即使是最复杂的任务, ...