提示
不要用传统开发的思维来理解 智能 Agent 的开发,也不要用传统框架的思想来理解 Agent 框架。
1. 前言
想要看明白 Eino 的源码,需要三个前置工作:
- 有一定的 LLM 项目开发经验,对于常见的关键词有一定的了解。
- 十分熟悉 Go 语言,对于泛型、接口、并发等有深刻的认识。
- 对于软件工程、代码架构等有一定的了解。

提示
不要用传统开发的思维来理解 智能 Agent 的开发,也不要用传统框架的思想来理解 Agent 框架。
想要看明白 Eino 的源码,需要三个前置工作:
我之前的昵称是Porco1Rosso,名字出自宫崎骏的《红猪》,男人三十一头猪嘛。当然,你也可以叫我老秦。
我来自河南洛阳,小时候在农村长大。早上五六点钟,我爷爷就摇醒我,然后摇醒他的单缸手扶拖拉机,下地干活儿。这个拖拉机啊,便宜实惠,皮实耐操,扩展性特别好,换上不同的挂件儿,可以犁地,翻土,起陇,换个货斗还可以拉货,深受广大劳动人民喜爱。它可比公司的中间件好用太多了,反正我是从来没见我爷爷嫌它难用,骂他是一坨屎。
到了高中的时候,最喜欢的事是周末去考古工地玩儿。那时候,我长得又高又壮,在工地实习的大学生们也爱带着我,扛着洛阳铲满山跑。也可能是这段探墓的经历,让我在业务排障上有一些特别的优势,在上家公司挖掘了各种细枝末节的线索,还原业务场景,避免了百万损失。
缓存雪崩是分布式系统中常见的缓存失效问题,指的是 大量缓存数据在同一时间段内集中失效或缓存服务整体不可用,导致原本由缓存承担的请求压力瞬间转移到后端数据库或服务,造成数据库负载过高甚至崩溃,最终引发系统级故障的现象。
重要
就是缓存失效,请求大量压到了 DB 上导致的服务崩溃。
雪崩跟击穿最大的区别:击穿是个别 Key 过期,雪崩是大量 Key 过期。导致雪崩的问题有很多,我们可以捋一捋:
在 Redis 中,Hotkey(热点键)是指在一段时间内,被大量客户端频繁访问的键。例如,在一个电商系统的秒杀活动中,代表秒杀商品库存的 Redis 键就可能是一个 Hotkey,因为众多用户会同时频繁地查询和更新这个键的值。
HotKey存在两个问题。第一个是缓存过期导致的穿透问题,突发的大流量可能会压垮数据库,进而拖累整个服务。另一个问题是,突发的大流量可能会给缓存带来风险,虽然Redis的性能很好,也是有上限的。当然,HotKey还有一些其他的问题,像是数据不一致啊,分片流量过高等。
HotKey问题的解决方案还是比较简单的,它的本质就是一个穿透问题。使用多级缓存,或者单飞都可以解决。HotKey的真正难点在于发现哪个Key是HotKey,只要发现的足够及时,就有多种办法解决问题。由HotKey引发的事故,多数情况下都是发现不及时,引发的。
缓存击穿是指某个数据暂时不在缓存里,需要回源到数据库读取,结果同一时间出现了大量的请求压在了 DB 上,进而导致数据库异常拖垮整个服务。
如果你还有印象,我们在讲”读更新、写删除“的时候,就明确说过,这个方法好用,但是存在击穿问题。
参考下图:

警告
以下策略有一个大前提:业务场景是读操作远远多于写操作。
租约机制是斯坦福大学在 1989 年提出来的一种方案,主要是为了解决分布式系统中缓存一致性问题的。没错,我们现在使用的技术方案,又是上世纪 90 年代出现的。
在分布式系统中,缓存的使用虽然能提高系统性能,但也带来了数据一致性的挑战。一方面,缓存的存在引入了确保数据一致性的开销和复杂性,降低了部分性能优势。另一方面,分布式系统中的通信延迟、网络故障以及主机故障等问题,使得缓存数据的一致性维护变得更加困难。例如,当多个客户端同时缓存了同一数据,而服务器端的数据发生变化时,如何及时、有效地通知客户端更新缓存,以保证数据的一致性,就是一个亟待解决的问题。
一般成规模的公司,普遍采用的方法。
Binlog 是 MySQL 数据库中的一种二进制日志文件,它记录了数据库中所有的更改操作,包括数据的插入、更新、删除以及数据库结构的修改等。它有一个重要的作用是同步数据,做主从复制,当然了同步数据嘛,也不局限于 MySql,像 ES 啊也是可以做的。
它的格式有很多种,一般用作同步数据的话,会采用 Row 格式。Row 格式则是记录了每一行数据的更改前后的具体内容。比如对于一个更新操作,它会记录更新前的整行数据和更新后的整行数据。这样可以确保主从复制的准确性,避免因 SQL 语句执行环境不同而导致的数据不一致问题,但缺点是日志文件会比较大,因为要记录每一行的详细数据。
go 1.24版本之后,对于路由这一块进行了升级改造,但是本质没有大的变化,大家可以搜索看下。
正如我们之前学过的,Go语言搭建一个服务器非常简单,只需要用到几个方法:
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
_, _ = fmt.Fprintf(writer, "关注 香香编程喵喵喵,关注香香编程谢谢喵喵喵!")
})
panic(http.ListenAndServe(":8080", nil))
什么是面向切面编程AOP? - 知乎
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于将横切关注点(Cross-cutting Concerns)与主要业务逻辑分离。横切关注点是指那些存在于应用程序中多个不同部分的功能,例如日志,鉴权等,它们跨越多个模块和对象。
go1.7是一个变动非常大的版本。一方面官方引入了Context来管理多个具有明显父级关系的GoRoutine,一方面对原有的代码包进行了大量的修改,让他们都能够支持Context。
我们之前已经讲过一个用来管理,编排多个GoRoutine的包sync.WaitGroup。它能够解决部分场景,但仍然有一些问题未能解决:
相关信息
希望你经过一段时间的学习,有一副人情练达的肉体和一个勇往无前的灵魂。
这是一个互联网十年老狗的一些总结,主要是帮助为大三,大四以及想要入门的同学快速了解一下GO语言作为后端主力开发语言的发展情况,以及成为初级开发工程需要掌握的基础技能,顺便帮助你快速了解互联网开发的基本面,为之后的工作奠定一个可靠的基础。
如果你学过软件工程的话,应该会了解设计模式是什么,用来干什么。简单的讲,设计模式是经验总结,是针对某些特定场景的解决方案。1995年,GOF提出了23种设计模式,在实际开发中使用到的种类非常少。设计模式有一个严肃的问题:增加代码量,降低代码可读性。当然了,这也是为了实现高内聚,低耦合付出的代价,想要得到一些,就得失去一些呗。
真正的灾难往往不是灾难本身,设计模式的灾难百分之九十九是开发人员的滥用。很多初学者,总是想把刚学会的设计模式用到自己的代码中,完全不考虑当前业务场景是否合适。于是,灾难悄无声息的酝酿起来了。
本文为极客时间《Go语言核心36讲》的学习笔记,梳理了相关的知识点。
当我们需要对一个程序进行性能分析的时,通常只有两种情况:
第一种,这个程序是个重要模块,上线后要迎接大流量场景者是支持核心业务线。不能出问题,出了问题就是大问题,今年的绩效就要玩砸了。第二种,程序已经出了性能问题。
第一种情况,需要我们大范围的进行压测,通过循序渐进的方式获取程序的性能瓶颈,为后续的技术优化和应急方案提供详细的指标和场景。比如:把QPS压到1W时,CPU、内存、响应耗时和协程数等指标具体是多少。后续,以此为参考制定优化指标和方案。
第二种情况,需要我们能够快速定位问题原因,尽快修复,减少损失。但,这是理论上。正常线上出现性能问题,应该先执行上文提到的应急方案,减小损失,保留现场。等危机解除,服务稳定后再分析程序的性能问题。
我们常常会遇到一些业务场景,需要我们每隔一段时间执行以下代码。比如:
这种业务场景,就需要我们用到定时器。最常用的定时器是linux下的crontab,之前使用PHP或者Python的时候,就是通过定义crontab,定时执行一条脚本语句实现定时任务。
提示
引申,熟悉下crontab。它仍然是常用的定时任务方案,绝大多数的自用小工具,都会使用。
本文为极客时间《Go语言核心36讲》的学习笔记,梳理了相关的知识点。
Go语言做网络开发是非常容易的一件事,它已经为我们封装好了Http包,开箱即用。除此之外,我们也可以用Gin框架或者使用fasthttp等三方包,快速搭建一个Web服务。但是,越是封装的方便,我们越是容易忽略底层的一些知识点。
我们这里先补充两个必要的知识:网络分层和进程通信。
这块知识属于计算机网络,可以直接去看书。
本文为极客时间《Go语言核心36讲》的学习笔记,梳理了相关的知识点。
通常情况下,我们是肯定不会直接起一个协程开始写逻辑代码的。Go语言已经帮我们写好了各种常用的并发工具,基本都在 sync(同步) 包中,可以开箱即用。
//正常情况下,是不会这样子直接起一个协程做并发业务的。
go func(){
....
}
这一块内容我们只简单提一下,不会展开。相关问题可以直接在网上找资料,非常全面。
在讨论并发之前,必须得先讲清楚并发和并行,以及他们两个之间的区别,还有各自会有哪些常见的问题。这两个名词都是操作系统里的概念:
相关信息
本文为极客时间《Go语言核心36讲》的学习笔记,梳理了相关的知识点。
通常 panic 和 recover 是用来处理异常问题的。我们来综述下,他们各自的特点:
本文为极客时间《Go语言核心36讲》的学习笔记,梳理了相关的知识点。
接口(硬件类接口)是指同一计算机不同功能层之间的通信规则称为接口。
接口(软件类接口)是指对协定进行定义的引用类型。其他类型实现接口,以保证它们支持某些操作。接口指定必须由类提供的成员或实现它的其他接口。与类相似,接口可以包含方法、属性、 索引器和事件作为成员。
从根本上讲:接口是一个中间层,使得调用和实现完全分离,解除了上下游的耦合,调用方不再依赖于某一个具体的模块,而是依赖于一个接口。举个例子: