熬夜整理!Redis 9种数据类型详解,第6种大部分人都没用过
熬夜整理!Redis 9种数据类型详解,第6种大部分人都没用过
从 String 到 Stream,从理论到实战,一文搞懂 Redis 所有数据类型
📌 写在前面
最近在复习 Redis,逛了下技术贴,发现挺多人只知道 String、List、Hash、Set、Sorted Set 这五种基本类型,遇到 UV 统计、附近的人、消息队列等场景就不知道怎么下手了。
其实 Redis 还有 Bitmap、HyperLogLog、Geo、Stream 等高级类型,用好它们,很多难题迎刃而解。
这篇文章花了几天时间整理,结合 Java 代码 + 实战场景,建议收藏慢慢看。
一、五种基本类型(面试必问,开发必会)
1.1 String —— 最通用的存储单元
一句话解释:一个 Key 对应一个字符串值,最大 512MB。二进制安全,图片、序列化对象都能存。
底层结构:SDS(简单动态字符串),预分配空间减少内存重分配。
实战场景:
- 缓存对象(JSON 字符串)
- 计数器(阅读量、点赞数)
- 分布式锁
- Session 共享
// 实战:接口限流(每分钟最多100次)
public boolean rateLimit(String userId) {
String key = "rate:limit:" + userId;
try (Jedis jedis = pool.getResource()) {
long count = jedis.incr(key);
if (count == 1) {
jedis.expire(key, 60);
}
return count <= 100;
}
}
1.2 Hash —— 对象存储神器
一句话解释:类似 Java 的 HashMap,适合存储对象的各个属性,可单独操作每个字段。
底层结构:字段少用压缩列表(ziplist),字段多用哈希表(hashtable)。
实战场景:
- 用户信息(可单独更新年龄/昵称)
- 购物车(商品ID为field,数量为value)
- 配置项
// 实战:购物车功能
public void addItem(String userId, String productId, int quantity) {
String key = "cart:" + userId;
try (Jedis jedis = pool.getResource()) {
jedis.hincrBy(key, productId, quantity);
jedis.expire(key, 86400); // 保留1天
}
}
public Map<String, String> getCart(String userId) {
try (Jedis jedis = pool.getResource()) {
return jedis.hgetAll("cart:" + userId);
}
}
1.3 List —— 有序可重复的队列
一句话解释:双向链表结构,可以从头尾两侧操作,有序可重复。
底层结构:数据少用压缩列表,数据多用快速列表(quicklist)。
实战场景:
- 消息队列(生产者 rpush,消费者 lpop)
- 最新动态(lpush + ltrim)
- 历史记录
// 实战:用户操作日志(保留最近100条)
public void recordAction(String userId, String action) {
String key = "actions:" + userId;
try (Jedis jedis = pool.getResource()) {
jedis.lpush(key, System.currentTimeMillis() + "|" + action);
jedis.ltrim(key, 0, 99); // 只保留100条
}
}
1.4 Set —— 自动去重的集合
一句话解释:无序且元素唯一,支持交集、并集、差集运算。
底层结构:全是整数用整数集合(intset),否则用哈希表。
实战场景:
- 标签系统(文章的标签集合)
- 共同好友(sinter)
- 抽奖(spop随机抽取)
// 实战:抽奖系统
public Set<String> draw(String activityId, int n) {
try (Jedis jedis = pool.getResource()) {
// 随机抽取n个中奖者(不放回)
return jedis.spop("lottery:" + activityId + ":participants", n);
}
}
1.5 Sorted Set —— 带排名的集合
一句话解释:每个元素关联一个分数(score),按分数自动排序,元素唯一。
底层结构:数据少用压缩列表,数据多用跳表(skiplist)+ 哈希表。
实战场景:
- 排行榜(游戏积分榜)
- 延迟队列(分数=执行时间戳)
- 带权重的任务队列
// 实战:游戏排行榜
public void updateScore(String playerId, long score) {
try (Jedis jedis = pool.getResource()) {
jedis.zadd("game:leaderboard", score, playerId);
}
}
// 获取前10名
public Set<Tuple> getTop10() {
try (Jedis jedis = pool.getResource()) {
return jedis.zrevrangeWithScores("game:leaderboard", 0, 9);
}
}
二、高级类型(大部分人没用过,但真的很强)
以下内容建议重点看,面试官问"用过哪些高级数据类型"时,你可以侃侃而谈。
2.1 Bitmap —— 位操作的大杀器
本质:String 类型的特殊用法,把字符串当成位数组操作。1个字节 = 8个位。
内存计算:存储 1 亿个用户的布尔状态仅需 12MB!
实战场景:
- 签到记录(365天只需46字节)
- 在线状态
- 用户行为埋点
// 实战:签到系统
public void signIn(long userId, LocalDate date) {
String key = "sign:" + userId + ":" + date.getYear();
int dayOfYear = date.getDayOfYear(); // 1-365
try (Jedis jedis = pool.getResource()) {
jedis.setbit(key, dayOfYear - 1, true);
}
}
// 本月签到天数
public long getMonthSignCount(long userId, YearMonth month) {
String key = "sign:" + userId + ":" + month.getYear();
int startDay = month.atDay(1).getDayOfYear();
int endDay = month.atEndOfMonth().getDayOfYear();
try (Jedis jedis = pool.getResource()) {
return jedis.bitcount(key, startDay - 1, endDay - 1);
}
}
2.2 HyperLogLog —— 基数统计黑科技
一句话解释:统计不重复元素的数量(基数),不存储元素本身,只存概率估算值。
内存占用:每个 key 仅 12KB,可统计上亿数据!
错误率:约 0.81%,适合不要求精确计数的场景。
实战场景:
- UV 统计(独立访客数)
- 日活月活
- 搜索词去重计数
// 实战:页面UV统计
public void visit(String pageId, String userId) {
String key = "uv:page:" + pageId + ":" + LocalDate.now();
try (Jedis jedis = pool.getResource()) {
jedis.pfadd(key, userId);
jedis.expire(key, 2592000); // 30天后过期
}
}
public long getTodayUV(String pageId) {
try (Jedis jedis = pool.getResource()) {
return jedis.pfcount("uv:page:" + pageId + ":" + LocalDate.now());
}
}
2.3 Geo —— 地理位置就用它
一句话解释:存储经纬度,支持距离计算和范围查询。基于 Sorted Set 实现。
实战场景:
- 附近的人
- 打车匹配
- 门店距离排序
// 实战:附近店铺搜索
public void addShop(Long shopId, double lng, double lat, String name) {
try (Jedis jedis = pool.getResource()) {
jedis.geoadd("geo:shops", lng, lat, String.valueOf(shopId));
jedis.hset("shop:" + shopId, "name", name);
}
}
// 查找附近1km内的店铺
public List<GeoRadiusResponse> findNearby(double lng, double lat, double radiusKm) {
try (Jedis jedis = pool.getResource()) {
return jedis.georadius("geo:shops", lng, lat, radiusKm,
GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist().sortAscending());
}
}
2.4 Bitfield —— 终极位操作器
一句话解释:Redis 3.2+ 引入,可对一个字符串进行多段位域操作,支持有符号/无符号整数。
优势:一条命令完成多个位的原子操作,比多次 getbit/setbit 高效得多。
实战场景:
- 紧凑存储多个小数字(用户的多项统计数据)
- 原子计数器(库存分段扣减)
// 实战:用户原子统计数据(64位存储多个指标)
// 位域布局:bit0-31登录次数 | bit32-47等级 | bit48-55VIP等级
public void incrLoginCount(String userId) {
try (Jedis jedis = pool.getResource()) {
// INCRBY 对指定位域递增(32位无符号)
jedis.bitfield("stats:" + userId, "INCRBY", "u32", "0", "1");
}
}
public Map<String, Long> getAllStats(String userId) {
try (Jedis jedis = pool.getResource()) {
List<Long> results = jedis.bitfield("stats:" + userId,
"GET", "u32", "0", // 登录次数
"GET", "u16", "32", // 等级
"GET", "u8", "48"); // VIP等级
// 封装返回...
}
}
2.5 Stream —— 消息队列完全体
一句话解释:Redis 5.0 引入,专为消息队列设计,类似 Kafka 的简化版。
核心特性:
- ✅ 消费者组(Consumer Group):多个消费者分摊消息
- ✅ 消息确认机制(ACK):可靠消费
- ✅ 消息持久化:消息存在 Redis 中
- ✅ 阻塞读取:支持阻塞和非阻塞两种模式
实战场景:
- 替代 List 做更可靠的消息队列
- 事件溯源
- 日志收集
- 通知系统
// 实战:订单事件处理
public void publishOrderEvent(String orderId, String eventType, Map<String, String> data) {
Map<String, String> event = new HashMap<>();
event.put("orderId", orderId);
event.put("eventType", eventType);
event.put("timestamp", String.valueOf(System.currentTimeMillis()));
event.putAll(data);
try (Jedis jedis = pool.getResource()) {
jedis.xadd("stream:order-events", StreamEntryID.NEW_ENTRY, event);
}
}
// 消费者组消费
public void consumeMessages(String consumerName, Consumer<Map<String, String>> handler) {
try (Jedis jedis = pool.getResource()) {
while (true) {
List<Map.Entry<String, List<StreamEntry>>> entries = jedis.xreadGroup(
GroupParams.groupParams("order-processors", consumerName)
.block(1000).count(10),
StreamEntryID.LAST_ENTRY, "stream:order-events");
for (StreamEntry entry : entries.get(0).getValue()) {
handler.accept(entry.getFields());
jedis.xack("stream:order-events", "order-processors", entry.getID());
}
}
}
}
2.6 Bloom Filter —— 布隆过滤器(Redis Module)
需要 Redis 4.0+ 并加载 RedisBloom 模块
一句话解释:一种概率性数据结构,能确定元素不存在,但判断存在可能有误判。
内存对比:1亿数据,Set 需要 ~1.5GB,Bloom Filter 只需 ~170MB!
误判率:0.01%~1%,可通过参数调节。
实战场景:
- 缓存穿透防护(过滤不存在的 key)
- 爬虫 URL 去重
- 垃圾邮件过滤
- 黑名单系统
// 实战:防缓存穿透
public Product getProduct(String productId) {
// 1. 布隆过滤器判断(一定不存在就直接返回)
if (!bloomFilter.mightExist(productId)) {
return null;
}
// 2. 查缓存
try (Jedis jedis = pool.getResource()) {
String cached = jedis.get("product:" + productId);
if (cached != null) {
return "null".equals(cached) ? null : JSON.parseObject(cached, Product.class);
}
}
// 3. 查数据库
Product product = dao.findById(productId);
// 4. 写入缓存
try (Jedis jedis = pool.getResource()) {
if (product == null) {
jedis.setex("product:" + productId, 60, "null");
} else {
jedis.setex("product:" + productId, 3600, JSON.toJSONString(product));
}
}
return product;
}
三、选型速查表(建议截图保存)
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 缓存对象(频繁改单字段) | Hash | 可单独更新,不用重写整个对象 |
| 缓存对象(整体读写) | String(JSON) | 一次读完,简单方便 |
| 排行榜 | Sorted Set | 天然按分数排序 |
| UV统计(允许误差) | HyperLogLog | 12KB统计上亿数据 |
| UV统计(精确) | Bitmap + Set | 精确但内存大 |
| 附近的人 | Geo | 开箱即用的距离计算 |
| 可靠消息队列 | Stream | 支持ACK和消费者组 |
| 简单任务队列 | List | 简单高效 |
| 去重+统计数量 | Set | 精确去重 |
| 去重判断(允许误差) | Bloom Filter | 内存节省90%+ |
| 签到/布尔状态 | Bitmap | 1个bit存一个状态 |
| 紧凑存储多个小数值 | Bitfield | 一个key存多个指标 |
四、常见面试题
Q1:Redis 为什么这么快?
- 纯内存操作
- 单线程(6.0前)避免上下文切换
- IO 多路复用
- 高效的数据结构设计
Q2:如何防止缓存穿透?
- 布隆过滤器(推荐)
- 缓存空对象
- 接口层校验
Q3:HyperLogLog 的误差是多少?
- 约 0.81%,但每个 key 仅 12KB,适合大数据量 UV 统计
Q4:Stream 和 List 做消息队列有什么区别?
- List 不支持 ACK,消费者挂了消息就丢失
- Stream 支持消费者组、ACK、消息持久化,更可靠
五、写在最后
Redis 的数据类型远不止面试常问的那五种。用好高级类型,可以:
- ✅ 节省 90%+ 内存(Bitmap、HyperLogLog、Bloom Filter)
- ✅ 简化代码逻辑(Geo 省去手动计算距离)
- ✅ 提升系统可靠性(Stream 提供 ACK 机制)
建议收藏本文,遇到对应场景时翻出来看看,选对数据结构,代码优雅十倍。
📢 如果觉得有用,点赞 + 评论 + 收藏 三连支持一下~
💬 评论区聊聊:你还用过哪些 Redis 高级功能?
标签:Redis Java 后端开发 面试题 缓存
本文作者:code_space | 首发:CSDN | 转载请联系作者
更多推荐




所有评论(0)