0%

IO模型

linux中有几种IO模型,如select、poll、epoll,这几个分别是什么呢?

select 模型

  • 工作流程:
    1. 首先创建一个文件描述符(fd)集合,添加需要监测的 socket 句柄。
    2. 调用select()函数,将 fd 集合从用户态拷贝到内核态。
    3. 内核遍历所有 fd,检查是否有 IO 事件(如可读、可写)就绪。
    4. 若有就绪事件,内核修改 fd 集合标记就绪的 fd,然后将集合拷贝回用户态。
    5. 用户态遍历 fd 集合,找到就绪的 fd 并处理。
  • 缺点:
    • 最大 fd 限制:默认 1024(可通过修改内核参数调整,但不推荐)。
    • 效率低:每次调用都需拷贝 fd 集合,且内核和用户态都要遍历所有 fd,高并发下开销剧增。
    • 水平触发(LT):若就绪事件未被处理,下次调用仍会通知,可能导致重复处理。

poll 模型

阅读全文 »

Linux 系统缓慢排查:从负载分析到根源定位

当 Linux 系统出现卡顿、响应缓慢时,需要系统性地排查资源瓶颈(CPU、内存、IO 等)。本文基于 “先定位瓶颈类型,再深挖具体原因” 的思路,详细介绍排查步骤和工具,帮助快速定位系统缓慢的根源。

第一步:通过 uptime 判断系统负载

uptime 是排查系统缓慢的起点,它能快速反映系统的整体负载情况:

1
2
uptime
# 输出示例:14:22:15 up 226 days, 15:08, 2 users, load average: 0.38, 0.19, 0.15

关键指标:平均负载(load average)

  • 三个数值分别代表1 分钟、5 分钟、15 分钟内的系统平均负载,表示单位时间内等待 CPU 处理的任务数(包括运行中、就绪和不可中断睡眠的进程)。
  • 判断标准:
    • 若负载值 ≤ CPU 核心数:系统负载正常(如 4 核 CPU 负载 ≤ 4 属正常)。
    • 若负载值 > CPU 核心数且持续升高:系统可能存在资源瓶颈。
    • 对比三个值可判断负载趋势:1 分钟 > 5 分钟 > 15 分钟 → 负载正在升高;反之则降低。

第二步:确定瓶颈类型(CPU / 内存 / IO)

系统缓慢的核心原因通常是 CPU、内存或 IO 中的某一项资源饱和。需结合工具进一步分析:

检查 CPU 瓶颈:topmpstat

top 观察 CPU 使用率:
1
top  # 进入实时监控界面,按 P 按 CPU 使用率排序

关注指标

  • %CPU 列:单个进程的 CPU 占用率(若某进程长期 ≥ 90%,可能是元凶)。
  • 顶部%Cpu行:
    • us(用户态 CPU):若长期 ≥ 80%,可能是应用程序(如 Java、Python)消耗过多 CPU。
    • sy(系统态 CPU):若长期 ≥ 30%,可能是内核操作(如频繁系统调用、进程调度)异常。
    • wa(IO 等待 CPU):若长期 ≥ 20%,说明 CPU 因等待磁盘 IO 而空闲,需重点排查 IO 问题。
    • id(空闲 CPU):若长期 ≤ 10%,说明 CPU 资源紧张。
mpstat 分析多核心负载:
阅读全文 »

Redis 实现限流:从滑动窗口到多方案对比

限流是保障系统稳定性的关键手段,通过控制单位时间内的请求量,防止服务因过载而崩溃。Redis 凭借其高性能和原子操作特性,成为实现分布式限流的常用工具。

滑动窗口限流

基于 Redis 的 ZSet 实现了滑动窗口限流,核心思想是精确统计任意时间窗口内的请求量,避免固定窗口的 “边界突发” 问题。

通过统计该窗口内的行为数量和限制的最大数量maxCount进行比较就可以得出当前的请求是否允许

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
public class RedisRateLimiter {
@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
public void test() {
for (int i = 0; i < 10; i++) {
System.out.println(isActionAllow("user/list", "127.0.0.1", 1, 5));
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}

}

}

/**
* 使用zset实现简单的滑动窗口限流
*
* @param uri
* @param ip
* @param period 时间窗口 单位秒
* @param maxCount 最大允许数量
* @return
*/
public boolean isActionAllow(String uri, String ip, int period, int maxCount) {
// key为 uri和ip
String key = String.format("hist:%s:%s", uri, ip);
long cur = System.currentTimeMillis();
List<Object> pipelined = stringRedisTemplate.executePipelined(
new RedisCallback<Long>() {

@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
// 记录行为
connection.zAdd(key.getBytes(), cur, String.valueOf(cur).getBytes());
// 移除时间窗口之前的行为记录
connection.zRemRangeByScore(key.getBytes(), 0, cur - period * 1000);
// 获取窗口内的行为数量
Long count = connection.zCard(key.getBytes());
// 清理冷数据,防止冷数据持续占用内存
connection.expire(key.getBytes(), period + 1);
return count;
}
}
);

// System.out.println(pipelined);
Object o = pipelined.get(2);
// System.out.println(o);
return Long.parseLong(String.valueOf(o)) <= maxCount;
}
}

如果时间窗口内允许的数量较大,会消耗大量的内存。则不适合该方式

实现原理

  • 存储结构:用 ZSet 存储请求记录,score 为请求时间戳(毫秒级),value 为时间戳(确保唯一性)。
  • 核心操作:
    1. 新增当前请求的时间戳到 ZSet
    2. 移除时间窗口外的记录(如窗口为 1 秒,则移除 当前时间 - 1000ms 之前的记录)。
    3. 统计窗口内的记录数,若小于等于 maxCount 则允许请求,否则限流。
    4. ZSet 设置过期时间,避免冷数据占用内存。

代码优化:原子性保证

上述代码使用了 pipeline 批量执行命令,但 pipeline 仅能减少网络往返,不能保证操作的原子性。高并发下,可能出现 “新增记录后,其他请求已修改了 ZSet,导致计数不准确” 的问题。

解决方案:用 Lua 脚本封装操作,确保添加、移除、计数三步原子执行:

阅读全文 »

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)、初始化参数、错误页面等。
示例配置:

阅读全文 »