记一次overflow

最近给一个项目加上了限速的功能,跑了一段时间后发现一个问题,超级管理员的速度阈值本来是最大的,实际使用是却发现好想是0。 打了个个断点, 发现admin的rate.Limit 确实是 -1000。 看了下代码,原来是overflow了。 1 2 - limit := rate.NewLimiter(rate.Limit(q.Speed*1000), 1000) + limit := rate.NewLimiter(rate.Limit(float64(q.Speed)*1000), 1000) q.Speed 的type是 int16 当时写的时候觉得float64怎么可能overflow, 现在发现 是int16 * 1000就已经overflow了。 所以类型转换的时候一定要先转换再计算。 update 如果是int64 转 int32 的话,则应该反过来: int32(a / 1000) 其中a是int64类型。 准确来说,小转大,先转换再计算; 大转小,先计算再转小。

February 27, 2023 · datewu

perm函数

工作上遇到一个问题,好奇goalng的排列数Perm是怎么实现的,看了下源代码,写的很简洁。 使用了随机交换算法来得到一个排列组合。 1 2 3 4 5 package rand // import "math/rand" func Perm(n int) []int Perm returns, as a slice of n ints, a pseudo-random permutation of the integers in the half-open interval [0,n) from the default Source. 交换 本质上说就是交换m[i]和m[j],且i> j。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func Perm(n int) []int { m := make([]int, n) for i := 0; i < n; i++ { j := rand.Intn(i + 1) // std implement // m[i] = m[j] // m[j] = i // swap m[i], m[j] = m[j], i // same effect // m[i] = i // m[i], m[j] = m[j], m[i] } return m }

December 28, 2021 · datewu

排列组合

上礼拜有人问我,如何从数组中选择和为n,长度为m的所有的子数组? 问题 输入 a = []int{10, 7, -5, 4, 8, 16, 70, -30, 91} m = 3, n = 15 输出 1 [[10 35 -30] [-5 4 16]] 答案 算法 这是一个典型的排列组合的问题,只要把Cn算出来然后做过滤就好,核心是数组组合的算法。 我使用的是递归算法: 选取包含第一个元素的组合:拼接第一个元素和剔除第一个元素后数组的所有的m-1的组合; 选取不含第二个元素的长度为m的组合; 将上面两个组合合并起来即可;(不用去重,因为没有重复的) 递归结束条件:当m=1的时候,直接放回所有数组元素;当m=len(input)时,直接返回[][]int{input};当m>len(input)时,返回空; golang实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package main import ( "flag" "fmt" ) var ( size = flag.Int("size", 3, "size of array") sum = flag.Int("sum", 0, "sum of two numbers") ) func main() { flag.Parse() input := []int{10, 7, -5, 4, 8, 16, 35, -30, 91} // for _, v := range chooseM(input, 8) { // fmt.Println(v) // } fmt.Println(input) fmt.Println(chooseSumN(input, *size, *sum)) } func chooseM(data []int, m int) [][]int { if len(data) < m { return nil } if len(data) == m { return [][]int{data} } if m == 1 { var res [][]int for _, v := range data { res = append(res, []int{v}) } return res } one := [][]int{} rest := chooseM(data[1:], m-1) for _, v := range rest { m := append([]int{data[0]}, v...) one = append(one, m) } return append(one, chooseM(data[1:], m)...) } func chooseSumN(data []int, m, n int) [][]int { out := [][]int{} for _, v := range chooseM(data, m) { sum := 0 for _, i := range v { sum += i } if sum == n { out = append(out, v) } } return out } 调试输出 1 2 3 4 5 6 7 ❯ ./c3 -sum 15 -size 2 [10 7 -5 4 8 16 35 -30 91] [[7 8]] ❯ ./c3 -sum 15 [10 7 -5 4 8 16 35 -30 91] [[10 35 -30] [-5 4 16]] 工程优化 上面是算法的解释,工程实现的时候可以把选择和过滤结合在一起做,提升代码的时间/空间性能。 ...

December 13, 2021 · datewu

clickhouse初始化

总体来看clickhouse的ddl操作方式和mysql很像。比如use database_name, create user ... identified by ''等。 server docker run 启动clickhouse服务: 1 docker run -d --name clickhouse-server -p 8123:8123 -p 9000:9000 --ulimit nofile=262144:262144 yandex/clickhouse-server:21.8.10.19 default user权限 默认defualt 用户不能添加新的用户,需要修改default权限,否则会报错Code: 497. DB::Exception: Received from localhost:9000. DB::Exception: default: Not enough privileges. To execute this query it's necessary to have the grant CREATE USER ON *.*. : 1 2 3 4 5 6 7 docker exec -it clickhouse-server bash apt update -y apt install vim vi /etc/clickhouse-server/users.xml # update <access_management>1</access_management> exit docker restart clickhouse-server ...

November 22, 2021 · datewu

用户线程调度模型

一般来说多线程3种并发模型: N:1, 把n个用户线程(Green threed)映射到一个操作系统的线程(OS Threed)上。 这种模型的优点是 用户线程之间的上下文切换(context switch)会非常快, 缺点是不能充分的运用多核CPU资源(一个OS Thread只能在一个CPU上); 2. 1:1, 每个用户线程映射到一个操作系统线程上。 这种和第一种的优缺点正好相反。1:1 可以充分利用多核处理器资源,但是上下文切换很慢。 M:N,把M个用户线程映射到N个操作系统线程上。 这种结合了前面两种模型的优点,同时规避了他们的缺点。 M/G/P golang runtime主要通过抽象出Machine/Processor/Groutine三种对象,和一些算法(steal)实现了M:N模型。 Machine M对应操作系统线程,代表被操作系统管理的线程资源。 Goroutine G对应用户线程(Goroutine),包含了stack,指令指针,还有一些会影响这个Goroutine调度的关键信息,比如有关的channel。 Processor P对应本地逻辑调度器上下文(context),是调度算法具体的执行对象,主要用来处理steal和hand-off等算法。 demo normal 上图中,我们有2个M(OS Thread),每个M都有一个本地的context(P),都在运行一个goroutine(G)。 有几点需要说明一下: M必须得到一个P才能运行goroutine里面的指令; P的数量等于环境变量GOMAXPROCS的值, 一般等于宿主机的处理器核心数; 灰色的goroutine没有在running,但是已经准备好被调度了。他们所处的队列叫runqueues,每当执行的go statement指令时,新的goroutine就会加到runqueue的尾部; 每个P都有自己本地runqueue。 (sys)call / hand-off M0把自己的context给了M1,流程是这样的: M0执行G0上的某条syscall指令; M0放弃P进入block状态,M1得到并继续执行P调度算法,可能去执行另外某一个goroutine; syscall返回,M0因为没有P所以不能继续执行G0。现在M0需要去偷一个P执行G0,否则就把G0放到global runqueue里面然后把自己放回thread cahe去sleep。 也有几点需要单独说明: M1可能是scheduler为了处理syscall特意创建的,也可能是来自thread cache; 当P本地的runqueue为空时,P会从global runqueue拉取G;即使本地runqueue没有空,P也会定期的检查global runqueue里的goroutine。 正是因为要处理syscal/hand-off,所以即使GOMAXPROCS等于1,Go还是会启动多个OS线程。 stealing work 当P自己本地的runqueue空了,而且global runqueue也是空的时候, P就会去其他P偷掉对方一半的G,从而使得自己和其它P都能高效工作,提高整体性能。 参考文档

November 19, 2021 · datewu

快慢指针

4月份腾讯面试的时候被问到如何在空间复杂度为O(1)前提下检查连表是否为闭环: 当时没想出来,面试官提醒用快慢指针也没写出来。 回到家里想了下,其实当时已经想出来了,没敢写出来: 1 2 3 4 5 6 7 8 9 10 11 func circular(head *ListNode) bool { slow, fast := head, head for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next if slow == fast { return true } } return false }

April 19, 2021 · datewu

调试golang测试

调试某个go test程序的时候,需要实现confirm/approve功能: 测试完testCase1之后(或者说是某个断点),用户输入yes,执行下一个case,输入no, 则退出整个测试。 分析 下意识的觉得这个很好实现,调用fmt.Scan应该就OK了。 但是写的时候才发现,go test 会强制重定向os.Stdin = /dev/null忽略所有的 stdin输入, 所以没法使用 fmt.Scan来等待键盘(用户)的输入: 1 2 3 4 5 // fmt.Scan reads from os.Stdin, which is set to /dev/null when testing. // Tests should be automated and self-contained anyway, so you don't want // to read from stdin. // Either use an external file, a bytes.Buffer, or a strings.Reader and // call scan.Fscan if you want to test **the literal** `scan` Function. 解决方案 可以参考事件驱动,使用named pipes来实现confirm/approve功能: ...

October 14, 2020 · datewu

细说kubeconfig

今天准备管理某一个kubernetes 集群时发现master主机22端口因为管理的需要被禁用了,无法登陆服务器。 问了一下运维人员,原来是基于安全原因,公司决定禁用所有服务器的root ssh登陆权限, 平时我都是ssh 登陆到master node,在服务器上直接使用kubectl命令 查看/部署/debug deployment/service等资源, 现在只好修改下本地 kubeconfig 文件,用自己本地的 kubectl 管理/操作kubernetes集群。 操作了一段时间后,发现用本地kubectl操作kubernetes体验蛮好的,特别是服务器缺少本地editor(vim) kubectl edit ... 的语法高亮支持。 配置kubeconfig过程分享如下,大体上说过就两步: 添加 context; use context。 1 2 vim .kube/config kubectl config use-context dev-8-admin@kubernetes 除了使用vim 编辑 .kube/config 文件,对于一些简单的配置也可以使用kubectl config command 快速配置kubeconfig: 1 2 3 4 5 6 7 8 9 10 11 12 13 ## create new cluster kubectl config set-cluster NAME [--server=server] [--certificate-authority=path/to/certificate/authority] [--insecure-skip-tls-verify=true] ## create new user kubectl config set-credentials NAME [--client-certificate=path/to/certfile] [--client-key=path/to/keyfile] [--token=bearer_token] [--username=basic_user] [--password=basic_password] [--auth-provider=provider_name] [--auth-provider-arg=key=value] [options] ## create new context kubectl config set-context [NAME | --current] [--cluster=cluster_nickname] [--user=user_nickname] [--namespace=namespace] [options] ## use context kubectl config use-context CONTEXT_NAME [options] 另外,kubectl config set 不支持对 certificate-authority-data字段的设置,只支持指定data文件的路径, 所以推荐用vim 编辑kubeconfig文件。 ...

August 11, 2019 · datewu

nopCloser函数

update: ioutil逐渐被io 取代。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package ioutil // import "io/ioutil" func NopCloser(r io.Reader) io.ReadCloser NopCloser returns a ReadCloser with a no-op Close method wrapping the provided Reader r. As of Go 1.16, this function simply calls io.NopCloser. package io // import "io" func NopCloser(r Reader) ReadCloser NopCloser returns a ReadCloser with a no-op Close method wrapping the provided Reader r. 最近使用baloo写集成测试,遇到了个需求, 在unmarshalrespones之后(或者之前)还要再输出一次response的纯文本格式供debug参考。 即需要多次读http.Resp.Body。 问题 response.Body 只能读一次,读完之后再进行read操作就会遇到EOF error。 分析问题 模糊记得baloo在一次请求中能多次(JSON() 和 String())读取response.Body内容。 仔细去看了下baloo的源代码,发现baloo自己在内部 封装了一个对象 http.RawResonse ,使用了 iouti.NopCloser函数重新填充了res.Body: 1 2 3 4 5 6 7 8 9 10 func readBodyJSON(res *http.Response) ([]byte, error) { body, err := ioutil.ReadAll(res.Body) if err != nil { return []byte{}, err } // Re-fill body reader stream after reading it res.Body = ioutil.NopCloser(bytes.NewBuffer(body)) return body, err } 解决方案 有了 ioutil.NopCloser函数,可以很快速的写出debugPlugin: ...

April 17, 2019 · datewu

Mysql Autocommit问题

客户反馈我们的产品有个很奇怪的问题。 添加完商品后,可以看到商品,但是一刷新页面,刚才添加的商品就消失啦。 以前没碰到过,一直都用的好好的为什么今天就不行了呢? 分析问题 既然一刷新即消失了,就证明没有写入到数据库。 没写入到数据库是什么原因呢? API 也没有报错呀? 更加奇怪的是,有的页面有这个问题,有的没有这个问题。 后端的API 有的是golang写的,有的是Java写的。 golang orm对mysql 数据库的写操作存在上面说的刷新就丢失的问题,Java的orm对mysql的写操作没有问题。 这是为什么呢? DBA 排查了很久发现原来是客户那边的DBA把 mysql的 session autocommit的配置关闭啦。 autocommit 翻了下文档,确定 Java的orm框架会忽略mysql server的配置默认自己commit,golang的orm则没有这个优化(也许是有但我们没有启用?)。 所以会出现 java的后端是正常的,golang的后端写不了mysql。 解决方案 客户DBA开启autocommit配置项后,产品业务恢复正常。

April 12, 2019 · datewu