摸着石头过河:知乎核心业务 Go 语言改造实践

Go-GoMetalinter-一个工具来自动应用所有静态分析工具并以标准化形式报告他们的输出 Go Metalinter - 一个工具来自动应用所有静态分析工具并以标准化形式报告他们的输出 立即下载

640?wx_fmt=jpeg

作者

      知乎资深工程师 xlzd
转载链接

      https://zhuanlan.zhihu.com/p/48039838

背景

众所周知,知乎社区后端的主力编程语言是 Python。

随着知乎用户的迅速增长和业务复杂度的持续增加,核心业务的流量在过去一年内增长了好几倍,对应的服务端的压力也越来越大。随着业务发展,我们发现 Python 作为动态解释型语言,较低的运行效率和较高的后期维护成本带来的问题逐渐暴露出来:

  1. 运行效率较低。知乎目前机房机柜空间已经不足,按照目前的用户和流量增长速度,可预见将在短期内服务器资源告急(针对这一点,知乎正在由单机房架构升级为异地多活架构);

  2. Python 过于灵活的语言特性,导致多人协作和项目维护成本较高。

受益于近些年开源社区的发展和容器等关键技术的普及,知乎的基础平台技术选型一直较为开放。在开放的标准之上,各个语言都有成熟的开源的中间件可供选择。这使得业务做选型时可以根据问题场景选择更合适的工具,语言也是一样。

基于此,为了解决资源占用问题和动态语言的维护成本问题,我们决定尝试使用静态语言对资源占用极高的核心业务进行重构。

为什么选择 Golang

如上所述,知乎在后端技术选型上比较开放。在过去几年里,除了 Python 作为主力语言开发,知乎内部也不乏 Java、Golang、NodeJS 和 Rust 等语言开发的项目。

640?wx_fmt=jpeg
通过 ZAE(Zhihu App Engine) 新建一个应用时,提供了多门语言的支持

Golang 是目前知乎内部讨论交流最活跃的编程语言之一,考虑到以下几点,我们决定尝试用 Golang 重构内部高并发量的核心业务:

  • 天然的并发优势,特别适合 IO 密集应用

  • 知乎内部基础组件的 Golang 版生态比较完善

  • 静态类型,多人协作开发和维护更加安全可靠

  • 构建好后只需一个可执行文件即可,方便部署

  • 学习成本低,且开发效率较 Python 没有明显降低

相比另一门也很优秀的待选语言—— Java,Golang 在知乎内部生态环境、部署的方便程度和工程师的兴趣上都更胜一筹,最终我们决定,选择 Golang 作为开发语言。

改造成果

截至目前,知乎社区 member(RPC,高峰数十万 QPS)、评论(RPC + HTTP)、问答(RPC + HTTP)服务已经全部通过 Golang 重写。同时因为在 Golang 化过程中我们对 Golang 基础组件的进一步完善,目前一些新的业务在开发之初就直接选择了 Golang 来实现,Golang 已经成为知乎内部新项目技术选型的推荐语言之一。

相比改造前,目前得到改进的点有以下:

  1. 节约了超过 80% 的服务器资源。由于我们的部署系统采用蓝绿部署,所以之前占用服务器资源最高的几个业务会因为容器资源原因无法同时部署,需要排队依次部署。重构后,服务器资源得到优化,服务器资源问题得到了有效解决。

  2. 多人开发和项目维护成本大幅下降。想必大家维护大型 Python 项目都有经常需要里三层、外三层确认一个函数的参数类型和返回值。而 Golang 里,大家都面向接口定义,然后根据接口来实现,这使得编码过程更加安全,很多 Python 代码运行时才能发现的问题可以在编译时即可发现。

  3. 完善了内部 Golang 基础组件。前面提到,知乎内部基础组件的 Golang 版比较完善,这是我们选择 Golang 的前提之一。不过,在重构的过程中,我们发现仍有部分基础组件不够完善甚至缺少。所以,我们也完善和提供了不少基础组件,为之后其它项目的 Golang 化改造提供了便利。

640?wx_fmt=jpeg
过去 10 个月问答服务的 CPU 核数占用变化趋势

实施过程

得益于知乎微服务化比较彻底,每个独立的微服务想要更换语言非常方便,我们可以方便地对单个业务进行改造,且几乎可以做到外部依赖方无感知。

知乎内部,每个独立的微服务有自己独立的各种资源,服务间是没有资源依赖的,全部通过 RPC 请求交互,每个对外提供服务(HTTP or RPC)的容器组,都通过独立的 HAProxy 地址代理对外提供服务。一个典型的微服务结构如下:

640?wx_fmt=jpeg
知乎内部一个典型的微服务组成,服务间没有资源依赖

所以,我们的 Golang 化改造分为了以下几步:

Step1. 用 Golang 重构逻辑

首先,我们会新起一个微服务,通过 Golang 来重构业务逻辑,但是:

  1. 新服务对外暴露的协议(HTTP 、RPC 接口定义和返回数据)与之前保持一致(保持协议一致很重要,之后迁移依赖方会更方便)

  2. 新的服务没有自己的资源,使用待重构服务的资源:

640?wx_fmt=jpeg
新服务(下)使用待重构服务(上)的资源,短期内资源混用

Step2. 验证新逻辑正确性

当代码重构完成后,在将流量切换到新逻辑之前,我们会先验证新服务的正确性。

针对读接口,由于其是幂等的,多次调用没有副作用,所以当新版接口实现完成后,我们会在老服务收到请求的同时,起一个协程请求新服务,并对比新老服务的数据是否一致:

    1. 当请求到达老服务后,会立即启一个协程请求新的服务,与此同时老服务的主逻辑会正常执行。

    2. 当请求返回后,会比较老服务与新实现的服务返回数据是否相同,如果不同,会打点记录 + 日志记录。

    3. 工程师根据打点指标和日志,发现新实现逻辑的错误,改正后继续验证(其实这一步,我们也发现了不少原本 Python 实现的错误)。

640?wx_fmt=jpeg
服务请求两边数据,并对比结果,但返回老服务的结果

而对于写接口,大部分并不是幂等的,所以针对写接口不能像上面这样验证。对于写接口,我们主要会通过以下手段保证新旧逻辑等价:

    1. 单元测试保证

    2. 开发者验证

    3. QA 验证

Step3. 灰度放量

当一切验证通过之后,我们会开始按照百分比转发流量。

此时,请求依然会被代理到老的服务的容器组,但是老服务不再处理请求,而是转发请求到新服务中,并将新服务返回的数据直接返回。

之所以不直接从流量入口切换,是为了保证稳定性,在出现问题时可以迅速回滚。

640?wx_fmt=jpeg
服务请求 Golang 实现

Step4. 切流量入口

当上一步的放量达到 100% 后,请求虽然依然会被代理到老的容器组,但返回的数据已经全部是新服务产生的。此时,我们可以把流量入口直接切换到新服务了。

640?wx_fmt=jpeg
请求直接打到新的服务,旧服务没有流量了

Step5. 下线老服务

到这里重构已经基本接近尾声了。不过新服务的资源还在老服务中,以及老的没有流量的服务其实还没有下线。

到这里,直接把老服务的资源归属调整为新服务,并下线老服务即可。

640?wx_fmt=jpeg
Goodbye,Python

至此,重构完成。

Golang 项目实践

在重构的过程中,我们踩了不少坑,这里摘其中一些与大家分享一下。如果大家有类似重构需求,可简单参考。

换语言重构的前提是了解业务

不要无脑翻译原来的代码,也不要无脑修复原本看似有问题的实现。在重构的初期,我们发现一些看似可以做得更好的点,闷头一顿修改之后,却产生了一些奇怪的问题。后面的经验是,在重构前一定要了解业务,了解原本的实现。最好整个重构的过程有对应业务的工程师也参与其中。

项目结构

关于合适的项目结构,其实我们也走过不少弯路。

一开始,我们根据在 Python 中的实践经验,层与层之间直接通过函数提供交互接口。但是,迅速发现 Golang 很难像 Python 一样,方便地通过 monkey patch 完成测试。

经过逐渐演进和参考各种开源项目,目前,我们的代码结构大致是这样:

.
├── bin              	--> 构建生成的可执行文件
├── cmd              	--> 各种服务的 main 函数入口( RPC、Web 等)
│   ├── service 
│   │    └── main.go
│   ├── web
│   └── worker
├── gen-go           	--> 根据 RPC thrift 接口自动生成
├── pkg              	--> 真正的实现部分(下面详细介绍)
│   ├── controller
│   ├── dao
│   ├── rpc
│   ├── service
│   └── web
│   	├── controller
│   	├── handler
│   	├── model
│   	└── router
├── thrift_files     	--> thrift 接口定义
│   └── interface.thrift
├── vendor           	--> 依赖的第三方库( dep ensure 自动拉取)
├── Gopkg.lock       	--> 第三方依赖版本控制
├── Gopkg.toml
├── joker.yml        	--> 应用构建配置
├── Makefile         	--> 本项目下常用的构建命令
└── README.md

分别是:

  • bin:构建生成的可执行文件,一般线上启动就是 `bin/xxxx-service`

  • cmd:各种服务(RPC、Web、离线任务等)的 main 函数入口,一般从这里开始执行

  • gen-go:thrift 编译自动生成的代码,一般会配置 Makefile,直接 `make thrift` 即可生成(这种方式有一个弊端:很难升级 thrift 版本)

  • pkg:真正的业务实现(下面详细介绍)

  • thrift_files:定义 RPC 接口协议

  • vendor:依赖的第三方库

其中,pkg 下放置着项目的真正逻辑实现,其结构为:

pkg/
├── controller    	
│   ├── ctl.go       	--> 接口
│   ├── impl         	--> 接口的业务实现
│   │	└── ctl.go
│   └── mock         	--> 接口的 mock 实现
│     	└── mock_ctl.go
├── dao           	
│   ├── impl
│   └── mock
├── rpc           	
│   ├── impl
│   └── mock
├── service       	--> 本项目 RPC 服务接口入口
│   ├── impl
│   └── mock
└── web           	--> Web 层(提供 HTTP 服务)
    ├── controller    	--> Web 层 controller 逻辑
    │   ├── impl
    │   └── mock
    ├── handler       	--> 各种 HTTP 接口实现
    ├── model         	-->
    ├── formatter     	--> 把 model 转换成输出给外部的格式
    └── router        	--> 路由

如上结构,值得关注的是我们在每一层之间一般都有 impl、mock 两个包。

640?wx_fmt=jpeg

这样做是因为 Golang 中不能像 Python 那样方便地动态 mock 掉一个实现,不能方便地测试。我们很看重测试,Golang 实现的测试覆盖率也保持在 85% 以上。所以我们将层与层之间先抽象出接口(如上 ctl.go),上层对下层的调用通过接口约定。在执行的时候,通过依赖注入绑定 impl 中对接口的实现来运行真正的业务逻辑,而测试的时候,绑定 mock 中对接口的实现来达到 mock 下层实现的目的。

同时,为了方便业务开发,我们也实现了一个 Golang 项目的脚手架,通过脚手架可以更方便地直接生成一个包含 HTTP & RPC 入口的 Golang 服务。这个脚手架已经集成到 ZAE(Zhihu App Engine),在创建出 Golang 项目后,默认的模板代码就生成好了。对于使用 Golang 开发的新项目,创建好就有了一个开箱即用的框架结构。

静态代码检查,越早越好

我们在开发的后期才意识到引入静态代码检查,其实最好的做法是在项目开始时就及时使用,并以较严格的标准保证主分支的代码质量。

在开发后期才引入的问题是,已经有太多代码不符合标准。所以我们不得不短期内忽略了很多检查项。

很多非常基础甚至愚蠢的错误,人总是无法 100% 避免的,这正是 linter 存在的价值。

实际实践中,我们使用 gometalinter。gometalinter 本身不做代码检查,而是集成了各种 linter,提供统一的配置和输出。我们集成了 vet、golint 和 errcheck 三种检查。

降级

降级的粒度究竟是什么?这个问题一些工程师的观点是 RPC 调用,而我们的答案是「功能」。

在重构过程中,我们按照「如果这个功能不可用,对用户的影响该是什么」的角度,将所有可降级的功能点都做了降级,并对所有降级加上对应的指标点和报警。最终的效果是,如果问答所有的外部 RPC 依赖全部挂了(包括 member 和鉴权这样的基础服务),问答本身仍然可以正常浏览问题和回答。

我们的降级是在 circuit 的基础上,封装指标收集和日志输出等功能。Twitch 也在生产环境中使用了这个库,且我们超过半年的使用中,还没有遇到什么问题。

anti-pattern: panic - recover

大部分人开始使用 Golang 开发后,一个非常不习惯的点就是它的错误处理。一个简单的 HTTP 接口实现可能是这样:

func (h *AnswerHandler) Get(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    loginId, err := auth.GetLoginID(ctx)
    if err != nil {
        zapi.RenderError(err)--->    return
    }

    answer, err := h.PrepareAnswer(ctx, r, loginId)
    if err != nil {
        zapi.RenderError(err)--->    return
    }

    formattedAnswer, err := h.ctl.FormatAnswer(ctx, loginId, answer)
    if err != nil {
        zapi.RenderError(err)--->    return
    }
    zapi.RenderJSON(w, formattedAnswer)}

如上,每行代码后有紧跟着一个错误判断。繁琐只是其次,主要问题在于,如果错误处理后面的 return 语句忘写,那么逻辑并不会被阻断,代码会继续向下执行。在实际开发过程中,我们也确实犯过类似的错误。

为此,我们通过一层 middleware,在框架外层将 panic 捕获,如果 recover 住的是框架定义的错误则转换为对应的 HTTP Error 渲染出去,反之继续向上层抛出去。改造后的代码成了这样:

func (h *AnswerHandler) Get(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()

    loginId := auth.MustGetLoginID(ctx)

    answer := h.MustPrepareAnswer(ctx, r, loginId)
    
    formattedAnswer := h.ctl.MustFormatAnswer(ctx, loginId, answer)

    zapi.RenderJSON(w, formattedAnswer)}

如上,业务逻辑中以前 RenderError 并直接紧接着返回的地方,现在再遇到 error 的时候,会直接 panic。这个 panic 会在 HTTP 框架层被捕获,如果是项目内定义的 HTTPError,则转换成对应的接口 4xx JSON 格式返回给前端,否则继续向上抛出,最终变成一个 5xx 返回前端。

这里提到这个实现并不是推荐大家这样做,Golang 官方明确不推荐这样使用。不过,这确实有效地解决了一些问题,这里提出来供大家多一种参考。

Goroutine 的启动

在构建 model 的时候,很多逻辑其实相互之间没有依赖是可以并发执行的。这时候,启动多个 goroutine 并发获取数据可以极大降低响应时间。

不过,刚使用 Golang 的人很容易踩到的一个 goroutine 坑点是,一个 goroutine 如果 panic 了,在它的父 goroutine 是无法 recover 的——严格来讲,并没有父子 goroutine 的概念,一旦启动,就是一个独立的 goroutine 了。

所以这里一定要非常注意,如果你新启动的 goroutine 可能 panic,一定需要本 goroutine 内 recover。当然,更好的方式是做一层封装,而不是在业务代码裸启动 goroutine。

因此我们参考了 Java 里面的 Future 功能,做了简单的封装。在需要启动 goroutine 的地方,通过封装的 Future 来启动,Future 来处理 panic 等各种状况。

http.Response Body 没有 close 导致 goroutine 泄露

一段时间内,我们发现服务 goroutine 数量随着时间不断上涨,并会随着重启容器立刻掉下来。因此我们猜测代码存在 goroutine 泄露。

640?wx_fmt=jpeg
Goroutine 数量随运行时间逐渐增长,并在重启后掉下来

通过 goroutine stack 和在依赖库打印日志,最终定位到的问题是某个内部的基础库使用了 http.Client,但是没有 `resp.Body.Close()`,导致发生 goroutine 泄露。

这里的一个经验教训是生产环境不要直接用 http.Get,自己生成一个 http client 的实例并设置 timeout 会更好。

修复这个问题后就正常了:

640?wx_fmt=jpeg
resp.Body.Close()

虽然简单几句话介绍了这个问题,但实际定位问题的步骤耗费了我们不少时间,后面可以新起一篇文章专门介绍下 goroutine 泄露的排查过程。

最后

核心业务的 Golang 化重构是由社区业务架构团队与社区内容技术团队的同学一起,经过 2018 年 Q2/Q3 的努力达成的目标。以下是两个团队的部分成员:

@姚钢强 @Adam Wen @万其平 @陈铮 @yetingsky @王志召 @柴小喵 @xlzd

640?wx_fmt=png

阅读更多精品文章,长按图片识别二维码关注「码洞」

FactoryTool量产神器实战:5分钟搞定多块RK3568板卡并行烧录 本文详细介绍了使用瑞芯微FactoryTool对多块RK3568板卡进行高效并行烧录的实战指南。文章从硬件连接、驱动配置、软件操作到排错技巧,提供了一套完整的量产解决方案,重点阐述了如何利用该工具在5分钟内完成多设备固件烧录,并确保eMMC存储的写入稳定性与效率,大幅提升生产线吞吐量。 阅读详情

相关推荐

gometalinter代码检查

简介 gometalinter工具可以检查Go代码中一些隐蔽的错误。go的编译器有的时候仅仅是能检查语法层面的错误,而不能检测出逻辑的错误。所以就需要一些额外的错误检查工具来进行代码检测。 该工具基本上集成了目前市场上所有的检测工具,然后可以并发的帮你静态分析你的代码 gometalinter 集成go语言几乎所有检测工具,静态分析代码,包含功能如下 go vet -工具可以帮我们静态分析我们的源码存在的各种问题,例如多余的代码,提前return的逻辑,struct的tag是否符合标准等。 go t

1484

Zabbix监控联想服务器XCC的SNMPv3配置实战

本文详细介绍了如何通过SNMPv3协议配置Zabbix监控联想服务器XCC的实战方法。文章从安全角度阐述了选择SNMPv3的必要性,并分步讲解了在XCC中启用SNMPv3、创建认证用户,以及在Zabbix中导入专用模板、创建主机并关联模板的完整流程,帮助运维人员实现对服务器硬件健康状态的自动化、安全监控。

weixin_29210727的博客 469

Go 包管理机制深入分析

前言 随着 Go 语言的深入使用,其依赖管理机制也一直是各位 Gopher 热衷于探讨的话题。Go 语言的源码依赖可通过 go get 命令来获取,但自动化程度不高,于是官方提供了 Dep 这样的自动化批量管理依赖的工具。虽然 Go 语言的依赖管理在很多方面还是不如人意,但整个体系正在日趋完善,本篇就将从最基本的依赖管理场景出发,一同探讨 Go 语言依赖管理的一些最佳实践。   Go 依赖管...

JFrog技术博客 1577

基于ASP.NET+SQLServer的美妆网站的设计与实现

通过本次ASP.NET MVC美妆商城项目的开发,我们实现了一个功能完善、性能稳定的在线购物平台。该项目不仅提升了用户的购物体验,也为商家提供了一个高效的商品管理和销售渠道。未来,我们将持续优化系统功能,提升用户体验,以适应不断变化的市场需求。

毕业设计大牛哥 1502

IC 时钟设计核心问题与高质量系统设计指南(附源码)

本文深入探讨了IC时钟设计的核心问题与高质量系统设计方法。时钟系统作为IC的"心脏",其性能直接影响时序稳定性、功耗效率和可靠性。文章从五个维度展开分析:时钟源选择与性能匹配、时钟分配网络设计、抖动抑制、多时钟域管理和功耗优化。针对高质量时钟系统设计,提出了时钟源优化、时钟树结构优化、干扰抑制与仿真验证等具体方法。同时,系统阐述了时钟与复位的协同设计关系,强调异步复位同步释放等技术的重要性。文章还总结了高质量IC系统设计的综合要点,包括电源/信号完整性、时序功耗平衡、可靠性设计及全流程验

Open 1062

gometalinter-golang代码质量检查分析工具

前言 最近在研究代码质量检测和分析这一个环节,当然代码质量分析是devops中持续集成部分非常重要的一个环节。涉及到团队协作的时候,很多公司会有自己的一套规则,最熟悉的是阿里巴巴的java代码参考手册,专家总结,大家按照规则去写代码。但是对于devops,有了规则远远不够,还需要提高代码检查的自动化程度,以及与其他的环节合作衔接上。那么如...

