Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
debug
.idea
*.iml
.vscode
go_install.sh

View File

@@ -0,0 +1,23 @@
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/live/broadcast-proxy/api/v1:all-srcs",
"//app/service/live/broadcast-proxy/cmd:all-srcs",
"//app/service/live/broadcast-proxy/conf:all-srcs",
"//app/service/live/broadcast-proxy/dispatch:all-srcs",
"//app/service/live/broadcast-proxy/expr:all-srcs",
"//app/service/live/broadcast-proxy/grocery:all-srcs",
"//app/service/live/broadcast-proxy/server:all-srcs",
"//app/service/live/broadcast-proxy/service:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,6 @@
# Owner
majiayi
# Author
# Reviewer

View File

@@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- majiayi
labels:
- live
- service
- service/live/broadcast-proxy
options:
no_parent_owners: true

View File

@@ -0,0 +1,57 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/app/service/live/broadcast-proxy/api/v1",
proto = ":v1_proto",
tags = ["automanaged"],
deps = ["@com_github_gogo_protobuf//gogoproto:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
embed = [":v1_go_proto"],
importpath = "go-common/app/service/live/broadcast-proxy/api/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/rpc/warden:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context: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"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,78 @@
syntax = "proto3";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
package live.broadcastproxy.v1;
option go_package = "v1";
service Danmaku {
rpc RoomMessage (RoomMessageRequest) returns (GeneralResponse);
rpc BroadcastMessage (BroadcastMessageRequest) returns(GeneralResponse);
rpc MultiRoomMessage (MultiRoomMessageRequest) returns(GeneralResponse);
rpc BatchRoomMessage(BatchRoomMessageRequest) returns(GeneralResponse);
rpc UserMessage(UserMessageRequest)returns(GeneralResponse);
rpc BatchUserMessage(BatchUserMessageRequest)returns(GeneralResponse);
rpc Dispatch(DispatchRequest)returns(DispatchResponse);
rpc SetAngryValue(SetAngryValueRequest)returns(SetAngryValueResponse);
rpc GetRoomOnlineCount(GetRoomOnlineCountRequest)returns(GetRoomOnlineCountResponse);
}
message GeneralResponse {
}
message RoomMessageRequest {
int32 room_id = 1 [(gogoproto.jsontag) = "room_id"];
string message = 2 [(gogoproto.jsontag) = "msg"];
int32 ensure = 3;
}
message BroadcastMessageRequest {
string message = 1;
repeated int32 exclude_room_id = 2;
}
message MultiRoomMessageRequest {
repeated int32 room_id = 1 [(gogoproto.jsontag) = "room_id"];
string message = 2 [(gogoproto.jsontag) = "msg"];
int32 ensure = 3;
}
message BatchRoomMessageRequest {
repeated RoomMessageRequest room_message = 1 [(gogoproto.jsontag) = "data"];
}
message UserMessageRequest {
int64 user_id = 1 [(gogoproto.jsontag) = "uid"];
string message = 2 [(gogoproto.jsontag) = "msg"];
}
message BatchUserMessageRequest {
repeated UserMessageRequest user_message = 1 [(gogoproto.jsontag) = "data"];
}
message DispatchRequest {
int64 user_id = 1 [(gogoproto.jsontag) = "uid"];
string user_ip = 2 [(gogoproto.jsontag) = "ip"];
}
message DispatchResponse {
repeated string ip = 1 [(gogoproto.jsontag) = "ip"];
repeated string host = 2 [(gogoproto.jsontag) = "host"];
}
message SetAngryValueRequest {
map<uint64,uint64> angry_value = 1 [(gogoproto.jsontag) = "angry_value"];
}
message SetAngryValueResponse {
}
message GetRoomOnlineCountRequest {
repeated uint64 room_id = 1 [(gogoproto.jsontag) = "room_id"];
}
message GetRoomOnlineCountResponse {
map<uint64,uint64> room_online_count = 1 [(gogoproto.jsontag) = "room_online_count"];
}

View File

@@ -0,0 +1,25 @@
package v1
import (
"context"
"go-common/library/net/rpc/warden"
"google.golang.org/grpc"
)
const kAppID = "live.broadcastproxy"
type Client struct {
DanmakuClient
}
// NewClient new resource grpc client
func NewClient(cfg *warden.ClientConfig, opts ...grpc.DialOption) (*Client, error) {
client := warden.NewClient(cfg, opts...)
conn, err := client.Dial(context.Background(), "discovery://default/"+kAppID)
if err != nil {
return nil, err
}
cli := &Client{}
cli.DanmakuClient = NewDanmakuClient(conn)
return cli, nil
}

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "cmd",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
data = ["live-broadcast-proxy.toml"],
importpath = "go-common/app/service/live/broadcast-proxy/cmd",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/live/broadcast-proxy/conf:go_default_library",
"//app/service/live/broadcast-proxy/server:go_default_library",
"//app/service/live/broadcast-proxy/service:go_default_library",
"//library/log: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,49 @@
# This is a TOML document. Boom.
version = "1.0.0"
user = "root"
pid = "/tmp/live-broadcast-proxy.pid"
dir = "./"
perf = "0.0.0.0:20215"
family = "live-broadcast-proxy"
trace = true
debug = true
[log]
#dir = "/data/log/broadcast-proxy"
vlevel = 100
stdout = true
[http]
address = "0.0.0.0:20214"
[backend]
maxIdleConnsPerHost = 512
probePath = "/monitor/ping"
backendServer = ["172.18.33.69:7172","172.18.33.119:80", "172.16.38.159:80", "172.16.38.159:7172"]
probesample = 100
[zookeeper]
address = ["172.18.33.171:2182"]
timeout = "5s"
configpath = "/live/setting/live_broadcast_proxy"
[sinaip]
data = "/data/conf/sinaip.dat"
[ipip]
v4= "/data/conf/v4.ipdb"
v6= "/data/conf/v6.ipdb"
[dispatch]
maxlimit = 2
defaultdomain = "broadcastlv.chat.bilibili.com"
wildcarddomainsuffix=".chat.bilibili.com"
filename="dispatch.json"
[sven]
treeID="24639"
zone="sh001"
env="prod"
build="docker-1"
token="768e1f6f4c3f4c545c8c89852c02fea6"

View File

@@ -0,0 +1,80 @@
package main
import (
"context"
"flag"
"go-common/app/service/live/broadcast-proxy/conf"
"go-common/app/service/live/broadcast-proxy/server"
"go-common/app/service/live/broadcast-proxy/service"
"go-common/library/log"
"net/http"
"net/http/pprof"
"runtime"
"time"
)
func RunPprofServer(addr string) {
pprofServer := http.NewServeMux()
pprofServer.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
pprofServer.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
pprofServer.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
pprofServer.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
pprofServer.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
go func() {
if e := http.ListenAndServe(addr, pprofServer); e != nil {
log.Error("pprof server error ListenAndServe addr:%s,error:%+v", addr, e)
}
defer func() {
if e := recover(); e != nil {
log.Error("expected panic from pprof server,error:%+v", e)
}
}()
}()
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
var confTomlFile string
flag.StringVar(&confTomlFile, "conf", "", "config file for broadcast proxy")
flag.Parse()
conf, err := conf.NewBroadcastProxyConfig(confTomlFile)
if err != nil {
panic(err)
}
log.Init(conf.Log)
log.Info("Broadcast Proxy Service:%s", time.Now().String())
log.Info("Broadcast Proxy Config:%+v", conf)
proxy, err := server.NewBroadcastProxy(conf.Backend.BackendServer, conf.Backend.ProbePath,
conf.Backend.MaxIdleConnsPerHost, conf.Backend.ProbeSample)
if err != nil {
panic(err)
}
defer proxy.Close()
dispatcher, err := server.NewCometDispatcher(conf.Ipip, conf.Dispatch, conf.Sven)
if err != nil {
panic(err)
}
defer dispatcher.Close()
httpService, err := server.NewBroadcastService(conf.Http.Address, proxy, dispatcher)
if err != nil {
panic(err)
}
defer httpService.Close()
grpcService, err := service.NewGrpcService(proxy, dispatcher)
if err != nil {
panic(err)
}
defer grpcService.Shutdown(context.Background())
RunPprofServer(conf.Perf)
quit := make(chan struct{})
<-quit
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["config.go"],
importpath = "go-common/app/service/live/broadcast-proxy/conf",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/conf:go_default_library",
"//library/log:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/BurntSushi/toml: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,106 @@
package conf
import (
"errors"
"github.com/BurntSushi/toml"
"go-common/library/conf"
"go-common/library/log"
xtime "go-common/library/time"
)
type BroadcastProxyConfig struct {
Perf string `toml:"perf"`
Log *log.Config
Http *HttpConfig
Backend *BackendConfig
ZooKeeper *ZooKeeperConfig
Ipip *IpipConfig
Dispatch *DispatchConfig
Sven *SvenConfig
}
type HttpConfig struct {
Address string
}
type BackendConfig struct {
MaxIdleConnsPerHost int
ProbePath string
BackendServer []string
ProbeSample int
}
type ZooKeeperConfig struct {
Address []string
Timeout xtime.Duration
ConfigPath string
}
type SinaIPConfig struct {
Data string
}
type IpipConfig struct {
V4 string
V6 string
}
type DispatchConfig struct {
MaxLimit int
DefaultDomain string
WildcardDomainSuffix string
FileName string
}
type SvenConfig struct {
TreeID string
Zone string
Env string
Build string
Token string
}
func NewBroadcastProxyConfig(file string) (*BroadcastProxyConfig, error) {
config := new(BroadcastProxyConfig)
if file != "" {
if err := config.local(file); err != nil {
return nil, err
}
} else {
if err := config.remote(); err != nil {
return nil, err
}
}
return config, nil
}
func (config *BroadcastProxyConfig) local(filename string) (err error) {
_, err = toml.DecodeFile(filename, config)
return
}
func (config *BroadcastProxyConfig) remote() error {
client, err := conf.New()
if err != nil {
return err
}
if err = config.load(client); err != nil {
return err
}
go func() {
for range client.Event() {
log.Info("config event")
}
}()
return nil
}
func (config *BroadcastProxyConfig) load(c *conf.Client) error {
s, ok := c.Value("live-broadcast-proxy.toml")
if !ok {
return errors.New("load config center error")
}
if _, err := toml.Decode(s, config); err != nil {
return errors.New("could not decode config")
}
return nil
}

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["matcher_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"matcher.go",
"min_heap.go",
"sinaip.go",
],
importpath = "go-common/app/service/live/broadcast-proxy/dispatch",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/live/broadcast-proxy/conf:go_default_library",
"//app/service/live/broadcast-proxy/expr:go_default_library",
"//library/log:go_default_library",
"//vendor/github.com/ipipdotnet/ipdb-go: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,306 @@
package dispatch
import (
"encoding/json"
"errors"
"github.com/ipipdotnet/ipdb-go"
"go-common/app/service/live/broadcast-proxy/conf"
"go-common/app/service/live/broadcast-proxy/expr"
"go-common/library/log"
"math"
"math/rand"
"strconv"
"strings"
"sync"
)
type Matcher struct {
ipDataV4 *ipdb.City
ipDataV6 *ipdb.City
heapPool sync.Pool
Config string
MaxLimit int `json:"ip_max_limit"`
DefaultDomain string `json:"default_domain"`
WildcardDomainSuffix string `json:"wildcard_domain_suffix"`
CommonDispatch struct {
ChinaDispatch struct {
ChinaTelecom *CommonBucket `json:"china_telecom"`
ChinaUnicom *CommonBucket `json:"china_unicom"`
CMCC *CommonBucket `json:"cmcc"`
ChinaOther *CommonBucket `json:"other"`
} `json:"china"`
OverseaDispatch []*CommonRuleBucket `json:"oversea"`
UnknownAreaDispatch *CommonBucket `json:"unknown"`
} `json:"danmaku_common_dispatch"`
VIPDispatch []*VIPRuleBucket `json:"danmaku_vip_dispatch"`
ServerGroup map[string][]string `json:"danmaku_comet_group"`
ServerHost map[string]string `json:"danmaku_comet_host"`
IPBlack []string `json:"ip_black"`
TempV6 []string `json:"temp_v6"`
forbiddenIP map[string]struct{}
}
type CommonBucket struct {
Master map[string]int `json:"master"`
Slave map[string]int `json:"slave"`
}
type CommonRuleBucket struct {
CommonBucket
Rule string `json:"rule"`
RuleExpr expr.Expr
}
type VIPRuleBucket struct {
Rule string `json:"rule"`
RuleExpr expr.Expr
IP []string `json:"ip"`
Group []string `json:"group"`
}
func NewMatcher(matcherConfig []byte, ipDataV4 *ipdb.City, ipDataV6 *ipdb.City, dispatchConfig *conf.DispatchConfig) (*Matcher, error) {
matcher := new(Matcher)
matcher.heapPool = sync.Pool{
New: func() interface{} {
return NewMinHeap()
},
}
matcher.forbiddenIP = make(map[string]struct{})
if ipDataV4 == nil || ipDataV6 == nil {
return nil, errors.New("invalid IP database")
}
matcher.ipDataV4 = ipDataV4
matcher.ipDataV6 = ipDataV6
matcher.Config = string(matcherConfig)
if err := json.Unmarshal(matcherConfig, matcher); err != nil {
return nil, err
}
for _, ip := range matcher.IPBlack {
matcher.forbiddenIP[ip] = struct{}{}
}
parser := expr.NewExpressionParser()
for _, oversea := range matcher.CommonDispatch.OverseaDispatch {
if oversea.Rule == "" {
oversea.Rule = "true"
}
if err := parser.Parse(oversea.Rule); err != nil {
log.Error("[Matcher] Parse rule expr:%s, error:%+v", oversea.Rule, err)
return nil, err
}
for _, variable := range parser.GetVariable() {
if variable != "$lng" && variable != "$lat" {
return nil, errors.New("oversea dispatch only supports variable $lng and $lat")
}
}
oversea.RuleExpr = parser.GetExpr()
}
for _, vip := range matcher.VIPDispatch {
if err := parser.Parse(vip.Rule); err != nil {
log.Error("[Matcher] Parse rule expr:%s, error:%+v", vip.Rule, err)
return nil, err
}
for _, variable := range parser.GetVariable() {
if variable != "$uid" {
return nil, errors.New("vip dispatch only supports variable $uid")
}
}
if len(parser.GetVariable()) == 0 {
return nil, errors.New("vip dispatch must contains variable $uid")
}
vip.RuleExpr = parser.GetExpr()
}
if matcher.MaxLimit == 0 && dispatchConfig != nil {
matcher.MaxLimit = dispatchConfig.MaxLimit
}
if matcher.DefaultDomain == "" && dispatchConfig != nil {
matcher.DefaultDomain = dispatchConfig.DefaultDomain
}
if matcher.WildcardDomainSuffix == "" && dispatchConfig != nil {
matcher.WildcardDomainSuffix = dispatchConfig.WildcardDomainSuffix
}
return matcher, nil
}
func (matcher *Matcher) GetConfig() string {
return matcher.Config
}
func (matcher *Matcher) Dispatch(ip string, uid int64) ([]string, []string) {
danmakuIP := matcher.dispatchInternal(ip, uid)
danmakuHost := make([]string, 0, len(danmakuIP))
for _, singleDanmakuIP := range danmakuIP {
if host, ok := matcher.ServerHost[singleDanmakuIP]; ok {
danmakuHost = append(danmakuHost, host+matcher.WildcardDomainSuffix)
}
}
danmakuIP = append(danmakuIP, matcher.DefaultDomain)
danmakuHost = append(danmakuHost, matcher.DefaultDomain)
return danmakuIP, danmakuHost
}
func (matcher *Matcher) dispatchInternal(ip string, uid int64) []string {
if _, ok := matcher.forbiddenIP[ip]; ok {
return []string{}
}
// VIP Dispatch
vipDispatchEnv := make(map[expr.Var]interface{})
vipDispatchEnv[expr.Var("$uid")] = uid
for _, vip := range matcher.VIPDispatch {
if v, err := expr.SafetyEvalBool(vip.RuleExpr, vipDispatchEnv); v && err == nil {
return matcher.pickFromVIPRuleBucket(vip)
} else {
if err != nil {
log.Error("[Matcher] VIP dispatch, uid:%d, eval rule expr:%s error:%+v", uid, vip.Rule, err)
}
}
}
// Common Dispatch
var ipDatabase *ipdb.City
for i := 0; i < len(ip); i++ {
if ip[i] == '.' {
ipDatabase = matcher.ipDataV4
break
} else if ip[i] == ':' {
ipDatabase = matcher.ipDataV6
//break
//TODO: this is temp solution, replace this block with "break" here when all server supports IPv6
return matcher.randomPickN(matcher.TempV6, matcher.MaxLimit)
}
}
if ipDatabase == nil {
return matcher.pickFromCommonBucket(matcher.CommonDispatch.UnknownAreaDispatch)
}
detail, err := ipDatabase.FindMap(ip, "EN")
if err != nil {
return matcher.pickFromCommonBucket(matcher.CommonDispatch.UnknownAreaDispatch)
}
country := strings.TrimSpace(detail["country_name"])
province := strings.TrimSpace(detail["region_name"])
isp := strings.TrimSpace(detail["isp_domain"])
latitude, _ := strconv.ParseFloat(detail["latitude"], 64)
longitude, _ := strconv.ParseFloat(detail["longitude"], 64)
if country != "China" && country != "Reserved" && country != "LAN Address" && country != "Loopback" {
return matcher.pickFromCommonRuleBucket(matcher.CommonDispatch.OverseaDispatch, latitude, longitude)
} else if country == "China" {
if province == "Hong Kong" || province == "Macau" || province == "Taiwan" {
return matcher.pickFromCommonRuleBucket(matcher.CommonDispatch.OverseaDispatch, latitude, longitude)
} else {
switch isp {
case "ChinaTelecom":
return matcher.pickFromCommonBucket(matcher.CommonDispatch.ChinaDispatch.ChinaTelecom)
case "ChinaMobile":
return matcher.pickFromCommonBucket(matcher.CommonDispatch.ChinaDispatch.CMCC)
case "ChinaUnicom":
return matcher.pickFromCommonBucket(matcher.CommonDispatch.ChinaDispatch.ChinaUnicom)
default:
return matcher.pickFromCommonBucket(matcher.CommonDispatch.ChinaDispatch.ChinaOther)
}
}
} else {
return matcher.pickFromCommonBucket(matcher.CommonDispatch.UnknownAreaDispatch)
}
}
func (matcher *Matcher) pickFromCommonRuleBucket(overseaBucket []*CommonRuleBucket, latitude float64, longitude float64) []string {
overseaDispatchEnv := make(map[expr.Var]interface{})
overseaDispatchEnv[expr.Var("$lat")] = latitude
overseaDispatchEnv[expr.Var("$lng")] = longitude
for _, bucket := range overseaBucket {
if v, err := expr.SafetyEvalBool(bucket.RuleExpr, overseaDispatchEnv); v && err == nil {
return matcher.pickFromCommonBucket(&bucket.CommonBucket)
}
}
return []string{}
}
func (matcher *Matcher) pickOneFromWeightedGroup(groupWeightDict map[string]int) (string, string) {
var luckyKey float64
var luckyGroup string
for group, weight := range groupWeightDict {
if weight > 0 {
key := math.Pow(rand.Float64(), 1.0/float64(weight))
if key >= luckyKey {
luckyKey = key
luckyGroup = group
}
}
}
luckyIP := matcher.ServerGroup[luckyGroup]
if len(luckyIP) == 0 {
return "", ""
}
return matcher.randomPickOne(luckyIP), luckyGroup
}
func (matcher *Matcher) pickNFromWeightedGroup(groupWeightDict map[string]int, n int, groupIgnore string) []string {
h := matcher.heapPool.Get().(*MinHeap)
for group, weight := range groupWeightDict {
if group != groupIgnore && weight > 0 {
key := math.Pow(rand.Float64(), 1.0/float64(weight))
if h.HeapLength() < n {
h.HeapPush(group, key)
} else {
_, top, _ := h.HeapTop()
if key > top {
h.HeapPush(group, key)
h.HeapPop()
}
}
}
}
r := make([]string, 0, n)
for h.HeapLength() > 0 {
v, _, _ := h.HeapPop()
member := matcher.ServerGroup[v.(string)]
if len(member) > 0 {
r = append(r, matcher.randomPickOne(member))
}
}
matcher.heapPool.Put(h)
return r
}
func (matcher *Matcher) pickFromCommonBucket(b *CommonBucket) []string {
r := make([]string, 0, matcher.MaxLimit)
masterIP, masterGroup := matcher.pickOneFromWeightedGroup(b.Master)
if masterIP != "" {
r = append(r, masterIP)
}
for _, slaveIP := range matcher.pickNFromWeightedGroup(b.Slave, matcher.MaxLimit-len(r), masterGroup) {
r = append(r, slaveIP)
}
return r
}
func (matcher *Matcher) pickFromVIPRuleBucket(b *VIPRuleBucket) []string {
var length int
for _, group := range b.Group {
length += len(matcher.ServerGroup[group])
}
length += len(b.IP)
candidate := make([]string, length)
i := 0
for _, group := range b.Group {
i += copy(candidate[i:], matcher.ServerGroup[group])
}
i += copy(candidate[i:], b.IP)
return matcher.randomPickN(candidate, matcher.MaxLimit)
}
func (matcher *Matcher) randomPickOne(s []string) string {
return s[rand.Intn(len(s))]
}
func (matcher *Matcher) randomPickN(s []string, n int) []string {
var r []string
if n > len(s) {
n = len(s)
}
for _, v := range rand.Perm(len(s))[0:n] {
r = append(r, s[v])
}
return r
}

View File

@@ -0,0 +1,159 @@
package dispatch
import (
"fmt"
"testing"
)
func TestMatcher(t *testing.T) {
config := []byte(`{
"ip_max_limit": 2,
"default_domain" : "broadcastlv.chat.bilibili.com",
"danmaku_common_dispatch": {
"china" :{
"china_telecom": {
"master": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24
},
"slave": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24,
"aliyun": 30
}
},
"china_unicom": {
"master": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24
},
"slave": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24,
"aliyun": 30
}
},
"cmcc": {
"master": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24
},
"slave": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24,
"aliyun": 30
}
},
"other": {
"master": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24
},
"slave": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24,
"aliyun": 30
}
}
},
"oversea": [
{
"rule":"($lng >= -20) && ($lng <= 160)",
"master": {
"tencent_siliconvalley": 10
},
"slave": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24,
"aliyun": 30
}
},
{
"master": {
"tencent_siliconvalley": 10
},
"slave": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24,
"aliyun": 30
}
}
],
"unknown" : {
"master": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24
},
"slave": {
"tencent_shanghai": 10,
"tencent_guangzhou": 6,
"kingsoft": 24,
"aliyun": 30
}
}
},
"danmaku_vip_dispatch" : [
{
"rule":"$uid==120497668",
"ip": ["118.89.14.174"]
},
{
"rule":"$uid % 10 == 1",
"group": ["tencent_guangzhou"]
},
{
"rule":"$uid == 221122111"
}
],
"danmaku_comet_group": {
"tencent_shanghai": [
"118.89.14.174",
"118.89.14.115",
"118.89.14.103",
"118.89.14.206",
"118.89.13.229"
],
"tencent_guangzhou": [
"211.159.194.41",
"211.159.194.115",
"211.159.194.105"
],
"tencent_hongkong": [
"119.28.56.183"
],
"tencent_siliconvalley": [
"49.51.37.200"
],
"kingsoft": [
"120.92.78.57",
"120.92.158.137",
"120.92.112.150"
],
"aliyun": [
"101.132.195.89",
"47.104.64.120",
"59.110.167.237",
"47.92.112.162",
"47.96.139.69",
"119.23.41.85"
]
}
}`)
m, err := NewMatcher(config, nil, nil, nil)
if err != nil {
t.Error(err)
t.Fail()
}
fmt.Println(m)
}

View File

@@ -0,0 +1,79 @@
package dispatch
import (
"container/heap"
"errors"
)
type HeapData []*HeapDataItem
type HeapDataItem struct {
value interface{}
weight float64
}
func (d HeapData) Len() int {
return len(d)
}
func (d HeapData) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func (d *HeapData) Push(x interface{}) {
item := x.(*HeapDataItem)
*d = append(*d, item)
}
func (d *HeapData) Pop() interface{} {
old := *d
n := len(old)
item := old[n-1]
*d = old[0 : n-1]
return item
}
type MinHeapData struct {
HeapData
}
func (d MinHeapData) Less(i, j int) bool {
return d.HeapData[i].weight < d.HeapData[j].weight
}
type MinHeap struct {
data MinHeapData
}
func NewMinHeap() *MinHeap {
h := new(MinHeap)
heap.Init(&h.data)
return h
}
func (h *MinHeap) HeapPush(value interface{}, weight float64) {
heap.Push(&h.data, &HeapDataItem{
value: value,
weight: weight,
})
}
func (h *MinHeap) HeapPop() (interface{}, float64, error) {
if h.data.Len() == 0 {
return nil, 0, errors.New("heap is empty")
}
item := heap.Pop(&h.data).(*HeapDataItem)
return item.value, item.weight, nil
}
func (h *MinHeap) HeapTop() (interface{}, float64, error) {
if h.data.Len() == 0 {
return nil, 0, errors.New("heap is empty")
}
item := h.data.HeapData[0]
return item.value, item.weight, nil
}
func (h *MinHeap) HeapLength() int {
return h.data.Len()
}

View File

@@ -0,0 +1,372 @@
package dispatch
import (
"bytes"
"encoding/binary"
"io"
"os"
"syscall"
)
type SinaIP struct {
country map[uint16]*Country
province map[uint16]*Province
city map[uint16]*City
isp map[uint16]*ISP
district map[uint16]*District
segment []*IPSegment
}
type IPDetail struct {
Country string
Province string
City string
ISP string
District string
Latitude float64
Longitude float64
}
type IPSegment struct {
start uint32
end uint32
country *Country
province *Province
city *City
isp *ISP
district *District
latitude float64
longitude float64
}
type Country struct {
name string
}
type Province struct {
name string
}
type City struct {
name string
}
type ISP struct {
name string
}
type District struct {
name string
}
func NewSinaIP(file string) (*SinaIP, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
return nil, err
}
data, err := syscall.Mmap(int(f.Fd()), 0, int(info.Size()), syscall.PROT_READ, syscall.MAP_PRIVATE)
if err != nil {
return nil, err
}
defer syscall.Munmap(data)
var (
segmentOffset uint32
countryOffset uint32
provinceOffset uint32
cityOffset uint32
ispOffset uint32
districtOffset uint32
)
reader := bytes.NewReader(data[0:24])
if err := binary.Read(reader, binary.BigEndian, &segmentOffset); err != nil {
return nil, err
}
if err := binary.Read(reader, binary.BigEndian, &countryOffset); err != nil {
return nil, err
}
if err := binary.Read(reader, binary.BigEndian, &provinceOffset); err != nil {
return nil, err
}
if err := binary.Read(reader, binary.BigEndian, &cityOffset); err != nil {
return nil, err
}
if err := binary.Read(reader, binary.BigEndian, &ispOffset); err != nil {
return nil, err
}
if err := binary.Read(reader, binary.BigEndian, &districtOffset); err != nil {
return nil, err
}
biliIP := new(SinaIP)
if err := biliIP.loadCity(data[cityOffset:]); err != nil {
return nil, err
}
if err := biliIP.loadCountry(data[countryOffset:]); err != nil {
return nil, err
}
if err := biliIP.loadProvince(data[provinceOffset:]); err != nil {
return nil, err
}
if err := biliIP.loadISP(data[ispOffset:]); err != nil {
return nil, err
}
if err := biliIP.loadDistrict(data[districtOffset:]); err != nil {
return nil, err
}
if err := biliIP.loadIPSegment(data[segmentOffset:]); err != nil {
return nil, err
}
return biliIP, nil
}
func (b *SinaIP) DoQuery(ip uint32) *IPDetail {
left := 0
right := len(b.segment) - 1
var r *IPDetail
for left <= right {
middle := left + (right-left)/2
s := b.segment[middle]
if ip >= s.start && ip <= s.end {
r = new(IPDetail)
if s.country != nil {
r.Country = s.country.name
}
if s.province != nil {
r.Province = s.province.name
}
if s.city != nil {
r.City = s.city.name
}
if s.isp != nil {
r.ISP = s.isp.name
}
if s.district != nil {
r.District = s.district.name
}
r.Latitude = s.latitude
r.Longitude = s.longitude
break
} else if ip < s.start {
right = middle - 1
} else if ip > s.end {
left = middle + 1
}
}
return r
}
func (b *SinaIP) loadCountry(data []byte) error {
reader := bytes.NewReader(data)
var count uint16
if err := binary.Read(reader, binary.BigEndian, &count); err != nil {
return err
}
b.country = make(map[uint16]*Country, count)
for i := uint16(0); i < count; i++ {
var code uint16
if err := binary.Read(reader, binary.BigEndian, &code); err != nil {
return err
}
var length uint8
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
return err
}
country := make([]byte, length)
if _, err := io.ReadFull(reader, country); err != nil {
return err
}
b.country[code] = &Country{
name: string(country),
}
}
return nil
}
func (b *SinaIP) loadProvince(data []byte) error {
reader := bytes.NewReader(data)
var count uint16
if err := binary.Read(reader, binary.BigEndian, &count); err != nil {
return err
}
b.province = make(map[uint16]*Province, count)
for i := uint16(0); i < count; i++ {
var code uint16
if err := binary.Read(reader, binary.BigEndian, &code); err != nil {
return err
}
var length uint8
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
return err
}
province := make([]byte, length)
if _, err := io.ReadFull(reader, province); err != nil {
return err
}
b.province[code] = &Province{
name: string(province),
}
}
return nil
}
func (b *SinaIP) loadCity(data []byte) error {
reader := bytes.NewReader(data)
var count uint16
if err := binary.Read(reader, binary.BigEndian, &count); err != nil {
return err
}
b.city = make(map[uint16]*City, count)
for i := uint16(0); i < count; i++ {
var code uint16
if err := binary.Read(reader, binary.BigEndian, &code); err != nil {
return err
}
var length uint8
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
return err
}
city := make([]byte, length)
if _, err := io.ReadFull(reader, city); err != nil {
return err
}
b.city[code] = &City{
name: string(city),
}
}
return nil
}
func (b *SinaIP) loadISP(data []byte) error {
reader := bytes.NewReader(data)
var count uint16
if err := binary.Read(reader, binary.BigEndian, &count); err != nil {
return err
}
b.isp = make(map[uint16]*ISP, count)
for i := uint16(0); i < count; i++ {
var code uint16
if err := binary.Read(reader, binary.BigEndian, &code); err != nil {
return err
}
var length uint8
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
return err
}
isp := make([]byte, length)
if _, err := io.ReadFull(reader, isp); err != nil {
return err
}
b.isp[code] = &ISP{
name: string(isp),
}
}
return nil
}
func (b *SinaIP) loadDistrict(data []byte) error {
reader := bytes.NewReader(data)
var count uint16
if err := binary.Read(reader, binary.BigEndian, &count); err != nil {
return err
}
b.district = make(map[uint16]*District, count)
for i := uint16(0); i < count; i++ {
var code uint16
if err := binary.Read(reader, binary.BigEndian, &code); err != nil {
return err
}
var length uint8
if err := binary.Read(reader, binary.BigEndian, &length); err != nil {
return err
}
district := make([]byte, length)
if _, err := io.ReadFull(reader, district); err != nil {
return err
}
b.district[code] = &District{
name: string(district),
}
}
return nil
}
func (b *SinaIP) loadIPSegment(data []byte) error {
reader := bytes.NewReader(data)
var count uint32
if err := binary.Read(reader, binary.BigEndian, &count); err != nil {
return err
}
b.segment = make([]*IPSegment, 0, count)
for i := uint32(0); i < count; i++ {
segment := new(IPSegment)
if err := binary.Read(reader, binary.BigEndian, &segment.start); err != nil {
return err
}
if err := binary.Read(reader, binary.BigEndian, &segment.end); err != nil {
return err
}
var (
countryCode uint16
provinceCode uint16
cityCode uint16
ispCode uint16
districtCode uint16
latitude int32
longitude int32
)
if err := binary.Read(reader, binary.BigEndian, &countryCode); err != nil {
return err
} else {
segment.country = b.country[countryCode]
}
if err := binary.Read(reader, binary.BigEndian, &provinceCode); err != nil {
return err
} else {
segment.province = b.province[provinceCode]
}
if err := binary.Read(reader, binary.BigEndian, &cityCode); err != nil {
return err
} else {
segment.city = b.city[cityCode]
}
if err := binary.Read(reader, binary.BigEndian, &ispCode); err != nil {
return err
} else {
segment.isp = b.isp[ispCode]
}
if err := binary.Read(reader, binary.BigEndian, &districtCode); err != nil {
return err
} else {
segment.district = b.district[districtCode]
}
if err := binary.Read(reader, binary.BigEndian, &latitude); err != nil {
return err
} else {
segment.latitude = float64(latitude) / float64(10000)
}
if err := binary.Read(reader, binary.BigEndian, &longitude); err != nil {
return err
} else {
segment.longitude = float64(longitude) / float64(10000)
}
b.segment = append(b.segment, segment)
}
return nil
}

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["expr_test.go"],
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"ast.go",
"check.go",
"eval.go",
"expr.go",
],
importpath = "go-common/app/service/live/broadcast-proxy/expr",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,289 @@
package expr
import (
"bytes"
"fmt"
"strconv"
"strings"
"text/scanner"
"unicode"
)
const (
TokenEOF = -(iota + 1)
TokenIdent
TokenInt
TokenFloat
TokenOperator
)
type lexer struct {
scan scanner.Scanner
token rune
text string
}
func (lex *lexer) getToken() rune {
return lex.token
}
func (lex *lexer) getText() string {
return lex.text
}
func (lex *lexer) next() {
token := lex.scan.Scan()
text := lex.scan.TokenText()
switch token {
case scanner.EOF:
lex.token = TokenEOF
lex.text = text
case scanner.Ident:
lex.token = TokenIdent
lex.text = text
case scanner.Int:
lex.token = TokenInt
lex.text = text
case scanner.Float:
lex.token = TokenFloat
lex.text = text
case '+', '-', '*', '/', '%', '~':
lex.token = TokenOperator
lex.text = text
case '&', '|', '=':
var buffer bytes.Buffer
lex.token = TokenOperator
buffer.WriteRune(token)
next := lex.scan.Peek()
if next == token {
buffer.WriteRune(next)
lex.scan.Scan()
}
lex.text = buffer.String()
case '>', '<', '!':
var buffer bytes.Buffer
lex.token = TokenOperator
buffer.WriteRune(token)
next := lex.scan.Peek()
if next == '=' {
buffer.WriteRune(next)
lex.scan.Scan()
}
lex.text = buffer.String()
default:
if token >= 0 {
lex.token = token
lex.text = text
} else {
msg := fmt.Sprintf("got unknown token:%q, text:%s", lex.token, lex.text)
panic(lexPanic(msg))
}
}
//fmt.Printf("token:%d, text:%s\n", lex.token, lex.text)
}
type lexPanic string
// describe returns a string describing the current token, for use in errors.
func (lex *lexer) describe() string {
switch lex.token {
case TokenEOF:
return "end of file"
case TokenIdent:
return fmt.Sprintf("identifier %s", lex.getText())
case TokenInt, TokenFloat:
return fmt.Sprintf("number %s", lex.getText())
}
return fmt.Sprintf("%q", rune(lex.getToken())) // any other rune
}
func precedence(token rune, text string) int {
if token == TokenOperator {
switch text {
case "~", "!":
return 9
case "*", "/", "%":
return 8
case "+", "-":
return 7
case ">", ">=", "<", "<=":
return 6
case "!=", "==", "=":
return 5
case "&":
return 4
case "|":
return 3
case "&&":
return 2
case "||":
return 1
default:
msg := fmt.Sprintf("unknown operator:%s", text)
panic(lexPanic(msg))
}
}
return 0
}
// ---- parser ----
type ExpressionParser struct {
expression Expr
variable map[string]struct{}
}
func NewExpressionParser() *ExpressionParser {
return &ExpressionParser{
expression: nil,
variable: make(map[string]struct{}),
}
}
// Parse parses the input string as an arithmetic expression.
//
// expr = num a literal number, e.g., 3.14159
// | id a variable name, e.g., x
// | id '(' expr ',' ... ')' a function call
// | '-' expr a unary operator ( + - ! )
// | expr '+' expr a binary operator ( + - * / && & || | == )
//
func (parser *ExpressionParser) Parse(input string) (err error) {
defer func() {
switch x := recover().(type) {
case nil:
// no panic
case lexPanic:
err = fmt.Errorf("%s", x)
default:
// unexpected panic: resume state of panic.
panic(x)
}
}()
lex := new(lexer)
lex.scan.Init(strings.NewReader(input))
lex.scan.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats
lex.scan.IsIdentRune = parser.isIdentRune
lex.next() // initial lookahead
parser.expression = nil
parser.variable = make(map[string]struct{})
e := parser.parseExpr(lex)
if lex.token != scanner.EOF {
return fmt.Errorf("unexpected %s", lex.describe())
}
parser.expression = e
return nil
}
func (parser *ExpressionParser) GetExpr() Expr {
return parser.expression
}
func (parser *ExpressionParser) GetVariable() []string {
variable := make([]string, 0, len(parser.variable))
for v := range parser.variable {
if v != "true" && v != "false" {
variable = append(variable, v)
}
}
return variable
}
func (parser *ExpressionParser) isIdentRune(ch rune, i int) bool {
return ch == '$' || ch == '_' || unicode.IsLetter(ch) || unicode.IsDigit(ch) && i > 0
}
func (parser *ExpressionParser) parseExpr(lex *lexer) Expr {
return parser.parseBinary(lex, 1)
}
// binary = unary ('+' binary)*
// parseBinary stops when it encounters an
// operator of lower precedence than prec1.
func (parser *ExpressionParser) parseBinary(lex *lexer, prec1 int) Expr {
lhs := parser.parseUnary(lex)
for prec := precedence(lex.getToken(), lex.getText()); prec >= prec1; prec-- {
for precedence(lex.getToken(), lex.getText()) == prec {
op := lex.getText()
lex.next() // consume operator
rhs := parser.parseBinary(lex, prec+1)
lhs = binary{op, lhs, rhs}
}
}
return lhs
}
// unary = '+' expr | primary
func (parser *ExpressionParser) parseUnary(lex *lexer) Expr {
if lex.getToken() == TokenOperator {
op := lex.getText()
if op == "+" || op == "-" || op == "~" || op == "!" {
lex.next()
return unary{op, parser.parseUnary(lex)}
} else {
msg := fmt.Sprintf("unary got unknown operator:%s", lex.getText())
panic(lexPanic(msg))
}
}
return parser.parsePrimary(lex)
}
// primary = id
// | id '(' expr ',' ... ',' expr ')'
// | num
// | '(' expr ')'
func (parser *ExpressionParser) parsePrimary(lex *lexer) Expr {
switch lex.token {
case TokenIdent:
id := lex.getText()
lex.next()
if lex.token != '(' {
parser.variable[id] = struct{}{}
return Var(id)
}
lex.next() // consume '('
var args []Expr
if lex.token != ')' {
for {
args = append(args, parser.parseExpr(lex))
if lex.token != ',' {
break
}
lex.next() // consume ','
}
if lex.token != ')' {
msg := fmt.Sprintf("got %q, want ')'", lex.token)
panic(lexPanic(msg))
}
}
lex.next() // consume ')'
return call{id, args}
case TokenFloat:
f, err := strconv.ParseFloat(lex.getText(), 64)
if err != nil {
panic(lexPanic(err.Error()))
}
lex.next() // consume number
return literal{value: f}
case TokenInt:
i, err := strconv.ParseInt(lex.getText(), 10, 64)
if err != nil {
panic(lexPanic(err.Error()))
}
lex.next() // consume number
return literal{value: i}
case '(':
lex.next() // consume '('
e := parser.parseExpr(lex)
if lex.token != ')' {
msg := fmt.Sprintf("got %s, want ')'", lex.describe())
panic(lexPanic(msg))
}
lex.next() // consume ')'
return e
}
msg := fmt.Sprintf("unexpected %s", lex.describe())
panic(lexPanic(msg))
}

