GithubHelp home page GithubHelp logo

go-rpc's Introduction

go-rpc

主要参考 https://github.com/megaredfan/rpc-demo 实现的微服务框架RPC框架

其他的实现阅读:
https://github.com/smallnest/rpcx
https://github.com/koding/kite

支持服务治理的相关功能,包括:
超时控制
服务注册与发现
服务负载均衡
限流和熔断
身份认证
监控和链路追踪
健康检查,包括端到端的心跳以及注册中心对服务实例的检查
暂时没实现idl的方式

系统设计
Image

service 是面向用户的接口,比如客户端和服务端实例的初始化和运行等等
client和server表示客户端和服务端的实例,它们负责发出请求和返回响应
selector 表示负载均衡,它负责决定具体要向哪个server发出请求 registery 表示注册中心,server在初始化完毕甚至是运行时都要向注册中心注册自身的相关信息,client能从注册中心查找到需要的server
codec 表示编解码,也就是将对象和二进制数据互相转换
protocol 表示通信协议
transport 表示通讯,它负责具体的网络通讯,将按照protocol组装好的二进制数据通过网络发送出去,并根据protocol指定的方式从网络读取数据

过滤器链
采用类似过滤器链一样的方式处理请求和响应,以此来达到对扩展开放,对修改关闭的效果。这熔断降级和限流、身份认证,鉴权,日志,链路追踪等功能在过滤器中实现。

消息协议
接下来设计具体的消息协议,所谓消息协议大概就是两台计算机为了互相通信而做的约定。举个例子,TCP协议约定了一个TCP数据包的具体格式,比如前2个byte表示源端口,第3和第4个byte表示目标端口,接下来是序号和确认序号等等。而在我们的RPC框架中,也需要定义自己的协议。一般来说,网络协议都分为head和body部分,head是一些元数据,是协议自身需要的数据,body则是上一层传递来的数据,只需要原封不动的接着传递下去就是了。

-------------------------------------------------------------------------------------------------
|2byte|1byte |4byte |4byte | header length |(total length - header length - 4byte)|
-------------------------------------------------------------------------------------------------
|magic|version|total length|header length| header | body |
-------------------------------------------------------------------------------------------------
两个byte的magic number开头,这样一来我们就可以快速的识别出非法的请求
一个byte表示协议的版本,目前可以一律设置为0
4个byte表示消息体剩余部分的总长度(total length)
4个byte表示消息头的长度(header length)
消息头(header),其长度根据前面解析出的长度(header length)决定
消息体(body),其长度为前面解析出的总长度减去消息头所占的长度(total length - 4 - header length)

三方组件:
日志库:
https://github.com/golang/glog

序列化库:
https://github.com/vmihailenco/msgpack

kv客户端
https://github.com/docker/libkv

链路跟踪api
github.com/opentracing/opentracing-go

自增序列号: https://github.com/google/uuid

使用方法:
·启动服务,创建客户端:
1,服务端:
配置服务端:每个配置项都可以自定义添加插件,自由选用

servertOption := server.Option{
		ProtocolType:   protocol.Default,
		SerializeType:  protocol.SerializeTypeMsgpack,
		CompressType:   protocol.CompressTypeNone,
		TransportType:  transport.TCPTransport,
		ShutDownWait:   time.Second * 12,
		Registry:       r1,
		RegisterOption: registry.RegisterOption{"my-app"},
    
    //基于标签的路由策略
		Tags:           map[string]string{"idc": "lf"}, //只允许机房为lf的请求,客户端取到信息会自己进行转移
	}

启动服务:

func StartServer(op *server.Option) {
	go func() {
		s, err := server.NewSGServer(op)
		if err != nil {
			glog.Error("new serializer failed", err)
			return
		}

		go s.Serve("tcp", "127.0.0.1:8888", nil)
	}()
}

·添加服务:
定义服务的结构体:

type TestService struct {
}

添加他的方法:

func (t TestService) Add(ctx context.Context, req *TestRequest, res *TestResponse) error {
	res.Reply = req.A + req.B
	return nil
}

