二叉树的遍历模版(递归,迭代)

图片来自 leetcode 深度优先遍历(dfs) 前序遍历 中序遍历 后序遍历 广度优先遍历(bfs) type TreeNode struct { Val int Left *TreeNode Right *TreeNode } 深度优先遍历 递归 递归版本,代码比较简单,只需改变 append 数据的位置即可。 前序遍历 func preorderTraversal(root *TreeNode) []int { var ret []int helper(root, &ret) return ret } func helper(root *TreeNode, data *[]int) { if root == nil { return } *data = append(*data, root.Val) helper(root.Left, data) helper(root.Right, data) } 中序遍历 func inorderTraversal(root *TreeNode) []int { var ret []int helper(root, &ret) return ret } func helper(root *TreeNode, data *[]int) { if root == nil { return } helper(root....

April 26, 2020 · 3 min · 徐旭

Github Actions 配置 CI/CD 自动发布 docker 镜像

Github Actions 是 Github 内置的 CI/CD 工具,现在已经对所有的开源项目免费开放了。 本文主要记录使用 Github Actions 实践 CI/CD 的一些配置。 功能目标 代码静态检查 代码单元测试 release/tag 时自动 build 镜像并推送到 docker hub 项目 Dockerfile 和 Makefile 项目主要目录 . ├── LICENSE ├── Makefile ├── README.md ├── config-srv │ ├── Makefile │ └── main.go ├── deployments │ ├── docker │ │ ├── config-srv │ │ │ └── Dockerfile ├── go.mod ├── go.sum config-srv 目录:服务代码 deployments 目录:所有服务的 Dockerfile Makefile 顶层 Makefile:build Docker 镜像 我们先看下顶层的 Makefile...

April 2, 2020 · 3 min · 徐旭

go-micro 动态加载插件源码分析

go-micro 框架支持动态加载插件,无需修改代码。 源码分析 启动服务前,设定 MICRO_PLUGIN 环境变量指定动态库 .so 文件路径,支持多个插件,逗号分割。程序启动前会读取 MICRO_PLUGIN 环境变量,并完成插件设定。 下面是其内部实现: go-micro/service.go func (s *service) Init(opts ...Option) { ... // setup the plugins for _, p := range strings.Split(os.Getenv("MICRO_PLUGIN"), ",") { if len(p) == 0 { continue } // 加载 .so 文件 c, err := plugin.Load(p) if err != nil { logger.Fatal(err) } // go-micro 初始化插件 if err := plugin.Init(c); err != nil { logger.Fatal(err) } } 从上面的代码可以看出,service 初始化化的时候,读取 MICRO_PLUGIN 环境变量中指定的 ....

March 28, 2020 · 5 min · 徐旭

[gev] 自定义协议支持

https://github.com/Allenxuxu/gev gev 是一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库,支持自定义协议,轻松快速搭建高性能服务器。 TCP 为什么会"粘包" TCP 本身就是面向流的协议,就是一串没有界限的数据。所以本质上来说 TCP 粘包是一个伪命题。 TCP 底层并不关心上层业务数据,会套接字缓冲区的实际情况进行包的划分,一个完整的业务数据可能会被拆分成多次进行发送,也可能会将多个小的业务数据封装成一个大的数据包发送(Nagle算法)。 gev 如何优雅处理 gev 通过回调函数 OnMessage 通知用户数据到来,回调函数中会将用户数据缓冲区(ringbuffer)通过参数传递过来。 用户通过对 ringbuffer 操作,来进行数据解包,获取到完整用户数据后再进行业务操作。这样又一个明显的缺点,就是会让业务操作和自定义协议解析代码堆在一起。 所以,最近对 gev 进行了一次较大改动,主要是为了能够以插件的形式支持各种自定义的数据协议,让使用者可以便捷处理 TCP 粘包问题,专注于业务逻辑。 做法如下,定义一个接口 Protocol // Protocol 自定义协议编解码接口 type Protocol interface { UnPacket(c *Connection, buffer *ringbuffer.RingBuffer) (interface{}, []byte) Packet(c *Connection, data []byte) []byte } 用户只需实现这个接口,并注册到 server 中,当客户端数据到来时,gev 会首先调用 UnPacket 方法,如果缓冲区中的数据足够组成一帧,则将数据解包,并返回真正的用户数据,然后在回调 OnMessage 函数并将数据通过参数传递。 下面,我们实现一个简单的自定义协议插件,来启动一个 Server : | 数据长度 n | payload | | 4字节 | n 字节 | // protocol....

October 31, 2019 · 3 min · 徐旭

Uber Go 风格指南

Uber Go 风格指南 译文:https://github.com/Allenxuxu/uber-go-guide 原文:https://github.com/uber-go/guide/blob/master/style.md 简介 风格是指规范代码的共同约定。风格一词其实是有点用词不当的,因为共同约定的范畴远远不止 gofmt 所做的源代码格式化这些。 本指南旨在通过详尽描述 Uber 在编写 Go 代码中的注意事项(规定)来解释其中复杂之处。制定这些注意事项(规定)是为了提高代码可维护性同时也让工程师们高效的使用 Go 的特性。 这份指南最初由 Prashant Varanasi 和 Simon Newton 编写,目的是让一些同事快速上手 Go 。多年来,已经根据其他人的反馈不断修改。 这份文档记录了我们在 Uber 遵守的 Go 惯用准则。其中很多准则是 Go 的通用准则,其他方面依赖于外部资源: Effective Go The Go common mistakes guide 所有的代码都应该通过 golint 和 go vet 检查。我们建议您设置编辑器: 保存时自动运行 goimports 自动运行 golint 和 go vet 来检查错误 您可以在这找到关于编辑器设定 Go tools 的相关信息: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins 指南 指向接口(interface)的指针 你基本永远不需要一个指向接口的指针。你应该直接将接口作为值传递,因为接口的底层数据就是指针。 一个接口包含两个字段: 类型指针,指向某些特定类型信息的指针。 数据指针。如果存储数据是一个指针变量,那就直接存储。如果存储数据是一个值变量,那就存储指向该值的指针。 如果你需要接口方法来修改这些底层数据,那你必须使用指针。...

October 13, 2019 · 13 min · 徐旭

Go 网络库并发吞吐量测试

https://github.com/Allenxuxu/gev 本文主要测试 gev 网络库和其他三方 Go 网络库以及标准库的吞吐量对比。 测试对象 gev :一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库 eviop :evio 的优化版本 evio :Fast event-loop networking for Go gnet :eviop 的网络模型替换版本 net 标准库 测试方法 采用陈硕测试 muduo 使用的 ping pong 协议来测试吞吐量。 简单地说,ping pong 协议是客户端和服务器都实现 echo 协议。当 TCP 连接建立时,客户端向服务器发送一些数据,服务器会 echo 回这些数据,然后客户端再 echo 回服务器。这些数据就会像乒乓球一样在客户端和服务器之间来回传送,直到有一方断开连接为止。这是用来测试吞吐量的常用办法。 测试的客户端代码: https://github.com/Allenxuxu/gev/blob/master/benchmarks/client/main.go 测试脚本:https://github.com/Allenxuxu/gev/blob/master/benchmarks/bench-pingpong.sh 主要做两项测试: 单线程单个 work 协程测试,测试并发连接数为 10/100/1000/10000 时的吞吐量 4线程4个 work 协程测试,测试并发连接数为 10/100/1000/10000 时的吞吐量 所有测试中,ping pong 消息的大小均为 4096 bytes,客户端始终是4线程运行。...

September 22, 2019 · 1 min · 徐旭

开源 gev: Go 实现基于 Reactor 模式的非阻塞 TCP 网络库

gev 轻量、快速的 Golang 网络库 gev 是一个轻量、快速的基于 Reactor 模式的非阻塞 TCP 网络库,支持自定义协议,轻松快速搭建高性能服务器。 为什么有 gev Golang 的 goroutine 虽然非常轻量,但是每启动一个 goroutine 仍需要 4k 左右的内存。读了鸟窝大佬的文章【百万 Go TCP 连接的思考: epoll方式减少资源占用】后,便去研究了了下 evio。 evio 虽然非常快,但是仍然存在一些问题,便尝试去优化它,于是有了 eviop 项目。关于 evio 的问题可以看我的另一篇博文 【Golang 网络库evio一些问题/bug和思考】。在优化 evio 完成 eviop 的过程中,因为其网络模型的缘故,愈加感觉修改它非常麻烦,成本比重新搞一个还高。 最终决定自己重搞一个,更加轻量,不需要的全去掉。加上大学时学习过 muduo ,便参考 muduo 的使用的 Reactor 模型实现 gev 。 在 linux 环境下,gev 底层使用 epoll ,这是 gev 会专注优化的地方。在 mac 下底层使用 kqueue,可能不会过多关注这部分的优化,毕竟很少有用 mac 做服务器的(Windows 环境"暂"不支持)。 特点 基于 epoll 和 kqueue 实现的高性能事件循环 支持多核多线程 动态扩容 Ring Buffer 实现的读写缓冲区 异步读写 SO_REUSEPORT 端口重用支持 支持 WebSocket 支持定时任务,延时任务 支持自定义协议,处理 TCP 粘包 网络模型 gev 只使用极少的 goroutine, 一个 goroutine 负责监听客户端连接,其他 goroutine (work 协程)负责处理已连接客户端的读写事件,work 协程数量可以配置,默认与运行主机 CPU 数量相同。...

September 19, 2019 · 1 min · 徐旭

Go net/http 浅析

GO HTTP Server 使用标准库构建 HTTP 服务 Go 语言标准库自带一个完善的 net/http 包,可以很方便编写一个可以直接运行的 Web 服务。 package main import ( "log" "net/http" ) func hello(w http.ResponseWriter, r *http.Request) { log.Println(r.Method, r.Host, r.RequestURI) w.Write([]byte("hello")) } func main() { http.HandleFunc("/hello", hello) //设置访问的路由 // http.Handle("/hello", http.HandlerFunc(hello)) // 和上面写法等价 err := http.ListenAndServe(":9090", nil) //设置监听的端口并启动 HTTP 服务 if err != nil { log.Fatal("ListenAndServe: ", err) } } $ curl -v 127.0.0.1:9090/hello * Trying 127.0.0.1... * TCP_NODELAY set * Connected to 127....

September 15, 2019 · 6 min · 徐旭

Go channel 拷贝问题

Go 的 channel 使用非常方便,但是总听说 channel 会拷贝传递的数据,生怕频繁拷贝影响效率。 究竟是怎么个拷贝法呢,下面会有两个 demo 验证下。 先说结论: Go channel 的发送接收数据的拷贝和 Go 的函数传参道理是一样的,都是默认的值拷贝。 如果你传递一个值,那么 Go 会复制一份新的;如果传递一个指针,则会拷贝这个指针,不会去拷贝这个指针所指的变量(这一点 C++ 选手可能会理解比较深)。 所以,如果你需要通过 channel 传递一个很大的 struct ,那么应该传递 指针。但是,要非常注意通过 channel 发送后,不要修改这个指,这会导致线程间潜在的竞争。 下面是两个验证的小 demo: 通过 channel 传递指针 package main import ( "fmt" "time" ) func recv(ch <-chan *int) { time.Sleep(1 * time.Second) out := <-ch fmt.Println("recv : ", out, *out) } func main() { i := 1 ch := make(chan *int, 2) fmt....

August 21, 2019 · 1 min · 徐旭

拥抱 Go module

go get 拉包一直时国内选手头疼的问题,虽然梯子可以解决问题,但是总是有很慢的时候,而且需要每台电脑都配置,特别是 CI 的服务器等,很烦人。 七牛云开源了 goproxy ,还免费提供 https://goproxy.cn 作为代理来拉包。 不过 GOPROXY 只有在 Go module 下才能使用,索性全面拥抱 Go module 一劳永逸。 修改一下配置文件,即可: sudo vi /etc/profile 在最后添加如下内容,开启 Go module 和代理: export GO111MODULE=on export GOPROXY=https://goproxy.cn 让配置文件立即生效 source /etc/profile 接下来就可以畅快 Go 了! PS: Go 1.16 已经默认开启 go moudle了。

August 20, 2019 · 1 min · 徐旭