|
| 1 | +/** |
| 2 | + * Copyright (C) 2015 Roland Kuhn <http://rolandkuhn.com> |
| 3 | + */ |
| 4 | +package com.reactivedesignpatterns.chapter16 |
| 5 | + |
| 6 | +import com.typesafe.config.ConfigFactory |
| 7 | +import akka.actor._ |
| 8 | +import akka.cluster._ |
| 9 | +import akka.cluster.sharding._ |
| 10 | +import java.net.URI |
| 11 | +import java.util.UUID |
| 12 | + |
| 13 | +object ShardSupport { |
| 14 | + /* |
| 15 | + * use the shoppingCart reference as the sharding key; the partial function |
| 16 | + * must return both the key and the message to be forwarded, and if it does |
| 17 | + * not match then the message is dropped |
| 18 | + */ |
| 19 | + val extractEntityId: ShardRegion.ExtractEntityId = { |
| 20 | + case mc @ ManagerCommand(cmd, _, _) => cmd.shoppingCart.id.toString -> mc |
| 21 | + case mc @ ManagerQuery(query, _, _) => query.shoppingCart.id.toString -> mc |
| 22 | + } |
| 23 | + |
| 24 | + /* |
| 25 | + * allocate shoppingCarts into 256 shards based on the low 8 bits of their |
| 26 | + * ID’s hash; this is a total function that must be defined for all messages |
| 27 | + * that are forwarded |
| 28 | + */ |
| 29 | + val extractShardId: ShardRegion.ExtractShardId = { |
| 30 | + case ManagerCommand(cmd, _, _) => toHex(cmd.shoppingCart.id.hashCode & 255) |
| 31 | + case ManagerQuery(query, _, _) => toHex(query.shoppingCart.id.hashCode & 255) |
| 32 | + } |
| 33 | + private def toHex(b: Int) = new java.lang.StringBuilder(2).append(hexDigits(b >> 4)).append(hexDigits(b & 15)).toString |
| 34 | + private val hexDigits = "0123456789ABCDEF" |
| 35 | + |
| 36 | + val RegionName = "ShoppingCart" |
| 37 | +} |
| 38 | + |
| 39 | +object ShardingExample extends App { |
| 40 | + val clusterConfig = ConfigFactory.parseString(""" |
| 41 | +akka.loglevel = INFO |
| 42 | +akka.actor.provider = "akka.cluster.ClusterActorRefProvider" |
| 43 | +akka.actor.warn-about-java-serializer-usage = off |
| 44 | +akka.cluster.min-nr-of-members = 2 |
| 45 | +akka.remote.netty.tcp { |
| 46 | + hostname = localhost |
| 47 | + port = 0 |
| 48 | +} |
| 49 | +akka.cluster.sharding.state-store-mode = ddata |
| 50 | +""") |
| 51 | + val node1Config = ConfigFactory.parseString("akka.remote.netty.tcp.port = 2552") |
| 52 | + |
| 53 | + val sys1 = ActorSystem("ShardingExample", node1Config.withFallback(clusterConfig)) |
| 54 | + val seed = Cluster(sys1).selfAddress |
| 55 | + |
| 56 | + def startNode(sys: ActorSystem): Unit = { |
| 57 | + Cluster(sys).join(seed) |
| 58 | + ClusterSharding(sys).start( |
| 59 | + typeName = ShardSupport.RegionName, |
| 60 | + entityProps = Props(new Manager), |
| 61 | + settings = ClusterShardingSettings(sys1), |
| 62 | + extractEntityId = ShardSupport.extractEntityId, |
| 63 | + extractShardId = ShardSupport.extractShardId) |
| 64 | + } |
| 65 | + |
| 66 | + startNode(sys1) |
| 67 | + |
| 68 | + val sys2 = ActorSystem("ShardingExample", clusterConfig) |
| 69 | + startNode(sys2) |
| 70 | + |
| 71 | + /* |
| 72 | + * From this point onward we can talk to the sharded shopping carts via |
| 73 | + * the shard region which acts as a local mediator that will send the |
| 74 | + * commands to the right node. |
| 75 | + */ |
| 76 | + val manager = ClusterSharding(sys1).shardRegion(ShardSupport.RegionName) |
| 77 | + |
| 78 | + def mkURI(): URI = URI.create(UUID.randomUUID().toString) |
| 79 | + |
| 80 | + val customer = CustomerRef(mkURI()) |
| 81 | + val item1, item2 = ItemRef(mkURI()) |
| 82 | + val shoppingCart1, shoppingCart2 = ShoppingCartRef(mkURI()) |
| 83 | + |
| 84 | + Cluster(sys1).registerOnMemberUp( |
| 85 | + sys1.actorOf(Props(new Actor with ActorLogging { |
| 86 | + manager ! ManagerCommand(SetOwner(shoppingCart1, customer), 0, self) |
| 87 | + manager ! ManagerCommand(AddItem(shoppingCart1, item1, 5), 1, self) |
| 88 | + manager ! ManagerCommand(AddItem(shoppingCart1, item1, -3), 2, self) |
| 89 | + manager ! ManagerCommand(AddItem(shoppingCart1, item2, 6), 3, self) |
| 90 | + manager ! ManagerCommand(RemoveItem(shoppingCart1, item1, 3), 4, self) |
| 91 | + manager ! ManagerQuery(GetItems(shoppingCart1), 5, self) |
| 92 | + |
| 93 | + def receive = { |
| 94 | + case ManagerEvent(id, event) => log.info("success ({}): {}", id, event) |
| 95 | + case ManagerRejection(id, msg) => log.warning("rejected ({}): {}", id, msg) |
| 96 | + case ManagerResult(id, result) => |
| 97 | + log.info("result ({}): {}", id, result) |
| 98 | + |
| 99 | + manager ! ManagerCommand(SetOwner(shoppingCart2, customer), 10, self) |
| 100 | + manager ! ManagerCommand(AddItem(shoppingCart2, item2, 15), 11, self) |
| 101 | + manager ! ManagerCommand(AddItem(shoppingCart2, item2, -3), 12, self) |
| 102 | + manager ! ManagerCommand(AddItem(shoppingCart2, item1, 60), 13, self) |
| 103 | + manager ! ManagerCommand(RemoveItem(shoppingCart2, item2, 3), 14, self) |
| 104 | + manager ! ManagerQuery(GetItems(shoppingCart2), 15, self) |
| 105 | + |
| 106 | + context.become(second) |
| 107 | + } |
| 108 | + def second: Receive = { |
| 109 | + case ManagerEvent(id, event) => log.info("success ({}): {}", id, event) |
| 110 | + case ManagerRejection(id, msg) => log.warning("rejected ({}): {}", id, msg) |
| 111 | + case ManagerResult(id, result) => |
| 112 | + log.info("result ({}): {}", id, result) |
| 113 | + sys1.terminate() |
| 114 | + sys2.terminate() |
| 115 | + } |
| 116 | + }), "client")) |
| 117 | +} |
0 commit comments