同时定义 request和response
的结构

type TestRequest struct {
	A int //发送的参数
	B int
}

type TestResponse struct {
	Reply int //返回的参数
}

在sgserver中注册这个服务

s.Register(service.TestService{})

2客户端:

配置:

op := &client.DefaultSGOption
	op.AppKey = "my-app"
	op.RequestTimeout = time.Millisecond * 100
	op.DialTimeout = time.Millisecond * 100
  
  //心跳、降级
	op.HeartbeatInterval = time.Second
	op.HeartbeatDegradeThreshold = 5
	op.Heartbeat = true
  
  
	op.SerializeType = protocol.SerializeTypeMsgpack
	op.CompressType = protocol.CompressTypeNone
	op.TransportType = transport.TCPTransport
	op.ProtocolType = protocol.Default
  
  //容错
	op.FailMode = client.FailRetry
	op.Retries = 3
  
  //鉴权
	op.Auth = "hello01"
  
  
  //熔断
	//一秒钟失败20次 就会进入贤者模式.. 因为lastupdate时间在不断更新,熔断后继续调用有可能恢复
	op.CircuitBreakerThreshold = 20
	op.CircuitBreakerWindow = time.Second

	//基于标签的路由策略
	op.Tagged = true
	op.Tags = map[string]string{"idc": "lf"}
  r2 := libkv.NewKVRegistry(store.ZK, "my-app", "/root/lizongrong/service",
		[]string{"127.0.0.1:1181", "127.0.0.1:2181", "127.0.0.1:3181"}, 1e10, nil)
	
	op.Registry = r2
  
  //限流
        op.Wrappers = append(op.Wrappers, &client.RateLimitInterceptor{Limit: ratelimit.NewRateLimiter(10, 2)}) //一秒10个,最多有两个排队

创建客户端:

	c := client.NewSGClient(*op)

使用客户端调用:

c.Call(ctx, "ArithService.Add", &Testrequest, &Testresponse)

添加中间件:
client接口:

type Wrapper interface {
	WrapCall(option *SGOption, callFunc CallFunc) CallFunc
}

服务端接口:

type Wrapper interface {
	WrapServe(s *SGServer, serveFunc ServeFunc) ServeFunc
	WrapHandleRequest(s *SGServer, requestFunc HandleRequestFunc) HandleRequestFunc
}

实现接口,并且在客户端和服务端初始化的时候或者之前加入到option中去:

s.option.Wrappers = append(s.option.Wrappers, &DefaultServerWrapper{})

如果需要扩展接口方法,需要实现把之前的wrapper都添加上扩展的方法,并且在wrapper.go中添加函数原型

http网关的使用方法: demo: https://github.com/tiancai110a/go-restful/tree/http-demo 接口仿照的gin,使用方法很像

配置:

	servertOption := server.Option{
		ProtocolType:   protocol.Default,
		SerializeType:  protocol.SerializeTypeMsgpack,
		CompressType:   protocol.CompressTypeNone,
		TransportType:  transport.TCPTransport,
		ShutDownWait:   time.Second * 12,
		Registry:       r1,
		RegisterOption: registry.RegisterOption{"my-app"},
		Tags:           map[string]string{"idc": "lf"}, //只允许机房为lf的请求,客户端取到信息会自己进行转移
	}

设置路由和中间件

func Load(s server.RPCServer) {
	// Middlewares.

	s.Use(middleware.TestMiddleware1)
	s.Use(middleware.TestMiddleware2)
	s.Use(middleware.TestMiddleware3)

	// The health check handlers
	svcd := s.Group(service.GET, "/view")
	{
		svcd.Route("/health", view.HealthCheck)
		svcd.Route("/disk", view.DiskCheck)
		svcd.Route("/cpu", view.CPUCheck)
		svcd.Route("/ram", view.RAMCheck)
	}

	return
}

启动:

func StartServer(op *server.Option) {
	go func() {
		s, err := server.NewSGServer(op)
		if err != nil {
			glog.Error("new serializer failed", err)
			return
		}
		router.Load(s)
		go s.Serve("tcp", "127.0.0.1:8888", nil)
	}()
}

