Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"dispatch.go",
"http.go",
"proxy.go",
"zk.go",
],
importpath = "go-common/app/service/live/broadcast-proxy/server",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/live/broadcast-proxy/conf:go_default_library",
"//app/service/live/broadcast-proxy/dispatch:go_default_library",
"//app/service/live/broadcast-proxy/grocery:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/ipipdotnet/ipdb-go:go_default_library",
"//vendor/github.com/samuel/go-zookeeper/zk:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,113 @@
package server
import (
"errors"
"fmt"
"github.com/ipipdotnet/ipdb-go"
"go-common/app/service/live/broadcast-proxy/conf"
"go-common/app/service/live/broadcast-proxy/dispatch"
"go-common/app/service/live/broadcast-proxy/grocery"
"go-common/library/log"
"sync"
)
type CometDispatcher struct {
sven *grocery.SvenClient
stopper chan struct{}
wg sync.WaitGroup
locker sync.RWMutex
ipDataV4 *ipdb.City
ipDataV6 *ipdb.City
matcher *dispatch.Matcher
config *conf.DispatchConfig
}
func NewCometDispatcher(ipipConfig *conf.IpipConfig,
dispatchConfig *conf.DispatchConfig, svenConfig *conf.SvenConfig) (*CometDispatcher, error) {
sven, err := grocery.NewSvenClient(svenConfig.TreeID, svenConfig.Zone, svenConfig.Env, svenConfig.Build,
svenConfig.Token)
if err != nil {
return nil, err
}
ipDataV4, err := ipdb.NewCity(ipipConfig.V4)
if err != nil {
return nil, err
}
ipDataV6, err := ipdb.NewCity(ipipConfig.V6)
if err != nil {
return nil, err
}
dispatcher := &CometDispatcher{
sven: sven,
stopper: make(chan struct{}),
ipDataV4: ipDataV4,
ipDataV6: ipDataV6,
config: dispatchConfig,
}
config := sven.Config()
if data, ok := config.Config[dispatchConfig.FileName]; ok {
dispatcher.updateDispatchConfig(data)
} else {
return nil, errors.New(fmt.Sprintf("cannot find %s in sven config", dispatchConfig.FileName))
}
dispatcher.wg.Add(1)
go func() {
defer dispatcher.wg.Done()
dispatcher.configWatcherProcess(dispatchConfig.FileName)
}()
return dispatcher, nil
}
func (dispatcher *CometDispatcher) Close() {
close(dispatcher.stopper)
dispatcher.wg.Wait()
dispatcher.sven.Close()
}
func (dispatcher *CometDispatcher) updateDispatchConfig(config string) error {
matcher, err := dispatch.NewMatcher([]byte(config), dispatcher.ipDataV4, dispatcher.ipDataV6, dispatcher.config)
if err != nil {
log.Error("parse rule config error:%v, data:%s", err, config)
return err
}
dispatcher.locker.Lock()
dispatcher.matcher = matcher
dispatcher.locker.Unlock()
log.Info("parse rule config ok, data:%s", config)
return nil
}
func (dispatcher *CometDispatcher) configWatcherProcess(filename string) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for config := range dispatcher.sven.ConfigNotify() {
log.Info("[sven]New version:%d", config.Version)
log.Info("[sven]New config: %v", config.Config)
if data, ok := config.Config[filename]; ok {
dispatcher.updateDispatchConfig(data)
}
}
}()
wg.Add(1)
go func() {
defer wg.Done()
for e := range dispatcher.sven.LogNotify() {
log.Info("[sven]log level:%v, message:%v", e.Level, e.Message)
}
}()
wg.Wait()
}
func (dispatcher *CometDispatcher) Dispatch(ip string, uid int64) ([]string, []string) {
var matcher *dispatch.Matcher
dispatcher.locker.RLock()
matcher = dispatcher.matcher
dispatcher.locker.RUnlock()
if matcher == nil {
return []string{dispatcher.config.DefaultDomain}, []string{dispatcher.config.DefaultDomain}
}
return matcher.Dispatch(ip, uid)
}

