GO 代码结构中为什么老有 cmd 和 internal 文件夹

总结: cmd 一个代码库多个启动程序, 清晰明了 internal 将外部依赖放置在 internal 外面, 避免内部逻辑代码直接引用. 比如, 数据持久层 MySQL 参考一下 ChatGPT 给我的答案: 在 Go 语言中,使用 cmd 目录和 internal 目录的组合是一种常见的方式来组织项目结构。 cmd 目录通常包含可执行程序的入口点。这些可执行程序可以是应用程序的命令行接口、工具或测试二进制文件。每个子目录都包含一个可执行程序的主要文件,以及与其关联的其他文件。这种风格使得每个程序都可以独立编译和分发,并且可以让其它人轻松了解和使用它们。 对于 internal 目录,其命名约束了该目录下的代码仅供内部使用。这意味着代码不应该由任何可以被外部依赖使用的包引用。这种风格允许对代码的控制更精细,因为程序员可以更好地控制哪些代码可以被其他人使用。 使用这样的结构来组织 Go 项目有很多好处,包括: 代码更易于维护和理解:使用独立的可执行程序和内部目录,可以让代码更清晰且更容易维护,使开发人员能够更好地理解代码的用途和位置。 扩展性强:该结构使得每个可执行程序都可以相对独立地进行开发、测试和部署。这也使得代码更有扩展和修改的完整性。 松耦合:使用 cmd 和 internal 目录方式可以大大增强代码间的松耦合度。这使得程序员可以更好地对代码变化做出反应,同时减少代码冲突及协调问题。 综上所述,使用 cmd 和 internal 目录方式更易于开发和维护,是现代化 Go 应用程序中广受欢迎的项目结构设计方式。

<span title='2023-08-03 20:34:58 +0800 +0800'>August 3, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Redis 在缓存场景中的应用模式

总结: 先更新数据库, 再删除Key 陈皓大佬的 缓存更新的套路 唉, 叹息

<span title='2023-08-03 18:49:09 +0800 +0800'>August 3, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

返读 Coolshell 记录

返向读一遍陈皓老师在 coolshell 上发布的文档. 并简单做一下读后记录. ETCD的内存问题 一个清晰的排查 Etcd 内存占用过大问题 一把梭:REST API 全用 POST RESTFul 为通用惯用标准, 切有各大厂的指导文档背书. 身为程序员要对自己的代码负责, 有程序员的操守, 反对"优雅不能当饭吃" 反对讨论问题使用: 讨论都是在主观的“我觉得”,“我认为”,“感觉还好”…… TCP 的那些事儿 (上) tcp_syncookies 可以用于防止 sync 攻击 seq_num 是根传输字节相关 ISN 的与一个假时钟相关, 每4微妙加一 TCP 的那些事儿(下) 我做系统架构的一些原则 完备性比性能更重要 控制逻辑进行收口 服从标准, 规范, 最佳实践 双向显示文本 如何做一个有质量的技术分享 描述好问题 怎么做, 为什么 最佳实践和方法总结 GO编程模式:PIPELINE 在今天,流式处理,函数式编程,以及应用网关对微服务进行简单的API编排, 其实都是受pipeline这种技术方式的影响,Pipeline这种技术在可以很容易的把代码按单一 职责的原则拆分成多个高内聚低耦合的小模块,然后可以很方便地拼装起来去完成比较复杂 的功能。 golang 的 Pipeline 代码大家都会写, 但是我一直没有思考过为什么要这样子写. GO编程模式:委托和反转控制 把控制逻辑与业务逻辑分层 未完待续…

<span title='2023-05-26 18:42:36 +0800 +0800'>May 26, 2023</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Golang 100 mistake 的简单读书笔记

variable shadowing init 弊端 初始化 error 的处理, 不应该是包自己用 panic 捕获错误终止程序,而是调用来决定是否应该终止程序 影响测试, 会在测试之前执行 获取执行数据,需要通过全局变量( 全局变量弊端:1. 会被修改,2. 影响测试) 什么时候使用 避免错误产生没有err 不会产生全局变量 getters and setters 如果只是简单获取值的话,就不应该用 方法命名 获取值 Balance 而不是 ( GetBalance) 设置值 SetBalance interface pollution interface 的价值 通用的方法( 如排序) 解耦 限制实体行为(仅提供特定方法) 只要在当我们真正用到的时候才去创建 we should create an interface when we need it, not when we foresee that we could need it interface 抽象 是通过发现的,而不是通过创造的 生产端接口声明,与实现放在同一个包中 消费端接口声明,与使用interface的放在同一个包中 Interface 是通过发现的,符合使用者的需求(与其他语言最大区别就是 Interface 为隐式实现)所以大部分接口应该是是消费端接口声明。 准备库定义生产端声明,可以提供自定义接口的能力。 生产端接口要尽量简洁。 return interfaceBe conservative in what you do, be liberal in fr Be conservative in what you do, be liberal in what you accept from others. ...

