260×260

科学搜查官yuchanns

咖啡、代码与键盘
  • Shenzhen, China
  • 后端开发工程师
readme.md

嗨~我是@yuchanns,网友们通常叫我羽毛,我的常用中文网名是科学搜查官,取自《逆转裁判》中的女角色宝月茜的职业。

我的头像是一只喝着咖啡敲着代码的地鼠,姿势模仿了《崩坏3》的布洛尼娅的一个表情包,由我不愿透露姓名的朋友所绘。

readme

你现在所看到的博客是我使用gatsby编写的主题博客gatsby-theme-yuchanns,融合了github和reddit两者中我所喜爱的元素,目前还在完善中;等vuerpess-next功能完善后将会提供基于vuepress的版本。

我还编写过另一个主题vuepress-theme-hermit,这是一个hugo-hermit的vuepress实现。

到现在为止我谈到的都是前端相关的作品,但前端只是我的兴趣——实际上我是一名后端开发工程师,使用过php、python,目前的主力开发语言是go,并且我对自己的gopher身份而感到自豪。

Posted 17 minutes ago

在Windows中用Linux环境开发

前几天看到这么一个说法:“中国人80%的所谓办公全都在微信和企业微信上完成”。

笔者对Windows并没有怀抱特别喜好或者特别讨厌的情绪,但鉴于笔者的工作日常开发目标平台是Linux,使用到的大量工具链在Linux上很容易获得而在Windows中安装过于麻烦,所以笔者自然更倾向于直接在Linux环境下进行开发工作。然而如上说法,笔者的沟通交流十分依赖企业微信和qq,在一鼓作气安装了Manjaro并被wine折腾得死去活来之后,最终还是乖乖投向了Windows的怀抱。至于业余时间,笔者则会在心爱的MacbookPro上进行兴趣编码(工具链同样容易获得!)。但在工作中能使用苹果笔记本的人往往只是少数。

幸运的是,在前不久,笔者想到了一个堪称绝妙的主意,让Windows和Linux如同MacOS上的PD…

Posted 10 days ago

[WIP]k8s学习之对象

注:本文基于版本1.9.0以上的k8s写作,可作为某种程度的api参考手册。如果你还没有一个k8s集群,可以参考这篇文章进行搭建。

前言

就像编程初学者第一课是“Hello World”一样,在k8s的初见中,我们通常是照葫芦画瓢编写一份xxx.yml文件,然后使用kubectl create -f xxx.yml执行命令,接着集群中某个作用生效了。如下,这份nginx-deployment.yml将会指挥集群创建一个关于nginx的deployment

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 # tells deployment to run 2 pods matching the template template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80

不幸的是,对于初学者来说,既不清楚自己在干什么,将会导致什么后果,也不了解这份清单中的字段表示了什么意思,包含有哪些可选项。

实际上,官方文档都一一给出了答案,不过鲜有新人能细致耐心地找出这些隐藏在角落里的长篇大论的说明及文中的扩展阅读延伸。

什么是k8s对象