weixin_33985507的博客 902

WCK Operation (WCK 操作)

LPDDR6 SDRAM采用双时钟系统(WCK和CK),其中WCK频率是CK的两倍。为确保数据正确传输,需通过WCK2CK同步操作使两种时钟对齐。LPDDR6支持两种同步模式:Auto-sync off模式和WCK Always on模式,可通过MR22寄存器选择。同步操作由特定命令(Read/Write/MRR等)触发,需在满足特定时序参数(如tWCKENL、tWCKPRE等)后完成。同步前必须完成WCK2CK训练,且不同操作(读取/写入/CAS)有各自的时序要求。该机制确保数据在正确时钟相位下传输,是L

李家小院 657

【go】goland安装格式化插件gofmt、goimports、gometalinter常见问题

前言 先简单介绍一下这三个插件作用:gofmt、goimports、gometalinter go fmt:保存的时候自动 格式化go代码 goimports:保存的时候自动导入处理包 gometalinter:保存的时候自动检查go语法 安装 1、开启File Watchers 2、在tools里面引入这三个包 但是,goimports需要我们进行go get之后才能使用(一般去cmd中执行,我是直接在goland的Terminal中执行的): go get golang.org/x/tools/

时光、疏离了记忆づ童话的博客 6917

基于深度学习的柠檬等级检测系统 —— YOLOv8 + UI界面 + 数据集

本项目的目标是构建一个柠檬等级检测系统,通过深度学习模型自动检测柠檬的等级。自动检测柠檬等级:通过图像中的柠檬特征,自动判断其成熟度和质量。实时视频检测:通过摄像头实时监控柠檬的质量等级。用户友好的UI界面:为用户提供简洁直观的操作界面,支持图像上传、实时视频检测等功能。本项目展示了如何基于YOLOv8模型开发一个柠檬等级检测系统,结合PyQt5实现了用户友好的UI界面。通过该系统,用户可以快速上传柠檬图像并自动识别其等级,极大提升了检测效率和准确性。数据集的扩展。

YOLO 1114

某智能物流无人值守系统 ExportReport SQL注入漏洞复现

某智能物流无人值守系统 ExportReport存在SQL注入漏洞,未经身份验证的远程攻击者除了可以利用 SQL 注入漏洞获取数据库中的信息(例如,管理员后台密码、站点的用户个人信息)之外,甚至在高权限的情况可向服务器中写入木马,进一步获取服务器系统权限。

0xSec 820

Go (Golang) 工具之依赖包管理工具goimports | Go语言规范-import规范(导入)排序 |GoLand:设置gofmt与goimports,保存时自动格式化代码

goimports可以自动对代码中的依赖包进行管理,如果有用到,就会自动import,也会对没有用到的包进行自动删除。

西京刀客 1万+

Altair HyperWorks软件二次开发:二次开发成果展示与报告撰写

HyperMesh Python API允许用户通过Python脚本控制HyperMesh的大部分功能,包括模型创建、修改和分析。它提供了丰富的函数库,可以处理节点、元素、材料属性、载荷和边界条件等。二次开发可以用于创建自定义工具,这些工具可以集成到HyperWorks中,提供特定的功能或简化复杂的操作。

kkchenjj的博客 1642

微服务 - Go语言从单体服务到微服务(设计方案篇)

微服务是一种思想,与编程语言无关,编程语言是思想下具体的一种实现方式,怎么设计架构方案和实现主要看主要面临的业务场景。

stark张宇 643

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

造个 Go Web 框架的轮子!

作为一线开发 Web 服务的工程师,我用过不少语言的不少框架,尤其近几年轮子层出不穷,每次刚用熟练一个,就有更新、更好的出现了。日常疲于奔命学习新框架,一次次陷入“死循环”。说到底,We...

EDDYCJY的博客 244

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

用Go写了个 Web 框架,拿下 60k 月薪 Offer

‍作为一线开发 Web 服务的工程师,我用过不少语言的不少框架,尤其近几年轮子层出不穷,每次刚用熟练一个,就有更新、更好的出现了。日常疲于奔命学习新框架,一次次陷入“死循环”。说到底,W...

TonyBai 708

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

