我用了近两天的时间,只为了写三行代码

事情发生在我们线上的一个遗留问题,问题大致情况是我们引入的一个库会导致服务在运行一段时间后出现死锁,并且会造成整个网站无法访问。 我们发现通过配置库的一些参数可以避免死锁的情况发生,但是会引发事务无法回滚的新问题。 由于后者相对故障等级更低一些,我们首先进行了故障降级,选择了优先保障服务运行。 后面则是排查为什么会新的配置会出现事务无法回滚,这个事情由我负责排查。 从部门同事那了解了问题的详细情况后我就开始着手进行排查。首先是对问题进行梳理把新引入的库与框架之间的关系以及整体的一个运行流程进行了梳理。 如果要对整个流程做详细的了解,需要阅读的代码量是十分庞大的,不仅需要阅读库,还需要对框架源码进行阅读。 就解决问题而言,我认为需要做粗略的流程梳理即可,通过对整个流程的一个大致了解,然后确定问题的具体发生位置,然后对具体位置进行详细了解。 在粗略梳理的过程中,我从新引入库开始着手,主要是由于库的代码量相对较少,阅读起来相对更快一些,且如果仅从库层面就能解决问题,也可以省略的框架代码的阅读。 对于库源码的了解我通过 wiki + 源码的方式,这样能够更省力的了解库的整个运行机制。 然而了解了库的运行机制后,我发现问题可能出现在库与框架的配合上。需要涉及到框架源码的阅读。 在开始阅读框架源码前,我决定在本地构建一个复现环境,通过对构建出的本地环境对源码进行 debug 调试。 经过反复的 debug 最终终于确定了问题出现的原因,是由于新库引入后,事务开启时新建一个数据库连接,而通过 ORM 进行数据库查询时会创建另一个数据库连接。这就导致事务和 SQL 的执行不是在同一个数据库连接中,也就造成了数据库事务无法生效的问题。 确定了问题的原因解决方案就好确定了,我们只需要保证事务和 SQL 执行在同一个连接中即可,而解决这个问题只写了三行代码,既保证了功能的完整,没有侵入库和框架进行底层源码的改造。 这次问题的排查让我想到了福特公司用一万美元画一条线的故事。 画一条线,1美元;知道在哪儿画线,9999美元。 这次的问题离不开部门同时的帮助,在排查的过程中和同事进行了 2-3 轮的沟通。不仅让我对问题有一个全面的认识,也几次在我遇到困惑时,给了我解决问题的灵感,包括在最后解决方案制定后的论证,都蕴含了整个团队的心血。 也希望这次的一个排查经过能够对看文章的你有一些启发,欢迎你分享你的感悟和思考。

August 27, 2022 · 1 min · 云溪

Casbin 入门

Casbin 是一个强大的、高效的开源访问控制框架,其权限管理机制支持多种访问控制模型。 通俗来讲就是一个权限管理的框架,把常见的权限管理集成在框架中,使用者只需要根据框架规范就可以很方便的实现相应的权限控制。 Casbin 支持的权限管理如下: ACL (Access Control List, 访问控制列表) 具有 超级用户 的 ACL 没有用户的 ACL: 对于没有身份验证或用户登录的系统尤其有用。 没有资源的 ACL: 某些场景可能只针对资源的类型, 而不是单个资源, 诸如 write-article , read-log 等权限。 它不控制对特定文章或日志的访问。 RBAC (基于角色的访问控制) 支持资源角色的RBAC: 用户和资源可以同时具有角色 (或组)。 支持域/租户的RBAC: 用户可以为不同的域/租户设置不同的角色集。 ABAC (基于属性的访问控制): 支持利用 resource.Owner 这种语法糖获取元素的属性。 RESTful: 支持路径, 如 /res/* , /res/: id 和 HTTP 方法, 如 GET , POST , PUT , DELETE 拒绝优先: 支持允许和拒绝授权, 拒绝优先于允许。 优先级: 策略规则按照先后次序确定优先级,类似于防火墙规则。 Model 概念 Model 相当于框架的配置文件,如我想在系统里有可能实现 是ALC 或者 RBAC ,应该如何告诉 Casbin 框架,我实现的是那种类型的权限控制呢?...

July 29, 2022 · 2 min · 云溪

golang 简洁架构介绍

在 go 开发中,首先遇到的一个问题,应该怎么设计开发架构。一个好的架构可以在后续的项目开发中能够更好的使用项目的推进,同时能够让团队的配合更加的顺利。 一个简洁的架构应该具有如下几个特点: 不依赖框架:好的架构应当是独立的,不依赖框架可扩展的 不依赖 UI:UI 应该很容易修改,而不需要改业务部分代码 独立于数据库:不绑定数据库,可以在不改动业务代码的情况下,更换其他数据库 可测试的: 能够比较方便测试用例的编写 下面目录结构引自 《go 整洁模板》 ├─cmd 应用入口 │ └─app ├─config ├─docs // 存放文档 ├─internal │ ├─app │ ├─controller // 控制器 │ │ ├─amqp_rpc │ │ └─http │ │ └─v1 │ ├─entity // 实体层 │ ├─middleware // 中间件 │ └─usecase │ ├─repo // 数据库操作 │ └─webapi // RESTful API ├─migrations ├─pkg //以被外部程序安全导入的包 │ ├─crypto │ ├─httpresponse │ ├─httpserver │ ├─logger │ ├─mysql │ ├─postgres │ ├─rabbitmq │ └─redis 分层介绍 层级调用是通过外层->内层,内层是不知道外层的存在的。在内层代码中,不应该引用外层声明的:变量、类、方法,结构体。...

June 29, 2022 · 1 min · 云溪

golang 接口(interface)最佳实践

接口定义 应该定义在接口所使用的包中,而非接口实现的包中。 以io包中的io.Reader/io.Wirter为例,接口定义在io包中,而他的实现bytes.Buffer、os.File等都是在不同包中。 接口实现应该返回具体类型 应该返回对应的结构体或指针,这样实现类如果扩展新的方法就可以直接添加,无需改动接口定义部分,更加灵活。 //为了方便演示,本实例代码未遵循「接口需要在使用包中声明」规范 package demo import "fmt" type Demo interface { show() error } type MyDemo struct { } //这里的返回值应该是 `MyDemo` 或 `*MyDemo` 而非 `Demo` func NewMyDemo() *MyDemo { return &MyDemo{} } func (md *MyDemo) show() error { fmt.Println("show") return nil } 接口应该什么时候定义 在当前模块有依赖外部服务的时候,这时候就会定义一个接口,来对外部的依赖资源进行抽象解耦,屏蔽接口的实现。相反,依赖的提供方在不知道使用方需求的时候,定义接口也就没什么意义。

June 11, 2022 · 1 min · 云溪

树莓派变身旁路由,扩展家中网络

由于家中网络覆盖存在死角,手中有一个树莓派,就准备刷入「openwrt」作为旁路由,扩展家中的无线网络。 整体思路如下,用树莓派的自带网口桥接主路由网络,然后通过树莓派无线网卡发射无线网络,从而达到扩展网络的目的。 如果你也正巧有类似的需求可以通过下面清单,准备所需材料: 树莓派 读卡器 TF 卡 台式机/笔记本(用于调试网络) 网线一根 下载镜像 镜像推荐:OpenWrt-Rpi ,这款镜像支持绝大多数的常见设备,并且镜像集成了很多很好用的功能,也免去了刷官方固件,后续自己折腾安装软件的过程。 进入固件下载 页面能够看到支持设备的列表,找到自己的设备,点击固件下载,进入你设备的固件下载页。 我这边是树莓派 3B 下载的为: immortalwrt-bcm27xx-bcm2710-rpi-3-ext4-factory.img.gz 到此镜像就已经下载好了,下面就是将下载好的镜像,写入树莓派的 TF 卡。 写入镜像 将 TF 卡插入读卡器,并将读卡器插入电脑上,镜像写入工具使用的为: Win32 Disk Imager 如果没有的话可以自行下载。打开 Win32DiskImager 软件,选择下载好的 「openwrt] 镜像和对应 TF 卡的盘符,点 Write 按钮开始写入镜像。 看到写入完成的提示后,将 TF 卡拔出插入树莓派,将准备的网线插入树莓派网口,并网线的另一端连接到电脑上,将树莓派开机。 openwrt 配置 接口配置 树莓派开机后,电脑端输入 192.168.1.1 可以看到路由器的登录页面。 镜像的默认密码为: password ,输入密码后进入系统后,点击「网络-接口」进行接口设置,系统默认已经创建好了 LAN 接口,点击 LAN 接口的修改,进行配置,具体配置如下: 传输协议:静态地址 IPv4 地址: 前三位与主路由地址相同,最后一位写一个内网中没有分配的地址即可。我这里为:192.168.1.100,需要注意的是此地址也是后续登录路由器管理界面的地址。 IPv4 子网掩码: 255.255.255.0 IPv4 网关: 主路由网关,我这里为 192.168.1.1 DNS 服务器1:114.114.114.114 DNS 服务器1:8.8.8.8 上述配置完成后,拉到页面底部点击 保存&应用 应用配置,配置应用成功后,重新登录路由器,此时再用192....

May 3, 2022 · 1 min · 云溪