0%

Tomcat 中的 Pipeline 与 Valve:请求处理的责任链模式

Tomcat 的 Pipeline(管道)和 Valve(阀门)是其核心组件,用于处理请求和响应的流程控制。它们基于责任链模式设计,将请求处理的不同任务拆解为独立的阀门,通过管道串联执行,实现了请求处理逻辑的解耦与灵活扩展。本文将深入解析 Pipeline 与 Valve 的工作原理、结构及配置方式。

Pipeline 与 Valve 的核心概念

基本定义

  • Pipeline(管道)
    每个 Tomcat 容器(EngineHostContextWrapper)都包含一个 Pipeline,它是一个任务执行链,负责按顺序调用一系列 Valve 处理请求。
  • Valve(阀门)
    每个 Valve 代表一个具体的处理任务(如日志记录、权限校验、请求转发等),类似于过滤器(Filter),但作用于 Tomcat 容器级别而非 Web 应用级别。
  • Basic Valve(基础阀门)
    每个 Pipeline 必须包含一个 Basic Valve,作为管道的最终处理器(责任链的终点),负责执行容器的核心逻辑(如 Wrapper 的 Servlet 调用、Host 的虚拟主机路由)。

设计思想:责任链模式

Pipeline 与 Valve 的设计借鉴了责任链模式,其核心特点是:

  • 每个 Valve 只处理自己负责的任务,处理完成后将请求传递给下一个 Valve。
  • 可通过添加 / 移除 Valve 灵活扩展请求处理逻辑,无需修改原有代码。
  • 不同容器的 Pipeline 独立工作,形成层级化的处理流程(如 Engine → Host → Context → Wrapper)。

Pipeline 与 Valve 的结构与实现

容器与 Pipeline 的对应关系

Tomcat 的四大容器(从顶层到应用层)均内置 Pipeline:

阅读全文 »

Maven 编码格式配置:统一项目编码避免乱码问题

在 Maven 构建过程中,若出现编码格式不一致(如默认 GBK 与项目 UTF-8 冲突),可能导致资源文件乱码、日志警告或构建失败。本文详细介绍如何统一 Maven 编码格式,确保项目构建的一致性。

编码问题的表现与影响

当 Maven 编码与项目编码不一致时,常见现象包括:

  • 构建日志出现警告:[WARNING] Using platform encoding (GBK actually) to copy filtered resources, i.e. build is platform dependent!
  • 资源文件(如 *.properties*.xml)在过滤或复制过程中出现乱码。
  • 编译后的 class 文件包含中文乱码,导致运行时异常。

这些问题的核心原因是:Maven 默认使用操作系统的平台编码(如 Windows 通常为 GBK),而项目可能采用 UTF-8 编码。

全局编码配置(推荐)

通过配置环境变量,统一 Maven 所有项目的编码格式,一劳永逸解决问题。

配置 MAVEN_OPTS 环境变量

步骤

  1. 打开系统环境变量配置(Windows 可通过「此电脑→属性→高级系统设置→环境变量」)。

  2. 新建系统变量MAVEN_OPTS,值为:

    1
    -Dfile.encoding=UTF-8
  3. 重启命令行工具(确保环境变量生效)。

验证:执行 mvn --version,查看输出中 Default locale 对应的编码是否为 UTF-8:

1
Default locale: zh_CN, platform encoding: UTF-8
阅读全文 »

二叉树:结构、分类与遍历全解析

二叉树是一种每个节点最多有两个子树(左子树和右子树)的树形结构,是数据结构中的基础且重要的模型。其简洁的层次关系和灵活的操作特性,使其广泛应用于索引、表达式解析、人工智能等领域。本文将从性质、分类、存储到遍历,全面解析二叉树。

二叉树的基本性质

二叉树的核心性质描述了节点数量、层数、深度之间的关系,是理解其结构的基础:

  1. 第 i 层节点数上限
    二叉树第i层(根节点为第 1 层)上的节点数目至多为 2^(i-1)
    • 例:第 3 层最多有 2^(3-1)=4 个节点。
  2. 深度为 k 的节点总数上限
    深度为k的二叉树至多有 2^k - 1 个节点(满二叉树的情况)。
    • 例:深度为 3 的满二叉树有 2^3 - 1 = 7 个节点。
  3. 叶子节点与度为 2 的节点关系
    对任何二叉树,若叶子节点数为n₀,度为 2 的节点数为n₂,则 n₀ = n₂ + 1
    • 原理:总边数 = 总节点数 - 1,且总边数 = 度为 1 的节点数 + 2× 度为 2 的节点数,联立推导可得。
  4. 完全二叉树的深度
    具有n个节点的完全二叉树,深度为 ⌊log₂n⌋ + 1⌊x⌋ 表示向下取整)。
    • 例:n=7时,深度为 ⌊log₂7⌋ + 1 = 2 + 1 = 3