<span title='2023-05-25 12:10:53 +0800 +0800'>May 25, 2023</span>&nbsp;·&nbsp;10 min&nbsp;·&nbsp;潜水员

Mysql 分区

可以解决什么问题 表太大, 无法全部放入内存中 表有热点数据, 其他均是历史数据 分区更容易维护, 批量删除,修复 可以跨多个硬件设备 减少单个索引互斥访问 独立备份和恢复分区 主要目的就是对表, 进行一个粗粒度的过滤; 原理 将一个表在物理上分层多个更小的部分, 但是在逻辑上, 仍是只有一个表. 执行 SQL 时, 可以通过合适的过滤今天, 过滤掉那边不需要查询的分区, 以此提高性能. 支持类型为: 水平分区, 不支持垂直分区; 分区的数据操作流程 先打开并锁住所有的分区底层表, 过滤掉多余的分区. 再进行操作. 分区类型 range list hash key 创建分区时, 分区的列, 必须是主键或者唯一索引的一部分 1 2 3 4 5 6 7 8 9 create table t1 ( col1 int null, col2 int null, col3 int null, unique key(col1, col2, col3) ) partitionby hash(col3) partitions 4; Range 创建分区 ...

<span title='2023-04-27 16:16:48 +0800 +0800'>April 27, 2023</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

Golang Gin

Golang Gin 框架的使用和理解 框架代码地址 中间的几个关键对象 Engine RouterGroup HandlerFunc Context Engine gin 的框架实例, 包含路由地址, 中间件, 框架配置. 通过New()或者Default()创建 New 不带任何中间件 Default 会带 logger 和 recover 通过 Engine.Use() 添加中间件到Engine的RouterGroup Engine.Run 启动, 并绑定到参数的地址 RouterGroup 1 2 3 4 5 6 type RouterGroup struct { Handlers HandlersChain basePath string engine *Engine root bool } 用于存储中间件的处理方法 – 存储再 HandlersChain 里面, 即[]HandlerFunc 真正的路由地址存储在engine.trees – 路由树, 检索请求地址和对应处理方法. Engine 包含 RouterGroup, RouterGroup 也会存储 Engine 的地址. 目的是为了, 加点api地址时, 根据当前具体配置的中间件, 再添加engine的路由树. 也就是说, 过程中修改了中间件, 不会影响到已经配置的路由的中间件. ...

<span title='2023-01-01 22:08:25 +0800 +0800'>January 1, 2023</span>&nbsp;·&nbsp;4 min&nbsp;·&nbsp;潜水员

Mysql 快速备份数据

1 2 CREATE TABLE dbto.table_name like dbfrom.table_name; insert into dbto.table_name select * from dbfrom.table_name; 原文

<span title='2022-12-20 10:55:10 +0800 +0800'>December 20, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Golang 是否需要为每个请求 New 一个 Client

背景 在改动旧代码的时候把, 一个使用全局 http.Client 的代码弄成了每一个请求会新 New 一个 http.Client 导致下游的 nginx 的连接数暴涨. 问题 处理多个请求的时候, 是否需要为每个请求 New 一个 Client 探索 在 StackOverflow 发现的相关答案 How to release http.Client in Go? 给的答案是建议复用 Client The Client’s Transport typically has internal state (cached TCP connections), so Clients should be reused instead of created as needed. Clients are safe for concurrent use by multiple goroutines. http.Client 的结构体 1 2 3 4 5 6 7 8 9 10 type Client struct { Transport RoundTripper CheckRedirect func(req *Request, via []*Request) error Jar CookieJar Timeout time.Duration } 在 RoundTripper 中实现了连接复用的逻辑 ...

<span title='2022-12-19 15:35:53 +0800 +0800'>December 19, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

查看 Linux 的负载情况

查看负载 系统平均负载 1 uptime 那么什么是系统平均负载呢? 系统平均负载是指在特定时间间隔内运行队列中的平均进程数。 如果每个CPU内核的当前活动进程数不大于3的话,那么系统的性能是良好的。如果每个CPU内核的任务数大于5,那么这台机器的性能有严重问题。 查看内存信息 1 free -h 查看 cpu 型号 1 cat /proc/cpuinfo | grep 'physical id' | sort | uniq | wc -l 核数 1 cat /proc/cpuinfo |grep "cores"|uniq|awk '{print $4}' 参考文章 https://www.eet-china.com/mp/a87720.html https://colobu.com/2019/02/22/how-to-find-cpu-cores-in-linux/ https://wangchujiang.com/linux-command/c/uptime.html

