MoreRSS

site iconSunZhongWei | 孙仲维 修改

博客名「大象笔记」,全干程序员一名,曾在金山,DNSPod,腾讯云,常驻烟台。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

SunZhongWei | 孙仲维 的 RSS 预览

午夜惊魂,在家发版本,线上 golang 找不到 GLIBC 版本导致服务挂掉

2025-05-30 13:56:29

昨晚接到紧急需求,需要增加一个搜索功能。当时在家里,感觉很简单的需求, 随便改改,直接发布到线上服务器就行,毕竟我大 golang 只要能编译通过,基本不会有大问题 😏。

但是,不出意外,出了意外。自动发布脚本,在重启 systemd 服务后, 使用 systemctl status 查看状态,发现 golang 服务没有启动成功。

通过命令行单独启动 go 服务,发现报错:

/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found

还好,我发布前,将可执行程序备份了一份。。。挂了一分钟,我就悄悄恢复了,险些提桶跑路。 还是明早回公司再发布吧,太吓人了。

version `GLIBC_2.3x' not found

之前遇到过两次这个问题,都是因为编译的环境和运行的环境不一致导致的。

第一次是测试 DotNet 8 AOT 编译时, 在 Ubuntu 22.04 上编译,然后 scp 到 Ubuntu 20.04 上执行。报错!

./TestAOT ./TestAOT: /lib/x86_64-linux-gnu/libc.so.6: version GLIBC_2.32' not found (required by ./TestAOT) ./TestAOT: /lib/x86_64-linux-gnu/libc.so.6: versionGLIBC_2.34' not found (required by ./TestAOT)

而在相同版本上的 Ubuntu Server 上,就能正常执行。。。详情参考: https://www.sunzhongwei.com/dotnet-8-aot

第二次是,在 docker alpine 容器内运行编译的 golang 程序,也是报这个错误。 如果使用 alpine 容器,就需要在编译 golang 代码时,增加编译配置。

CGO_ENABLED=0 go build

详情参考这个 https://www.sunzhongwei.com/docker-deploy-golang-in-production

解决方法

这次问题的解决方法也很简单,禁用 cgo 编译即可。

在编译 golang 代码时,设置环境变量 CGO_ENABLED=0,然后重新编译即可。

CGO_ENABLED=0 go build

为何 glibc 版本不一致

因为我在 WSL Ubuntu 24.04 上编译的 golang 代码,而线上服务器是 Ubuntu 18.04。

家里的电脑环境:

> cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"

> getconf GNU_LIBC_VERSION
glibc 2.39

服务器的环境:

> cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"

> getconf GNU_LIBC_VERSION
glibc 2.27

而公司开发机是

  • Ubuntu 20.04.2 LTS。
  • glibc 2.31

很奇怪,我在公司开发机上编译的 golang 代码,在服务器上运行是正常的。 难道是因为 ubuntu 20.04 的 glibc 版本和 ubuntu 18.04 的 glibc 版本是兼容的? 但是从版本号上看,不应该是兼容的啊。。。摸不着头脑。

golang 与 glibc 的版本兼容性

参考

https://github.com/golang/go/issues/58550

AFAIU, for CGO_ENABLED=1, you'll need to use a glibc version during compile that matches (or is lower) than the one on your target runtime system.

也就是说,编译时,本机的 glibc 版本需要和运行时的 glibc 版本一致或更低,但是不能更高。而我在 WSL Ubuntu 24.04 上编译的代码,使用了 glibc 2.39,而服务器上是 glibc 2.27,所以会报错。

默认情况下,CGO_ENABLED 是开启的。只有交叉编译时,CGO_ENABLED 才会被禁用。 所以,还是手动设置 CGO_ENABLED=0 来禁用 cgo 编译,比较安全。

什么是 CGO 和 glibc

CGO is a feature of the Go programming language that allows Go programs to call C functions and access C data structures. This can be useful for interfacing with existing C libraries. However, using CGO can also introduce glibc dependencies into your Go program.

CGO 是 Go 编程语言的一个特性,它允许 Go 程序调用 C 函数和访问 C 数据结构。这对于与现有的 C 库进行交互非常有用。然而,使用 CGO 也可能会将 glibc 依赖引入到你的 Go 程序中。

glibc is the GNU C library, and it is the default C library on most Linux distributions. However, glibc versions can vary from one distribution to another. This can cause problems if you try to run a Go program that uses CGO on a different distribution than the one it was built on. For example, a Go program built on Ubuntu 22.04 will not run on Ubuntu 20.04.

glibc 是 GNU C 库,它是大多数 Linux 发行版上的默认 C 库。然而,glibc 的版本在不同的发行版之间可能会有所不同。如果你尝试在与构建时不同的发行版上运行使用 CGO 的 Go 程序,这可能会导致问题。例如,在 Ubuntu 22.04 上构建的 Go 程序将无法在 Ubuntu 20.04 上运行。

参考,最棒的一个解释文章:

https://www.elastiflow.com/blog/posts/disabling-cgo-to-remove-glibc-dependency

禁用 CGO 的好处

Portability: Disabling CGO makes Go programs more portable because they no longer depend on C libraries. This means that Go programs can be run on systems that do not have glibc installed, such as Alpine Linux and FreeBSD, or on different versions of the same operating system, like Ubuntu 20.04 and 22.04.

可移植性:禁用 CGO 可以使 Go 程序更具可移植性,因为它们不再依赖 C 库。这意味着 Go 程序可以在没有安装 glibc 的系统上运行,例如 Alpine Linux 和 FreeBSD,或者在同一操作系统的不同版本上运行,如 Ubuntu 20.04 和 22.04。

Security: Disabling CGO can make Go programs more secure because it reduces the program's attack surface. C libraries can contain security vulnerabilities, and disabling CGO prevents these vulnerabilities from being exploited in Go programs. See this vulnerability report of glibc in Debian 11 (Ubuntu 20.04).

安全性:禁用 CGO 可以使 Go 程序更安全,因为它减少了程序的攻击面。C 库可能包含安全漏洞,禁用 CGO 可以防止这些漏洞在 Go 程序中被利用。请参阅 Debian 11(Ubuntu 20.04)中 glibc 的此漏洞报告。

Performance: In most cases, disabling CGO can improve the performance of Go programs. For an example, see CockroachDB's article on The cost and complexity of Cgo.

性能:在大多数情况下,禁用 CGO 可以提高 Go 程序的性能。有关示例,请参阅 CockroachDB 关于 Cgo 的成本和复杂性的文章。

禁用 CGO 的缺点

Functionality: Disabling CGO may limit the functionality of Go programs. There are C libraries that do not have an equivalent Go module, therefore making it more challenging to integrate with such systems.

功能性:禁用 CGO 可能会限制 Go 程序的功能。有些 C 库没有等效的 Go 模块。

Performance: Theoretically, disabling CGO can degrade the performance of Go programs. This is because Go will have to implement some functionality in pure Go, which may be less efficient than using a C library. However, this is theoretical because the overhead of invoking C libraries likely outweighs the implementation performance difference.

性能:理论上,禁用 CGO 可能会降低 Go 程序的性能。这是因为 Go 将不得不在纯 Go 中实现某些功能,这可能不如使用 C 库高效。然而,这只是理论上的,因为调用 C 库的开销可能超过实现性能差异。

总结

  • 禁用 CGO 是消除 Go 程序中 glibc 依赖的方法。可以使 go 程序更具可移植性,并且在不同的 Linux 发行版上运行时不太可能出现问题。
  • 自动化部署脚本,需要备份之前版本的可执行文件。如果出现类似问题,可以快速恢复。
  • 如果不得不启用 CGO, 在 docker 相同版本的 ubuntu 容器中编译 golang 项目
  • systemd 服务重启时,如果 status 失败,立即显示错误日志,快速定位问题

阿里云群发短信通知的失败问题排查

2025-05-28 17:21:10

在开发的大赛报名系统,截至尾声,组织方希望群发一条短信通知所有参赛队的队长, 赶紧提交作品,不要挤在最后一天,防止人多导致系统卡顿。毕竟大量的文件及视频上传。

但是,众所周知的原因,现在发送验证码短信的成功率都达不到 98%,特别是贵州移动的手机号。 怎么都收不到短信验证码。

我担心群发短信,会不会也有类似的问题?

更难的地方是,组织方给出的短信模板是 180 多个字符,相当于要发送 3 条短信才能发送完整。 我担心的是:

  1. 成本太高。预留的额度不够
  2. 文字越多,触发敏感词被拦截的概率越高。

于是,用我仅有的两个报备成功的签名进行群发测试,结果如下:

  1. 签名一【XXX】,这个短信签名(目前用于大赛注册验证码),群发测试百分百失败(电信,移动)。
  2. 签名二【YYYYYYY】,这个长的签名,类似于公司全称(14个字,注意签名也算短信文本长度),移动的手机号测试了两个可以成功,但是电信的百分百失败。
  3. 精简了一下群发短信的文案内容,从 187 个字,精简到 80 个字,为了规避意外触发敏感词,被拦截的可能。测试结果同2,电信还是百分百失败,即便短信签名通过了运营商的备案。

跟组织方讨论了一下解决方案,目前的想法是:

  1. 组织方使用他们那边短信平台群发电信和联通的手机号
  2. 我这边使用阿里云的短信平台群发移动的手机号
  3. 再使用邮件群发一遍

导出群发手机号列表

我用 golang 写了个接口,将没有提交作品的参赛队队长的手机号导出成 Excel 文件, 并且取出手机号的前三位来判断运营商。一并导出到 Excel 文件中。

然后,按照运营商列排序即可,批量选择指定运营商的手机号,在阿里云短信服务后台,使用群发助手手动发送即可。

群发策略

手动群发的策略是:

  1. 先发送 10 测试短信,看看是否能成功。成功率 50% 😓
  2. 又发送了 30 个,成功率 100%。可能是少量多批,成功率能高一些
  3. 又试了 60 个,成功率 95% 以上。
  4. 最后 574 个移动号码发送完,成功率 94% 以上。

感想

现在的短信管控越来越严格,严格到了脑残的地步。

通知短信就算了,验证码短信都能被拦截,导致用户体验极差。

Magento .phtml 代码中获取当前域名,并条件输出菜单

2025-05-27 22:04:17

不知道为啥,使用 Magento 的 Mage::app() 获取 store 信息会导致网站崩溃。。。 所以,直接使用纯 PHP 的方式获取当前域名。

纯 PHP 的实现

<?php

//$storeName = Mage::app()->getStore()->getName();  // 这行会导致网站崩溃,所以换个 PHP 的方式获取域名
$domain = $_SERVER['HTTP_HOST'];
?>


<span><?php echo $domain; ?></span>

条件输出菜单

<?php if ($domain === 'www.sunzhongwei.com') : ?>
    <a href="/about-us">About Zhongwei</a>
<?php endif; ?>
<?php if ($domain === 'sunzhongwei.com') : ?>
	<a href="/contact-us">Contact Us</a>
<?php endif; ?>

题外话

PHP 的写法确实简单粗暴,要是没有那么多漏洞,性能再强悍一点,真是写 WEB 的首选语言。

可惜,PHP 的漏洞太多了 (看了几个 magento 的插件代码,代码质量惨不忍睹),性能也不够强悍。被 Golang 淘汰是必然。

golang gin 基于 Casbin 实现权限控制

2025-05-26 16:41:05

最近在使用 golang gin 开发一套公司内部的人事管理系统,但是发现权限管理比之前搞的系统要复杂不少, 所以我想借机了解一下传说中的 Casbin 权限控制库,看看是否可以借鉴一下。

涉及权限控制的需求场景

  • 人事部门负责人需要能看到所有的人事数据
  • 人事部门的普通 HR,只能看到部分部门的人事数据。即只能看到自己负责的部门数据
  • IT 部门或者行政部门指定人员,能看到资产管理模块的数据。即每个员工有哪些名下资产,显示器,主机,桌子椅子之类的。方便离职时进行资产回收。
  • 部门负责人能看到自己部门的所有员工数据,除了薪资相关的字段。且只有看的权限。不能修改和删除。
  • 员工个人能通过企业微信的授权登录,查看自己的个人信息。包括姓名,工号,入职时间,部门,岗位等。能修改一些基础信息等。
  • 一个集团公司下,有多个子公司。每个子公司都有自己的 HR 系统。HR 系统的权限控制需要支持集团公司下的多租户模式。

现有 gin route group 的缺陷

总感觉,我目前实用的 gin route group + middleware 的方式,无法满足复杂的权限控制需求。 而且对于粒度更细的权限控制,无法做到灵活的配置。

我目前实现的系统,都是基于角色的权限控制。即每个角色对应一组权限。 还是不太灵活。

当然根源是,gin 没有内置权限控制的功能,我自己实现的这套感觉很乱。我不知道规范的做法是怎样的。 所以,需要找个开源的权限控制库,学习参考一下。

Casbin 示例

policy.csv 文件内容

p, alice, /api/*, read
p, bob, /version, write

预定义一些策略,也可以存储到数据库, alice 可以访问所有 /api 开头的路径,bob 只能访问 /version 路径。

go 代码

// 加载模型与策略,也可以存储到数据库
e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")

sub := "alice" // the user that wants to access a resource.
obj := "data1" // the resource that is going to be accessed.
act := "read" // the operation that the user performs on the resource.

ok, err := e.Enforce(sub, obj, act) //判断用户是否有权限

实际使用中,e.Enforce() 方法通常会在中间件 (middleware) 中调用,以便在处理请求之前进行权限检查。

参考:

https://juejin.cn/post/7108533587292454949

Casbin 的功能简介

一种基于元模型的访问控制策略描述语言 PML (PERM modeling language) 及其实施机制 PML-EM (PML enforcement mechanism)

  • 策略语言无关性
  • 访问控制模型无关性: 即支持多种访问控制模型,如基于角色的访问控制(RBAC)、基于属性的访问控制(ABAC)以及 BLP 模型等。
  • 程序设计语言无关性

参考论文:

https://www.jos.org.cn/jos/article/pdf/5624

一些术语

  • 访问控制模型(Access Control Model):定义了如何对资源进行访问控制的规则和方法。
  • 访问控制策略(Access Control Policy):具体的规则和策略,用于定义哪些用户或角色可以访问哪些资源以及可以执行哪些操作。
  • 访问控制列表(Access Control List, ACL):一种常见的访问控制策略,列出了每个用户或角色对特定资源的访问权限。
  • 角色(Role):一组权限的集合,用户可以被分配到一个或多个角色中,以简化权限管理。
  • 权限(Permission):对资源的具体操作权限,如读取、写入、删除等。
  • 策略(Policy):定义了访问控制的规则,通常包括主体(用户或角色)、客体(资源)和操作(权限)。
  • 策略引擎(Policy Engine):用于评估访问控制策略并决定是否允许访问的组件。
  • 策略存储(Policy Store):存储访问控制策略的地方,可以是文件、数据库或其他存储系统。
  • 基于属性访问控制 (attribute-based access control, 简称 ABAC) 模型
  • 基于角色访问控制 (role-based access control, 简称 RBAC) 模型
  • PERM (Policy, Effect, Request, Matchers) 模型
  • 策略描述语言 PML (PERM modeling language)
  • 基于请求(request)、策略(policy)、匹配器(matcher)、策略效果(effect) 的访问控制元模型 PERM (policy-effect-request-matcher)
  • 策略实施机制 PML-EM (PML enforcement mechanism)
  • 认证(Authentication): 在 Web 程序中对应的是 HTTP Status 401 错误
  • 授权(Authorization): 在 Web 程序中对应的是 HTTP Status 403 错误

Casbin 入门介绍

https://casbin.org/docs/tutorials/

里面有一堆高质量的介绍文档,找几篇读读,就能有个大概的了解。剩下的就是动手测试一下了。

Request 原语

一个访问请求通常由经典的三元组构成:

  • Subject:请求的主体,通常是用户或角色。
  • Object:请求的对象,通常是资源或数据。
  • Action:请求的操作,通常是对资源的具体操作,如读取、写入等。

Policy 原语 (策略规则) 类似,也是这三个部分。

ACL (Access Control List) 与 RBAC (Role-Based Access Control),ABAC (Attribute-Based Access Control) 的区别

  • ACL 需要存储每个用户对每个资源的权限,适用于用户数量较少的场景。
  • RBAC 通过角色来管理权限,适用于用户数量较多的场景。用户可以被分配到一个或多个角色中,角色定义了一组权限。但是,不能做的很细粒度的控制。这也是我目前遇到的困扰。但是我感觉,就算在代码中写死部分细微的权限控制逻辑,也能满足需求。唯一的问题就是对于管理员来说不可见,也不能灵活配置。例如,同样是人事部门的角色,有的 HR 能修改一些基础信息等,有的 HR 只能查看数据。
  • ABAC 通过属性来管理权限,适用于需要更细粒度控制的场景。用户、资源和环境的属性可以用来定义访问控制策略。

参考:

https://github.com/xizhibei/blog/issues/101

多租户的支持

参考:

https://www.cnblogs.com/wang_yb/p/9987397.html

策略规则 Policy 存储到数据库的方法

保存到 CSV 不是一个好的选择,应该存储到数据库中。这样可以更灵活地管理和查询策略规则。

否则,每次都需要登录服务器,修改 CSV 文件,然后重启服务,才能生效。这样不够灵活。

参考:

https://casbin.org/zh/docs/policy-storage/

这里支持 Gorm MySQL 的适配器:

https://casbin.org/zh/docs/adapters

实际场景实用

https://www.cnblogs.com/studyzy/p/11380736.html

gin middleware 插件

https://casbin.org/zh/docs/middlewares

这里推荐了两个插件 authz (这个居然是 gin 官方的,不可思议) 和 go-casbin, 不过这么简单的 middleware,我感觉并不需要三方的实现。 不过可以参考一下代码。

感想

  • 涉及的概念比较多,需要耐心学习和理解,即便拿出半天时间来阅读理解,都是值得的。
  • 即便最后不使用 Casbin 作为权限控制的实现,也能从中学到很多关于权限控制的设计思路和实现方式。
  • 是否有简化版的 casbin 实现,只保留策略规则的部分。

今天就到这里吧,实在看不下去了,太枯燥了。

对话机器人 Rasa(四十一):两例 slot 未赋值问题

2025-05-18 19:26:46

突然习惯了在 rasa custom action 中直接赋值 slot, 而不在 yml 流程中注明。

我发现在 rasa 中,如果不在 stories.yml 中写明 slot_was_set,则对应的 slot 就不会在 custom action 中赋值成功,这是为什么? 而有的流程,不在 stories.yml 中标注 slot_was_set,slot 也能赋值成功。

实际上,这是一个错觉。

因为,实际测试发现。只有特地类型的 slot 需要在 stories 中写明 slot_was_set,其他类型的 slot 都可以在 custom action 中直接赋值。

案例一:影响流程的 slot

如果定义 slot 时,使用的是影响流程的类型。例如:

  some_slot:
    type: text
    influence_conversation: true
    mappings:
      - type: custom

some_slot 是影响流程的 slot,其赋值必须写到 stories.yml 或者 rules.yml 中, 否则在 custom action 中赋值不会生效。

案例二:from entity 的 slot

  some_slot:
    type: text
    influence_conversation: false
    mappings:
      - type: from_entity
        entity: some_entity

from entity,这种要么改成 from custom,要么 stories 中明确写明赋值。否则也是不会生效的。

set slot 是否必须写在 story / rule 中

AI 给出的建议:

  • 必须写 slot_was_set:当 Slot 影响对话流程(influence_conversation=true),且需要在训练中让模型感知状态变化时。
  • 可不写 slot_was_set:当 Slot 不参与对话决策(如纯存储用途),且无需模型学习其变化时。

查看合集

📖 对话机器人 Rasa 中文系列教程

对话机器人 Rasa(四十):一则 FollowUpAction 引起的随机返回问题

2025-05-18 15:20:30

问题现象

在 Rasa 某个 action 中,使用了 FollowUpAction 的方式, 但是在执行过程中,出现了多余返回的问题。

我起初以为是类似之前遇到的 对话机器人 Rasa(二十四):两例多返回额外消息的 bug 定位 ,是因为 slot set 导致的,或者 form loop 未标记结束导致的。

但是,经过一段一段代码逐一注释,并插入调试返回之后。发现问题的根源是 FollowUpAction 的使用。

return [
	FollowUpAction("action_next_step"),
	SetSlot("slot_name", "value"),
]

不推荐使用 FollowUpAction

https://github.com/RasaHQ/rasa-sdk/issues/151

不推荐使用 followup action, 推荐使用 story。

解决方案

将 FollowUpAction 的逻辑,抽象成一个函数,其他地方直接调用这个函数,不使用 followup action 的方式。

对应的原有的 next action, 直接调用这个封装好的函数即可。

TODO

  • 清理掉项目中其他地方不必要的 FollowUpAction

调试技巧

在遇到 rasa 中诡异的返回时,可以简化 action 的逻辑,直接返回调试信息。 通过排除法,逐步缩小问题范围。

例如,一个 action 中有多个逻辑分支,可以将每个分支的逻辑逐一注释掉,或者替换为简单的返回语句。 或者,直接在 action 开始就返回调试信息,并 return。

FollowUpAction 是否需要在 story / rule 中注明

我没有找到对应的文档,于是问了 AI,AI 说并不需要,因为这是 action 强制返回的内容。

我觉得也有道理,毕竟,如果 action 都强制返回了 FollowUpAction 了,在 Story 中再注明,是否会引起执行两次都未可知。

VSCode 排查技巧

vscode 中搜索文件内容时,如何使用正则包含 FollowupAction 但是不包含 action_deactivate_loop

^(?=.*FollowupAction)(?!.*action_deactivate_loop).+$

查看合集

📖 对话机器人 Rasa 中文系列教程