0%

AI RAG

后续团队需要做企业大脑,会用到RAG,先比较完整的了解下RAG,把零碎知识补的完整一些,做个记录。

### 标准RAG(Retrieval-Augmented Generation)的运行流程图

标准RAG系统的运行流程可以分为两大阶段:索引阶段(Indexing Phase)(预处理数据)和运行时阶段(Runtime Phase)(查询处理)。以下是详细的流程描述,我会结合常见的流程图示例来说明。RAG的核心是先从外部知识库中检索相关信息,然后用这些信息增强LLM的生成,以减少幻觉并提供更准确的回答。

这个流程图展示了RAG的高层结构:从数据源到最终输出的完整路径。

1. 索引阶段(Indexing Phase):预构建知识库

这一阶段发生在系统启动前,用于将外部数据转化为可检索的形式。通常是离线过程,目的是创建向量数据库以支持高效查询。

  • 步骤1: 数据采集(Data Ingestion)
    从各种来源(如文档、数据库、网页)收集原始数据。这些数据可以是结构化(e.g., SQL表)或非结构化(e.g., PDF、文本文件)。
  • 步骤2: 数据分块(Chunking)
    将长文档切分成小块(chunks),通常每个chunk 100-500 tokens,以匹配嵌入模型的输入限制。分块策略包括固定长度、按句子或语义分块(e.g., 使用LLM检测自然断点)。
  • 步骤3: 嵌入生成(Embedding Generation)
    使用嵌入模型(如BERT、OpenAI的text-embedding-ada-002)将每个chunk转化为向量表示(dense vector,e.g., 768维)。这捕捉语义相似性。
  • 步骤4: 索引存储(Indexing and Storage)
    将向量和对应的原始chunk存储到向量数据库(Vector DB,如FAISS、Pinecone、Milvus)。同时可能添加元数据(如来源、时间戳)。索引使用近似最近邻(ANN)算法如HNSW来加速检索。

这一阶段的输出是一个可查询的向量数据库。

这个图更详细地展示了索引和检索的子步骤,包括分块、嵌入和重排序。

2. 运行时阶段(Runtime Phase):实时查询处理

这是用户交互时的核心流程,对应你之前描述的“检索 + 生成”。

  • 步骤1: 用户输入(User Query Input)
    用户提供查询(query),e.g., “什么是RAG?”。
  • 步骤2: 查询嵌入(Query Embedding)
    使用相同的嵌入模型将query转化为向量。
  • 步骤3: 检索(Retrieval)
    在向量数据库中计算query向量与所有chunk向量的相似度(e.g., 余弦相似度)。返回Top-K个最相似的chunks(原始文本 + 元数据)。可选:重排序(Re-ranking)使用另一个模型(如Cross-Encoder)进一步过滤,提高相关性。
  • 步骤4: 提示构建(Prompt Construction)
    将检索到的Top-K chunks(原始文本)与query拼接成一个Prompt。模板示例:
    1
    2
    3
    4
    系统提示: 你是一个助手,使用以下上下文回答问题。
    上下文: [chunk1] [chunk2] ... [chunkK]
    问题: [query]
    回答:
    注意:这里不直接传递向量,只传递文本。Prompt长度需控制在LLM上下文窗口内(e.g., 8K-128K tokens)。
  • 步骤5: 生成(Generation)
    将Prompt输入LLM(e.g., GPT-4、LLaMA),LLM基于Prompt生成回答。生成过程使用解码算法如beam search或greedy decoding。
  • 步骤6: 输出响应(Output Response)
    返回生成的文本给用户。可选:后处理,如引用来源或验证事实。

结构化和非结构化数据的融合,以及Prompt + Context的输入到LLM。

详细流程的潜在变体和优化

  • 高级检索:可结合关键字搜索(BM25)与向量搜索的混合检索(Hybrid Search),或使用查询重写(Query Rewriting)来扩展query。
  • 多跳检索:对于复杂查询,多次检索(e.g., 先检索实体,再检索关系)。
  • 性能考虑:检索延迟通常<1s,生成取决于LLM大小。常见问题:噪声chunk(无关信息)导致Prompt过长,可用压缩(如摘要)优化。
  • 工具与框架:实现时常用LangChain、Haystack或LlamaIndex。嵌入模型:Sentence Transformers;向量DB:Weaviate。

总结

一句话概括RAG就像给AI装了个”外挂搜索引擎”,问啥先从知识库里找答案,再让AI组织回答。

整个流程就两步大动作:

1️⃣ 准备阶段(一次性干完)

  • 把一堆文档(PDF、网页、数据库)切成小块(像切西瓜一样)
  • 给每块内容**打个”指纹”**(向量嵌入,类似数字DNA)
  • 把这些”指纹+原文”存进搜索引擎(向量数据库)

比喻:就像给图书馆每本书都贴上标签,方便以后快速找书。

2️⃣ 回答问题时(每次提问都这样)

1
2
3
4
用户问问题 → AI先去"图书馆"翻书 → 找到相关内容 → 组织答案给用户
↓ ↓ ↓ ↓
"什么是RAG?" 搜"指纹"匹配 挑出3-5段 "RAG是检索增强生成..."
找相关段落 最相关的书 用这些内容回答

详细拆解

  1. 你问问题 → AI把你的问题也打个”指纹”
  2. AI去搜 → 在图书馆里找跟你问题”指纹”最像的几本书
  3. 挑重点 → 不把整本书都拿出来,只拿相关段落
  4. 拼答案 → 把这些段落+你的问题一起给AI,让它重新组织回答
  5. 输出 → AI用找到的”参考资料”给你准确回答

关键点:

  • 向量(指纹)只用来”找书”不直接给AI看
  • AI真正读的是书里的原文,不是那些数字指纹
  • 找书快(秒级),读懂写回答慢(几秒到几十秒)

为啥要这样?

  • 不RAG:AI只能凭记忆瞎编,容易胡说八道
  • 有RAG:AI像查字典一样先找事实,再组织语言回答

通俗理解:RAG就是让AI**”不瞎编,先查资料”**,回答前先翻书确认事实!

参考文档

https://www.6clicks.com/resources/blog/understanding-rag-retrieval-augmented-generation-explained
https://danielp1.substack.com/p/navigating-retrieval-augmented-generation

Linux netplan

初创团队,各方面有限,我们是saas+硬件,但是我们只有单个公网IP、一个一级域名,所以为了短时间适配生产、研发、测试三个环境,同时支持SaaS+硬件通信,我们需要做前端入口的流量管理,团队的小伙伴选择了OPNsense。
这一篇是我为了了解该技术方案而简单整理的。

# 解决Ubuntu Server 24.04删除网卡后的Netplan问题

引言

在Ubuntu Server 24.04中,Netplan是默认的网络配置工具,使用YAML文件管理网络设置。最近,我在虚拟机中配置了双网卡(一张内网,一张外网),但删除一张网卡后,网络无法正常工作。经过调试,我通过手动更新Netplan配置文件解决了问题,以下是我的经验分享。

问题描述

我的虚拟机最初配置了两张网卡:enp0s3(外网,静态IP)用于访问外部网络,enp0s8(内网,DHCP)用于本地通信。删除enp0s8后,运行netplan apply没有生效,ip a显示enp0s3未正确分配IP。日志(journalctl -u systemd-networkd)提示Netplan仍尝试配置已删除的网卡。

1
2
4: ens38: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 00:30:26:8c:78:57 brd ff:ff:ff:ff:ff:ff

解决方案

以下是解决步骤:

  1. 启动网卡
1
ip link set ens38 up
  1. 检查现有Netplan配置
    查看/etc/netplan/目录中的配置文件(通常为00-installer-config.yaml)。原始配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    network:
    version: 2
    ethernets:
    enp0s3:
    dhcp4: no
    addresses: [172.162.1.100/24]
    gateway4: 172.162.1.1
    nameservers:
    addresses: [8.8.8.8, 8.8.4.4]
    enp0s8:
    dhcp4: yes
  2. 更新配置文件
    删除enp0s8相关配置,仅保留enp0s3。修改后的文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    network:
    version: 2
    ethernets:
    enp0s3:
    dhcp4: no
    addresses: [172.162.1.100/24]
    gateway4: 172.162.1.1
    nameservers:
    addresses: [8.8.8.8, 8.8.4.4]

    注意:确保YAML缩进为2个空格,避免格式错误。

  3. 验证并应用配置
    检查配置语法:

    1
    sudo netplan --debug apply

    确认无错误后,应用配置:

    1
    sudo netplan apply
  4. 测试网络
    验证接口状态和连通性:

    1
    2
    ip a
    ping 8.8.8.8
  5. 检查日志
    如果仍不生效,查看日志:

    1
    journalctl -u systemd-networkd

总结

删除虚拟机网卡后,Netplan不会自动更新配置,需手动移除无效网卡的配置条目。关键是检查YAML文件、确保格式正确,并使用netplan --debug apply定位问题。在多网卡场景下,建议定期验证网卡名称(ip link)和配置文件一致性。遇到类似问题?欢迎留言分享你的经验!

网络安全 软路由

初创团队,各方面有限,我们是saas+硬件,但是我们只有单个公网IP、一个一级域名,所以为了短时间适配生产、研发、测试三个环境,同时支持SaaS+硬件通信,我们需要做前端入口的流量管理,团队的小伙伴选择了OPNsense。
这一篇是我为了了解该技术方案而简单整理的。

OPNsense的防火墙模块基于FreeBSD的pf(Packet Filter),提供了强大的NAT功能,包括Port Forward(转发规则)、Outbound NAT(出站NAT)和NPTv6(IPv6前缀转换)。从我的截图可以看到,Port Forward页面列出了WAN到LAN的TCP流量规则(如Web服务和RDP),而Outbound NAT页面显示了自动生成的出站规则。这些功能让我在单一公网IP下实现了多环境隔离和外部访问。
NAT策略:Port Forward与Outbound NAT的协同
最初我以为Port Forward能解决所有需求,但实践证明,仅靠它处理入站流量是不够的。Outbound NAT才是出站流量的关键,尤其在多VLAN和硬件通信场景中,两者需协同工作。

Port Forward:

用途:处理入站流量,将公网端口映射到内部IP。例如,我配置了WAN:443到192.168.10.10:443,让外部通过prod.example.com访问生产环境。
局限:不管理出站流量,硬件向SaaS发送数据时需依赖Outbound NAT。
配置:Firewall → NAT → 转发,添加规则(Protocol: TCP;Destination: WAN address:443;Redirect to: 192.168.10.10:443)。

Outbound NAT:

位置:Firewall → NAT → Outbound,当前为自动模式,自动为WAN出站流量分配公网IP。
优化:切换到手动模式,为每个VLAN设置规则,确保出站流量隔离。
我的经验:自动模式曾因端口冲突导致硬件API请求失败,切换到手动后问题解决。

找到并配置Outbound NAT
Outbound NAT是管理出站流量的关键,位于Firewall → NAT → Outbound页面。从我的截图可以看到,默认使用“自动生成规则”,为LAN和Loopback网段分配WAN地址。但对于复杂场景,我切换到手动模式以满足需求。

手动配置:
点击“切换到手动规则”(Switch to Manual Outbound NAT rule generation),保存。
添加规则:Interface: WAN;Source: 192.168.10.0/24(生产),Translation: WAN地址,描述:“Production Outbound”。
依次为研发(192.168.20.0/24)和测试(192.168.30.0/24)设置规则。
硬件场景:为生产VLAN的硬件(如192.168.10.10)添加规则,确保其API请求(如curl https://prod.example.com/api/v1/data)顺利出站。

优化:启用NAT Reflection(Firewall → Advanced),让内部设备用公网IP访问暴露服务。
经验:备份配置(System → Config History)后切换模式,避免误操作。日志监控(Firewall → Log Files)帮助我定位流量问题。

下一步行动项

实战配置:综合NAT策略

入站:Port Forward规则处理prod.example.com的HTTPS请求,映射到生产环境的Web服务器。
出站:手动Outbound NAT为每个VLAN配置规则,保障出站流量隔离。我的硬件通过生产VLAN的规则上传数据,测试显示吞吐量稳定。
硬件场景:结合Port Forward和Outbound NAT,硬件既能接收SaaS命令(入站),又能上传数据(出站),单IP利用率显著提升。
NPTv6(未来扩展):当前用IPv4,但NPTv6为IPv6网络的前缀转换提供了可能,适合ISP支持IPv6时升级。

硬件通信:NAT与子域名的结合
我的SaaS硬件通过prod.example.com与服务通信,NAT策略确保其双向通信。

入站:Port Forward映射WAN:443到192.168.10.10:443,配合HAProxy根据子域名分发流量。
出站:Outbound NAT规则让硬件出站请求使用公网IP,日志显示连接正常。
安全:用别名(Firewall → Aliases)定义硬件IP范围,限制未授权访问。
实践:我用curl测试硬件请求,确认数据成功上传到SaaS。

VPN: 团队vpn后续我会研究是否能用OPNsense

AI提效 Claude Sonnet 4
最近产品上线的前期准备,小团队+AI编程,各项工作几乎手撮。因为产品投入市场后很可能会有一个迅速从100到10000的过程,所以我要前置考虑一些事情,慢慢 查漏补缺。此篇是我对于数据灾备的参考播客之一。基于ByteByteGo博客+一些实操+AI辅助而输出该博客。

数据库复制指南:核心概念与策略

Database Replication Guide: Key Concepts and Strategies

引言 | Introduction

每个现代应用程序都依赖于数据,用户期望数据快速、实时且始终可访问。然而,数据库并不是魔法,它们可能会失败或在负载下变慢。它们也会遇到物理和地理限制,这就是复制变得必要的地方。

Every modern application relies on data, and users expect that data to be fast, current, and always accessible. However, databases are not magic. They can fail or slow down under load. They can also encounter physical and geographic limits, which is where replication becomes necessary.

数据库复制意味着在多台机器上保持相同数据的副本。这些机器可以位于同一个数据中心,也可以分布在全球各地。目标很简单:

  • 提高容错性
  • 扩展读取能力
  • 通过将数据移近需要的地方来减少延迟

Database Replication means keeping copies of the same data across multiple machines. These machines can sit in the same data center or be spread across the globe. The goal is straightforward:

  • Increase fault tolerance
  • Scale reads
  • Reduce latency by bringing data closer to where it’s needed

复制的重要性 | The Importance of Replication

复制是任何旨在在不丢失数据或令用户失望的情况下从故障中恢复的系统的核心。无论是毫秒级更新的社交动态、处理限时抢购的电商网站,还是处理全球交易的金融系统,复制确保系统即使在部分组件故障时也能继续运行。

Replication sits at the heart of any system that aims to survive failures without losing data or disappointing users. Whether it’s a social feed updating in milliseconds, an e-commerce site handling flash sales, or a financial system processing global transactions, replication ensures the system continues to operate, even when parts of it break.

然而,复制也带来了复杂性。它迫使我们在一致性、可用性和性能之间做出艰难的决定。数据库可能正常运行,但滞后的副本仍可能提供过时的数据。网络分区可能使两个主节点认为它们在负责,导致脑裂写入。围绕这些问题进行设计并非易事。

However, replication also introduces complexity. It forces difficult decisions around consistency, availability, and performance. The database might be up, but a lagging replica can still serve stale data. A network partition might make two leader nodes think they’re in charge, leading to split-brain writes. Designing around these issues is non-trivial.

复制策略概述 | Overview of Replication Strategies

在分布式数据库中,有三种主要的复制策略:

In distributed databases, there are three main replication strategies:

1. 单主复制 (Single-Leader Replication)

工作原理 | How It Works:

  • 一个主节点接收所有写入操作
  • 主节点将更改复制到多个从节点
  • 从节点提供读取服务

优势 | Advantages:

  • 简单且易于理解
  • 强一致性保证
  • 避免写入冲突

劣势 | Disadvantages:

  • 主节点成为单点故障

  • 写入性能受限于单个节点

  • 主节点故障时需要故障转移

  • One primary node accepts all writes

  • Primary replicates changes to multiple secondary nodes

  • Secondary nodes serve read requests

  • Simple and easy to understand

  • Strong consistency guarantees

  • Avoids write conflicts

  • Primary node becomes a single point of failure

  • Write performance limited to single node

  • Requires failover when primary fails

2. 多主复制 (Multi-Leader Replication)

工作原理 | How It Works:

  • 多个主节点可以接受写入
  • 主节点之间相互复制更改
  • 需要冲突检测和解决机制

优势 | Advantages:

  • 高写入可用性
  • 更好的性能和容错性
  • 适合多数据中心部署

劣势 | Disadvantages:

  • 写入冲突需要解决

  • 复杂的一致性模型

  • 需要冲突解决策略

  • Multiple primary nodes can accept writes

  • Primaries replicate changes to each other

  • Requires conflict detection and resolution

  • High write availability

  • Better performance and fault tolerance

  • Suitable for multi-datacenter deployments

  • Write conflicts need resolution

  • Complex consistency model

  • Requires conflict resolution strategies

3. 无主复制 (Leaderless Replication)

工作原理 | How It Works:

  • 所有副本都是对等的
  • 客户端可以向任何副本写入
  • 使用仲裁机制确保一致性

优势 | Advantages:

  • 高可用性
  • 简单的故障处理
  • 良好的可扩展性

劣势 | Disadvantages:

  • 最终一致性

  • 复杂的读取修复

  • 需要仲裁机制

  • All replicas are peers

  • Clients can write to any replica

  • Uses quorum mechanisms for consistency

  • High availability

  • Simple failure handling

  • Good scalability

  • Eventual consistency

  • Complex read repair

  • Requires quorum mechanisms

复制延迟的挑战 | Challenges of Replication Lag

复制延迟是分布式数据库面临的一个关键挑战。当主节点接收写入并将更改传播到副本时,存在时间延迟。这种延迟可能导致:

Replication lag is a key challenge faced by distributed databases. When the primary node receives a write and propagates changes to replicas, there’s a time delay. This lag can lead to:

读取后写入不一致 | Read-After-Write Inconsistency

用户写入数据后立即读取可能看到旧数据。

Users might see stale data when reading immediately after writing.

单调读取问题 | Monotonic Read Issues

用户可能看到数据”倒退”,即先看到新数据后看到旧数据。

Users might see data “go backwards” - seeing newer data then older data.

因果关系违反 | Causality Violations

相关事件可能以错误的顺序出现。

Related events might appear in the wrong order.

选择合适的复制策略 | Choosing the Right Replication Strategy

何时选择单主复制 | When to Choose Single-Leader Replication

  • 需要强一致性的应用

  • 写入量相对较低

  • 简单的故障转移需求

  • Applications requiring strong consistency

  • Relatively low write volume

  • Simple failover requirements

何时选择多主复制 | When to Choose Multi-Leader Replication

  • 多数据中心部署

  • 高写入可用性需求

  • 可以容忍冲突解决的复杂性

  • Multi-datacenter deployments

  • High write availability requirements

  • Can tolerate conflict resolution complexity

何时选择无主复制 | When to Choose Leaderless Replication

  • 最终一致性可接受

  • 需要高可用性

  • 简单的扩展需求

  • Eventual consistency is acceptable

  • High availability is needed

  • Simple scaling requirements

实现考虑因素 | Implementation Considerations

一致性模型 | Consistency Models

  • 强一致性: 所有副本始终同步

  • 最终一致性: 副本最终会收敛

  • 因果一致性: 保持事件的因果关系

  • Strong Consistency: All replicas always in sync

  • Eventual Consistency: Replicas eventually converge

  • Causal Consistency: Maintains causality between events

冲突解决策略 | Conflict Resolution Strategies

  • 最后写入获胜 (LWW): 基于时间戳的简单策略

  • 应用层解决: 让应用程序处理冲突

  • 合并策略: 自动合并冲突的更改

  • Last Write Wins (LWW): Simple timestamp-based strategy

  • Application-level resolution: Let application handle conflicts

  • Merge strategies: Automatically merge conflicting changes

网络分区处理 | Network Partition Handling

  • CAP定理: 在一致性、可用性和分区容忍性之间选择

  • 脑裂预防: 使用仲裁和租约机制

  • 分区检测: 监控网络连接状态

  • CAP Theorem: Choose between consistency, availability, and partition tolerance

  • Split-brain prevention: Use quorum and lease mechanisms

  • Partition detection: Monitor network connectivity

现实世界的例子 | Real-World Examples

单主复制系统 | Single-Leader Systems

  • MySQL主从复制: 传统的主从架构

  • PostgreSQL流复制: 支持同步和异步复制

  • MongoDB副本集: 自动故障转移

  • MySQL Master-Slave: Traditional master-slave architecture

  • PostgreSQL Streaming: Supports sync and async replication

  • MongoDB Replica Sets: Automatic failover

多主复制系统 | Multi-Leader Systems

  • MySQL集群: 多主动主配置

  • CouchDB: 文档数据库的多主复制

  • Cassandra: 分布式NoSQL数据库

  • MySQL Cluster: Multi-active master configuration

  • CouchDB: Multi-master replication for document databases

  • Cassandra: Distributed NoSQL database

无主复制系统 | Leaderless Systems

  • Amazon DynamoDB: 无主键值存储

  • Apache Cassandra: 对等复制

  • Riak: 分布式键值存储

  • Amazon DynamoDB: Leaderless key-value store

  • Apache Cassandra: Peer-to-peer replication

  • Riak: Distributed key-value store

监控和维护 | Monitoring and Maintenance

关键指标 | Key Metrics

  • 复制延迟: 主副本之间的时间差

  • 吞吐量: 每秒处理的操作数

  • 可用性: 系统正常运行时间百分比

  • Replication Lag: Time difference between primary and replicas

  • Throughput: Operations processed per second

  • Availability: System uptime percentage

维护最佳实践 | Maintenance Best Practices

  • 定期备份和恢复测试

  • 监控复制状态

  • 计划故障转移演练

  • Regular backup and recovery testing

  • Monitor replication status

  • Plan failover drills

PostgreSQL复制实战经验 | PostgreSQL Replication Practical Experience

为什么选择PostgreSQL | Why Choose PostgreSQL

在实际项目中,PostgreSQL作为企业级开源数据库,在复制、扩展功能方面有着独特的优势。我的上一家公司的几个项目选用的就是PostgreSQL,有以下深刻体会:

In real projects, PostgreSQL as an enterprise-grade open-source database has unique advantages in replication. Through my experience with PostgreSQL replication in multiple projects, I have the following insights:

PostgreSQL的复制优势 | PostgreSQL Replication Advantages:

  • 流复制稳定可靠: 相比MySQL的binlog复制,PostgreSQL的流复制更加稳定,延迟更低

  • 逻辑复制灵活: 支持表级复制,可以选择性复制部分数据

  • 强一致性保证: 同步复制模式下可以确保零数据丢失

  • 丰富的监控工具: pg_stat_replication视图提供详细的复制状态信息

  • Stable streaming replication: Compared to MySQL’s binlog replication, PostgreSQL’s streaming replication is more stable with lower latency

  • Flexible logical replication: Supports table-level replication, allowing selective data replication

  • Strong consistency guarantees: Synchronous replication mode ensures zero data loss

  • Rich monitoring tools: pg_stat_replication view provides detailed replication status information

PostgreSQL复制最佳实践 | PostgreSQL Replication Best Practices

基于实际运维经验,我总结了以下PostgreSQL复制的最佳实践:

Based on practical operational experience, I’ve summarized the following PostgreSQL replication best practices:

1. 流复制配置建议 | Streaming Replication Configuration Recommendations

主库配置要点 | Primary Configuration Key Points:

1
2
3
4
5
6
-- postgresql.conf
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
synchronous_commit = on # 根据业务需求调整
synchronous_standby_names = '*' # 同步复制

从库配置要点 | Standby Configuration Key Points:

1
2
3
4
-- postgresql.conf
hot_standby = on
max_standby_streaming_delay = 30s
wal_receiver_status_interval = 1s

2. 监控和告警策略 | Monitoring and Alert Strategies

关键监控指标 | Key Monitoring Metrics:

  • 复制延迟: 通过pg_stat_replication.replay_lag监控
  • WAL发送状态: 监控pg_stat_replication.state
  • 磁盘空间: WAL日志积累可能导致磁盘满
  • 网络连接: 复制连接的稳定性

告警阈值建议 | Recommended Alert Thresholds:

  • 复制延迟超过10秒告警

  • WAL发送异常立即告警

  • 主从连接断开超过1分钟告警

  • Replication lag: Monitor via pg_stat_replication.replay_lag

  • WAL sender status: Monitor pg_stat_replication.state

  • Disk space: WAL log accumulation may cause disk full

  • Network connection: Stability of replication connections

  • Replication lag exceeding 10 seconds

  • WAL sender exceptions immediate alert

  • Primary-standby connection lost for more than 1 minute

3. 故障切换实践 | Failover Practices

自动故障切换工具推荐 | Recommended Automatic Failover Tools:

  • Patroni: 基于etcd/consul的高可用解决方案
  • repmgr: 轻量级的复制管理工具
  • Stolon: 云原生的PostgreSQL高可用方案

手动故障切换步骤 | Manual Failover Steps:

  1. 确认主库真正故障
  2. 提升从库为主库:pg_promote()
  3. 重新配置应用连接
  4. 修复原主库并重建复制
  • Patroni: High availability solution based on etcd/consul
  • repmgr: Lightweight replication management tool
  • Stolon: Cloud-native PostgreSQL high availability solution
  1. Confirm primary database is truly failed
  2. Promote standby to primary: pg_promote()
  3. Reconfigure application connections
  4. Repair original primary and rebuild replication

我的技术观点 | My Technical Perspectives

关于复制策略选择 | On Replication Strategy Selection

单主复制依然是主流 | Single-Leader Replication Remains Mainstream

虽然多主复制和无主复制在理论上很吸引人,但在实际生产环境中,我发现单主复制仍然是最可靠的选择,特别是对于需要强一致性的业务场景。原因如下:

While multi-leader and leaderless replication are theoretically attractive, in actual production environments, I find single-leader replication is still the most reliable choice, especially for business scenarios requiring strong consistency. Here’s why:

  1. 复杂性可控: 单主复制的逻辑简单,故障排查容易

  2. 一致性保证: 避免了复杂的冲突解决机制

  3. 工具成熟: PostgreSQL的单主复制工具链非常成熟

  4. 性能可预测: 读写分离的性能模式清晰

  5. Manageable complexity: Single-leader replication logic is simple, easy to troubleshoot

  6. Consistency guarantee: Avoids complex conflict resolution mechanisms

  7. Mature tooling: PostgreSQL’s single-leader replication toolchain is very mature

  8. Predictable performance: Clear read-write separation performance pattern

关于同步vs异步复制 | On Synchronous vs Asynchronous Replication

混合模式是最佳选择 | Hybrid Mode is the Best Choice

在实际项目中,我通常采用”同步+异步”的混合复制模式:

In actual projects, I usually adopt a “synchronous + asynchronous” hybrid replication mode:

  • 关键业务: 使用同步复制,确保数据安全
  • 读取扩展: 使用异步复制,提供更多读取能力
  • 跨地域备份: 使用异步复制,降低网络延迟影响

配置示例 | Configuration Example:

1
synchronous_standby_names = 'FIRST 1 (standby1), standby2, standby3'
  • Critical business: Use synchronous replication to ensure data safety
  • Read scaling: Use asynchronous replication for more read capacity
  • Cross-region backup: Use asynchronous replication to reduce network latency impact

关于PostgreSQL版本选择 | On PostgreSQL Version Selection

推荐PostgreSQL 14+版本 | Recommend PostgreSQL 14+ Versions

基于我的使用经验,PostgreSQL 14及以上版本在复制功能上有显著改进:

Based on my experience, PostgreSQL 14 and above versions have significant improvements in replication features:

  1. 逻辑复制增强: 支持二进制格式,性能提升30%以上

  2. 复制监控改进: 更丰富的统计信息和监控视图

  3. 故障恢复优化: 崩溃恢复时间大幅缩短

  4. 安全性增强: 支持更细粒度的复制权限控制

  5. Logical replication enhancements: Support for binary format, 30%+ performance improvement

  6. Replication monitoring improvements: Richer statistics and monitoring views

  7. Failover optimization: Significantly reduced crash recovery time

  8. Security enhancements: Support for more granular replication permission control

网络和安全配置 | Network and Security Configuration

网络优化 | Network Optimization:

  • 使用专用网络进行复制
  • 配置合适的TCP参数优化
  • 监控网络带宽使用情况

安全配置 | Security Configuration:

  • 使用SSL加密复制连接

  • 配置防火墙规则

  • 定期更新密码和证书

  • Use dedicated network for replication

  • Configure appropriate TCP parameter optimization

  • Monitor network bandwidth usage

  • Use SSL encryption for replication connections

  • Configure firewall rules

  • Regularly update passwords and certificates

结论 | Conclusion

数据库复制是构建可靠、可扩展系统的基础技术。选择正确的复制策略取决于应用程序的具体需求,包括一致性要求、可用性目标和性能期望。理解每种策略的权衡是设计成功分布式系统的关键。

Database replication is a fundamental technology for building reliable, scalable systems. Choosing the right replication strategy depends on your application’s specific requirements, including consistency needs, availability goals, and performance expectations. Understanding the trade-offs of each approach is crucial for designing successful distributed systems.

基于我在PostgreSQL复制方面的实战经验,我强烈建议:从简单开始,逐步优化。先建立稳定的单主复制架构,然后根据业务增长和性能需求,逐步引入更复杂的复制策略。PostgreSQL作为企业级数据库,其复制功能完全能够满足大多数业务场景的需求。

Based on my practical experience with PostgreSQL replication, I strongly recommend: Start simple, optimize gradually. First establish a stable single-leader replication architecture, then gradually introduce more complex replication strategies based on business growth and performance requirements. PostgreSQL as an enterprise-grade database, its replication features can fully meet the needs of most business scenarios.

无论选择哪种策略,都需要仔细考虑实现细节、监控系统状态,并为故障情况做好准备。随着应用程序的发展,复制策略也可能需要演进以满足新的需求。

Regardless of which strategy you choose, careful consideration of implementation details, monitoring system health, and preparing for failure scenarios is essential. As applications evolve, replication strategies may need to evolve as well to meet new requirements.


本文基于ByteByteGo的数据库复制指南编写,旨在为开发者提供全面的复制策略参考。

参考:This article is based on ByteByteGo’s database replication guide, aimed at providing developers with comprehensive reference for replication strategies.

前言

在创业公司干TeamLeader,很多事情都需要自己去完成,Jira作为一款优秀的项目管理工具,可以帮助我们更高效地完成这些任务。本文将介绍 Jira 的一些基本功能和使用方法,以及如何在软件质量保证(SQA)中发挥其作用。

在现代软件开发中,软件质量保证(SQA)是确保产品可靠性和用户满意度的核心环节。Jira 作为主流的项目与测试管理工具,通过插件和与CI/CD工具的集成,极大提升了测试管理的效率和可追溯性。

Jira在软件质量保证中的作用

  • 集中管理测试流程:Jira 支持需求、缺陷、测试用例、测试计划等全流程管理,便于追踪每个阶段的质量状态。
  • 可扩展的测试管理插件:如 Zephyr、synapseRT、Test Management 等插件,扩展了 Jira 的测试管理能力,实现测试用例、测试套件、测试执行和缺陷的统一管理。
  • 需求与缺陷追踪:通过需求追踪矩阵和缺陷管理,确保每个需求都被充分测试,每个缺陷都能被及时发现和修复。

主要插件及CI/CD集成

  • ZephyrsynapseRT 等插件可与 Jenkins 等 CI/CD 工具集成,实现自动化测试结果的回传和可视化。
  • 插件配置流程一般包括:
    1. 在 Jira 安装对应插件。
    2. 在 Jenkins 安装插件并配置 Jira 连接。
    3. 在测试用例中关联自动化脚本,测试执行后自动同步结果到 Jira。
  • 通过集成,测试执行、结果反馈、缺陷跟踪实现自动化闭环,提升了测试效率和质量可控性。

最佳实践与总结

  • 明确测试流程,合理使用插件进行测试用例和缺陷管理。
  • 利用 CI/CD 工具与 Jira 集成,实现自动化测试与持续反馈。
  • 定期生成报告,监控项目质量,及时调整测试策略。

Jira 结合测试管理插件和自动化工具,为软件质量保证提供了强大支撑,是现代敏捷团队不可或缺的工具之一。

专业观点

从专业角度来看,Jira 不仅仅是一个项目管理工具,更是现代软件质量保证体系的中枢。它通过流程化、可追溯和自动化的管理方式,将需求、开发、测试、缺陷等环节有机串联,极大提升了团队协作效率和产品交付质量。

但在实际落地过程中,也存在一些挑战:如插件生态复杂、集成配置门槛较高、团队成员对流程规范的认知和执行力参差不齐等。因此,企业在引入 Jira 及其测试管理方案时,需结合自身实际,制定清晰的流程规范,并持续进行培训和优化。

展望未来,随着 DevOps、AI 测试等理念的发展,Jira 及其生态将更加智能化和自动化。如何更好地与云原生、微服务架构、智能分析等新技术融合,将是提升软件质量管理能力的关键方向。


使用心得

在实际使用 Jira 结合测试管理插件的过程中,我体会最深的是其对测试流程标准化和可追溯性的提升。通过需求、测试用例、缺陷的全链路管理,极大方便了团队协作和问题定位。尤其在多项目、多团队协作时,Jira 的权限和视图配置可以灵活满足不同角色的需求。

但也遇到过一些实际问题,比如:

  • 插件兼容性和升级带来的配置丢失或功能异常,需要定期备份和测试环境验证。
  • 自动化测试结果与 Jira 的集成,初期配置较为繁琐,建议团队制定详细的集成文档和标准流程。
  • 测试用例和缺陷数据量大时,Jira 的性能和检索效率会受到影响,建议定期归档历史数据。

总体来说,Jira+测试管理插件是提升测试管理效率和质量的有力工具,但要发挥最大价值,离不开团队的流程规范、持续优化和技术积累。

参考:JIRA-测试管理实用手册-全

网络 HTTP

从HTTP/1到HTTP/3:现代Web通信协议的演进

深入解析互联网基础设施的技术变革

HTTP是现代互联网通信的基石,从浏览器请求到Kubernetes集群内的微服务调用,几乎所有的网络交互都基于HTTP协议。每当浏览器加载页面、APP获取API响应、或后端服务相互查询时,几乎都是通过HTTP进行的。即使底层传输协议发生变化,这一点仍然成立——例如,gRPC将自己包装在HTTP/2之上,而主导后端设计的RESTful API只是建立在HTTP动词和状态码之上的约定。

基础认知:理解HTTP的生态角色

在深入探讨HTTP演进之前,我们必须理解HTTP运行在一个复杂的生态系统中。现代应用不仅仅是孤立地处理HTTP——它们还必须考虑数据库选择(SQL vs NoSQL)、CAP定理的权衡(一致性、可用性、分区容错性),以及HTTP所依赖的底层网络协议。

经典的客户端-服务器模型

alt text

这个简单的交换隐藏了巨大的复杂性。当你的浏览器请求/index.html时,它不仅仅是在获取一个文件——它参与了一个精心编排的复杂过程:

  • DNS解析
  • TCP连接建立
  • HTTP请求/响应循环
  • 连接管理
  • 缓存策略

HTTP/1.0:先驱者(1996年)

HTTP/1.0在当时是革命性的,但以今天的标准来看极其简单:

主要特征:

  • 无状态:每个请求都是独立的
  • 基于文本:人类可读的协议
  • 每请求一连接:每个资源都需要新的TCP连接
  • 无持久性:每次响应后连接都会关闭

问题所在:
想象加载一个包含50个资源(图片、CSS、JavaScript)的网页。HTTP/1.0需要建立50个独立的TCP连接。考虑到TCP的三次握手开销,这种效率是灾难性的。

1
2
3
请求1: [SYN] → [SYN-ACK] → [ACK] → [HTTP请求] → [响应] → [FIN]
请求2: [SYN] → [SYN-ACK] → [ACK] → [HTTP请求] → [响应] → [FIN]
... (再重复48次)

HTTP/1.1:主力军(1997年)

HTTP/1.1解决了HTTP/1.0的低效问题,成为互联网20多年来的主力协议。

主要改进:

1. 持久连接

1
Connection: keep-alive

HTTP/1.1允许在单个TCP连接上进行多个请求,而不是每次请求后都关闭连接。

2. 请求管道化
可以发送多个请求而无需等待响应,但响应仍必须按顺序处理。

3. Host头部

1
Host: example.com

启用虚拟主机——单个IP地址上的多个网站。

4. 分块传输编码

1
Transfer-Encoding: chunked

允许在不知道总内容长度的情况下流式传输响应。

队头阻塞(HOL Blocking)问题

尽管有这些改进,HTTP/1.1仍存在一个根本限制:队头阻塞。在管道化连接中,如果第一个响应延迟,所有后续响应都必须等待,即使它们已经准备好了。

1
2
3
请求A ────────────────────> [处理中...]
请求B ──> [就绪] [等待A]
请求C ──> [就绪] [等待A]

这与数据库理论相关。正如数据库在CAP定理中面临权衡(无法同时完美地拥有一致性、可用性和分区容错性),网络协议也面临自己的约束。HTTP/1.1选择了简单性和有序交付,而非并行性。

HTTP/2:游戏改变者(2015年)

HTTP/2代表了Web性能思维的根本转变。

革命性特性:

1. 二进制协议
与HTTP/1.1的基于文本格式不同,HTTP/2使用二进制帧,使其解析更高效、更不容易出错。

2. 多路复用
多个请求和响应可以在单个TCP连接上交错进行,不会相互阻塞。

1
2
3
4
连接1:
├── 流1: [请求A] ──> [响应A]
├── 流2: [请求B] ──> [响应B]
└── 流3: [请求C] ──> [响应C]

3. 服务器推送
服务器可以主动向客户端发送资源:

1
2
客户端请求: index.html
服务器推送: style.css, script.js, logo.png

4. 头部压缩(HPACK)
通过压缩HTTP头部(通常包含重复信息)来减少开销。

5. 流优先级
客户端可以指示哪些资源更重要:

1
Priority: weight=200, depends_on=stream_1

TCP瓶颈

然而,HTTP/2仍然依赖TCP,TCP在传输层有自己的队头阻塞。如果单个TCP数据包丢失,整个连接就会停滞,直到重传完成,影响所有HTTP/2流。

这反映了分布式数据库中的一致性挑战。正如最终一致性系统(如NoSQL文档存储)可以通过放松严格的一致性要求来提供更好的可用性,HTTP/3后来也会放松TCP的严格顺序保证。

HTTP/3:突破束缚(2020年)

HTTP/3通过完全放弃TCP而采用基于UDP的QUIC,代表了HTTP演进中最激进的变化。

QUIC:革命基础

QUIC(Quick UDP Internet Connections)解决了TCP的根本限制:

1. 减少连接建立
QUIC结合了传输和加密握手:

1
2
TCP + TLS: 3次往返
QUIC: 1次往返(后续连接为0-RTT)

2. 每流控制流量
与TCP的连接级流量控制不同,QUIC提供流级控制,消除了传输层队头阻塞。

3. 连接迁移
移动设备在WiFi和蜂窝网络之间切换时可以保持连接。

4. 内置加密
与基于TCP的HTTP/2不同,QUIC在设计上就包含加密——没有未加密的QUIC。

HTTP/3的革命性架构

HTTP/3从根本上改变了我们对Web通信的思考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
HTTP/3协议栈:
┌─────────────────┐
│ 应用层 │ ← HTTP/3语义
├─────────────────┤
│ QUIC │ ← 传输+安全
├─────────────────┤
│ UDP │ ← 网络层
└─────────────────┘

传统协议栈:
┌─────────────────┐
│ 应用层 │ ← HTTP/1.1 或 HTTP/2
├─────────────────┤
│ TLS │ ← 安全层
├─────────────────┤
│ TCP │ ← 传输层
├─────────────────┤
│ IP │ ← 网络层
└─────────────────┘

演进时间线:可视化之旅

HTTP/1.1(1997年):顺序处理

1
2
3
4
5
6
7
8
9
客户端 ────────────> 服务器
│ TCP连接 │
│ ┌─────────────┐ │
│ │ 请求1 │──>│
│ │<── 响应 │ │
│ │ 请求2 │──>│
│ │<── 响应 │ │
│ └─────────────┘ │
│ 关闭连接 │

主要特性:

  • 持久连接和管道化
  • 使用TCP进行可靠传输
  • 应用层队头阻塞
  • 基于文本的协议

HTTP/2(2015年):多路复用革命

1
2
3
4
5
6
7
8
9
客户端 ────────────> 服务器
│ 单个TCP连接 │
│ ┌─────────────────────┐ │
│ │ 流1: 请求A │──>│
│ │ 流2: 请求B │──>│
│ │ 流3: 请求C │──>│
│ │<────── 响应 ────│ │
│ │ (多路复用) │ │
│ └─────────────────────┘ │

革命性变化:

  • 二进制帧层:高效解析和减少错误
  • 多路复用:同一TCP连接上的多个并发请求
  • 流优先级:关键资源获得优先级
  • 服务器推送:主动资源传递
  • 头部压缩(HPACK):减少开销

二进制帧的魔力:

1
2
3
4
5
6
HTTP消息(逻辑)
┌─────────────────┐
│ 请求头部 │ ──> 帧头部 <类型 = 头部>
├─────────────────┤ 帧主体 <压缩头部>
│ 请求主体 │ ──> 帧头部 <类型 = 数据>
└─────────────────┘ 帧主体 <实际数据>

HTTP/3(2019年):QUIC优势

1
2
3
4
5
6
7
8
9
10
客户端 ────────────> 服务器
│ 基于UDP的QUIC │
│ ┌─────────────────────┐ │
│ │ ╔═══════════════╗ │ │
│ │ ║ HTTP请求 ║ │ │
│ │ ╚═══════════════╝ │ │
│ │ ╔═══════════════╗ │ │
│ │ ║ HTTP响应 ║ │ │
│ │ ╚═══════════════╝ │ │
│ └─────────────────────┘ │

QUIC的颠覆性特性:

  • 无需正式连接建立
  • 基于UDP的最大灵活性
  • 每流流量控制消除传输层HOL阻塞
  • 0-RTT连接恢复
  • 连接迁移支持

性能对比:现实世界的影响

连接建立

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 + TLS:
[DNS] → [TCP SYN] → [TCP ACK] → [TLS握手] → [HTTP请求]
总计: ~3-4次往返

HTTP/2 + TLS:
[DNS] → [TCP SYN] → [TCP ACK] → [TLS握手] → [HTTP请求]
总计: ~3-4次往返(与HTTP/1.1相同)

HTTP/3 + QUIC:
[DNS] → [QUIC握手 + TLS + HTTP请求]
总计: ~1-2次往返(回访客户端为0-RTT)

队头阻塞分析

HTTP/1.1:应用层和传输层都有阻塞

1
2
3
请求A [████████████████] (慢)
请求B [██] (快,但在等待)
请求C [███] (快,但在等待)

HTTP/2:解决应用层,但TCP仍然阻塞

1
2
3
流A [████████████████] (数据包丢失影响所有)
流B [██] (被TCP重传阻塞)
流C [███] (被TCP重传阻塞)

HTTP/3:任何层都没有阻塞

1
2
3
流A [████████████████] (独立)
流B [██] (立即完成)
流C [███] (立即完成)

数据库类比:理解权衡

正如数据库面临CAP定理约束,HTTP协议也在做权衡:

HTTP/1.1:像ACID数据库

  • 强一致性:请求按顺序处理
  • 可靠性:TCP保证传输
  • 简单性:易于实现和调试
  • 性能代价:顺序处理限制吞吐量

HTTP/2:像带事务的NoSQL

  • 更好性能:多路复用增加吞吐量
  • 维持一致性:仍依赖TCP顺序
  • 增加复杂性:二进制协议、流管理
  • 部分解决方案:仍受TCP队头阻塞影响

HTTP/3:像最终一致性系统

  • 最大性能:独立流处理
  • 放松顺序:QUIC在适当时允许乱序传输
  • 高可用性:连接迁移、更快恢复
  • 复杂性权衡:更复杂的流控制和拥塞管理

现实世界的实现挑战

对开发者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// HTTP/1.1: 简单但低效
fetch('/api/data1').then(() =>
fetch('/api/data2').then(() =>
fetch('/api/data3')
)
);

// HTTP/2: 高效多路复用
Promise.all([
fetch('/api/data1'),
fetch('/api/data2'),
fetch('/api/data3')
]); // 所有请求在单连接上多路复用

// HTTP/3: 相同API,更好性能
// 无需代码更改 - 浏览器自动处理QUIC

对基础设施团队

  • 负载均衡器:必须支持QUIC转发
  • CDN:需要HTTP/3边缘服务器
  • 监控:QUIC连接的新指标
  • 调试:二进制协议需要专门工具

未来:下一步是什么?

当前采用情况(2024年)

  • HTTP/3:约30%的网站支持
  • 主要参与者:Google、Cloudflare、Facebook引领采用
  • 浏览器支持:所有现代浏览器都支持HTTP/3
  • 移动影响:在移动网络上获益最大

新兴模式

  1. 混合协议:同时使用HTTP/2和HTTP/3
  2. 边缘计算:HTTP/3的低延迟完美适合边缘部署
  3. 物联网集成:QUIC的效率有利于资源受限设备
  4. 实时应用:基于HTTP/3的WebRTC实现更好的流媒体

结论:理解性能权衡

从HTTP/1.1到HTTP/3的演进反映了分布式系统思维的广泛演进。正如我们从单体数据库转向具有最终一致性的微服务,HTTP也从简单的请求-响应模式演进为复杂的多路复用、加密、移动优化协议。

关键要点:

  1. HTTP/1.1仍然是基础——简单、可靠、通用支持
  2. HTTP/2解决了应用层多路复用但受TCP约束
  3. HTTP/3代表了对传输协议的根本重新思考

对系统架构师的建议:

  • 考虑为移动重点应用使用HTTP/3
  • 监控目标市场的采用率
  • 规划渐进式迁移策略
  • 理解HTTP/2在未来几年仍将相关

大局观:
理解HTTP不在于记忆状态码,而在于内化协议演进中固化的性能权衡。HTTP/1.0打开了大门。HTTP/1.1使其在规模上可用。HTTP/2通过在单个TCP连接上多路复用流来推动效率。而基于UDP上QUIC构建的HTTP/3,终于突破了数十年的旧约束。

在我们这个微秒级至关重要、移动连接变化无常的互联世界中,这些协议改进不仅仅是技术好奇心——它们是使现代Web体验成为可能的基础。


作为网络工程师和系统架构师,我们的工作不仅是实现这些协议,更要理解它们的基本权衡,并为每个特定挑战选择正确的工具。Web性能的未来不在于任何单一协议,而在于理解何时以及如何有效地利用每一个协议。

AI提效 Claude Sonnet 4+ ChatGPT
此系列,多多少少都会用到AI。

背景

刚购置了一台服务器,准备付尾款了,但是因为服务器是全新的,只装了几台虚拟机,显卡啥的都还没有用,所以在付钱之前需要先测试一下,看看表现。

跑了一整晚,第二天,看着没问题就删掉了虚拟机,忘了截图。

服务器配置

  1. CPU: 2*96
  2. 内存:384G
  3. 硬盘:5*3.4T
  4. 显卡:2*RTX5880

准备工作

  1. 开启显卡直通
  2. 停掉所有虚拟机
  3. 新建一台临时虚拟机
    1. 分配所有CPU+350GB内存+500G硬盘+2*RTX5880
    2. 安装ubuntu 24.04

安装显卡对应的驱动

1
2
3
sudo apt install ubuntu-drivers-common
ubuntu-drivers devices
sudo ubuntu-drivers autoinstall

安装烤鸡需要的工具

  1. gpu-burn
  2. stress-ng

系统测试脚本( GPU + CPU + 内存 + IO综合烤机测试)

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
38
39
#!/bin/bash

set -e

# 设置工具路径(修改为你实际的 gpu_burn 目录)
GPU_BURN_PATH="$HOME/gpu-burn/gpu_burn"

# 检查是否存在命令
command -v stress-ng >/dev/null 2>&1 || { echo >&2 "需要安装 stress-ng:sudo apt install stress-ng -y"; exit 1; }
command -v fio >/dev/null 2>&1 || { echo >&2 "需要安装 fio:sudo apt install fio -y"; exit 1; }

# 检查 GPU_BURN 是否存在
if [ ! -f "$GPU_BURN_PATH" ]; then
echo "找不到 gpu_burn,可在 https://github.com/wilicc/gpu-burn 下载并编译"
exit 1
fi

echo "开始 GPU + CPU + 内存 + IO 综合烤机测试"

# GPU 测试
"$GPU_BURN_PATH" 86400 &
GPU_PID=$!

# CPU 测试
stress-ng --cpu 0 --timeout 24h --metrics-brief &
CPU_PID=$!

# 内存测试
stress-ng --vm 4 --vm-bytes 80% --timeout 24h --metrics-brief &
MEM_PID=$!

# 磁盘I/O测试
fio --name=randwrite --ioengine=libaio --iodepth=16 --rw=randwrite --bs=4k --direct=1 --size=4G --numjobs=4 --runtime=86400 --group_reporting &
IO_PID=$!

echo "测试开始,PID: GPU=$GPU_PID, CPU=$CPU_PID, MEM=$MEM_PID, IO=$IO_PID"
wait


监控几种方案

  1. watch命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 每5秒刷新GPU状态
    watch -n 5 nvidia-smi

    # 每10秒刷新(减少刷新频率)
    watch -n 10 nvidia-smi

    # 只看关键信息
    watch -n 5 "nvidia-smi --query-gpu=name,temperature.gpu,power.draw,utilization.gpu,memory.used --format=csv"

  2. nvtop
    1
    2
    3
    4
    5
    6
    7
    bash# 安装nvtop - GPU实时监控工具
    sudo apt update
    sudo apt install nvtop -y

    # 运行nvtop
    nvtop

3.安装netdata + GPU集成插件

AI提效 Claude Sonnet 4
此系列都是基于AI辅助实现

国外的

1
2
# 能访问国外的一件搞定,比如我的Bandwagon
curl -fsSL https://get.docker.com | sudo bash

国内的

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/bin/bash

# Docker和Docker Compose完整安装脚本 for Ubuntu
# 支持Ubuntu 20.04/22.04/24.04及更新版本

set -e # 遇到错误时退出

echo "================================"
echo "Docker和Docker Compose安装脚本"
echo "================================"

# 检查是否为root用户
if [ "$EUID" -eq 0 ]; then
echo "请不要使用root用户运行此脚本"
echo "请使用普通用户运行:./install_docker.sh"
exit 1
fi

# 检查系统版本
if ! command -v lsb_release &> /dev/null; then
sudo apt update
sudo apt install -y lsb-release
fi

UBUNTU_VERSION=$(lsb_release -rs)
echo "检测到Ubuntu版本: $UBUNTU_VERSION"

# 备份原有的sources.list
echo "1. 备份并配置APT镜像源..."
sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup.$(date +%Y%m%d_%H%M%S)

# 配置阿里云APT镜像源
sudo tee /etc/apt/sources.list > /dev/null <<EOF
# 阿里云镜像源
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs) main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-backports main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ $(lsb_release -cs)-security main restricted universe multiverse
EOF

# 更新系统
echo "2. 更新系统包..."
sudo apt update

# 安装必要依赖
echo "3. 安装必要依赖..."
sudo apt install -y \
apt-transport-https \
ca-certificates \
curl \
software-properties-common \
gnupg \
lsb-release

# 添加Docker官方GPG密钥(使用阿里云镜像)
echo "4. 添加Docker GPG密钥..."
curl -fsSL https://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 添加Docker仓库(使用阿里云镜像)
echo "5. 添加Docker仓库..."
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 更新包索引
echo "6. 更新包索引..."
sudo apt update

# 安装Docker
echo "7. 安装Docker..."
sudo apt install -y docker-ce docker-ce-cli containerd.io

# 启动Docker服务并设置开机自启
echo "8. 启动Docker服务..."
sudo systemctl start docker
sudo systemctl enable docker

# 将当前用户添加到docker组
echo "9. 配置用户权限..."
sudo usermod -aG docker $USER

# 配置Docker国内镜像加速器
echo "10. 配置Docker镜像加速器..."
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<EOF
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://dockerhub.icu",
"https://docker.chenby.cn",
"https://dockerproxy.com"
],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
EOF

# 重启Docker服务以应用配置
echo "11. 重启Docker服务..."
sudo systemctl daemon-reload
sudo systemctl restart docker

# 安装Docker Compose
echo "12. 安装Docker Compose..."

# 检查Ubuntu版本,选择合适的安装方法
if [[ $(echo "$UBUNTU_VERSION >= 22.04" | bc -l) -eq 1 ]] 2>/dev/null || [[ "$UBUNTU_VERSION" == "22.04" ]] || [[ "$UBUNTU_VERSION" > "22.04" ]]; then
# Ubuntu 22.04及以上版本,使用官方插件
echo " 使用官方插件安装Docker Compose..."
sudo apt install -y docker-compose-plugin
COMPOSE_CMD="docker compose"
else
# Ubuntu 20.04及以下版本,使用二进制文件安装
echo " 使用二进制文件安装Docker Compose..."
COMPOSE_VERSION="v2.24.1"
sudo curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
COMPOSE_CMD="docker-compose"
fi

# 等待Docker服务完全启动
echo "13. 等待Docker服务启动..."
sleep 3

# 测试安装
echo "14. 测试安装..."
echo "Docker版本:"
docker --version

echo "Docker Compose版本:"
if [[ "$COMPOSE_CMD" == "docker compose" ]]; then
docker compose version
else
docker-compose --version
fi

echo "Docker服务状态:"
sudo systemctl is-active docker

echo ""
echo "================================"
echo "安装完成!"
echo "================================"
echo ""
echo "重要提示:"
echo "1. 请重新登录系统或运行 'newgrp docker' 来使用户组权限生效"
echo "2. 然后运行 'docker run hello-world' 来测试安装"
echo ""
if [[ "$COMPOSE_CMD" == "docker compose" ]]; then
echo "Docker Compose命令: docker compose (注意是空格)"
echo "如果习惯使用 docker-compose,可以创建别名:"
echo "echo 'alias docker-compose=\"docker compose\"' >> ~/.bashrc && source ~/.bashrc"
else
echo "Docker Compose命令: docker-compose"
fi
echo ""
echo "常用命令:"
echo " docker --version # 查看Docker版本"
echo " $COMPOSE_CMD version # 查看Docker Compose版本"
echo " docker images # 查看镜像列表"
echo " docker ps # 查看运行中的容器"
echo " docker run hello-world # 测试Docker安装"
echo ""
echo "镜像加速器已配置,拉取镜像速度会更快!"
echo ""
echo "如需卸载Docker,请运行:"
echo "sudo apt purge docker-ce docker-ce-cli containerd.io docker-compose-plugin"
echo "sudo rm -rf /var/lib/docker /etc/docker"

如何优化Spring Boot应用以处理每秒百万请求

在当今高流量的互联网环境中,应用程序需要处理的请求量正在迅速增长。作为Java开发者,我们经常面临如何扩展Spring Boot应用以处理大量并发请求的挑战。本文将探讨如何将Spring Boot应用从处理每秒5万请求优化到每秒100万请求的实用策略,并提供详细的代码示例和实施步骤。

面临的挑战

想象一下这样的场景:你的团队被告知应用需要在三个月内实现20倍的流量增长,从每秒5万请求提升到每秒100万请求,而且硬件预算有限。这听起来几乎是不可能完成的任务。然而,通过深入分析性能瓶颈和应用一系列优化技术,我们可以达成这一目标。

在开始优化之前,我们需要了解系统的现状:

  • 基于Spring Boot 2.x的传统MVC架构
  • 使用内嵌的Tomcat服务器
  • 采用JPA进行数据库访问
  • 典型的N层架构(Controller > Service > Repository)
  • 高峰期出现频繁的GC暂停和超时错误

关键性能指标

在开始优化过程之前,了解我们的目标至关重要。成功优化后的应用应该达到以下性能指标:

  • 最大吞吐量:每秒120万请求
  • 平均响应时间:85毫秒(优化前为350毫秒)
  • 95%响应时间:120毫秒(优化前为850毫秒)
  • 峰值CPU使用率:60-70%(优化前为85-95%)
  • 内存使用:可用堆的50%(优化前为75%)
  • 数据库查询:通过缓存减少70%