View File

@@ -0,0 +1,51 @@
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package expr
import (
"fmt"
)
//!+Check
func (v Var) Check(vars map[Var]interface{}) error {
vars[v] = true
return nil
}
func (literal) Check(vars map[Var]interface{}) error {
return nil
}
func (u unary) Check(vars map[Var]interface{}) error {
return u.x.Check(vars)
}
func (b binary) Check(vars map[Var]interface{}) error {
if err := b.x.Check(vars); err != nil {
return err
}
return b.y.Check(vars)
}
func (c call) Check(vars map[Var]interface{}) error {
arity, ok := numParams[c.fn]
if !ok {
return fmt.Errorf("unknown function %q", c.fn)
}
if len(c.args) != arity {
return fmt.Errorf("call to %s has %d args, want %d",
c.fn, len(c.args), arity)
}
for _, arg := range c.args {
if err := arg.Check(vars); err != nil {
return err
}
}
return nil
}
var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1}
//!-Check

View File

@@ -0,0 +1,505 @@
package expr
import (
"fmt"
"math"
"reflect"
)
type Env map[Var]interface{}
type runtimePanic string
func SafetyEvalBool(expr Expr, env Env) (value bool, err error) {
defer func() {
switch x := recover().(type) {
case nil:
// no panic
case runtimePanic:
value = false
err = fmt.Errorf("%s", x)
default:
// unexpected panic: resume state of panic.
panic(x)
}
}()
if expr == nil {
return false, nil
}
value = ConvertToBool(expr.Eval(env))
return
}
func (v Var) Eval(env Env) reflect.Value {
switch v {
case "true":
return reflect.ValueOf(true)
case "false":
return reflect.ValueOf(false)
default:
if i, ok := env[v]; ok {
return reflect.ValueOf(i)
}
panic(runtimePanic(fmt.Sprintf("undefined variable: %s", v)))
}
}
func (l literal) Eval(_ Env) reflect.Value {
return reflect.ValueOf(l.value)
}
func (u unary) Eval(env Env) reflect.Value {
switch u.op {
case "+":
return unaryPlus(u.x.Eval(env))
case "-":
return unaryMinus(u.x.Eval(env))
case "!":
return logicalNegation(u.x.Eval(env))
case "~":
return bitwiseComplement(u.x.Eval(env))
}
panic(runtimePanic(fmt.Sprintf("unsupported unary operator: %q", u.op)))
}
func (b binary) Eval(env Env) reflect.Value {
switch b.op {
case "+":
return addition(b.x.Eval(env), b.y.Eval(env))
case "-":
return subtraction(b.x.Eval(env), b.y.Eval(env))
case "*":
return multiplication(b.x.Eval(env), b.y.Eval(env))
case "/":
return division(b.x.Eval(env), b.y.Eval(env))
case "%":
return modulus(b.x.Eval(env), b.y.Eval(env))
case "&":
return bitwiseAnd(b.x.Eval(env), b.y.Eval(env))
case "&&":
return logicalAnd(b.x.Eval(env), b.y.Eval(env))
case "|":
return bitwiseOr(b.x.Eval(env), b.y.Eval(env))
case "||":
return logicalOr(b.x.Eval(env), b.y.Eval(env))
case "=", "==":
return comparisonEqual(b.x.Eval(env), b.y.Eval(env))
case ">":
return comparisonGreater(b.x.Eval(env), b.y.Eval(env))
case ">=":
return comparisonGreaterOrEqual(b.x.Eval(env), b.y.Eval(env))
case "<":
return comparisonLess(b.x.Eval(env), b.y.Eval(env))
case "<=":
return comparisonLessOrEqual(b.x.Eval(env), b.y.Eval(env))
case "!=":
return comparisonNotEqual(b.x.Eval(env), b.y.Eval(env))
}
panic(runtimePanic(fmt.Sprintf("unsupported binary operator: %q", b.op)))
}
func (c call) Eval(env Env) reflect.Value {
switch c.fn {
case "pow":
return reflect.ValueOf(math.Pow(ConvertToFloat(c.args[0].Eval(env)), ConvertToFloat(c.args[1].Eval(env))))
case "sin":
return reflect.ValueOf(math.Sin(ConvertToFloat(c.args[0].Eval(env))))
case "sqrt":
return reflect.ValueOf(math.Sqrt(ConvertToFloat(c.args[0].Eval(env))))
}
panic(runtimePanic(fmt.Sprintf("unsupported function call: %s", c.fn)))
}
func ConvertToBool(v reflect.Value) bool {
switch v.Kind() {
case reflect.Bool:
return v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return v.Uint() != 0
case reflect.Float32, reflect.Float64:
return v.Float() != 0
default:
panic(runtimePanic(fmt.Sprintf("cannot convert data type: %s to bool", v.Kind().String())))
}
}
func ConvertToInt(v reflect.Value) int64 {
switch v.Kind() {
case reflect.Bool:
if v.Bool() {
return 1
} else {
return 0
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return int64(v.Uint())
case reflect.Float32, reflect.Float64:
return int64(v.Float())
default:
panic(runtimePanic(fmt.Sprintf("cannot convert data type: %s to int", v.Kind().String())))
}
}
func ConvertToUint(v reflect.Value) uint64 {
switch v.Kind() {
case reflect.Bool:
if v.Bool() {
return 1
} else {
return 0
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return uint64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return v.Uint()
case reflect.Float32, reflect.Float64:
return uint64(v.Float())
default:
panic(runtimePanic(fmt.Sprintf("cannot convert data type: %s to uint", v.Kind().String())))
}
}
func ConvertToFloat(v reflect.Value) float64 {
switch v.Kind() {
case reflect.Bool:
if v.Bool() {
return 1
} else {
return 0
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return float64(v.Uint())
case reflect.Float32, reflect.Float64:
return v.Float()
default:
panic(runtimePanic(fmt.Sprintf("cannot convert data type: %s to float", v.Kind().String())))
}
}
func unaryPlus(v reflect.Value) reflect.Value {
return v
}
func unaryMinus(v reflect.Value) reflect.Value {
switch v.Kind() {
case reflect.Bool:
return v
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(-v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return reflect.ValueOf(-v.Uint())
case reflect.Float32, reflect.Float64:
return reflect.ValueOf(-v.Float())
default:
panic(runtimePanic(fmt.Sprintf("unary minus not support type: %s", v.Kind().String())))
}
}
func logicalNegation(v reflect.Value) reflect.Value {
return reflect.ValueOf(!ConvertToBool(v))
}
func bitwiseComplement(v reflect.Value) reflect.Value {
switch v.Kind() {
case reflect.Bool:
return reflect.ValueOf(!v.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return reflect.ValueOf(^v.Int())
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return reflect.ValueOf(^v.Uint())
case reflect.Float32, reflect.Float64:
panic(runtimePanic("cannot eval ~ for float"))
default:
panic(runtimePanic(fmt.Sprintf("bitwise complement not support type: %s", v.Kind().String())))
}
}
func typeLevel(k reflect.Kind) int {
switch k {
case reflect.Float32, reflect.Float64:
return 4
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return 3
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return 2
case reflect.Bool:
return 1
default:
return 0
}
}
func typeAscend(a reflect.Kind, b reflect.Kind) reflect.Kind {
if typeLevel(a) >= typeLevel(b) {
return a
} else {
return b
}
}
func addition(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) + ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) + ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) + ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) + ConvertToInt(right)
return reflect.ValueOf(r != 0)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support addition", left.Kind().String(), right.Kind().String())))
}
}
func subtraction(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) - ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) - ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) - ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) - ConvertToInt(right)
return reflect.ValueOf(r != 0)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support subtraction", left.Kind().String(), right.Kind().String())))
}
}
func multiplication(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) * ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) * ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) * ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) * ConvertToInt(right)
return reflect.ValueOf(r != 0)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support multiplication", left.Kind().String(), right.Kind().String())))
}
}
func division(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) / ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) / ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) / ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) / ConvertToInt(right)
return reflect.ValueOf(r != 0)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support division", left.Kind().String(), right.Kind().String())))
}
}
func modulus(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) % ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) % ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) % ConvertToInt(right)
return reflect.ValueOf(r != 0)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support division", left.Kind().String(), right.Kind().String())))
}
}
func bitwiseAnd(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) & ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) & ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToBool(left) && ConvertToBool(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support bitwise and", left.Kind().String(), right.Kind().String())))
}
}
func bitwiseOr(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) | ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) | ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToBool(left) || ConvertToBool(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support bitwise or", left.Kind().String(), right.Kind().String())))
}
}
func logicalAnd(left reflect.Value, right reflect.Value) reflect.Value {
r := ConvertToBool(left) && ConvertToBool(right)
return reflect.ValueOf(r)
}
func logicalOr(left reflect.Value, right reflect.Value) reflect.Value {
r := ConvertToBool(left) || ConvertToBool(right)
return reflect.ValueOf(r)
}
func comparisonEqual(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) == ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) == ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) == ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) == ConvertToInt(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support comparison equal", left.Kind().String(), right.Kind().String())))
}
}
func comparisonNotEqual(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) != ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) != ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) != ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) != ConvertToInt(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support comparison not equal", left.Kind().String(), right.Kind().String())))
}
}
func comparisonGreater(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) > ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) > ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) > ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) > ConvertToInt(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support comparison greater", left.Kind().String(), right.Kind().String())))
}
}
func comparisonGreaterOrEqual(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) >= ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) >= ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) >= ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) >= ConvertToInt(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support comparison greater or equal", left.Kind().String(), right.Kind().String())))
}
}
func comparisonLess(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) < ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) < ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) < ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) < ConvertToInt(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support comparison less", left.Kind().String(), right.Kind().String())))
}
}
func comparisonLessOrEqual(left reflect.Value, right reflect.Value) reflect.Value {
k := typeAscend(left.Kind(), right.Kind())
switch k {
case reflect.Float32, reflect.Float64:
r := ConvertToFloat(left) <= ConvertToFloat(right)
return reflect.ValueOf(r)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
r := ConvertToUint(left) <= ConvertToUint(right)
return reflect.ValueOf(r)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
r := ConvertToInt(left) <= ConvertToInt(right)
return reflect.ValueOf(r)
case reflect.Bool:
r := ConvertToInt(left) <= ConvertToInt(right)
return reflect.ValueOf(r)
default:
panic(runtimePanic(fmt.Sprintf("type %s and %s not support comparison less or equal", left.Kind().String(), right.Kind().String())))
}
}

