采集插件需要增量采集,避免重复抓取、重复入库、浪费性能,你在 Go 里怎么做去重?

Redis Bitmap/ 布隆过滤器

对比项 Redis Set Redis Bitmap 布隆过滤器 (本地 / RedisBloom)
准确性 100% 精准,无误判 100% 精准,无误判 概率型:不存在 = 一定不存在,存在 = 可能误判,误判率可控 (0.01%~1%)
存储数据 完整存原始字符串 (URL / 字符串) 只能存数字下标(偏移量),无法直接存字符串 不存原始数据,多次 hash 映射 bit 位,只存标记位
内存开销 极大,单条 URL≈40~60Byte;1000 万 URL≈500MB+ 稠密数据极省:1 亿 bit≈12MB;稀疏数据巨浪费(下标离散空出大量 bit) 极致节省:100 万数据、0.01% 误判≈2.3MB,单元素仅十几 bit,是 Set 的 1/200 左右
增删能力 支持新增、删除、随机剔除成员 支持 SETBIT/GETBIT/DEL,单个 bit 可清零删除 标准布隆不支持删除(计数布隆除外,空间翻倍)
查询复杂度 O(1),SISMEMBER O(1) GETBIT O (k),k = 哈希函数数量 (7~14),固定耗时,不随数据量变大而变慢
数据格式限制 任意字符串:URL、手机号、文本都行 必须转为数字 ID,不能直接存 URL 任意字符串,自动 hash 转下标,无需人工编码 ID

四次挥手:断开 TCP 连接(全双工,两端可单独关发送)

标志位:FIN(结束)、ACK(确认),任意一方都能主动发起关闭,以客户端主动关闭举例:

第一次挥手:C→S:FIN=1

客户端不再发数据,发 FIN 报文,C 进入FIN_WAIT_1。
我不再发数据了。

第二次挥手:S→C:ACK=1

服务端回复 ACK 确认关闭客户端发送通道,S 进入CLOSE_WAIT;
此时服务端仍能向客户端发剩余数据,客户端收到 ACK 进入FIN_WAIT_2。
收到关闭请求,我不再收你的数据,但我还有数据可以发给你。

第三次挥手:S→C:FIN=1

服务端数据全部发完,发送 FIN,S 进入LAST_ACK。
我的数据发完了,我也要关闭发送。

第四次挥手:C→S:ACK=1

客户端回复 ACK,进入TIME_WAIT(等待 2MSL);
服务端收到 ACK 立刻CLOSED;客户端等待 2MSL 后无报文到达,最终关闭。
收到,连接彻底关闭。

三次握手两次行不行?四次挥手三次行不行?

一、为什么三次握手不能改成两次?

两次握手流程(假想)

  1. 客户端发 SYN
  2. 服务端回复 SYN+ACK,连接直接建立

致命问题:历史滞留 SYN 报文

客户端之前失效、迟到的旧SYN包延迟到达服务器:

  • 服务器收到SYN → 立马回复SYN+ACK、建立连接、分配端口资源
  • 客户端早已断开,无视这个ACK,不会收发数据
  • 服务器一直阻塞占用资源,空等数据,造成资源浪费

三次握手的作用:
客户端第三次ACK用来确认服务端收到连接,只有客户端确认后,服务端才正式建连。迟到的旧SYN到达后,客户端不会回复ACK,服务器收不到第三次报文,自动释放连接,避免无效连接。

两次无法校验客户端是否在线、报文是否过期

二、四次挥手能不能简化成三次?

特殊场景可以三次,正常大多必须四次
TCP是全双工通信,收发通道互相独立:A关发通道≠B不能继续发数据。

1. 常规四次(标准情况)

  1. C发FIN(C不再发数据)
  2. S回ACK(确认收到关闭,S还能继续发剩余数据)
  3. S数据发完再发FIN(S也不再发数据)
  4. C回ACK

第2、3步不能合并:中间存在服务端遗留数据传输时间,必须分开,所以是4次。

2. 可以变成3次挥手的特例

服务端收到FIN时,恰好数据全部发送完毕
服务端可以把「ACK确认 + FIN关闭」合并成一个报文:

  1. C→S:FIN
  2. S→C:ACK+FIN(合并第二条、第三条报文)
  3. C→S:ACK
    👉 此时就变成三次挥手,属于特例,不是通用规范。

三、高频面试小结

  1. 握手必须三次,两次绝对不行:防冗余SYN造成服务端僵死连接。
  2. 挥手默认四次,极端情况三次可行:只有服务端无剩余数据时才能合并ACK与FIN。

