• 数据服务的分布式模型


    数据服务的分布式模型

    分布式系统要求个节点分布在网络上,并通过消息传递合作来完成一个共同的目标。
    分布式系统的三大关键特征:

    • 节点之间并发工作
    • 没有全局锁
    • 某个节点发生错误不影响其它节点

    模型

    接口服务层提供对外的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) != ""
    }
    

      

      

  • 相关阅读:
    微分方程概述
    Vite 使用TSX/JSX
    java去掉html标签,只留文本内容
    设置gradle默认缓存文件路径(笔记)
    mysql 求年龄
    sql 工作记录1
    windows脚本创建桌面快捷图标方式
    vue命名规范
    span做成按钮时,文字不被选中样式
    Vue3 + Vite + TS项目引入iconfont图标(Svg方式)
  • 原文地址:https://www.cnblogs.com/-wenli/p/12526878.html
Copyright © 2020-2023  润新知