View File

@@ -0,0 +1,37 @@
package expr
import "reflect"
// A Var identifies a variable, e.g., x.
type Var string
// A literal is a numeric constant, e.g., 3.141.
type literal struct {
value interface{}
}
// An Expr is an arithmetic expression.
type Expr interface {
// Eval returns the value of this Expr in the environment env.
Eval(env Env) reflect.Value
// Check reports errors in this Expr and adds its Vars to the set.
Check(vars map[Var]interface{}) error
}
// A unary represents a unary operator expression, e.g., -x.
type unary struct {
op string // one of '+', '-', '!', '~'
x Expr
}
// A binary represents a binary operator expression, e.g., x+y.
type binary struct {
op string
x, y Expr
}
// A call represents a function call expression, e.g., sin(x).
type call struct {
fn string // one of "pow", "sin", "sqrt"
args []Expr
}

View File

@@ -0,0 +1,49 @@
package expr
import (
"fmt"
"testing"
)
func TestExpr(t *testing.T) {
tests := []struct {
expr string
env Env
want string
}{
{"$1 > 80 && $2 <9", Env{"$1": 100, "$2": 2}, "true"},
{"$1 % 10", Env{"$1": 104, "$2": 2}, "4"},
{"pow(x, false) + pow(y, false)", Env{"x": 12, "y": 1}, "2"},
{"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"},
{"5.0 / 9 * (F - 32)", Env{"F": -40}, "-40"},
{"5.0 / 9 * (F - 32)", Env{"F": 32}, "0"},
{"5.0 / 9 * (F - 32)", Env{"F": 212}, "100"},
////!-Eval
//// additional tests that don't appear in the book
{"-1 + -x", Env{"x": 1}, "-2"},
{"-1 - x", Env{"x": 1}, "-2"},
{"a >= 10", Env{"a": 15}, "true"},
{"b >= sin(10) && a < 1", Env{"a": 9, "b": 10}, "false"},
{"!!!true", Env{"a": 9, "b": 10}, "false"},
//!+Eval
}
var prevExpr string
parser := NewExpressionParser()
for _, test := range tests {
// Print expr only when it changes.
if test.expr != prevExpr {
t.Logf("\n%s\n", test.expr)
prevExpr = test.expr
}
if err := parser.Parse(test.expr); err != nil {
t.Error(err) // parse error
continue
}
got := fmt.Sprintf("%v", parser.GetExpr().Eval(test.env))
t.Logf("\t%v => %s\n", test.env, got)
if got != test.want {
t.Errorf("%s.Eval() in %v = %q, want %q\n",
test.expr, test.env, got, test.want)
}
}
}