补充考点:TIME-WAIT为什么必须等2MSL

  1. 保证最后一个ACK丢包时,被动关闭方能重发FIN;
  2. 等待网络中旧报文全部过期,避免新连接收到历史残包。

WebSocket vs HTTP

1. 连接模型本质

HTTP:短连接 / 一问一答(单向)

  • 请求→响应,连接用完就断(HTTP1.1长连接只是复用TCP,依旧一问一答)
  • 只能客户端主动发请求,服务器不能主动推送数据

WebSocket:长连接全双工

  • 先通过一次HTTP握手升级协议,后续全程复用同一条TCP连接
  • 客户端、服务端任意一方随时主动发消息(服务端可主动推送)

2. 通信流程

  1. HTTP
    客户端发请求 → 服务器返回数据 → TCP断开/闲置断开
    实时场景(聊天、推送)只能用轮询/长轮询,频繁发HTTP请求,开销大。

  2. WebSocket
    ① 客户端发HTTP GET带升级头(Upgrade:websocket)握手
    ② 服务端返回101 Switching Protocols,协议切换成功
    之后全双工二进制/文本传输,不再走HTTP协议

3. 头部开销

  • HTTP:每次请求都带 Cookie、Header,头部冗余大
  • WebSocket:握手后报文头极短(2~10字节),传输成本极低

4. 适用场景

HTTP

普通接口查询、静态资源下载、表单提交(一次性请求

WebSocket

聊天室、实时大屏、股票行情、IM聊天、设备实时上报(需要服务端主动推送

5. 版本补充

  • HTTP/2 支持服务端推送,但仍是基于请求模型,做不到任意时刻自由互发消息,替代不了WS。

DNS、DHCP、ARP、ICMP 简明区分

1. ARP(地址解析协议)

链路层→网络层过渡,局域网内网
作用:IP地址 ↔ MAC地址互相转换

  • 广播发ARP请求:已知IP,查对方网卡MAC
  • 单播ARP应答:回复本机MAC
    工作环境:同一个局域网,跨路由无效。

2. ICMP(网际控制报文协议)

网络层协议,依附IP承载
作用:差错报告、网络连通探测
典型应用:ping、traceroute(路由追踪)

  • 目标不可达、超时、路由异常全靠ICMP上报。

3. DNS(域名系统)

应用层
作用:域名→IP解析www.baidu.com → 服务器IP)
客户端发DNS查询→DNS服务器返回IP,之后才建立TCP连接。

4. DHCP(动态主机配置协议)

应用层(UDP 67/68端口)
作用:自动给终端分配IP、子网掩码、网关、DNS地址
四步流程:DISCOVER→OFFER→REQUEST→ACK
电脑插网线自动拿IP就是DHCP。

层级

  • 二层附近:ARP
  • 网络层:ICMP
  • 应用层:DHCP、DNS

sync.map 底层

type Map struct {
mu Mutex // 互斥锁,仅保护 dirty map
read atomic.Value // 只读 map(readOnly 结构体),无锁访问
dirty map[any]*entry // 脏 map,包含最新数据,需要加锁访问
misses int // 读未命中计数器,触发 dirty 升级为 read
}

type readOnly struct {
m map[any]*entry
amended bool // true:dirty 有 read 没有的新数据
}

协程泄露

启动了一个 goroutine,但它永远无法退出、一直存活在后台,占用内存和资源不释放

redis防止雪崩

设置 TTL 时在基础过期时间上 + [1~30min]随机秒数,打散过期高峰,避免同一批淘汰
永不过期策略
热点数据不设置过期时间,靠主动更新 / 淘汰机制淘汰。
定时任务主动刷新热点缓存
在缓存过期前,后台异步主动更新缓存,避免过期空窗。
高可用架构:主从 + 哨兵 Sentinel / Redis Cluster 集群
主节点挂了自动故障切换,从顶替上位,缓存不整体瘫痪。
多级缓存(本地缓存 Caffeine/Guava + Redis)
应用 JVM 本地缓存做兜底,Redis 崩了优先走本地缓存,不会直达 DB。
限流 + 熔断 + 降级(Sentinel、Resilience4j)
限流:限制落到 DB 的 QPS
熔断:DB 压力过大直接熔断,返回兜底数据,不再查库
降级:非核心接口直接返回默认值
DB 层:分库分表、连接池限流
数据库连接池设合理最大值,防止连接被打满。