mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-04-29 16:58:34 +00:00
Least load balancer (#2999)
* v5: Health Check & LeastLoad Strategy (rebased from 2c5a71490368500a982018a74a6d519c7e121816) Some changes will be necessary to integrate it into V2Ray * Update proto * parse duration conf with time.Parse() * moving health ping to observatory as a standalone component * moving health ping to observatory as a standalone component: auto generated file * add initialization for health ping * incorporate changes in router implementation * support principle target output * add v4 json support for BurstObservatory & fix balancer reference * update API command * remove cancelled API * return zero length value when observer is not found * remove duplicated targeted dispatch * adjust test with updated structure * bug fix for observer * fix strategy selector * fix strategy least load * Fix ticker usage ticker.Close does not close ticker.C * feat: Replace default Health Ping URL to HTTPS (#1991) * fix selectLeastLoad() returns wrong number of nodes (#2083) * Test: fix leastload strategy unit test * fix(router): panic caused by concurrent map read and write (#2678) * Clean up code --------- Co-authored-by: Jebbs <qjebbs@gmail.com> Co-authored-by: Shelikhoo <xiaokangwang@outlook.com> Co-authored-by: 世界 <i@sekai.icu> Co-authored-by: Bernd Eichelberger <46166740+4-FLOSS-Free-Libre-Open-Source-Software@users.noreply.github.com> Co-authored-by: 秋のかえで <autmaple@protonmail.com> Co-authored-by: Rinka <kujourinka@gmail.com>
This commit is contained in:
parent
bf02392969
commit
fa5d7a255b
105 changed files with 3523 additions and 429 deletions
|
@ -15,6 +15,8 @@ var CmdAPI = &base.Command{
|
|||
cmdGetStats,
|
||||
cmdQueryStats,
|
||||
cmdSysStats,
|
||||
cmdBalancerInfo,
|
||||
cmdBalancerOverride,
|
||||
cmdAddInbounds,
|
||||
cmdAddOutbounds,
|
||||
cmdRemoveInbounds,
|
||||
|
|
108
main/commands/all/api/balancer_info.go
Normal file
108
main/commands/all/api/balancer_info.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
routerService "github.com/xtls/xray-core/app/router/command"
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
)
|
||||
|
||||
// TODO: support "-json" flag for json output
|
||||
var cmdBalancerInfo = &base.Command{
|
||||
CustomFlags: true,
|
||||
UsageLine: "{{.Exec}} api bi [--server=127.0.0.1:8080] [balancer]...",
|
||||
Short: "balancer information",
|
||||
Long: `
|
||||
Get information of specified balancers, including health, strategy
|
||||
and selecting. If no balancer tag specified, get information of
|
||||
all balancers.
|
||||
|
||||
> Make sure you have "RoutingService" set in "config.api.services"
|
||||
of server config.
|
||||
|
||||
Arguments:
|
||||
|
||||
-json
|
||||
Use json output.
|
||||
|
||||
-s, -server <server:port>
|
||||
The API server address. Default 127.0.0.1:8080
|
||||
|
||||
-t, -timeout <seconds>
|
||||
Timeout seconds to call API. Default 3
|
||||
|
||||
Example:
|
||||
|
||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 balancer1 balancer2
|
||||
`,
|
||||
Run: executeBalancerInfo,
|
||||
}
|
||||
|
||||
func executeBalancerInfo(cmd *base.Command, args []string) {
|
||||
setSharedFlags(cmd)
|
||||
cmd.Flag.Parse(args)
|
||||
|
||||
conn, ctx, close := dialAPIServer()
|
||||
defer close()
|
||||
|
||||
client := routerService.NewRoutingServiceClient(conn)
|
||||
r := &routerService.GetBalancerInfoRequest{Tag: args[0]}
|
||||
resp, err := client.GetBalancerInfo(ctx, r)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to get health information: %s", err)
|
||||
}
|
||||
|
||||
if apiJSON {
|
||||
showJSONResponse(resp)
|
||||
return
|
||||
}
|
||||
|
||||
showBalancerInfo(resp.Balancer)
|
||||
|
||||
}
|
||||
|
||||
func showBalancerInfo(b *routerService.BalancerMsg) {
|
||||
const tableIndent = 4
|
||||
sb := new(strings.Builder)
|
||||
// Override
|
||||
if b.Override != nil {
|
||||
sb.WriteString(" - Selecting Override:\n")
|
||||
for i, s := range []string{b.Override.Target} {
|
||||
writeRow(sb, tableIndent, i+1, []string{s}, nil)
|
||||
}
|
||||
}
|
||||
// Selects
|
||||
sb.WriteString(" - Selects:\n")
|
||||
|
||||
for i, o := range b.PrincipleTarget.Tag {
|
||||
writeRow(sb, tableIndent, i+1, []string{o}, nil)
|
||||
}
|
||||
os.Stdout.WriteString(sb.String())
|
||||
}
|
||||
|
||||
func getColumnFormats(titles []string) []string {
|
||||
w := make([]string, len(titles))
|
||||
for i, t := range titles {
|
||||
w[i] = fmt.Sprintf("%%-%ds ", len(t))
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func writeRow(sb *strings.Builder, indent, index int, values, formats []string) {
|
||||
if index == 0 {
|
||||
// title line
|
||||
sb.WriteString(strings.Repeat(" ", indent+4))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%s%-4d", strings.Repeat(" ", indent), index))
|
||||
}
|
||||
for i, v := range values {
|
||||
format := "%-14s"
|
||||
if i < len(formats) {
|
||||
format = formats[i]
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(format, v))
|
||||
}
|
||||
sb.WriteByte('\n')
|
||||
}
|
77
main/commands/all/api/balancer_override.go
Normal file
77
main/commands/all/api/balancer_override.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
routerService "github.com/xtls/xray-core/app/router/command"
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
)
|
||||
|
||||
var cmdBalancerOverride = &base.Command{
|
||||
CustomFlags: true,
|
||||
UsageLine: "{{.Exec}} api bo [--server=127.0.0.1:8080] <-b balancer> outboundTag",
|
||||
Short: "balancer override",
|
||||
Long: `
|
||||
Override a balancer's selection.
|
||||
|
||||
> Make sure you have "RoutingService" set in "config.api.services"
|
||||
of server config.
|
||||
|
||||
Once a balancer's selecting is overridden:
|
||||
|
||||
- The balancer's selection result will always be outboundTag
|
||||
|
||||
Arguments:
|
||||
|
||||
-r, -remove
|
||||
Remove the overridden
|
||||
|
||||
-r, -remove
|
||||
Remove the override
|
||||
|
||||
-s, -server
|
||||
The API server address. Default 127.0.0.1:8080
|
||||
|
||||
-t, -timeout
|
||||
Timeout seconds to call API. Default 3
|
||||
|
||||
Example:
|
||||
|
||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -b balancer tag
|
||||
{{.Exec}} {{.LongName}} --server=127.0.0.1:8080 -b balancer -r
|
||||
`,
|
||||
Run: executeBalancerOverride,
|
||||
}
|
||||
|
||||
func executeBalancerOverride(cmd *base.Command, args []string) {
|
||||
var (
|
||||
balancer string
|
||||
remove bool
|
||||
)
|
||||
cmd.Flag.StringVar(&balancer, "b", "", "")
|
||||
cmd.Flag.StringVar(&balancer, "balancer", "", "")
|
||||
cmd.Flag.BoolVar(&remove, "r", false, "")
|
||||
cmd.Flag.BoolVar(&remove, "remove", false, "")
|
||||
setSharedFlags(cmd)
|
||||
cmd.Flag.Parse(args)
|
||||
|
||||
if balancer == "" {
|
||||
base.Fatalf("balancer tag not specified")
|
||||
}
|
||||
|
||||
conn, ctx, close := dialAPIServer()
|
||||
defer close()
|
||||
|
||||
client := routerService.NewRoutingServiceClient(conn)
|
||||
target := ""
|
||||
if !remove {
|
||||
target = cmd.Flag.Args()[0]
|
||||
}
|
||||
r := &routerService.OverrideBalancerTargetRequest{
|
||||
BalancerTag: balancer,
|
||||
Target: target,
|
||||
}
|
||||
|
||||
_, err := client.OverrideBalancerTarget(ctx, r)
|
||||
if err != nil {
|
||||
base.Fatalf("failed to perform balancer health checks: %s", err)
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -15,7 +16,6 @@ import (
|
|||
"github.com/xtls/xray-core/common/buf"
|
||||
"github.com/xtls/xray-core/main/commands/base"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
|
@ -24,6 +24,7 @@ type serviceHandler func(ctx context.Context, conn *grpc.ClientConn, cmd *base.C
|
|||
var (
|
||||
apiServerAddrPtr string
|
||||
apiTimeout int
|
||||
apiJSON bool
|
||||
)
|
||||
|
||||
func setSharedFlags(cmd *base.Command) {
|
||||
|
@ -31,6 +32,7 @@ func setSharedFlags(cmd *base.Command) {
|
|||
cmd.Flag.StringVar(&apiServerAddrPtr, "server", "127.0.0.1:8080", "")
|
||||
cmd.Flag.IntVar(&apiTimeout, "t", 3, "")
|
||||
cmd.Flag.IntVar(&apiTimeout, "timeout", 3, "")
|
||||
cmd.Flag.BoolVar(&apiJSON, "json", false, "")
|
||||
}
|
||||
|
||||
func dialAPIServer() (conn *grpc.ClientConn, ctx context.Context, close func()) {
|
||||
|
@ -103,13 +105,8 @@ func fetchHTTPContent(target string) ([]byte, error) {
|
|||
return content, nil
|
||||
}
|
||||
|
||||
func protoToJSONString(m proto.Message, _, indent string) (string, error) {
|
||||
ops := protojson.MarshalOptions{
|
||||
Indent: indent,
|
||||
EmitUnpopulated: true,
|
||||
}
|
||||
b, err := ops.Marshal(m)
|
||||
return string(b), err
|
||||
func protoToJSONString(m proto.Message, prefix, indent string) (string, error) {
|
||||
return strings.TrimSpace(protojson.MarshalOptions{Indent: indent}.Format(m)), nil
|
||||
}
|
||||
|
||||
func showJSONResponse(m proto.Message) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue