0%

HTML 列表样式详解:有序列表、无序列表与自定义样式全指南

HTML 列表是网页中组织结构化内容的核心元素,主要分为 有序列表(<ol>无序列表(<ul>,二者均通过 <li> 标签定义列表项。结合 CSS 的 list-style-type 等属性,可实现从 “默认符号” 到 “自定义图标” 的丰富样式。从 “核心标签→样式属性→自定义方案→实战场景” 四个维度,全面讲解 HTML 列表的使用与美化技巧。

HTML 列表核心标签

HTML 列表的基础结构由 “容器标签” 和 “列表项标签” 组成,两种列表的核心标签与默认行为差异如下:

1. 有序列表(<ol>:Ordered List)

  • 作用:用于展示有明确顺序的内容(如步骤、排名、优先级),默认以数字(1, 2, 3…)作为标记;
  • 核心标签:
    • <ol>:有序列表容器,定义列表的整体范围;
    • <li>:列表项(List Item),封装每一条具体内容,必须嵌套在 <ol> 内;
  • 默认样式:数字标记(decimal),列表项左侧缩进,上下有默认间距。
基础示例:
1
2
3
4
5
6
<!-- 有序列表基础用法 -->
<ol>
<li>第一步:打开浏览器</li>
<li>第二步:输入目标 URL</li>
<li>第三步:按下回车键访问</li>
</ol>
默认效果:
  1. 第一步:打开浏览器
  2. 第二步:输入目标 URL
  3. 第三步:按下回车键访问

2. 无序列表(<ul>:Unordered List)

  • 作用:用于展示无顺序关系的内容(如商品列表、标签、导航菜单),默认以实心圆(●)作为标记;
  • 核心标签:
    • <ul>:无序列表容器,定义列表的整体范围;
    • <li>:列表项,必须嵌套在 <ul> 内;
  • 默认样式:实心圆标记(disc),列表项左侧缩进,上下有默认间距。
基础示例:
阅读全文 »

Redis quicklist 实现详解(基于 6.0.10 版本)

Redis 的 List 类型在 3.2 版本后引入了 quicklist 作为底层实现,替代了此前的 “ziplist + linkedlist” 混合策略。quicklist 结合了 ziplist 的内存紧凑性和 linkedlist 的灵活扩展性,是 Redis 对列表结构的一次重要优化。本文结合源码结构和设计思路,详解 quicklist 的实现机制。

quicklist 的设计背景:为何替代旧方案?

在 3.2 版本前,Redis List 采用自适应编码:

  • 元素少时用 ziplist(压缩列表):连续内存存储,节省空间,但插入 / 删除大元素时需移动大量数据(O (N) 时间复杂度)。
  • 元素多时用 linkedlist(双向链表):节点独立分配内存,插入 / 删除高效(O (1)),但指针开销大(每个节点的 prev/next 指针占 16 字节),且内存碎片化严重。

旧方案的核心问题

  • ziplist 过大时(如存储 10 万个元素),一次插入 / 删除操作可能阻塞主线程(需移动大量内存)。
  • linkedlist 节点分散存储,内存利用率低,且缓存命中率差(CPU 缓存难以命中连续数据)。

quicklist 的改进思路
将 List 拆分为多个小段,每段用 ziplist 存储(保持紧凑性),段之间用双向指针连接(保持灵活性)。既避免了单个 ziplist 过大的性能问题,又减少了 linkedlist 的指针开销和碎片化。

quicklist 核心结构解析

quicklist 的实现依赖两个核心结构体:quicklist(列表本身)和 quicklistNode(列表节点)。

1. quicklist 结构体(列表整体)

阅读全文 »

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 脚本封装操作,确保添加、移除、计数三步原子执行:

阅读全文 »