Создание кластера сервисов в Golang с использованием HashiCorp Memberlist
Memberlist – это пакет Golang от Hashicorp для реализации нашей собственной кластеризации для любой службы Golang с использованием протокола Gossip.
Вот ссылка на пакет: https://github.com/hashicorp/memberlist
Этот пакет позволяет очень легко реализовать в Golang кластер сервисных узлов. Сервисные узлы могут быть любой внутренней службой, работающей на сервере, наиболее распространенным примером может быть pub/sub сервер.
Ниже описывается краткое объяснение того, как вы можете реализовать свой собственный кластер услуг.
Сначала импортируйте пакет в свой сервис с помощью оператора импорта Go:
import (“github.com/hashicorp/memberlist”)
В каждом узле вы инициализируете объект списка участников с помощью объекта конфигурации, как указано ниже:
config := memberlist.DefaultLocalConfig()
config.BindPort = 8080 //The port is used for both UDP and TCP gossip
config.BindAddr = “127.0.0.1” //here you need to provide the IP on which this node will be listening on for the gossip protocol from other nodes.
Если вы хотите предоставить свой пользовательский транспорт, чтобы предоставить настраиваемый код о том, как ваш узел будет взаимодействовать с другими узлами, вы также можете сделать это, установив поле Транспорт для объекта конфигурации. Ниже приводится официальная документация пакета Memberlist.
Transport Transport
// Transport is a hook for providing custom code to communicate with
// other nodes. If this is left nil, then memberlist will by default
// make a NetTransport using BindAddr and BindPort from this structure.
config.Name = “node1” // name of this node, this is very important to provide the node name
list, err := memberlist.Create(config) // create the memberlist object with the config object we just created above. This list object will be used to get the list of members in your cluster and push any data that we need to distribute to other nodes.
if err != nil { //handle the error if there is any error in initializing the cluster nodes
fmt.Errorf(“Error initializing Cluster node with error %v”, err)
return nil
}
list.LocalNode().Meta = []byte(“6547”)
Meta – это тег для установки любой произвольной информации о локальном узле. Здесь я установил порт, на котором этот узел может прослушивать отправленные данные от других узлов. Это очень важно, поскольку, когда другие узлы перебирают список элементов, они могут использовать тег Meta, чтобы определить, на какой порт передавать данные.
После создания списка участников все подчиненные узлы должны присоединиться к кластеру.
var node []string{“0.0.0.0:8080”} // need to provide at least one known cluster node. Usually provide the IP:BindPort of the master node. The master node need not join anyone. All the other slave nodes can join the master node using the code given below.
list.Join(node )
Теперь вам нужно прослушивать данные от других узлов и событие, когда текущий узел покидает кластер.
go ListenToNodesData (list) // Прослушивание данные от других узлов в отдельной процедуре go.
go ListenNodeLeave (list) // Прослушивание событие выхода этого узла из кластера.
go PushDataToOtherNodes (list) // Эта функция будет иметь логику в отношении того, что вы хотите делать с данными, которые вы получаете от клиента через Интернет. Вы захотите отправить его всем другим узлам, чтобы они могли решить, что они хотят делать с этими данными, исходя из своей индивидуальной логики.
Ниже я приведу примерную реализацию этих двух функций, но она будет меняться в зависимости от логики вашего сервиса.
Реализация ListenNodeLeave:
func ListenNodeLeave(ml *memberlist.Memberlist) {
// Create a channel to listen for exit signals
stop := make(chan os.Signal, 1)
// Register the signals we want to be notified, these 3 indicate exit
// signals, similar to CTRL+C
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
<-stop
// Leave the cluster with a 5 second timeout. If leaving takes more than 5
// seconds we return.
if err := ml.Leave(time.Second * 5); err != nil {
panic(err)
}
os.Exit(1)
}
Реализация ListenToNodesData:
func ListenToNodesData(ml *memberlist.Memberlist) {
l, err := net.Listen(“tcp”, ml.LocalNode().Addr.String()+”:”+”6547”) // this is the same port which you provided in the Meta tag as explained above.
if err != nil {
log.Fatal(“Cannot start the cluster node:”, err)
}
defer l.Close()
log.Println(“Running local cluster node at:”, 6547)
for {
c, err := l.Accept() // wait for connection from other nodes and this connection the other nodes will be pushing the data
if err != nil {
continue
}
HandleData(c) // handle the data which you received from other nodes. You can write your own logic as per your service implementation.
}
Реализация PushDataToOtherNodes:
func PushDataToOtherNodes(list){
//write your logic here to get the data from your client either over the internet //or from a different backend service may be.
//let say we got the data in the data variable
var data []byte(“Data to be sent to other nodes in my cluster”)
for _, m := range list.Members() { //iterate over the member list and push data to other cluster nodes.
if m == ml.LocalNode() { //its the localnode so we don’t want to use this data
continue
}
var conn net.Conn
var err error
log.Println(“Dialing:”, m.Address())
port := string(m.Meta) // taking the port of the other node in our cluster
conn, err = net.Dial(“tcp”, m.Addr.String()+”:”+port)
if err != nil {
log.Println(“could not connect to server: “, err)
continue
}
_, err = conn.Write(data) //send the data we received from client to other nodes
if err != nil {
log.Println(“Cluster down with address:”, m.Address())
}
}
}