基数树,相当于是一种前缀树。对于基数树的每个节点,如果该节点是确定的子树的话,就和父节点合并。基数树可用来构建关联数组。
在上面的图里也可以看到,数据结构会把所有相同前缀都提取 剩余的都作为子节点。
从上面可以看到基数树是一个前缀树,图中也可以看到数据结构。那基数树在Gin中是如何应用的呢?举一个例子其实就能看得出来
router.GET("/support", handler1)
router.GET("/search", handler2)
router.GET("/contact", handler3)
router.GET("/group/user/", handler4)
router.GET("/group/user/test", handler5)
/ (handler = nil, indices = "scg")
s (handler = nil, indices = "ue")
upport (handler = handler1, indices = "")
earch (handler = handler2, indices = "")
contact (handler = handler3, indices = "")
group/user/ (handler = handler4, indices = "u")
test (handler = handler5, indices = "")
https://segmentfault.com/a/1190000019149860
路由注册通过任意一个路由注册方法,都可以进入到 func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes
这个方法。
// 挂载路由的实际处理函数
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
// 计算最终的绝对路径 base + relativePath
// 注意base也是绝对路径
absolutePath := group.calculateAbsolutePath(relativePath)
// 合并处理函数,将中间件和逻辑函数结合在一起
// 一般来说这里传入的handlder是逻辑函数 len(handlers) = 1
// 只有少数的handler会有自己的中间件处理函数 len(handlers) > 1
handlers = group.combineHandlers(handlers)
// 将处理好的 HandlersChain 加载到Radix Tree中去
// 这也表明,这里的RouterGroup只会载处理路由时发挥作用
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
看到这里回到engine.addRoute中去,通过深入发现如下调用链:
engine.GET -> routergroup.GET -> routergroup.handle -> engine.addRoute -> methodTree.addRoute -> node(radix-tree’s node).insertChild
r.Get, r.Post等方法实质都是通过调用 r.Handle 实现的
func (r *Router) Handle(method, path string, handle Handle) {
// 路径注册必须从/开始,否则直接报错
if path[0] != '/' {
panic("path must begin with '/' in path '" + path + "'")
}
// 路由树map不存在需要新建
if r.trees == nil {
r.trees = make(map[string]*node)
}
// 获取当前方法所对应树的根节点,不存在则新建一个
root := r.trees[method]
if root == nil {
root = new(node)
r.trees[method] = root
}
// 向路由树当中添加一条一条路由
root.addRoute(path, handle)
}
https://zhuanlan.zhihu.com/p/110976568
gin 与 net/http 的关系这基本是整个过程的代码了.
ln, err := net.Listen(“tcp”, addr)做了初试化了socket, bind, listen的 *** 作.rw, e := l.Accept()进行accept, 等待客户端进行连接go c.serve(ctx) 启动新的goroutine来处理本次请求. 同时主goroutine继续等待客户端连接, 进行高并发 *** 作h, _ := mux.Handler® 获取注册的路由, 然后拿到这个路由的handler, 然后将处理结果返回给客户端 RPCRPC(Remote ProcedureCall,远程过程调用) 是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络细节的应用程序通信协议。
RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。
开发者无需另外的为这个调用过程编写网络通信相关代码, RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
现在业界有很多开源的优秀 RPC 框架,例如 Spring Cloud、Dubbo、Thrift 等。
RPC采用客户端-服务器端的工作模式,请求程序就是一个客户端,而服务提供程序就是一个服务器端。
当执行一个远程过程调用时,客户端程序首先先发送一个带有参数的调用信息到服务端,然后等待服务端响应。
在服务端,服务进程保持睡眠状态直到客户端的调用信息到达。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息。然后等待下一个调用。
数据发送时先缓存到内存中 => 传输(数据从 如程序对象、结构体 转为数据包 文本、byte[])=> 封装到传输时的数据结构(序列化) =>
二、gRPC 2.1 gRPC 优点 基于HTTP/2HTTP/2 提供了二进制、长连接、连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU,帮助移动设备延长电池寿命等。
gRPC 的协议设计上使用了HTTP2 现有的语义,请求和响应的数据使用HTTP Body 发送,其他的控制信息则用Header 表示。
HTTP/2 对比 HTTP 1.1浏览器请求 url -> 解析域名 -> 建立 HTTP 连接 -> 服务器处理文件 -> 返回数据 -> 浏览器解析、渲染文件
这个流程最大的问题是,每次请求都需要建立一次 HTTP 连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,而且逻辑上是非必需的,
为了解决这个问题, HTTP 1.1 中提供了 Keep-Alive,允许我们建立一次 HTTP 连接,来返回多次请求数据。
HTTP 1.1有两个问题:
HTTP 1.1 基于串行文件传输数据,因此这些请求必须是有序的,所以实际上我们只是节省了建立连接的时间,而获取数据的时间并没有减少
最大并发数问题,假设我们在 Apache 中设置了最大并发数 300,而因为浏览器本身的限制,最大请求数为 6,那么服务器能承载的最高并发数是 50
HTTP 2 解决方案:
HTTP/2 引入二进制数据帧和流的概念,其中帧对数据进行顺序标识,这样浏览器收到数据之后,就可以按照序列对数据进行合并,而不会出现合并后数据错乱的情况。因为有了序列,服务器就可以并行的传输数据。
HTTP/2 对同一域名下所有请求都是基于流,也就是说同一域名不管访问多少文件,也只建立一路连接。同样Apache的最大连接数为300,因为有了这个新特性,最大的并发就可以提升到300,比原来提升了6倍。
多路复用
多路复用GRPC使用HTTP/2作为应用层的传输协议,HTTP/2会复用底层的TCP连接。
每一次RPC调用会产生一个新的Stream,每个Stream包含多个Frame,Frame是HTTP/2里面最小的数据传输单位。同时每个Stream有唯一的ID标识,如果是客户端创建的则ID是奇数,服务端创建的ID则是偶数。如果一条连接上的ID使用完了,Client会新建一条连接,Server也会给Client发送一个GOAWAY Frame强制让Client新建一条连接。一条GRPC连接允许并发的发送和接收多个Stream,而控制的参数便是MaxConcurrentStreams,Golang的服务端默认是100。
超时重连
我们在通过调用Dial或者DialContext函数创建连接时,默认只是返回ClientConn结构体指针,同时会启动一个Goroutine异步的去建立连接。如果想要等连接建立完再返回,可以指定grpc.WithBlock()传入Options来实现。超时机制很简单,在调用的时候传入一个timeout的context就可以了。重连机制通过启动一个Goroutine异步的去建立连接实现的,可以避免服务器因为连接空闲时间过长关闭连接、服务器重启等造成的客户端连接失效问题。也就是说通过GRPC的重连机制可以完美的解决连接池设计原则中的空闲连接的超时与保活问题。
gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。压缩和传输效率高,语法简单,表达力强。
proto文件生成目标代码,简单易用protobuf二进制消息,性能好/效率高(空间和时间效率都很不错)多语言支持 2.2 gRPC 缺点: GRPC尚未提供连接池,需要自行实现尚未提供“服务发现”、“负载均衡”机制因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。(nginx1.9版本已支持)Protobuf二进制可读性差(貌似提供了Text_Fromat功能)默认不具备动态特性(可以通过动态定义生成消息类型或者动态编译支持) 2.3 grpc meatdata在http请求当中我们可以设置header用来传递数据,grpc底层采用http2协议也是支持传递数据的,采用的是metadata。
Metadata 对于 gRPC 本身来说透明, 它使得 client 和 server 能为对方提供本次调用的信息。
就像一次 http 请求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 请求, Metadata 的生命周期则是一次 RPC 调用。
有两种发送 metadata 到 server 的方法,推荐的方法是使用 AppendToOutgoingContext,如果 metadata 已存在则会合并,不存在则添加
func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context
// create a new context with some metadata
ctx := metadata.AppendToOutgoingContext(ctx, "k1", "v1", "k1", "v2", "k2", "v3")
// later, add some more metadata to the context (e.g. in an interceptor)
ctx := metadata.AppendToOutgoingContext(ctx, "k3", "v4")
接收
服务端调用 FromIncomingContext 即可从 context 中接收 client 发送的 metadata
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
// 解析metada中的信息并验证
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, grpc.Errorf(codes.Unauthenticated, "无Token认证信息")
}
var (
appid string
appkey string
)
if val, ok := md["appid"]; ok {
appid = val[0]
}
if val, ok := md["appkey"]; ok {
appkey = val[0]
}
if appid != "101010" || appkey != "i am key" {
return nil, grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey)
}
log.Printf("Received: %v.\nToken info: appid=%s,appkey=%s", in.GetName(), appid, appkey)
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
gRPC 默认两种认证方式
基于SSL/TLS认证方式
远程调用认证方式
两种方式能够混合使用
2.4 grpc 负载均衡https://blog.csdn.net/weixin_43746433/article/details/114290213
HTTP和RPC的优缺点传输层
实际应用过程中,五层协议结构里面是没有表示层和会话层的。应该说它们和应用层合并了。我们应该将重点放在应用层和传输层这两个层面。因为HTTP是应用层协议,而TCP是传输层协议。好,知道了网络的分层模型以后我们可以更好地理解为什么RPC服务相比HTTP服务要Nice一些!
传输协议
RPC:可以基于TCP协议,也可以基于HTTP协议
HTTP:基于HTTP协议
传输效率RPC:使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率
HTTP:如果是基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单的封装以下是可以作为一个RPC来使用的,这时标准RPC框架更多的是服务治理
性能消耗RPC:可以基于thrift实现高效的二进制传输
HTTP:大部分是通过json来实现的,字节大小和序列化耗时都比thrift要更消耗性能
负载均衡RPC:基本都自带了负载均衡策略
HTTP:需要配置Nginx,HAProxy来实现
服务治理RPC:能做到自动通知,不影响上游
HTTP:需要事先通知,修改Nginx/HAProxy配置
总结RPC主要用于公司内部的服务调用,性能消耗低,传输效率高,服务治理方便。HTTP主要用于对外的异构环境,浏览器接口调用,APP接口调用,第三方接口调用等。
但是对于大型企业来说,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先就是长链接,不必每次通信都要像http一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的 *** 作。
Go语言中的RPC支持与处理在Go中,标准库提供的net/rpc包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。这使得用Go语言开发的多个进程之间的通信变得非常简单
net/rpc包允许PRC客户端程序通过网络或者其他IO连接调用一个远程对象的公开方法(该方法必须是外部可访问即首字母大写)。
在PRC服务端,可将一个对象注册为可访问的服务,之后该对象的公开方法就能够以远程的方式提供访问。
https://grpc.io/docs/languages/go/quickstart/
grpcgRPC是一个开源的远程过程调用框架,用于服务之间的高性能通信。服务之间的通信可以使用各种语言,通过可插拔的负载均衡、追踪、健康检查和身份验证,这让它被认为是一种非常高效的方法。在默认情况下,gRPC使用协议缓冲(Protocol Buffer)来序列化结构化数据。通常,在微服务架构中,gRPC被认为是REST更好的替代方案。gRPC中的“g”取名于最初开发该技术的Google。
在gRPC里,客户端可以直接调用不同机器上的服务应用的方法,就像是本地对象一样,所以创建分布式应用和服务就很简单了。
在很多RPC(Remote Procedure Call Protocol)系统里,gRPC是基于定义一个服务,指定一个可以远程调用的带有参数和返回类型的的方法。
在服务端,服务实现这个接口并且运行gRPC服务处理客户端调用。
在客户端,有一个stub提供和服务端相同的方法。
http的实现技术:HttpClient 缺点是消息封装臃肿。
相同点:底层通讯都是基于socket,都可以实现远程调用,都可以实现服务调用服务
不同点:RPC:框架有:dubbo、cxf、(RMI远程方法调用)Hessian
当使用RPC框架实现服务间调用的时候,要求服务提供方和服务消费方 都必须使用统一的RPC框架,要么都dubbo,要么都cxf
跨 *** 作系统在同一编程语言内使用
优势:调用快、处理快
http:框架有:httpClient
当使用http进行服务间调用的时候,无需关注服务提供方使用的编程语言,也无需关注服务消费方使用的编程语言,服务提供方只需要提供restful风格的接口,服务消费方,按照restful的原则,请求服务,即可
跨系统跨编程语言的远程调用框架
优势:通用性强
1 RPC要求服务提供方和服务调用方都需要使用相同的技术,要么都grpc,要么都dubbo
而http无需关注语言的实现,只需要遵循rest规范
2 RPC的开发要求较多,像Hessian框架还需要服务器提供完整的接口代码(包名.类名.方法名必须完全一致),否则客户端无法运行
通过将 结构化的数据 进行 串行化(序列化),从而实现 数据存储 / RPC 数据交换的功能
序列化: 将 数据结构或对象 转换成 二进制串 的过程
反序列化:将在序列化过程中所生成的二进制串 转换成 数据结构或者对象 的过程
Protocol Buffer 反序列化直接读取二进制字节数据流,反序列化就是 encode
的反过程,同样是一些二进制 *** 作。
反序列化的时候,通常只需要用到 length。tag 值只是用来标识类型的,Properties 的
setEncAndDec() 方法里面已经把每个类型对应的 decode 解码器初始化好了,所以反序列化的时候,tag 值可以直接跳过,从length 开始处理。
https://zhuanlan.zhihu.com/p/101783606
序列化 / 反序列化 属于 TCP/IP模型 应用层 和 OSI`模型 展示层的主要功能:
(序列化)把 应用层的对象 转换成 二进制串
(反序列化)把 二进制串 转换成 应用层的对象
所以, Protocol Buffer属于 TCP/IP模型的应用层 & OSI模型的展示层
通过将 结构化的数据 进行 串行化(序列化),从而实现 数据存储 / RPC 数据交换的功能
序列化: 将 数据结构或对象 转换成 二进制串 的过程反序列化:将在序列化过程中所生成的二进制串 转换成 数据结构或者对象 的过程Protocol Buffer 序列化采用 Varint、Zigzag 方法,压缩 int 型整数和带符号的整数。对浮点型数字不做压缩(这里可以进一步的压缩,Protocol Buffer 还有提升空间)。编码 .proto 文件,会对 option 和 repeated 字段进行检查,若 optional 或 repeated 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不进行序列化(少编码一个字段)。
上面这两点做到了压缩数据,序列化工作量减少。
序列化的过程都是二进制的位移,速度非常快。数据都以 tag - length - value (或者 tag -value)的形式存在二进制数据流中。采用了 TLV 结构存储数据以后,也摆脱了 JSON 中的 {、}、;、这些分隔符,没有这些分隔符也算是再一次减少了一部分数据。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)