Go mod 小结

go.mod 文件 module example.com/foobar go 1.13 require ( example.com/apple v0.1.2 example.com/banana v1.2.3 example.com/banana/v2 v2.3.4 example.com/pineapple v0.0.0-20190924185754-1b0db40df49a ) exclude example.com/banana v1.2.4 replace example.com/apple v0.1.2 => example.com/rda v0.1.0 replace example.com/banana => example.com/hugebanana module:用于定义当前项目的模块路径。 go:用于设置预期的 Go 版本。 require:用于设置一个特定的模块版本。 exclude:用于从使用中排除一个特定的模块版本。 replace:用于将一个模块版本替换为另外一个模块版本。 版本表示方式 基于某一个commit的伪版本号 基本版本前缀-commit的UTC时间-commit的hash前12位 vX.0.0-yyyymmddhhmmss-abcdefabcdef vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef 需要注意的是,同一个仓库的 v2.x.x 和之前小于 v2 大版本的代码被认为是两个不同的仓库。...

May 12, 2021 · 1 min · 徐旭

Go1.16 embed 和 Vue

vue 相关代码: https://github.com/Allenxuxu/ginvue 先全局安装下 vue cli 并创建一个 demo 项目 npm install -g @vue/cli vue create web 然后我们进入 web 目录,修改生成的 package.json 文件调整一下 build 生成的静态文件目录。 –dest 是指定输出的目录 **–no-clean 是让他不要每次覆盖我们的目录,因为后面我们会放一个 go 文件到那个目录。 ** "build": "vue-cli-service build --no-clean --dest ../static", 再新增一个 vue.config.js 文件来修改下 , 这里将 production 的 publicPath 修改成带一个前缀 /ui/ , 这里主要就是为了后面我们的go 代码路由设置方便,所有的前端静态文件请求都带上 /ui 前缀,和后端 API 接口带 /api 前缀区分。 module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/ui/' : '/' } 最后我们再 web 目录运行 npm run build,会生成一个 static 目录(也就是我们修改的 package....

April 20, 2021 · 2 min · 徐旭

Golang slice map channel 小技巧

Slice vs Array Slice 和 Array 是不同的类型 package main func main() { s := make([]int, 100) printSlice(s) var a [100]int printArray(a) } func printSlice(s []int) { println(len(s)) // 100 println(cap(s)) // 100 } func printArray(a [100]int) { println(len(a)) // 100 println(cap(a)) // 100 } Slice 结构体 type slice struct { array unsafe.Pointer len int cap int } 下面的汇编表明,当类型是 slice 的时候,打印 len 或者 cap 的时候,会去栈上取数据: MOVQ 0x28(SP), AX MOVQ AX, 0x8(SP) CALL 0xbfc [1:5]R_CALL:runtime....

April 17, 2021 · 3 min · 徐旭

golang protobuf 字段为零值时 json 序列化忽略问题

protoc 编译生成的 pb.go 文件,默认情况下 tag 中会设置 json 忽略零值的返回属性 omitempty。 type Message struct { Header map[string]string `protobuf:"bytes,1,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } 一个比较 hack 的方式,是在 pb.go 文件生成后,手动去删掉 omitempty 。每次手动去删除,比较麻烦且容易出错,下面提供一个 Makefile ,每次生成 pb.go 的时候就去删除 omitempty 。 proto: protoc --proto_path=. --go_out=. --micro_out=. config/config.proto ls config/*.pb.go | xargs -n1 -IX bash -c 'sed s/,omitempty// X > X.tmp && mv X{.tmp,}' proto 目标的第一个命令是调用 protoc 根据 config/config....

June 2, 2020 · 1 min · 徐旭

go chan 实用示例

尝试发送 select { case c <- struct{}{}: default: fmt.Println("chan 已满,发送不成功") } 尝试接收 select { case v := <- c: default: fmt.Println("chan 中没有信息,接收不成功") } 标准编译器对尝试发送和尝试接收代码块做了特别的优化,使得它们的执行效率比多 case分支的普通 select代码块执行效率高得多。 无阻塞的检查一个 chan 是否关闭 假设我们可以保证没有任何协程会向一个通道发送数据,则我们可以使用下面的代码来(并发安全地)检查此通道是否已经关闭,此检查不会阻塞当前协程。 func IsClosed(c chan struct{}) bool { select { case <-c: return true default: } return false } 最快回应 package main import ( "fmt" "math/rand" "time" ) func source(c chan<- int, id int) { rb := rand....

May 30, 2020 · 1 min · 徐旭

二叉堆与堆排序

二叉堆是一组能够用堆有序的完全二叉树排序的元素,一般用数组来存储。 大顶堆, 每个结点的值都大于或等于其左右孩子结点的值,其顶部为最大值。 小顶堆,每个结点的值都小于或等于其左右孩子结点的值,其顶部为最小值。 二叉堆 性质 根节点在数组中的位置是 1 左边子节点 2i 右子节点 2i+1 父节点 i / 2 最后一个非叶子节点为 len / 2 根节点在数组中的位置是 0 左子节点 2i + 1 右边子节点 2i+ 2 父节点的下标是 (i − 1) / 2 最后一个非叶子节点为 len / 2 - 1 图片来自知乎 实现 构造二叉堆 找到最后一个非叶子节点 ( len / 2 或者 len / 2 - 1) 从最后一个非叶子节点下标索引开始递减,逐个下沉 插入节点 在数组的最末尾插入新节点 将最后一个节点上浮,时间复杂度为O(log n) 比较当前节点与父节点 不满足 堆性质* *则交换 删除根节点 删除根节点用于堆排序...

May 30, 2020 · 2 min · 徐旭

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

图片来自 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 · 徐旭

[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 · 徐旭