元素码农
基础
UML建模
数据结构
算法
设计模式
网络
TCP/IP协议
HTTPS安全机制
WebSocket实时通信
数据库
sqlite
postgresql
clickhouse
后端
rust
go
java
php
mysql
redis
mongodb
etcd
nats
zincsearch
前端
浏览器
javascript
typescript
vue3
react
游戏
unity
unreal
C++
C#
Lua
App
android
ios
flutter
react-native
安全
Web安全
测试
软件测试
自动化测试 - Playwright
人工智能
Python
langChain
langGraph
运维
linux
docker
工具
git
svn
🌞
🌙
目录
▶
Redis核心
▶
数据结构
字符串实现
哈希表实现
列表实现
集合实现
有序集合实现
▶
内存管理
内存分配策略
淘汰算法
▶
持久化
▶
RDB机制
快照生成原理
文件格式解析
▶
AOF机制
命令追加策略
重写过程分析
▶
高可用
▶
主从复制
SYNC原理
增量复制
▶
哨兵机制
故障检测
领导选举
▶
高级特性
▶
事务系统
ACID实现
WATCH原理
▶
Lua脚本
沙盒环境
执行管道
▶
实战问题
▶
缓存问题
缓存雪崩
缓存穿透
缓存击穿
缓存预热
▶
数据一致性
读写一致性
双写一致性
▶
性能优化
大key处理
热点key优化
发布时间:
2025-03-22 10:34
↑
☰
# Redis字符串(String)实现原理 ## 引言 Redis字符串是Redis中最基本且最常用的数据类型。本文将深入探讨Redis字符串的实现原理、内部编码方式以及实际应用场景,帮助读者更好地理解和使用这一核心数据结构。 ## 底层实现 ### SDS(Simple Dynamic String) Redis没有直接使用C语言的字符串(以空字符结尾的字符数组),而是自己构建了一种名为Simple Dynamic String(SDS)的抽象类型。SDS不仅保存了字符串,还记录了字符串的长度等信息。 ```c struct sdshdr { // 记录buf数组中已使用字节的数量 int len; // 记录buf数组中未使用字节的数量 int free; // 字节数组,用于保存字符串 char buf[]; }; ``` ### 优势特点 1. **常数复杂度获取字符串长度** - 由于SDS记录了len属性,获取字符串长度的时间复杂度为O(1) - 而C字符串需要遍历到结尾,复杂度为O(n) 2. **避免缓冲区溢出** - SDS在修改字符串时会先检查空间是否满足需求 - 如果不满足,会自动扩展空间,杜绝缓冲区溢出 3. **减少内存重分配次数** - 空间预分配:当SDS需要扩展时,会多分配一些空间 - 惰性空间释放:缩短字符串时,不立即释放多余空间 4. **二进制安全** - 可以存储任意二进制数据,不仅限于字符 - 以len属性判断字符串结束,而不是空字符 ## 内部编码 Redis会根据字符串的长度和内容采用不同的内部编码: ### 1. int编码 当字符串值是整数且在long范围内时,会用int编码: ```redis SET number "10086" ``` 这种情况下,Redis直接用long类型存储,节省内存。 ### 2. embstr编码 当字符串长度小于等于44字节时,使用embstr编码: ```redis SET name "Redis" ``` embstr的特点是: - 内存连续,一次分配 - 只读,修改时转换为raw编码 ### 3. raw编码 当字符串长度大于44字节时,使用raw编码: ```redis SET blog "这是一个很长的字符串..." ``` raw编码特点: - 需要两次内存分配 - 可以修改 - 内存使用相对embstr更多 ## 常用命令 ### 基本操作 ```redis # 设置字符串键值对 SET key value [EX seconds] [PX milliseconds] [NX|XX] # 获取字符串值 GET key # 获取指定范围的字符串 GETRANGE key start end # 设置指定位置的字符串 SETRANGE key offset value ``` ### 批量操作 ```redis # 批量设置键值对 MSET key1 value1 key2 value2 ... # 批量获取值 MGET key1 key2 ... ``` ### 计数操作 ```redis # 递增 INCR key INCRBY key increment # 递减 DECR key DECRBY key decrement ``` ## 应用场景 ### 1. 缓存 最常见的使用场景是作为缓存,存储热点数据: ```redis # 缓存用户信息 SET user:1001 "{\"name\":\"张三\",\"age\":25}" # 设置过期时间 SETEX cache_key 3600 "cached data" ``` ### 2. 计数器 利用INCR命令族实现计数功能: ```redis # 页面访问量 INCR page:views # 用户积分 INCRBY user:points 100 ``` ### 3. 分布式锁 利用SET命令的原子性实现分布式锁: ```redis # 获取锁 SET lock_key unique_value NX PX 10000 # 释放锁(使用Lua脚本保证原子性) if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end ``` ### 4. 共享Session 在分布式系统中存储会话信息: ```redis # 存储session SETEX session:token 1800 "{\"user_id\":1001,\"login_time\":1632047291}" ``` ## 性能优化 ### 1. 键值设计 - 避免过长的键名 - 遵循命名规范,如user:1001:profile - 合理设置过期时间 ### 2. 批量操作 优先使用批量命令: ```redis # 优先使用 MSET user:1:name "张三" user:2:name "李四" # 而不是多次 SET user:1:name "张三" SET user:2:name "李四" ``` ### 3. 内存优化 - 合理选择数据结构 - 及时清理过期数据 - 考虑使用压缩算法 ## 注意事项 1. **大小限制** - 单个String类型的值最大可以存储512MB - 建议控制在10KB以内 2. **原子性** - 所有单个命令都是原子的 - 多个命令的原子性需要使用事务或Lua脚本 3. **过期策略** - 建议设置过期时间 - 避免存储永久数据 ## 总结 Redis字符串通过SDS实现,提供了多种内部编码方式,在保证性能的同时节省内存。它的应用场景非常广泛,从简单的缓存到分布式锁都能胜任。在使用时需要注意数据大小、原子性和过期策略等问题,合理运用可以充分发挥Redis的性能优势。