View File

@@ -0,0 +1,31 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"log.go",
"sven.go",
],
importpath = "go-common/app/service/live/broadcast-proxy/grocery",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
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,44 @@
# sven.go
可能是最最精简的配置中心 SDK 了,简单接入。
# 集成
```go
package main
import (
"nano-repo/grocery"
"log"
)
func main() {
sven, err := grocery.NewSvenClient("13586", "sh001", "dev",
"docker-1", "7c41388b593d562120bec1bcb355e538")
if err != nil {
panic(err)
}
//Get the latest configuration with Config method anytime
c := sven.Config()
log.Printf("Initial version:%d", c.Version)
log.Printf("Initial config :%v", c.Config)
go func(){
//Get configuration change event with ConfigNotify method
for config := range sven.ConfigNotify() {
log.Printf("New version:%d", config.Version)
log.Printf("New config: %v", config.Config)
}
}()
go func(){
for e := range sven.LogNotify() {
log.Printf("Sven log return, level:%v, message:%v", e.Level, e.Message)
}
}()
quit := make(chan struct{})
<- quit
}
```

View File

@@ -0,0 +1,34 @@
package grocery
type level int
const (
FINEST level = iota
FINE
DEBUG
TRACE
INFO
WARNING
ERROR
CRITICAL
)
var (
kLevelStrings = [...]string{"FINEST", "FINE", "DEBUG", "TRACE", "INFO", "WARNING", "ERROR", "CRITICAL"}
)
func (l level) String() string {
if l < 0 || int(l) > len(kLevelStrings) {
return "UNKNOWN"
}
return kLevelStrings[int(l)]
}
type LogRecord struct {
Level level // The log level
Message string // The log message
}
func (r *LogRecord) String() string {
return r.Message
}

