mcp与skill

如果把mcp比作车子,那么skill就是如何骑车

Function Calling(函数调用) 是大模型的一项能力,指大语言模型根据用户提问,主动识别意图、选择并调用外部工具 / 预设函数,把自然语言指令转为结构化函数调用请求,执行后再整合结果返回给用户。

Function Calling 是底层通信能力 / 协议,Skill 是上层封装的业务能力 / 功能集合,二者是「实现方式」和「业务载体」的关系。

1. Function Calling(函数调用 / 工具调用)

本质:技术协议、调用机制
作用:让大模型按约定格式(JSON)输出函数名 + 参数,对接外部代码 / 接口,是通用底层能力。
粒度:最小执行单元 = 单个函数 / 接口。
范围:纯技术层,和业务无关,所有模型 / 框架通用。

2. Skill(技能)

本质:面向用户的业务功能、能力包
作用:给用户提供完整服务,是产品 / 业务层面的抽象。
粒度:一个 Skill 可包含多个函数、多轮调用、逻辑编排。
范围:业务层,平台 / 产品自定义(如天气技能、计算器技能、点餐技能)

TCP链接建立为什么是三次握手不是两次?

两次只能证明「客户端发得出、服务端收得到」,无法验证服务端发得出、客户端收得到,会造成服务端资源浪费。

TCP关闭链接的四次握手,部分情况能合三次

TCP 断开是双向关闭,默认一方先主动关闭,分两个独立方向收尾。
标准流程(客户端主动断开)

1. 第一次挥手:客户端 → 服务端

客户端发 FIN
含义:客户端不再发送新数据,请求关闭「客户端→服务端」方向。

2. 第二次挥手:服务端 → 客户端

服务端发 ACK
含义:收到你的关闭请求。
关键点:此时服务端还能继续向客户端发剩余数据,通道没彻底关。

3. 第三次挥手:服务端 → 客户端

当服务端所有数据都发送完毕后,发 FIN
含义:服务端也不再发数据,请求关闭「服务端→客户端」方向。

4. 第四次挥手:客户端 → 服务端

客户端发 ACK
含义:收到,双方彻底断开连接。
能合是极端情况,大部分时候还是四次挥手
客户端发 FIN 后,服务端恰好没有任何剩余数据
那么服务端可以把 ACK + FIN 合并成一个报文,此时会变成三次挥手。

TCP 协议在完成四次挥手后是直接断开吗,要等待多久才断开

客户端主动关闭为例,走完四次挥手后分两端状态,重点讲等待机制、时长、作用。

一、完整状态流转(客户端主动断连)

角色:主动方(客户端)被动方(服务端)

  1. 客户端 → 服务端:FIN(第一次挥手)
  2. 服务端 → 客户端:ACK(第二次挥手)
  3. 服务端 → 客户端:FIN(第三次挥手)
  4. 客户端 → 服务端:ACK(第四次挥手)

两端最终行为

  • 被动方(服务端):收到第四次挥手的 ACK立即关闭连接,进入 CLOSED 状态,无等待。
  • 主动方(客户端):发出第四次挥手的 ACK 后,不会马上关闭,进入 TIME_WAIT 状态,必须等待一段时间。

二、TIME_WAIT:等待多久?

标准规定:等待 2MSL

  • MSL(Maximum Segment Lifetime):报文段最大生存时间,RFC 标准定义 MSL = 2 分钟
  • 所以 2MSL = 4 分钟(240 秒)

补充:实际操作系统会做优化,Linux 默认 MSL 设为 30s,对应 2MSL = 60 秒

等待结束后,主动方才进入 CLOSED,连接彻底释放。


三、为什么一定要等 2MSL?(两个核心目的)

1. 确保最后一个 ACK 报文能被对方收到

第四次挥手的 ACK 是主动方发的最后一个包:

  • 如果这个 ACK 丢失,被动方(服务端)会超时重发 FIN
  • 主动方处在 TIME_WAIT 阶段,收到重传的 FIN 后,会重新补发 ACK
  • 若不等、直接关闭,主动方端口已释放,无法回应重传的 FIN,被动方会一直重传、资源无法释放。

2. 防止「残留旧报文」干扰新连接

网络中可能存在延迟的老旧 TCP 报文,会在链路里游荡:

  • 2MSL 时长足以保证本连接所有旧报文全部从网络中消失
  • 避免同一端口后续新建连接时,收到上一次连接的过期数据,造成数据错乱。

四、补充常见状态 & 易错点

  1. CLOSE_WAIT
    被动方收到 FIN、回复 ACK 后进入此状态,代表本地还有数据要发,发完才会发 FIN。如果大量 CLOSE_WAIT 堆积,一般是应用层没调用关闭套接字(代码漏关连接)。

  2. 端口复用问题
    主动方端口在 TIME_WAIT 期间被占用,短时间内无法立刻用同端口新建连接;高并发服务常通过调整内核参数(如 tcp_tw_reuse)优化。

  3. 反向场景:服务端主动断开
    逻辑完全一致:主动断开的一端进入 TIME_WAIT 等待 2MSL,被动端收到 ACK 直接关闭


总结

  1. 四次挥手后:被动端立刻断开,主动端进入 TIME_WAIT 等待
  2. 标准等待时长:2MSL(标准4分钟,Linux 常用60秒);
  3. 等待原因:保证最后 ACK 可靠送达、清空网络残留旧报文。

IPC(进程间通信)

1. 管道(Pipe)

无名管道:亲缘进程(父子进程)专用
半双工(单向通信)
简单、系统自带

2. 命名管道(FIFO)

有名字,任意进程都能用
半双工
以文件形式存在

3. 消息队列(Message Queue)

消息链表,按消息格式收发
全双工、独立于进程
支持优先级、消息类型

4. 共享内存(Shared Memory)

最快的 IPC
多个进程直接访问同一块内存
缺点:需要自己加锁(信号量)控制同步

5. 信号量(Semaphore)

不是传数据,是锁、同步机制
控制多进程临界区访问
防止并发冲突

6. 信号(Signal)

软中断,简单通知机制
例:kill -9、Ctrl+C
只能传信号,不能传大量数据

7. 套接字(Socket)

跨主机通信
最通用、网络编程必备
支持 TCP/UDP

8. 内存映射(mmap)

将文件映射到内存
进程读写内存 = 读写文件
大文件、高并发常用

Go Map 真删除 vs 假删除

Go 语言里 delete(map, key) 是真正的物理删除,会直接把键值对从内存中移除;而假删除(软删除) 是业务层的逻辑删除,不会真正移除数据,只是标记为「已删除」,查询时过滤掉标记数据即可。

下面分两部分讲清楚:原生真删除 + 业务假删除(核心)


一、Go Map 原生真删除(delete 内置函数)

这是 Go 官方提供的物理删除,直接清除内存中的键值对,释放空间。

用法

1
delete(你的map, 要删除的key)

特点

  1. 无返回值:即使 key 不存在,delete 也不会报错
  2. 并发不安全:多协程操作必须加锁
  3. 真正释放:键值对彻底从 map 中消失

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

func main() {
user := map[string]int{
"张三": 20,
"李四": 22,
}

// 真删除:直接移除 "张三"
delete(user, "张三")

fmt.Println(user) // 输出:map[李四:22]
}

二、Go Map 假删除(软删除 / 逻辑删除)

假删除 = 不删数据,只打删除标记
适用场景:需要保留历史数据、审计日志、恢复数据的业务。

实现方案

把 map 的值定义为结构体,增加一个 Deleted bool 删除标记:

  • 标记 Deleted: true = 已删除(假删除)
  • 标记 Deleted: false = 正常数据
  • 查询/遍历只返回未标记的数据

完整代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import "fmt"

// 定义带删除标记的结构体
type Value struct {
Data int // 真实业务数据
Deleted bool // 删除标记:true=假删除,false=正常
}

func main() {
// 定义带标记的 map
m := map[string]Value{
"a": {Data: 10, Deleted: false},
"b": {Data: 20, Deleted: false},
}

// ========== 1. 假删除:只打标记,不真正删除 ==========
key := "a"
val, exists := m[key]
if exists {
val.Deleted = true // 标记为已删除
m[key] = val
}

// ========== 2. 遍历:只查询未被假删除的数据 ==========
fmt.Println("有效数据:")
for k, v := range m {
if !v.Deleted { // 过滤掉标记为删除的
fmt.Printf("%s: %d\n", k, v.Data)
}
}

// ========== 3. 查看原始 map(数据还在,只是被标记) ==========
fmt.Println("\n完整 map(包含假删除数据):")
fmt.Println(m)
}

输出结果

1
2
3
4
5
有效数据:
b: 20

完整 map(包含假删除数据):
map[a:{10 true} b:{20 false}]

假删除核心优势

  1. 数据可恢复:把 Deleted 改回 false 就恢复
  2. 保留历史:不会丢失原始数据
  3. 业务可控:查询、统计时自主过滤删除数据
  4. 无内存释放:数据仍在 map 中,只是被标记隐藏

三、真删除 vs 假删除 对比

特性 真删除(delete 假删除(标记删除)
数据是否消失 是,物理移除 否,保留在 map 中
内存是否释放
能否恢复 不能 能(修改标记)
实现方式 内置 delete() 函数 结构体 + Deleted 标记
使用场景 无需保留的临时数据 需留痕、可恢复的业务数据

总结

  1. Go 原生没有假删除delete(map, key)真·物理删除
  2. 假删除是业务逻辑:用结构体+删除标记实现
  3. 真删除适合清理无用数据;假删除适合需要保留历史、可恢复的数据
  4. 假删除的核心:标记为已删除 + 查询时过滤

回表查询

普通索引 只存 索引列的值 + 主键 ID
你查询时,数据库先在索引树找到目标数据的主键 ID
然后拿着这个 ID 去聚簇索引(原表数据) 查一遍,拿到完整行数据
第 3 步就是回表