// create eureka service registries in each zone func CreateEureka() { // setup name service and cross zone replication links znames := archaius.Conf.ZoneNames Create("eureka", EurekaPkg, archaius.Conf.Regions, len(archaius.Conf.ZoneNames)) for n, ch := range eurekachan { var n1, n2 string switch names.Zone(n) { case znames[0]: n1 = znames[1] n2 = znames[2] case znames[1]: n1 = znames[0] n2 = znames[2] case znames[2]: n1 = znames[0] n2 = znames[1] } for nn, cch := range eurekachan { if names.Region(nn) == names.Region(n) && (names.Zone(nn) == n1 || names.Zone(nn) == n2) { //log.Println("Eureka cross connect from: " + n + " to " + nn) gotocol.Send(ch, gotocol.Message{gotocol.NameDrop, cch, time.Now(), gotocol.NilContext, nn}) } } } }
func Lookup(name string) string { ip := mapped[name] if ip != "" { return ip } // find indexes for matching zone and region in the config r := names.Region(name) ri := 0 z := names.Zone(name) zi := 0 for i, rr := range archaius.Conf.RegionNames { if rr == r { ri = i break } } for i, zr := range archaius.Conf.ZoneNames { if zr == z { zi = i break } } // increment first to avoid IP 0.0 and get the node counter in the region/zone allocated[ri][zi]++ node := allocated[ri][zi] // format as xxx.xxx.xxx.xxx addr := fmt.Sprintf("%v%v.%v", archaius.Conf.IPRanges[ri][zi], node/256, node%256) mapped[name] = addr return addr }
// NameDrop updates local buddy list func NameDrop(dependencies *map[string]time.Time, router *ribbon.Router, msg gotocol.Message, name string, listener chan gotocol.Message, eureka map[string]chan gotocol.Message, crosszone ...bool) { if msg.ResponseChan == nil { // dependency by service name, needs to be looked up in eureka (*dependencies)[msg.Intention] = msg.Sent // remember it for later for _, ch := range eureka { //log.Println(name + " looking up " + msg.Intention) gotocol.Send(ch, gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NilContext, msg.Intention}) } } else { // update dependency with full name and listener channel microservice := msg.Intention // message body is buddy name if len(crosszone) > 0 || names.Zone(name) == names.Zone(microservice) { if microservice != name && router.Named(microservice) == nil { // don't talk to myself or record duplicates // remember how to talk to this buddy router.Add(microservice, msg.ResponseChan, msg.Sent) // message channel is buddy's listener (*dependencies)[names.Service(microservice)] = msg.Sent for _, ch := range eureka { // tell just one of the service registries I have a new buddy to talk to so it doesn't get logged more than once gotocol.Send(ch, gotocol.Message{gotocol.Inform, listener, time.Now(), gotocol.NilContext, name + " " + microservice}) return } } } } }
// Start a node using the named package, and connect it to any dependencies func StartNode(name string, dependencies ...string) { if names.Package(name) == EurekaPkg { eurekachan[name] = make(chan gotocol.Message, archaius.Conf.Population/len(archaius.Conf.ZoneNames)) // buffer sized to a zone go eureka.Start(eurekachan[name], name) return } else { noodles[name] = make(chan gotocol.Message) } // start the service and tell it it's name switch names.Package(name) { case PiratePkg: go pirate.Start(noodles[name]) case ElbPkg: go elb.Start(noodles[name]) case DenominatorPkg: go denominator.Start(noodles[name]) case ZuulPkg: go zuul.Start(noodles[name]) case KaryonPkg: go karyon.Start(noodles[name]) case MonolithPkg: go monolith.Start(noodles[name]) case StaashPkg: go staash.Start(noodles[name]) case RiakPkg: fallthrough // fake Riak using priamCassandra case PriamCassandraPkg: go priamCassandra.Start(noodles[name]) case CachePkg: fallthrough // fake memcache using store case VolumePkg: fallthrough // fake disk volume using store case StorePkg: go store.Start(noodles[name]) default: log.Fatal("asgard: unknown package: " + names.Package(name)) } noodles[name] <- gotocol.Message{gotocol.Hello, listener, time.Now(), gotocol.NilContext, name} // there is a eureka service registry in each zone, so in-zone services just get to talk to their local registry // elb are cross zone, so need to see all registries in a region // denominator are cross region so need to see all registries globally // priamCassandra depends explicitly on eureka for cross region clusters crossregion := false for _, d := range dependencies { if d == "eureka" { crossregion = true } } for n, ch := range eurekachan { if names.Region(name) == "*" || crossregion { // need to know every eureka in all zones and regions gotocol.Send(noodles[name], gotocol.Message{gotocol.Inform, ch, time.Now(), gotocol.NilContext, n}) } else { if names.Zone(name) == "*" && names.Region(name) == names.Region(n) { // need every eureka in my region gotocol.Send(noodles[name], gotocol.Message{gotocol.Inform, ch, time.Now(), gotocol.NilContext, n}) } else { if names.RegionZone(name) == names.RegionZone(n) { // just the eureka in this specific zone gotocol.Send(noodles[name], gotocol.Message{gotocol.Inform, ch, time.Now(), gotocol.NilContext, n}) } } } } //log.Println(dependencies) // pass on symbolic dependencies without channels that will be looked up in Eureka later for _, dep := range dependencies { if dep != "" && dep != "eureka" { // ignore special case of eureka in dependency list //log.Println(name + " depends on " + dep) gotocol.Send(noodles[name], gotocol.Message{gotocol.NameDrop, nil, time.Now(), gotocol.NilContext, dep}) } } }
// WriteNode writes the node to a file given a space separated name and service type func WriteNode(nameService string, t time.Time) { if Enabled == false { return } var node, pack string fmt.Sscanf(nameService, "%s%s", &node, &pack) // space delimited tstamp := t.Format(time.RFC3339Nano) // node id should be unique and package indicates service type nodestmt, err := db.Prepare(fmt.Sprintf("CREATE (:%v:%v:%v {name:{0}, node:{1}, timestamp:{2}, ip:{3}, region:{4}, zone:{5}})", archaius.Conf.Arch+ss, pack, names.Service(node))) if err != nil { log.Fatal(err) } _, err = nodestmt.Exec(names.Instance(node), node, tstamp, dhcp.Lookup(node), names.Region(node), names.Zone(node)) if err != nil { log.Fatal(err) } nodestmt.Close() }
// Start priamCassandra, all configuration and state is sent via messages func Start(listener chan gotocol.Message) { // remember the channel to talk to microservices microservices := ribbon.MakeRouter() // track the hash values owned by each node in the ring var ring ByToken dependencies := make(map[string]time.Time) // dependent services and time last updated store := make(map[string]string, 4) // key value store store["why?"] = "because..." var parent chan gotocol.Message // remember how to talk back to creator var name string // remember my name eureka := make(map[string]chan gotocol.Message, len(archaius.Conf.ZoneNames)*archaius.Conf.Regions) // service registry per zone and region hist := collect.NewHist("") ep, _ := time.ParseDuration(archaius.Conf.EurekaPoll) eurekaTicker := time.NewTicker(ep) for { select { case msg := <-listener: flow.Instrument(msg, name, hist) switch msg.Imposition { case gotocol.Hello: if name == "" { // if I don't have a name yet remember what I've been named parent = msg.ResponseChan // remember how to talk to my namer name = msg.Intention // message body is my name hist = collect.NewHist(name) } case gotocol.Inform: eureka[msg.Intention] = handlers.Inform(msg, name, listener) case gotocol.NameDrop: // cross zone = true handlers.NameDrop(&dependencies, microservices, msg, name, listener, eureka, true) case gotocol.Forget: // forget a buddy handlers.Forget(&dependencies, microservices, msg) case gotocol.Chat: // Gossip setup notification of hash values for nodes, cass1:123,cass2:456 ring = RingConfig(msg.Intention) case gotocol.GetRequest: // see if the data is stored on this node i := ring.Find(ringHash(msg.Intention)) //log.Printf("%v: %v %v\n", name, i, ringHash(msg.Intention)) if len(ring) == 0 || ring[i].name == name { // ring is setup so only respond if this is the right place // return any stored value for this key (Cassandra READ.ONE behavior) outmsg := gotocol.Message{gotocol.GetResponse, listener, time.Now(), msg.Ctx, store[msg.Intention]} flow.AnnotateSend(outmsg, name) outmsg.GoSend(msg.ResponseChan) } else { // forward the message to the right place, but don't change the ResponseChan or span outmsg := gotocol.Message{gotocol.GetRequest, msg.ResponseChan, time.Now(), msg.Ctx.AddSpan(), msg.Intention} flow.AnnotateSend(outmsg, name) outmsg.GoSend(microservices.Named(ring[i].name)) } case gotocol.GetResponse: // return path from a request, send payload back up, not used by priamCassandra currently case gotocol.Put: // set a key value pair and replicate globally var key, value string fmt.Sscanf(msg.Intention, "%s%s", &key, &value) if key != "" && value != "" { i := ring.Find(ringHash(key)) if len(ring) == 0 || ring[i].name == name { // ring is setup so only store if this is the right place store[key] = value } else { // forward the message to the right place, but don't change the ResponseChan or context parent outmsg := gotocol.Message{gotocol.Put, msg.ResponseChan, time.Now(), msg.Ctx.AddSpan(), msg.Intention} flow.AnnotateSend(outmsg, name) outmsg.GoSend(microservices.Named(ring[i].name)) } // duplicate the request on to priamCassandra nodes in each zone and one in each region for _, z := range names.OtherZones(name, archaius.Conf.ZoneNames) { // replicate request for _, n := range microservices.Names() { if names.Region(n) == names.Region(name) && names.Zone(n) == z { outmsg := gotocol.Message{gotocol.Replicate, listener, time.Now(), msg.Ctx.NewParent(), msg.Intention} flow.AnnotateSend(outmsg, name) outmsg.GoSend(microservices.Named(n)) break // only need to send it to one node in each zone } } } for _, r := range names.OtherRegions(name, archaius.Conf.RegionNames[0:archaius.Conf.Regions]) { for _, n := range microservices.Names() { if names.Region(n) == r { outmsg := gotocol.Message{gotocol.Replicate, listener, time.Now(), msg.Ctx.NewParent(), msg.Intention} flow.AnnotateSend(outmsg, name) outmsg.GoSend(microservices.Named(n)) break // only need to send it to one node in each region } } } } case gotocol.Replicate: // Replicate is only used between priamCassandra nodes // end point for a request var key, value string fmt.Sscanf(msg.Intention, "%s%s", &key, &value) // log.Printf("priamCassandra: %v:%v", key, value) if key != "" && value != "" { i := ring.Find(ringHash(key)) if len(ring) == 0 || ring[i].name == name { // ring is setup so only store if this is the right place store[key] = value } else { // forward the message to the right place, but don't change the ResponseChan outmsg := gotocol.Message{gotocol.Replicate, msg.ResponseChan, time.Now(), msg.Ctx, msg.Intention} flow.AnnotateSend(outmsg, name) outmsg.GoSend(microservices.Named(ring[i].name)) } } // name looks like: netflixoss.us-east-1.zoneC.cassTurtle.priamCassandra.cassTurtle11 myregion := names.Region(name) //log.Printf("%v: %v\n", name, myregion) // find if this was a cross region Replicate // find the name matching incoming request channel to see where its coming from in := microservices.NameChan(msg.ResponseChan) if in != "" && myregion != names.Region(in) { // Replicate from out of region needs to be Replicated once only to other zones in this Region for _, z := range names.OtherZones(name, archaius.Conf.ZoneNames) { // replicate request for _, n := range microservices.Names() { if names.Region(n) == myregion && names.Zone(n) == z { outmsg := gotocol.Message{gotocol.Replicate, listener, time.Now(), msg.Ctx.NewParent(), msg.Intention} flow.AnnotateSend(outmsg, name) outmsg.GoSend(microservices.Named(n)) break // only need to send it to one node in each zone } } } break } case gotocol.Goodbye: gotocol.Message{gotocol.Goodbye, nil, time.Now(), gotocol.NilContext, name}.GoSend(parent) return } case <-eurekaTicker.C: // check to see if any new dependencies have appeared for dep, _ := range dependencies { for _, ch := range eureka { ch <- gotocol.Message{gotocol.GetRequest, listener, time.Now(), gotocol.NilContext, dep} } } } } }