在数据爆炸的时代，传统关系型数据库在全文检索、复杂条件过滤等场景下性能不足。Elasticsearch（简称 ES）作为一款分布式、高扩展、高实时的全文搜索引擎，凭借其强大的检索能力和灵活的架构，成为日志分析、电商搜索、内容推荐等场景的首选工具。本文将从 Elasticsearch 核心概念出发，结合 Java 实战，带你快速入门 ES 的使用。一、Elasticsearch 核心概念在使用 Elasticsearch 前，需要先理解其核心术语和数据模型，这是后续操作的基础。1. 基本概念Elasticsearch 是基于 Lucene 的分布式搜索引擎，其核心概念与传统数据库有对应关系，但设计理念不同：Elasticsearch 概念传统数据库概念说明Index（索引）Database（库）一个索引是具有相似结构的文档集合（如“商品索引”“用户索引”）。Document（文档）Row（行）索引中的每条数据记录，以 JSON 格式存储（如一个商品信息）。Field（字段）Column（列）文档中的属性（如商品的“名称”“价格”）。Mapping（映射）Schema（表结构）定义文档的字段类型、分词器等元数据（类似数据库表的字段定义）。Shard（分片）-索引的分片，分布式存储的核心（一个索引可分为多个分片，分散在不同节点）。Replica（副本）-分片的备份，用于故障转移和负载均衡（副本数越多，查询性能越好）。2. 核心特性全文检索：支持关键词、短语、模糊匹配等复杂检索，内置分词器（可自定义）。分布式架构：自动分片和副本管理，支持水平扩展，应对海量数据。实时性：文档写入后近实时可查（延迟毫秒级）。聚合分析：支持统计、分组、排序等复杂分析（类似 SQL 的 GROUP BY、SUM 等）。二、环境准备1. 安装 Elasticsearch推荐使用 Docker 快速部署（本地安装需提前配置 JDK 环境）：# 拉取 ES 镜像（7.14.0 版本为例，与后续客户端版本保持一致） docker pull elasticsearch:7.14.0 # 启动容器（映射 9200 端口为 HTTP 接口，9300 为节点间通信端口） docker run -d --name es -p 9200:9200 -p 9300:9300 \ -e "discovery.type=single-node" \ # 单节点模式（开发环境） -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \ # 限制 JVM 内存 elasticsearch:7.14.0启动后访问 http://localhost:9200，若返回以下 JSON 则说明启动成功：{ "name" : "xxxx", "cluster_name" : "docker-cluster", "version" : { "number" : "7.14.0", ... }, "tagline" : "You Know, for Search" }2. 引入 Java 客户端依赖Elasticsearch 官方推荐使用 Elasticsearch High Level REST Client（高level REST客户端），它封装了底层 HTTP 通信，支持所有 ES 操作。在 Maven 项目的 pom.xml 中添加依赖（版本需与 ES 服务器一致）： <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.14.0</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.14.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.5</version> </dependency>3. 初始化客户端创建 ElasticsearchClient 工具类，初始化连接：import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; public class ESClient { // 单例客户端实例 private static RestHighLevelClient client; static { // 初始化客户端（连接本地 ES 服务） client = new RestHighLevelClient( RestClient.builder(new HttpHost("localhost", 9200, "http")) ); } // 获取客户端 public static RestHighLevelClient getClient() { return client; } // 关闭客户端（程序退出时调用） public static void close() throws IOException { if (client != null) { client.close(); } } }三、Java 操作 Elasticsearch 实战以“商品搜索”场景为例，演示索引创建、文档 CRUD、查询等核心操作。1. 定义商品实体类文档在 Java 中通常映射为实体类，使用 Jackson 注解指定 JSON 字段名：import com.fasterxml.jackson.annotation.JsonProperty; public class Product { @JsonProperty("id") // 对应文档中的 id 字段 private Long id; @JsonProperty("name") // 商品名称（需分词检索） private String name; @JsonProperty("price") // 价格（数值类型） private Double price; @JsonProperty("category") // 分类（keyword 类型，不分词） private String category; // 构造函数、getter、setter 省略 }2. 创建索引与映射（Mapping）索引需要先定义映射（类似表结构），指定字段类型和分词器。例如为商品索引定义映射：import org.elasticsearch.action.admin.indices.create.CreateIndexRequest; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.common.xcontent.XContentType; public class IndexDemo { public static void createProductIndex() throws IOException { // 1. 创建创建索引的请求 CreateIndexRequest request = new CreateIndexRequest("product"); // 索引名：product // 2. 定义映射（JSON 格式） String mappingJson = "{\n" + " \"mappings\": {\n" + " \"properties\": {\n" + " \"id\": { \"type\": \"long\" },\n" + // 长整型 " \"name\": { \n" + " \"type\": \"text\", \n" + // text 类型支持分词 " \"analyzer\": \"ik_max_word\" \n" + // 使用 IK 分词器（需提前安装） " },\n" + " \"price\": { \"type\": \"double\" },\n" + // 浮点型 " \"category\": { \"type\": \"keyword\" } // keyword 类型不分词 " }\n" + " }\n" + "}"; request.source(mappingJson, XContentType.JSON); // 3. 执行请求 CreateIndexResponse response = ESClient.getClient() .indices() .create(request, RequestOptions.DEFAULT); // 4. 处理响应 if (response.isAcknowledged()) { System.out.println("索引创建成功！"); } else { System.out.println("索引创建失败！"); } } public static void main(String[] args) throws IOException { createProductIndex(); ESClient.close(); // 关闭客户端 } }注意：ik_max_word 是中文分词器，需提前在 ES 中安装（下载 IK 插件，解压到 ES 插件目录）。3. 文档操作（CRUD）（1）新增文档（Create）import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.RequestOptions; import com.fasterxml.jackson.databind.ObjectMapper; public class DocumentDemo { // 新增商品文档 public static void addProduct(Product product) throws IOException { // 1. 创建新增请求（指定索引和文档 ID） IndexRequest request = new IndexRequest("product"); request.id(product.getId().toString()); // 文档 ID 设为商品 ID // 2. 将实体类转为 JSON 字符串 ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(product); request.source(json, XContentType.JSON); // 3. 执行请求 IndexResponse response = ESClient.getClient() .index(request, RequestOptions.DEFAULT); // 4. 输出结果 System.out.println("新增文档结果：" + response.getResult()); } public static void main(String[] args) throws IOException { // 新增一个商品 Product product = new Product(1L, "华为 Mate 60 手机", 5999.0, "手机"); addProduct(product); ESClient.close(); } }（2）查询文档（Read）import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.client.RequestOptions; import com.fasterxml.jackson.databind.ObjectMapper; public class DocumentDemo { // 根据 ID 查询文档 public static Product getProductById(Long id) throws IOException { // 1. 创建查询请求 GetRequest request = new GetRequest("product", id.toString()); // 2. 执行请求 GetResponse response = ESClient.getClient() .get(request, RequestOptions.DEFAULT); // 3. 解析结果 if (response.isExists()) { String json = response.getSourceAsString(); ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(json, Product.class); // JSON 转实体类 } return null; } public static void main(String[] args) throws IOException { Product product = getProductById(1L); System.out.println("查询到商品：" + product.getName()); // 输出：华为 Mate 60 手机 ESClient.close(); } }（3）更新文档（Update）import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.RequestOptions; import com.fasterxml.jackson.databind.ObjectMapper; public class DocumentDemo { // 更新商品价格 public static void updateProductPrice(Long id, Double newPrice) throws IOException { // 1. 创建更新请求 UpdateRequest request = new UpdateRequest("product", id.toString()); // 2. 设置更新字段（部分更新） request.doc("price", newPrice); // 仅更新 price 字段 // 3. 执行请求 UpdateResponse response = ESClient.getClient() .update(request, RequestOptions.DEFAULT); System.out.println("更新结果：" + response.getResult()); } public static void main(String[] args) throws IOException { updateProductPrice(1L, 6299.0); // 将价格改为 6299 ESClient.close(); } }（4）删除文档（Delete）import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.client.RequestOptions; public class DocumentDemo { // 删除文档 public static void deleteProduct(Long id) throws IOException { // 1. 创建删除请求 DeleteRequest request = new DeleteRequest("product", id.toString()); // 2. 执行请求 DeleteResponse response = ESClient.getClient() .delete(request, RequestOptions.DEFAULT); System.out.println("删除结果：" + response.getResult()); } public static void main(String[] args) throws IOException { deleteProduct(1L); ESClient.close(); } }4. 高级查询：全文检索与过滤Elasticsearch 的核心价值在于强大的查询能力，例如“搜索名称包含‘手机’且价格小于 6000 的商品”：import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.builder.SearchSourceBuilder; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.ArrayList; import java.util.List; public class SearchDemo { // 搜索商品 public static List<Product> searchProducts() throws IOException { // 1. 创建搜索请求 SearchRequest request = new SearchRequest("product"); // 2. 构建查询条件 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); // 组合查询：名称包含“手机”（全文检索）且价格 < 6000（范围过滤） sourceBuilder.query(QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("name", "手机")) // 全文检索 .filter(QueryBuilders.rangeQuery("price").lt(6000))); // 过滤 request.source(sourceBuilder); // 3. 执行查询 SearchResponse response = ESClient.getClient() .search(request, RequestOptions.DEFAULT); // 4. 解析结果 List<Product> products = new ArrayList<>(); ObjectMapper mapper = new ObjectMapper(); for (SearchHit hit : response.getHits().getHits()) { Product product = mapper.readValue(hit.getSourceAsString(), Product.class); products.add(product); } return products; } public static void main(String[] args) throws IOException { List<Product> products = searchProducts(); System.out.println("搜索到 " + products.size() + " 个商品"); for (Product p : products) { System.out.println(p.getName() + " - " + p.getPrice()); } ESClient.close(); } }四、入门必备：常见问题与最佳实践1. 索引设计原则索引名小写：ES 索引名不允许大写字母。合理拆分索引：按时间（如日志索引 log-2024-10）或业务拆分，避免单索引过大。Mapping 提前定义：避免动态映射导致字段类型不符合预期（如数字被识别为字符串）。2. 性能优化建议批量操作：大量文档写入/更新时使用 BulkRequest，减少网络开销。合理设置分片：单索引分片数建议 3-5 个（过多会增加集群负担），副本数根据节点数配置（如 2 节点可设 1 个副本）。避免深分页：使用 scroll 或 search_after 替代 from+size 实现大数据量分页。3. 分词器选择中文场景：优先使用 IK 分词器（ik_max_word 细粒度分词，ik_smart 粗粒度分词）。英文场景：默认的 standard 分词器即可，或使用 english 分词器（支持词根提取）。五、总结与进阶方向本文通过实战案例，介绍了 Elasticsearch 的核心概念、Java 客户端的基本用法，包括索引创建、文档 CRUD 和高级查询。掌握这些内容后，你已具备 ES 入门能力。进阶学习方向：聚合分析：使用 AggregationBuilder 实现统计（如按分类分组统计商品数量）。集群部署：学习 ES 集群配置、节点角色、分片分配策略。性能调优：JVM 内存配置、索引刷新频率、缓存机制等。结合框架：集成 Spring Data Elasticsearch，简化开发（提供 Repository 接口）。Elasticsearch 是分布式搜索领域的佼佼者，学好它能让你在数据检索、分析场景中如虎添翼。动手实践是掌握的关键，赶紧尝试扩展本文案例吧！
在分布式系统中，消息队列是实现异步通信、解耦服务、削峰填谷的核心组件。而 RabbitMQ 作为一款高性能、可靠的开源消息中间件，凭借其灵活的路由策略和丰富的功能，成为了许多企业的首选。本文将以 SpringAMQP 为桥梁，带大家快速入门 RabbitMQ，从基础概念到实战代码，一步步掌握消息队列的核心用法。一、RabbitMQ 核心概念扫盲在开始实战前，需先理清 RabbitMQ 的核心组件，这是理解后续内容的基础。1. 消息队列的基本模型RabbitMQ 基于「生产者 - 消费者」模型工作，核心组件包括：生产者（Producer）：发送消息的应用程序。例如：用户下单后，负责发送 “订单创建” 消息的服务。消费者（Consumer）：接收并处理消息的应用程序。例如：接收 “订单创建” 消息后，负责发送短信通知的服务。队列（Queue）：存储消息的缓冲区。消息会先进入队列，再由消费者从队列中取出处理。队列支持持久化（重启后消息不丢失）、限流等特性。交换机（Exchange）：接收生产者发送的消息，并根据 “路由规则” 将消息转发到对应的队列。交换机不存储消息，若没有匹配的队列，消息会被丢弃或返回给生产者（需额外配置）。路由键（Routing Key）：生产者发送消息时指定的 “钥匙”，交换机通过路由键判断消息该转发到哪个队列。绑定（Binding）：将交换机与队列关联的 “桥梁”，同时指定 “绑定键（Binding Key）”。交换机通过对比路由键和绑定键来匹配队列。2. 交换机的 4 种核心类型交换机是 RabbitMQ 路由策略的核心，不同类型对应不同路由规则，新手需重点掌握以下 4 种：交换机类型路由规则适用场景Direct（直接交换机）路由键与绑定键 完全匹配 才转发一对一通信（如订单状态同步）Fanout（扇出交换机）忽略路由键，将消息转发给 所有绑定的队列一对多广播（如日志同步、短信群发）Topic（主题交换机）支持通配符匹配（* 匹配一个词，# 匹配多个词）多条件路由（如按地区、类型筛选消息）Headers（头交换机）通过消息头（Headers）的键值对匹配复杂多字段匹配（较少用）二、环境准备：3 步搭建 SpringAMQP+RabbitMQ 环境使用 SpringAMQP 操作 RabbitMQ，需完成 “安装 RabbitMQ→创建 SpringBoot 项目→配置依赖与参数” 三步。1. 安装 RabbitMQ（以 Docker 为例）RabbitMQ 依赖 Erlang 环境，直接安装较繁琐，推荐用 Docker 一键部署：# 拉取 RabbitMQ 镜像（带管理界面） docker pull rabbitmq:3-management # 启动容器（映射端口 5672 用于通信，15672 用于管理界面） docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management启动后，访问 http://localhost:15672 进入管理界面，默认账号密码均为 guest（生产环境需修改）。2. 创建 SpringBoot 项目并引入依赖用 IDEA 创建 SpringBoot 项目，在 pom.xml 中引入 SpringAMQP 依赖（SpringBoot 自动管理版本）： <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>3. 配置 RabbitMQ 连接参数在 application.yml 中配置 RabbitMQ 连接信息（IP、端口、账号密码等）：spring: rabbitmq: host: localhost # RabbitMQ 服务地址（本地用 localhost，远程填服务器 IP） port: 5672 # 通信端口（默认 5672） username: guest # 用户名（默认 guest） password: guest # 密码（默认 guest） virtual-host: / # 虚拟主机（默认 /，用于隔离不同环境的消息）至此，环境搭建完成，接下来进入实战环节。三、实战：用 SpringAMQP 实现消息收发SpringAMQP 提供两种核心操作方式：基于注解的简单用法：适合快速开发基于 API 的灵活配置：适合复杂场景我们先从简单的 “Direct 交换机 + 队列” 开始，实现一对一消息通信。1. 基础案例：Direct 交换机的消息收发步骤 1：声明交换机、队列与绑定关系通过 @Bean 注解在配置类中声明交换机、队列，并建立绑定关系：import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class DirectRabbitConfig { // 声明 Direct 交换机 @Bean public DirectExchange directExchange() { // 参数：交换机名称、是否持久化（重启后不丢失）、是否自动删除（无队列绑定时删除） return new DirectExchange("direct.exchange", true, false); } // 声明队列 @Bean public Queue directQueue() { // 参数：队列名称、是否持久化、是否排他（仅当前连接可见）、是否自动删除（无消费者时删除） return new Queue("direct.queue", true, false, false); } // 绑定交换机与队列（指定绑定键） @Bean public Binding directBinding(DirectExchange directExchange, Queue directQueue) { // 将队列通过绑定键 "direct.key" 绑定到交换机 return BindingBuilder.bind(directQueue).to(directExchange).with("direct.key"); } }步骤 2：实现生产者（发送消息）使用 RabbitTemplate（SpringAMQP 核心工具类）发送消息，在测试类中编写发送逻辑：import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.amqp.rabbit.core.RabbitTemplate; @SpringBootTest public class RabbitMQProducerTest { @Autowired private RabbitTemplate rabbitTemplate; // 注入消息发送工具类 @Test public void testSendDirectMessage() { String message = "Hello, Direct RabbitMQ!"; // 参数：交换机名称、路由键（需与绑定键一致）、消息内容 rabbitTemplate.convertAndSend("direct.exchange", "direct.key", message); System.out.println("消息发送成功：" + message); } }步骤 3：实现消费者（接收消息）使用 @RabbitListener 注解监听指定队列，队列中有消息时自动触发方法处理：import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component // 注入 Spring 容器，确保被扫描到 public class RabbitMQConsumer { // 监听 direct.queue 队列，有消息时自动执行该方法 @RabbitListener(queues = "direct.queue") public void receiveDirectMessage(String message) { System.out.println("消费者收到 Direct 消息：" + message); // 此处可添加业务逻辑（如处理订单、发送通知等） } }步骤 4：测试效果启动 SpringBoot 项目（消费者自动注册监听）；运行 RabbitMQProducerTest 中的 testSendDirectMessage 方法；查看控制台输出：生产者控制台：消息发送成功：Hello, Direct RabbitMQ!消费者控制台：消费者收到 Direct 消息：Hello, Direct RabbitMQ!同时，可在 RabbitMQ 管理界面的 Queues 标签页中，查看 direct.queue 的消息状态（如 “Ready” 数量为 0，说明消息已被消费）。2. 进阶案例：Fanout 交换机的广播功能Fanout 交换机的特点是 “广播消息”，忽略路由键，转发给所有绑定的队列。我们以 “日志广播” 场景为例实现：步骤 1：声明 Fanout 交换机与多个队列import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.FanoutExchange; import org.springframework.amqp.core.Queue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FanoutRabbitConfig { // 声明 Fanout 交换机 @Bean public FanoutExchange fanoutExchange() { return new FanoutExchange("fanout.exchange", true, false); } // 声明两个队列（分别接收不同类型的日志） @Bean public Queue fanoutQueue1() { return new Queue("fanout.queue1", true); } @Bean public Queue fanoutQueue2() { return new Queue("fanout.queue2", true); } // 绑定队列 1 到 Fanout 交换机（无需路由键） @Bean public Binding fanoutBinding1(FanoutExchange fanoutExchange, Queue fanoutQueue1) { return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange); } // 绑定队列 2 到 Fanout 交换机（无需路由键） @Bean public Binding fanoutBinding2(FanoutExchange fanoutExchange, Queue fanoutQueue2) { return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange); } }步骤 2：发送广播消息@Test public void testSendFanoutMessage() { String logMessage = "用户[123]登录系统"; // Fanout 交换机忽略路由键，第二个参数可传任意值（或 null） rabbitTemplate.convertAndSend("fanout.exchange", "", logMessage); System.out.println("广播日志发送成功：" + logMessage); }步骤 3：两个消费者接收消息import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class FanoutConsumer { // 监听队列 1：接收 info 日志 @RabbitListener(queues = "fanout.queue1") public void receiveFanoutMessage1(String message) { System.out.println("消费者 1 收到日志：" + message); } // 监听队列 2：接收 error 日志（演示用，实际可处理不同级别日志） @RabbitListener(queues = "fanout.queue2") public void receiveFanoutMessage2(String message) { System.out.println("消费者 2 收到日志：" + message); } }测试后会发现：两个消费者都会收到相同的广播消息，这就是 Fanout 交换机的 “一对多” 特性。四、RabbitMQ 入门必备：关键特性与注意事项掌握基础用法后，需了解 RabbitMQ 的关键特性，避免踩坑。1. 消息持久化：防止消息丢失默认情况下，队列和消息均为 “非持久化”，RabbitMQ 重启后会丢失。实现持久化需满足两个条件：队列声明时设置 durable=true（如前文 new Queue("direct.queue", true)）；发送消息时设置 deliveryMode=2（SpringAMQP 默认已配置，无需手动操作）。2. 消息确认：确保消息可靠投递为避免 “消息发送后未知是否成功”，RabbitMQ 提供两种确认机制：生产者确认（Publisher Confirm）：生产者发送消息后，RabbitMQ 返回确认信号，告知消息是否到达交换机；消费者确认（Consumer ACK）：消费者处理完消息后，需向 RabbitMQ 发送确认信号，RabbitMQ 才会删除队列中的消息（SpringAMQP 默认自动 ACK，也可手动配置）。3. 避免重复消费：幂等性设计因网络延迟、消费者重启等原因，可能出现 “消息重复消费”（如消费者处理完消息后，ACK 信号未发出，RabbitMQ 重新投递）。解决方法是保证消费者业务逻辑的幂等性，例如：使用唯一消息 ID，消费前查询是否已处理；数据库操作使用 “唯一索引” 或 “乐观锁”，避免重复插入 / 更新。五、总结与下一步学习方向本文通过 “概念→环境→实战” 的流程，带大家入门 RabbitMQ+SpringAMQP 的核心用法，重点掌握：RabbitMQ 的核心组件（交换机、队列、路由键等）；4 种交换机的路由规则与适用场景；基于 SpringAMQP 实现消息的发送与接收；持久化、确认机制等关键特性。若想进一步深入，可学习以下内容：高级特性：死信队列（处理无法消费的消息）、延迟队列（实现定时任务）、消息限流；集群部署：RabbitMQ 集群搭建（主从、镜像模式），保证高可用；性能优化：交换机 / 队列设计、消息大小控制、连接池配置。RabbitMQ 作为消息队列的 “入门首选”，掌握它不仅能解决实际项目中的异步通信问题，也能为后续学习 Kafka、RocketMQ 等中间件打下基础。赶紧动手试试吧！
Java 基于 Sentinel 的服务保护入门指南：请求限流、线程隔离和服务熔断
在微服务架构中，服务的稳定性至关重要。随着服务数量增多和调用关系复杂化，服务熔断、限流、隔离等保护机制成为保障系统稳定性的关键。Sentinel 作为阿里巴巴开源的流量控制组件，为我们提供了一站式的服务保护解决方案。本文将带你快速入门 Sentinel，实现请求限流、线程隔离和服务熔断功能。什么是 Sentinel？Sentinel 是面向分布式服务架构的流量控制组件，主要以流量为切入点，从流量控制、熔断降级、系统负载保护等多个维度来帮助开发者保障微服务的稳定性。核心特性：丰富的流量控制策略（基于QPS、线程数等）熔断降级能力（基于响应时间、异常比例等）系统自适应保护实时监控和控制台易用的扩展点环境准备技术栈JDK 1.8+Spring Boot 2.7.x（本文以Spring Boot为例）Maven 3.6+引入依赖在 pom.xml 中添加以下依赖： <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2021.0.5.0</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.6</version> </dependency>下载并启动 Sentinel 控制台从 Sentinel Releases 下载控制台 jar 包（例如：sentinel-dashboard-1.8.6.jar）启动控制台：java -jar sentinel-dashboard-1.8.6.jar --server.port=8080访问 http://localhost:8080，默认用户名/密码：sentinel/sentinel配置应用连接控制台在 application.yml 中添加配置：spring: application: name: sentinel-demo cloud: sentinel: transport: dashboard: localhost:8080 # 控制台地址 port: 8719 # 本地客户端端口，默认8719，如被占用会自动加1 server: port: 8081核心功能实现1. 请求限流限流是保护服务的第一道防线，防止流量突增导致服务崩溃。基于注解的限流实现import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class LimitController { /** * 基础限流示例 * value: 资源名称，必填 * blockHandler: 限流/降级后的处理方法 */ @GetMapping("/limit") @SentinelResource(value = "limitResource", blockHandler = "handleLimit") public String limit() { return "正常访问：" + System.currentTimeMillis(); } // 限流处理方法，参数和返回值需与原方法一致，并额外添加BlockException参数 public String handleLimit(BlockException e) { return "请求过于频繁，请稍后再试！"; } }在控制台配置限流规则启动应用后，访问一次 /limit 接口（Sentinel 采用懒加载机制，首次访问后才会在控制台显示）进入控制台 -> 簇点链路 -> 找到 limitResource 资源点击 "流控"，配置规则：阈值类型：QPS单机阈值：5（每秒最多5次请求）其他保持默认现在快速刷新 /limit 接口，超过5次/秒就会触发限流。2. 线程隔离线程隔离用于控制某个服务调用的资源消耗，避免单个服务故障影响整个应用。Sentinel 支持两种隔离方式：线程池隔离和信号量隔离。线程池隔离实现import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IsolationController { /** * 线程池隔离示例 */ @GetMapping("/isolation") @SentinelResource(value = "isolationResource", blockHandler = "handleIsolation", fallback = "fallbackIsolation") public String isolation() throws InterruptedException { // 模拟耗时操作 Thread.sleep(100); return "隔离服务正常响应"; } // 限流/降级处理 public String handleIsolation(BlockException e) { return "服务繁忙，请稍后再试"; } // 业务异常处理 public String fallbackIsolation(Throwable e) { return "服务处理异常: " + e.getMessage(); } }配置线程隔离规则控制台 -> 簇点链路 -> isolationResource -> 点击 "流控"选择 "线程数" 作为阈值类型配置单机阈值（例如：3，表示最多同时处理3个请求）当并发请求超过3个时，多余的请求会被阻塞并执行 handleIsolation 方法。3. 服务熔断当服务调用出现异常（如超时、错误率高）时，熔断机制会暂时停止调用该服务，避免级联故障，待服务恢复后再逐步恢复调用。熔断示例实现import com.alibaba.csp.sentinel.annotation.SentinelResource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class CircuitBreakerController { private int errorCount = 0; /** * 熔断降级示例 */ @GetMapping("/breaker") @SentinelResource(value = "breakerResource", fallback = "breakerFallback", blockHandler = "breakerBlockHandler") public String breaker() { errorCount++; // 模拟50%的错误率 if (errorCount % 2 == 0) { throw new RuntimeException("服务内部错误"); } return "服务正常响应"; } // 熔断后的降级处理 public String breakerFallback(Throwable e) { return "服务已熔断，暂时无法提供服务: " + e.getMessage(); } // 限流处理 public String breakerBlockHandler(BlockException e) { return "请求被限流"; } }配置熔断规则控制台 -> 簇点链路 -> breakerResource -> 点击 "降级"配置熔断规则：降级策略：异常比例比例阈值：0.5（错误率超过50%）熔断时长：10（熔断10秒）最小请求数：5（至少5个请求才开始计算错误率）连续访问 /breaker 接口，当错误率超过50%时，服务会进入熔断状态，10秒内所有请求都会直接返回降级结果。规则持久化默认情况下，Sentinel 规则存储在内存中，应用重启后会丢失。生产环境需要配置规则持久化。以 Nacos 为例配置规则持久化：添加依赖：<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.6</version> </dependency>配置 Nacos 数据源：spring: cloud: sentinel: datasource: ds1: nacos: server-addr: localhost:8848 dataId: sentinel-rules groupId: DEFAULT_GROUP rule-type: flow # 支持flow, degrade, param-flow, system, authority总结本文介绍了 Sentinel 的核心功能及基本使用方法：请求限流：通过控制QPS或线程数保护服务不被流量冲垮线程隔离：限制服务调用的资源占用，避免级联故障服务熔断：当服务异常时自动熔断，保护系统稳定Sentinel 还提供了更高级的特性，如参数限流、系统自适应保护、网关限流等，可根据实际需求深入学习。通过合理配置这些保护机制，能有效提高微服务架构的稳定性和可靠性，从容应对各种流量场景。参考资料Sentinel 官方文档Spring Cloud Alibaba Sentinel 文档Sentinel GitHub 仓库
Java 基于 Seata 的分布式事务入门指南：从部署到完整实战
在微服务架构中，一个业务操作往往涉及多个服务的数据库修改（如下单→扣库存→减余额）。分布式事务的核心是保证这些跨服务操作的原子性：要么全部成功，要么全部失败。Seata 作为阿里开源的分布式事务框架，提供了简单易用的解决方案。本文将从部署到完整业务代码，带你掌握基于 Seata 的分布式事务实现。一、核心概念与原理1. Seata 核心角色TC（Transaction Coordinator）：事务协调者（独立服务），负责协调全局事务的提交或回滚。TM（Transaction Manager）：事务管理器（在发起全局事务的服务中），负责开启和结束全局事务。RM（Resource Manager）：资源管理器（在参与事务的每个服务中），负责分支事务的提交/回滚。2. 事务模式本文重点讲解两种常用模式：AT 模式：无侵入式，基于 undo log 实现自动回滚（推荐生产使用，性能好）。XA 模式：基于数据库原生 XA 协议，强一致性但性能较低（适合对一致性要求极高的场景）。二、部署 Seata TC 服务TC 是 Seata 的核心协调服务，必须先部署。1. 下载 Seata 服务器从 Seata 官网 下载 1.6.1 版本，解压后目录结构如下：seata-server-1.6.1/ ├── bin/ # 启动脚本 ├── conf/ # 配置文件 ├── lib/ # 依赖包2. 配置 TC 服务修改 conf/application.yml，配置注册中心（Nacos）和存储模式：server: port: 8091 # TC 服务端口 spring: application: name: seata-server # 注册中心配置（使用 Nacos） registry: type: nacos nacos: server-addr: 127.0.0.1:8848 # Nacos 地址 group: SEATA_GROUP # 分组（默认即可） application: seata-server # TC 在 Nacos 中的服务名 # 配置中心（从 Nacos 读取配置） config: type: nacos nacos: server-addr: 127.0.0.1:8848 group: SEATA_GROUP dataId: seataServer.properties # 配置文件名 # 存储模式（file 适合测试，生产用 db） store: mode: file3. 在 Nacos 中添加 Seata 配置登录 Nacos 控制台（http://localhost:8848，账号密码 nacos/nacos），创建配置：Data ID：seataServer.propertiesGroup：SEATA_GROUP配置内容：# 事务组映射（微服务需与之一致） service.vgroupMapping.my_test_tx_group=default # TC 服务地址（由注册中心自动发现） service.default.grouplist=127.0.0.1:8091 metrics.enabled=false # 关闭监控，减少干扰4. 启动 TC 服务Windows：双击 bin/seata-server.batLinux/Mac：执行 sh bin/seata-server.sh启动成功后，在 Nacos 服务列表中能看到 seata-server，说明 TC 部署完成。三、环境准备与数据库设计1. 微服务架构我们将实现一个经典的分布式事务场景：订单服务（order-service）：创建订单库存服务（storage-service）：扣减商品库存账户服务（account-service）：扣减用户余额业务流程：用户下单 → 订单服务创建订单 → 调用库存服务扣库存 → 调用账户服务扣余额 → 全部成功则提交，任一失败则回滚。2. 数据库设计为三个服务分别创建数据库，并添加 undo_log 表（AT 模式必需）。（1）订单库（order_db）-- 订单表 CREATE TABLE `order` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL COMMENT '用户ID', `product_id` bigint NOT NULL COMMENT '商品ID', `count` int NOT NULL COMMENT '购买数量', `money` decimal(10,2) NOT NULL COMMENT '总金额', `status` int NOT NULL COMMENT '订单状态：0-创建中，1-已完成', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- AT 模式回滚日志表 CREATE TABLE `undo_log` ( `id` bigint NOT NULL AUTO_INCREMENT, `branch_id` bigint NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;（2）库存库（storage_db）-- 库存表 CREATE TABLE `storage` ( `id` bigint NOT NULL AUTO_INCREMENT, `product_id` bigint NOT NULL COMMENT '商品ID', `total` int NOT NULL COMMENT '总库存', `used` int NOT NULL COMMENT '已使用库存', `residue` int NOT NULL COMMENT '剩余库存', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 初始化数据（商品1库存100） INSERT INTO `storage` VALUES (1, 1, 100, 0, 100); -- 回滚日志表（同上，复制 order_db 的 undo_log 表结构）（3）账户库（account_db）-- 账户表 CREATE TABLE `account` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL COMMENT '用户ID', `total` decimal(10,2) NOT NULL COMMENT '总余额', `used` decimal(10,2) NOT NULL COMMENT '已使用余额', `residue` decimal(10,2) NOT NULL COMMENT '剩余余额', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 初始化数据（用户1余额1000） INSERT INTO `account` VALUES (1, 1, 1000, 0, 1000); -- 回滚日志表（同上）四、微服务集成 Seata1. 统一依赖（所有服务）在 pom.xml 中添加依赖：<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2.2.7.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies>2. 订单服务（order-service）（1）配置文件（application.yml）server: port: 8081 spring: application: name: order-service cloud: nacos: discovery: server-addr: localhost:8848 # Nacos 地址 alibaba: seata: tx-service-group: my_test_tx_group # 事务组名（与 TC 一致） datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/order_db?useSSL=false username: root password: 123456 # 替换为你的数据库密码 # MyBatis 配置 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.order.entity # Seata 配置 seata: registry: type: nacos nacos: server-addr: localhost:8848 group: SEATA_GROUP tx-service-group: my_test_tx_group # 与上面一致（2）实体类（Order.java）package com.example.order.entity; import lombok.Data; import java.math.BigDecimal; @Data public class Order { private Long id; private Long userId; private Long productId; private Integer count; private BigDecimal money; private Integer status; // 0-创建中，1-已完成 }（3）Mapper 接口与 XML// OrderMapper.java package com.example.order.mapper; import com.example.order.entity.Order; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OrderMapper { // 创建订单 void create(Order order); // 更新订单状态 void updateStatus(@Param("id") Long id, @Param("status") Integer status); } <?xml version="1.0" encoding="UTF-8"?> <mapper namespace="com.example.order.mapper.OrderMapper"> <insert id="create"> INSERT INTO `order` (user_id, product_id, count, money, status) VALUES (#{userId}, #{productId}, #{count}, #{money}, 0) </insert> <update id="updateStatus"> UPDATE `order` SET status = #{status} WHERE id = #{id} </update> </mapper>（4）Feign 客户端（调用其他服务）// StorageFeignClient.java package com.example.order.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; @FeignClient(name = "storage-service") // 目标服务名 public interface StorageFeignClient { // 扣减库存 @PostMapping("/storage/decrease") String decrease( @RequestParam("productId") Long productId, @RequestParam("count") Integer count ); } // AccountFeignClient.java package com.example.order.feign; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import java.math.BigDecimal; @FeignClient(name = "account-service") public interface AccountFeignClient { // 扣减余额 @PostMapping("/account/decrease") String decrease( @RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money ); }（5）Service 层（核心业务逻辑）package com.example.order.service; import com.alibaba.fastjson.JSONObject; import com.example.order.entity.Order; import com.example.order.feign.AccountFeignClient; import com.example.order.feign.StorageFeignClient; import com.example.order.mapper.OrderMapper; import io.seata.core.context.RootContext; import io.seata.spring.annotation.GlobalTransactional; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service @Slf4j public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private StorageFeignClient storageFeignClient; @Autowired private AccountFeignClient accountFeignClient; /** * 创建订单（全局事务入口） * @GlobalTransactional：标记为全局事务，name 为事务名（唯一） */ @GlobalTransactional(name = "create-order", rollbackFor = Exception.class) public String createOrder(Order order) { log.info("全局事务ID：{}", RootContext.getXID()); // 打印全局事务ID // 1. 创建订单（本地事务） log.info("开始创建订单..."); orderMapper.create(order); // 2. 扣减库存（调用库存服务） log.info("开始扣减库存..."); String storageResult = storageFeignClient.decrease(order.getProductId(), order.getCount()); if (!"扣减库存成功".equals(storageResult)) { throw new RuntimeException("扣减库存失败：" + storageResult); } // 3. 扣减余额（调用账户服务） log.info("开始扣减余额..."); String accountResult = accountFeignClient.decrease(order.getUserId(), order.getMoney()); if (!"扣减余额成功".equals(accountResult)) { throw new RuntimeException("扣减余额失败：" + accountResult); } // 4. 更新订单状态为“已完成” log.info("更新订单状态..."); orderMapper.updateStatus(order.getId(), 1); // 模拟异常（测试回滚时打开注释） // int i = 1 / 0; return "订单创建成功，ID：" + order.getId(); } }（6）Controller 层package com.example.order.controller; import com.example.order.entity.Order; import com.example.order.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class OrderController { @Autowired private OrderService orderService; @PostMapping("/order/create") public String createOrder(@RequestBody Order order) { return orderService.createOrder(order); } }（7）启动类package com.example.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @EnableFeignClients // 开启 Feign 客户端 public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class, args); } }3. 库存服务（storage-service）（1）配置文件（application.yml）server: port: 8082 spring: application: name: storage-service cloud: nacos: discovery: server-addr: localhost:8848 alibaba: seata: tx-service-group: my_test_tx_group datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/storage_db?useSSL=false username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.storage.entity seata: registry: type: nacos nacos: server-addr: localhost:8848 group: SEATA_GROUP tx-service-group: my_test_tx_group（2）实体类（Storage.java）package com.example.storage.entity; import lombok.Data; @Data public class Storage { private Long id; private Long productId; private Integer total; private Integer used; private Integer residue; }（3）Mapper 接口与 XML// StorageMapper.java package com.example.storage.mapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface StorageMapper { // 扣减库存 void decrease(@Param("productId") Long productId, @Param("count") Integer count); } <?xml version="1.0" encoding="UTF-8"?> <mapper namespace="com.example.storage.mapper.StorageMapper"> <update id="decrease"> UPDATE storage SET used = used + #{count}, residue = residue - #{count} WHERE product_id = #{productId} </update> </mapper>（4）Service 层package com.example.storage.service; import com.example.storage.mapper.StorageMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Slf4j public class StorageService { @Autowired private StorageMapper storageMapper; // 扣减库存（本地事务） @Transactional public void decrease(Long productId, Integer count) { log.info("扣减库存：productId={}, count={}", productId, count); storageMapper.decrease(productId, count); } }（5）Controller 层package com.example.storage.controller; import com.example.storage.service.StorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class StorageController { @Autowired private StorageService storageService; @PostMapping("/storage/decrease") public String decrease( @RequestParam("productId") Long productId, @RequestParam("count") Integer count ) { storageService.decrease(productId, count); return "扣减库存成功"; } }（6）启动类package com.example.storage; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class StorageServiceApplication { public static void main(String[] args) { SpringApplication.run(StorageServiceApplication.class, args); } }4. 账户服务（account-service）（1）配置文件（application.yml）server: port: 8083 spring: application: name: account-service cloud: nacos: discovery: server-addr: localhost:8848 alibaba: seata: tx-service-group: my_test_tx_group datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/account_db?useSSL=false username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.account.entity seata: registry: type: nacos nacos: server-addr: localhost:8848 group: SEATA_GROUP tx-service-group: my_test_tx_group（2）实体类（Account.java）package com.example.account.entity; import lombok.Data; import java.math.BigDecimal; @Data public class Account { private Long id; private Long userId; private BigDecimal total; private BigDecimal used; private BigDecimal residue; }（3）Mapper 接口与 XML// AccountMapper.java package com.example.account.mapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.math.BigDecimal; @Mapper public interface AccountMapper { // 扣减余额 void decrease(@Param("userId") Long userId, @Param("money") BigDecimal money); } <?xml version="1.0" encoding="UTF-8"?> <mapper namespace="com.example.account.mapper.AccountMapper"> <update id="decrease"> UPDATE account SET used = used + #{money}, residue = residue - #{money} WHERE user_id = #{userId} </update> </mapper>（4）Service 层package com.example.account.service; import com.example.account.mapper.AccountMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; @Service @Slf4j public class AccountService { @Autowired private AccountMapper accountMapper; // 扣减余额（本地事务） @Transactional public void decrease(Long userId, BigDecimal money) { log.info("扣减余额：userId={}, money={}", userId, money); accountMapper.decrease(userId, money); } }（5）Controller 层package com.example.account.controller; import com.example.account.service.AccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.math.BigDecimal; @RestController public class AccountController { @Autowired private AccountService accountService; @PostMapping("/account/decrease") public String decrease( @RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money ) { accountService.decrease(userId, money); return "扣减余额成功"; } }（6）启动类package com.example.account; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class AccountServiceApplication { public static void main(String[] args) { SpringApplication.run(AccountServiceApplication.class, args); } }五、AT 模式实战AT 模式是 Seata 的默认模式，无需额外配置，直接使用即可。1. 测试正常流程启动 Nacos、Seata TC 服务。启动订单服务、库存服务、账户服务。发送请求创建订单：curl -X POST http://localhost:8081/order/create \ -H "Content-Type: application/json" \ -d '{"userId":1,"productId":1,"count":10,"money":100.00}'检查数据库：订单表新增记录，状态为 1（已完成）。库存表：used=10，residue=90。账户表：used=100，residue=900。→ 所有操作成功提交。2. 测试回滚流程在订单服务的 createOrder 方法中打开异常注释：int i = 1 / 0;。再次发送请求，会触发异常。检查数据库：订单表无新增记录（或状态仍为 0，但最终会回滚）。库存和账户数据未变化。→ 所有操作成功回滚。六、XA 模式实战XA 模式基于数据库原生 XA 协议，需修改配置开启。1. 切换为 XA 模式在三个服务的 application.yml 中添加：seata: data-source-proxy-mode: XA # 默认为 AT，改为 XA2. 测试 XA 模式重复 AT 模式的测试步骤，结果一致。区别在于：XA 模式下，事务执行过程中会锁定数据（如扣库存时其他事务无法修改该记录）。性能略低于 AT 模式（因资源锁定时间更长）。七、AT 与 XA 模式对比特性AT 模式XA 模式侵入性无（自动生成 undo log）无（依赖数据库原生支持）性能高（异步提交，无锁等待）低（锁定资源直到全局提交）一致性最终一致性强一致性适用场景大多数微服务场景金融核心交易等强一致性场景八、常见问题解决事务不回滚？检查 @GlobalTransactional 是否添加在订单服务的 createOrder 方法上。确保所有服务的 tx-service-group 与 TC 配置一致（my_test_tx_group）。检查 undo_log 表是否创建（AT 模式必需）。服务间调用失败？检查 Nacos 服务列表，确认三个服务和 Seata 均已注册。检查 Feign 客户端的服务名是否正确（storage-service、account-service）。XA 模式下数据库死锁？尽量缩短事务执行时间，减少资源锁定时间。避免多个事务同时修改同一批数据。九、总结本文通过完整的代码示例，讲解了基于 Seata 的分布式事务方案：部署 Seata TC 服务作为事务协调者。实现三个微服务（订单、库存、账户），通过 Feign 实现服务间调用。在订单服务中用 @GlobalTransactional 标记全局事务入口。分别测试了 AT 模式（默认，高性能）和 XA 模式（强一致性）。通过 Seata，我们可以在微服务架构中轻松保证跨服务操作的原子性，无需手动编写复杂的回滚逻辑。实际项目中，建议优先选择 AT 模式，仅在强一致性场景下使用 XA 模式。
Nacos 从入门到实战：服务发现与配置管理利器
什么是 Nacos？Nacos（Dynamic Naming and Configuration Service）是阿里巴巴开源的一款动态服务发现、配置管理和服务管理平台，致力于帮助开发者快速构建云原生应用。Nacos是目前国内企业中占比最多的注册中心组件，核心定位 “注册中心 + 配置中心”，适配微服务架构全场景需求，目前已经加入SpringCloudAilibaba中。简单来说，Nacos 可以解决两个核心问题：服务发现：让微服务之间能够相互找到对方（类似通讯录）配置管理：集中管理所有服务的配置，支持动态更新（无需重启服务）为什么选择 Nacos？1. 服务注册与发现一站式解决方案：同时支持服务发现和配置管理，避免集成多个组件的复杂性高可用：支持集群部署，保证服务稳定性动态配置：配置修改后实时推送，无需重启应用易用性：提供友好的控制台界面，操作简单生态兼容：与 Spring Cloud、Dubbo 等主流微服务框架无缝集成环境准备系统要求JDK 8+（推荐 JDK 11）Maven 3.2+（如需源码编译）内存：至少 2GB（生产环境建议 4GB+）快速安装 Nacos 服务端方式 1：直接下载安装包（推荐）下载最新稳定版安装包# 以 2.3.2 版本为例（请替换为最新版本） wget https://github.com/alibaba/nacos/releases/download/2.3.2/nacos-server-2.3.2.tar.gz解压安装包tar -zxvf nacos-server-2.3.2.tar.gz cd nacos/bin移动Nacos（单机模式）# Linux/Mac sh startup.sh -m standalone # Windows cmd startup.cmd -m standalone访问控制台地址：http://localhost:8848/nacos默认账号密码：nacos /nacos方式 2：Docker 快速部署# 拉取镜像 docker pull nacos/nacos-server:v2.3.2 # 启动容器（单机模式） docker run --name nacos -d -p 8848:8848 \ -e MODE=standalone \ -e NACOS_AUTH_ENABLE=true \ nacos/nacos-server:v2.3.2核心功能实战1. 配置管理新建配置登录 Nacos 控制台，进入「配置管理」→「配置列表」点击「+」新增配置，填写信息：Data ID：通常使用「服务名 - 环境.yml」格式（如 user-service-dev.yml）Group：默认 DEFAULT_GROUP 即可（可自定义分组）配置格式：选择 YAML配置内容：server: port: 8081 user: name: 张三 age: 18点击「发布」保存配置在 Spring Boot 中使用配置添加依赖（pom.xml）<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2021.0.5.0</version> </dependency>创建配置文件 bootstrap.yml（必须是 bootstrap 而非 application）spring: application: name: user-service # 服务名，与 Data ID 前缀对应 cloud: nacos: config: server-addr: localhost:8848 # Nacos 服务地址 file-extension: yml # 配置文件格式 group: DEFAULT_GROUP # 配置分组 profiles: active: dev # 环境，与 Data ID 中的环境对应编写测试代码@RestController @RefreshScope // 开启配置自动刷新 public class UserController { @Value("${user.name}") private String userName; @Value("${user.age}") private Integer userAge; @GetMapping("/user/info") public String getUserInfo() { return "姓名：" + userName + "，年龄：" + userAge; } }测试动态配置：在 Nacos 控制台修改配置并发布，无需重启服务，访问接口即可看到更新结果2. 服务发现服务注册（Spring Cloud 示例）添加依赖（pom.xml）<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2021.0.5.0</version> </dependency>配置服务注册信息（application.yml）spring: cloud: nacos: discovery: server-addr: localhost:8848 # Nacos 服务地址在启动类添加注解@SpringBootApplication @EnableDiscoveryClient // 开启服务注册发现功能 public class UserServiceApplication { public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); } }启动服务后，在 Nacos 控制台「服务管理」→「服务列表」中可看到注册的服务服务调用（使用 OpenFeign）添加 OpenFeign 依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>创建 Feign 客户端接口@FeignClient(name = "order-service") // 目标服务名 public interface OrderServiceClient { @GetMapping("/order/{id}") String getOrderById(@PathVariable("id") Long id); }在启动类添加 @EnableFeignClients 注解调用服务@RestController public class UserController { @Autowired private OrderServiceClient orderServiceClient; @GetMapping("/user/order/{id}") public String getUserOrder(@PathVariable Long id) { // 调用订单服务 return orderServiceClient.getOrderById(id); } }Nacos 集群部署（生产环境必备）单机模式仅用于开发测试，生产环境需部署集群以保证高可用。集群部署步骤概要准备 3 台服务器（或虚拟机），确保网络互通每台服务器安装 Nacos配置 MySQL 数据库（存储集群元数据）修改 conf/cluster.conf 文件，添加所有节点地址192.168.1.101:8848 192.168.1.102:8848 192.168.1.103:8848修改 conf/application.properties 配置数据库连接分别启动所有节点（无需指定 standalone 参数）配置负载均衡器（如 Nginx）转发请求到集群节点常见问题解决Nacos 启动失败检查端口是否被占用（默认 8848）确保 JDK 版本符合要求查看 logs 目录下的日志文件排查具体错误服务注册不上检查服务端地址是否正确检查网络是否通畅（防火墙设置）确认依赖版本是否兼容配置无法读取检查 Data ID 是否与服务名 + 环境 + 格式匹配确认配置文件是否为 bootstrap.yml检查分组是否正确总结Nacos 作为微服务架构中的核心组件，提供了简洁高效的服务发现和配置管理能力。通过本文的介绍，你已经掌握了 Nacos 的基本安装、核心功能使用和集群部署要点。在实际项目中，建议结合具体业务场景，合理规划配置分组和服务命名，同时做好集群监控和容灾设计，充分发挥 Nacos 的优势。
一、什么是 Spring Cloud Gateway？Spring Cloud Gateway 是 Spring 生态专为微服务架构设计的 API 网关，基于 Netty 实现非阻塞响应式编程，核心功能包括：路由转发：将客户端请求按规则转发到对应微服务过滤器链：请求转发前后的增强处理（如认证、日志、限流）负载均衡：集成 Spring Cloud LoadBalancer 实现服务实例动态路由断言匹配：通过多种规则精确匹配请求相比传统网关（如 Zuul），Gateway 具有更高的性能和更丰富的功能，是微服务架构的"流量入口"。二、路由的核心组成一个完整的 Gateway 路由由四部分构成：要素说明ID路由唯一标识（不可重复）URI目标服务地址（支持 lb://服务名 或 http://ip:port）Predicates路由断言（匹配请求的规则，多断言需同时满足）Filters过滤器（请求转发前后的处理逻辑）三、路由配置方式（环境搭建）1. 引入依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>2. 配置文件方式（推荐）通过 application.yml 配置，支持动态刷新：server: port: 8080 # 网关端口 spring: application: name: gateway # 服务名称 cloud: nacos: server-addr: localhost:8848 # nacos地址 gateway: routes: # 网关路由配置 # 用户服务路由 - id: user-service # 路由id，自定义，只要唯一即可 # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址 uri: lb://userService # 路由的目标地址 lb就是LoadBalance负载均衡，后面跟服务名称 predicates: # 路由断言，也就是判断请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配，只要以/user/开头就符合要求 filters: - StripPrefix=1 # 去掉路径中第一个前缀（/api） - AddRequestHeader=X-Service, user # 添加请求头 # 订单服务路由 - id: order-service-route uri: http://localhost:8082 # 直接转发到指定地址 predicates: - Path=/orders/** - After=2025-10-01T00:00:00+08:00[Asia/Shanghai] # 时间断言3. 代码方式配置通过 RouteLocatorBuilder 编程式定义路由：import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RouteConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() // 产品服务路由 .route("product-service-route", r -> r .path("/api/products/**") // 路径断言 .filters(f -> f.stripPrefix(1)) // 过滤器 .uri("lb://product-service")) // 目标服务 .build(); } }四、路由断言详解断言（Predicate）是请求的"匹配规则"，Gateway 内置多种断言工厂，支持组合使用（需同时满足）。断言类型示例说明PathPath=/api/**,/v1/**匹配请求路径（支持多路径和通配符 */**）MethodMethod=GET,POST匹配 HTTP 方法（GET/POST/PUT/DELETE 等）HeaderHeader=X-Request-Id, \d+匹配请求头（值支持正则表达式）QueryQuery=name, zhangsan匹配请求参数（如 ?name=zhangsan）HostHost=**.example.com匹配请求主机名（如 api.example.com）CookieCookie=sessionId, ^[a-z0-9]+$匹配 Cookie 值（支持正则）AfterAfter=2025-10-28T12:00:00+08:00[Asia/Shanghai]匹配指定时间之后的请求BeforeBefore=2025-12-31T23:59:59+08:00[Asia/Shanghai]匹配指定时间之前的请求BetweenBetween=2025-10-01T00:00:00+08:00[Asia/Shanghai], 2025-12-31T23:59:59+08:00[Asia/Shanghai]匹配时间区间内的请求组合断言示例：predicates: - Path=/api/pay/** - Method=POST - Header=Content-Type, application/json - Query=source, app # 要求包含 source=app 参数五、过滤器详解过滤器用于请求转发的增强处理，按生效范围分为：局部过滤器：仅对绑定的路由生效全局过滤器：对所有路由生效1. 常用内置过滤器过滤器示例说明StripPrefixStripPrefix=2去掉路径前 N 级前缀（如 /api/v1/users→/users）PrefixPathPrefixPath=/api给路径添加前缀（如 /users→/api/users）AddRequestHeaderAddRequestHeader=X-Env, prod添加请求头AddResponseHeaderAddResponseHeader=X-Gateway, scg添加响应头SetPathSetPath=/new/{segment}重写路径（使用占位符）RequestRateLimiter见实战案例基于 Redis 令牌桶的限流CircuitBreaker见实战案例熔断降级（需结合 Resilience4j）全局内置过滤器（对所有路由生效）：spring: cloud: gateway: default-filters: - AddResponseHeader=X-Powered-By, SpringCloudGateway - Logging=INFO # 日志过滤器2. 自定义局部过滤器（继承 AbstractGatewayFilterFactory）规范：类名必须以 GatewayFilterFactory 结尾（Spring 自动扫描要求），否则无法在配置文件中引用。案例：接口版本校验过滤器需求：通过请求头 X-API-Version 控制访问，仅允许指定版本的请求通过。步骤 1：实现过滤器import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; // 类名必须以 GatewayFilterFactory 结尾，否则配置文件无法识别 @Component public class ApiVersionGatewayFilterFactory extends AbstractGatewayFilterFactory<ApiVersionGatewayFilterFactory.Config> { // 配置类：存储过滤器参数（允许的版本号） public static class Config { private String allowedVersion; // getter 和 setter public String getAllowedVersion() { return allowedVersion; } public void setAllowedVersion(String allowedVersion) { this.allowedVersion = allowedVersion; } } // 构造器：指定配置类类型 public ApiVersionGatewayFilterFactory() { super(Config.class); } // 定义配置参数的顺序（与 Config 属性对应） @Override public List<String> shortcutFieldOrder() { return Arrays.asList("allowedVersion"); } // 核心过滤逻辑 @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { // 1. 获取请求头中的版本号 String requestVersion = exchange.getRequest().getHeaders().getFirst("X-API-Version"); // 2. 校验版本 if (config.getAllowedVersion().equals(requestVersion)) { // 版本匹配，继续执行后续过滤器 return chain.filter(exchange); } else { // 版本不匹配，返回 403 禁止访问 exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); return exchange.getResponse().setComplete(); } }; } }步骤 2：绑定到路由在配置文件中通过类名前缀引用（去掉 GatewayFilterFactory 后缀）：spring: cloud: gateway: routes: - id: user-service-route uri: lb://user-service predicates: - Path=/api/users/** filters: - StripPrefix=1 # 引用自定义过滤器（使用前缀 ApiVersion） - name: ApiVersion args: allowedVersion: v2 # 允许 v2 版本访问3. 自定义全局过滤器（实现 GlobalFilter）全局过滤器对所有路由生效，需实现 GlobalFilter 和 Ordered 接口，并通过 @Component 注入容器。案例：Token 认证全局过滤器需求：所有请求必须携带有效的 Token（请求头 Authorization），否则拒绝访问。import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class TokenAuthGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) { // 1. 获取请求头中的 Token String token = exchange.getRequest().getHeaders().getFirst("Authorization"); // 2. 校验 Token（实际项目需调用认证服务） if (token == null || !token.startsWith("Bearer ")) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 3. Token 有效，继续执行 return chain.filter(exchange); } /** * 执行顺序：数值越小优先级越高 * 此处设置在响应写入前执行（NettyWriteResponseFilter.ORDER = -1） */ @Override public int getOrder() { return NettyWriteResponseFilter.ORDER - 1; } }六、路由优先级与动态配置1. 路由优先级当多个路由的断言同时匹配请求时，配置顺序决定优先级（先配置的路由优先匹配）。建议：精确匹配的路由放在前面模糊匹配的路由放在后面routes: # 精确匹配优先 - id: user-detail-route uri: lb://user-service predicates: - Path=/api/users/{id} # 匹配单个用户查询 # 模糊匹配在后 - id: user-list-route uri: lb://user-service predicates: - Path=/api/users/** # 匹配所有用户相关请求2. 动态路由配置通过配置中心（如 Nacos、Apollo）实现路由动态更新，无需重启网关：在配置中心创建 gateway-routes.yml 存储路由配置网关服务引入配置中心依赖并配置地址配置中心推送变更后，网关自动刷新路由七、实战案例：完整配置spring: application: name: api-gateway cloud: gateway: default-filters: - AddResponseHeader=X-Gateway, SpringCloudGateway # 全局限流过滤器 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 # 令牌生成速率（每秒10个） redis-rate-limiter.burstCapacity: 20 # 令牌桶容量 key-resolver: "#{@ipKeyResolver}" # 按IP限流 routes: # 用户服务路由（绑定版本过滤器） - id: user-service uri: lb://user-service predicates: - Path=/api/v1/users/** - Method=GET,POST filters: - StripPrefix=2 # 去掉 /api/v1 前缀 - name: ApiVersion args: allowedVersion: v1 # 订单服务路由（熔断处理） - id: order-service uri: lb://order-service predicates: - Path=/api/v1/orders/** - After=2025-01-01T00:00:00+08:00[Asia/Shanghai] filters: - StripPrefix=2 - name: CircuitBreaker args: name: orderServiceCircuitBreaker fallbackUri: forward:/fallback/orders # 熔断降级地址配套限流 KeyResolver：import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import reactor.core.publisher.Mono; @Configuration public class RateLimitConfig { // 按客户端IP限流 @Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just( exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() ); } }八、调试技巧开启 Debug 日志：logging: level: org.springframework.cloud.gateway: debug # 打印路由匹配和过滤器执行日志使用 Actuator 端点：management: endpoints: web: exposure: include: gateway, routes # 暴露网关监控端点访问 http://localhost:8080/actuator/gateway/routes 可查看所有路由配置。九、总结通过本文，你已掌握：路由的核心组成与两种配置方式（文件/代码）断言规则的组合使用（路径、方法、时间等）内置过滤器的常用场景（路径处理、头信息添加等）自定义过滤器的实现：局部过滤器：继承 AbstractGatewayFilterFactory，类名以 GatewayFilterFactory 结尾全局过滤器：实现 GlobalFilter 和 Ordered 接口路由优先级与动态配置技巧合理设计路由和过滤器，能让 Gateway 成为微服务架构的"智能入口"，实现请求的高效转发与安全管控。
在高并发场景中，传统数据库往往面临读写压力过大的问题。Redis（Remote Dictionary Server）作为一款高性能的内存数据库，凭借其快读写、多数据结构、支持持久化等特性，成为缓存、分布式锁、计数器等场景的首选工具。本文将从 Redis 核心概念出发，结合 Java 实战，带你快速掌握 Redis 的入门用法。一、Redis 核心概念在使用 Redis 前，需先理解其核心特性和数据模型，这是后续实战的基础。1. 核心特性内存存储：数据主要存于内存，读写速度极快（单机 QPS 可达 10 万+）。多数据结构：支持 String（字符串）、Hash（哈希）、List（列表）、Set（集合）、Sorted Set（有序集合）等多种结构。持久化：支持 RDB（快照）和 AOF（日志）两种持久化方式，避免内存数据丢失。高可用：支持主从复制、哨兵模式、集群模式，保证服务稳定。原子操作：提供incr、decr等原子命令，适合实现计数器、分布式锁等场景。2. 核心数据结构Redis 的核心价值在于丰富的数据结构，不同结构对应不同场景，新手需重点掌握以下 5 种：数据结构特点适用场景String（字符串）最基础类型，可存储文本、数字，支持拼接、截取缓存用户信息、存储计数器、分布式 IDHash（哈希）键值对集合，适合存储对象（如用户信息）缓存用户详情、商品信息List（列表）有序、可重复的元素集合，支持两端操作实现消息队列、最新消息列表Set（集合）无序、不可重复的元素集合，支持交集、并集好友关系、标签去重、共同关注Sorted Set（有序集合）有序、不可重复，元素关联“分数”排序排行榜、带权重的消息队列二、环境准备1. 安装 Redis推荐使用 Docker 快速部署（本地安装需手动配置，步骤较繁琐）：# 拉取 Redis 镜像（6.2 版本为例，稳定且常用） docker pull redis:6.2 # 启动容器（映射 6379 端口，设置密码为 123456） docker run -d --name redis -p 6379:6379 redis:6.2 --requirepass "123456"启动后，可通过 redis-cli 测试连接（需先进入容器）：# 进入 Redis 容器 docker exec -it redis /bin/bash # 连接 Redis 服务（输入密码 123456） redis-cli -a 123456 # 测试命令（返回 PONG 说明连接成功） ping2. 引入 Java 客户端依赖Java 操作 Redis 最常用的客户端是 Jedis（轻量、易用）和 Spring Data Redis（集成 Spring，简化开发）。本文先以 Jedis 为例，后续补充 Spring 集成方案。在 Maven 项目的 pom.xml 中添加 Jedis 依赖： <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.7.0</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.8.1</version> <scope>test</scope> </dependency>3. 初始化 Jedis 连接创建 RedisClient 工具类，封装 Jedis 连接的初始化和关闭（实际项目中建议用连接池，避免频繁创建连接）：import redis.clients.jedis.Jedis; public class RedisClient { // Redis 服务地址和端口 private static final String HOST = "localhost"; private static final int PORT = 6379; // Redis 密码 private static final String PASSWORD = "123456"; // 获取 Jedis 连接 public static Jedis getConnection() { // 1. 创建连接 Jedis jedis = new Jedis(HOST, PORT); // 2. 验证密码 jedis.auth(PASSWORD); // 3. 选择数据库（默认 0 号库，共 16 个库，0-15） jedis.select(0); return jedis; } // 关闭连接 public static void closeConnection(Jedis jedis) { if (jedis != null) { jedis.close(); } } }注意：实际项目中需使用 Jedis 连接池（JedisPool），避免频繁创建/关闭连接导致性能损耗，下文会补充连接池配置。三、Java 操作 Redis 实战以“电商缓存”场景为例，演示 5 种核心数据结构的常用操作。1. String 类型：缓存商品名称与计数器String 是 Redis 最基础的类型，可存储文本或数字，支持 set（存值）、get（取值）、incr（自增）等命令。import org.junit.jupiter.api.Test; import redis.clients.jedis.Jedis; public class RedisStringTest { @Test public void testStringOps() { // 1. 获取连接 Jedis jedis = RedisClient.getConnection(); try { // （1）存储商品名称（key：product:name:1，value：华为 Mate 60） jedis.set("product:name:1", "华为 Mate 60"); System.out.println("商品名称：" + jedis.get("product:name:1")); // （2）存储商品库存（数字类型，支持自增自减） jedis.set("product:stock:1", "100"); // 初始库存 100 jedis.incr("product:stock:1"); // 库存 +1（变为 101） jedis.decrBy("product:stock:1", 5); // 库存 -5（变为 96） System.out.println("商品库存：" + jedis.get("product:stock:1")); // （3）设置过期时间（10 秒后自动删除，适合临时缓存） jedis.setex("product:temp:1", 10, "临时缓存数据"); System.out.println("临时缓存剩余时间（秒）：" + jedis.ttl("product:temp:1")); } finally { // 2. 关闭连接（避免资源泄漏） RedisClient.closeConnection(jedis); } } }2. Hash 类型：缓存用户详情Hash 适合存储“对象”（如用户、商品），可单独操作某个字段（无需修改整个对象），节省内存和带宽。import org.junit.jupiter.api.Test; import redis.clients.jedis.Jedis; import java.util.Map; public class RedisHashTest { @Test public void testHashOps() { Jedis jedis = RedisClient.getConnection(); try { // （1）存储用户详情（key：user:1，字段：name/age/phone） jedis.hset("user:1", "name", "张三"); jedis.hset("user:1", "age", "25"); jedis.hset("user:1", "phone", "13800138000"); // （2）获取单个字段（用户姓名） System.out.println("用户姓名：" + jedis.hget("user:1", "name")); // （3）获取所有字段和值（完整用户信息） Map<String, String> userMap = jedis.hgetAll("user:1"); System.out.println("完整用户信息：" + userMap); // （4）删除某个字段（删除用户手机号） jedis.hdel("user:1", "phone"); System.out.println("删除手机号后：" + jedis.hgetAll("user:1")); } finally { RedisClient.closeConnection(jedis); } } }3. List 类型：实现简单消息队列List 是有序、可重复的集合，支持 lpush（左插）、rpop（右取）等命令，适合实现“先进先出”（FIFO）的消息队列。import org.junit.jupiter.api.Test; import redis.clients.jedis.Jedis; public class RedisListTest { @Test public void testListOps() { Jedis jedis = RedisClient.getConnection(); try { // 队列 key：order:queue（存储订单 ID） String queueKey = "order:queue"; // （1）生产者：向左插入 3 个订单 ID（模拟下单） jedis.lpush(queueKey, "order1001", "order1002", "order1003"); System.out.println("队列长度：" + jedis.llen(queueKey)); // 输出 3 // （2）消费者：向右取出订单 ID（模拟处理订单） String orderId1 = jedis.rpop(queueKey); String orderId2 = jedis.rpop(queueKey); System.out.println("处理的订单：" + orderId1 + "、" + orderId2); System.out.println("剩余队列长度：" + jedis.llen(queueKey)); // 输出 1 // （3）查看队列所有元素 System.out.println("剩余订单：" + jedis.lrange(queueKey, 0, -1)); } finally { RedisClient.closeConnection(jedis); } } }4. Set 类型：实现用户标签去重Set 是无序、不可重复的集合，支持交集、并集、差集等操作，适合标签去重、好友关系管理。import org.junit.jupiter.api.Test; import redis.clients.jedis.Jedis; import java.util.Set; public class RedisSetTest { @Test public void testSetOps() { Jedis jedis = RedisClient.getConnection(); try { // （1）存储用户 1 的标签（key：user:tags:1） jedis.sadd("user:tags:1", "足球", "篮球", "游戏"); // 存储用户 2 的标签（key：user:tags:2） jedis.sadd("user:tags:2", "篮球", "音乐", "电影"); // （2）获取用户 1 的所有标签（自动去重） Set<String> user1Tags = jedis.smembers("user:tags:1"); System.out.println("用户 1 标签：" + user1Tags); // （3）判断用户 1 是否有“游戏”标签 boolean hasGameTag = jedis.sismember("user:tags:1", "游戏"); System.out.println("用户 1 有游戏标签：" + hasGameTag); // 输出 true // （4）求两个用户的共同标签（交集） Set<String> commonTags = jedis.sinter("user:tags:1", "user:tags:2"); System.out.println("共同标签：" + commonTags); // 输出 [篮球] } finally { RedisClient.closeConnection(jedis); } } }5. Sorted Set 类型：实现商品销量排行榜Sorted Set 与 Set 类似，但每个元素关联一个“分数”（score），按分数自动排序，适合实现排行榜、带权重的队列。import org.junit.jupiter.api.Test; import redis.clients.jedis.Jedis; import redis.clients.jedis.Tuple; import java.util.Set; public class RedisSortedSetTest { @Test public void testSortedSetOps() { Jedis jedis = RedisClient.getConnection(); try { // 排行榜 key：product:sales:rank（按销量排序） String rankKey = "product:sales:rank"; // （1）存储商品销量（商品 ID 为元素，销量为分数） jedis.zadd(rankKey, 1000, "product1001"); // 商品 1 销量 1000 jedis.zadd(rankKey, 2500, "product1002"); // 商品 2 销量 2500 jedis.zadd(rankKey, 1800, "product1003"); // 商品 3 销量 1800 // （2）获取销量前 2 的商品（降序排列，0 表示第 1 名，1 表示第 2 名） Set<Tuple> top2 = jedis.zrevrangeWithScores(rankKey, 0, 1); System.out.println("销量前 2 商品："); for (Tuple tuple : top2) { System.out.println("商品 ID：" + tuple.getElement() + "，销量：" + tuple.getScore()); } // （3）获取商品 3 的销量排名（降序，返回排名索引，从 0 开始） long rank = jedis.zrevrank(rankKey, "product1003"); System.out.println("商品 3 销量排名：第 " + (rank + 1) + " 名"); // 输出第 2 名 } finally { RedisClient.closeConnection(jedis); } } }四、进阶：Jedis 连接池配置前文的简单连接方式不适合高并发场景，需使用 JedisPool（连接池）管理连接，减少连接创建/关闭的开销。1. 配置连接池import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class RedisPoolClient { // 连接池配置 private static final JedisPoolConfig POOL_CONFIG; // 连接池实例（单例） private static final JedisPool JEDIS_POOL; // 静态代码块：初始化连接池 static { // 1. 配置连接池参数 POOL_CONFIG = new JedisPoolConfig(); POOL_CONFIG.setMaxTotal(20); // 最大连接数 POOL_CONFIG.setMaxIdle(10); // 最大空闲连接数 POOL_CONFIG.setMinIdle(5); // 最小空闲连接数 POOL_CONFIG.setTestOnBorrow(true); // 借连接时测试可用性 // 2. 初始化连接池 JEDIS_POOL = new JedisPool( POOL_CONFIG, "localhost", // 地址 6379, // 端口 10000, // 连接超时时间（毫秒） "123456" // 密码 ); } // 从连接池获取连接 public static Jedis getConnection() { return JEDIS_POOL.getResource(); } // 关闭连接（实际是归还到连接池） public static void closeConnection(Jedis jedis) { if (jedis != null) { jedis.close(); } } }2. 使用连接池后续操作只需替换 RedisClient 为 RedisPoolClient 即可，用法完全一致：// 从连接池获取连接 Jedis jedis = RedisPoolClient.getConnection(); try { // 执行 Redis 命令（如 set、get 等） } finally { // 归还连接到池 RedisPoolClient.closeConnection(jedis); }五、入门必备：常见问题与最佳实践1. 键名设计规范使用冒号分隔层级：如 product:name:1（业务:字段:ID），便于区分和管理。避免过长键名：键名会占用内存，建议简洁（如用 user:1 而非 user_info_123）。统一命名风格：全小写，避免特殊字符，保持团队一致。2. 避免数据丢失：持久化配置Redis 内存数据默认不持久化，需手动开启 RDB 或 AOF：RDB：定期生成内存快照（适合数据恢复，但可能丢失少量数据）。AOF：记录每一条写命令（数据更安全，但文件
什么是 MyBatis-Plus？MyBatis-Plus（简称 MP）是 MyBatis 的增强工具，在 MyBatis 的基础上只做增强不做改变，旨在简化开发、提高效率。简单来说，MyBatis-Plus 可以帮你省去大量重复的 CRUD 代码，让你专注于业务逻辑开发。为什么选择 MyBatis-Plus？无侵入：只做增强不做改变，完全兼容 MyBatis强大的 CRUD 操作：内置通用 Mapper，无需编写 SQL 即可完成增删改查条件构造器：灵活构建查询条件，告别字符串拼接 SQL 的痛苦分页插件：一键实现分页功能，无需复杂配置代码生成器：自动生成 Entity、Mapper、Service、Controller 代码多种主键策略：支持自动生成主键，包括雪花算法、UUID 等环境准备技术依赖JDK 8+Maven 3.6+Spring Boot 2.7.x（本文以 Spring Boot 整合为例）MySQL 8.0（或其他关系型数据库）快速入门1. 创建数据库和表-- 创建数据库 CREATE DATABASE mp_demo DEFAULT CHARACTER SET utf8mb4; -- 使用数据库 USE mp_demo; -- 创建用户表 CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', create_time DATETIME NULL DEFAULT NULL COMMENT '创建时间', update_time DATETIME NULL DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 插入测试数据 INSERT INTO user (name, age, email) VALUES ('张三', 18, 'zhangsan@example.com'), ('李四', 20, 'lisi@example.com'), ('王五', 22, 'wangwu@example.com');2. 初始化 Spring Boot 项目并添加依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.15</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>3. 配置数据库连接application.ymlspring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 # 替换为你的数据库密码 # MyBatis-Plus 配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名 logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值4. 创建实体类（Entity）package com.example.mpdemo.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import java.time.LocalDateTime; import lombok.Data; @Data // Lombok 注解，自动生成getter、setter等方法 @TableName("user") // 对应数据库表名 public class User { @TableId(type = IdType.AUTO) // 主键自增 private Long id; private String name; private Integer age; private String email; @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; }5. 创建 Mapper 接口package com.example.mpdemo.mapper; import com.example.mpdemo.entity.User; import com.baomidou.mybatisplus.core.mapper.BaseMapper; // 继承 BaseMapper 即可获得基本的 CRUD 方法 public interface UserMapper extends BaseMapper<User> { }6. 在启动类添加注解package com.example.mpdemo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.example.mpdemo.mapper") // 扫描 Mapper 接口 public class MpDemoApplication { public static void main(String[] args) { SpringApplication.run(MpDemoApplication.class, args); } }7. 创建自动填充处理器（可选）用于自动填充 createTime 和 updateTime 字段：package com.example.mpdemo.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component public class MyMetaObjectHandler implements MetaObjectHandler { // 插入时的填充策略 @Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } // 更新时的填充策略 @Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } }核心功能实战1. 基本 CRUD 操作创建 Service 层（可选，但推荐）：package com.example.mpdemo.service; import com.example.mpdemo.entity.User; import com.baomidou.mybatisplus.extension.service.IService; public interface UserService extends IService<User> { }实现 Service：package com.example.mpdemo.service.impl; import com.example.mpdemo.entity.User; import com.example.mpdemo.mapper.UserMapper; import com.example.mpdemo.service.UserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }测试 CRUD 操作（使用 JUnit 测试）：package com.example.mpdemo; import com.example.mpdemo.entity.User; import com.example.mpdemo.service.UserService; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest public class UserServiceTest { @Autowired private UserService userService; // 测试查询所有 @Test public void testSelectAll() { List<User> users = userService.list(); users.forEach(System.out::println); } // 测试新增 @Test public void testInsert() { User user = new User(); user.setName("赵六"); user.setAge(25); user.setEmail("zhaoliu@example.com"); boolean save = userService.save(user); System.out.println("保存结果：" + save); } // 测试更新 @Test public void testUpdate() { User user = new User(); user.setId(1L); user.setAge(19); // 修改年龄 boolean update = userService.updateById(user); System.out.println("更新结果：" + update); } // 测试删除 @Test public void testDelete() { boolean remove = userService.removeById(4L); System.out.println("删除结果：" + remove); } }2. 条件构造器（QueryWrapper）QueryWrapper 是 MyBatis-Plus 的核心功能之一，用于构建复杂查询条件：// 测试条件查询 @Test public void testQueryWrapper() { // 1. 查询年龄大于20且邮箱不为空的用户 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.gt("age", 20) // 大于 .isNotNull("email"); List<User> users = userService.list(queryWrapper); System.out.println("年龄大于20且邮箱不为空的用户：" + users); // 2. 查询姓名包含"张"且年龄在15-25之间的用户 QueryWrapper<User> queryWrapper2 = new QueryWrapper<>(); queryWrapper2.like("name", "张") // 模糊查询 .between("age", 15, 25); List<User> users2 = userService.list(queryWrapper2); System.out.println("姓名包含张且年龄在15-25之间的用户：" + users2); // 3. 按年龄降序排序，只查询id和name字段 QueryWrapper<User> queryWrapper3 = new QueryWrapper<>(); queryWrapper3.orderByDesc("age") // 降序排序 .select("id", "name"); // 只查询指定字段 List<User> users3 = userService.list(queryWrapper3); System.out.println("按年龄降序排序的用户：" + users3); }3. 分页查询配置分页插件：package com.example.mpdemo.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyBatisPlusConfig { // 分页插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }测试分页查询：// 测试分页查询 @Test public void testPage() { // 第1页，每页2条数据 Page<User> page = new Page<>(1, 2); // 可以添加查询条件 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.ge("age", 18); // 年龄大于等于18 // 执行分页查询 Page<User> userPage = userService.page(page, queryWrapper); // 分页结果 System.out.println("总记录数：" + userPage.getTotal()); System.out.println("总页数：" + userPage.getPages()); System.out.println("当前页数据：" + userPage.getRecords()); }4. 逻辑删除逻辑删除并不是真正删除数据，而是通过一个字段标记数据是否被删除（如 deleted 字段）。添加逻辑删除字段到数据库表：ALTER TABLE user ADD COLUMN deleted INT DEFAULT 0 COMMENT '逻辑删除字段(0-未删除,1-已删除)';在实体类中添加对应字段：@TableLogic // 标记为逻辑删除字段 private Integer deleted;测试逻辑删除：// 测试逻辑删除 @Test public void testLogicDelete() { boolean remove = userService.removeById(1L); System.out.println("删除结果：" + remove); }执行后，数据并不会被真正删除，而是将 deleted 字段设为 1，查询时会自动过滤已删除的数据。代码生成器（AutoGenerator）MyBatis-Plus 提供了代码生成器，可以自动生成 Entity、Mapper、Service、Controller 等代码，极大提高开发效率。使用步骤：添加代码生成器依赖：<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency>编写代码生成器类：package com.example.mpdemo.generator; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; import java.util.Collections; public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create("jdbc:mysql://localhost:3306/mp_demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "123456") .globalConfig(builder -> { builder.author("yourname") // 设置作者 .enableSwagger() // 开启 Swagger 模式 .fileOverride() // 覆盖已生成文件 .outputDir(System.getProperty("user.dir") + "/src/main/java"); // 指定输出目录 }) .packageConfig(builder -> { builder.parent("com.example.mpdemo") // 设置父包名 .moduleName("") // 设置父包模块名 .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/mapper")); // 设置mapperXml生成路径 }) .strategyConfig(builder -> { builder.addInclude("user") // 设置需要生成的表名 .addTablePrefix("t_", "c_"); // 设置过滤表前缀 }) .templateEngine(new VelocityTemplateEngine()) // 使用Velocity引擎模板，默认就是Velocity .execute(); } }运行 main 方法，即可自动生成代码。常见问题解决依赖冲突：MyBatis-Plus 已经包含 MyBatis 依赖，无需重复引入 MyBatis分页插件不生效：检查是否正确配置了分页插件，是否在 Spring 容器中被扫描到逻辑删除不生效：确保实体类字段添加了 @TableLogic 注解，数据库字段默认值为 0字段填充不生效：检查填充处理器是否添加了 @Component 注解，是否正确实现了 MetaObjectHandler 接口条件构造器报错：注意字段名是否与数据库表字段一致，避免使用关键字作为字段名总结MyBatis-Plus 是一款非常优秀的 MyBatis 增强工具，通过本文的学习，你已经掌握了它的核心功能：基本 CRUD 操作（无需编写 SQL）条件构造器（灵活构建查询条件）分页查询（一键实现分页）逻辑删除（安全删除数据）代码生成器（自动生成重复代码）在实际项目中，合理使用 MyBatis-Plus 可以大幅减少重复劳动，提高开发效率。同时，对于复杂查询，MyBatis-Plus 也支持自定义 SQL，兼顾了灵活性和开发效率。