在go的微服務架構中
使用go-kit組件進行開發(fā)微服務
type Reg struct {
Host string
Port int
Client consul.Client
}
func MakeReg (host string , port int) (*Reg , error) {
reg := api.DefaultConfig()
reg.Address = host + ":" + strconv.Itoa(port)
apiclient , err = api.NewClient(reg)
if err != nil {
return nil , err
}
client := consul.NewClient(apiclient)
return Reg{Host : host , Port : port ,Client : client} , nil
}
func (r Reg) Resiter (servicename , tag , host , seviceid ,checkinter ,healthcheckhttp ,deregisterafter string , port int) bool {
congig := api.AgentServiceRegistration{
Port : port ,
Address : host ,
Name := servicename,
ID := serviceid,
Ckeck : api.AgentServiceCheck{
Interval : checkinter,
HTTP : "http://" + host + ":" + healthcheckhttp ,
DeregisterCriticalServiceAfter : deregisterafter,
}
}
if err := r.Client.Register(config) ; err != nil {
fmt.Println(err)
return false
}
return true
}
func (r Reg) Deregister (serviceid string) bool {
dreg := api.AgentServiceRegistration{ID : serviceid}
if err != r.Client.Deregister(config) ; err != nil {
fmt.Println(err)
return false
}
return true
}
func (r Reg) discover (servicename , tag string ,passingonly bool) ( []*api.ServiceEntry ,error ) {
if entries ,_ ,err := r.Client.Service(servicename , tag , passingonly , nil) ;err != nil {
return nil ,err
}else{
return entries , nil
}
}
補充:go-kit 與 grpc 結合實現(xiàn)注冊發(fā)現(xiàn)與負載均衡
介紹
grpc提供了簡單的負載均衡,需要自己實現(xiàn)服務發(fā)現(xiàn)resolve。我們既然要使用go-kit來治理微服務,那么我們就使用go-kit的注冊發(fā)現(xiàn)、負載均衡機制。
go-kit官方【stringsvc3】例子中使用的負載均衡方案是通過服務端轉發(fā)進行,翻找下源碼go-kit的服務注冊發(fā)現(xiàn)、負載均衡在【sd】包中。下面我們介紹怎么通過go-kit進行客戶端負載均衡。
go-kit提供的注冊中心
1、 etcd
2、 consul
3、 eureka
4、 zookeeper
go-kit提供的負載均衡
1、 random[隨機]
2、 roundRobin[輪詢]
只需實現(xiàn)Balancer接口,我們可以很容易的增加其它負載均衡機制
type Balancer interface {
Endpoint() (endpoint.Endpoint, error)
}
etcd注冊發(fā)現(xiàn)
etcd和zookeeper類似是一個高可用、強一致性的存儲倉庫,擁有服務發(fā)現(xiàn)功能。 我們就通過go-kit提供的etcd包來實現(xiàn)服務注冊發(fā)現(xiàn)
服務端代碼
服務注冊
1、連接注冊中心
2、注冊當前服務
var (
//etcd服務地址
etcdServer = "127.0.0.1:2379"
//服務的信息目錄
prefix = "/services/book/"
//當前啟動服務實例的地址
instance = "127.0.0.1:50052"
//服務實例注冊的路徑
key = prefix + instance
//服務實例注冊的val
value = instance
ctx = context.Background()
//服務監(jiān)聽地址
serviceAddress = ":50052"
)
//etcd的連接參數(shù)
options := etcdv3.ClientOptions{
DialTimeout: time.Second * 3,
DialKeepAlive: time.Second * 3,
}
//創(chuàng)建etcd連接
client, err := etcdv3.NewClient(ctx, []string{etcdServer}, options)
if err != nil {
panic(err)
}
// 創(chuàng)建注冊器
registrar := etcdv3.NewRegistrar(client, etcdv3.Service{
Key: key,
Value: value,
}, log.NewNopLogger())
// 注冊器啟動注冊
registrar.Register()
完整代碼
package main
import (
"grpc-test/pb"
"context"
grpc_transport "github.com/go-kit/kit/transport/grpc"
"github.com/go-kit/kit/endpoint"
"google.golang.org/grpc"
"net"
"github.com/go-kit/kit/sd/etcdv3"
"github.com/go-kit/kit/log"
"time"
)
type BookServer struct {
bookListHandler grpc_transport.Handler
bookInfoHandler grpc_transport.Handler
}
//通過grpc調(diào)用GetBookInfo時,GetBookInfo只做數(shù)據(jù)透傳, 調(diào)用BookServer中對應Handler.ServeGRPC轉交給go-kit處理
func (s *BookServer) GetBookInfo(ctx context.Context, in *book.BookInfoParams) (*book.BookInfo, error) {
_, rsp, err := s.bookInfoHandler.ServeGRPC(ctx, in)
if err != nil {
return nil, err
}
return rsp.(*book.BookInfo),err
}
//通過grpc調(diào)用GetBookList時,GetBookList只做數(shù)據(jù)透傳, 調(diào)用BookServer中對應Handler.ServeGRPC轉交給go-kit處理
func (s *BookServer) GetBookList(ctx context.Context, in *book.BookListParams) (*book.BookList, error) {
_, rsp, err := s.bookListHandler.ServeGRPC(ctx, in)
if err != nil {
return nil, err
}
return rsp.(*book.BookList),err
}
//創(chuàng)建bookList的EndPoint
func makeGetBookListEndpoint() endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
//請求列表時返回 書籍列表
bl := new(book.BookList)
bl.BookList = append(bl.BookList, book.BookInfo{BookId:1,BookName:"21天精通php"})
bl.BookList = append(bl.BookList, book.BookInfo{BookId:2,BookName:"21天精通java"})
return bl,nil
}
}
//創(chuàng)建bookInfo的EndPoint
func makeGetBookInfoEndpoint() endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
//請求詳情時返回 書籍信息
req := request.(*book.BookInfoParams)
b := new(book.BookInfo)
b.BookId = req.BookId
b.BookName = "21天精通php"
return b,nil
}
}
func decodeRequest(_ context.Context, req interface{}) (interface{}, error) {
return req, nil
}
func encodeResponse(_ context.Context, rsp interface{}) (interface{}, error) {
return rsp, nil
}
func main() {
var (
//etcd服務地址
etcdServer = "127.0.0.1:2379"
//服務的信息目錄
prefix = "/services/book/"
//當前啟動服務實例的地址
instance = "127.0.0.1:50052"
//服務實例注冊的路徑
key = prefix + instance
//服務實例注冊的val
value = instance
ctx = context.Background()
//服務監(jiān)聽地址
serviceAddress = ":50052"
)
//etcd的連接參數(shù)
options := etcdv3.ClientOptions{
DialTimeout: time.Second * 3,
DialKeepAlive: time.Second * 3,
}
//創(chuàng)建etcd連接
client, err := etcdv3.NewClient(ctx, []string{etcdServer}, options)
if err != nil {
panic(err)
}
// 創(chuàng)建注冊器
registrar := etcdv3.NewRegistrar(client, etcdv3.Service{
Key: key,
Value: value,
}, log.NewNopLogger())
// 注冊器啟動注冊
registrar.Register()
bookServer := new(BookServer)
bookListHandler := grpc_transport.NewServer(
makeGetBookListEndpoint(),
decodeRequest,
encodeResponse,
)
bookServer.bookListHandler = bookListHandler
bookInfoHandler := grpc_transport.NewServer(
makeGetBookInfoEndpoint(),
decodeRequest,
encodeResponse,
)
bookServer.bookInfoHandler = bookInfoHandler
ls, _ := net.Listen("tcp", serviceAddress)
gs := grpc.NewServer(grpc.UnaryInterceptor(grpc_transport.Interceptor))
book.RegisterBookServiceServer(gs, bookServer)
gs.Serve(ls)
}
客戶端代碼
客戶端流程
1、 連接注冊中心
2、 獲取提供的服務
3、 監(jiān)聽服務目錄變化,目錄變化更新本地緩存
4、 創(chuàng)建負載均衡器
5、 獲取請求的 endPoint
完整代碼
package main
import (
"context"
"github.com/go-kit/kit/sd/etcdv3"
"time"
"github.com/go-kit/kit/sd"
"github.com/go-kit/kit/log"
"github.com/go-kit/kit/endpoint"
"io"
"github.com/go-kit/kit/sd/lb"
"grpc-test/pb"
"fmt"
"google.golang.org/grpc"
)
func main() {
var (
//注冊中心地址
etcdServer = "127.0.0.1:2379"
//監(jiān)聽的服務前綴
prefix = "/services/book/"
ctx = context.Background()
)
options := etcdv3.ClientOptions{
DialTimeout: time.Second * 3,
DialKeepAlive: time.Second * 3,
}
//連接注冊中心
client, err := etcdv3.NewClient(ctx, []string{etcdServer}, options)
if err != nil {
panic(err)
}
logger := log.NewNopLogger()
//創(chuàng)建實例管理器, 此管理器會Watch監(jiān)聽etc中prefix的目錄變化更新緩存的服務實例數(shù)據(jù)
instancer, err := etcdv3.NewInstancer(client, prefix, logger)
if err != nil {
panic(err)
}
//創(chuàng)建端點管理器, 此管理器根據(jù)Factory和監(jiān)聽的到實例創(chuàng)建endPoint并訂閱instancer的變化動態(tài)更新Factory創(chuàng)建的endPoint
endpointer := sd.NewEndpointer(instancer, reqFactory, logger)
//創(chuàng)建負載均衡器
balancer := lb.NewRoundRobin(endpointer)
/**
我們可以通過負載均衡器直接獲取請求的endPoint,發(fā)起請求
reqEndPoint,_ := balancer.Endpoint()
*/
/**
也可以通過retry定義嘗試次數(shù)進行請求
*/
reqEndPoint := lb.Retry(3, 3*time.Second, balancer)
//現(xiàn)在我們可以通過 endPoint 發(fā)起請求了
req := struct{}{}
if _, err = reqEndPoint(ctx, req); err != nil {
panic(err)
}
}
//通過傳入的 實例地址 創(chuàng)建對應的請求endPoint
func reqFactory(instanceAddr string) (endpoint.Endpoint, io.Closer, error) {
return func(ctx context.Context, request interface{}) (interface{}, error) {
fmt.Println("請求服務: ", instanceAddr)
conn, err := grpc.Dial(instanceAddr, grpc.WithInsecure())
if err != nil {
fmt.Println(err)
panic("connect error")
}
defer conn.Close()
bookClient := book.NewBookServiceClient(conn)
bi,_:=bookClient.GetBookInfo(context.Background(),book.BookInfoParams{BookId:1})
fmt.Println("獲取書籍詳情")
fmt.Println("bookId: 1", " => ", "bookName:", bi.BookName)
bl,_ := bookClient.GetBookList(context.Background(), book.BookListParams{Page:1, Limit:10})
fmt.Println("獲取書籍列表")
for _,b := range bl.BookList {
fmt.Println("bookId:", b.BookId, " => ", "bookName:", b.BookName)
}
return nil,nil
},nil,nil
}
測試
請求測試
請求服務: 127.0.0.1:50052
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
負載均衡測試
1、 修改server的注冊監(jiān)聽端口,啟動多個server
instance = "127.0.0.1:50052"
serviceAddress = ":50052"
2、client發(fā)起多次請求
req := struct{}{}
for i := 1; i = 8; i++ {
if _, err = reqEndPoint(ctx, req); err != nil {
panic(err)
}
}
通過返回結果中記錄的請求地址,我們可以看到已經(jīng)按照輪詢的方式請求不同的微服務實例。
請求服務: 127.0.0.1:50051
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
請求服務: 127.0.0.1:50052
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
請求服務: 127.0.0.1:50051
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
請求服務: 127.0.0.1:50052
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
請求服務: 127.0.0.1:50051
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
請求服務: 127.0.0.1:50052
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
請求服務: 127.0.0.1:50051
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
請求服務: 127.0.0.1:50052
獲取書籍詳情
bookId: 1 => bookName: 21天精通php
獲取書籍列表
bookId: 1 => bookName: 21天精通php
bookId: 2 => bookName: 21天精通java
Process finished with exit code 0
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
您可能感興趣的文章:- Goland 斷點調(diào)試Debug的操作
- golang中的空slice案例
- Go語言切片前或中間插入項與內(nèi)置copy()函數(shù)詳解
- golang中切片copy復制和等號復制的區(qū)別介紹
- go語言中切片與內(nèi)存復制 memcpy 的實現(xiàn)操作
- Go語言中的UTF-8實現(xiàn)
- go-kit組件使用hystrix中間件的操作