数据服务的分布式模型
分布式系统要求个节点分布在网络上,并通过消息传递合作来完成一个共同的目标。
分布式系统的三大关键特征:
- 节点之间并发工作
- 没有全局锁
- 某个节点发生错误不影响其它节点
模型
接口服务层提供对外的REST接口,而数据服务提供对接口服务的REST接口。
接口服务接收用户请求,数据服务接收接口服务的请求,接口服务作为HTTP客户端向数据服务请求数据。
1.每一个数据服务节点都需要向所有的接口服务通知自身存在,这里数据服务需要周期性向接口服务发送心跳包ip:port,而接口服务需要处理心跳包,维持可用的数据服务列表。
注意:心跳包,数据服务向apiServers此交换器发送信息,每个接口服务启动自动绑定此交换器,就可以接收到此交换器的信息。
2.接口服务接收到用户请求后,需要定位数据被保存在哪个数据服务节点上。
注意:定位,接口服务向dataServers此交换器发送信息,每个数据服务启动自动绑定此交换器,就可以结束此交换器的信息。
所以这里接口服务与数据服务是使用Rabbitmq消息队列通信。
数据服务data server
package main import ( "RabbitMQ" "fmt" "io" "log" "net/http" "os" "strconv" "strings" "time" ) //Rabbitmq使用简介 //向交换机发送消息的步骤 //1.q := rabbitMQ.New("amqp://user:password@ip:port/","队列名") //2.defer q.Close() //3.q.Publish("交换机名","消息内容","") //从exchange等待消息 //q := rabbitMQ.New("amqp://user:password@ip:port/","队列名") //defer q.Close() //q.Bind("交换机名","") //c := q.Consume() //for msg := range c { ////处理msg,msg为接收信息 //} //向某消息队列发送消息 //q := rabbitMQ.New("amqp://user:password@ip:port/","队列名") //defer q.Close() //q.Send("队列名","Hello World!") //接收消息队列的消息 //q := rabbitMQ.New("amqp://user:password@ip:port/","队列名") //defer q.Close() //c := q.Consume() //msg := <-c //注意 //数据封装和具体示例:https://www.cnblogs.com/-wenli/p/12203202.html //2.发送消息,只需要连接到rabbitmq,就可以直接向某消息队列发送。 //3.接收消息,接收消息不管是从消息队列还是exchange,本质都是从信道中接收,信道默认为阻塞信道,玩过go语言的就很懂了! //数据服务: //1.每一个数据服务都必须向api exchange发送心跳信息ip:port。 //2.每一个数据服务启动时自动绑定dataSevers exchange,接受data exchange的定位消息,如果有资源,则返回自身地址ip:port,如果没有则不回复 func main() { //心跳服务 go StartHeartbeat() //资源定位服务 go StartLocate() http.HandleFunc("/objects/",Handler) println("server...") log.Fatal(http.ListenAndServe("127.0.0.1:8006", nil)) } func Handler(w http.ResponseWriter, r *http.Request){ m := r.Method if m == http.MethodPut{ Put(w,r) return } if m == http.MethodGet{ Get(w,r) return } w.WriteHeader(http.StatusMethodNotAllowed) } func Put(w http.ResponseWriter,r *http.Request){ //do something } func Get(w http.ResponseWriter,r *http.Request){ //do something } //心跳服务 //1.向api exchange发送心跳信息 func StartHeartbeat(){ q := rabbitMQ.New("rabbitmq链接","") defer q.Close() for{ q.Publish("apiServers","127.0.0.1:8006","") time.Sleep(5 * time.Second) } } //资源定位服务 //2.接受data exchange的定位消息,如果有资源,则返回自身地址ip:port,如果没有则不回复 func StartLocate(){ q := rabbitMQ.New("rabbitmq链接","") defer q.Close() q.Bind("dataServers","") c := q.Consume() for msg := range c { object, e := strconv.Unquote(string(msg.Body)) if e != nil { panic(e) } if Locate("D:/Go/test/"+ "/objects/" + object){ fmt.Printf("%s is exist ",object) q.Send(msg.ReplyTo,"127.0.0.1:8006") }else{ fmt.Printf("%s is not exist ",object) q.Send(msg.ReplyTo,"") } } } func Locate(name string)bool{ _,err := os.Stat(name) return !os.IsNotExist(err) }
接口服务 api Server
package main import ( "Heartbeat" "Locate" "fmt" "log" "net/http" ) //接口服务 //1.每一个接口服务启动时自动绑定apiServers exchange,接收每一个数据服务的心跳包。 //2.当有客户端请求到某个接口服务时,此接口向dataServers exchange发送数据定位消息。 //服务端编写的业务逻辑处理程序 //hander函数: 具有func(w http.ResponseWriter, r *http.Requests)签名的函数 func myHandler(w http.ResponseWriter, r *http.Request) { fmt.Println(r.RemoteAddr, "连接成功") //r.RemoteAddr远程网络地址 fmt.Println("method = ", r.Method) //请求方法 fmt.Println("url = ", r.URL.Path) fmt.Println("header = ", r.Header) fmt.Println("body = ", r.Body) w.Write([]byte("hello go")) //给客户端恢复的数据 } func main() { //心跳服务 //1.接收数据服务节点的心跳消息 go Heartbeat.ListenHeartbeat() //2.服务端测试函数 http.HandleFunc("/go", myHandler) //3.发送定位消息并接收定位消息 http.HandleFunc("/locate/",Locate.Handler) println("server...") log.Fatal(http.ListenAndServe("127.0.0.1:8005", nil)) }
接口服务的心跳包处理heartbeat
package Heartbeat import ( "RabbitMQ" "fmt" "math/rand" "strconv" "sync" "time" ) //接口服务的心跳包处理 //接口服务接收每一个数据服务的心跳包,并维持应该数据服务列表,并对数据服务列表进行数据保活机制。 var dataServers = make(map[string]time.Time) var mutex sync.Mutex //监听并处理数据服务的心跳包 func ListenHeartbeat(){ q := rabbitMQ.New("rabbitmq链接","") defer q.Close() q.Bind("apiServers","") c := q.Consume() go removeExpiredDataServer() for msg := range c{ dataServer,e := strconv.Unquote(string(msg.Body)) if e != nil { panic(e) } mutex.Lock() //加互斥锁 fmt.Println(dataServer) dataServers[dataServer] = time.Now() mutex.Unlock() //解互斥锁 } } func removeExpiredDataServer(){ for{ time.Sleep(5 * time.Second) mutex.Lock() for s,t := range dataServers { if t.Add(10*time.Second).Before(time.Now()){ delete(dataServers,s) } } mutex.Unlock() } } //获得接口服务维持的数据服务列表 func GetDataServers() []string { mutex.Lock() defer mutex.Unlock() ds := make([]string,0) for s,_:= range dataServers { ds = append(ds,s) } return ds } //随机选择一个数据服务节点 func ChooseRandomDataServer() string{ ds := GetDataServers() n := len(ds) if n == 0 { return "" } return ds[rand.Intn(n)] }
接口服务的定义处理locate
package Locate import ( rabbitMQ "RabbitMQ" "encoding/json" "fmt" "net/http" "strconv" "strings" "time" ) //接口服务的定位服务处理 func Handler(w http.ResponseWriter, r *http.Request){ //获取url信息进行分割,并定位 info := Locate(strings.Split(r.URL.EscapedPath(),"/")[2]) if len(info) == 0{ s := "资源不存在" w.Write([]byte(s)) } b, _:= json.Marshal(info) w.Write(b) } //向dataServers exchge发送定位信息并接收 func Locate(name string)string{ q := rabbitMQ.New("rabbitmq链接","") //先定位 q.Publish("dataServers",name,"") fmt.Printf("定位对象为:%s ",name) //再接收,如果要接收信息,必须要有信道 c := q.Consume() go func(){ time.Sleep(time.Second) q.Close() }() msg := <-c s,_ := strconv.Unquote(string(msg.Body)) fmt.Printf("目标锁定:%s ",s) return s } func Exist(name string) bool{ return Locate(name) != "" }