Redis系列之基础篇
前言
redis用了一段时间了,却只会简单的增删改查.近期决定通过《Redis深度历险》这本书系统学习下.记下这份笔记以供回顾和网友学习.
1. Redis简介
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
——————来自官网
翻译下官网说法: Redis是一个开源的,基于内存的数据结构存储,用于做数据库,缓存,消息中间件.它支持多种数据结构string,hash,list,set,zset巴拉巴拉巴拉(英语不好勿喷…)
经验来说Redis一般用来做缓存,分布式锁呀等等…
1.1 为什么要用Redis?
两句话可以概括Redis:
-
是一个完全开源免费的key-value内存数据库
-
通常用于做缓存.
突然发现Redis和Java中的Map的功能是很相似的.既然这样的话,我们为什么不用Map呢?
- Redis可以用几十G内存做缓存,Map不行,一般JVM分几个G就很大了
- Redis的缓存可以持久化,Map是内存对象,程序一重启数据就没了
- Redis可以实现分布式缓存,Map的缓存只是本地的,分布式下不具备缓存一致性
- Redis可以处理每秒百万级的并发,是专业的缓存服务,Map只是普通的Java对象
- Redis缓存有过期机制,Map本身无此功能
- Redis有丰富的API,而Map相比下过于单调
1.2 Redis可以做什么?
Redis的业务范围非常广泛,来看看Redis可以用在哪些地方
- 记录帖子的点赞数、评论数和点击数(hash)
- 记录用户的帖子ID列表(排序),便于快速显示用户的帖子列表(zset)
- 记录帖子的标题、摘要、作者和封面信息,用于列表页展示(hash)
- 记录帖子的点赞用户ID列表,评论ID列表,用于显示和去重计数(zset)
- 缓存近期热帖内容(帖子内容的空间占用较大),减少数据库压力(hash)
- 记录帖子的相关文章ID,根据内容推荐相关帖子(list)
- 若帖子ID是整数自增的,可以使用Redis来分配帖子ID(计数器)
- 收藏夹和帖子之间的关系(zset)
- 记录热榜帖子ID列表、总热榜和分类热榜(zset)
- 缓存用户行为历史,过滤恶意行为(zset、hash)
当然,实际上可能并没有这么多,毕竟请求压力不大的情况下,很多数据都是在数据库中直接查询的,只有在请求压力很大,才会把数据挪到缓存中来操作。
1.3 Redis的历史(扩展)
Redis由意大利人Salvatore Sanfilippo(Antirez)开发。
Antirez出生于非英语系国家,英语能力长期以来是一个短板,他曾经专门为自己蹩脚的英语能力写过一篇博客《英语伤痛15年》,用自己的成长经历来鼓励那些英语不好的技术开发者们努力攻克英语难关。
Redis的默认端口是6379,这个端口号并不是随机选的,而是由于手机键盘字母“MERZ”的位置决定的。
“MERZ”在Antirez的朋友圈语言中是“愚蠢”的代名词,它由意大利广告女郎“Alessia Merz”在电视节目上说了一堆愚蠢的话而被人熟知。
2. Redis数据结构
本文不会讲解Redis的安装,具体安装方法请百度。
本文简述常用命令,其余请查阅官方文档。
2.1 五种基本数据结构
Redis有五种基础数据结构,分别为:string(字符串)、list(列表)、hash(字典)、set(集合)和zset(有序集合)。这五种数据结构的熟练使用,是Redis的相关知识中最基础、最重要的部分,也是Redis面试题中被问到最多的知识点。
2.1.1 string(字符串)
string是Redis中最简单的数据结构,它的内部表示就是一个字符数组。Redis所有的数据结构都以唯一的key字符串作为名称,然后通过这个唯一的key值来获取相应的value数据。不同类型的数据结构的差异就在于value的结构不一样。
字符串结构使用非常广泛,常用于缓存用户信息。Redis的字符串是动态字符串,是可以修改的字符串,内部结构类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
内部为当前字符串分配的实际空间capacity一般要高于实际字符串长度len。当字符串长度小于1MB时,扩容都是加倍现有的空间。若长度超过1MB,扩容时一次最多扩容1MB空间。字符串最大长度为512MB。
【键值对】
相当于字典的key和value,支持简单的增删改查操作。
> set name keben # 添加/修改值
> get name # 获取值
> exists name # 检测是否存在
> del name # 删除值
【批量键值对】
可以对多个字符串进行批量读写,节省网络耗时开销。
> mset name keben age 6 desc cool # 批量添加/修改值
> mget name age desc # 返回一个列表
【过期和set命令扩展】
可以对key设置过期时间,到时间会被自动删除,常用来控制缓存的时效时间。
> expire name 5 # 5s后过期
> setex name 5 keben # 5s后过期,相当于set+expire
> setnx name keben # 若不存在就创建,存在则不创建
【计数】
若value值是一个整数,还可以对它进行自增操作。自增是有范围的,它的范围在signed long的最大值和最小值之间,超过这个范围,Redis会报错。
> incr age # 自增1
> incrby age # 自减1
2.1.2 list(列表)
Redis的列表相当于Java中的LinkedList,注意它是链表而不是数组。这意味着list的插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂度为O(n)。列表中每个元素都使用了双向指针顺序,是为双向链表。
当列表中最后一个元素弹出后,该数据结构会被自动删除,内存被回收。
Redis的列表结构常用于做异步队列使用。将需要延后处理的任务结构体序列化成字符串,塞入Redis的列表,另一个线程从列表中轮询数据进行处理。
【右进左出:队列】
队列是先进先出的数据结构,常用于消息排队和异步逻辑处理,它会确保元素的访问顺序性。
> rpush books java python kotlin php # books列表批量添加值
> llen books # 查看books大小
> lpop books # 弹出队列的第一个
【右进右出:栈】
栈是先进后出的数据结构,跟队列正好相反。拿Redis的列表数据结构来做栈使用的业务场景并不多见。
> rpop books # 弹出最后一个入栈的
【慢操作】
lindex相当于Java链表的get(int index)方法,它需要对链表进行遍历,性能随着参数index增大而变差。
ltrim有两个参数 start_index,end_index,定义一个区间,截取保留区间内数据。
> lindex books 1
> lrange books 0 -1 # 获取所有元素
> ltrim books 1 0 # 清空了整个列表,因为区间范围长度为负
list底层存储的不是一个简单的linkedlist,而是称为“快速链表”(quicklist)的一个结构。
在列表元素少时,会用一块连续的内存存储,这个结构是ziplist,即压缩列表。当数据量较多时才会改为quicklist。因为普通的链表需要的附加指针空间太大(prev、next),会浪费空间,还会加重内存的碎片化。Redis将链表和ziplist结合组成quicklist,就是用链表将ziplist使用双向指针串起来使用。
2.1.3 hash(字典)
Redis里的字典相当于Java中的HashMap,它是无序的,内部存储了很多键值对,实现结构上与HashMap也是一样的,都是“数组+链表”二维结构。而存放的位置是根据key的hashCode来计算的,若两个key计算出来的在数组的同一位,则在这个下标下使用链表串接起来。
但是Redis中的hash只能存字符串,而且它们rehash的方式也不一样。因为HashMap在字典很大时,rehash是很耗时的,需要一次性全部rehash。Redis为了追求高性能,不能堵塞服务,采用了渐进式rehash策略。
渐进式rehash的时候,不会一次全部rehash,而是将旧hash里的内容慢慢迁移(迁移后旧的里面就没有了)到新的hash结构中,在彻底迁移完之前,查询时会同时查询两个hash结构。当迁移完成后,就会用新的hash结构,旧的数据结构被自动删除。注意,hash结构的存储消耗要高于单个字符串。
> hset books java "Head first Java" # 值中若是有空格,要用引号括起来
> hgetall books # 获取所有key,value key和value间隔出现
> hlen books # size
> hget books java # get
> hmset books java "Hello java" php "hello pnp" #batch set
> hincrby user age 10 # add 10
2.1.4 set(集合)
Redis中的set和list都是字符串序列,非常相似,不同于set是用hash来实现值的唯一性,无序的,不可重复的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值NULL。
当集合中的最后一个元素被移除之后,数据结构被自动删除,内存被回收。
> sadd books java # add
> smembers books # 获取全部
> sismeber books java # 查询值是否存在
> scard books # 获取大小
> spop books # 弹出一个
2.1.5 zset(有序集合)
zset是Redis提供的最有特色的数据结构,也是面试中最常出现的角色。它是ziplist和skiplist(跳跃列表)的结合体,一方面它是一个set,保证了值的唯一性,另一方面它可以给每个值赋予一个score,代表着这个值的排序权重。当存储元素的数量小于128个,所有member的长度都小于64字节,就会使用ziplist。
> zadd game 256 "keben" # 添加 游戏计分 用户keben 分数 256
> zrange game 0 -1 # 按score排序列出,参数区间为排名范围
> zrevrange game 0 -1 # 按score逆序列出,参数区间为排名范围
> zcard game # size
> zscore game "keben" # 获取 keben 的 分数(内部score使用double类型存储,可能存在小数点精度问题)
> zrank game "keben" # 获取 keben 的 排名
> zrangebyscore game 0 100 # 根据分值区间遍历 zset
> zrangebyscore game -inf 256 withscores # 根据分值区间(-无穷大,256]
> zrem game kobe # 删除
跳跃列表的结构非常特殊,也比较复杂。zset要支持随机的插入和删除,不宜于使用数组来表示。而普通链表又不适用于查找定位插入点。
跳跃列表是一种层级关系。
随机生成层级,把每一层级的串联起来。查找时先从最上层级(lv.3)查找。
如若新增一个 21, 会先从lv.3进行对比,21<24,然后从lv.2(9)至 lv.2(24)中查找.,最后在lv.1(9,16,24)中找到合适位置,进行插入,并随机生成层数。生成的几率也是越高层几率越小。
2.2 容器形数据结构的通用规则
list、set、hash、zset这四种数据结构是容器型数据结构,它们共享下面两条通用规则。
1. create if not exit: 若容器不存在,就会创建一个再进行操作。
2. drop if no elements: 如果容器里的元素没有了,那么立即删除容器,释放内存。
2.3 过期时间
Redis所有的数据结构都可以设置过期时间,时间到了,Redis会自动删除相应的对象。注意,过期是以对象为单位,例hash结构的过期是整个hash对象的过期,而不是其中的某个子key的过期。
若一个字符串已经设置了过期时间,再用set修改它,那么它的过期时间会消失。
> set name keben
> expire name 500 # 设置过期时间 (秒)
> ttl name # 查看剩余存活时间
最后
总结下下~
本文讲解了Redis的一些基本使用和五种数据结构。
- string --> 简单的key-value,数组结构
- list --> 双向链表结构,底层采用“ziplist+quicklist”
- hash --> "数组+链表"结构
- set --> 无序列表
- zset --> 采用“ziplist+skiplist”结构
如果有什么遗漏或错误,希望大家在留言区指出,加以补充~
转载请注明