View File

@@ -0,0 +1,166 @@
package server
import (
"context"
"encoding/json"
"go-common/library/log"
"io/ioutil"
"net/http"
"strconv"
"sync"
"time"
)
type BroadcastService struct {
wg sync.WaitGroup
server *http.Server
proxy *BroadcastProxy
dispatch *CometDispatcher
}
func NewBroadcastService(addr string, proxy *BroadcastProxy, dispatch *CometDispatcher) (*BroadcastService, error) {
service := &BroadcastService{
proxy: proxy,
dispatch: dispatch,
}
service.wg.Add(1)
go func() {
defer service.wg.Done()
service.httpServerProcess(addr)
}()
return service, nil
}
func (service *BroadcastService) Close() {
service.server.Shutdown(context.Background())
}
func (service *BroadcastService) httpServerProcess(addr string) {
mux := http.NewServeMux()
mux.HandleFunc("/", service.Proxy)
mux.HandleFunc("/monitor/ping", service.Ping)
mux.HandleFunc("/dm/x/internal/v1/dispatch", service.Dispatch)
mux.HandleFunc("/dm/x/internal/v1/set_angry_value", service.SetAngryValue)
service.server = &http.Server{Addr: addr, Handler: mux}
service.server.SetKeepAlivesEnabled(true)
if err := service.server.ListenAndServe(); err != nil {
if err != http.ErrServerClosed {
panic(err)
}
}
}
func writeJsonResult(w http.ResponseWriter, r *http.Request, begin time.Time, v interface{}) {
data, err := json.Marshal(v)
if err != nil {
log.Error("[Http] write result json.Marshal:%v error:%v", v, err)
return
}
if _, err := w.Write([]byte(data)); err != nil {
log.Error("[Http] write result socket error:%v", err)
return
}
end := time.Now()
log.Info("request %s, response:%s, time cost:%s", r.RequestURI, data, end.Sub(begin).String())
}
func (service *BroadcastService) Proxy(w http.ResponseWriter, r *http.Request) {
service.proxy.HandleRequest(w, r)
}
func (service *BroadcastService) Ping(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("pong"))
}
func (service *BroadcastService) Dispatch(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
var result struct {
Code int `json:"code"`
Data struct {
DanmakuServer []string `json:"dm_server"`
DanmakuHost []string `json:"dm_host"`
} `json:"data"`
}
defer writeJsonResult(w, r, time.Now(), &result)
ip := r.URL.Query().Get("ip")
uid, _ := strconv.ParseInt(r.URL.Query().Get("uid"), 10, 64)
result.Code = 0
result.Data.DanmakuServer, result.Data.DanmakuHost = service.dispatch.Dispatch(ip, uid)
}
func (service *BroadcastService) SetAngryValue(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
requestBody, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
// 气人值的workaround
//go func() {
responseCollection, errCollection := service.proxy.RequestAllBackend(r.Method, "/dm/1/num/change", requestBody)
for i := range responseCollection {
if errCollection[i] != nil {
log.Error("SetAngryValue server:%d error:%+v", i, errCollection[i])
} else {
log.Info("SetAngryValue server:%d result:%s", i, responseCollection[i])
}
}
//}()
w.WriteHeader(http.StatusOK)
response, _ := json.Marshal(map[string]interface{}{"ret": 1})
w.Write(response)
return
}
func (service *BroadcastService) SetAngryValueV2(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
requestBody, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
//go func() {
responseCollection, errCollection := service.proxy.RequestAllBackend(r.Method,
"/dm/x/internal/v2/set_angry_value", requestBody)
for i := range responseCollection {
if errCollection[i] != nil {
log.Error("SetAngryValueV2 server:%d error:%+v", i, errCollection[i])
w.WriteHeader(http.StatusServiceUnavailable)
response, _ := json.Marshal(map[string]interface{}{"code": -1, "msg": errCollection[i].Error()})
w.Write(response)
return
}
var result struct {
Code int `json:"code"`
Message string `json:"msg"`
}
if err := json.Unmarshal([]byte(responseCollection[i]), &result); err != nil {
log.Error("SetAngryValueV2 server:%d response:%s", i, responseCollection[i])
w.WriteHeader(http.StatusServiceUnavailable)
response, _ := json.Marshal(map[string]interface{}{"code": -2, "msg": responseCollection[i]})
w.Write(response)
return
}
if result.Code != 0 {
w.WriteHeader(http.StatusOK)
w.Write([]byte(responseCollection[i]))
return
}
}
//}()
w.WriteHeader(http.StatusOK)
response, _ := json.Marshal(map[string]interface{}{"code": 0})
w.Write(response)
return
}

