提示
不要用传统开发的思维来理解 智能 Agent 的开发,也不要用传统框架的思想来理解 Agent 框架。
1. 前言
想要看明白 Eino 的源码,需要三个前置工作:
- 有一定的 LLM 项目开发经验,对于常见的关键词有一定的了解。
- 十分熟悉 Go 语言,对于泛型、接口、并发等有深刻的认识。
- 对于软件工程、代码架构等有一定的了解。
提示
不要用传统开发的思维来理解 智能 Agent 的开发,也不要用传统框架的思想来理解 Agent 框架。
想要看明白 Eino 的源码,需要三个前置工作:
缓存雪崩是分布式系统中常见的缓存失效问题,指的是 大量缓存数据在同一时间段内集中失效或缓存服务整体不可用,导致原本由缓存承担的请求压力瞬间转移到后端数据库或服务,造成数据库负载过高甚至崩溃,最终引发系统级故障的现象。
重要
就是缓存失效,请求大量压到了 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。它能够解决部分场景,但仍然有一些问题未能解决:
假如,我们正在请求一个接口,获取用户的个人信息,这个接口需要1秒钟的时间进行数据查询,回溯,拼接等逻辑,然后再返回给我们。在这一秒钟内,服务因为某些原因,出BUG了,Panic了,那么服务可能会爆出500的错误。如果是被人为的关闭了,会发生什么?应该发生什么?
相关信息
这是架构领域的一道经典面试题。解决方案就是:优雅关闭。
package main
import (
"github.com/gin-gonic/gin"
"strings"
"fmt"
"net/http"
)
func main() {
r := gin.Default()
r.Use(Cors()) //开启中间件 允许使用跨域请求
r.run()
}
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin") //请求头部
if origin != "" {
//接收客户端发送的origin (重要!)
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
//服务器支持的所有跨域请求的方法
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
//允许跨域设置可以返回其他子段,可以自定义字段
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
// 允许浏览器(客户端)可以解析的头部 (重要)
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers")
//设置缓存时间
c.Header("Access-Control-Max-Age", "172800")
//允许客户端传递校验信息比如 cookie (重要)
c.Header("Access-Control-Allow-Credentials", "true")
}
//允许类型校验
if method == "OPTIONS" {
c.JSON(http.StatusOK, "ok!")
}
defer func() {
if err := recover(); err != nil {
log.Printf("Panic info is: %v", err)
}
}()
c.Next()
}
}