View File

@@ -0,0 +1,314 @@
package grocery
import (
"context"
"encoding/json"
"errors"
"io/ioutil"
"net"
"net/http"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
const (
kLogChanSize = 0xFF
kSvenCheckTimeout = time.Minute
kSvenGetTimeout = 30 * time.Second
kSvenHost = "http://config.bilibili.co"
kSvenCheckAPI = kSvenHost + "/config/v2/check"
kSvenGetAPI = kSvenHost + "/config/v2/get"
kDefaultHost = "invalid-host-name"
kDefaultIP = "127.0.0.1"
kCodeNotModified = -304
)
type SvenClient struct {
treeID string
zone string
env string
build string
token string
host string
ip string
config atomic.Value
log chan *LogRecord
notify chan *SvenConfig
changeSignal chan struct{}
httpClient *http.Client
ctx context.Context
cancel context.CancelFunc
closeSignal chan struct{}
configLoadWg sync.WaitGroup
configNotifyWg sync.WaitGroup
}
type SvenConfig struct {
Version int
Config map[string]string
}
func NewSvenClient(treeID string, zone string, env string, build string, token string) (*SvenClient, error) {
host, err := os.Hostname()
if err != nil {
host = kDefaultHost
}
ip := kDefaultIP
addr, err := net.InterfaceAddrs()
if err == nil {
for _, a := range addr {
if n, ok := a.(*net.IPNet); ok && !n.IP.IsLoopback() && n.IP.To4() != nil {
ip = n.IP.String()
}
}
}
c := &SvenClient{
treeID: treeID,
zone: zone,
env: env,
build: build,
token: token,
host: host,
ip: ip,
log: make(chan *LogRecord, kLogChanSize),
notify: make(chan *SvenConfig),
changeSignal: make(chan struct{}, 1),
httpClient: new(http.Client),
closeSignal: make(chan struct{}),
}
c.ctx, c.cancel = context.WithCancel(context.Background())
version, config, err := c.svenConfigSync()
if err != nil {
return nil, err
}
c.config.Store(&SvenConfig{
Version: version,
Config: config,
})
c.changeSignal <- struct{}{}
c.configLoadWg.Add(1)
go func() {
defer c.configLoadWg.Done()
c.configLoadProcess()
}()
c.configNotifyWg.Add(1)
go func() {
defer c.configNotifyWg.Done()
c.configNotifyProcess()
}()
return c, nil
}
func (c *SvenClient) Close() {
close(c.closeSignal)
c.cancel()
c.configLoadWg.Wait()
close(c.changeSignal)
c.configNotifyWg.Wait()
}
func (c *SvenClient) Config() *SvenConfig {
result := &SvenConfig{
Version: -1,
Config: make(map[string]string),
}
if config, ok := c.config.Load().(*SvenConfig); ok {
result.Version = config.Version
for k, v := range config.Config {
result.Config[k] = v
}
}
return result
}
func (c *SvenClient) ConfigNotify() <-chan *SvenConfig {
return c.notify
}
func (c *SvenClient) LogNotify() <-chan *LogRecord {
return c.log
}
func (c *SvenClient) configLoadProcess() {
for {
select {
case <-c.closeSignal:
return
default:
}
current, ok := c.config.Load().(*SvenConfig)
if !ok {
current = &SvenConfig{
Version: -1,
}
}
version, err := c.svenCheckVersion(current.Version)
//config not modified
if version == current.Version {
c.postLog(INFO, "config not modified")
continue
}
if err != nil {
c.postLog(ERROR, err.Error())
continue
}
if current.Version == version {
continue
}
config, err := c.svenGetConfig(version)
if err != nil {
c.postLog(ERROR, err.Error())
continue
}
c.config.Store(&SvenConfig{
Version: version,
Config: config,
})
select {
case c.changeSignal <- struct{}{}:
default:
}
}
}
func (c *SvenClient) configNotifyProcess() {
for range c.changeSignal {
select {
case <-c.closeSignal:
return
case c.notify <- c.Config():
}
}
}
func (c *SvenClient) postLog(lv level, msg string) {
select {
case c.log <- &LogRecord{Level: lv, Message: msg}:
default:
}
}
func (c *SvenClient) svenConfigSync() (int, map[string]string, error) {
version, err := c.svenCheckVersion(-1)
if err != nil {
return -1, nil, err
}
config, err := c.svenGetConfig(version)
if err != nil {
return -1, nil, err
}
return version, config, nil
}
func (c *SvenClient) svenCheckVersion(version int) (int, error) {
var err error
req, err := http.NewRequest("GET", kSvenCheckAPI, nil)
if err != nil {
return -1, err
}
q := req.URL.Query()
q.Add("build", c.build)
q.Add("hostname", c.host)
q.Add("ip", c.ip)
q.Add("service", strings.Join([]string{c.treeID, c.env, c.zone}, "_"))
q.Add("token", c.token)
q.Add("version", strconv.Itoa(version))
req.URL.RawQuery = q.Encode()
ctx, cancel := context.WithTimeout(c.ctx, kSvenCheckTimeout)
defer cancel()
req = req.WithContext(ctx)
resp, err := c.httpClient.Do(req)
if err != nil {
return -1, err
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
return -1, err
}
var svenCheckRespBody struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
Version int `json:"version"`
} `json:"data"`
}
if err = json.Unmarshal(result, &svenCheckRespBody); err != nil {
return -1, err
}
if svenCheckRespBody.Code != 0 {
if svenCheckRespBody.Code == kCodeNotModified {
return version, nil
}
return -1, errors.New(svenCheckRespBody.Message)
}
return svenCheckRespBody.Data.Version, nil
}
func (c *SvenClient) svenGetConfig(version int) (map[string]string, error) {
var err error
req, err := http.NewRequest("GET", kSvenGetAPI, nil)
if err != nil {
return nil, err
}
q := req.URL.Query()
q.Add("build", c.build)
q.Add("hostname", c.host)
q.Add("ip", c.ip)
q.Add("service", strings.Join([]string{c.treeID, c.env, c.zone}, "_"))
q.Add("token", c.token)
q.Add("version", strconv.Itoa(version))
req.URL.RawQuery = q.Encode()
ctx, cancel := context.WithTimeout(c.ctx, kSvenGetTimeout)
defer cancel()
req = req.WithContext(ctx)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var svenGetRespBody struct {
Code int `json:"code"`
Message string `json:"message"`
TTL int `json:"ttl"`
Data struct {
Version int `json:"version"`
MD5 string `json:"md5"`
Content string `json:"content"`
} `json:"data"`
}
if err = json.Unmarshal(result, &svenGetRespBody); err != nil {
return nil, err
}
if svenGetRespBody.Code != 0 {
return nil, errors.New(svenGetRespBody.Message)
}
var configContent []struct {
Name string `json:"name"`
Config string `json:"config"`
}
if err = json.Unmarshal([]byte(svenGetRespBody.Data.Content), &configContent); err != nil {
return nil, err
}
config := make(map[string]string)
for _, c := range configContent {
config[c.Name] = strings.TrimSpace(c.Config)
}
return config, nil
}

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, "/")
}

