Redis作为一款高性能的内存数据库,其强大的灵活性和高效性,核心源于它支持的八大核心数据类型。不同于传统关系型数据库的固定表结构,Redis的每一种数据类型都有其独特的底层实现、适用场景和操作命令,掌握这些数据类型,是用好Redis、避开生产坑的关键。
本文将逐一拆解Redis八大核心数据类型------String(字符串)、Hash(哈希)、List(列表)、Set(集合)、ZSet(有序集合)、Bitmap(位图)、HyperLogLog(基数统计)、GEO(地理空间),从底层实现原理、核心操作命令,到实际业务落地场景,全方位讲透,让你既能理解"是什么",也能明白"怎么用""为什么这么用"。
一、String(字符串):Redis最基础的"万能类型"
String是Redis最基础、最常用的数据类型,也是所有数据类型的"基础载体"------Redis中所有的Key本质上都是String类型,而Value既可以是字符串、数字,也可以是二进制数据(如图片、视频片段),是Redis中最灵活的类型。
1. 底层实现
Redis的String并非C语言原生的char*字符串(固定长度、二进制不安全),而是自定义的简单动态字符串(SDS,Simple Dynamic String),核心结构由3部分组成:
-
len:当前字符串的实际长度(字节数),无需遍历即可获取,时间复杂度O(1);
-
free:当前字符串的空闲空间(字节数),用于预分配空间,减少内存分配次数;
-
buf:字节数组,存储字符串内容,末尾会自动添加\0(作为结束标识,不计算在len内)。
示例:存储字符串"redis"的SDS,len=5,free=0,buf=[r,e,d,i,s,\0]。
SDS的核心优势的是"动态扩容"和"二进制安全":
-
动态扩容:修改字符串时,若空间不足,会自动扩容(小于1MB时翻倍扩容,大于1MB时每次增加1MB);
-
二进制安全:通过len标识长度,不依赖\0结束,可存储图片、音频等二进制数据,避免被截断。
2. 核心操作命令(高频必记)
-
SET key value [EX seconds] [PX milliseconds]:设置键值对,可指定过期时间(EX秒级,PX毫秒级);
-
GET key:获取指定key的value,若key不存在返回nil;
-
APPEND key value:在指定key的value末尾追加字符串,返回追加后的总长度;
-
STRLEN key:获取key对应的value长度,时间复杂度O(1);
-
INCR key:将value为数字的key自增1(仅支持整数),若key不存在则初始化为0后自增;
-
DECR key:将value为数字的key自减1,用法同INCR;
-
INCRBY key increment:自增指定数值(如INCRBY num 5,自增5);
-
SETNX key value:仅当key不存在时设置值(原子操作),常用于分布式锁。
3. 实战场景
-
缓存热点数据:如首页banner、热门商品基本信息(key=product:info:1001,value=JSON字符串);
-
计数器:如文章阅读量、商品销量(INCR read:count:1001);
-
分布式锁:基于SETNX实现简单分布式锁(后续缓存章节详细讲解);
-
存储二进制数据:如用户头像(将图片转为二进制,用SET存储,GET获取后还原)。
二、Hash(哈希):适合存储"对象型数据"的高效类型
Hash类型是Redis中用于存储"键值对集合"的数据类型,本质上是一个"Hash表",适合存储对象型数据(如用户信息、商品详情)------可以单独操作对象的某个字段,无需修改整个对象,比String存储对象更节省内存、更高效。
1. 底层实现(自适应结构)
Hash的底层实现会根据数据量自动切换,兼顾内存占用和操作效率:
-
压缩列表(ziplist):当Hash的字段数量少、字段值小时(默认配置:hash-max-ziplist-entries=512,hash-max-ziplist-value=64),使用ziplist存储。ziplist是一种紧凑的连续内存结构,将所有字段(field)和值(value)按顺序存储在一块连续内存中,减少内存碎片,查询效率高。
-
字典(dict):当Hash的字段数量或字段值超过上述阈值时,自动切换为dict存储。dict类似Java的HashMap,采用"数组+链表"的结构(哈希冲突时用链表解决),支持O(1)的增删改查操作。
dict的扩容机制:当哈希表的负载因子(used/size)超过1时,翻倍扩容;负载因子低于0.1时,减半缩容,确保操作效率。
2. 核心操作命令
-
HSET key field value:给指定key的Hash表设置字段和值,若字段已存在则覆盖;
-
HGET key field:获取指定key的Hash表中某个字段的值;
-
HGETALL key:获取指定key的Hash表中所有字段和值(注意:字段过多时会阻塞Redis,建议用HSCAN渐进式遍历);
-
HDEL key field [field ...]:删除指定key的Hash表中一个或多个字段;
-
HLEN key:获取指定key的Hash表中字段的数量;
-
HKEYS key:获取指定key的Hash表中所有字段名;
-
HVALS key:获取指定key的Hash表中所有字段值;
-
HEXISTS key field:判断指定key的Hash表中是否存在某个字段,存在返回1,不存在返回0。
3. 实战场景
-
存储用户信息:key=user:info:1001,field=name/age/gender,value对应具体值,修改年龄时只需执行HSET user:info:1001 age 25;
-
存储商品详情:key=product:detail:1001,field=price/stock/category,无需存储完整JSON,灵活修改单个字段;
-
缓存小对象集合:如购物车(key=cart:user:1001,field=productId,value=数量)。
三、List(列表):有序、可重复的"双向链表"
List类型是Redis中用于存储"有序、可重复"元素的线性结构,本质上是一个双向链表(Redis 3.2后优化为quicklist),支持从两端插入、删除元素,查询两端元素速度极快(O(1)),查询中间元素速度较慢(O(n)),适合场景化的顺序存储需求。
1. 底层实现
-
Redis 3.2之前:小数据量时用压缩列表(ziplist) ,大数据量时用双端链表(adlist);
-
Redis 3.2之后:统一改为quicklist(快速列表),本质是"双端链表+压缩列表"的组合------将List拆分为多个ziplist,每个ziplist作为一个节点,用双端链表连接,既保留ziplist的内存紧凑性,又解决双端链表内存碎片多的问题。
quicklist的核心优势:两端操作快,内存占用比纯双端链表少,性能比纯ziplist更稳定。
2. 核心操作命令
-
LPUSH key value [value ...]:从列表左侧插入一个或多个元素,返回插入后的列表长度;
-
RPUSH key value [value ...]:从列表右侧插入一个或多个元素,用法同LPUSH;
-
LPOP key:从列表左侧弹出一个元素(删除并返回);
-
RPOP key:从列表右侧弹出一个元素,用法同LPOP;
-
LRANGE key start stop:获取列表中从start到stop的元素(start=0,stop=-1表示获取所有元素);
-
LLEN key:获取列表的长度;
-
LREM key count value:删除列表中count个值为value的元素(count>0从左删,count<0从右删,count=0删除所有);
-
BLPOP key [key ...] timeout:阻塞式从左侧弹出元素,若列表为空,等待timeout秒后返回nil(常用于消息队列)。
3. 实战场景
-
消息队列:基于LPUSH+BRPOP实现简单的消息队列(生产者LPUSH发送消息,消费者BRPOP阻塞接收消息);
-
最新消息列表:如用户消息通知(LPUSH添加消息,LRANGE获取最新10条消息);
-
栈/队列实现:LPUSH+LPOP实现栈(先进后出),LPUSH+RPOP实现队列(先进先出)。
四、Set(集合):无序、不可重复的"哈希集合"
Set类型是Redis中用于存储"无序、不可重复"元素的集合,本质上是一个基于dict实现的哈希集合(key是集合元素,value为null),支持交集、并集、差集等集合操作,适合需要去重、统计交集/并集的场景。
1. 底层实现(自适应结构)
-
整数集合(intset):当Set中的所有元素都是整数,且数量不超过阈值(默认配置:set-max-intset-entries=512)时,使用intset存储。intset是紧凑的整数数组,按升序排列,支持二分查找(O(log n)),内存占用极低。
-
字典(dict):当Set中存在非整数元素,或元素数量超过阈值时,使用dict存储,利用dict的O(1)增删改查特性,保证高效操作。
2. 核心操作命令
-
SADD key member [member ...]:向集合中添加一个或多个元素,重复元素会自动去重,返回添加成功的元素个数;
-
SMEMBERS key:获取集合中所有元素(元素过多时阻塞Redis,建议用SSCAN渐进式遍历);
-
SREM key member [member ...]:从集合中删除一个或多个元素,返回删除成功的个数;
-
SISMEMBER key member:判断元素是否在集合中,存在返回1,不存在返回0;
-
SCARD key:获取集合的元素个数;
-
SINTER key1 key2:求两个集合的交集(共同元素);
-
SUNION key1 key2:求两个集合的并集(所有元素,去重);
-
SDIFF key1 key2:求两个集合的差集(key1中有、key2中没有的元素)。
3. 实战场景
-
用户标签:key=user:tag:1001,member=篮球/音乐/旅行,用于用户画像、个性化推荐;
-
去重统计:如网站UV统计(SADD uv:20240329 192.168.1.1,SCARD获取UV数);
-
好友关系:key=user:friend:1001,member=好友ID,SINTER求两个用户的共同好友。
五、ZSet(有序集合):有序、不可重复的"排序集合"
ZSet(Sorted Set,有序集合)是Redis中最复杂的数据类型之一,兼具Set的"不可重复"特性和List的"有序"特性,每个元素都会关联一个"分数(score)",Redis通过分数对元素进行排序(升序/降序),支持范围查询,适合排行榜、优先级队列等场景。
1. 底层实现(跳跃表+字典)
ZSet的底层采用"跳跃表(skiplist)+ 字典(dict)"的组合,两者协同工作,兼顾排序和查询效率:
-
跳跃表(skiplist):核心用于排序和范围查询。跳跃表是一种有序数据结构,通过"多层索引"实现快速查找------每一层都是一个有序链表,上层链表是下层链表的"索引",可快速跳过大量元素,查询效率接近O(log n)。Redis的跳跃表最多支持64层,层数越高,查询速度越快。
-
字典(dict):核心用于快速获取元素的分数(score),key是ZSet的元素,value是对应的分数,支持O(1)的分数查询和修改。
优势:跳跃表的插入、删除、范围查询效率高,字典的单点查询效率高,两者结合,完美适配"排序+快速查询"的需求。
2. 核心操作命令
-
ZADD key score member [score member ...]:向有序集合中添加元素,指定分数,若元素已存在则更新分数,返回添加/更新的元素个数;
-
ZRANGE key start stop [WITHSCORES]:按分数升序获取从start到stop的元素,加上WITHSCORES可同时返回分数;
-
ZREVRANGE key start stop [WITHSCORES]:按分数降序获取元素,用法同ZRANGE;
-
ZSCORE key member:获取指定元素的分数;
-
ZREM key member [member ...]:从有序集合中删除一个或多个元素,返回删除成功的个数;
-
ZCOUNT key min max:统计分数在min到max之间的元素个数;
-
ZRANK key member:获取元素按分数升序的排名(排名从0开始);
-
ZREVRANK key member:获取元素按分数降序的排名,用法同ZRANK。
3. 实战场景
-
排行榜:如商品销量排行榜(key=product:rank:sales,score=销量,member=商品ID,ZREVRANGE获取top10);
-
优先级队列:如任务调度(key=task:queue,score=优先级,member=任务ID,ZRANGE获取优先级最高的任务);
-
分数统计:如学生成绩排名(key=student:rank:score,score=成绩,member=学生ID)。
六、Bitmap(位图):高效存储"布尔值"的紧凑类型
Bitmap(位图)是Redis中用于存储"布尔值(true/false)"的紧凑数据类型,本质上是一个二进制数组(每个bit位对应一个布尔值,0表示false,1表示true),内存占用极低------1MB内存可存储800多万个布尔值,适合大量布尔型数据的存储和统计。
1. 底层实现
Bitmap的底层基于String类型实现(String本质是字节数组,每个字节包含8个bit位),Redis通过对字节数组的bit位进行操作,实现布尔值的存储和查询,核心是"bit位级别的操作",效率极高。
2. 核心操作命令
-
SETBIT key offset value:设置指定偏移量(offset)的bit位值(0或1),offset从0开始,返回该bit位原来的值;
-
GETBIT key offset:获取指定偏移量的bit位值(0或1),若offset超出范围,返回0;
-
BITCOUNT key [start end]:统计key对应的位图中,值为1的bit位个数(start和end指字节偏移量,默认统计所有);
-
BITOP operation destkey key [key ...]:对多个位图执行位运算(AND/OR/XOR/NOT),结果存储到destkey中;
-
BITPOS key value [start end]:查找位图中第一个值为value(0或1)的bit位偏移量。
3. 实战场景
-
签到统计:key=user:sign:1001:202403,offset=日期(1-31),SETBIT user:sign:1001:202403 5 1表示5号签到,BITCOUNT统计月签到天数;
-
在线状态:key=user:online,offset=用户ID,SETBIT user:online 1001 1表示用户1001在线,GETBIT查询在线状态;
-
权限控制:key=role:perm:admin,offset=权限ID,bit位为1表示拥有该权限,BITOP实现权限交集/并集。
七、HyperLogLog(基数统计):海量数据的"去重统计神器"
HyperLogLog(简称HLL)是Redis中用于"基数统计"的高级数据类型,核心作用是统计一个集合中"不重复元素的个数"(基数),无需存储所有元素,仅通过少量内存即可实现海量数据的统计------12KB内存可统计千万级数据,误差率仅为0.81%,适合UV、PV等无需精确统计的场景。
1. 底层实现
HyperLogLog基于"概率算法"实现,核心原理是通过统计"元素哈希值的前导零个数",估算集合的基数,无需存储元素本身,仅存储统计所需的中间数据,因此内存占用极低。
注意:HyperLogLog是"估算值",不是精确值,误差率固定为0.81%,但对于UV等场景,这个误差完全可接受,换来的是内存的极大节省。
2. 核心操作命令
-
PFADD key element [element ...]:向HyperLogLog中添加一个或多个元素,返回1表示基数发生变化,0表示基数未变化;
-
PFCOUNT key [key ...]:统计一个或多个HyperLogLog的基数(不重复元素个数);
-
PFMERGE destkey key [key ...]:将多个HyperLogLog合并,合并后的基数是所有集合的并集基数,结果存储到destkey中。
3. 实战场景
-
网站UV统计:key=uv:website:20240329,PFADD添加访问用户IP,PFCOUNT获取当日UV;
-
商品浏览去重统计:key=product:view:1001,PFADD添加浏览用户ID,统计不重复浏览人数;
-
海量数据去重:如日志中的用户ID去重统计,无需存储所有ID,仅用12KB内存即可完成千万级统计。
八、GEO(地理空间):专门处理"地理位置"的类型
GEO(Geospatial)是Redis 3.2版本新增的地理空间数据类型,专门用于存储和操作地理位置信息(经纬度),支持根据经纬度计算距离、查找附近的地点等操作,适合地图、外卖、打车等需要地理位置服务的场景。
1. 底层实现
GEO的底层基于ZSet实现:将经纬度转换为一个"geohash值"(通过特定算法将二维经纬度映射为一维整数),作为ZSet的score,地理位置名称作为member,利用ZSet的排序特性,实现地理位置的快速查询和距离计算。
2. 核心操作命令
-
GEOADD key longitude latitude member [longitude latitude member ...]:向指定key中添加地理位置(经度、纬度、名称),返回添加成功的个数;
-
GEOPOS key member [member ...]:获取指定地理位置的经纬度;
-
GEODIST key member1 member2 [unit]:计算两个地理位置之间的距离,unit可选(m=米,km=千米,mi=英里,ft=英尺),默认米;
-
GEORADIUS key longitude latitude radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]:根据指定经纬度和半径,查找该范围内的地理位置,COUNT指定返回个数;
-
GEORADIUSBYMEMBER key member radius unit [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]:根据指定地理位置,查找其周围radius范围内的地点,用法同GEORADIUS;
-
GEODEL key member [member ...]:删除指定的地理位置。
3. 实战场景
-
外卖商家定位:key=takeout:merchant,存储商家经纬度,GEORADIUS查找用户附近的商家;
-
打车平台附近车辆:key=taxi:car,存储车辆经纬度,GEORADIUSBYMEMBER查找用户周围的车辆;
-
地理位置距离计算:如计算用户与景点的距离,用于推荐附近景点。
九、总结:八大数据类型核心对比与选型建议
Redis八大核心数据类型,各有其底层实现和适用场景,选择正确的类型,能大幅提升Redis的性能和内存利用率,避免不必要的性能瓶颈。以下是核心对比和选型建议,方便大家快速选型:
| 数据类型 | 核心特性 | 底层实现 | 核心场景 |
|---|---|---|---|
| String | 万能、灵活,可存字符串/数字/二进制 | SDS(简单动态字符串) | 缓存热点数据、计数器、分布式锁 |
| Hash | 对象型存储,可单独操作字段 | ziplist + dict | 存储用户/商品信息、小对象集合 |
| List | 有序、可重复,两端操作快 | quicklist(双端链表+ziplist) | 消息队列、最新消息列表、栈/队列 |
| Set | 无序、不可重复,支持集合运算 | intset + dict | 用户标签、去重统计、好友关系 |
| ZSet | 有序、不可重复,按分数排序 | skiplist + dict | 排行榜、优先级队列、分数统计 |
| Bitmap | 紧凑存储布尔值,bit位操作 | String(字节数组) | 签到统计、在线状态、权限控制 |
| HyperLogLog | 海量数据基数统计,内存占用低 | 概率算法 | UV统计、海量数据去重 |
| GEO | 地理位置存储与距离计算 | ZSet(geohash映射) | 地图服务、附近地点查询 |
最后提醒:Redis的选型核心是"贴合业务场景"------无需追求复杂类型,简单场景用String,对象场景用Hash,排序场景用ZSet,海量去重用HyperLogLog,根据业务需求选择最合适的类型,才能发挥Redis的最大价值。