时光很匆忙 留下一些痕迹

礼法岂是为吾辈而设

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

Kafka 设计与理解

整体架构 图片来自 https://zhuanlan.zhihu.com/p/103249714 Producer 负责发布消息到Kafka broker。Producer发送消息到broker时,会根据分区策略选择将其存储到哪一个Partition。 常规的有轮询,随机等策略,主要是为了将消息均衡的发送到各个 partition,提高并行度,从而提高吞吐。常用的还有按 key 哈希,主要是为了实现业务 partition 有序的需求。 Consumer 消息消费者,从Kafka broker读取消息的客户端。 Consumer Group Consumer Group是Kafka提供的可扩展且具有容错性的消费者机制,每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。 Consumer Group下可以有一个或多个Consumer实例。这里的实例可以是一个单独的进程,也可以是同一进程下的线程。 Group ID是一个字符串,在一个Kafka集群中,它标识唯一的一个Consumer Group。 Consumer Group下所有实例订阅的主题的单个分区,只能分配给组内的某个Consumer实例消费。 进度提交 消费者在消费的过程中需要记录自己消费了多少数据,即消费位置信息。在Kafka中,这个位置信息有个专门的术语:位移(Offset)。 Rebalance 何时触发 rebalance 组成员数量发生变化,有新成员加入,或者有成员实例崩溃退出。 订阅主题数量发生变化:Consumer Group可以使用正则表达式的方式订阅主题,比如果consumer.subscribe(Pattern.compile(“t.*c”))就表明该Group订阅所有以字母t开头、字母c结尾的主题。在Consumer Group的运行过程中,你新创建了一个满足这样条件的主题,那么该Group就会发生Rebalance 订阅主题的分区数发生变化,如主题扩容。 Rebalance本质上是一种协议,规定了一个Consumer Group下的所有Consumer如何达成一致,来分配订阅Topic的每个分区Topic的每个分区。 比如某个Group下有20个Consumer实例,它订阅了一个具有100个分区的Topic。正常情况下,Kafka平均会为每个Consumer分配5个分区。这个分配的过程就叫Rebalance。 Rebalance 的流程 在消费者端,重平衡分为两个步骤:分别是 加入组 和 等待领导消费者(Leader Consumer)分配方案。这两个步骤分别对应两类特定的请求:JoinGroup请求和SyncGroup请求。 当组内成员加入组时,它会向 协调者 发送JoinGroup请求(后面会介绍协调者)。在该请求中,每个成员都要将自己订阅的主题上报,这样协调者就能收集到所有成员的订阅信息。一旦收集了全部成员的JoinGroup请求后,协调者会从这些成员中选择一个担任这个消费者组的领导者。 通常情况下,第一个发送JoinGroup请求的成员自动成为领导者。你一定要注意区分这里的领导者和之前我们介绍的领导者副本,它们不是一个概念。这里的领导者是具体的消费者实例,它既不是副本,也不是协调者。领导者消费者的任务是收集所有成员的订阅信息,然后根据这些信息,制定具体的分区消费分配方案。 选出领导者之后,协调者会把消费者组订阅信息封装进JoinGroup请求的响应体中,然后发给领导者,由领导者统一做出分配方案后,进入到下一步:发送SyncGroup请求。 在这一步中,领导者向协调者发送SyncGroup请求,将刚刚做出的分配方案发给协调者。值得注意的是,其他成员也会向协调者发送SyncGroup请求,只不过请求体中并没有实际的内容。这一步的主要目的是让协调者接收分配方案,然后统一以SyncGroup响应的方式分发给所有成员,这样组内所有成员就都知道自己该消费哪些分区了。 JoinGroup请求的主要作用是将组成员订阅信息发送给领导者消费者,待领导者制定好分配方案后,重平衡流程进入到SyncGroup请求阶段。 SyncGroup请求的主要目的,就是让协调者把领导者制定的分配方案下发给各个组内成员。当所有成员都成功接收到分配方案后,消费者组进入到Stable状态,即开始正常的消费工作。 正常情况下,每个组内成员都会定期汇报位移给协调者。当重平衡开启时,协调者会给予成员一段缓冲时间,要求每个成员必须在这段时间内快速地上报自己的位移信息,然后再开启正常的JoinGroup/SyncGroup请求发送。 Broker Kafka集群包含一个或多个服务器,这种服务器被称为broker。一个 broker 可以容纳多个 topic。brocker 是 kafka 中的核心组件,负责消息的存储,分区,路由信息的管理等。...

May 2, 2021 · 2 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 · 徐旭

RocketMQ 设计与理解

整体架构 RocketMQ 主要由 Producer、Broker、Consumer、Name Server 四个部分组成。 其中Producer 负责生产消息,Consumer 负责消费消息,Broker 负责存储消息,Name server 充当路由消息的提供者。 每个 Broker 可以存储多个Topic的消息,每个Topic的消息也可以分片存储于不同的 Broker。Message Queue 用于存储消息的物理地址,每个Topic中的消息地址存储于多个 Message Queue 中。 Topic Topic 是一种逻辑上的分区,是同一类消息的集合,每一个消息只能属于一个 Topic ,是RocketMQ进行消息订阅的基本单位。 每个 topic 会被分成很多 Messsage Queue ,和 Kafka 中的 Partition 概念一样,topic 的数据被分布在不同的 Message Queue 中。 在业务增长,消息量增大时,可以增大 topic 的 Message Queue,这样可以将压力分摊到更多的 broker 上。因为 Producer 可以发送消息的时候可以通过指定的算法,将消息均匀的发送到每个 Message Queue。 NameServer 生产者或消费者能够通过 Name Server查找各 Topic 相应的Broker IP 列表。 Name Server 可以多机部署变成一个集群保证高可用,但这些机器间彼此并不通信,也就是说三者的元数据舍弃了强一致性。 每一个 broker 启动时会向全部的 Name server 机器注册心跳,心跳里包含自己机器上 Topic 的拓扑信息,之后每隔 30s 更新一次,然后生产者和消费者启动的时候任选一台 Name Server 机器拉取所需的 Topic 的路由信息缓存在本地内存中,之后每隔 30s 定时从远端拉取更新本地缓存。...

April 5, 2021 · 2 min · 徐旭

git 修改已经 commit 的邮箱信息

开发过程中,经常会出现提交邮箱搞错的情况。在公司项目中错误提交了自己的 GitHub 邮箱,或者在开源项目中提交了公司邮箱。 下面记录一下补救措施。 先修改 .git/config 或者 修改全局的,修改成你需要的邮箱信息。 [user] email = [email protected] name = yourname git log 找到要修改的那一条 commit,复制要修改的commit 的前一条 commit 的哈希值。 git rebase -i {{刚刚复制的哈希值}} # 或者最近 3 条 $ git rebase -i HEAD~3 然后后会出现一个 vim 打开的文本,将需要修改的 commit 信息前面的 pick 文本改成 edit,保存退出。 修改邮箱信息 git commit --amend --author="name <[email protected]>" --no-edit 然后 git rebase --continue 中间也可跳过或退出 rebase 模式git rebase --skip git rebase --abort 循环执行上面两步,当输出 Successfully rebased and updated refs/heads/master. 修改完成。...

September 10, 2020 · 1 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 · 徐旭