尝试造了这么多 Web 框架轮子,直到发现了这个……

‍‍作为一线开发 Web 服务的工程师,我用过不少语言的不少框架,尤其近几年轮子层出不穷,每次刚用熟练一个,就有更新、更好的出现了。日常疲于奔命学习新框架,一次次陷入“死循环”。说到底,...

新亮笔记 297

知识锦集分享(三)

“无状态”的那点事儿   今日头条大佬十年面试了 2000 人,总结了这 5 点!  最新的15个Android库,你了解吗?    Hystrix 停止开发。。。Spring Cloud 何去何从?   摸着石头过河:知乎核心业务 Go 语言改造实践    面试专栏|一篇文章搞定ArrayList和LinkedList所有面试问题  用Python告诉你,现在的房租有多高?    美团容器平台架...

qswm_18301240090的博客 327

k8s如何学习?掌握Go Web 框架是关键

作为一线开发 Web 服务的工程师,我用过不少语言的不少框架,尤其近几年轮子层出不穷,每次刚用熟练一个,就有更新、更好的出现了。日常疲于奔命学习新框架,一次次陷入“死循环”。说到底,Web...

weixin_38320674的博客 445

如何从零开始写一个 web 框架?

‍作为一线开发 Web 服务的工程师,我用过不少语言的不少框架,尤其近几年轮子层出不穷,每次刚用熟练一个,就有更新、更好的出现了。日常疲于奔命学习新框架,一次次陷入“死循环”。说到底,We...

梦醒人间 2020

自己动手写了个 Web 框架,我膨胀了

‍作为一线开发 Web 服务的工程师,我用过不少语言的不少框架,尤其近几年轮子层出不穷,每次刚用熟练一个,就有更新、更好的出现了。日常疲于奔命学习新框架,一次次陷入“死循环”。说到底,We...

小詹学python的博客 357

LangGraph多智能体通信协议:消息格式与序列化机制详解

本文将从「问题背景→核心概念→LG-MACP的设计逻辑→内置消息格式详解→序列化机制深度对比(JSON/Pickle/Protobuf/MessagePack)→自定义通信协议的实现→跨语言通信的实践方案→性能优化→生产案例→最佳实践→未来趋势」这12个维度,用10000+字的篇幅、10+个完整的代码示例(Python/Go)、3个mermaid架构图、2个数学模型、1个性能测试对比表、1个发展历史对比表,带你从零到一、从浅到深地掌握LG-MACP的所有核心知识点,彻底解决多智能体应用开发中的通信痛点。

大数据洞察的博客 261

Java自动化环境配置:从Spring Boot到K8s的工程化实践

环境配置是软件工程中的基础概念,它决定了应用在不同运行环境下的行为一致性。其核心原理在于将可变配置与业务代码分离,通过外部化、分层加载机制实现灵活管理。这一实践的技术价值在于保障自动化任务的稳定执行、提升团队协作效率,并支撑持续集成与交付流程。在应用场景上,从本地开发、测试到生产部署,再到容器化与云原生环境,都需要针对性的配置策略。本文聚焦于Java自动化项目,深入探讨如何利用Spring Boot Profile、Docker及Kubernetes的ConfigMap与Secret等机制,系统化解决环境一

culiao6493的博客 515

【粉丝福利社】一本书讲透数据指标体系

本文主要探讨数据指标体系的顶层设计方法论及其在企业中的应用。以某制造业企业为例,展示了业务域分层架构设计模式,强调指标体系需与组织架构、管理战略相匹配。文章指出,优秀的数据指标体系应遵循"方向大致正确"原则,采用"核心指标-关键指标-操作指标"三级分层设计,通过自顶向下拆解与自底向上反馈实现业务优化。作者提出数据指标体系80%的应用场景集中于数据看板等分析类应用,并推荐由业务与技术复合型人才主导设计。最后介绍了《一本书讲透数据指标体系》的核心价值,包含三大行业实战案例、四大落地困境解决方案及AI融合前瞻视角

愚公智库 3万+
上一篇: 《快学 Go 语言》第 10 课 —— 错误与异常
下一篇: 一位工作了 10 年的 Java 高级架构师的技术之路
codehole_
博客等级 码龄7年 34粉丝 132原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值