首先,在这些配置文件中,我们都在用Yaml语法描述一些内容,这些内容包含着键值对,所以最终可以表达出一个对象,这个对象将会被k8s读取,作为创建实体依据,告知系统工作的期望状态。这被称为k8s对象(kubernetes objects

Posted 12 days ago

[WIP]关于微服务的思考

答应了朋友,结合在公司的经历给他们出一篇关于转型微服务、演进的思考文章。先立一个坑。

Posted 14 days ago

[WIP]mongodb的使用

mongodb是以json格式存储的文档型NoSQL数据库。

笔者以前基本上是以mysql使用为主。最近工作上使用到了mongodb,感觉它的聚合能力非常强大,但语法使用颇为复杂,并且在go中使用上手不太容易,因此决定写一篇文章记录一下自己的使用心得。

go mongo

前置准备

首先我们需要一份官方使用文档,然后再安装一份数据库实例。尽管官方网站提供了在线云实例,但是不知道为什么我这边访问特别的卡,即使科学上网也一样。于是我打算用docker快速部署一个容器,然后用navicat连接。

docker run -d --name mongo -p 27017:27017 -p 28017:28017 mongo:4.4

为后面curd以及复杂聚合操作准备,我还需要以下这些集合以及文档。。。

Posted 17 days ago

[WIP]prometheus的使用

prometheus是一款开源的监控解决方案,通过主动定时拉取应用对外暴露的指标路由,通过图形化展示进程内部的运行状态,并且使用promsql提供查询。

prometheus operator diagram

本文内容预计包括prometheus的快速部署,查询语法介绍、与go项目的集成、使用grafana进行可视化、在k8s中的集成以及通过插件设置阈值进行告警等实践操作。

Posted 21 days ago

goroutine的泄露

今天同事遇到一个问题,是关于goroutine的泄露。

问题和分析

代码简化后如下:

package main import ( "errors" "fmt" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" "log" "net/http" ) func leakGrs() error { s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} ch := make(chan error) for i := range s { go func(i int) { var err error if i == 3 { err = errors.New("something wrong") } ch <- err }(i) } for range s { err := <-ch if err != nil { return err } } return nil } func handlerLeakGrs(c *gin.Context) { err := leakGrs() c.JSON(http.StatusOK, gin.H{ "err": fmt.Sprintf("%+v", err), }) } func main() { engine := gin.Default() pprof.Register(engine) engine.GET("/", handlerLeakGrs) server := http.Server{ Addr: ":8080", Handler: engine, } if err := server.ListenAndServe(); err != nil { log.Fatalf("cannot start server: %+v", err) } }

由于引入了pprof,所以我们可以通过http://localhost:8080/debug/pprof/goroutine?debug=1访问到goroutine状况——

在一开始,一共有三个goroutine,分别是一个主G和两个net/http的G:

goroutine profile: total 3 1 @ 0x1038170 0x103178a 0x1030d55 0x10ca245 0x10cb141 0x10cb123 0x11a389f 0x11b684e 0x12c9a88 0x10677b1 # 0x1030d54 internal/poll.runtime_pollWait+0x54 /Users/yuchanns/go/go1.14/src/runtime/netpoll.go:203 # 0x10ca244 internal/poll.(*pollDesc).wait+0x44 /Users/yuchanns/go/go1.14/src/internal/poll/fd_poll_runtime.go:87 # 0x10cb140 internal/poll.(*pollDesc).waitRead+0x200 /Users/yuchanns/go/go1.14/src/internal/poll/fd_poll_runtime.go:92 # 0x10cb122 internal/poll.(*FD).Read+0x1e2 /Users/yuchanns/go/go1.14/src/internal/poll/fd_unix.go:169 # 0x11a389e net.(*netFD).Read+0x4e /Users/yuchanns/go/go1.14/src/net/fd_unix.go:202 # 0x11b684d net.(*conn).Read+0x8d /Users/yuchanns/go/go1.14/src/net/net.go:184 # 0x12c9a87 net/http.(*connReader).backgroundRead+0x57 /Users/yuchanns/go/go1.14/src/net/http/server.go:678 1 @ 0x1038170 0x103178a 0x1030d55 0x10ca245 0x10ccaa4 0x10cca86 0x11a4152 0x11bf2a2 0x11be0e4 0x12d42dd 0x12d4027 0x15882d9 0x1037d92 0x10677b1 # 0x1030d54 internal/poll.runtime_pollWait+0x54 /Users/yuchanns/go/go1.14/src/runtime/netpoll.go:203 # 0x10ca244 internal/poll.(*pollDesc).wait+0x44 /Users/yuchanns/go/go1.14/src/internal/poll/fd_poll_runtime.go:87 # 0x10ccaa3 internal/poll.(*pollDesc).waitRead+0x1d3 /Users/yuchanns/go/go1.14/src/internal/poll/fd_poll_runtime.go:92 # 0x10cca85 internal/poll.(*FD).Accept+0x1b5 /Users/yuchanns/go/go1.14/src/internal/poll/fd_unix.go:384 # 0x11a4151 net.(*netFD).accept+0x41 /Users/yuchanns/go/go1.14/src/net/fd_unix.go:238 # 0x11bf2a1 net.(*TCPListener).accept+0x31 /Users/yuchanns/go/go1.14/src/net/tcpsock_posix.go:139 # 0x11be0e3 net.(*TCPListener).Accept+0x63 /Users/yuchanns/go/go1.14/src/net/tcpsock.go:261 # 0x12d42dc net/http.(*Server).Serve+0x25c /Users/yuchanns/go/go1.14/src/net/http/server.go:2901 # 0x12d4026 net/http.(*Server).ListenAndServe+0xb6 /Users/yuchanns/go/go1.14/src/net/http/server.go:2830 # 0x15882d8 main.main+0x138 /Users/yuchanns/Coding/golang/gobyexample/main.go:51 # 0x1037d91 runtime.main+0x211 /Users/yuchanns/go/go1.14/src/runtime/proc.go:203 1 @ 0x13603a5 0x13601c0 0x135cf8a 0x136a3aa 0x1587ef8 0x1587ea7 0x157245b 0x1585cb0 0x157245b 0x1584de1 0x157245b 0x157c346 0x157ba1e 0x12d3f33 0x12cf8ac 0x10677b1 # 0x13603a4 runtime/pprof.writeRuntimeProfile+0x94 /Users/yuchanns/go/go1.14/src/runtime/pprof/pprof.go:694 # 0x13601bf runtime/pprof.writeGoroutine+0x9f /Users/yuchanns/go/go1.14/src/runtime/pprof/pprof.go:656 # 0x135cf89 runtime/pprof.(*Profile).WriteTo+0x3d9 /Users/yuchanns/go/go1.14/src/runtime/pprof/pprof.go:329 # 0x136a3a9 net/http/pprof.handler.ServeHTTP+0x339 /Users/yuchanns/go/go1.14/src/net/http/pprof/pprof.go:248 # 0x1587ef7 net/http.HandlerFunc.ServeHTTP+0x77 /Users/yuchanns/go/go1.14/src/net/http/server.go:2012 # 0x1587ea6 github.com/gin-contrib/pprof.pprofHandler.func1+0x26 /Users/yuchanns/go/pkg/mod/github.com/gin-contrib/pprof@v1.3.0/pprof.go:56 # 0x157245a github.com/gin-gonic/gin.(*Context).Next+0x3a /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/context.go:156 # 0x1585caf github.com/gin-gonic/gin.RecoveryWithWriter.func1+0x5f /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/recovery.go:83 # 0x157245a github.com/gin-gonic/gin.(*Context).Next+0x3a /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/context.go:156 # 0x1584de0 github.com/gin-gonic/gin.LoggerWithConfig.func1+0xe0 /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/logger.go:241 # 0x157245a github.com/gin-gonic/gin.(*Context).Next+0x3a /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/context.go:156 # 0x157c345 github.com/gin-gonic/gin.(*Engine).handleHTTPRequest+0x665 /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/gin.go:409 # 0x157ba1d github.com/gin-gonic/gin.(*Engine).ServeHTTP+0x17d /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/gin.go:367 # 0x12d3f32 net/http.serverHandler.ServeHTTP+0xa2 /Users/yuchanns/go/go1.14/src/net/http/server.go:2807 # 0x12cf8ab net/http.(*conn).serve+0x86b /Users/yuchanns/go/go1.14/src/net/http/server.go:1895

然后对http://localhost:8080/debug/pprof/goroutine?debug=1进行多次访问,再次查看goroutine状态,发现G的数量多达153,其中150个全是main.leakGrs这个方法的:

goroutine profile: total 153 150 @ 0x1038170 0x1006dfd 0x1006bc5 0x15883ee 0x10677b1 # 0x15883ed main.leakGrs.func1+0x4d /Users/yuchanns/Coding/golang/gobyexample/main.go:22 1 @ 0x1038170 0x103178a 0x1030d55 0x10ca245 0x10ccaa4 0x10cca86 0x11a4152 0x11bf2a2 0x11be0e4 0x12d42dd 0x12d4027 0x15882d9 0x1037d92 0x10677b1 # 0x1030d54 internal/poll.runtime_pollWait+0x54 /Users/yuchanns/go/go1.14/src/runtime/netpoll.go:203 # 0x10ca244 internal/poll.(*pollDesc).wait+0x44 /Users/yuchanns/go/go1.14/src/internal/poll/fd_poll_runtime.go:87 # 0x10ccaa3 internal/poll.(*pollDesc).waitRead+0x1d3 /Users/yuchanns/go/go1.14/src/internal/poll/fd_poll_runtime.go:92 # 0x10cca85 internal/poll.(*FD).Accept+0x1b5 /Users/yuchanns/go/go1.14/src/internal/poll/fd_unix.go:384 # 0x11a4151 net.(*netFD).accept+0x41 /Users/yuchanns/go/go1.14/src/net/fd_unix.go:238 # 0x11bf2a1 net.(*TCPListener).accept+0x31 /Users/yuchanns/go/go1.14/src/net/tcpsock_posix.go:139 # 0x11be0e3 net.(*TCPListener).Accept+0x63 /Users/yuchanns/go/go1.14/src/net/tcpsock.go:261 # 0x12d42dc net/http.(*Server).Serve+0x25c /Users/yuchanns/go/go1.14/src/net/http/server.go:2901 # 0x12d4026 net/http.(*Server).ListenAndServe+0xb6 /Users/yuchanns/go/go1.14/src/net/http/server.go:2830 # 0x15882d8 main.main+0x138 /Users/yuchanns/Coding/golang/gobyexample/main.go:51 # 0x1037d91 runtime.main+0x211 /Users/yuchanns/go/go1.14/src/runtime/proc.go:203 1 @ 0x1054a5e 0x10b3ee6 0x10cb071 0x10cb03a 0x11a389f 0x11b684e 0x12c9a88 0x10677b1 # 0x1054a5d syscall.syscall+0x2d /Users/yuchanns/go/go1.14/src/runtime/sys_darwin.go:63 # 0x10b3ee5 syscall.read+0x65 /Users/yuchanns/go/go1.14/src/syscall/zsyscall_darwin_amd64.go:1242 # 0x10cb070 syscall.Read+0x130 /Users/yuchanns/go/go1.14/src/syscall/syscall_unix.go:189 # 0x10cb039 internal/poll.(*FD).Read+0xf9 /Users/yuchanns/go/go1.14/src/internal/poll/fd_unix.go:165 # 0x11a389e net.(*netFD).Read+0x4e /Users/yuchanns/go/go1.14/src/net/fd_unix.go:202 # 0x11b684d net.(*conn).Read+0x8d /Users/yuchanns/go/go1.14/src/net/net.go:184 # 0x12c9a87 net/http.(*connReader).backgroundRead+0x57 /Users/yuchanns/go/go1.14/src/net/http/server.go:678 1 @ 0x13603a5 0x13601c0 0x135cf8a 0x136a3aa 0x1587ef8 0x1587ea7 0x157245b 0x1585cb0 0x157245b 0x1584de1 0x157245b 0x157c346 0x157ba1e 0x12d3f33 0x12cf8ac 0x10677b1 # 0x13603a4 runtime/pprof.writeRuntimeProfile+0x94 /Users/yuchanns/go/go1.14/src/runtime/pprof/pprof.go:694 # 0x13601bf runtime/pprof.writeGoroutine+0x9f /Users/yuchanns/go/go1.14/src/runtime/pprof/pprof.go:656 # 0x135cf89 runtime/pprof.(*Profile).WriteTo+0x3d9 /Users/yuchanns/go/go1.14/src/runtime/pprof/pprof.go:329 # 0x136a3a9 net/http/pprof.handler.ServeHTTP+0x339 /Users/yuchanns/go/go1.14/src/net/http/pprof/pprof.go:248 # 0x1587ef7 net/http.HandlerFunc.ServeHTTP+0x77 /Users/yuchanns/go/go1.14/src/net/http/server.go:2012 # 0x1587ea6 github.com/gin-contrib/pprof.pprofHandler.func1+0x26 /Users/yuchanns/go/pkg/mod/github.com/gin-contrib/pprof@v1.3.0/pprof.go:56 # 0x157245a github.com/gin-gonic/gin.(*Context).Next+0x3a /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/context.go:156 # 0x1585caf github.com/gin-gonic/gin.RecoveryWithWriter.func1+0x5f /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/recovery.go:83 # 0x157245a github.com/gin-gonic/gin.(*Context).Next+0x3a /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/context.go:156 # 0x1584de0 github.com/gin-gonic/gin.LoggerWithConfig.func1+0xe0 /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/logger.go:241 # 0x157245a github.com/gin-gonic/gin.(*Context).Next+0x3a /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/context.go:156 # 0x157c345 github.com/gin-gonic/gin.(*Engine).handleHTTPRequest+0x665 /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/gin.go:409 # 0x157ba1d github.com/gin-gonic/gin.(*Engine).ServeHTTP+0x17d /Users/yuchanns/go/pkg/mod/github.com/gin-gonic/gin@v1.6.2/gin.go:367 # 0x12d3f32 net/http.serverHandler.ServeHTTP+0xa2 /Users/yuchanns/go/go1.14/src/net/http/server.go:2807 # 0x12cf8ab net/http.(*conn).serve+0x86b /Users/yuchanns/go/go1.14/src/net/http/server.go:1895

同事贴出他的代码,询问错误在哪里。实际代码比较复杂,特别是在循环返回错误那块,因此一时之间大家也没看出问题所在。

幸好因为事先知道问题在于这段代码造成的泄露,经过简化,很快大家就弄清楚了问题在于使用了go…

Posted a month ago

[WIP]Procreate的使用

这是第一篇关于绘画的文章。

Procreate5X Lockup 01

我一直想要学习绘画,ipad pro也购买了好几个月,但是想做的事情太多,人的精力却有限。今天终于下定决心,开启了这篇文章,作为督促自己去完成的动力。

本文是关于ipad pro上的一个数码绘画app的使用笔记,可作为教程/备忘录查阅使用。

Posted a month ago

[WIP]porotobuf的使用

protobuf是谷歌开发的一款跨平台跨语言强扩展性的用于序列化数据的协议,就像人们常用的xml、json一样。它主要由C++编写,用户按照语法可以批量生成对应语言的代码模板,用于诸如微服务rpc交换数据之类的通信。

本文将会结合官方文档以及grpc,从安装到案例进行全面的讲解。

protobuf

Posted a month ago

[WIP]go项目怎么布局

萌新总是喜欢问,“go的项目应该怎么组织结构?如何分层?”。

群友们纷纷给出自己的见解,转语言玩家又在犹疑:“我以前写Java/PHP/Python的时候不这么做,感觉这个不是很合适呀~”

笔者首先想说的是,拘泥于固定范式往往是因为使用者被其他语言成熟生态缔造的框架目录规范所禁锢了认知。没有了他人制定的秩序指导,一时间无所适从显得迷茫的结果。

当然自由并不意味着为所欲为,无论是从协作的角度考虑,还是从逻辑清晰的角度去看,一个规范的目录结构还是有所必要的。然而规范并不是一成不变的模板,笔者想强调的是,我们应该根据具体的开发方向、业务的类型来决定项目的结构。

在发表长篇大论之前,笔者先插叙一个前置的话题基础——go的模板。

Template

Posted 2 months ago

那些**即服务

前言

本文作为工作调研结果的整理。

笔者在上一家公司的主要工作是主导和开发SaaS,最近换了新的工作,内容变成了开发PaaS。后者可以说是前者的增强版,不过具体有什么分别,笔者并未做过仔细的考虑。

最近我的导师要求我准备资料、认真调研,理解PaaS和SaaS的区别。考虑到经常与这俩名词一起出现的还有IaaS,便想着顺便一起了解一下。

PaaS是什么

参考资料美洽CTO李令辉访谈

PaaS是一种售卖平台服务的SaaS。

举个俗例:

一个客户到饭馆吃饭,做饭的厨师提供的就是SaaS产品;而厨师做饭需要锅碗瓢盆煤气吸油烟机等工具,也就是一个做饭的平台,他可以向厂商订购这个平台。厂商提供的就是PaaS服务。

PaaS可以分为三个种类:

  • 面向基础产品
  • 面向业务产品
  • 面向开发产品

面向基础产品

提供基础架构服务的产品,比如微服务框架、配置管理中心、队列、缓存、数据库和服务监控等。

Posted 2 months ago

delve使用笔记

delve(以下简称dlv)是一个go语言debug工具。

如果读者有使用Goland进行debug的经验,应该很熟悉如下场景——

当我们进行debug的时候,只需要在代码对应的地方打上断点,然后右键main或者Test*开头函数旁边的绿色三角箭头标记,选择debug,IDE就会自动执行到断点处并暂停程序的运行。接着我们在IDE下方弹出的Debug窗口里,点击黄色的箭头(Step Info)就可以进行单步调试;同时我们还可以在Vriables框里看到该函数中被分配的变量类型及其内容等信息;在frame窗口里可以切换不同的goroutine并查看相应的栈帧,以及选择某一帧查看当时的信息。

goland debug

这是JetBrains编辑器的福利之一,而一旦我们因为许可证问题(比如开源许可证不可用于商业开发)不能使用Goland,又该怎么进行debug呢?答案当然是开头提到的dlv。

快速开始

dlv的获取方式很简单,和平时拉取go第三方库的方式一样,执行go get -u github.com/go-delve/delve/cmd/dlv

Posted 2 months ago

pprof的使用

前些天进行面试的时候,面试官问了我pprof的使用问题。我才意识到,虽然使用次数颇为不少,但并没有详细探究pprof的用法并总结成文章,以至于被询问“你常用的命令是哪几个?”时,只来得及想起toplist两个命令。

这篇文章对pprof的使用进行一个总结,内容将会包括“pprof是什么”、“runtime/pprofnet/http/pprof有什么关系”、“如何生成报告”、“分析报告有哪几种方式”以及“结合实际案例进行一些分析”。

pprof简述

pprof是一个用于分析数据的可视化和分析工具,由谷歌公司的开发团队使用go语言编写成的。它通过一些手段收集进程在运行中调用堆栈等信息,按照一定格式保存到一个proto协议的结果文件中,然后对这个文件生成多种维度的可视化分析结果,用于对进程运行过程状况的分析。

要使用完整的pprof工具,可以使用go get工具进行获取和安装:

go get -u github.com/google/pprof

不过平常情况下,我们可以直接使用go标准库的runtime/pprof以及net/http/pprof生成报告,并使用go tool pprof

Posted 2 months ago

[WIP]《春物》观后感

当我还在读书的时候看过不少优秀轻小说,例如《全金属狂潮》、《凉宫春日系列》、《圣魔之血》、《吸血鬼猎人D》、《废弃公主》等等,因此动过写小说的念头,也曾尝试写过几万字短篇。

然而问题在于,没有经过系统的锻炼和合理的思考,写出来的故事往往不成体系,难以为继。如何组织剧情条理,使之具备有趣吸引人的特征,一直是我的困扰。

再后来,工作后,也没有余裕去思考这些东西。

今年来一直有再次进行写作的念头,想把过去设想过的故事使用合理的方式描写出来。恰好最近处于工作空窗期,看了大名鼎鼎的《我的青春恋爱物语果然有问题。》动画第一季。从一开始的略感无聊,到中期的稍显有趣,以及结尾的共情释怀,盛名之下果然无虚士。这么日常的题材作者却以巧妙的伏笔和富有特色的人设娓娓道来,正是一种剧情组织的绝佳范例。因此我(第一次)打算好好分析一下作者的安排用意,借此提炼出关于剧情组织的技巧,吸收己用。

my youth romantic comedy

注:限于时间因素,笔者没有阅读原作小说。本文以动画为参考展开分析。

Posted 2 months ago

sqlmock的使用

今天q群一哥们儿说,他使用beego orm的InserOrUpdate的时候出现了相同主键还是会执行新增插入的bug,找我帮忙看看什么情况。

当时我的第一反应是让他先在debug模式下打印sql语句看看有没有什么问题,但小伙子可能是比较紧张一直打印不出来。

由于我当时不在生产电脑前,对beego也不是很熟悉,只能临时用普通电脑装一个go,设置一下环境拉一下代码写一个测试用例。因为安装mysql太麻烦了,所以我打算简单的用DATA-DOG/go-sqlmock来mock数据库返回。

于是就顺手写一下使用记录,算是给那位大兄弟的一个教程科普吧。

情景简述

案例情景介绍如下:有一个TExchangeInfo结构体,实例化后填充数据,然后执行InsertOrUpdate,当数据存在时,使用更新,当数据不存在时才插入:

type TExchangeInfo struct { ID int64 `orm:"column(id);auto"` DeparmentID int64 `orm:"column(deparment_id)"` Times uint `orm:"column(times)"` Number uint `orm:"column(number)"` Lastmodified time.Time `orm:"column(lastmodified);type(datetime);auto_now"` }

sqlmock使用

sqlmock…

Posted 3 months ago

编写主引导扇区

说明

x86系列文章为书籍《x86汇编语言:从实模式到保护模式》的学习笔记,内容属于笔者学习总结性质。

笔者将之发表到博客上,一方面是作为笔记存档,另一方面希望对同在阅读本书的小伙伴起到理解帮助作用。

计算机的启动过程

前置概念

  • 内存:动态随机访问存储器(DRAM),访问任何一个内存单元的速度和地址无关。
  • BIOS:只读存储器(ROM),固化了开机时要执行的指令(主要是硬件的诊断、检测和初始化)。
  • 硬盘:外存储器之一。

    • 磁头:在同一个轴上拥有许多盘片,每个盘片拥有上下两个磁头,编号为0、1、2、3……以此类推。
    • 磁道:磁头距离圆心每步进一次,都可以形成一个圆圈,就是磁道,依次编号为0、1、2、3……。
    • 柱面:寻道过程是机械动作,需要尽量减少对磁头的移动,所以访问数据优先按照磁头0读第一个盘上面的磁道0、磁头1读第一个盘下面磁道0、磁头2读第二个盘上面的磁道0……读完全部盘面的磁道0后再返回磁头0读第一个盘的磁道1——这一访问过程中形成的圆柱就是柱面,依次编号为0、1、2、3……。
    • 扇区:磁道上可以进行分段,呈现扇形,就是扇区。通常为63个。依次编号为1、2、3、4……。
    • 主引导扇区:0面0道1扇区。
Posted 3 months ago

指令和指令集

说明

x86系列文章为书籍《x86汇编语言:从实模式到保护模式》的学习笔记,内容属于笔者学习总结性质。

笔者将之发表到博客上,一方面是作为笔记存档,另一方面希望对同在阅读本书的小伙伴起到理解帮助作用。

前置概念

  • 寄存器:处理器在操作过程中将数据临时存储的电路。4位、8位、16位、32位和64位寄存器,表示可以容纳的数据容量比特数(bit)。
  • 单位换算:16bit = 2byte = 1word
  • 高低位:从右到左为字节上的每一位编号。0是最低位;最左边是最高位。
  • 字:特指16个二进制位的长度,编号是0~15。0~7是低字节,8~15是高字节。
  • 双字:32位寄存器可以存放4个字节(byte),编号0~31。0~15是低字节,16~31是高字节。

内存储器

内存按字节来组织,单词访问的最小单位为1字节。

内存地址采用十六进制表示法,第一个字节地址为0000H,第二个字节地址为0001H…

Posted 3 months ago

十六进制计数法

说明

x86系列文章为书籍《x86汇编语言:从实模式到保护模式》的学习笔记,内容属于笔者学习总结性质。

笔者将之发表到博客上,一方面是作为笔记存档,另一方面希望对同在阅读本书的小伙伴起到理解帮助作用。

进制概念

  • 基数:计数法用来表示的数字符号的个数。例如,二进制可用来表示的数字符号有0和1,基数就是2
  • 权:每个数字在不同的位置上(个位、十位、百位……)具有不同的基数放大倍数。用公式表示为$基数^{n-1}$(n表示从右数第几位),就是权。
  • 1B、10D、13H:B(Binary)表示二进制、D(Decimal)表示十进制、H(Hexadecimal)表示十六进制。

十进制和其他进制换算规则

10D转换为1010B

$$ \frac{10}{2}=5 \cdots\cdots 0 $$ $$ \frac{5}{2}=2 \cdots\cdots 1 $$ $$ \frac{2}{2}=1 \cdots\cdots 0 $$ $$ \frac{1}{2}=0 \cdots\cdots 1 $$

将被除数除去对应进制的基数(上例为2),存余再取商继续除去基数,反复直到商为0。将余数从下到上的顺序取回表示成从左到右的数字,就完成了十进制到其他进制的转换。

1010B转换为10D

Posted 3 months ago

【译】写给Vue开发者的React指南

原文:react-for-vue-developers

在过去的三年里,我曾在不同的项目中使用了React和Vue,遍及小型网站和大规模的应用。

上个月我写了一篇《为什么我偏爱React更甚于Vue》的文章,很快我参加了全栈之音上Adam Wathan的谈话节目,从一名Vue开发者的角度对React进行了讨论。

我们在播客上讲了很多东西,但大部分内容可以说都是通过一些代码片段对比两者之间的相似与不同之处。

这篇文章简明扼要地介绍了Vue的大部分功能,以及在2019年我是如何通过React的hooks实现同样的效果。

模板

React可选项:JSX。

Vue使用HTML字符串和一些自定义指令作为模板。通常推荐以.vue后缀来区分模板和脚本(以及可选的样式)。

<template> <p>Hello, {{ name }}!</p> </template> <script> export default { props: ['name'] }; </script>

React则使用JSX——一种ECMAScript的扩展(语法糖)。

export default function Greeter({ name }) { return <p>Hello, {name}!</p>; }

条件渲染

React可选项:逻辑&&

Posted 5 months ago

使用NFS共享文件夹

这是一篇衍生自笔者的k8s学习过程的前置知识文章。

前言

在谈论到k8s的持久卷(Persistent Volume)时,其实质表达的意思是,将集群系统节点中的某个路径挂载到pod中。

例如,使用基于docker驱动的minkube时,持久卷是将minikube容器中的某个路径挂载到容器中的docker创建的pod中——它的存储不受pod生命周期的影响,因而得名。然而当你删除minikube集群时,持久卷中的东西照样会消失,毕竟这是在minikube容器中的卷。

而笔者更希望的持久卷能像使用docker-compose那样,做到真正持久在宿主机上,不受minikube存在与否的影响。

在minikube的issue社区中,笔者发现有人正在讨论相关的feature request#7604。遗憾的是该问题是今年四月11才提出的,而解决的PR#8136截止本文写下之时的1…

Posted 5 months ago

在Scheme中实现While

在ChezScheme中没有关于循环的语法。

最近在看数据结构和算法的东西,使用scheme来实现。打算使用单元测试+docker的方式进行,却发现无法使用死循环来维持容器生存。

没有的话,实现一个就好了,lisp的自由度很高,一个while自然不在话下。

定义语法

我们使用的是define-syntax关键字,来源于R6RS标准。本质上是创建一个宏(Macro),只不过lisp的宏比C系语言更强大一些。

(define-syntax <keyword> <expression>)

这里需要注意下和define的区别。两者都可以创建一个函数,但是define使用的参数是形参,而define-syntax可以对参数进行修改影响到外部,这也是实现循环的一个必要条件。

(library (libs customsyntax) (export while) (import (chezscheme)) (define-syntax while (syntax-rules () ((_ pred proc1 ...) (let loop () (when pred proc1 ... (loop)))))) )

while的内部的实现其实就是递归的自我调用,结合when对条件进行判断来决定什么时候结束递归。

下面是使用示范:

(import (chezscheme) (libs customsyntax)) (let ((i 0) (a-second (make-time 'time-duration 0 1))) (while (> 5 i) (display (current-time 'time-utc)) (newline) (sleep a-second) (set! i (+ i 1))) )

每隔一秒输出一次utc时间,循环5次

Posted 5 months ago

k8s学习笔记

k8s学习笔记。

先从简单的学起,记录笔记。手册需要好好研究——

Hello Minikube

安装环境

部署配置:

OS: Deepin 15.11 x86_64

Model: NUC8i7BEH J72992-307

Kernel: 4.15.0-30deepin-generic

CPU: Intel i7-8559U (8) @ 4.5GHz

GPU: Intel Integrated Graphics

Memory: 15909MB

通过MBP访问局域网NUC环境。

确保支持虚拟化技术:

grep -E --color 'vmx|svm' /proc/cpuinfo # 输出不为空

安装docker

sudo apt install docker-ce # 通过apt管理包安装docker sudo groupadd docker # 创建docker组(一般安装docker会自动创建) sudo gpasswd -a ${USER} docker # 将当前用户加入到docker用户组 sudo service docker restart # 重启docker docker version # Docker Engine - Community # Version: 18.09.6

配置/etc/docker/daemon.json的镜像源为国内镜像源(阿里地址)

安装kubectl

需要科学上网,我的做法是通过MBP下载二进制包然后再通过FileZilla传递到NUC上。

# curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl # 可下载最新版 curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.0/bin/linux/amd64/kubectl chmod +x ./kubectl # 可执行 sudo mv ./kubectl /usr/local/bin/kubectl # 配置环境可用

配置shell补全和别名,我使用的是oh-my-zsh…

Posted 7 months ago

使用Jenkins(一)

jenkins2

前言

Jenkins是一个用Java编写的持续集成开源工具,与之类似的还有TravisCICircleCI以及最近推出的GithubActions等工具。笔者以前使用得较多的是Circle——无论是Travis还是Circle,两者都是github上十分流行于开源者之间的选择。

但是编码生活不仅仅只有开源,当我们编写一些私密商业项目、公司内部项目时,往往不希望源码流出到第三者CI/CD工具中,这时候我们就可以选择开源可自行部署的Jenkins。

使用docker安装

在官网上提供了多种Jenkins安装部署的方式,笔者是容器化部署的推崇者,因此选择docker镜像部署的方式,这样可以免除因对Java不熟而历程坎坷的问题。

镜像的选择

如果我们到dockerhub上进行搜索,会发现排名第一的是名为Jenkins…

Posted 7 months ago

容器化技术初探索笔记(一)Namespace

前言

虽然作为一个开发人员,快速布置好开发环境是一个无需多言的基本功,但是每切换到一个新的主机环境,想要完全重现自己熟悉的环境依旧是一个耗时麻烦的工作。于是在过去两年中我逐渐习惯于把docker作为自己的开发环境——得益于可定制化的开箱即用配置我再也不用操心每一步动作该如何因地制宜。

那么容器这种技术到底是如何实现的,原理是什么?在使用的过程中我的好奇心越来越重,终于买了一本《自己动手写Docker》1的书,尝试对容器化技术的实现进行初步的了解。

环境

在此先说明下自己使用的开发环境,以便保证读者获得行为一致的结果:

  • 操作系统:Ubuntu 20.04LTS
  • 语言版本:Golang1.14、PHP7.4-cli
  • 内核版本: 5.4.0-28-generic
  • 硬件:Intel NUC8i7BEH
yuchanns@yuchanns-NUC8i7BEH --------------------------- OS: Ubuntu 20.04 LTS x86_64 Host: NUC8i7BEH J72992-307 Kernel: 5.4.0-28-generic Uptime: 16 hours, 47 mins Packages: 1622 (dpkg), 6 (snap) Shell: zsh 5.8 Terminal: /dev/pts/2 CPU: Intel i7-8559U (8) @ 4.500GHz GPU: Intel Iris Plus Graphics 655 Memory: 955MiB / 15881MiB

书籍中所使用的内核版本较旧,因此部分设置有所改变,本文以Ubuntu 20.04LTS

Posted 8 months ago

Go语言错误处理

前言

go的原生错误处理十分简略。

刚接触go时,我们对它的错误处理最直观的认识就是在调用函数方法时,常常会有多返回值,而最后一个值一般就是错误类型error;当接收它的变量值为nil时,表示函数正确执行;否则我们可以通过打印该变量的结果来获取错误信息——这个结果是一个字符串。

使用过其他现代高级语言(Java、Python、PHP等等)的人,都知道这些语言的错误是通过try... catch...的方式进行抛出捕获。姑且不论哪种方式更好,是退步还是进步,对于程序调试来说,这些语言都提供了十分详细的可回溯信息,常用的有“文件名”、“函数名”、“代码行数”和“错误信息”,可以辅助快速定位错误的发生原因;此外我们还可以通过捕获不同类型的错误来有选择地后续处理的错误(比如捕获FileNotFoundException时选择创建文件而捕获HttpException时直接终止程序继续执行)。

go…

Posted 8 months ago

用redis做队列

最近做一个小型项目,因为rabbitMQ等专业软件比较重,故团队决定采用redis实现一个轻量的队列。

目标

在这篇文章中,你可以获得:

  • redigo包的基本用法
  • 初步认知context包的作用
  • 了解一个功能模块的实现思路以及如何逐步完善的步骤
  • Go特性(selectchannelgoroutine)的利用

最终代码量大概265行左右。

redis队列的原生用法

redis并不是被设计用来做队列的,事实上它并不是那么适合作为队列载体——官方也不推荐用来做队列,甚至因为使用redis做队列的人太多,而促使antirez(redis的作者)开发了另一个名为Disque1的专业队列库,据说将会加入到redis6中。

尽管如此,redis依旧提供了号称Reliable queue的队列指令2

我们知道,当生产者在另一端生成消息之后,这一端的消费者就要取出这一消息进行消费动作;而在消费的过程中如果出现任何异常——例如“程序崩溃”等问题,造成进程的退出,消息就会丢失。为此,redis官方提供了RPOPLPUSH这一队列指令,在从队列中取出消息的同时又塞进另一个队列中。这样当程序发生异常退出时,我们也可以通过第二个队列来找回丢失的消息。

Posted 9 months ago

使用CircleCI发布版本

最近我写了两个博客皮肤。

写第一个的时候,手动填写版本号,进行发布,又要在github上贴tag,又要npm publish,有时候顺序搞反了,或者忘记其中一个操作,很麻烦。

后来我在看date-fns1的的仓库的时候,注意到这个仓库的package.json文件里的version字段是一个字符串,写着:

"version": "DON'T CHANGE; IT'S SET AUTOMATICALLY DURING DEPLOYMENT; ALSO, USE YARN FOR DEVELOPMENT",

好奇之下花了几个小时研究,总算初步用上了ci自动发版本的功能——

前言

本文理论上分为两个部分,第一部分是文章的核心内容,说明持续集成发版本的一些要点,第二部分则是结合npm发布来实践。当然第一部分适用于任何语言产品包的版本发布,只是笔者最近正好用在nodejs里所以才使用npm…

Posted 10 months ago

json-iterator/go使用笔记

json-iterator是由滴滴开源的第三方json编码库,它同时提供GoJava两个版本。

为什么使用

这个库具有很多优点。最常被人称道的就是性能高于充满反射的官方提供的编码库——据说在编码结构体时候,Go版本的效率是encoding/json的6倍,而Java版本的效率是官方的3倍。

同时这个库还完全兼容官方库的api,替换官方库的方式不需要那么hack

import jsoniter "github.com/json-iterator/go" type Student struct { ID uint `json:"id"` Age uint8 `json:"age"` Gender uint8 `json:"gender"` Name string `json:"name"` Location Location `json:"location"` } type Location struct { Country string Province string City string District string } var s = Student{ ID: 1, Age: 27, Gender: 1, Name: "yuchanns", Location: Location{ Country: "China", Province: "Guangdong", City: "Shenzhen", District: "Nanshan", }, } func Marshal() { // 使用ConfigCompatibleWithStandardLibrary完全兼容官方库 json := jsoniter.ConfigCompatibleWithStandardLibrary result, _ := json.Marshal(&s) println(result) // output: {"id":1,"age":27,"gender":1,"name":"yuchanns","location":{"Country":"China","Province":"Guangdong","City":"Shenzhen","District":"Nanshan"}} }

对于gin-gonic/gin,官方提供1$ go build -tags=jsoniter .命令在编译时替换编码库。更多信息请看文后补充

对于一些没有提供替换接口的库,也可以通过monkey补丁2来简单粗暴的替换掉官方编码库。

// 使用go get -u "bou.ke/monkey"获取猴子补丁库 import ( "bou.ke/monkey" "encoding/json" jsoniter "github.com/json-iterator/go" ) func MonkeyPatch() ([]byte, error) { monkey.Patch(json.Marshal, func(v interface{}) ([]byte, error) { println("via monkey patch") return jsoniter.Marshal(v) }) sjson, err := json.Marshal(&s) if err == nil { println(string(sjson)) } }

注意,该补丁只需在main函数中定义一次就可以到处使用。

自定义编码解码器

当然,如果只是这些,并不值得我专门写一篇笔记来记录。值得一提的是json-iterator/go还提供了一个十分好用的自定义解码编码功能

Posted 10 months ago

pongo2使用笔记

pongo21是一个Django风格的模板引擎,目标是模板语法完全适配Django。

简介

虽然传统的MVC/MTV大势已去,日益复杂的前端技术致使如今的Trending是前后分离,后端模板引擎不再受到开发者的重视,但是依然有开源库还提供这一功能。

甚至gin本身也自带了一个简陋的模板引擎,虽然正式开发中基本上不会用到这个功能,但是笔者还是想要了解一下相关的事情。然而在稍微阅读了gin的文档和源码之后,稍微写一个小demo时,笔者发现gin的模板引擎过于简陋,很是不爽。于是决定找找第三方的模板引擎。

在笔者有限的经历中,用起来最强大和舒爽的模板引擎当属Jinja2——这是一个用python实现的著名的模板引擎,具有诸如MacroBlockInclude等强大功能,同时性能也很好。在pkg.go.dev中,笔者轻易找到了一个自称A Django-syntax like template-engine的模板引擎,它就是pongo2。这里对不了解python…

Posted 10 months ago

go编译工具的使用之汇编

有时候我们想要知道写出来的代码是怎么编译执行的,这时候go tool compile就是一个很好用的工具。

本文相关代码yuchanns/gobyexample

如何输出汇编代码

有三种方法可以输出go代码的汇编代码:

  • go tool compile 生成obj文件
  • go build -gcflags 生成最终二进制文件
  • go build然后在go tool objdump 对二进制文件进行反汇编

当然,具体行为还需要在这些命令后面加上具体的flag。flag的内容可以通过查阅官方文档获得。

本文涉及Flags说明

-N 禁止优化

-S 输出汇编代码

-l 禁止内联

什么是内联

如果学过c/c++就知道,通过inline关键字修饰的函数叫做内联函数。内联函数的优势是在编译过程中直接展开函数中的代码,将其替换到源码的函数调用位置,这样可以节省函数调用的消耗,提高运行速度。适用于函数体短小且频繁调用的函数,如果函数体太大了,会增大目标代码。是一种空间换时间

Posted 10 months ago

go新手常见陷阱

节选自《50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs》,仅摘录一些笔者比较在意的片段。

关联仓库yuchanns/gobyexample(包含测试用例)

初级篇

未指定类型变量不能用nil初始化

支持nil初始化的变量类型有interfacefunctionpointermapslicechannel。所以使用nil初始化未指定类型的变量会导致编译器无法自动推断:

package main func main() { var x interface{} = nil _ = x }

初始化为nil的map无法添加元素

应该使用make方法声明来对map进行实际的内存分配;slice可以使用append方法对值为nil追加元素。

当然,初始化slice时最好预估一个长度,节省重复扩容开销。

package main func main() { m := make(map[string]int) // var m map[string]int // 错误示范,初始化值为nil m["one"] = 1 // 如果对上述值为nil的map添加元素,会报错 var s []int s = append(s, 1) // 正确的slice追加元素用法 }

初始化string不能为nil

nil不支持string类型的初始化。它的初始值应为空字符串:

package main func main() { var s string // var s string = nil // 错误示范,cannot use nil as type string in assignment if s == "" { s = "default" } }

range…

Posted 10 months ago

使用docker作为开发环境

2017,我曾写过关于利用虚拟机如何搭建开发环境的文章

之后我还尝试过Windows Subsystem Linux,也因为曾经是外包需要频繁搭建环境的关系从优哉游哉地编译源码转向一键快捷的宝塔面板(生产环境不推荐,极不安全!)。

为什么要使用Docker

无论是windows环境下的wamp,或是linux环境下的lnmp;无论是外包快速切换项目,还是跳槽入职新公司,搭建开发环境总是一件非常繁琐地事情,众所周知——尤其是像php之类的严重依赖c扩展的语言,在安装各种扩展的过程中简直是费时费力,恨不得把自己现有的环境整个复制过去。事实上我也曾在github写过Dotfile整理收集自己的习惯设置。

这里要给不涉及cgo时的go点个赞!

当我使用Anaconda/Miniconda安装的python环境开发项目中的一个桌面客户端时,又因为目标用户环境复杂的系统而挠头不已。

虽然我日常使用Mac作为私人开发环境,并且十分讨厌使用Windows开发(因为Windows安装各种扩展麻烦,并且生产环境实际也是Linux),但上班这种外力因素也逼迫你不得不与windows打交道——我曾冲动之下把公司系统换成Ubuntu,结果工作中交流常用QQ和微信也要为此开启一个装了Windows的虚拟机十分痛苦!

  • 2020
  • 2019
  • 2017
  • 2016