View File

@@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["grpc.go"],
importpath = "go-common/app/service/live/broadcast-proxy/service",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/live/broadcast-proxy/api/v1:go_default_library",
"//app/service/live/broadcast-proxy/server:go_default_library",
"//app/service/live/broadcast-proxy/service/v1:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/time:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/service/live/broadcast-proxy/service/v1:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,27 @@
package service
import (
"errors"
v1pb "go-common/app/service/live/broadcast-proxy/api/v1"
"go-common/app/service/live/broadcast-proxy/server"
v1srv "go-common/app/service/live/broadcast-proxy/service/v1"
"go-common/library/net/rpc/warden"
xtime "go-common/library/time"
"google.golang.org/grpc"
"time"
)
func NewGrpcService(p *server.BroadcastProxy, d *server.CometDispatcher) (*warden.Server, error) {
if p == nil || d == nil {
return nil, errors.New("empty proxy")
}
ws := warden.NewServer(&warden.ServerConfig{
Timeout: xtime.Duration(30 * time.Second),
}, grpc.MaxRecvMsgSize(1024 * 1024 * 1024), grpc.MaxSendMsgSize(1024 * 1024 * 1024))
v1pb.RegisterDanmakuServer(ws.Server(), v1srv.NewDanmakuService(p, d))
ws, err := ws.Start()
if err != nil {
return nil, err
}
return ws, nil
}

View File

@@ -0,0 +1,34 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["danmaku.go"],
importpath = "go-common/app/service/live/broadcast-proxy/service/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/service/live/broadcast-proxy/api/v1:go_default_library",
"//app/service/live/broadcast-proxy/server:go_default_library",
"//library/ecode:go_default_library",
"//library/log: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,410 @@
package v1
import (
"bytes"
"compress/gzip"
"context"
"encoding/gob"
"encoding/json"
v1pb "go-common/app/service/live/broadcast-proxy/api/v1"
"go-common/app/service/live/broadcast-proxy/server"
"go-common/library/ecode"
"go-common/library/log"
"io/ioutil"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"time"
)
const (
kRequestIsNil = "request is nil"
)
// DanmakuService struct
type DanmakuService struct {
proxy *server.BroadcastProxy
dispatcher *server.CometDispatcher
}
//NewDanmakuService init
func NewDanmakuService(p *server.BroadcastProxy, d *server.CometDispatcher) (s *DanmakuService) {
s = &DanmakuService{
proxy: p,
dispatcher: d,
}
return s
}
func (s *DanmakuService) writeLog(method string, begin time.Time, req interface{}, resp interface{}) {
end := time.Now()
log.Info("method %s, request:%v, response:%v, time cost:%s", method, req, resp, end.Sub(begin).String())
}
// RoomMessage implementation
func (s *DanmakuService) RoomMessage(ctx context.Context, req *v1pb.RoomMessageRequest) (resp *v1pb.GeneralResponse, err error) {
resp = &v1pb.GeneralResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
httpRequest, err := http.NewRequest("POST", "/dm/1/push", strings.NewReader(req.Message))
if err != nil {
return resp, ecode.Error(-2, err.Error())
}
//httpRequest = httpRequest.WithContext(ctx)
q := httpRequest.URL.Query()
q.Add("cid", strconv.FormatInt(int64(req.RoomId), 10))
q.Add("ensure", strconv.FormatInt(int64(req.Ensure), 10))
httpRequest.URL.RawQuery = q.Encode()
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-3, "remote http response code:%s", httpResponse.Status)
}
result, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-4, err.Error())
}
var jsonRespBody struct {
Code int `json:"ret"`
}
if err = json.Unmarshal(result, &jsonRespBody); err != nil {
return resp, ecode.Error(-5, err.Error())
}
if jsonRespBody.Code != 1 {
return resp, ecode.Error(-6, "internal server error")
}
return resp, nil
}
// BroadcastMessage implementation
func (s *DanmakuService) BroadcastMessage(ctx context.Context, req *v1pb.BroadcastMessageRequest) (resp *v1pb.GeneralResponse, err error) {
resp = &v1pb.GeneralResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
httpRequest, err := http.NewRequest("POST", "/dm/1/push/all", strings.NewReader(req.Message))
if err != nil {
return resp, ecode.Error(-2, err.Error())
}
var exclude strings.Builder
if len(req.ExcludeRoomId) > 0 {
exclude.WriteString(strconv.FormatInt(int64(req.ExcludeRoomId[0]), 10))
}
if len(req.ExcludeRoomId) > 1 {
for _, excludeRoom := range req.ExcludeRoomId[1:] {
exclude.WriteByte(',')
exclude.WriteString(strconv.FormatInt(int64(excludeRoom), 10))
}
}
//httpRequest = httpRequest.WithContext(ctx)
q := httpRequest.URL.Query()
q.Add("exclude_room", exclude.String())
httpRequest.URL.RawQuery = q.Encode()
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-5, "remote http response code:%s", httpResponse.Status)
}
result, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-3, err.Error())
}
var jsonRespBody struct {
Code int `json:"ret"`
}
if err = json.Unmarshal(result, &jsonRespBody); err != nil {
return resp, ecode.Error(-5, err.Error())
}
if jsonRespBody.Code != 1 {
return resp, ecode.Error(-4, "internal server error")
}
return resp, nil
}
// MultiRoomMessage implementation
func (s *DanmakuService) MultiRoomMessage(ctx context.Context, req *v1pb.MultiRoomMessageRequest) (resp *v1pb.GeneralResponse, err error) {
resp = &v1pb.GeneralResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
jsonRequestData, err := json.Marshal(req)
if err != nil {
return resp, ecode.Error(-2, err.Error())
}
var httpRequestBuffer bytes.Buffer
gzipWriter, err := gzip.NewWriterLevel(&httpRequestBuffer, gzip.BestCompression)
if err != nil {
return resp, ecode.Error(-3, err.Error())
}
gzipWriter.Write(jsonRequestData)
gzipWriter.Close()
httpRequest, err := http.NewRequest("POST", "/dm/v1/push/multi_room", &httpRequestBuffer)
if err != nil {
return resp, ecode.Error(-4, err.Error())
}
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-7, "remote http response code:%s", httpResponse.Status)
}
result, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-5, err.Error())
}
var jsonRespBody struct {
Code int `json:"code"`
Message string `json:"msg"`
}
if err = json.Unmarshal(result, &jsonRespBody); err != nil {
return resp, ecode.Error(-6, err.Error())
}
if jsonRespBody.Code != 0 {
return resp, ecode.Error(ecode.Code(jsonRespBody.Code), jsonRespBody.Message)
}
return resp, nil
}
// BatchRoomMessage implementation
func (s *DanmakuService) BatchRoomMessage(ctx context.Context, req *v1pb.BatchRoomMessageRequest) (resp *v1pb.GeneralResponse, err error) {
resp = &v1pb.GeneralResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
jsonRequestData, err := json.Marshal(req)
if err != nil {
return resp, ecode.Error(-2, err.Error())
}
var httpRequestBuffer bytes.Buffer
gzipWriter, err := gzip.NewWriterLevel(&httpRequestBuffer, gzip.BestCompression)
if err != nil {
return resp, ecode.Error(-3, err.Error())
}
gzipWriter.Write(jsonRequestData)
gzipWriter.Close()
httpRequest, err := http.NewRequest("POST", "/dm/v1/push/multi_msg", &httpRequestBuffer)
if err != nil {
return resp, ecode.Error(-4, err.Error())
}
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-7, "remote http response code:%s", httpResponse.Status)
}
result, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-5, err.Error())
}
var jsonRespBody struct {
Code int `json:"code"`
Message string `json:"msg"`
}
if err = json.Unmarshal(result, &jsonRespBody); err != nil {
return resp, ecode.Error(-6, err.Error())
}
if jsonRespBody.Code != 0 {
return resp, ecode.Error(ecode.Code(jsonRespBody.Code), jsonRespBody.Message)
}
return resp, nil
}
// UserMessage implementation
func (s *DanmakuService) UserMessage(ctx context.Context, req *v1pb.UserMessageRequest) (resp *v1pb.GeneralResponse, err error) {
resp = &v1pb.GeneralResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
httpRequest, err := http.NewRequest("POST", "/dm/v1/push/user_msg", strings.NewReader(req.Message))
if err != nil {
return resp, ecode.Error(-2, err.Error())
}
//httpRequest = httpRequest.WithContext(ctx)
q := httpRequest.URL.Query()
q.Add("uid", strconv.FormatInt(int64(req.UserId), 10))
httpRequest.URL.RawQuery = q.Encode()
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-7, "remote http response code:%s", httpResponse.Status)
}
result, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-3, err.Error())
}
var jsonRespBody struct {
Code int `json:"code"`
Message string `json:"msg"`
}
if err = json.Unmarshal(result, &jsonRespBody); err != nil {
return resp, ecode.Error(-4, err.Error())
}
if jsonRespBody.Code != 0 {
return resp, ecode.Error(ecode.Code(jsonRespBody.Code), jsonRespBody.Message)
}
return resp, nil
}
// BatchUserMessage implementation
func (s *DanmakuService) BatchUserMessage(ctx context.Context, req *v1pb.BatchUserMessageRequest) (resp *v1pb.GeneralResponse, err error) {
resp = &v1pb.GeneralResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
jsonRequestData, err := json.Marshal(req)
if err != nil {
return resp, ecode.Error(-2, err.Error())
}
var httpRequestBuffer bytes.Buffer
gzipWriter, err := gzip.NewWriterLevel(&httpRequestBuffer, gzip.BestCompression)
if err != nil {
return resp, ecode.Error(-3, err.Error())
}
gzipWriter.Write(jsonRequestData)
gzipWriter.Close()
httpRequest, err := http.NewRequest("POST", "/dm/v1/push/multi_user_msg", &httpRequestBuffer)
if err != nil {
return resp, ecode.Error(-4, err.Error())
}
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-7, "remote http response code:%s", httpResponse.Status)
}
result, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-5, err.Error())
}
var jsonRespBody struct {
Code int `json:"code"`
Message string `json:"msg"`
}
if err = json.Unmarshal(result, &jsonRespBody); err != nil {
return resp, ecode.Error(-6, err.Error())
}
if jsonRespBody.Code != 0 {
return resp, ecode.Error(ecode.Code(jsonRespBody.Code), jsonRespBody.Message)
}
return resp, nil
}
// Dispatch implementation
func (s *DanmakuService) Dispatch(ctx context.Context, req *v1pb.DispatchRequest) (resp *v1pb.DispatchResponse, err error) {
resp = &v1pb.DispatchResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
defer s.writeLog("Dispatch", time.Now(), req, resp)
ip, host := s.dispatcher.Dispatch(req.UserIp, req.UserId)
resp.Host = host
resp.Ip = ip
return resp, nil
}
func (s *DanmakuService) SetAngryValue(ctx context.Context, req *v1pb.SetAngryValueRequest) (resp *v1pb.SetAngryValueResponse, err error) {
resp = &v1pb.SetAngryValueResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
defer s.writeLog("SetAngryValue", time.Now(), req, resp)
if len(req.AngryValue) == 0 {
return resp, ecode.Error(-2, "empty angry value")
}
var rawAngryValueBuffer bytes.Buffer
enc := gob.NewEncoder(&rawAngryValueBuffer)
if err = enc.Encode(req.AngryValue); err != nil {
return resp, ecode.Error(-3, err.Error())
}
var httpRequestBuffer bytes.Buffer
gzipWriter, _ := gzip.NewWriterLevel(&httpRequestBuffer, gzip.BestCompression)
gzipWriter.Write(rawAngryValueBuffer.Bytes())
gzipWriter.Close()
httpRequest, err := http.NewRequest("POST", "/dm/x/internal/v2/set_angry_value", &httpRequestBuffer)
if err != nil {
return resp, ecode.Error(-4, err.Error())
}
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-5, "remote http response code:%s", httpResponse.Status)
}
result, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-6, err.Error())
}
var jsonRespBody struct {
Code int `json:"code"`
Message string `json:"msg"`
}
if err = json.Unmarshal(result, &jsonRespBody); err != nil {
return resp, ecode.Error(-7, err.Error())
}
if jsonRespBody.Code != 0 {
return resp, ecode.Error(ecode.Code(jsonRespBody.Code), jsonRespBody.Message)
}
return resp, nil
}
func (s *DanmakuService) GetRoomOnlineCount(ctx context.Context, req *v1pb.GetRoomOnlineCountRequest) (resp *v1pb.GetRoomOnlineCountResponse, err error) {
resp = &v1pb.GetRoomOnlineCountResponse{}
if req == nil {
return resp, ecode.Error(-1, kRequestIsNil)
}
defer s.writeLog("GetRoomOnlineCount", time.Now(), req, resp)
if len(req.RoomId) == 0 {
return resp, ecode.Error(-2, "empty angry value")
}
var rawRoomIdBuffer bytes.Buffer
enc := gob.NewEncoder(&rawRoomIdBuffer)
if err = enc.Encode(req.RoomId); err != nil {
return resp, ecode.Error(-3, err.Error())
}
var httpRequestBuffer bytes.Buffer
gzipWriter, _ := gzip.NewWriterLevel(&httpRequestBuffer, gzip.BestCompression)
gzipWriter.Write(rawRoomIdBuffer.Bytes())
gzipWriter.Close()
httpRequest, err := http.NewRequest("POST", "/dm/x/internal/v3/get_room_online_count", &httpRequestBuffer)
if err != nil {
return resp, ecode.Error(-2, err.Error())
}
httpRecorder := httptest.NewRecorder()
s.proxy.HandleRequest(httpRecorder, httpRequest)
httpResponse := httpRecorder.Result()
defer httpResponse.Body.Close()
if httpResponse.StatusCode != http.StatusOK {
return resp, ecode.Errorf(-3, "remote http response code:%s", httpResponse.Status)
}
reader, err := gzip.NewReader(httpResponse.Body)
if err != nil {
return resp, ecode.Error(-4, err.Error())
}
defer reader.Close()
dec := gob.NewDecoder(reader)
var resultData struct {
Code int
Message string
Data map[uint64]uint64
}
if err = dec.Decode(&resultData); err != nil {
return resp, ecode.Error(-5, err.Error())
}
if resultData.Code != 0 {
return resp, ecode.Error(ecode.Code(resultData.Code), resultData.Message)
}
resp.RoomOnlineCount = resultData.Data
return resp, nil
}