优化策略

1. 采用响应式编程模型(Spring WebFlux)

最有影响力的变化是采用Spring WebFlux进行响应式编程。这不仅仅是简单的替换,而是需要重新思考应用程序的结构。首先,更新项目依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 将spring-boot-starter-web替换为spring-boot-starter-webflux -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>2.7.5</version>
</dependency>

<!-- 使用Netty作为服务器 -->
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>1.0.24</version>
</dependency>

<!-- 引入Reactor测试支持 -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>

然后,改变控制器实现方式:

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
38
39
40
41
42
43
44
// 传统Spring MVC控制器
@RestController
@RequestMapping("/api/products")
public class ProductController {

@Autowired
private ProductService productService;

@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}

@GetMapping
public ResponseEntity<List<Product>> getAllProducts() {
List<Product> products = productService.getAllProducts();
return ResponseEntity.ok(products);
}
}

// 响应式WebFlux控制器
@RestController
@RequestMapping("/api/products")
public class ReactiveProductController {

@Autowired
private ReactiveProductService productService;

@GetMapping("/{id}")
public Mono<ResponseEntity<Product>> getProduct(@PathVariable Long id) {
return productService.getProductById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}

@GetMapping
public Mono<ResponseEntity<List<Product>>> getAllProducts() {
return productService.getAllProducts()
.collectList()
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
}

服务层也需要相应改变:

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
// 传统的服务实现
@Service
public class ProductService {
@Autowired
private ProductRepository repository;

public Product getProductById(Long id) {
return repository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}

public List<Product> getAllProducts() {
return repository.findAll();
}
}

// 响应式服务实现
@Service
public class ReactiveProductService {
@Autowired
private ReactiveProductRepository repository;

public Mono<Product> getProductById(Long id) {
return repository.findById(id)
.switchIfEmpty(Mono.error(new ProductNotFoundException(id)));
}

public Flux<Product> getAllProducts() {
return repository.findAll();
}
}

2. 优化数据库访问

数据库通常是系统的主要瓶颈。以下是一些优化数据库访问的详细策略:

2.1 采用响应式数据库驱动

将传统的JDBC驱动替换为响应式数据库驱动:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 添加R2DBC依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>

<!-- MySQL的R2DBC驱动 -->
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<version>0.8.2.RELEASE</version>
</dependency>

配置R2DBC连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class R2dbcConfig {

@Bean
public ConnectionFactory connectionFactory() {
return MySqlConnectionFactory.from(
MySqlConnectionConfiguration.builder()
.host("localhost")
.port(3306)
.username("root")
.password("password")
.database("reactive_demo")
.build()
);
}

@Bean
public R2dbcEntityTemplate r2dbcEntityTemplate(ConnectionFactory connectionFactory) {
return new R2dbcEntityTemplate(connectionFactory);
}
}

创建响应式Repository:

1
2
3
4
5
6
7
8
9
public interface ReactiveProductRepository extends ReactiveCrudRepository<Product, Long> {

Flux<Product> findByCategory(String category);

Mono<Product> findByName(String name);

@Query("SELECT * FROM products WHERE price > :minPrice")
Flux<Product> findExpensiveProducts(BigDecimal minPrice);
}

2.2 实施多级缓存策略

引入Redis缓存:

1
2
3
4
5
<!-- Redis响应式支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

配置Redis缓存:

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
@Configuration
@EnableRedisRepositories
public class RedisConfig {

@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
return new LettuceConnectionFactory("localhost", 6379);
}

@Bean
public ReactiveRedisTemplate<String, Product> reactiveRedisTemplate(
ReactiveRedisConnectionFactory factory) {

StringRedisSerializer keySerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer<Product> valueSerializer =
new Jackson2JsonRedisSerializer<>(Product.class);

RedisSerializationContext.RedisSerializationContextBuilder<String, Product> builder =
RedisSerializationContext.newSerializationContext(keySerializer);

RedisSerializationContext<String, Product> context =
builder.value(valueSerializer).build();

return new ReactiveRedisTemplate<>(factory, context);
}
}

在服务层实现缓存:

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
38
39
40
@Service
public class CachedProductService {

private static final String CACHE_KEY_PREFIX = "product:";
private static final Duration CACHE_TTL = Duration.ofMinutes(10);

@Autowired
private ReactiveProductRepository repository;

@Autowired
private ReactiveRedisTemplate<String, Product> redisTemplate;

public Mono<Product> getProductById(Long id) {
String cacheKey = CACHE_KEY_PREFIX + id;

// 先尝试从缓存获取
return redisTemplate.opsForValue().get(cacheKey)
.switchIfEmpty(
// 缓存未命中,从数据库加载
repository.findById(id)
.flatMap(product -> {
// 将结果存入缓存
return redisTemplate.opsForValue()
.set(cacheKey, product, CACHE_TTL)
.thenReturn(product);
})
);
}

public Flux<Product> getAllProducts() {
// 对于列表类型的数据,可以使用不同的缓存策略
return repository.findAll();
}

// 清除缓存的方法,在更新产品时调用
public Mono<Void> invalidateCache(Long id) {
String cacheKey = CACHE_KEY_PREFIX + id;
return redisTemplate.delete(cacheKey);
}
}

2.3 优化SQL查询

使用索引优化:

1
2
3
4
-- 为常用查询字段添加索引
CREATE INDEX idx_product_category ON products(category);
CREATE INDEX idx_product_price ON products(price);
CREATE INDEX idx_product_name ON products(name);

优化查询方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 避免使用count(*)
public Mono<Boolean> existsByCategory(String category) {
return repository.findFirstByCategory(category)
.map(product -> true)
.defaultIfEmpty(false);
}

// 使用分页减少数据传输量
public Flux<Product> getProductsByCategory(String category, int page, int size) {
return repository.findByCategory(category,
PageRequest.of(page, size, Sort.by("name").ascending()));
}

// 使用投影只返回需要的字段
@Query("SELECT id, name, price FROM products WHERE category = :category")
Flux<ProductSummary> findProductSummariesByCategory(String category);

2.4 实现读写分离

配置主从数据源:

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
38
@Configuration
public class DatabaseConfig {

@Bean
@Primary
@Qualifier("masterConnectionFactory")
public ConnectionFactory masterConnectionFactory() {
return MySqlConnectionFactory.from(
MySqlConnectionConfiguration.builder()
.host("master-db.example.com")
.port(3306)
.username("master_user")
.password("master_pass")
.database("products")
.build()
);
}

@Bean
@Qualifier("slaveConnectionFactory")
public ConnectionFactory slaveConnectionFactory() {
return MySqlConnectionFactory.from(
MySqlConnectionConfiguration.builder()
.host("slave-db.example.com")
.port(3306)
.username("read_user")
.password("read_pass")
.database("products")
.build()
);
}

@Bean
public TransactionManager transactionManager(
@Qualifier("masterConnectionFactory") ConnectionFactory connectionFactory) {
return new R2dbcTransactionManager(connectionFactory);
}
}

创建读写分离的Repository:

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
@Repository
public class ReadWriteProductRepository {

private final R2dbcEntityTemplate masterTemplate;
private final R2dbcEntityTemplate slaveTemplate;

public ReadWriteProductRepository(
@Qualifier("masterConnectionFactory") ConnectionFactory masterFactory,
@Qualifier("slaveConnectionFactory") ConnectionFactory slaveFactory) {
this.masterTemplate = new R2dbcEntityTemplate(masterFactory);
this.slaveTemplate = new R2dbcEntityTemplate(slaveFactory);
}

// 写操作使用主库
@Transactional
public Mono<Product> save(Product product) {
return masterTemplate.insert(Product.class)
.into("products")
.using(product);
}

// 读操作使用从库
public Mono<Product> findById(Long id) {
return slaveTemplate.select(Product.class)
.from("products")
.matching(Query.query(Criteria.where("id").is(id)))
.one();
}

public Flux<Product> findAll() {
return slaveTemplate.select(Product.class)
.from("products")
.all();
}
}

3. 配置调优

性能提升往往来自于正确的配置调整。以下是详细的配置优化步骤:

3.1 调整Netty服务器参数

application.yml中配置Netty参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
webflux:
base-path: /api

netty:
connection:
timeout: 5000 # 连接超时时间(毫秒)
worker:
count: 16 # worker线程数量, 建议设置为CPU核心数的2倍
boss:
count: 2 # boss线程数量
buffer:
size: 32768 # 缓冲区大小(字节)

在Java代码中自定义Netty配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Configuration
public class NettyConfig {

@Bean
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();

factory.addServerCustomizers(server -> {
HttpServer httpServer = (HttpServer) server;

// 设置更大的接收缓冲区大小
httpServer.tcpConfiguration(tcpServer ->
tcpServer.option(ChannelOption.SO_RCVBUF, 128 * 1024)
.option(ChannelOption.SO_SNDBUF, 128 * 1024)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.TCP_NODELAY, true));

return httpServer;
});

return factory;
}
}

3.2 优化JVM堆和垃圾收集器

application.properties中添加JVM参数,或者直接在启动脚本中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 堆内存设置
-Xms4g
-Xmx4g

# 年轻代大小,建议为堆的1/3到1/2
-Xmn2g

# GC相关设置,使用G1收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=2
-XX:InitiatingHeapOccupancyPercent=70

# 禁用类元数据的GC,防止Full GC
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m

# 优化内存分配
-XX:+AlwaysPreTouch
-XX:+DisableExplicitGC

# 开启GC日志记录
-Xlog:gc*:file=logs/gc-%t.log:time,uptime,level,tags:filecount=5,filesize=100m

3.3 连接池优化

配置R2DBC连接池:

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
@Configuration
public class ConnectionPoolConfig {

@Bean
public ConnectionFactory connectionFactory() {
ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder()
.connectionFactory(createConnectionFactory())
.maxIdleTime(Duration.ofMinutes(30))
.maxLifeTime(Duration.ofHours(2))
.maxAcquireTime(Duration.ofSeconds(3))
.initialSize(10)
.maxSize(50) // 根据负载调整
.minIdle(10)
.build();

return new ConnectionPool(configuration);
}

private ConnectionFactory createConnectionFactory() {
return MySqlConnectionFactory.from(
MySqlConnectionConfiguration.builder()
.host("localhost")
.port(3306)
.username("root")
.password("password")
.database("reactive_demo")
.connectTimeout(Duration.ofSeconds(3))
.build()
);
}
}

配置Redis连接池:

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
@Configuration
public class RedisConnectionConfig {

@Bean
public ReactiveRedisConnectionFactory reactiveRedisConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofMillis(100)) // 命令超时
.shutdownTimeout(Duration.ZERO) // 关闭超时
.clientOptions(ClientOptions.builder()
.socketOptions(SocketOptions.builder()
.connectTimeout(Duration.ofMillis(100)) // 连接超时
.keepAlive(true)
.build())
.build())
.clientResources(DefaultClientResources.builder()
.ioThreadPoolSize(4) // IO线程池大小
.computationThreadPoolSize(4) // 计算线程池大小
.build())
.build();

RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration();
serverConfig.setHostName("localhost");
serverConfig.setPort(6379);

return new LettuceConnectionFactory(serverConfig, clientConfig);
}
}

3.4 HTTP客户端调优

配置WebClient高性能连接池:

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
@Configuration
public class WebClientConfig {

@Bean
public WebClient webClient() {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.doOnConnected(conn ->
conn.addHandlerLast(new ReadTimeoutHandler(10))
.addHandlerLast(new WriteTimeoutHandler(10)))
.responseTimeout(Duration.ofSeconds(5))
.compress(true)
.wiretap(true) // 开发环境调试使用,生产环境应关闭
.keepAlive(true)
.poolResources(ConnectionProvider.builder("custom")
.maxConnections(500)
.pendingAcquireTimeout(Duration.ofMillis(5000))
.pendingAcquireMaxCount(1000)
.maxIdleTime(Duration.ofMillis(8000))
.build());

return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.codecs(configurer ->
configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024))
.build();
}
}

4. 混合架构方法

不是所有的端点都需要改为响应式的。下面是混合架构的详细实现:

4.1 定义混合架构路由

使用Spring的路由器函数:

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
@Configuration
public class RouterConfig {

// 高流量端点使用响应式处理
@Bean
public RouterFunction<ServerResponse> highTrafficRoutes(
ReactiveProductHandler productHandler) {

return RouterFunctions
.route(GET("/api/products").and(accept(APPLICATION_JSON)),
productHandler::getAllProducts)
.andRoute(GET("/api/products/{id}").and(accept(APPLICATION_JSON)),
productHandler::getProduct)
.andRoute(GET("/api/products/category/{category}").and(accept(APPLICATION_JSON)),
productHandler::getProductsByCategory);
}

// 低流量的管理端点保留传统MVC
@Bean
public WebMvcConfigurer mvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/admin").setViewName("admin");
registry.addViewController("/dashboard").setViewName("dashboard");
}
};
}
}

4.2 响应式处理器实现

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
38
39
40
41
42
@Component
public class ReactiveProductHandler {

private final ReactiveProductService productService;

public ReactiveProductHandler(ReactiveProductService productService) {
this.productService = productService;
}

public Mono<ServerResponse> getProduct(ServerRequest request) {
Long productId = Long.valueOf(request.pathVariable("id"));

return productService.getProductById(productId)
.flatMap(product ->
ServerResponse.ok()
.contentType(APPLICATION_JSON)
.bodyValue(product))
.switchIfEmpty(ServerResponse.notFound().build());
}

public Mono<ServerResponse> getAllProducts(ServerRequest request) {
return productService.getAllProducts()
.collectList()
.flatMap(products ->
ServerResponse.ok()
.contentType(APPLICATION_JSON)
.bodyValue(products))
.switchIfEmpty(ServerResponse.notFound().build());
}

public Mono<ServerResponse> getProductsByCategory(ServerRequest request) {
String category = request.pathVariable("category");

return productService.getProductsByCategory(category)
.collectList()
.flatMap(products ->
ServerResponse.ok()
.contentType(APPLICATION_JSON)
.bodyValue(products))
.switchIfEmpty(ServerResponse.notFound().build());
}
}

4.3 实现逐步迁移策略

创建适配器以支持新旧代码互操作:

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
@Component
public class ProductServiceAdapter {

private final ReactiveProductService reactiveService;
private final LegacyProductService legacyService;

@Autowired
public ProductServiceAdapter(
ReactiveProductService reactiveService,
LegacyProductService legacyService) {
this.reactiveService = reactiveService;
this.legacyService = legacyService;
}

// 将响应式服务适配到传统服务
public Product getProductSync(Long id) {
return reactiveService.getProductById(id).block();
}

// 将传统服务适配到响应式服务
public Mono<Product> getProductReactive(Long id) {
return Mono.fromCallable(() -> legacyService.getProductById(id))
.subscribeOn(Schedulers.boundedElastic());
}

// 批量操作适配
public List<Product> getAllProductsSync() {
return reactiveService.getAllProducts().collectList().block();
}

public Flux<Product> getAllProductsReactive() {
return Flux.defer(() -> Flux.fromIterable(legacyService.getAllProducts()))
.subscribeOn(Schedulers.boundedElastic());
}
}

5. 水平扩展与Kubernetes

在优化应用程序之后,可以通过Kubernetes进行智能水平扩展。以下是详细的配置和实现:

5.1 创建Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM openjdk:17-jdk-slim

WORKDIR /app

COPY target/reactive-product-service-0.0.1-SNAPSHOT.jar app.jar

# 设置JVM参数
ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -Xms1g -Xmx1g -XX:+AlwaysPreTouch"

EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=5s --timeout=3s --retries=3 CMD curl -f http://localhost:8080/actuator/health || exit 1

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

5.2 创建Kubernetes部署配置

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
apiVersion: apps/v1
kind: Deployment
metadata:
name: product-service
namespace: ecommerce
spec:
replicas: 3
selector:
matchLabels:
app: product-service
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
app: product-service
spec:
containers:
- name: product-service
image: my-registry/product-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "1000m"
memory: "2Gi"
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 20
timeoutSeconds: 5
failureThreshold: 3
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: SERVER_PORT
value: "8080"
- name: JAVA_OPTS
value: "-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -Xms1g -Xmx1g -XX:+AlwaysPreTouch"

5.3 创建服务和入口配置

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
apiVersion: v1
kind: Service
metadata:
name: product-service
namespace: ecommerce
spec:
selector:
app: product-service
ports:
- port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: product-service-ingress
namespace: ecommerce
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
nginx.ingress.kubernetes.io/proxy-send-timeout: "60"
spec:
rules:
- host: api.example.com
http:
paths:
- path: /api/products
pathType: Prefix
backend:
service:
name: product-service
port:
number: 80

5.4 配置自动扩缩容

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
38
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: product-service-hpa
namespace: ecommerce
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: product-service
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 20
periodSeconds: 60

5.5 实现优雅降级机制

在应用程序中添加断路器模式:

1
2
3
4
5
6
7
8
9
10
11
<!-- 添加Resilience4j依赖 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
<version>1.7.1</version>
</dependency>

