0%

Redis Hash 数据结构实现详解(基于 6.0.10 版本)

Redis 中的 Hash 类型(哈希表)是一种键值对集合,底层采用混合编码策略(ziplist 或 hash table),兼顾内存效率和操作性能。本文结合源码结构,详解其实现机制、编码转换、哈希冲突处理及扩容策略。

Hash 类型的编码策略

Redis 的 Hash 类型根据数据规模自动选择两种编码方式,通过配置参数控制转换阈值:

编码方式 适用场景 转换条件(满足任一即触发)
OBJ_ENCODING_ZIPLIST(压缩列表) 元素少且小(键值均为短字符串) 1. 元素数量 > hash-max-ziplist-entries(默认 512) 2. 任一键 / 值长度 > hash-max-ziplist-value(默认 64 字节)
OBJ_ENCODING_HT(哈希表) 元素多或大(键值为长字符串 / 整数) 上述条件触发后,自动转为哈希表编码

两种编码的权衡:

  • ziplist:连续内存存储,无哈希表元数据开销,内存紧凑(适合小数据),但插入 / 删除需移动元素(O (N) 时间复杂度)。
  • hash table:哈希表结构,插入 / 删除 / 查询平均 O (1) 时间复杂度(适合大数据),但内存开销较高(需存储指针和哈希表结构)。

哈希表核心源码结构

当 Hash 类型采用 OBJ_ENCODING_HT 编码时,底层依赖 dict(字典)结构体实现,源码如下:

1. dict(字典)

1
2
3
4
5
6
7
typedef struct dict {
dictType *type; // 字典类型(包含哈希函数、键值复制/释放函数等)
void *privdata; // 私有数据(传给 type 中函数的参数)
dictht ht[2]; // 两个哈希表(ht[0] 存实际数据,ht[1] 用于扩容/缩容)
long rehashidx; // rehash 索引(-1 表示未进行 rehash,否则为当前迁移的数组下标)
int16_t pauserehash; // 暂停 rehash 标志(>0 表示暂停)
} dict;
  • 核心作用:管理哈希表的生命周期(创建、插入、查询、扩容等),通过 ht[0]ht[1] 实现动态扩容。
阅读全文 »

网络攻击:被动攻击与主动攻击的分类与特点

网络攻击是指通过网络对目标系统、设备或数据进行未授权的访问、干扰或破坏的行为。根据攻击方式和目标的不同,可分为被动攻击主动攻击两大类,两者在目标、手段和影响上有显著区别。

被动攻击(Passive Attacks)

被动攻击的核心特点是不直接干扰系统运行,而是通过窃听、监视等方式获取敏感信息,主要破坏数据的保密性。攻击者不会修改数据或中断服务,因此难以被察觉。

主要类型

  1. 消息内容监听(Eavesdropping)
    • 攻击者通过监听网络传输(如未加密的通信信道),窃取敏感信息(如用户名、密码、交易数据、私人消息等)。
    • 示例:在公共 Wi-Fi 中监听 HTTP 明文传输的登录信息,或通过网络嗅探工具(如 Wireshark)捕获未加密的数据包。
  2. 业务流分析(Traffic Analysis)
    • 攻击者不直接获取消息内容,而是通过分析通信模式(如通信频率、数据量、源地址、目标地址、传输时间等)推断敏感信息。
    • 示例:通过分析某公司与银行之间的通信频率和数据量,推测其业务规模或交易周期;通过监测军事基地的网络流量峰值,判断可能的军事行动。

防御手段

  • 加密传输:使用 SSL/TLS 等加密协议对通信内容进行加密(如 HTTPS),使监听的消息无法被解析。
  • 流量混淆:通过随机化数据包大小、添加冗余数据等方式,干扰攻击者的业务流分析。
  • 物理隔离:对敏感网络(如内部办公网)采取物理隔离措施,限制外部访问。

主动攻击(Active Attacks)

主动攻击的核心特点是主动干预系统运行,通过修改数据、伪造身份、中断服务等方式破坏系统的可用性、完整性或真实性,攻击行为通常会对系统造成直接影响,较容易被检测到。

阅读全文 »

Scala 泛型:类型安全与灵活性的平衡

泛型是 Scala 中实现类型抽象的核心机制,它允许在定义类、特质、方法时使用类型参数,从而编写与具体类型无关的通用代码。Scala 泛型不仅支持类似 Java 的上下界约束,还引入了协变、逆变等独特特性,进一步增强了类型系统的灵活性。本文将全面解析 Scala 泛型的用法与高级特性。

泛型基础:类型参数的定义与使用

Scala 泛型使用方括号 [] 定义类型参数,可用于类、特质、方法等,实现代码的复用与类型安全。

泛型类

在类定义中声明类型参数,使类能处理多种类型的数据:

1
2
3
4
5
6
7
8
9
10
11
12
// 定义泛型类 Box,类型参数为 T
class Box[T](val content: T) {
// 方法返回值使用类型参数 T
def getContent: T = content
}

// 创建不同类型的 Box 实例
val intBox = new Box[Int](10)
val strBox = new Box[String]("hello")

println(intBox.getContent) // 输出:10(类型为 Int)
println(strBox.getContent) // 输出:hello(类型为 String)

泛型方法

在方法中声明类型参数,使方法能独立于类的类型参数处理不同类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
object GenericUtils {
// 泛型方法:交换数组中两个位置的元素
def swap[T](array: Array[T], i: Int, j: Int): Unit = {
val temp = array(i)
array(i) = array(j)
array(j) = temp
}
}

// 测试泛型方法
val intArray = Array(1, 2, 3)
GenericUtils.swap(intArray, 0, 2)
println(intArray.mkString(",")) // 输出:3,2,1

val strArray = Array("a", "b", "c")
GenericUtils.swap(strArray, 1, 2)
println(strArray.mkString(",")) // 输出:a,c,b
阅读全文 »

J2EE 分布式事务规范:JTA、JTS 与两阶段提交协议

在分布式系统中,保证跨多个资源(如数据库、消息队列)的事务一致性是核心挑战。J2EE 提供了两套规范支持分布式事务:JTA(Java Transaction API)JTS(Java Transaction Service)。其中,JTA 定义了分布式事务的高层接口,JTS 则规定了事务管理器的实现标准,而两阶段提交协议(2PC) 是实现分布式事务强一致性的核心机制。本文将详细解析这些概念及其工作原理。

JTA 与 JTS:分布式事务的规范体系

J2EE 的分布式事务规范由 JTA 和 JTS 共同构成,二者分工明确,形成 “接口定义 - 实现规范” 的层级关系。

JTA:分布式事务的高层 API

JTA(javax.transaction 包)是一套与具体实现无关、与协议无关的高层接口规范,定义了分布式事务的核心操作(如开始、提交、回滚),供开发者与事务管理器交互。其核心目标是屏蔽底层资源(如数据库、消息队列)的差异,提供统一的事务编程模型。

JTA 主要包含以下接口:

接口 作用描述
UserTransaction 供应用程序直接使用的事务操作接口,提供 begin()commit()rollback() 等方法,控制事务生命周期。
Status 定义事务的状态常量(如 STATUS_ACTIVESTATUS_COMMITTEDSTATUS_ROLLEDBACK 等),用于表示事务当前状态。
Synchronization 允许应用程序在事务提交或回滚前后注册回调逻辑(beforeCompletion()afterCompletion()),用于资源清理或日志记录。
Transaction 代表一个分布式事务对象,提供获取事务状态、注册同步器等方法,主要由事务管理器内部使用。
TransactionManager 由事务管理器实现,负责事务的创建、传播和管理,应用程序通常不直接使用,而是通过 UserTransaction 间接交互。
UserTransaction 核心方法示例:
阅读全文 »

spark广播变量:高效分发大对象的分布式只读变量

在 Spark 分布式计算中,当需要在多个 Task 间共享大对象(如配置表、字典数据)时,默认的闭包传递机制会导致数据重复传输和内存浪费。广播变量(Broadcast Variable)作为 Spark 提供的第二种共享变量,通过优化数据分发策略,显著提升了大规模数据共享的效率。本文将详细解析广播变量的原理、用法及最佳实践。

广播变量的核心问题与解决思路

闭包数据分发的痛点

在 Spark 中,当 RDD 操作(如 mapjoin)的闭包中引用外部变量时,变量会被序列化并复制到每个 Task 中。若变量较大(如 1GB 的字典表),则会导致:

  • 网络传输开销大:每个 Task 都需下载完整变量,集群网络压力剧增;
  • 内存浪费:一个 Executor 上的多个 Task 持有相同变量的副本,占用大量内存;
  • 任务启动慢:变量序列化和传输耗时,延长任务准备时间。

示例:无广播变量的低效分发

1
2
3
4
5
val largeMap = Map(/* 1GB 数据 */)  // 大对象  
val rdd = sc.parallelize(1 to 10000, 100) // 100 个分区

// 每个 Task 都会复制 largeMap,共 100 份
val result = rdd.map(x => largeMap.getOrElse(x, 0))

广播变量的优化机制

广播变量通过以下方式解决上述问题:

  • 每个节点仅存一份:大对象被广播到每个 Executor 节点,而非每个 Task;
  • 共享访问:Executor 上的所有 Task 共享同一份变量副本;
  • 高效分发协议:使用 BitTorrent 类似的分布式传输协议,避免 Driver 成为瓶颈。

广播变量的基本用法

广播变量的创建与使用

广播变量的使用流程分为三步:创建 → 访问 → 销毁(可选)。

阅读全文 »