0%

Redis 布隆过滤器详解:原理、安装与实践

布隆过滤器(Bloom Filter)是一种空间效率极高的概率性数据结构,用于判断一个元素是否属于某个集合。它的核心优势是占用内存少、查询速度快,但存在一定的误判率(False Positive)。Redis 本身不自带布隆过滤器,但可通过 RedisBloom 插件实现,广泛用于缓存穿透防护、海量数据去重等场景。

布隆过滤器核心原理

布隆过滤器由一个二进制数组(Bitmap)多个哈希函数组成,其工作流程如下:

1. 添加元素(Add)

  • 步骤 1:对元素 x 应用 k 个哈希函数,得到 k 个哈希值(整数)。
  • 步骤 2:将每个哈希值对 Bitmap 的长度 m 取模,得到 k 个索引位置。
  • 步骤 3:将 Bitmap 中这 k 个位置的值设为 1

2. 查询元素(Exists)

  • 步骤 1:对元素 x 应用同样的 k 个哈希函数,得到 k 个索引位置。
  • 步骤 2:检查 Bitmap 中这k个位置是否全为1:
    • 若有任何一个位置为 0,则元素一定不存在于集合中。
    • 若全为 1,则元素可能存在(存在误判,因不同元素可能哈希到相同位置)。

3. 关键特性

  • 空间效率:仅需少量内存(如存储 1 亿个元素,误判率 1% 时,约需 12MB 内存)。
  • 时间效率:添加和查询的时间复杂度均为 O(k)k 为哈希函数数量,通常为 3-10)。
  • 误判率:存在一定概率将 “不存在的元素” 判断为 “存在”,但不会将 “存在的元素” 判断为 “不存在”。
  • 不支持删除:无法从布隆过滤器中删除元素(删除会影响其他元素的判断)。

RedisBloom 插件安装(基于 Redis 6.0.10)

Redis 需通过 RedisBloom 插件启用布隆过滤器功能,步骤如下:

1. 下载与编译

阅读全文 »

Java Web 项目目录结构:规范与最佳实践

Java Web 项目的目录结构遵循一定的规范,合理的目录组织不仅便于开发维护,还能确保应用在不同 Servlet 容器(如 Tomcat、Jetty)中正常运行。本文将详细解析标准 Web 项目的目录结构、各目录的作用及配置要点。

Web 应用的根目录

Web 应用的根目录(通常称为上下文路径,Context Path)是应用部署到容器后的访问入口(如 http://localhost:8080/myapp 中的 myapp 即为根目录)。根目录下的文件和子目录可分为两类:公开资源(客户端可直接访问)和私有资源WEB-INF 目录,客户端不可直接访问)。

核心目录与文件详解

WEB-INF 目录(私有资源)

WEB-INF 是 Web 应用的核心私有目录,存放应用的配置文件、类文件和依赖库,客户端无法通过 URL 直接访问(需通过 Servlet 或 JSP 转发访问)。

(1)WEB-INF/web.xml

Web 应用的部署描述符,用于配置 Servlet、过滤器(Filter)、监听器(Listener)、初始化参数、错误页面等。
示例配置:

阅读全文 »

Spring Security 中获取用户信息的原理与实践

在 Web 应用中,Spring Security 能够在每次请求时识别用户身份并验证权限,核心在于 SecurityContextPersistenceFilterSecurityContext 的管理。本文结合源码解析这一机制的实现原理,并介绍在实际开发中获取用户信息的常用方式。

SecurityContext 的生命周期管理

SecurityContext 是存储用户认证信息的容器,其生命周期由 SecurityContextPersistenceFilter 全程管理,确保每个请求都能获取到当前用户的身份信息。

1. 核心流程:请求到来时加载上下文

当一个请求进入应用时,SecurityContextPersistenceFilter 会执行以下操作:

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
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;

// 确保过滤器只对同一请求请求处理一次
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

HttpRequest// 1. 从 Session 中加载 SecurityContext
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

try {
// 2. 将上下文存入 SecurityContextHolder(ThreadLocal 存储)
SecurityContextHolder.setContext(contextBeforeChainExecution);
// 3. 执行后续过滤器链(包括权限校验等)
chain.doFilter(holder.getRequest(), holder.getResponse());
} finally {
// 4. 请求结束:清除 ThreadLocal 中的上下文
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
// 5. 将上下文保存回 Session
repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
}
}
阅读全文 »

MySQL 存储表情符号:utf8 与 utf8mb4 的区别及解决方案

MySQL 中存储表情符号(如 😄、🎉 等)时出现的 Incorrect string value 错误,根源是字符集对 Unicode 编码的支持不全。默认的 utf8 字符集无法存储 4 字节的表情符号,需改用 utf8mb4 字符集。本文详细解析两者的区别及完整解决方案。

错误原因:utf8 字符集的局限性

MySQL 中的 utf8 字符集存在设计缺陷:

  • 实际是 utf8mb3 的别名,仅支持最多 3 字节 的 Unicode 字符。
  • 而表情符号(如 😄 的 Unicode 编码为 U+1F604)属于 4 字节字符,超出了 utf8mb3 的存储范围,因此插入时会报错:
    1366 - Incorrect string value: '\xF0\x9F\x98\x84' for column 'name' at row 1

utf8 与 utf8mb4 的核心区别

字符集 最大字节数 支持的 Unicode 范围 能否存储表情符号 适用场景
utf8 3 字节 基本多文种平面(BMP,U+0000~U+FFFF) 不能 无表情、无特殊符号的场景
utf8mb4 4 字节 全部 Unicode 字符(含 U+10000~U+10FFFF) 需存储表情、Emoji、特殊符号的场景
  • 关键结论utf8mb4 是 MySQL 中真正完整支持 UTF-8 编码的字符集,utf8 是不完整的子集(仅 3 字节)。

存储表情符号的完整解决方案

要在 MySQL 中正确存储表情符号,需从 字段、表、数据库 三个层级修改字符集为 utf8mb4,并确保连接层也使用该字符集。

阅读全文 »

Elasticsearch 分页查询:从基础到深度分页的解决方案

在 Elasticsearch 中,分页查询是获取大量数据的常用操作,但不同分页方式的性能和适用场景差异显著。本文详细解析 from+sizescrollsearch_after 三种分页方式的原理、用法及优缺点,帮助你根据业务场景选择最优方案。

基础分页:from+size(浅分页)

from+size 是最直观的分页方式,通过 from(跳过的条数)和 size(返回的条数)控制分页,适合数据量小、分页深度浅的场景(如前 10 页)。

用法示例

1
2
3
4
5
6
7
# 基础语法
GET index/_search
{
"from": 100, # 跳过前100条
"size": 20, # 返回20条
"query": { "match_all": {} }
}

原理

  • 当请求 from=100,size=20 时,Elasticsearch 会向每个分片发送请求,要求每个分片返回 from+size=120 条数据。
  • 协调节点收集所有分片的 120 条数据后,进行全局排序,再截取第 101~120 条作为结果返回。

优缺点

  • 优点:实现简单,支持随机跳页(如直接从第 1 页跳到第 10 页)。
  • 缺点:
    • 深度分页(from 过大,如 from=10000)时,每个分片需返回大量数据(10000+size),导致网络传输和排序开销激增,甚至触发内存限制(默认 index.max_result_window=10000,超过会报错)。

游标分页:scroll(批量处理)

scroll 适用于批量处理全量数据(如数据导出、索引迁移),通过创建数据快照生成 scroll_id,后续基于游标获取下一页,不支持随机跳页。

用法示例

(1)初始化 scroll 并获取第一页
阅读全文 »