实现:

网络通信模型:
Image

用户调用方法:
方法的参数必须为三个,而且第一个必须是context.Context类型
第二个是服务名.方法名
后面两个是request和response
方法返回值必须是error类型
客户端通过"Type.Method"的形式来引用服务方法,其中Type是方法实现类的全类名,Method就是方法名

服务发现中心:
使用zookeeper,可以自由选用其他 如ETCD,cousul

定时拉取和监听数据 ,推拉结合

定时拉取服务列表并缓存本地

查询时直接返回缓存

注册时在zk添加节点 注销时在zk删除节点

监听时并不监听每个服务提供者,而是监听其父级目录,有变更时再统一拉取服务提供者列表

注册和注销时需要更改父级目录的内容(lastUpdate)来触发监听

使用了libkv的库来做zk的客户端,所以不能使用临时节点自动触发下线,其他注册中心也不支持临时节点,需要客户端做探活

开一个heartbeat协程做探活:

发送方法名为空的rpc调用请求作为探活,ptorocol里面request类型为探活类型

探活不受到降级,鉴权,和标签路由的拦截

探活的结果会触发降级

负载均衡:
只有随机选择,面对本地缓存的服务列表,从中随机选择一个

长连接及网络重连:
为了减少频繁创建和断开网络连接的开销,我们维持了客户端到服务端的长连接,并把创建好的连接(RPCClient对象)用map缓存起来,key就是对应的服务端的标识。客户端在调用前根据负载均衡的结果检索到缓存好的RPCClient然后发起调用。当我们检索不到对应的客户端或者发现缓存的客户端已经失效时,需要重新建立连接(重新创建RPCClient对象)

集群容错:

客户端中配置:

type FailMode byte
const (
	FailFast FailMode = iota //快速失败
	FailOver //重试其他服务器
	FailRetry //重试同一个服务器
	FailSafe //忽略失败,直接返回
)

客户端心跳:

发送方法名为空的rpc调用请求作为探活,ptorocol里面request类型为探活类型

探活不能受到降级,鉴权,和标签路由的拦截

探活的结果会触发降级

降级机制:

调用里设置fileter,触发降级的时候标记相关服务为降级,filter会过滤掉,建立一个degradwraaper来实现
设置一个计数器
探活成功会将计数器置0,连续失败多次触发降级,如果再次成功,会触发服务标记为非降级正常工作

鉴权:

meta信息中带有标签,不符合规则的标签会被屏蔽,并且发送失败的response,同样使用中间件来完成

熔断:

调用失败的时候触发不再重试 分数量阈值x和时间阈值y,必须在时间y内失败次数够x次。才会触发熔断,每次调用之前在wapper中触发 AllowRequest 判断是否触发熔断,触发的话就禁止请求,同样放到selector里面

同时实现了服务端和客户端的,服务端主要用来做集群熔断,待以后实现

限流:ratelimiter

机制:

开一个额外的协程,每隔一段时间往里面放一个时间戳作为token,每次判断是否响应的或者是否请求的时候从中取一个,如果已经被取光了就阻塞住等待,协程的大小决定了允许瞬间峰值的大小,客户端和服务端都有实现,选一边就行,同样基于wrapper

链路追踪:使用开源的opentracing

1,根据请求方法名等信息生成链路信息
2,通过rpc metadata传递追踪信息

基于标签的路由策略:
用于流量转移,切断某些rpc或者某些身份的流量

跟降级差不多,实现一个filter,放到client中供selector调用

并且服务端和客户端在meta中打入自己的标签,不匹配的请求将会被禁止

实现http网关:

通过http来请求服务而不是通过rpc请求,需要将http请求转换成rpc交给自己运行,收到rpcx的启发,gateway是实现resultful的前提

实现方法是 将原先rpc的协议头放到http中,接收到以后再将http头中的内容提取出来,合成rpc包,交给原先的接口

go-rpc's People

Contributors

zongrongli avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.