You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gosuv/distributed.go

250 lines
6.4 KiB

/*
分布式实现
话外:作者的整体代码结构对于我的分布式修改太合适了
*/
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/bluele/gcache"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/qiniu/log"
)
type Cluster struct {
slaves gcache.Cache
client *http.Client
suv *Supervisor
}
func (cluster *Cluster) join() {
data := url.Values{"slave": []string{cfg.Server.Addr}}
request, err := http.NewRequest(http.MethodPost, "http://"+cfg.Server.Master+"/distributed/join", strings.NewReader(data.Encode()))
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
request.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
if err != nil {
log.Errorf("join cluster %s : %s", cfg.Server.Master, err)
return
}
cluster.auth(request)
resp, err := cluster.client.Do(request)
if err != nil {
log.Errorf("join cluster %s : %s", cfg.Server.Master, err)
return
}
if resp.StatusCode == http.StatusOK {
log.Debugf("join to master %s", cfg.Server.Master)
} else {
log.Debugf("join to master %s error: %d", cfg.Server.Master, resp.StatusCode)
}
}
func (cluster *Cluster) auth(request *http.Request) {
if cfg.Server.HttpAuth.Enabled {
request.SetBasicAuth(cfg.Server.HttpAuth.User, cfg.Server.HttpAuth.Password)
}
}
func (cluster *Cluster) dialWebSocket(wsUrl string) (*websocket.Conn, *http.Response, error) {
var dialer *websocket.Dialer
if cfg.Server.HttpAuth.Enabled {
dialer = &websocket.Dialer{Proxy: func(r *http.Request) (*url.URL, error) {
cluster.auth(r)
return websocket.DefaultDialer.Proxy(r)
}}
} else {
dialer = websocket.DefaultDialer
}
return dialer.Dial(wsUrl, nil)
}
func (cluster *Cluster) requestSlave(url, method string, bodyBuffer *bytes.Buffer) ([]byte, error) {
request, err := http.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
cluster.auth(request)
resp, err := cluster.client.Do(request)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}
func (cluster *Cluster) cmdJoinCluster(w http.ResponseWriter, r *http.Request) {
slave := r.PostFormValue("slave")
if slave == "" {
w.WriteHeader(http.StatusForbidden)
return
}
if strings.HasPrefix(slave, ":") {
idx := strings.LastIndex(r.RemoteAddr, ":")
slave = r.RemoteAddr[:idx] + slave
}
log.Debugf("%s join cluster.", slave)
if out, err := cluster.slaves.Get(slave); err != nil || out == nil {
cluster.suv.broadcastEvent("new slave : " + slave)
}
cluster.slaves.Set(slave, slave)
w.WriteHeader(http.StatusOK)
}
//获取分布式系统下所有的内容
func (cluster *Cluster) cmdQueryDistributedPrograms(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
slaves := []string{}
for _, v := range cluster.slaves.GetALL() {
if slave, ok := v.(string); ok {
slaves = append(slaves, slave)
}
}
sort.Strings(slaves)
jsonOut := "{"
idx := 0
for _, slave := range slaves {
reqUrl := fmt.Sprintf("http://%s/api/programs", slave)
if body, err := cluster.requestSlave(reqUrl, http.MethodGet, nil); err == nil {
jsonOut += fmt.Sprintf("\"%s\":%s", slave, body)
}
if idx < cluster.slaves.Len()-1 {
jsonOut += ","
}
idx += 1
}
jsonOut += "}"
w.Write([]byte(jsonOut))
}
func (cluster *Cluster) cmdSetting(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
slave := mux.Vars(r)["slave"]
cluster.suv.renderHTML(w, "setting", map[string]string{
"Name": name,
"Slave": slave,
})
}
func (cluster *Cluster) cmdWebSocketProxy(w http.ResponseWriter, r *http.Request) {
sock, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Error("upgrade:", err)
return
}
defer sock.Close()
slave := mux.Vars(r)["slave"]
slaveUri := strings.Replace(r.RequestURI, "/distributed/"+slave, "", 1)
wsUrl := fmt.Sprintf("ws://%s%s", slave, slaveUri)
log.Infof("proxy websocket :%s", wsUrl)
ws, _, err := cluster.dialWebSocket(wsUrl)
if err != nil {
log.Error("dial:", err)
return
}
defer ws.Close()
for {
messageType, data, err := ws.ReadMessage()
if err != nil {
log.Error("read message:", err)
return
}
if messageType == websocket.CloseMessage {
log.Infof("close socket")
return
}
w, err := sock.NextWriter(messageType)
if err != nil {
log.Error("write err:", err)
return
}
_, err = w.Write(data)
if err != nil {
log.Error("read:", err)
return
}
}
}
func (cluster *Cluster) slaveHttpProxy(w http.ResponseWriter, r *http.Request) {
slave := mux.Vars(r)["slave"]
slaveUri := strings.Replace(r.RequestURI, "/distributed/"+slave, "", 1)
requestUrl := fmt.Sprintf("http://%s%s", slave, slaveUri)
log.Infof("proxy :%s %s", r.Method, requestUrl)
request, err := http.NewRequest(r.Method, requestUrl, r.Body)
for k, v := range r.Header {
request.Header.Set(k, strings.Join(v, ","))
}
if err != nil {
log.Error(err)
}
cluster.auth(request)
resp, err := cluster.client.Do(request)
if err != nil {
log.Error(err)
}
defer resp.Body.Close()
if body, err := ioutil.ReadAll(resp.Body); err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
} else {
for k, v := range resp.Header {
w.Header().Set(k, strings.Join(v, ","))
}
w.Write(body)
cluster.suv.broadcastEvent("execute ok : " + slaveUri)
}
}
func newDistributed(suv *Supervisor, hdlr http.Handler) error {
cluster.suv = suv
r := hdlr.(*mux.Router)
r.HandleFunc("/distributed/join", cluster.cmdJoinCluster).Methods("POST")
r.HandleFunc("/distributed/api/programs", cluster.cmdQueryDistributedPrograms).Methods("GET")
r.HandleFunc("/distributed/{slave}/settings/{name}", cluster.cmdSetting)
for _, path := range []string{
"/distributed/{slave}/api/programs", "/distributed/{slave}/api/programs/{name}",
"/distributed/{slave}/api/programs/{name}/start", "/distributed/{slave}/api/programs/{name}/stop",
} {
r.HandleFunc(path, cluster.slaveHttpProxy)
}
r.HandleFunc("/distributed/{slave}/ws/logs/{name}", cluster.cmdWebSocketProxy)
r.HandleFunc("/distributed/{slave}/ws/perfs/{name}", cluster.cmdWebSocketProxy)
if cfg.Server.Master != "" {
go func() {
t1 := time.NewTimer(time.Second)
for {
select {
case <-t1.C:
cluster.join()
t1.Reset(time.Second)
}
}
}()
}
return nil
}
var cluster = Cluster{
slaves: gcache.New(1000).LRU().Expiration(time.Second * 3).Build(),
client: new(http.Client),
}