View File

@@ -0,0 +1,201 @@
package server
import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"sync"
"sync/atomic"
"time"
"go-common/library/log"
)
type BroadcastProxy struct {
backend []string
probePath string
reverseProxy []*httputil.ReverseProxy
bestClientIndex int32
probeSample int
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
}
func NewBroadcastProxy(backend []string, probePath string, maxIdleConns int, probeSample int) (*BroadcastProxy, error) {
if len(backend) == 0 {
return nil, errors.New("Require at least one backend")
}
proxy := new(BroadcastProxy)
if probeSample > 0 {
proxy.probeSample = probeSample
} else {
proxy.probeSample = 1
}
for _, addr := range backend {
proxy.backend = append(proxy.backend, addr)
proxy.probePath = probePath
p := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: addr,
})
p.Transport = &http.Transport{
DisableKeepAlives: false,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConns,
}
proxy.reverseProxy = append(proxy.reverseProxy, p)
}
proxy.ctx, proxy.cancel = context.WithCancel(context.Background())
proxy.wg.Add(1)
go func() {
defer proxy.wg.Done()
proxy.mainProbeProcess()
}()
return proxy, nil
}
func (proxy *BroadcastProxy) Close() {
proxy.cancel()
proxy.wg.Wait()
}
func (proxy *BroadcastProxy) HandleRequest(w http.ResponseWriter, r *http.Request) {
t1 := time.Now()
i := atomic.LoadInt32(&proxy.bestClientIndex)
proxy.reverseProxy[i].ServeHTTP(w, r)
t2 := time.Now()
log.V(3).Info("proxy process req:%s,backend id:%d, timecost:%s", r.RequestURI, i, t2.Sub(t1).String())
}
func (proxy *BroadcastProxy) RequestAllBackend(method, uri string, requestBody []byte) ([]string, []error) {
responseCollection := make([]string, len(proxy.reverseProxy))
errCollection := make([]error, len(proxy.reverseProxy))
var wg sync.WaitGroup
for i := range proxy.reverseProxy {
wg.Add(1)
go func(index int) {
defer wg.Done()
req, err := http.NewRequest(method, uri, bytes.NewReader(requestBody))
if err != nil {
errCollection[index] = err
return
}
httpRecorder := httptest.NewRecorder()
proxy.reverseProxy[index].ServeHTTP(httpRecorder, req)
resp := httpRecorder.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
errCollection[index] = errors.New(fmt.Sprintf("http response:%s", resp.Status))
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
errCollection[index] = err
return
}
responseCollection[index] = string(body)
}(i)
}
wg.Wait()
return responseCollection, errCollection
}
func (proxy *BroadcastProxy) mainProbeProcess() {
var wg sync.WaitGroup
interval := make([]time.Duration, len(proxy.backend))
for {
fast := -1
for i, probe := range proxy.reverseProxy {
wg.Add(1)
go func(p *httputil.ReverseProxy, index int) {
defer wg.Done()
interval[index] = proxy.unitProbeProcess(p, index)
}(probe, i)
}
wg.Wait()
for i, v := range interval {
if fast < 0 {
fast = i
} else {
if v < interval[fast] {
fast = i
}
}
}
atomic.StoreInt32(&proxy.bestClientIndex, int32(fast))
log.Info("[probe result]best server id:%d,addr:%s", fast, proxy.backend[fast])
for i, d := range interval {
log.Info("[probe log]server id:%d,addr:%s,avg time cost:%fms", i, proxy.backend[i], 1000*d.Seconds())
}
select {
case <-time.After(time.Second):
case <-proxy.ctx.Done():
return
}
}
}
func (proxy *BroadcastProxy) unitProbeProcess(p *httputil.ReverseProxy, backendIndex int) time.Duration {
var (
wg sync.WaitGroup
duration int64
)
for i := 0; i < proxy.probeSample; i++ {
wg.Add(1)
go func() {
defer wg.Done()
timeout := time.Second
<-time.After(time.Duration(rand.Intn(proxy.probeSample)) * time.Millisecond)
if timeCost, err := proxy.checkMonitorPing(p, backendIndex, timeout); err == nil {
atomic.AddInt64(&duration, timeCost.Nanoseconds())
} else {
atomic.AddInt64(&duration, timeout.Nanoseconds())
}
}()
}
wg.Wait()
return time.Duration(duration/int64(proxy.probeSample)) * time.Nanosecond
}
func (proxy *BroadcastProxy) checkMonitorPing(p *httputil.ReverseProxy, backendIndex int, timeout time.Duration) (time.Duration, error) {
req, err := http.NewRequest("GET", proxy.probePath, nil)
if err != nil {
return timeout, err
}
ctx, cancel := context.WithTimeout(proxy.ctx, timeout)
defer cancel()
req = req.WithContext(ctx)
recorder := httptest.NewRecorder()
beginTime := time.Now()
p.ServeHTTP(recorder, req)
resp := recorder.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Error("probe:server id:%d,addr:%s,send requset error:%s", backendIndex,
proxy.backend[backendIndex], resp.Status)
return timeout, errors.New("http response:" + resp.Status)
}
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Error("probe:server id:%d,addr:%s,read response error:%v", backendIndex,
proxy.backend[backendIndex], err)
return timeout, err
}
if !bytes.Equal(result, []byte("pong")) {
log.Error("probe:server id:%d,addr:%s,not match response:%s", backendIndex,
proxy.backend[backendIndex], result)
return timeout, err
}
endTime := time.Now()
return endTime.Sub(beginTime), nil
}

