golang 實現(xiàn)一個負(fù)載均衡案例(隨機(jī),輪訓(xùn))
今天用go實現(xiàn)一個簡單的負(fù)載均衡的算法,雖然簡單,還是要寫一下。
1.首先就是服務(wù)器的信息
package balance type Instance struct { host string port int } func NewInstance(host string, port int) *Instance { return &Instance{ host: host, port: port, } } func (p *Instance) GetHost() string { return p.host } func (p *Instance) GetPort() int { return p.port }
2.接著定義接口
package balance type Balance interface { /** *負(fù)載均衡算法 */ DoBalance([] *Instance,...string) (*Instance,error) }
3.接著,是實現(xiàn)接口,random.go
package balance import ( "errors" "math/rand" ) func init() { RegisterBalance("random",&RandomBalance{}) } type RandomBalance struct { } func (p *RandomBalance) DoBalance(insts [] *Instance,key...string) (inst *Instance, err error) { if len(insts) == 0 { err = errors.New("no instance") return } lens := len(insts) index := rand.Intn(lens) inst = insts[index] return }
roundrobin.go
package balance import ( "errors" ) func init() { RegisterBalance("round", &RoundRobinBalance{}) } type RoundRobinBalance struct { curIndex int } func (p *RoundRobinBalance) DoBalance(insts [] *Instance, key ...string) (inst *Instance, err error) { if len(insts) == 0 { err = errors.New("no instance") return } lens := len(insts) if p.curIndex >= lens { p.curIndex = 0 } inst = insts[p.curIndex] p.curIndex++ return }
4 然后,全部交給管理器來管理,這也是為什么上面的文件全部重寫了init函數(shù)
package balance import ( "fmt" ) type BalanceMgr struct { allBalance map[string]Balance } var mgr = BalanceMgr{ allBalance: make(map[string]Balance), } func (p *BalanceMgr) registerBalance(name string, b Balance) { p.allBalance[name] = b } func RegisterBalance(name string, b Balance) { mgr.registerBalance(name, b) } func DoBalance(name string, insts []*Instance) (inst *Instance, err error) { balance, ok := mgr.allBalance[name] if !ok { err = fmt.Errorf("not fount %s", name) fmt.Println("not found ",name) return } inst, err = balance.DoBalance(insts) if err != nil { err = fmt.Errorf(" %s erros", name) return } return }
下面進(jìn)行測試:
func main() { var insts []*balance.Instance for i := 0; i < 10; i++ { host := fmt.Sprintf("192.168.%d.%d", rand.Intn(255), rand.Intn(255)) port, _ := strconv.Atoi(fmt.Sprintf("880%d", i)) one := balance.NewInstance(host, port) insts = append(insts, one) } var name = "round" if len(os.Args) > 1 { name = os.Args[1] } for { inst, err := balance.DoBalance(name, insts) if err != nil { fmt.Println("do balance err") time.Sleep(time.Second) continue } fmt.Println(inst) time.Sleep(time.Second) } }
5.如果想擴(kuò)展這個,又不入侵原來的代碼結(jié)構(gòu),可以類比上面實現(xiàn)dobalance接口即可
package add import ( "awesomeProject/test/balance" "fmt" "math/rand" "hash/crc32" ) func init() { balance.RegisterBalance("hash", &HashBalance{}) } type HashBalance struct { key string } func (p *HashBalance) DoBalance(insts [] *balance.Instance, key ...string) (inst *balance.Instance, err error) { defKey := fmt.Sprintf("%d", rand.Int()) if len(key) > 0 { defKey = key[0] } lens := len(insts) if lens == 0 { err = fmt.Errorf("no balance") return } hashVal := crc32.Checksum([]byte(defKey), crc32.MakeTable(crc32.IEEE)) index := int(hashVal) % lens inst = insts[index] return }
這樣就能交給管理器統(tǒng)一管理了,而且不會影響原來的api。
補(bǔ)充:golang grpc配合nginx實現(xiàn)負(fù)載均衡
概述
grpc負(fù)載均衡有主要有進(jìn)程內(nèi)balance, 進(jìn)程外balance, proxy 三種方式,本文敘述的是proxy方式,以前進(jìn)程內(nèi)的方式比較流行,靠etcd或者consul等服務(wù)發(fā)現(xiàn)來輪詢,隨機(jī)等方式實現(xiàn)負(fù)載均衡。
現(xiàn)在nginx 1.13過后正式支持grpc, 由于nginx穩(wěn)定,高并發(fā)量,功能強(qiáng)大,更難能可貴的是部署方便,并且不像進(jìn)程內(nèi)balance那樣不同的語言要寫不同的實現(xiàn),因此我非常推崇這種方式。
nginx的配置
確認(rèn)安裝版本大于1.13的nginx后打開配置文件,寫入如下配置
upstream lb{ #負(fù)載均衡的grpc服務(wù)器地址 server 127.0.0.1:50052; server 127.0.0.1:50053; server 127.0.0.1:50054; #keepalive 500;#這個東西是nginx和rpc服務(wù)器群保持長連接的總數(shù),設(shè)置可以提高效率,同時避免nginx到rpc服務(wù)器之間默認(rèn)是短連接并發(fā)過后造成time_wait過多 } server { listen 9527 http2; access_log /var/log/nginx/host.access.log main; http2_max_requests 10000;#這里默認(rèn)是1000,并發(fā)量上來會報錯,因此設(shè)置大一點 #grpc_socket_keepalive on;#這個東西nginx1.5過后支持 location / { grpc_pass grpc://lb; error_page 502 = /error502grpc; } location = /error502grpc { internal; default_type application/grpc; add_header grpc-status 14; add_header grpc-message "Unavailable"; return 204; } }
可以在host.access.log日志文件里面看到數(shù)據(jù)轉(zhuǎn)發(fā)記錄
proto文件:
syntax = "proto3"; // 指定proto版本 package grpctest; // 指定包名 // 定義Hello服務(wù) service Hello { // 定義SayHello方法 rpc SayHello(HelloRequest) returns (HelloReply) {} } // HelloRequest 請求結(jié)構(gòu) message HelloRequest { string name = 1; } // HelloReply 響應(yīng)結(jié)構(gòu) message HelloReply { string message = 1; }
客戶端:
客戶端連接地址填寫nginx的監(jiān)聽地址,相關(guān)代碼如下:
package main import ( pb "protobuf/grpctest" // 引入proto包 "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" "fmt" "time" ) const ( // Address gRPC服務(wù)地址 Address = "127.0.0.1:9527" ) func main() { // 連接 conn, err := grpc.Dial(Address, grpc.WithInsecure()) if err != nil { grpclog.Fatalln(err) } defer conn.Close() // 初始化客戶端 c := pb.NewHelloClient(conn) reqBody := new(pb.HelloRequest) reqBody.Name = "gRPC" // 調(diào)用方法 for{ r, err := c.SayHello(context.Background(), reqBody) if err != nil { grpclog.Fatalln(err) } fmt.Println(r.Message) time.Sleep(time.Second) } }
服務(wù)端:
package main import ( "net" "fmt" pb "protobuf/grpctest" // 引入編譯生成的包 "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" ) const ( // Address gRPC服務(wù)地址 Address = "127.0.0.1:50052" //Address = "127.0.0.1:50053" //Address = "127.0.0.1:50054" ) var HelloService = helloService{} type helloService struct{} func (this helloService) SayHello(ctx context.Context,in *pb.HelloRequest)(*pb.HelloReply,error){ resp := new(pb.HelloReply) resp.Message = Address+" hello"+in.Name+"." return resp,nil } func main(){ listen,err:=net.Listen("tcp",Address) if err != nil{ grpclog.Fatalf("failed to listen: %v", err) } s:=grpc.NewServer() pb.RegisterHelloServer(s,HelloService) grpclog.Println("Listen on " + Address) s.Serve(listen) }
測試
以50052,50053,50054 3個端口啟3個服務(wù)端進(jìn)程,運行客戶端代碼,即可看見如下效果:
負(fù)載均衡完美實現(xiàn), 打開日志文件,可以看到post的地址為 /grpctest.Hello/SayHello,nginx配置為所有請求都按默認(rèn) localtion / 轉(zhuǎn)發(fā),因此 nginx再配上合適的路由規(guī)則,還可實現(xiàn)更靈活轉(zhuǎn)發(fā),也可達(dá)到微服務(wù)注冊的目的,非常方便。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
go中利用reflect實現(xiàn)json序列化的示例代碼
和Java語言一樣,Go也實現(xiàn)運行時反射,這為我們提供一種可以在運行時操作任意類型對象的能力,本文給大家介紹了在go中如何利用reflect實現(xiàn)json序列化,需要的朋友可以參考下2024-03-03Go調(diào)度器學(xué)習(xí)之goroutine調(diào)度詳解
這篇文章主要為大家詳細(xì)介紹了Go調(diào)度器中g(shù)oroutine調(diào)度的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03解決Golang并發(fā)工具Singleflight的問題
前段時間在一個項目里使用到了分布式鎖進(jìn)行共享資源的訪問限制,后來了解到Golang里還能夠使用singleflight對共享資源的訪問做限制,于是利用空余時間了解,將知識沉淀下來,并做分享2022-05-05