<span title='2022-12-16 14:31:33 +0800 +0800'>December 16, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Git 查看文件指定范围的修改记录

查看一个文件指定范围内的所有修改记录 1 git log -p -2 -L1081,+5:'hello/world.go' -p -2 或者 --patch -2 往前展示两个 commit 的 diff . 在 git 中 commit 和 patch 是同一个意思参考下文. git commands patching A few commands in Git are centered around the concept of thinking of commits in terms of the changes they introduce, as though the commit series is a series of patches. -L 语法 -L<start>,<end>:<file>, -L:<funcname>:<file> 限制指定查看范围.

<span title='2022-12-12 15:29:36 +0800 +0800'>December 12, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

代码整洁架构

代码整洁架构 核心思想 最重要的是依赖顺序需要内收 – 业务逻辑不能依赖框架 分层 简单分层四层 Entities Use Cases Interface Adapters Framework and Drivers Entity 实体抽象层 我的理解应该是在公司业务, 或者项目领域上对于业务模型的抽象. 不容易改变 (除非公司 业务, 或者项目方向改变). 应该是与 领域驱动设计 不谋而合 Use Cases 使用场景层 业务使用场景, 应该是存放相关不同业务场景的具体实现流程 Interface Adapters 接口转化器层 负责 Use Cases 数据 与外部使用数据转换器实现. 比较特别的例子, 将 Use Cases 产生的数据与外部的 UI 表现层所需要的数据格式做转化. Framework and Divers 数据库和框架层, 外部工具包接口依赖之类的. 依赖倒置 当遇到跨层依赖的时候, 内层需要引用到外层逻辑时: 比如, Use Case 要呈现 UI 数据结构 (Interface Adapters 层) 时, 不能直接引用 UI 层的数据模型. 而是, 通过依赖倒置. 将 UI 层的处理逻辑, 注入 Use Case 层进行处理, 实现目标. ...

<span title='2022-12-05 09:21:02 +0800 +0800'>December 5, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

go 使用 runtime 包进行内存占用分析

使用场景 写个demo, 想查看一下程序内部的内存占用情况. 使用方法 主角 runtime 包 对象 MemStats 方法 ReadMemStats demo 展示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // PrintMemUsage outputs the current, total and OS memory being used. As well as the number // of garage collection cycles completed. func PrintMemUsage() { bToMb := func(b uint64) uint64 { return b / 1024 / 1024 } var m runtime.MemStats runtime.ReadMemStats(&m) // For info on each, see: https://golang.org/pkg/runtime/#MemStats fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc)) fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc)) fmt.Printf("\tSys = %v MiB", bToMb(m.Sys)) fmt.Printf("\tNumGC = %v\n", m.NumGC) } 代码出处 ...

<span title='2022-11-18 19:27:35 +0800 +0800'>November 18, 2022</span>&nbsp;·&nbsp;5 min&nbsp;·&nbsp;潜水员

Linux 文件系统简单操作流程

文件系统原理 BIOS:启动主动运行的韧体,会认识第一个可启动的装置 MBR:第一个可启动装置的第一个磁区内的主要启动记录区块,内含启动管理程序; 启动管理程序(boot loader):一支可读取核心文件来运行的软件; 相关命令 查看磁盘信息 1 fdisk -l macOS 1 diskutil list 查看磁盘用量 1 df -h 查看文件大小 1 du -h 新磁盘的安装流程 对磁盘进行分割,以创建可用的 partition ; 对该 partition 进行格式化( format ),以创建系统可用的 filesystem; 在 Linux 系统上,需要创建挂载点 ( 亦即是目录 ),并将他挂载上来; 操作磁盘分区, fdisk 后面跟具体的物理磁盘 1 fdisk /dev/hdc 创建一个ext4文件系统 1 mkfs -t ext4 /dev/vdb1 挂载磁盘分区 1 2 mkdir /mnt/hdc6 mount /dev/hdc6 /mnt/hdc6 参考 Linux 磁盘与文件系统管理

<span title='2022-11-05 13:52:16 +0800 +0800'>November 5, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Golang Minimal Version Selection 意识流翻译(仅开头)

