技术阅读周刊,每周更新。
1 | func NewServer( |
1 | func addRoutes( |
主函数只调用run函数来运行服务
1 | func run(ctx context.Context, w io.Writer, args []string) error { |
返回闭包 handle
1 | // handleSomething handles one of those web requests |
定义通用的encode和decode函数
1 | func encode[T any](w http.ResponseWriter, r *http.Request, status int, v T) error { |
提供一个抽象的 Validator 接口用于验证
1 |
|
自定义校验需要实现 Validator
接口。
1 | func handleTemplate(files string...) http.HandlerFunc { |
这是一篇 OTel 的科普文章
OpenTelemetry 提供一个统一、可扩展的框架,用于收集、分析和观察分布式系统的性能数据。它包括一组API、库、代理和收集器,这些组件可以跨多种编程语言和平台实现对应用程序的监控。
OpenTelemetry 整合 OpenTracing 和 OpenCensus。
2019年,两个社区进行了合并。
同时 OTel 具备以下特征:
统一性:OpenTelemetry 提供了一个统一的API,使得开发者可以在不同的编程语言和框架中以一致的方式实现监控。
可扩展性:可以编写自己的扩展来满足个性化需要
跨平台:OpenTelemetry 支持多种编程语言,如 Java、Python、Go、.NET 等,以及多种云服务和容器平台。
社区驱动:作为一个开源项目,OpenTelemetry 由一个活跃的社区支持,社区成员贡献代码、文档和最佳实践。
与现有工具的兼容性:OpenTelemetry 设计时考虑了与现有监控工具的兼容性,如 Prometheus、Jaeger、Zipkin 等,这使得它可以轻松地集成到现有的监控基础设施中。
提供了一种名为:OTLP(OpenTelemetry Protocol)的通讯协议,基于 gRPC。
使用该协议用于客户端与 Collector 采集器进行交互。
Collector 是 OpenTelemetry 架构中的一个关键组件,它负责接收、处理和导出数据(Trace/log/metrics)。
它可以接受从客户端发出的数据进行处理,同时可以导出为不同格式的数据。
总的来说 OTel 是可观测系统的新标准,基于它可以兼容以前使用的 Prometheus、 victoriametrics、skywalking 等系统,同时还可以灵活扩展,不用与任何但一生态或技术栈进行绑定。
本文总结了一些常用的 git 配置
pull.ff only
或 pull.rebase true
:这两个选项都可以避免在执行git pull
时意外创建合并提交,特别是当上游分支已经发生了变化的时候。
merge.conflictstyle diff3
:这个选项使得合并冲突更易于阅读,通过在冲突中显示原始代码版本,帮助用户更好地解决冲突。
rebase.autosquash true
和 rebase.autostash true
:这些选项使得修改旧提交变得更容易,并且自动处理stash。
push.default simple
或 push.default current
:这些选项告诉git push
自动推送当前分支到同名的远程分支。
init.defaultBranch main
:创建新仓库时,默认创建main
分支而不是master
分支。
commit.verbose true
:在提交时显示整个提交差异。
rerere.enabled true
:启用rerere
功能,自动解决冲突
help.autocorrect
:设置自动矫正的级别,以自动运行建议的命令。
core.pager delta
:设置Git使用的分页器,例如使用delta
来查看带有语法高亮的diff。
diff.algorithm histogram
:设置Git的diff算法,以改善函数重排时的diff显示。
文章链接:
#Newletters
]]>Pulsar3.2.0 于 2024-02-05 发布,提供了一些新特性和修复了一些 bug ,共有 57 位开发者提交了 88 次 commit。
以下是一些关键特性介绍.
在 3.2 中对速率限制做了重构:
PIP-322 Pulsar Rate Limiting Refactoring.
速率限制器是 Pulsar 服务质量(Qos)保证的重要渠道,主要解决了以下问题:
Netty IO
线程,从而增加其他 topic 的发送延迟Pulsar 支持 Topic 压缩,在 3.2 之前的版本中 topic 压缩时会保留 Null key 的消息。
从 3.2.0 开始将会修改默认行为,默认不会保留,这可以减少存储。如果想要恢复以前的策略可以在 broker.conf 中新增配置:
1 | topicCompactionRetainNullKey=true |
具体信息请参考:PIP-318.
3.2.0 中引入了PIP-326: Bill of Materials(BOM) 来简化依赖管理。
Pulsar 是发展最快的开源项目之一,被 Apache 基金会评选为参与度前五的项目,社区欢迎对开源、消息系统、streaming 感兴趣的参与贡献🎉,可以通过以下资源与社区保持联系:
🔗参考链接:
之后便准备将迟迟未写的 2023 总结补完,这个传统从16年至今已经坚持将近 7 年时间了,今年当然也不能意外。
今年要说最让我印象深刻的事就是健身了,为此我投入了大量的时间。
我记得是在 22 年四月份当时是因为确实长胖太明显了,下定决心找个教练进行训练,效果确实也有。
去年也分享过,最后从 75kg 减到 66kg;但大部分时间都是被动的进行训练,所以到了 23 年初的时候其实就反弹不少了。
而今年最大的不同是我由原先的被动健身改为主动了,甚至到后面一天不练还浑身不舒服。
所以今年我大部分时间都是自己锻炼,因为我是个 I 人,比较喜欢一个人,所以夏天的时候是每天早上 7 点多去健身房然后再去公司。
到了冬天早上确实是起不来,就改为了中午去训练。
就这样不知不觉就坚持了大半年,直到现在。
训练日志见文末。
甚至现在偶尔找教练训练时,他说我比他练的都勤🤣。
最终达到的效果就是生活作息更加规律,同时身体素质也是肉眼可见的提升。
其余的就是胸肌有些轮廓、肩部也比以往更圆润一些,腹肌在某些特定角度也可以若隐若现(当然这个得体脂足够低才行),今年的主要目标是上半年认真刷刷脂。
工作今年大体上没有什么变化,但经济不景气应该每个人都能感受到;目前我能苟着的同时还能学一些自己感兴趣的东西就非常满足了。
到现在依然很怀恋在上家公司的日子。
今年在公司主要还是维护 Pulsar,同时也给社区贡献了一些代码,算是这么些年来最认真参与开源的一年。
感兴趣的可以看看之前写的文章:
相比我以前的工作来说,现在的岗位是基础架构,所以接触的几乎都是一些开源产品,这也是我个人感兴趣的方向。
所以虽然同事之间的交流没有之前的公司那么频繁(我们部门和业务团队在不同的城市),但因为由兴趣驱动,所以也没那么枯燥。
对了,年中的时候还头脑发热去报了一个英语线下培训班,上了两月后发现除非是连续每天上 8 小时突击几个月,不然别想一下子速成。
平时没有使用英语的环境,那就只能自己创造了,我现在会坚持每天看一个油管的科技视频,目前看生肉有字幕的情况下勉强可以理解。
同时又因为今年长期都在水开源社区,导致我现在看英文文档、邮件之类的不借助翻译也没那么吃力,算是开了一个好头。
今年争取再多听听英文播客,虽然暂时无法通过英语找到远程工作,但利用英文确实可以打开新世界。
今年算是播客的重度用户,其实听播客的习惯前几年就有了,但那时候大部分是再开车的时候听,今年因为每天有1~2小时的健身时间,所以健身的时候几乎都是听播客过来的。
个人觉得播客是非常好的内容输入源,比很多视频内容的质量还高;这里推荐几个我常听的频道:
在年底的时候无意间利用 Pulsar 完成了我人生的第一笔咨询服务,当时还发了个朋友圈。
没想到之后又有个朋友来咨询了一些关于职场的问题,完事后客户满意度还挺高。
于是我今年也准备好好筹备下,说不定真能做成一个副业。
打个广告,感兴趣的也可以私聊。
年底还好运获得了掘金的签约资格:
我算是掘金最早一批用户了,记得是 16 年就开始在上面发布文章,这也是长期坚持获得的肯定。
而且掘金由于被字节收购后资金明显比前几年宽裕,参与过几次征文活动还是收获了一些现金奖励。
现在和掘金签约后还能获得更多的现金和流量奖励,对作者和平台来说都是双赢,只是今后的文章需要先在掘金发布三个月后才可以同步到其他平台。
所以掘金还没关注的我的朋友赶紧关注一波吧:
https://juejin.cn/user/835284565229597
技能上除了刚才在工作中提到的 Pulsar 外还额外学习了:
VictoriaMetrics
入门到安装VictoriaLog
一个新的日志存储数据库,之前也写过一篇介绍使用文章。VictoriaLog
做过一点贡献。OTel
的 extension
,熟悉了 OTel
的一些概念和实践。
从今年长期使用的 tag 来看,果然还是 Pulsar
和 kubernetes
使用的最多。
今年的博客数据产量算是比较多的了,确实也是有我工作的关系,平时接触到的大部分都是些技术问题,所以能写的东西也就比较多了。
同时也再尝试每周发布技术周刊:
目前发了十几期,效果不错,大部分都是一些英文文章,自己也能学到一些东西。
之前也提到了今年算是我比较深入的参与开源项目,以往大部分都是发布一些个人作品,当然也有给一些个人或者小项目提过 PR,现在看来多少有点”小打小闹“了。
因为在公司主要维护 Pulsar,所以不可避免的就需要和社区沟通,不管是反馈 Bug 还是修复问题流程都比以往正规,毕竟这也是一个 Apache 顶级项目。
主要活跃的是 Pulsar 主仓库,合并了 14 个 PR。
其次是 pulsar-client-go 也就是 Pulsar 的 Go 客户端,合并了 6 个 PR。
然后是 VictoriaMetrics
,其实主要就是给他们新发布的 VictoriaLogs
修了个 Bug,也是第一次被单独提及的贡献。
最后就是年底的时候在一个做可观测性大佬的公众号下看到的项目:cprobe
主要是贡献了一个 helm 安装仓库以及几个插件,这是一个对新手很友好的项目,对开源感兴趣的都可以来参与下。
当然贡献数量不能作为评判参与开源的唯一标准,但确实比较好量化的指标,今年加油继续贡献。
又到了给往年打分的环节了:
去年算是完成了 60%,今年的定一些容易实现的目标:
]]>长图预警
2023 年是一个重要的里程碑,参与主仓库贡献的开发者达到了 600 位。
自从 Pulsar 从 2018 毕业成为 Apache 顶级项目至今一共又 12K+ 的代码提交次数、639 位贡献者、12.2k star、3.5k fork、10k+ 的 slack 用户。
社区发布 Apache Pulsar 3.0,这是第一个长期支持 (LTS) 版本,从 Pulsar 3.0 开始,可以满足不同用户对稳定性和新功能的需求,同时减轻维护历史版本的负担。
以往的版本发布周期很短,一般是 3~4 个月,为了可以跟上社区新版,往往需要不停的升级,对维护中的负担较大。
今后的维护时间表如上图,以稳定为主的团队可以选择 LTS 版本,追求新功能的团队可以选择 feature 版本。
https://pulsar.apache.org/官方网站得到了新的设计。
提供了 Pulsar Admin Go 的客户端,方便 Go 用户管理 Pulsar 资源
PIP-264 提案已经获得了社区批准开始开发,它将解决 topic 数量达到 50k~100M 的可观测性问题。
同时 Pulsar 社区已经为 OpenTelemetry 提交了两个特性 Near-zero memory allocations metric filtering upon collection 已经作为了 OpenTelemetry 的规范。
2023 年,Pulsar 社区在全球范围内举办了一系列活动。
没有贡献者社区很难发展,2023年加入了许多新面孔。
2023年,社区发布了两个 major version 和 12 个 minor version 版本;最大的里程碑依然是发布了首个 LTS 版本 Pulsar3.0。
超过了 140 个贡献者提交了大约 1500 次提交。
同时也带来了一些重要的特性,比如新版本的负载均衡器,大规模的延时消息支持。
更新了以下一些客户端:
2023 年Pulsar 社区也与多个开源项目进行了集成:
继续推进使用 OpenTelemetry 替换现有的可观测性系统
PIP-322 Pulsar Rate Limiting Refactoring限流重构已经被合并,将在 3.2 版本中发布。
将 SQL 模块移除后有效的减少了镜像大小以及构建时间。
2024 年将会继续举办活动,包括 Pulsar Summit North America 和 Pulsar Summit APAC。在这里可以查看以往的活动。
🔗参考链接:
]]>前段时间无意间看到一篇公众号 招贤令:一起来搞一个新开源项目,作者介绍他想要做一个开源项目:cprobe 用于整合目前市面上散落在各地的 Exporter
,统一进行管理。
比如我们常用的 blackbox_exporter/mysqld_exporter
等。
以往的每一个 Exporter 都需要单独部署运维。
同时又完全兼容 Prometheus
生态,也可以复用现有的监控面板。
恰好这段时间我也在公司从事可观测性相关的业务,发现这确实是一个痛点。
于是便一直在关注这个项目,同时也做了些贡献;因为该项目的核心是用于整合 exporter,所以为其编写插件也是非常重要的贡献了。
整个项目执行流程图如下:
可以看到编写插件最核心的便是自定义插件解析自定义的配置文件、抓取指标的逻辑。
比如我们需要在配置中指定抓取目标的域名、抓取规则等。
这里 cprobe
已经抽象出了两个接口,我们只需要做对应的实现即可。
1 | type Plugin interface { |
下面就以我之前编写的 Consul 为例。
1 | # Allows any Consul server (non-leader) to service a read. |
这里每个插件的配置都不相同,所以我们需要将配置解析到具体的结构体中。
1 | func (*Consul) ParseConfig(baseDir string, bs []byte) (any, error) { |
解析配置文件没啥好说的,根据自己的逻辑实现即可,可能会配置一些默认值而已。
下面是核心的抓取逻辑,本质上就是使用对应插件的 Client
获取一些核心指标封装为 Prometheus
的 Metric
,然后由 cprobe
写入到远端的 Prometheus
中(或者是兼容 Prometheus
的数据库中)。
1 |
|
所有的指标数据都是通过对应的客户端获取。
如果是迁移一个存在的 export 到 cprobe 中时,这些抓取代码我们都可以直接复制对应 repo 中的代码。
比如我就是参考的:https://github.com/prometheus/consul_exporter
除非我们是重新写一个插件,不然对于一些流行的库或者是中间件都已经有对应的 exporter
了。
具体的列表可以参考这里:
https://prometheus.io/docs/instrumenting/exporters/
之后便需要在对应的插件目录(./conf.d
)创建我们的配置文件:
为了方便测试,可以在启动 cprobe 时添加 -no-writer
让指标打印在控制台,从而方便调试。
之前就有人问我有没有毕竟好上手的开源项目,这不就来了吗?
正好目前项目创建时间不长,代码和功能也比较简单,同时还有可观察系统大佬带队,确实是一个非常适合新手参与的开源项目。
项目地址:
https://github.com/cprobe/cprobe
最后夹带一点私货:前两天帮一个读者朋友做了一次付费的技术咨询(主要是关于 Pulsar 相关的),也是我第一次做付费内容,这种拿人钱财替人消灾难道就是知识付费的味道吗😂?
所以我就趁热打铁在朋友圈发了个广告,没想到又有个朋友找我做关于职场相关咨询,最后能帮助到对方自己也很开心。
其实经常也有人通过社媒、邮件等渠道找我帮忙看问题,一些简单的我通常也会抽时间回复。
但后面这位朋友也提到,如果我不是付费,他也不好意思来找我聊这些内容,毕竟涉及到一些隐私,同时也需要占用双方 1~2 小时的时间。
这样明码标价的方式确实也能更方便的沟通,同时也能减轻对方的心里负担,直接从白嫖转为付费大佬。
铺垫了这么多,主要目的是想进行一个小范围的尝试,如果对以下内容感兴趣的朋友欢迎加我微信私聊:
包括但不限于技术、职场、开源等我有经验的行业都可以聊。
反馈不错的话也需要可以作为我的长期副业做下去。
#Blog
最近我们的 Pulsar 存储有很长一段时间数据一直得不到回收,但消息确实已经是 ACK 了,理论上应该是会被回收的,随着时间流逝不但没回收还一直再涨,最后在没找到原因的情况下就只有一直不停的扩容。
最后磁盘是得到了回收,过程先不表,之后再讨论。
为了防止类似的问题再次发生,我们希望可以监控到磁盘维度,能够列出各个日志文件的大小以及创建时间。
这时就需要对 Pulsar
的存储模型有一定的了解,也就有了这篇文章。
讲到 Pulsar 的存储模型,本质上就是 Bookkeeper 的存储模型。
Pulsar 所有的消息读写都是通过 Bookkeeper 实现的。
Bookkeeper
是一个可扩展、可容错、低延迟的日志存储数据库,基于 Append Only 模型。(数据只能追加不能修改)
这里我利用 Pulsar 和 Bookkeeper 的 Admin API 列出了 Broker 和 BK 中 Ledger 分别占用的磁盘空间。
关于这个如何获取和计算的,后续也准备提交给社区。
但和我们实际 kubernetes
中的磁盘占用量依然对不上,所以就想看看在 BK 中实际的存储日志和 Ledger
到底差在哪里。
知道 Ledger 就可以通过 Ledger 的元数据中找到对应的 topic,从而判断哪些 topic 的数据导致统计不能匹配。
Bookkeeper 有提提供一个Admin API 可以返回当前 BK 所使用了哪些日志文件的接口:
https://bookkeeper.apache.org/docs/admin/http#endpoint-apiv1bookielist_disk_filefile_typetype
从返回的结果可以看出,落到具体的磁盘上只有一个文件名称,是无法知道具体和哪些 Ledger 进行关联的,也就无法知道具体的 topic 了。
此时只能大胆假设,应该每个文件和具体的消息 ID 有一个映射关系,也就是索引。
所以需要搞清楚这个索引是如何运行的。
我查阅了一些网上的文章和源码大概梳理了一个存储流程:
Journal
/Entrylog
LedgerId+EntryId
生成索引信息存放到 RockDB
中(Pulsar
的场景使用的是 DbLedgerStorage
实现)。Journal
和 EntryLog
实现消息的读写分离。简单来说 BK 在存储数据的时候会进行双写,Journal
目录用于存放写的数据,对消息顺序没有要求,写完后就可以清除了。
而 Entry
目录主要用于后续消费消息进行读取使用,大部分场景都是顺序读,毕竟我们消费消息的时候很少会回溯,所以需要充分利用磁盘的 PageCache,将顺序的消息尽量的存储在一起。
同一个日志文件中可能会存放多个 Ledger 的消息,这些数据如果不排序直接写入就会导致乱序,而消费时大概率是顺序的,但具体到磁盘的表现就是随机读了,这样读取效率较低。
所以我们使用 Helm
部署 Bookkeeper
的时候需要分别指定 journal
和 ledgers
的目录
1 | volumes: |
每次在写入和读取数据的时候都需要通过消息 ID 也就是 ledgerId 和 entryId 来获取索引信息。
也印证了之前索引的猜测。
所以借助于 BK 读写分离的特性,我们还可以单独优化存储。
比如写入 Journal
的磁盘因为是顺序写入,所以即便是普通的 HDD
硬盘速度也很快。
大部分场景下都是读大于写,所以我们可以单独为 Ledger
分配高性能 SSD 磁盘,按需使用。
因为在最底层的日志文件中无法直接通过 ledgerId 得知占用磁盘的大小,所以我们实际的磁盘占用率对不上的问题依然没有得到解决,这个问题我还会持续跟进,有新的进展再继续同步。
#Blog #Pulsar
]]>技术阅读周刊,每周更新。
本文是
Golang
核心作者之一 Rob Pike 去年底在澳大利亚 GopherConAU 会议上的分享;总结了 Go 语言 14 年来的做对了哪些事情、做错了哪些事情。
主要包括:
本文介绍了作为一个平台工程师需要掌握的工具。
先定义了什么是平台工程师:
为研发人员提供平台资源进行开发,让开发人员可以在云环境中自助完成整个软件生命周期的各个环节,比如基础环境搭建、代码 pipelines、监控等。
以下是会用到的工具:
URL: https://blog.quastor.org/p/load-balancing-algorithms-explained-visually?utm_source=tldrwebdev
本文介绍了一些负载均衡算法以及其优缺点。
轮询算法(Round Robin):每个请求按顺序分配到不同服务器。实现简单,但不能考虑服务器负载情况。
加权轮询(Weighted Round Robin):考虑服务器性能给各服务器设置权重,请求分配按权重比例进行。仍然不能实时反应服务器负载变化。
最少连接数(Least Connections):实时监测各服务器连接数,将请求分配到连接数最少的服务器上。实现较复杂,需要定期探测各服务器状态。
最短响应时间(Least Response Time):监测各服务器响应时间,分配给响应最快的服务器。
双随机选择(Power of Two Choices):随机选择两台服务器,将请求分配给负载较轻的一台。减少监测开销。
一致哈希(Consistent Hashing):根据请求关键信息计算哈希值,将请求分配给对应的机器范围。解决主机添加和删除问题。
其他算法如根据磁盘、内存利用率进行负载分配等。
URL: https://www.reddit.com/r/golang/comments/176b5pn/what_problem_did_go_actually_solve_for_google/
这是一个 Reddit 上的帖子,OP 的问题是 Rob 在之前的分享中提到 Golang 创建的原因是要解决 Google 内部的问题,但没有具体讲 Google 到底遇到了什么问题?
什么问题是几百种编程语言都无法解决的问题?
以下是一些高赞回答:
总体来说, Go 主要解决的是在大型分布式系统中如何更高效地进行协作开发、实现高性能又易维护。这正是 Google 当时最关心的问题。
文章链接:
#Newletters
]]>正好最近社区也补充了相关细节,本次也接着这个机会再次复盘一下,毕竟这是一个非常致命的 Bug。
先来回顾下当时的情况:升级当晚没有出现啥问题,各个流量指标、生产者、消费者数量都是在正常范围内波动。
事后才知道,因为只是删除了很少一部分的 topic,所以从监控中反应不出来。
早上上班后陆续有部分业务反馈应用连不上 topic,提示 topic nof found
.
1 | org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'Producer': Invocation of init method failed; nested exception is org.apache.pulsar.client.api.PulsarClientException$TopicDoesNotExistException: Topic Not Found. |
因为只是部分应用在反馈,所以起初怀疑是 broker 升级之后导致老版本的 pulsar-client 存在兼容性问题。
所以我就拿了平时测试用的 topic 再配合多个老版本的 sdk 进行测试,发现没有问题。
直到这一步还好,至少证明是小范故障。
因为提示的是 topic 不存在,所以就准备查一下 topic 的元数据是否正常。
查询后发现元数据是存在的。
之后我便想看看提示了 topic 不存在的 topic 的归属,然后再看看那个 broker 中是否有异常日志。
发现查看归属的接口也是提示 topic 不存在,此时我便怀疑是 topic 的负载出现了问题,导致这些 topic 没有绑定到具体的 broker。
于是便重启了 broker,结果依然没有解决问题。
之后我们查询了 topic 的 internal state 发现元数据中会少一个分区。
我们尝试将这个分区数恢复后,发现这个 topic 就可以正常连接了。
于是再挑选了几个异常的 topic 发现都是同样的问题,恢复分区数之后也可以正常连接了。
所以我写了一个工具遍历了所有的 topic,检测分区数是否正常,不正常时便修复。
1 | void checkPartition() { |
修复好所有 topic 之后便开始排查根因,因为看到的是元数据不一致所以怀疑是 zk 里的数据和 broker 内存中的数据不同导致的这个问题。
但我们查看了 zookeeper 中的数据发现一切又是正常的,所以只能转变思路。
之后我们通过有问题的 topic 在日志中找到了一个关键日志:
以及具体的堆栈。
此时具体的原因已经很明显了,元数据这些自然是没问题;根本原因是 topic 被删除了,但被删除的 topic 只是某个分区,所以我们在查询 internalState
时才发发现少一个 topic。
通过这个删除日志定位到具体的删除代码:
1 | org.apache.pulsar.broker.service.persistent.PersistentTopic#checkReplication |
原来是这里的 configuredClusters
值为空才导致的 topic 调用了 deleteForcefully()
被删除。
而这个值是从 topic 的 Policy 中获取的。
通过上图中的堆栈跟踪,怀疑是重启 broker 导致的 topic unload ,同时 broker 又在构建 topic 导致了对 topicPolicy 的读写。
最终导致 topicPolicy 为空。
只要写个单测可以复现这个问题就好办了:
1 |
|
同时还得查询元数据有耗时才能复现:
只能手动 sleep 模拟这个耗时
具体也可以参考这个 issue
https://github.com/apache/pulsar/issues/21653#issuecomment-1842962452
此时就会发现有 topic 被删除了,而且是随机删除的,因为出现并发的几率本身也是随机的。
这里画了一个流程图就比较清晰了,在 broker 重启的时候会有两个线程同时topicPolicy 进行操作。
在 thread3 读取 topicPolicy 进行判断时,thread2 可能还没有把数据准备好,所以就导致了 topic 被删除。
既然知道了问题原因就好修复了,我们只需要把 thread3 和 thread2 修改为串行执行就好了。
这也是处理并发最简单高效的方法,就是直接避免并发;加锁、队列啥的虽然也可以解决,但代码复杂度也高了很多,所以能不并发就尽量不要并发。
但要把这个修复推送到社区上游主分支最好是要加上单测,这样即便是后续有其他的改动也能保证这个 bug 不会再次出现。
之后在社区大佬的帮助下完善了单测,最终合并了这个修复。
再次证明写单测往往比代码更复杂,也更花费时间。
PR:https://github.com/apache/pulsar/pull/21704
因为社区合并代码再发版的周期较长,而我们又急于修复该问题;不然都不敢重启 broker,因为每重启一次都可能会导致不知道哪个 topic 就被删除了。
所以我们自己在本地构建了一个修复的镜像,准备在线上进行替换。
此时坑又来了,我们满怀信心的替换了一个镜像再观察日志发现居然还有删除的日志😱。
冷静下来一分析,原来是当前替换进行的 broker 没有问题了,但它处理的 topic 被转移到了其他 broker 中,而其他的 broker 并没有替换为我们最新的镜像。
所以导致 topic 在其他 broker 中依然被删除了。
除非我们停机,将所有的镜像都替换之后再一起重启。
但这样的成本太高了,最好是可以平滑发布。
最终我们想到一个办法,使用 arthas
去关闭了一个 broker 的一个选项,之后就不会执行出现 bug 的那段代码了。
1 | curl -O https://arthas.aliyun.com/arthas-boot.jar && java -jar arthas-boot.jar 1 -c "vmtool -x 3 --action getInstances --className org.apache.pulsar.broker.ServiceConfiguration --express 'instances[0].setTopicLevelPoliciesEnabled(false)'" |
我也将操作方法贴到了对于 issue 的评论区。
https://github.com/apache/pulsar/issues/21653#issuecomment-1857548997
如果不幸碰到了这个 bug,可以参考修复。
删除的这些 topic 的同时它的订阅者也被删除了,所以我们还需要修复订阅者:
1 | String topicName = "persistent://tenant/ns/topicName"; |
之所以说这个 bug 非常致命,是因为这样会导致 topic 的数据丢失,同时这些 topic 上的数据也会被删除。
后续 https://github.com/apache/pulsar/pull/21704#issuecomment-1878315926社区也补充了一些场景。
其实场景 2 更容易出现复现,毕竟更容易出现并发;也就是我们碰到的场景
说来也奇怪,结合社区的 issue 和其他大佬的反馈,这个问题只有我们碰到了,估计也是这个问题的触发条件也比较苛刻:
systemTopic/topicLevelPolices
符合以上条件的集群就需要注意了。
其实这个问题在这个 PR 就已经引入了
https://github.com/apache/pulsar/pull/11021
所以已经存在蛮久了,后续我们也将检测元数据作为升级流程之一了,确保升级后数据依然是完整的。
相关的 issue 和 PR:
https://github.com/apache/pulsar/issues/21653
https://github.com/apache/pulsar/pull/21704
#Blog #Pulsar
]]>技术阅读周刊,每周更新。
URL: https://cloud.tencent.com/developer/article/2245703
本文分析了 Pulsar 消息的生命周期,主要是如何保留和回收消息
URL: https://mp.weixin.qq.com/s/EC-YLm71YB4cMDoTjrdfyg
推荐了一些常用的 kubernetes 管理工具
velero
URL: https://dev.to/pankajgupta221b/3-terminal-commands-to-increase-your-productivity-57dm?ref=dailydev
作者介绍了几个常用的可以提高生产力的终端命令
别名非常好用,以下是我常用的一些别名:
1 | -='cd -' |
这个在有时候需要 debug 日志或者复制一些长文本到剪贴板里非常有用
1 | cat xx.properties |grep timeout | pbcopy |
这样就可以把 timeout 这个关键字从文件中复制到粘贴板,我就可以将它复制到其他地方使用。
在终端中使用 ctrl+r 就可以根据关键字在历史命令中查找命令,这个在忘记了一些命令但只记得关键字的时候非常有用。
我这里使用的终端是 Warp ,交互上更加好用一些。
可以用于显示日历
文章链接:
#Newletters
]]>在上一篇文章 Pulsar3.0 升级指北讲了关于升级 Pulsar 集群的关键步骤与灾难恢复,本次主要分享一些 Pulsar3.0
的新功能与可能带来的一些问题。
先来个欲扬先抑,聊聊升级后所碰到的问题吧。
其中有两个问题我们感知比较明显,特别是第一个。
我们在上个月某天凌晨从 2.11.2
升级到 3.0.1
之后,进行了上一篇文章中所提到的功能性测试,发现没什么问题,觉得一切都还挺顺利的,半个小时搞定后就下班了。
结果哪知道第二天是被电话叫醒的,有部分业务反馈业务重启之后就无法连接到 Pulsar 了。
最终定位是 topic 被删除了。
其中的细节还蛮多的,修复过程也是一波三折,后面我会单独写一篇文章来详细梳理这个过程。
在这个 issue 和 PR 中有详细的描述:
https://github.com/apache/pulsar/issues/21653
https://github.com/apache/pulsar/pull/21704
感兴趣的朋友也可以先看看。
第二个问题不是那么严重,是升级后发现 bookkeeper 的一些监控指标丢失了,比如这里的写入延迟:
我也定位了蛮久,但不管是官方的 docker 镜像还是源码编译都无法复现这个问题。
最终丢失的指标有这些:
详细内容可以参考这个 issue:
https://github.com/apache/pulsar/issues/21766
讲完了遇到的 bug,再来看看带来的新特性,重点介绍我们用得上的特性。
当我们升级或者是重启 broker 的时候,全部重启成功后其实会发现最后重启的那个 broker 是没有流量的。
这个原理和优化在之前写过的 Pulsar负载均衡原理及优化 其实有详细介绍。
本次 3.0 终于将那个优化发版了,之后只要我们配置 lowerBoundarySheddingEnabled: true
就能开启这个低负载均衡的一个特性,使得低负载的 broker 依然有流量进入。
Pulsar 可能会因为消息消费异常导致游标出现空洞,从而导致磁盘得不到释放;
所以我们有一个定时任务,会定期扫描积压消息的 topic 判断是否存在空洞消息,如果存在便可以在管理台使用 skipMessage API 跳过空洞消息,从而释放磁盘。
但在 3.0 之前这个跳过 API 存在 bug,只要跳过的数量超过 8 时,实际跳过的数量就会小于 8.
具体 issue 和修复过程在这里:
https://github.com/apache/pulsar/issues/20262
https://github.com/apache/pulsar/pull/20326
总之这个问题在 3.0 之后也是修复了,有类似需求的朋友也可以使用。
同时也支持了一个新的负载均衡器,解决了以下问题:
non-persistent
来存储负载信息,就不再依赖 zk 。leader broker
进行重定向到具体的 broker,其实这些重定向并无意义,徒增了系统开销。更多完整信息可以参考这个 PIP: PIP-192: New Pulsar Broker Load Balancer
第二个重大特性是支持大规模延迟消息,相信是有不少企业选择 Pulsar 也是因为他原生就支持延迟消息。
我们也是大量在业务中使用延迟消息,以往的延迟消息有着以下一些问题:
最后即便是升级到了 3.0 依然还有一些待优化的功能,在之前的 从 Pulsar Client 的原理到它的监控面板中有提到给客户端加了一些监控埋点信息。
最终使用下来发现还缺一个 ack 耗时的一个面板,其实日常碰到最多的问题就是突然不能消费了(或者消费过慢)。
这时如果有这样的耗时面板,首先就可以定位出是否是消费者本身的问题。
目前还在开发中,大概类似于这样的数据。
Pulsar3.0 是 Pulsar 的第一个 LTS 版本,推荐尽快升级可以获得长期支持。
但只要是软件就会有 bug,即便是 LTS 版本,所以大家日常使用碰到 Bug 建议多向社区反馈,一起推动 Pulsar 的进步。
#Blog #Pulsar
]]>技术阅读周刊,每周更新。
使用 Deno 和 Go 进行基本的接口对比
Deno
v1.38.0都是最简单的 httpServer:
1 | Deno.serve({ |
1 | package main |
总的来说 Deno 比 Go 慢了 30% 左右,但 CPU 占有率比 Go 更少,Go 的内存占用更低。
这个对比就图一乐。
URL: https://blog.stackademic.com/top-7-spring-boot-design-patterns-unveiled-4a2569f8d324
7 个我们可以学习的 Spring Boot 的设计模式
Singleton Pattern
单例模式这个没啥好说的,面试都被讲烂了,依然很经典。
当我们使用这些注解声明一个 Bean 时@Component
, @Service
, @Repository
, or @Controller
,就会被创建一个单例对象注入到 IOC 容器中。
Spring 也提供了工厂模式的接口,我们可以自定义创建逻辑:
1 | import org.springframework.beans.factory.FactoryBean; |
这个其实不算是 Spring 所提供的,但确实很好用;通常用于创建需要很多可选参数的对象时使用:
1 | import lombok.Builder; |
代理模式在 spring 中通常用于 AOP 切面,可以实现一些通用的非业务逻辑功能;比如日志、缓存、安全检测等:
1 | import org.aspectj.lang.annotation.Aspect; |
本质上是将业务解耦,生产者发布事件,订阅者接收事件,只是 spring 帮我们封装好了逻辑。
1 | import org.springframework.context.ApplicationEvent; |
URL: https://news.shulie.io/?p=8229
链接里有 B 站视频,文字版链接:https://mp.weixin.qq.com/s/FySeVBL7EfihOlNDBvAPpw
转转的监控方案
kubernetes
,所以是基于 kubernetes 的 SD 实现的服务发现。文章链接:
#Newletters
]]>Pulsar3.0 是 Pulsar 社区推出的第一个 LTS 长期支持版本。
如图所示,LTS 版本会最长支持到 36 个月,而 Feature 版本最多只有六个月;类似于我们使用的 JDK11,17,21
都是可以长期使用的;所以也推荐大家都升级到 LTS 版本。
作为首个 LTS 版本,3.0 自然也是自带了许多新特性,这个会在后续介绍。
先来看看升级指南:
在官方的兼容表中会发现:不推荐跨版本升级。
也就是说如果你现在还在使用的是 2.10.x,那么推荐是先升级到 2.11.x 然后再升级到 3.0.x.
而且根据我们的使用经验来看,首个版本是不保险的,即便是 LTS 版本;
所以不推荐直接升级到 3.0.0,而是更推荐 3.0.1+,这个小版本会修复 3.0 所带来的一些 bug。
先讲一下我们的升级流程,大家可以用做参考。
根据我们的使用场景,为了以防万一,首先需要将我们的插件依赖升级到对应的版本。
其实简单来说就是更新下依赖,然后再重新打包,在后续的流程进行测试。
之后是预热镜像,我们使用 harbor
搭建了自己的 docker 镜像仓库,这样在升级重启镜像的时候可以更快的从内网拉取镜像。
毕竟一个 pulsar-all 的镜像也不小,尽量的缩短启动时间。
预热的过程也很简单:
1 | docker pull apachepulsar/pulsar-all:3.0.1 |
之后升级的时候就可以使用私服的镜像了。
我这边有写了一个 cli
可以帮我快速创建或升级一个集群,然后触发我所编写的功能测试。
1 | ./pulsar-upgrade-cli upgrade pulsar-test ./charts/pulsar --version x.x.x -f charts/pulsar/values.yaml -n pulsar-test |
这个 cli 很简单,一共就做三件事:
之后的效果如下:
主要就是覆盖了我们的使用场景,都跑通过之后才会走后续的流程。
之后会启动一个 200 左右的并发生产和消费数据,模拟线上的使用情况,会一直让这个任务跑着,大概一晚上就可以了,第二天通过监控查看:
组件的升级步骤这里参考了官方指南:
https://pulsar.apache.org/docs/3.1.x/administration-upgrade/#upgrade-zookeeper-optional
只要一步步按照这个流程走,问题不大,哪一步出现问题后需要及时回滚,回滚流程参考下面的回滚部分。
同时在升级过程中需要一直查看 broker 的 error 日志,如果有明显的不符合预期的日志一定要注意。
在升级 bookkeeper 的时候,broker 可能会出现 bk 连接失败的异常,这个可以不用在意。
都升级完后就是线上业务验证环节了:
Pulsar-SQL
和 broker-interceptor
实现的。 ✅ 2023-12-24当出现异常的时候需要立即回滚,这里的异常一般就是消息收发异常,客户端掉线等。
经过我的测试 3.0.x 的存储和之前的版本是兼容的,所以 bookkeeper
都能降级其他的组件就没啥可担心的了。
需要降级时直接将所有组件降级为上一个版本即可。
因为是从 2.x 升级到 3.x 也是涉及到了跨大版本,所以也准备了灾难恢复的方案。
比如极端情况下升级失败,所有数据丢失的情况。
整个灾难恢复的主要目的就是恢复后的集群对外提供的域名不发生变化,同时所有的客户端可以自动重连上来,也就是最坏的情况下所有的数据丢了可以接受,但不能影响业务正常使用。
所以我们的流程如下:
1 |
|
第一步是备份 topic:
因为我们客户端使用了 JWT 验证,所有为了使得恢复的 Pulsar 集群可以让客户端无缝切换到新集群,因此必须得使用相同的公私钥。
这个其实比较简单,我们使用的是 helm 安装的集群,所以只需要备份好 Secret
即可。
1 | apiVersion: v1 |
首先使用 helm 重新创建一个新集群:
1 | ./scripts/pulsar/prepare_helm_release.sh -n pulsar -k pulsar |
直接使用刚才备份的公私钥覆盖到新集群即可。
进入 toolset pod 创建需要使用的 tenant/namespace
1 | k exec -it pulsar-toolset-0 -n pulsar bash |
之后便是最重要的元数据恢复了:
1 |
|
流程和备份类似:
因为授权接口限制了并发调用,所有需要加锁,导致整个恢复的流程就会比较慢。
8000 topic 的 namespace 大概恢复时间为 40min 左右。
之后依次恢复其他 namespace 即可。
1 | admin.namespaces().setNamespaceMessageTTL("tenant/namespace", 3600 * 6); |
如果之前的集群有设置 TTL 或者是 backlogQuota 时都需要手动恢复。
以上就是整个升级和灾难恢复的流程,当然灾难恢复希望大家不要碰到。
我会在下一篇详细介绍 Pulsar 3.0
的新功能以及所碰到的一些坑。
技术阅读周刊,每周更新。
本文讲解了基于最新的 Spring Boot3.2 和 Java 21 所使用到的技术栈
数据库使用 Postgres15
和 flyway 来管理数据库 schema 的迁移。
Spring6 实现了新的 RFC9457规范,实现以下接口:
1 |
|
1 | { |
1 |
|
1 |
|
当有无法处理的异常时,就需要配置一个兜底的异常。
1 | <dependency> |
1 | public interface CompanyRepository extends JpaRepository<Company, Long> { |
Spring 提供了标准的缓存接口,即便是后续需要切换到 Redis,使用的 API 和注解都不会发生改变。
Java21 后支持了虚拟线程,几乎可以无限的实现线程,在 Spring Boot 3.2 需要单独开启。
1 | spring.threads.virtual.enabled |
1 | <dependency> |
1 | spring: |
注意在生成环境不要暴露管理 API
1 | <dependency> |
同步请求的时候每个请求都会带上 traceId
和 spanId
,如果是异步请求时候需要配置:spring.reactor.context-propagation=true
如果使用 @Async
时:
1 |
|
本地测试时候可以使用 Otel Desktop Viewer
1 | management: |
URL: https://www.shuttle.rs/blog/2023/09/27/rust-vs-go-comparison
动手比较 Rust 和 Go
本文是通过编写一个 web 服务来进行比较的。
URL: https://www.zhihu.com/question/21409296/answer/1040884859
图文并茂,讲解了 G-M-P 各自之间的关系,以及调度模型。
GoMAXPROCS
决定小车的数量。文章链接:
#Newletters
]]>前段时间在使用 Pulsar 的 admin API 时,发现其中的一个接口响应非常慢:
1 | admin.topics().getPartitionedStats(topic); |
使用 curl 拿到的响应结果非常大,同时也非常耗时:
具体的 issue 在这里:https://github.com/apache/pulsar/issues/21200
后面经过分析,是因为某些 topic 的生产者和消费者非常多,导致这个查询 topic 统计的接口数据量非常大。
但在我这个场景其实是不需要这些生产者和消费者信息的,现在就导致这个 topic 无法查看状态,所以就建议新增两个参数可以过滤这两个字段。
因为涉及到新增 API 了,所以社区维护者就建议我起草一个提案试试:
此时就涉及到什么情况下需要给社区发起一个提案的问题了。
在官方的提案指南中有着详细的说明,简单来说就是:
首先第一步就是根据官方模版起草一个提案:
重点描述背景、目的、详细设计等。
并发起一个 PR,如果不确定怎么写的话可以参考已经合并了的提案。
之后则是将这个 PR 发送到开发组邮箱中,让社区成员参与讨论。
这一步可能会比较耗时,提案内容可能会被反复修改。
发起提案的一个重要目的是可以让社区成员进行讨论,评估是否需要这个提案或者是否
有其他解决方法。
经过讨论,如果提案获得通过后就可以发起投票了,至少需要有三个 binding 通过的投票后这个提案就通过了。
虽然任何人都可以参与投票,但社区只会考虑 PMC 的投票建议;投票的时效性也只有 48h。
48 小时候便可以发一个投票结果的邮件,如果达到通过条件便可以通知参与投票的 PMC 合并这个 PR 了。
之后就是没啥好说的实现过程,因为通常我们是需要在提案里详细描述实现过程以及涉及到修改的地方。
只要提案被 review 通过后实现起来就非常简单了,跟着提案里的流程实现就好了。
这点非常类似于我们在企业中对某个业务做技术方案,如果大家都按照类似的流程严格审核方案,那实现起来是非常快的,而且可以尽量的减少事后扯皮。
所以最后我的实现 PR 提交之后,都没有任何的修改意见,直接就合并了;也大大降低了审核人员的负担,提高整体效率。
以上就是我第一次参与 Pulsar 社区的提案过程,我猜测其他社区的流程也是大差不差;其中重点就是异步沟通;大家都认可之后真的会比实时通信的效率高很多。
具体的提案细节可以阅读官方指南 https://github.com/apache/pulsar/blob/master/pip/README.md
#Blog #Pulsar
]]>技术阅读周刊,每周更新。
URL: https://blog.canopas.com/golang-14-shorthand-tricks-you-might-not-know-8d8d21954c49
1 | // Long form |
1 | // Long form |
1 | a, b := 1, 2 |
1 | // Long form |
1 | // Long form |
1 | // Long form |
1 | // Long form |
1 | // Long form |
1 | // Long form |
1 | // Long form |
1 | // Long form |
一些常用的 Java 技巧
善用 Lambda 表达式
1 | // Before |
使用 Optionals 替代 null
1 | Optional<String> maybeName = Optional.ofNullable(person.getName()); |
使用 stream 简化集合操作
1 | List<Integer> evenNumbers = numbers.stream() |
String.format 拼接字符串
1 | String s1 = "Hello"; |
使用 default method 扩展接口
1 | import java.time.LocalDateTime; |
使用枚举替换常量
1 | public class Main { |
使用 try-with-Resource 管理资源
1 | try (FileReader fileReader = new FileReader("example.txt"); |
本文对比了 SpringBoot Webflux 和 Vert.x 的性能对比
以下是两个框架写的压测接口:
1 | package hello; |
最后直接看对比结果吧:
最终作者根据一个计算公式得出两个框架的得分,规则如下:
Vert.x
得分超过 Webflux
55%⬆️不过个人觉得压测结果再好,套上业务后,比如一个接口查询了多个后端服务,后端服务有依赖于多个数据库,最终出来的 RT 大家都差不多。
除非是某些对性能极致要求的场景,比如实时数据分析、物联网中间件等和直接业务不太相关领域。
它的底层依然是 Netty,但比 Netty 提供了跟易用的 API。
讲解了 git cherry-pick 的作用,什么时候该用,什么时候不用。
举个例子:
一些大型的开源项目往往都会有一个主分子,同时维护了不同版本的子分支,有些用户可能就会一直使用一些长期维护的子分支,比如 v2.1.0 \ v2.3.0
但对于大部分的开发者来说主要会维护主分支,也会在主分支上推进一些新功能,这些新功能不一定会同步到上述提到的两个老版本中。
但对于一些安全漏洞,重大 bug 等是需要同步到这些子分支的,但又不能把一些不兼容的新特性同步到子分支中。
此时就可以使用 cherry-pick 这个功能,只将某一个提交给 pick 到目标分支中。
1 | Cherry pick more than 1 SHA. |
文章链接:
#Newletters
]]>之前写过一篇文章 VictoriaLogs:一款超低占用的 ElasticSearch 替代方案讲到了我们使用 Victorialogs
来存储 Pulsar 消息队列的消息 trace 信息。
而其中的关键的埋点信息是通过 Pulsar 的 BrokerInterceptor
实现的,后面就有朋友咨询这块代码是否开源,目前是没有开源的,不过借此机会可以聊聊如何实现一个 BrokerInterceptor
插件,当前还没有相关的介绍文档。
其实当时我在找 BrokerInterceptor
的相关资料时就发现官方并没有提供对应的开发文档。
只有一个 additional servlet的开发文档,而 BrokerInterceptor
只在 YouTube 上找到了一个社区分享的视频。
虽说看视频可以跟着实现,但总归是没有文档方便。
在这之前还是先讲讲 BrokerInterceptor
有什么用?
其实从它所提供的接口就能看出,在消息到达 Broker 后的一些关键节点都提供了相关的接口,实现这些接口就能做很多事情了,比如我这里所需要的消息追踪。
下面开始如何使用 BrokerInterceptor
:
首先是创建一个 Maven
项目,然后引入相关的依赖:
1 | <dependency> |
然后我们便可以实现 org.apache.pulsar.broker.intercept.BrokerInterceptor
来完成具体的业务了。
在我们做消息追踪的场景下,我们实现了以下几个接口:
以 messageProduced
为例,需要解析出消息ID,然后拼接成一个字符串写入 Victorialogs
存储中,其余的两个埋点也是类似的。
1 |
|
我们需要创建一个项目描述文件,路径如下:src/main/resources/META-INF/services/broker_interceptor.yml
名字也是固定的,broker 会在启动的时候读取这个文件,其内容如下:
1 | name: interceptor-name |
重点是填写自定义实现类的全限定名。
1 | <build> |
由于 Broker 识别的是 nar 包,所以我们需要配置 nar 包插件,之后使用 mvn package
就会生成出 nar 包。
我们还需要在 broker.conf 中配置:
1 | brokerInterceptors: "interceptor-name" |
也就是刚才配置的插件名称。
不过需要注意的是,如果你是使用 helm 安装的 pulsar,在 3.1 版本之前需要手动将brokerInterceptors
写入到 broker.conf
中。
1 | FROM apachepulsar/pulsar-all:3.0.1 |
不然在最终容器中的 broker.conf
中是读取不到这个配置的,导致插件没有生效。
我们是重新基于官方镜像打的一个包含自定义插件的镜像,最终使用这个镜像进行部署。
https://github.com/apache/pulsar/pull/20719
我在这个 PR 中已经将配置加入进去了,但得在 3.1 之后才能生效;也就是在 3.1 之前都得加上加上这行:
1 | RUN echo "\n" >> /pulsar/conf/broker.conf |
目前来看 Pulsar 的 BrokerInterceptor
应该使用不多,不然使用 helm 安装时是不可能生效的;而且官方文档也没用相关的描述。
#Blog #Pulsar
]]>技术阅读周刊,每周更新。
URL: https://tech.meituan.com/2023/12/04/ten-years-of-meituan-technology-blog.html
美团技术博客更新十周年了,这个博客确实在广大开发者心中都是有口皆碑的;记得当初在这里看过 HashMap 的原理分析、动态线程池等技术;
现在也有加到订阅列表里,有更新时会第一时间阅读
URL: https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html
本文描述了美团对 API 做异步优化的过程,最终选择了 CompletableFuture 的过程
CompletableFuture
使用起来的坑还是蛮多的,推荐大家都应该阅读下。
ForkJoinPool
线程池,可能会有阻塞的情况;也可以直接传入自定义的线程池CompletableFuture
的异常往往会被包装为CompletionException,所以最好是要异常工具类进行提取1 | public class ExceptionUtils { |
URL: https://mp.weixin.qq.com/s/QJn6-EzPp7PXar-GdMITCA
虽然这是一篇软文,不过其中几个论据确实是有道理的。
而 K8s 的控制器则是基于另一种思路:机器能做的事就不应该由人来做。通过 Operator,可以实现24 小时不间断地同步期望状态和实际状态,而这是用 Ansible 很难实现的,你用 Ansible 实现是想写个定时任务嘛?
目前市面上大部分云服务厂商所提供的数据库服务也都是跑在 kubernetes 中的。
URL: https://github.com/deckarep/golang-set
一个泛型的 Go Set 库, 还提供了一些集合常用的操作工具,比如 Contains/Difference/Intersect 等函数。
已经被这些公司采用了:
1 | // Syntax example, doesn't compile. |
文章链接:
#Newletters
]]>技术阅读周刊,每周更新。
URL: https://last9.io/blog/prometheus-vs-victoriametrics/?ref=dailydev
对比了 Prometheus 和 VM 的区别
考虑到和云原生的环境的兼容性,那 Prometheus 可能更合适些,毕竟是 CNCF 组织下的项目。
但如果考虑到性能、存储、资源占用性,VM 会更合适一些。
这是一个 Rust 的 newsletter,介绍了十个项目 idea 可以提高你的 Rust 的水平,我看了下这些项目也不怎么限制语言,任何语言都可以尝试下。
简易版的 grep 命令
简单:读取文件根据搜索条件输出搜索结果,涉及到的技术栈:短域名服务
中等:接收一个长域名,转换为一个短域名,访问短域名时可以自动重定向到长域名。基于文本的冒险游戏
中等:用户可以探索房间,选择物品,解密等。基本的网络爬虫
简单:爬取一个网页然后提取指定的信息。实时聊天应用
中等:支持多个人用户加入房间,可以给每个人发送消息。Markdown 解析为 HTML
中等:简单的 HTTP 服务
中等:支持静态文件服务器,也可以处理 RESTful 请求。URL: https://itnext.io/bloom-filters-and-go-1d5ac62557de
多年前我也用 Java 写过一个布隆过滤器,本文作者介绍用 Go 来实现,不过原理都差不多。
布隆过滤器有以下特点:
所以基于以上特性就有了下面这些应用场景:
URL: https://betterprogramming.pub/concurrency-with-select-goroutines-and-channels-9786e0c6be3c
使用 select goroutine channel 掌握并发
1 | func quickestApiResponse(functions []*Function) { |
1 | func main() { |
1 | ch := make(chan string) |
文章链接:
#Newletters
]]>当我们在生产环境发布应用时,必须要考虑到当前系统还有用户正在使用的情况,所以尽量需要做到不停机发版。
所以在发布过程中理论上之前的 v1 版本依然存在,必须得等待 v2 版本启动成功后再删除历史的 v1 版本。
如果 v2 版本启动失败 v1 版本不会做任何操作,依然能对外提供服务。
这是我们预期中的发布流程,要在 kubernetes 使用该功能也非常简单,只需要在 spec 下配置相关策略即可:
1 | spec: |
这个配置的含义是:
maxSurge
:滚动更新过程中可以最多超过预期 Pod 数量的百分比,当然也可以填整数。maxUnavailable
:滚动更新过程中最大不可用 Pod 数量超过预期的百分比。这样一旦我们更新了 Pod 的镜像时,kubernetes 就会先创建一个新版本的 Pod 等待他启动成功后再逐步更新剩下的 Pod。
滚动升级过程中不可避免的又会碰到一个优雅停机的问题,毕竟是需要停掉老的 Pod。
这时我们需要注意两种情况:
第一个问题如果我们使用的是 Go
,可以使用一个钩子来监听 kubernetes
发出的退出信号:
1 | quit := make(chan os.Signal) |
在这里执行对应的资源释放。
如果使用的是 spring boot
也有对应的配置:
1 | server: |
当应用收到退出信号后,spring boot 将不会再接收新的请求,并等待现有的请求处理完毕。
但 kubernetes 也不会无限等待应用将 Pod 将任务执行完毕,我们可以在 Pod 中配置
1 | terminationGracePeriodSeconds: 30 |
来定义需要等待多长时间,这里是超过 30s 之后就会强行 kill Pod。
具体值大家可以根据实际情况配置
1 | spec: |
同时我们也可以配置 preStop
做一个 sleep 来确保 kubernetes
将准备删除的 Pod 在 Iptable
中已经更新了之后再删除 Pod
。
这样可以避免第二种情况:已经删除的 Pod
依然还有请求路由过来。
具体可以参考 spring boot
文档:
https://docs.spring.io/spring-boot/docs/2.4.4/reference/htmlsingle/#cloud-deployment-kubernetes-container-lifecycle
回滚其实也可以看作是升级的一种,只是升级到了历史版本,在 kubernetes
中回滚应用非常简单。
1 | 回滚到上一个版本 |
同时 kubernetes 也能保证是滚动回滚的。
在之前的 如何优雅重启 kubernetes 的 Pod 那篇文章中写过,如果想要优雅重启 Pod 也可以使用 rollout 命令,它也也可以保证是滚动重启。
1 | k rollout restart deployment/nginx |
使用 kubernetes
的滚动更新确实要比我们以往的传统运维简单许多,就几个命令的事情之前得写一些复杂的运维脚本才能实现。
本文的所有源码在这里可以访问:
https://github.com/crossoverJie/k8s-combat
#Blog #K8s
今天进入 kubernetes
的运维部分(并不是运维 kubernetes
,而是运维应用),其实日常我们大部分使用 kubernetes
的功能就是以往运维的工作,现在云原生将运维和研发关系变得更紧密了。
今天主要讲解 Probe
探针相关的功能,探针最实用的功能就是可以控制应用优雅上线。
举个例子,当我们的 service 关联了多个 Pod 的时候,其中一个 Pod 正在重启但还没达到可以对外提供服务的状态,这时候如果有流量进入。
那这个请求肯定就会出现异常,从而导致问题,所以我们需要一个和 kubernetes
沟通的渠道,告诉它什么时候可以将流量放进来。
比如如图所示的情况,红色 Pod
在未就绪的时候就不会有流量。
使用就绪探针就可以达到类似的效果:
1 | readinessProbe: |
这个配置也很直接:
但没有配置就绪探针时,一旦 Pod 的
Endpoint
加入到 service 中(Pod 进入Running
状态),请求就有可能被转发过来,所以配置就绪探针是非常有必要的。
而启动探针往往是和就绪探针搭配干活的,如果我们一个 Pod 启动时间过长,比如超过上面配置的失败检测次数,此时 Pod 就会被 kubernetes 重启,这样可能会进入无限重启的循环。
所以启动探针可以先检测一次是否已经启动,直到启动成功后才会做后续的检测。
1 | startupProbe: |
我这里两个检测接口是同一个,具体得根据自己是实际业务进行配置;
比如应用端口启动之后并不代表业务已经就绪了,可能某些基础数据还没加载到内存中,这个时候就需要自己写其他的接口来配置就绪探针了。
所有关于探针相关的日志都可以在 Pod 的事件中查看,比如如果一个应用在启动的过程中频繁重启,那就可以看看是不是某个探针检测失败了。
存活探针往往是用于保证应用高可用的,虽然 kubernetes 可以在 Pod 退出后自动重启,比如 Pod OOM
;但应用假死他是检测不出来的。
为了保证这种情况下 Pod 也能被自动重启,就可以配合存活探针使用:
1 | livenessProbe: |
一旦接口响应失败,kubernetes 就会尝试重启。
以上探针配置最好是可以在研效平台可视化配置,这样维护起来也比较简单。
探针是维护应用健康的必要手段,强烈推荐大家都进行配置。
本文的所有源码在这里可以访问:
https://github.com/crossoverJie/k8s-combat
#Blog