配置断路器:

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class ResilienceConfig {

@Bean
public CircuitBreakerRegistry circuitBreakerRegistry() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.slowCallRateThreshold(50)
.slowCallDurationThreshold(Duration.ofSeconds(1))
.permittedNumberOfCallsInHalfOpenState(10)
.minimumNumberOfCalls(20)
.slidingWindowType(CircuitBreakerConfig.SlidingWindowType.

参考

https://medium.com/javarevisited/how-i-optimized-a-spring-boot-application-to-handle-1m-requests-second-0cbb2f2823ed

AI 第五篇

这几天想起翻起来Google之前新发布的白皮书《Prompt Engineering》,学习下再简单用Claude提取下主要内容记录下。

掌握提示工程:打造AI高效交互的艺术与科学

在人工智能迅猛发展的今天,提示工程(Prompt Engineering)已成为连接人类意图与AI输出的关键桥梁。Google最新发布的白皮书《Prompt Engineering》为我们提供了全面而深入的指南,让我们一起探索如何通过精心设计的提示让大语言模型发挥最大潜力。

提示工程的本质

提示工程是设计高质量提示的过程,旨在引导大语言模型(LLM)产生准确、相关且有用的输出。正如白皮书中所强调的:

“你不需要成为数据科学家或机器学习工程师——每个人都可以编写提示。然而,设计最有效的提示可能很复杂。”

大语言模型本质上是一个预测引擎,它接收序列文本作为输入,然后基于训练数据预测下一个词元(token)应该是什么。当你编写提示时,你正在尝试设置LLM以预测正确的词元序列。

理解大语言模型的输出配置

在开始提示工程之前,了解如何配置LLM输出至关重要:

输出长度控制

控制模型生成的词元数量是一项重要配置。生成更多词元需要更多计算资源,导致能耗增加、响应时间潜在变慢,以及成本提高。限制输出长度对于某些提示技术特别重要,如ReAct(推理与行动),在这种情况下,LLM会在你想要的响应后继续生成无用词元。

采样控制机制

LLM不会正式预测单个词元,而是为每个可能的下一个词元分配概率。这些词元概率然后被采样以确定下一个生成的词元。

温度参数

温度控制词元选择中的随机性程度:

  • 低温度(接近0):适用于需要确定性响应的提示,选择概率最高的词元
  • 高温度(接近1或更高):产生更多样化或意外的结果,使所有词元被选中的可能性更加均等

Top-K和Top-P(核采样)

这两种设置限制预测的下一个词元只能来自概率最高的词元:

  • Top-K采样:从模型预测分布中选择K个最可能的词元
  • Top-P采样:选择累积概率不超过特定值(P)的最可能词元

综合配置时,一般起点建议:

  • 相对连贯且有创意的结果:温度0.2,top-P 0.95,top-K 30
  • 特别有创意的结果:温度0.9,top-P 0.99,top-K 40
  • 较少创意的结果:温度0.1,top-P 0.9,top-K 20
  • 只有一个正确答案的任务(如数学问题):温度0

提示技术详解

零样本提示(Zero-shot)

最简单的提示类型,仅提供任务描述和一些文本让LLM开始。适用于模型已经理解的简单任务。

示例

1
2
3
将电影评论分类为积极、中性或消极。
评论:"她"是一项揭示人工智能发展方向的研究,如果AI继续不受控制地发展,人类将走向何方。我希望有更多像这样的杰作。
情感:

单样本和少样本提示(One-shot & Few-shot)

通过在提示中提供一个或多个示例,帮助模型理解所需的输出格式或模式。这种方法特别适用于需要特定输出结构的任务。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
解析客户的披萨订单为有效的JSON:

例子1:
我想要一个小号披萨,配芝士、番茄酱和意大利香肠。
JSON响应:
{
"size": "small",
"type": "normal",
"ingredients": [["cheese", "tomato sauce", "pepperoni"]]
}

例子2:
我想要一个大号披萨,配番茄酱、罗勒和马苏里拉奶酪。
{
"size": "large",
"type": "normal",
"ingredients": [["tomato sauce", "basil", "mozzarella"]]
}

现在,我想要一个大号披萨,一半是芝士和马苏里拉奶酪,另一半是番茄酱、火腿和菠萝。
JSON响应:

少样本提示的示例数量取决于任务复杂性、示例质量和模型能力。一般建议至少使用3-5个示例,复杂任务可能需要更多。

系统、角色和上下文提示

这三种技术都用于引导LLM生成文本,但侧重点不同:

系统提示

设置语言模型的整体上下文和目的,定义模型应该做什么的”大局观”。

示例

1
2
3
将电影评论分类为积极、中性或消极。只返回大写的标签。
评论:"她"是一部揭示AI不受控制进化可能导致人类走向何方的令人不安的研究。它太令人不安以至于我无法观看完。
情感:

角色提示

为语言模型分配特定角色或身份,帮助模型生成与该角色一致的响应。

示例

1
2
3
我希望你扮演旅游指南的角色。我会告诉你我的位置,你将为我推荐3个附近可以参观的地方。在某些情况下,我还会告诉你我想参观的地点类型。
我的建议:"我在阿姆斯特丹,我只想参观博物馆。"
旅游建议:

上下文提示

提供与当前对话或任务相关的具体细节或背景信息,帮助模型理解要求的细微差别。

示例

1
2
上下文:你正在为一个关于80年代街机视频游戏的博客撰写文章。
建议3个可以撰写文章的主题,并简要描述这些文章应该包含什么内容。

退一步提示(Step-back)

先考虑与特定任务相关的一般问题,然后将答案用于后续提示,激活相关背景知识和推理过程。

示例

1
2
3
步骤1:基于流行的第一人称射击游戏,什么是5个虚构的关键设置有助于第一人称射击游戏中挑战和引人入胜的关卡故事线?

步骤2:利用上面的主题,为第一人称射击游戏编写一个新关卡的一段故事情节,既具挑战性又引人入胜。

思维链提示(Chain of Thought)

通过生成中间推理步骤来改善推理能力,特别适用于数学问题、逻辑推理等需要分步思考的任务。

示例

1
2
3
4
5
6
当我3岁时,我的伙伴是我年龄的3倍。现在,我20岁了。我的伙伴多大?让我们一步一步思考。

1. 当我3岁时,我的伙伴是3*3=9岁。
2. 这意味着我的伙伴比我大6岁。
3. 现在我20岁了,所以我的伙伴应该是20+6=26岁。
因此,我的伙伴现在26岁。

思维链提示可以与单样本或少样本结合使用,效果更佳:

1
2
3
4
5
问题:当我哥哥2岁时,我的年龄是他的两倍。现在我40岁了。我哥哥多大?让我们一步一步思考。
答案:当我哥哥2岁时,我是2*2=4岁。这意味着我比他大2岁。现在我40岁了,所以我哥哥是40-2=38岁。答案是38。

问题:当我3岁时,我的伙伴是我年龄的3倍。现在,我20岁了。我的伙伴多大?让我们一步一步思考。
答案:

自我一致性(Self-consistency)

结合抽样和多数投票,生成多样化的推理路径并选择最一致的答案。这种方法改进了LLM在各种任务中的准确性和回答一致性。

过程

  1. 使用相同提示多次,生成不同的推理路径
  2. 从每个生成的响应中提取答案
  3. 选择最常见的答案

思维树(Tree of Thoughts)

允许LLM同时探索多个不同的推理路径,通过在树的不同节点分支来解决问题。特别适合需要探索的复杂任务。

推理与行动(ReAct)

结合自然语言推理与执行外部工具操作的能力,使LLM能够解决复杂任务。例如,查询网络获取信息,然后基于结果继续推理。

Python示例

1
2
3
4
5
6
7
8
9
10
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import VertexAI

prompt = "Metallica乐队成员有多少个孩子?"
llm = VertexAI(temperature=0.1)
tools = load_tools(["serpapi"], llm=llm)
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.run(prompt)

自动提示工程(Automatic Prompt Engineering)

利用LLM自身生成更多提示,评估它们,并可能修改好的提示,然后重复这个过程:

  1. 编写生成输出变体的提示
  2. 根据选定指标评估所有指令候选
  3. 选择评估分数最高的指令候选

代码提示技巧

编写代码提示

LLM可以帮助开发人员加速编写代码过程,实现各种自动化任务:

示例

1
用Bash编写一个代码片段,询问文件夹名称。然后将该文件夹的内容重命名,在文件名前加上"draft"。

解释代码提示

LLM也可以帮助理解他人的代码:

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
解释以下Bash代码:
#!/bin/bash
echo "Enter the folder name: "
read folder_name
if [ ! -d "$folder_name" ]; then
echo "Folder does not exist."
exit 1
fi
files=( "$folder_name"/* )
for file in "${files[@]}"; do
new_file_name="draft_$(basename "$file")"
mv "$file" "$new_file_name"
done
echo "Files renamed successfully."

翻译代码提示

将代码从一种语言翻译到另一种语言:

示例

1
2
将以下Bash代码翻译为Python片段:
[Bash代码]

调试和审查代码提示

修复代码中的错误并提供改进建议:

示例

1
2
3
4
5
6
7
8
以下Python代码报错:
Traceback (most recent call last):
File "/Users/leeboonstra/Documents/test_folder/rename_files.py", line 7, in <module>
text = toUpperCase(prefix)
NameError: name 'toUpperCase' is not defined

调试错误并解释如何改进代码。
[Python代码]

提示工程最佳实践

提供示例

在提示中提供示例是最重要的最佳实践之一,它作为强大的教学工具,展示期望的输出或类似响应,提高模型输出的准确性、风格和语调。

设计简单

提示应简洁、清晰、易于理解。如果对你来说已经令人困惑,对模型来说也可能如此。避免使用复杂语言和提供不必要的信息。

示例改进

1
2
3
4
5
改进前:
我现在正在纽约参观,想了解更多关于好地方的信息。我带着两个3岁的孩子。我们假期应该去哪里?

改进后:
作为旅游指南,描述纽约曼哈顿适合带3岁孩子参观的好地方。

明确输出要求

具体说明所需的输出形式。简洁的指令可能无法充分引导LLM或过于笼统。

示例

1
生成一篇关于5大视频游戏主机的3段落博客文章。博客文章应该信息丰富且引人入胜,并以对话风格撰写。

使用指令而非约束

指令和约束在提示中用于引导LLM输出,但研究表明,专注于积极指令比严重依赖约束更有效:

  • 指令:直接传达期望的结果
  • 约束:设置响应的限制或边界

示例

1
2
3
4
5
推荐做法:
生成一段关于5大视频游戏主机的博客文章。只讨论主机、制造公司、发布年份和总销量。

避免做法:
生成一段关于5大视频游戏主机的博客文章。不要列出视频游戏名称。

控制最大令牌长度

通过配置或在提示中明确要求特定长度来控制生成的LLM响应长度:

1
用推特长度的消息解释量子物理学。

在提示中使用变量

使用变量使提示更加动态和可重用,特别是在集成到应用程序中时:

1
2
3
4
5
变量:
{city} = "阿姆斯特丹"

提示:
你是一名旅游指南。告诉我关于这个城市的一个事实:{city}

实验输入格式和写作风格

不同的模型、配置、提示格式、词汇选择可能产生不同结果。因此,尝试不同的提示属性如风格、词汇选择和提示类型(零样本、少样本、系统提示)非常重要。

例如,关于Sega Dreamcast的提示可以表述为:

  • 问题:Sega Dreamcast是什么,为什么它是如此革命性的主机?
  • 陈述:Sega Dreamcast是世嘉在1999年发布的第六代视频游戏主机…
  • 指令:写一段描述Sega Dreamcast主机并解释为什么它如此革命性的段落。

分类任务中混合类别

对于分类任务的少样本提示,确保混合可能的响应类别,避免模型只是记住示例顺序而非学习每个类别的关键特征。

适应模型更新

随时了解模型架构变化、添加的数据和新功能。尝试新版模型并调整提示以更好地利用新特性。

尝试不同输出格式

考虑实验输出格式,对于非创意任务(如提取、选择、解析、排序、排名或分类数据),尝试使用JSON或XML等结构化格式。

JSON输出优势

  • 始终以相同风格返回
  • 专注于您想要接收的数据
  • 降低幻觉风险
  • 使其关系感知
  • 获得数据类型
  • 可以排序

处理JSON输出的特殊考虑

JSON修复

虽然以JSON格式返回数据提供了众多优势,但也存在一些缺点。JSON的结构化特性虽然有利于解析和在应用程序中使用,但需要比纯文本更多的词元,导致处理时间增加和成本提高。此外,JSON的冗长可能轻易消耗整个输出窗口,特别是当生成由于词元限制而突然中断时,这通常会导致缺少关键的闭合大括号或方括号,使输出无法使用。

幸运的是,json-repair库(可在PyPI上获取)在这些情况下非常宝贵。此库智能地尝试自动修复不完整或格式错误的JSON对象。

使用JSON模式

JSON模式定义了JSON输入的预期结构和数据类型。通过提供模式,您为LLM提供了一个清晰的数据蓝图,帮助它专注于相关信息并减少误解输入的风险。此外,模式可以帮助建立不同数据片段之间的关系,甚至通过包含特定格式的日期或时间戳字段使LLM”时间感知”。

与其他提示工程师合作实验

如果您需要尝试制定良好的提示,找多人进行尝试可能会有所帮助。当每个人都遵循最佳实践时,您会看到不同提示尝试之间的性能差异。

思维链最佳实践

  • 在推理之后放置答案是必需的,因为推理的生成会改变模型在预测最终答案时获得的词元
  • 使用思维链和自我一致性时,需要能够从提示中提取最终答案,与推理分开
  • 对于思维链提示,将温度设置为0
  • 思维链提示基于贪婪解码,根据语言模型分配的最高概率预测序列中的下一个词。通常,在使用推理得出最终答案时,可能只有一个正确答案,因此温度应始终设置为0

记录提示尝试

详细记录您的提示尝试至关重要,这样您可以随着时间的推移了解什么有效,什么无效。建议使用谷歌表格,包含以下字段:

  • 名称
  • 目标
  • 模型
  • 温度
  • 令牌限制
  • Top-K
  • Top-P
  • 提示
  • 输出

除了这些字段外,跟踪提示版本(迭代)、结果是否OK/NOT OK/SOMETIMES OK的字段,以及反馈字段也很有帮助。

结语

提示工程是一门艺术,也是一门科学,需要持续的实践和改进。通过掌握本白皮书中详述的各种技术和最佳实践,您可以有效地引导大语言模型产生准确、相关且有用的输出,充分发挥AI的潜力,无论是用于个人项目还是企业级应用。

记住,提示工程是一个迭代过程。设计并测试不同的提示,分析并记录结果。根据模型的表现优化您的提示。不断实验,直到达到所需的输出。当您更换模型或模型配置时,回顾并继续使用先前使用的提示进行实验。

通过这种方法,您将能够与AI系统建立更有效、更精确的交互,释放其真正的潜力。