二叉树的分类

根据结构和特性,二叉树可分为以下几类:

普通二叉树

每个节点最多有 2 个子树(左子树和右子树),无其他约束。

二叉查找树(BST,Binary Search Tree)

核心特性

阅读全文 »

spark连接jdbc数据库全指南:从 JdbcRDD 到 DataFrame

Spark 提供了多种方式连接关系型数据库(如 MySQL、PostgreSQL),实现数据的读写操作。其中,JdbcRDD 是早期基于 RDD 的数据库交互方式,而 DataFrame API 则提供了更简洁、高效的操作接口。本文将详细介绍 Spark 连接 JDBC 数据库的两种核心方法,包括使用 JdbcRDD 进行低阶操作,以及通过 DataFrame 实现高阶数据处理,并提供最佳实践与优化技巧。

基于 RDD 的 JDBC 连接:JdbcRDD

JdbcRDD 是 Spark 早期为 RDD 设计的 JDBC 交互工具,允许通过 SQL 查询从数据库读取数据并转换为 RDD。其核心思想是并行化执行 SQL 查询,通过分区键将数据分片到多个 Task 中读取。

JdbcRDD 的基本用法

核心构造参数

JdbcRDD 的构造函数需指定以下关键参数:

1
2
3
4
5
6
7
8
9
JdbcRDD(  
sc: SparkContext, // SparkContext 实例
getConnection: () => Connection, // 生成数据库连接的函数
sql: String, // 查询 SQL(需包含分区占位符)
lowerBound: Long, // 分区键下界
upperBound: Long, // 分区键上界
numPartitions: Int, // 分区数
mapRow: (ResultSet) => T // 结果集转换函数(将 ResultSet 映射为 RDD 元素)
)
完整示例:读取 MySQL 数据
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
import org.apache.spark.rdd.JdbcRDD  
import java.sql.{Connection, DriverManager, ResultSet}

object JdbcRDDExample {
def main(args: Array[String]): Unit = {
val sc = new SparkContext("local[*]", "JdbcRDDExample")

// 1. 定义数据库连接函数(每个分区创建一个连接)
def getConnection(): Connection = {
Class.forName("com.mysql.cj.jdbc.Driver") // MySQL 8.0+ 驱动
DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC",
"root", // 数据库用户名
"password" // 数据库密码
)
}

// 2. 定义结果集映射函数(将 ResultSet 转换为元组)
def mapRow(rs: ResultSet): (Int, String, Int) = {
(rs.getInt("id"), rs.getString("name"), rs.getInt("age"))
}

// 3. 创建 JdbcRDD(按 id 分区查询)
val jdbcRDD = new JdbcRDD(
sc,
getConnection,
sql = "SELECT id, name, age FROM user WHERE id >= ? AND id <= ?", // 带分区占位符的 SQL
lowerBound = 1, // id 下界
upperBound = 100, // id 上界
numPartitions = 3, // 分为 3 个分区
mapRow = mapRow // 结果映射函数
)

// 4. 处理数据
jdbcRDD.collect().foreach(println)

sc.stop()
}
}
阅读全文 »

Redis 过期提醒功能详解:基于键空间事件的实现

Redis 的 notify-keyspace-events 配置提供了键空间事件通知功能,可实时监听键的过期、删除、修改等事件,其中过期提醒是最常用的场景(如订单超时取消、会话过期清理等)。本文基于 Redis 6.0.10 版本,详解过期提醒的配置、订阅方式及实践注意事项。

核心原理:键空间事件(Keyspace Events)

Redis 的事件通知基于发布 - 订阅(Pub/Sub) 机制:当键发生特定操作(如过期、删除)时,Redis 会自动向指定频道发布事件消息,客户端通过订阅这些频道即可实时获取通知。

  • 事件分类:
    • keyspace 事件:以 __keyspace@<db>__:<key> 为频道,消息为事件类型(如 expired)。
    • keyevent 事件:以 __keyevent@<db>__:<event> 为频道,消息为键名(如 order:1001)。
      (过期提醒通常关注 keyevent 事件,直接获取过期的键名。)

配置开启过期提醒

配置 notify-keyspace-events

redis.conf 中设置事件通知类型,开启过期事件(x)和键事件(E):

1
notify-keyspace-events Ex
  • E:启用 keyevent 事件(按事件类型订阅)。
  • x:启用过期事件(键过期时触发)。

动态修改配置(无需重启)

阅读全文 »