View File

@@ -0,0 +1,214 @@
package server
import (
"github.com/samuel/go-zookeeper/zk"
"go-common/library/log"
"strings"
"time"
)
type ZkClient struct {
conn *zk.Conn
address []string
timeout time.Duration
dialTime time.Time
closed bool
stopper chan struct{}
}
func NewZkClient(addrs []string, timeout time.Duration) (*ZkClient, error) {
if timeout <= 0 {
timeout = time.Second * 5
}
c := &ZkClient{
address: addrs,
timeout: timeout,
stopper: make(chan struct{}),
}
if err := c.Reset(); err != nil {
return nil, err
}
return c, nil
}
func (c *ZkClient) GetTimeout() time.Duration {
return c.timeout
}
func (c *ZkClient) RecursiveCreate(path string) error {
if path == "" || path == "/" {
return nil
}
if exists, _, err := c.conn.Exists(path); err != nil {
return err
} else if exists {
return nil
}
if err := c.RecursiveCreate(path[0:strings.LastIndex(path, "/")]); err != nil {
return err
}
_, err := c.conn.Create(path, []byte{}, 0, zk.WorldACL(zk.PermAll))
if err != nil && err != zk.ErrNodeExists {
return err
}
return nil
}
func (c *ZkClient) Reset() error {
c.dialTime = time.Now()
conn, events, err := zk.Connect(c.address, c.timeout)
if err != nil {
return err
}
if c.conn != nil {
c.conn.Close()
c.conn = nil
}
c.conn = conn
go func() {
for ev := range events {
if ev.Err == nil {
log.V(2).Info("[ZooKeeper]Event Info:%+v", ev)
} else {
log.Error("[ZooKeeper]Event Error:%+v", ev)
}
}
}()
return nil
}
func (c *ZkClient) CreateEphemeralNode(path string, node string, data []byte) (string, error) {
if err := c.RecursiveCreate(path); err != nil {
return "", err
}
nodePath := strings.Join([]string{path, node}, "/")
path, err := c.conn.Create(nodePath, data, zk.FlagEphemeral, zk.WorldACL(zk.PermAll))
return path, err
}
func (c *ZkClient) CreatePersistNode(path string, node string, data []byte) (string, error) {
if err := c.RecursiveCreate(path); err != nil {
return "", err
}
nodePath := strings.Join([]string{path, node}, "/")
path, err := c.conn.Create(nodePath, data, 0, zk.WorldACL(zk.PermAll))
return path, err
}
func (c *ZkClient) Exists(path string, node string) (bool, int32, error) {
fullPath := strings.Join([]string{path, node}, "/")
exists, stat, err := c.conn.Exists(fullPath)
return exists, stat.Version, err
}
func (c *ZkClient) SetNodeData(path string, node string, data []byte, version int32) error {
var err error
fullPath := strings.Join([]string{path, node}, "/")
_, err = c.conn.Set(fullPath, data, version)
return err
}
func (c *ZkClient) GetNodeData(path string, node string) ([]byte, int32, error) {
var err error
fullPath := strings.Join([]string{path, node}, "/")
data, stat, err := c.conn.Get(fullPath)
return data, stat.Version, err
}
func (c *ZkClient) DeleteNode(path string, node string) error {
var err error
fullPath := strings.Join([]string{path, node}, "/")
exists, stat, err := c.conn.Exists(fullPath)
if err != nil {
return err
}
if !exists {
return nil
}
err = c.conn.Delete(fullPath, stat.Version)
return err
}
func (c *ZkClient) Close() {
if c.closed {
return
}
c.closed = true
if c.conn != nil {
c.conn.Close()
}
close(c.stopper)
}
func (c *ZkClient) GetChildren(node string) ([]string, error) {
children, _, err := c.conn.Children(node)
return children, err
}
func (c *ZkClient) GetChildrenWithData(node string) (map[string]string, error) {
children, _, err := c.conn.Children(node)
result := make(map[string]string)
for _, child := range children {
if data, _, e := c.conn.Get(strings.Join([]string{node, child}, "/")); e == nil {
result[child] = string(data)
} else {
log.Error("[ZookeeperClient]GetChildrenWithData:get child:%s failed, err:%s", child, e.Error())
}
}
return result, err
}
func (c *ZkClient) GetData(path string) (string, error) {
data, _, err := c.conn.Get(path)
return string(data), err
}
func (c *ZkClient) WatchChildren(path string) (map[string]struct{}, <-chan zk.Event, error) {
if exists, _, err := c.conn.Exists(path); err != nil {
return nil, nil, err
} else if !exists {
return nil, nil, zk.ErrNoNode
}
children, _, event, err := c.conn.ChildrenW(path)
if err != nil {
return nil, nil, err
}
result := make(map[string]struct{})
for _, child := range children {
result[child] = struct{}{}
}
return result, event, nil
}
func (c *ZkClient) WatchChildrenWithData(node string) (map[string]string, <-chan zk.Event, error) {
if exists, _, err := c.conn.Exists(node); err != nil {
return nil, nil, err
} else if !exists {
return nil, nil, zk.ErrNoNode
}
children, _, event, err := c.conn.ChildrenW(node)
if err != nil {
return nil, nil, err
}
result := make(map[string]string)
for _, child := range children {
if data, _, e := c.conn.Get(strings.Join([]string{node, child}, "/")); e == nil {
result[child] = string(data)
} else {
return nil, nil, e
}
}
return result, event, nil
}
func (c *ZkClient) WatchData(path string) ([]byte, <-chan zk.Event, error) {
data, _, event, err := c.conn.GetW(path)
return data, event, err
}
func (c *ZkClient) ZooKeeperPath(args ...string) string {
return strings.Join(args, "/")
}