原文: Minimal Version Selection 意识流翻译, 只是简单的把自己的理解转化成中文. 仅供参考. 翻译: Go 构建相关的命令需要决定模块版本. 把这些模块和版本号称之为编译列表(build list). 为了稳定的开发, 今天的编译列表必须和明天的编译列表是一致的. 但是开发者也必须支持 编译列表的改变: 升级所有的模块, 升级单个模块, 降级单个模块. 版本选择的问题定位为: 给定一个算法支持对编译列表进行下面四种操作: 构建当前的编译列表 升级所有模块到各自对应的最新版本 升级一个模块到对应的版本 降级一个模块到对应的版本 最后两种操作是在一个具体的模块进行升级和降级. 在满足依赖需求的前提下, 尽量少的进 行升级, 降级, 添加或者移除模块. 这篇博文展示了最小版本选择算法, 一个新的, 简单的版本选择方案. 最小版本选择是容易 理解和能简单推测出他的运行行为的. 而且还能高稳定构建的, 表名一个用户所使用的依赖 是非常接近开发者所使用的依赖. 能够高效实现, 没有使用什么复杂的递归图算法. 仅仅只 用了几行 go 的代码就是实现了. 最小选择算法假设所有的模块都声明了它自己对于其他模块的依赖清单. 且所有的模块都遵 守语义化版本: 向前兼容的版本使用小版本号, 不向前兼容的版本使用大版本号. 对应的四种列表操作: 构建当前模块的编译列表: 将当前模块引用的依赖列表加在一起, 重复的仅保持一份. 升级所有模块版本: 所有最新引用的依赖, 重新构建一份列表 升级某一具体模块版本: 构建没有升级的列表, 再将升级的模块新的引用列表加入, 加入 时如果如果已经存在, 就是仅保存最新的版本 降级一个模块到对应的版本: 反解开所有依赖降级的模块, 每一个依赖都需降级到目标 模块版本之下 这些操作简单, 高效, 且容易实现 剩余内容 略 ...

<span title='2022-11-01 00:15:50 +0800 +0800'>November 1, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Golang 解决依赖包版本冲突

遇到了 grpc 不遵循语义版本, 导致不同版本包之间的冲突. 更新了目标的版本模块之后, 编译一下就发现原先项目引用的 gozero 框架报错了. 搜索一下 相关的关键词,就可以定位到问题是 grpc 搞的鬼. 再找到对应的兼容版本, 升级到对应的版本就可以了. go 依赖版本选择 [golang 的最小版本选择]https://ynikl.github.io/blog/golang-minimal-version-selection/ 大体意思: 会选择当前编译需要依赖包的最高版本(使用语义化版本) 寻找依赖的原因 go mod why 寻找自己项目引用某个包的 最短引用路径, 导致会引用目标包的 1 go mod why google.golang.org/grpc 输出目标包的引用依赖层级 1 2 3 4 5 6 ❯ go mod why google.golang.org/grpc # google.golang.org/grpc hello/world/test git.test.cn/company-open/rpc-pkgs google.golang.org/grpc go mod graph 可以打印出, 模块的依赖图 1 2 3 4 5 6 example.com/main example.com/a@v1.1.0 example.com/main example.com/b@v1.2.0 example.com/a@v1.1.0 example.com/b@v1.1.1 example.com/a@v1.1.0 example.com/c@v1.3.0 example.com/b@v1.1.0 example.com/c@v1.1.0 example.com/b@v1.2.0 example.com/c@v1.2.0

<span title='2022-10-31 08:26:33 +0800 +0800'>October 31, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员

Python 基本语法快速学习

学习路径 数据结构 流程控制 代码组织 工程化 数据结构 strings int list tuple dictionary 如何查看一个基本的类型对象拥有的方法 1 2 3 name = "ada" dir(name) dir 获取对象的属性 字符串 1 name = "ada lovelace" 拼接, 用+ 1 2 3 first_name = "ada" last_name = "lovelace" full_name = first_name + " " + last_name 声明字符串中 '单引号和" 双引号没有区别, 可以用来相互替换 多行字串使用'''连续三个单引号或双引号声明 字符串格式化 format 1 2 3 >>> 'Hey {name}, there is a 0x{errno:x} error!'.format( ... name=name, errno=errno) 'Hey Bob, there is a 0xbadc0ffee error!' ‘f’string 模版 ...

<span title='2022-10-09 17:34:39 +0800 +0800'>October 9, 2022</span>&nbsp;·&nbsp;3 min&nbsp;·&nbsp;潜水员

乐观锁和悲观锁

乐观锁和悲观锁 首先, 乐观锁和悲观锁和本身并不是一种具体锁. 而是一种编程的并发控制思想. 原名应该叫做乐观并发控制(Optimistic concurrency control) 简称 OCC 和 悲观并发控制(Pessimistic Concurrency Control) 简称 PCC 什么是锁 维基百科对锁的定义 In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive: a mechanism that enforces limits on access to a resource when there are many threads of execution. A lock is designed to enforce a mutual exclusion concurrency control policy, and with a variety of possible methods there exists multiple unique implementations for different applications. ...

<span title='2022-10-02 14:05:43 +0800 +0800'>October 2, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

Go errgroup 的基本用法

实现并发控制 在 golang 代码中如果要对一段代码进行并发限制. 通常的做法都是在写一个 channel 进行传入和传出. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 func main() { concurrencyNum := 10 limitCh := make(chan bool, concurrencyNum) wg := new(sync.WaitGroup) for i := 0; i < 100; i++ { limitCh <- true wg.Add(1) go func() { defer func() { <-limitCh wg.Done() }() time.Sleep(1 * time.Second) fmt.Println("do some things...") }() } wg.Wait() fmt.Println("ok") } 如果如果中间运行代码有可能存在错误, 捕获错误. 有两种方法: ...

<span title='2022-09-19 09:19:31 +0800 +0800'>September 19, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

如何查看 golang 编译之后调用的源码方法

在 golang 中查看源码是比较方便的. 可以直接到 官方包文档中直接查看文档和跳转到源码 但是, 当我们想看一些更加底层的实现方法时, 就需要知道编译器将对应的方法编译成 什么底层方法了. 比如, 我知道一些make(map[int]bool)是怎么实现的. 这时候就需要一些方法了. 引用一下鸟窝大佬的文章 总结一下三种方法: go tool compile -N -l -S makemap.go go tool compile -N -l -S makemap.go go tool compile -N -l -S makemap.go go tool compile 产生的汇编代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0x001c 00028 (main.go:5) FUNCDATA $1, gclocals·PdpXzE88ETLbqQ9okAZ04w==(SB) 0x001c 00028 (main.go:5) FUNCDATA $2, main.main.stkobj(SB) 0x001c 00028 (main.go:6) STP (ZR, ZR), main..autotmp_4-48(SP) 0x0020 00032 (main.go:6) STP (ZR, ZR), main..autotmp_4-32(SP) 0x0024 00036 (main.go:6) STP (ZR, ZR), main..autotmp_4-16(SP) 0x0028 00040 (main.go:6) MOVD $type.map[int]bool(SB), R0 0x0030 00048 (main.go:6) MOVD $100, R1 0x0034 00052 (main.go:6) MOVD $main..autotmp_4-48(SP), R2 0x0038 00056 (main.go:6) PCDATA $1, ZR 0x0038 00056 (main.go:6) CALL runtime.makemap(SB) 0x003c 00060 (main.go:6) MOVD R0, main.mp-112(SP) 0x0040 00064 (main.go:7) MOVD R0, R1 0x0044 00068 (main.go:7) MOVD ZR, R2 0x0048 00072 (main.go:7) MOVD $type.map[int]bool(SB), R0 go tool objdump产生的汇编代码 ...

<span title='2022-09-18 21:37:55 +0800 +0800'>September 18, 2022</span>&nbsp;·&nbsp;2 min&nbsp;·&nbsp;潜水员

Content Disposition

Content-Disposition 常见是用在 http 请求的 Response 的 Header 头部. 告诉请求客户端(浏览器) 如何处理内容; Content-Disposition是在 MIME 标准定义的. http 中的用法只是其中的一小部分. 语法参数 inline 会在浏览器内部显示 1 Content-Disposition: inline attachment 会被保存成文件 1 Content-Disposition: attachment 后面可以跟 filename, 值为预设文件名称, 中间使用;分号隔开. 1 Content-Disposition: attachment; filename="filename.jpg" 拓展参数, 有两个文件名称参数可选 filename* filename filename* 采用了 RFC 5987 中规定的编码方式, 假如两个参数都使用 filename* 的优先级更高 RFC 5987 该提议最终还是引用 RFC 2231 中的编码方式. 下面简单介绍一下语法 *星号用于标记该同名参数是支持该编码语法的, 就如( filename* 之于 filename) '单个逗号用于分割 字符集名称 , 语言, 文件名称 %百分号用于标记编码方式, 参考RFC 2047, 所以文件名中不能有% 1 filename*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A 使用us-ascii编码, en-us英语 ...

<span title='2022-09-04 21:32:24 +0800 +0800'>September 4, 2022</span>&nbsp;·&nbsp;1 min&nbsp;·&nbsp;潜水员