Golang 网络库 evio 一些问题/bug和思考

Fast event-loop networking for Go 最近翻了 evio 的源码,发现一些问题,主要集中在 linux 平台 epoll 上和读写的处理。 用来唤醒 epoll 的 eventfd 写入数据没有读出 listen 的 fd 注册到所有事件循环,epoll 的惊群问题 loopWrite 在内核缓冲区满,无法一次写入时,出现写入数据丢失 eventfd 的使用问题 在 internal/internal_linux.go 中封装了 epoll 的使用 API 。 // Poll ... type Poll struct { fd int // epoll fd wfd int // wake fd notes noteQueue } 在 OpenPoll 时,会创建一个 eventfd 并将 fd 赋值给 Poll 的 wfd 成员, 并且注册到 epoll 监听可读事件。...

August 15, 2019 · 3 min · 徐旭

Golang 高性能网络库 evio 源码解析

阅读前提:了解 epoll evio 是一个基于事件驱动的网络框架,它非常轻量而且相比 Go net 标准库更快。其底层使用epoll 和 kqueue 系统调度实现。 原理 evio 是 Reactor 模式的简单实现。Reactor 本质就是“non-blocking IO + IO multiplexing”,通过非阻塞IO+ IO 多路复用来处理并发。程序运行一个或者多个事件循环,通过在事件循环中注册回调的方式实现业务逻辑。 evio 将所有文件描述符设为非阻塞,并注册到事件循环( epoll / kqueue )中。相较于传统的 per thread per connection 的处理方法,线程使用更少,线程资源利用率更高。 evio 需要在服务启动前,注册回调函数,当事件循环中有事件到来时,会调用回调函数处理。 使用示例 先从一个简单的 echo server 的例子来了解 evio 。 package main import ( "flag" "fmt" "log" "strings" "github.com/tidwall/evio" ) func main() { var port int var loops int var udp bool var trace bool var reuseport bool var stdlib bool flag....

August 6, 2019 · 11 min · 徐旭

Golang 极简入门教程

Hello World 我们以传统的“hello world”案例开始吧。 package main import "fmt" func main() { fmt.Println("Hello World") } Go的源文件以 .go 为后缀名,这些文件名均由小写字母(推荐做法)组成且不包含空格和其他特殊字符,如 main.go 。如果文件名由多个部分组成,则使用下划线 _ 对它们进行分隔,如 main_test.go 。 Go是一门编译型语言,Go语言的工具链将源代码及其依赖转换成计算机的机器指令。Go语言提供的工具都通过一个单独的命令 go 调用,go 命令有一系列子命令。 $ go help Go is a tool for managing Go source code. Usage: go <command> [arguments] The commands are: bug start a bug report build compile packages and dependencies clean remove object files and cached files doc show documentation for package or symbol env print Go environment information fix update packages to use new APIs fmt gofmt (reformat) package sources generate generate Go files by processing source get download and install packages and dependencies install compile and install packages and dependencies list list packages or modules mod module maintenance run compile and run Go program test test packages tool run specified go tool version print Go version vet report likely mistakes in packages ....

August 4, 2019 · 13 min · 徐旭

dokcer swarm 部署go-micro微服务应用

微服务应用使用容器部署非常方便,但是当应用服务注册自身地址(ip:port)到服务注册中心的时候,如果注册的是容器内的ip,别的服务是无法访问到的。 解决这个问题,可以在运行容器的时候指定网络模式为 host (–net=host) ,这样就可以跳过 Docker 的独立网络栈,直接通过本机IP端口就可以访问,但是这样会大量占用本地端口。 最好的场景还是后端服务都在容器网络中,仅 API 网关暴露一个端口供外部访问,但是同时还后端服务还需要能实现跨机器的网络连通。 早期 Docker 本身的容器网络本身并不支持跨机器,也就是说明如果容器部署在不同的节点(服务器)上面,只能通过暴露端口到宿主机上,再通过宿主机之间进行通信。Docker 12.0 之后的版本自带 Docker Swarm,Docker Swarm 的 Overlay 网络驱动可以实现跨主机网络通信。Kubernetes 固然好,但是同时也非常重,学习成本也很大,Swarm 在小项目中还是有用武之地的。 dokcer swarm 集群搭建 准备两台安装有 docker 的机器: 192.168.0.1 192.168.0.2 192.168.0.1 创建master节点 # docker swarm init # docker swarm join \ --token SWMTKN-1-3uu3gjkdt6xgk06wd1c9gfog8xec99ga69ilcclyzyk181n5ki-6f7frw75gvpdwsl1yvpf885lw \ 192.168.0.1:2377 This node joined a swarm as a worker. 复制上面的 docker swarm join … 在 192.168.0.2 上执行,即将本机加入 swarm 集群。 至此,我们已经创建了一个最基础的 swarm 的集群,执行命令查看: # docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS r76ighlnw0p2r0tbd9wmoqaep server2 Ready Active rzqbzl58hlu89xoty4cedn0er * server1 Ready Active Leader 创建 overlay 网络 先创建一个可以跨机器的 overlay 网络...

July 18, 2019 · 2 min · 徐旭

Go Micro hystrix 熔断

hystrix-go hystrix是Netflix开源的一个JAVA项目,不过GitHub也有golang的实现版本hystrix-go hystrix-dashboard hystrix并没有自带一个仪表盘,无法直观的查看接口的健康状况。所以,我们采用GitHub的一个开源实现hystrix-dashboard。 docker run --name hystrix-dashboard -d -p 8081:9002 mlabouardy/hystrix-dashboard:latest micro API网关插件 关于hystrix的工作原理,可以查阅相关资料,这里只讲解如何封装插件在micro API网关中使用。 err := hystrix.Do("my_command", func() error { // talk to other services return nil }, nil) 使用hystrix.Do() 同步API,第一个参数是command, 应该是与当前请求一一对应的一个名称,如入“GET-/test”。第二个参数传入一个函数,函数包含我我们自己的错误逻辑,当请求失败时应该返回error。hystrix会根据我们的失败率执行熔断策略。 封装Handler // BreakerWrapper hystrix breaker func BreakerWrapper(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Method + "-" + r.RequestURI log.Println(name) err := hystrix.Do(name, func() error { sct := &status_code.StatusCodeTracker{ResponseWriter: w, Status: http.StatusOK} h....

June 27, 2019 · 2 min · 徐旭

Go Micro 服务健康检查

服务健康检查 在微服务架构中,每个服务都会存在多个实例,可能部署在不同的主机中。因为网络或者主机等不确定因素,每个服务都可能会出现故障。我们需要能够监控每个服务实例的健康状态,当一个服务故障时,及时将它从注册中心删除。 实现 micro提供两个方法可以直接实现健康检查功能 micro.RegisterTTL(time.Second*30), micro.RegisterInterval(time.Second*20), Interval就是间隔多久服务会重新注册 TTL就是注册服务的过期时间,如果服务挂了,超过过期时间后,注册中心也会将服务删除 micro内部服务注册的流程 当我们执行service.Run() 时内部会执行Start() 在Start函数中又会执行s.opts.Server.Start(),方法的实现在go-micro/server/rpc_server.go中。 我们跳转到内部server的Start方法 可以发现micro使用一个定时器按照间隔时间去自动重新注册。当服务意外故障,无法向注册中心重新注册时,如果超过了设定的TTL时间,注册中心就会将服务删除。 修改源码 service := grpc.NewService( micro.Name("go.micro.srv.hello"), micro.WrapHandler(ocplugin.NewHandlerWrapper(t)), + micro.RegisterTTL(time.Second*15), + micro.RegisterInterval(time.Second*10), // micro.Version("latest"), ) service := web.NewService( web.Name(name), web.Version("lastest"), + web.RegisterTTL(time.Second*15), + web.RegisterInterval(time.Second*10), web.MicroService(grpc.NewService()), )

June 27, 2019 · 1 min · 徐旭

Golang实现默认参数

在golang 中是不支持默认参数的,micro中有一种优雅的实现方法(并非 micro 首创),叫做 Functional Options Patter。Functional Options 可以用来实现简洁的支持默认参数的函数方法。 options package server import ( "time" ) type Options struct { ConnectTimeOut time.Duration Name string Address string } type Option func(*Options) func newOptions(opt ...Option) Options { opts := Options{} for _, o := range opt { o(&opts) } if len(opts.Address) == 0 { opts.Address = DefaultAddress } if len(opts.Name) == 0 { opts.Name = DefaultName } if opts.ConnectTimeOut == time.Duration(0) { opts....

June 27, 2019 · 2 min · 徐旭

Go Micro API网管增加 JWT 鉴权

micro API网关 micro API网关是基于go-micro开发的,具有服务发现,负载均衡和RPC通信的能力。 业界普遍做法是将鉴权,限流,熔断等功能也纳入API网关。micro API网关本身是可插拔的,可以通过新增插件的方式加入其他功能。 JWT (JSON Web Token) JWT是是微服务中常用的授权技术,关于JWT的技术原理可以参考阮一峰的博文 JWT库封装 lib/token 目录下封装了JWT的库。有一点特殊的是,库中利用consul的KV存储和micro的go-config库实现了动态更新JWT的PrivateKey功能,实际生产中还是应该使用拥有发布和权限管理的配置中心。 go-config 是micro作者实现的一个可动态加载、可插拔的配置库,可以从多种格式文件或者远程服务获取配置。详情可以参考文档中文文档|英文文档 PrivateKey是JWT在编解码时使用的私钥,一旦泄漏,客户端便可以利用这个私钥篡改、伪造Token。所以一般生产环境中都必须具备动态更新私钥的能力,一旦发现泄漏可以立即更改,或者定期更换私钥,提高安全性。 // InitConfig 初始化 func (srv *Token) InitConfig(address string, path ...string) { consulSource := consul.NewSource( consul.WithAddress(address), ) srv.conf = config.NewConfig() err := srv.conf.Load(consulSource) if err != nil { log.Fatal(err) } value := srv.conf.Get(path...).Bytes() if err != nil { log.Fatal(err) } srv.put(value) log.Println("JWT privateKey:", string(srv.get())) srv.enableAutoUpdate(path...) } func (srv *Token) enableAutoUpdate(path ....

June 24, 2019 · 2 min · 徐旭

Go Micro jaeger 分布式链路追踪

安装jaeger jaeger提供一个all in one 的docker镜像,可以快速搭建实验环境 docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:1.6 OpenTracing OpenTracing通过提供平台无关、厂商无关的API,使得开发人员能够方便的添加(或更换)追踪系统的实现。 OpenTracing提供了用于运营支撑系统的和针对特定平台的辅助程序库。 jaeger兼容OpenTracing API,所以我们使用OpenTracing的程序库可以方便的替换追踪工具。 OpenTracing中文文档 jaeger使用 封住一下jaeger的初始化操作方便使用,详细用法可以查看 jaeger-client-go // lib/tracer // NewTracer 创建一个jaeger Tracer func NewTracer(servicename string, addr string) (opentracing.Tracer, io.Closer, error) { cfg := jaegercfg.Configuration{ ServiceName: servicename, Sampler: &jaegercfg.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: &jaegercfg.ReporterConfig{ LogSpans: true, BufferFlushInterval: 1 * time....

June 24, 2019 · 3 min · 徐旭

Go Micro 重试机制

在分布式系统中,经常会有服务出现故障,所以良好的重试机制可以大大的提高系统的可用性。本文主要分析micro的客户端重试机制,以及实例演示。 micro 重试实现 micro框架提供方法设置客户端重试的次数。 Client.Init( client.Retries(3), ) 当client请求失败时,客户端会根据selector的策略选择下一个节点重试请求。这样当一个服务实例故障时,客户端可以自动调用另一个实例。 我们来看看micro 客户端内部重试的实现: go-micro\client\rpc_client.go func (r *rpcClient) Call(ctx context.Context, request Request, response interface{}, opts ...CallOption) error { ... //客户端call 调用函数, 在下面的循环中调用 call := func(i int) error { // call backoff first. Someone may want an initial start delay t, err := callOpts.Backoff(ctx, request, i) if err != nil { return errors.InternalServerError("go.micro.client", "backoff error: %v", err.Error()) } // only sleep if greater than 0 if t....

June 20, 2019 · 3 min · 徐旭