写在开头:
本章是Redis学习归纳第二部分,着重于归纳redis的基础知识。
文章内容输出来源:拉勾教育大数据高薪训练营。
Redis是一个Key-Value的存储系统,使用ANSI C语言编写。
key的类型是字符串。
value的数据类型有:
1.可以将key值以冒号分割
2.把表名转换为Key的前缀,冒号后放表的主键,第三段放列名
比如:用户表user, 转换为redis的key-value存储
那么username 的 key就是: user:9:username
email的key:user:9:email
表示明确:看key知道意思 ,不易被覆盖
命令名称 | 使用方式 | 命令描述 |
---|---|---|
set | set key value | 赋值 |
get | get key | 取值 |
getset | getset key value | 取值并赋值 |
setnx | setnx key value | 当value不存在时采用赋值 set key value NX PX 3000 原子操作,px 设置毫秒数 |
append | append key value | 向尾部追加值 |
strlen | strlen key | 获取字符串长度 |
incr | incr key | 递增数字 |
incrby | incrby key increment | 增加指定的整数 |
decr | decr key | 递减数字 |
decrby | decrby key decrement | 减少指定的整数 |
应用场景:
1、key和命令是字符串
2、普通的赋值
3、incr用于乐观锁
incr:递增数字,可用于实现乐观锁 watch(事务)
4、setnx用于分布式锁
当value不存在时采用赋值,可用于实现分布式锁
list列表类型可以存储有序、可重复的元素,获取头部或尾部附近的记录是极快的,list的元素个数最多为2^32-1个(40亿)
命令名称 | 命令格式 | 描述 |
lpush | lpush key v1 v2 v3 | 从左侧插入列表 |
lpop | lpop key | 从列表左侧取出 |
rpush | rpush key v1 v2 v3 | 从右侧插入列表 |
rpop | rpop key | 从列表右侧取出 |
lpushx | lpushx key value | 将值插入到列表头部 |
rpushx | rpushx key value | 将值插入到列表尾部 |
blpop | blpop key timeout | 从列表左侧取出,当列表为空时阻塞,可以设置最大阻塞时 间,单位为秒 |
brpop | blpop key timeout | 从列表右侧取出,当列表为空时阻塞,可以设置最大阻塞时 间,单位为秒 |
llen | llen key | 获得列表中元素个数 |
lindex | lindex key index | 获得列表中下标为index的元素 index从0开始 |
lrange | lrange key start end | 返回列表中指定区间的元素,区间通过start和end指定 |
lrem | lrem key count value | 删除列表中与value相等的元素,当count>0时&#xff0c; lrem会从列表左边开始删除;d当count<0时,lrem会从列表后边开始删除;当count&#61;0时&#xff0c; lrem删除所有值为value的元素 |
lset | lset key index value | 将列表index位置的元素设置成value的值 |
ltrim | ltrim key start end | 对列表进行修剪&#xff0c;只保留start到end区间 |
rpoplpush | rpoplpush key1 key2 | 从key1列表右侧弹出并插入到key2列表左侧 |
brpoplpush | brpoplpush key1 key2 | 从key1列表右侧弹出并插入到key2列表左侧&#xff0c;会阻塞 |
linsert | linsert key BEFORE/AFTERpivot value | 将value插入到列表&#xff0c;且位于值pivot之前或之后 |
应用场景&#xff1a;
1、作为栈或队列使用
列表有序可以作为栈和队列使用
2、可用于各种列表&#xff0c;比如用户列表、商品列表、评论列表等。
使用案例&#xff1a;
Set&#xff1a;无序、唯一元素
集合中最大的成员数为 2^32 - 1
常见操作命令如下表&#xff1a;
命令名称 | 命令格式 | 描述 |
---|---|---|
sadd | sadd key mem1 mem2 .... | 为集合添加新成员 |
srem | srem key mem1 mem2 .... | 删除集合中指定成员 |
smembers | smembers key | 获得集合中所有元素 |
spop | spop key | 返回集合中一个随机元素&#xff0c;并将该元素删除 |
srandmember | srandmember key | 返回集合中一个随机元素&#xff0c;不会删除该元素 |
scard | scard key | 获得集合中元素的数量 |
sismember | sismember key member | 判断元素是否在集合内 |
sinter | sinter key1 key2 key3 | 求多集合的交集 |
sdiff | sdiff key1 key2 key3 | 求多集合的差集 |
sunion | sunion key1 key2 key3 | 求多集合的并集 |
应用场景&#xff1a;
适用于不能重复的且不需要顺序的数据结构
比如&#xff1a;关注的用户&#xff0c;还可以通过spop进行随机抽奖
SortedSet(ZSet) 有序集合&#xff1a; 元素本身是无序不重复的
每个元素关联一个分数(score)
可按分数排序&#xff0c;分数可重复
常见操作命令如下表&#xff1a;
名称 | 格式 | 含义 |
---|---|---|
zadd | zadd key score1 member1 score2 member2 .. | 为有序集合添加新成员 |
zrem | zrem key mem1 mem2 .... | 删除有序集合中指定成员 |
zcard | zcard key | 获得有序集合中的元素数量 |
zcount | zcount key min max | 返回集合中score值在[min,max]区间 的元素数量 |
zincrby | zincrby key increment member | 在集合的member分值上加increment |
zscore | zscore key member | 获得集合中member的分值 |
zrank | zrank key member | 获得集合中member的排名&#xff08;按分值从 小到大&#xff09; |
zrevrank | zrevrank key member | 获得集合中member的排名&#xff08;按分值从 大到小&#xff09; |
zrange | zrange key start end | 获得集合中指定区间成员&#xff0c;按分数递增排序 |
zrevrange | zrevrange key start end | 获得集合中指定区间成员&#xff0c;按分数递减排序 |
应用场景&#xff1a;
由于可以按照分值排序&#xff0c;所以适用于各种排行榜。比如&#xff1a;点击排行榜、销量排行榜、关注排行榜等。
Redis hash 是一个 string 类型的 field 和 value 的映射表&#xff0c;它提供了字段和字段值的映射。
每个 hash 可以存储 2^32 - 1 键值对&#xff08;40多亿&#xff09;
名称 | 格式 | 含义 |
---|---|---|
hset | hset key field value | 赋值&#xff0c;不区别新增或修改 |
hmset | hmset key field1 value1 field2 value2 | 批量赋值 |
hsetnx | hsetnx key field value | 赋值&#xff0c;如果filed存在则不操作 |
hexists | hexists key filed | 查看某个field是否存在 |
hget | hget key field | 获取一个字段值 |
hmget | hmget key field1 field2 ... | 获取多个字段值 |
hgetall | hgetall key | 获取全部key-value |
hdel | hdel key field1 field2... | 删除指定字段 |
hincrby | hincrby key field increment | 指定字段自增increment |
hlen | hlen key | 获得字段数量 |
bitmap是进行位操作的
通过一个bit位来表示某个元素对应的值或者状态,其中的key就是对应元素本身。
bitmap本身会极大的节省储存空间。
常见操作命令如下表
名称 | 格式 | 描述 |
---|---|---|
setbit | setbit key offset value | 设置key在offset处的bit值(只能是0或者 1)。 |
getbit | getbit key offset | 获得key在offset处的bit值 |
bitcount | bitcount key | 获得key的bit位为1的个数 |
bitpos | bitpos key value | 返回第一个被设置为bit值的索引值 |
bitop | bitop and[or/xor/not] destkey key [key …] | 对多个key 进行逻辑运算后存入destkey 中 |
应用场景&#xff1a;
1、用户每月签到&#xff0c;用户id为key &#xff0c; 日期作为偏移量 1表示签到
2、统计活跃用户, 日期为key&#xff0c;用户id为偏移量 1表示活跃
3、查询用户在线状态&#xff0c; 日期为key&#xff0c;用户id为偏移量 1表示在线
geo是Redis用来处理位置信息的。在Redis3.2中正式使用。主要是利用了Z阶曲线、Base32编码和geohash算法
Z阶曲线
在x轴和y轴上将十进制数转化为二进制数&#xff0c;采用x轴和y轴对应的二进制数依次交叉后得到一个六位数编
码。把数字从小到大依次连起来的曲线称为Z阶曲线&#xff0c;Z阶曲线是把多维转换成一维的一种方法。
Base32编码
Base32这种数据编码机制&#xff0c;主要用来把二进制数据编码成可见的字符串&#xff0c;其编码规则是&#xff1a;任意给定一个二进制数据&#xff0c;以5个位(bit)为一组进行切分(base64以6个位(bit)为一组)&#xff0c;对切分而成的每个组进行编码得到1个可见字符。Base32编码表字符集中的字符总数为32个&#xff08;0-9、b-z去掉a、i、l、o&#xff09;&#xff0c;这也是Base32名字的由来。
geohash算法
Gustavo在2008年2月上线了http://geohash.org网站。Geohash是一种地理位置信息编码方法。 经过geohash映射后&#xff0c;地球上任意位置的经纬度坐标可以表示成一个较短的字符串。可以方便的存储在数据库中&#xff0c;附在邮件上&#xff0c;以及方便的使用在其他服务中。以北京的坐标举例&#xff0c;[39.928167,116.389550]可以转换成 wx4g0s8q3jf9 。
Redis中经纬度使用52位的整数进行编码&#xff0c;放进zset中&#xff0c;zset的value元素是key&#xff0c;score是GeoHash的52位整数值。在使用Redis进行Geo查询时&#xff0c;其内部对应的操作其实只是zset(skiplist)的操作。通过zset的score进行排序就可以得到坐标附近的其它元素&#xff0c;通过将score还原成坐标值就可以得到元素的原始坐标
应用场景
1、记录地理位置
2、计算距离
3、查找"附近的人"
Redis性能高&#xff1a;
官方数据
读&#xff1a;110000次/s
写&#xff1a;81000次/s
长期使用&#xff0c;key会不断增加&#xff0c;Redis作为缓存使用&#xff0c;物理内存也会满
内存与硬盘交换&#xff08;swap&#xff09; 虚拟内存 &#xff0c;频繁IO 性能急剧下降
不设置的场景
Redis的key是固定的&#xff0c;不会增加
Redis作为DB使用&#xff0c;保证数据的完整性&#xff0c;不能淘汰 &#xff0c; 可以做集群&#xff0c;横向扩展
缓存淘汰策略&#xff1a;禁止驱逐 &#xff08;默认&#xff09;
设置的场景
Redis是作为缓存使用&#xff0c;不断增加Key
maxmemory &#xff1a; 默认为0 不限制
问题&#xff1a;达到物理内存后性能急剧下架&#xff0c;甚至崩溃
内存与硬盘交换&#xff08;swap&#xff09; 虚拟内存 &#xff0c;频繁IO 性能急剧下降
设置多少&#xff1f;
与业务有关
1个Redis实例&#xff0c;保证系统运行 1 G &#xff0c;剩下的就都可以设置Redis
物理内存的3/4
在redis.conf中
maxmemory 1024mb
命令&#xff1a; 获得maxmemory数
CONFIG GET maxmemory
设置maxmemory后&#xff0c;当趋近maxmemory时&#xff0c;通过缓存淘汰策略&#xff0c;从内存中删除对象
不设置maxmemory &#xff0c;无最大内存限制 maxmemory-policy noeviction &#xff08;禁止驱逐&#xff09; 不淘汰
设置maxmemory maxmemory-policy 要配置
在Redis中可以使用expire命令设置一个键的存活时间(ttl: time to live)&#xff0c;过了这段时间&#xff0c;该键就会自动被删除。
上面的代码是Redis 中关于数据库的结构体定义&#xff0c;这个结构体定义中除了 id 以外都是指向字典的指针&#xff0c;其中我们只看 dict 和 expires
dict 用来维护一个 Redis 数据库中包含的所有 Key-Value 键值对&#xff0c;expires则用于维护一个 Redis 数据库中设置了失效时间的键(即key与失效时间的映射)。
当我们使用 expire命令设置一个key的失效时间时&#xff0c;Redis 首先到 dict 这个字典表中查找要设置的key是否存在&#xff0c;如果存在就将这个key和失效时间添加到 expires 这个字典表。
当我们使用 setex命令向系统插入数据时&#xff0c;Redis 首先将 Key 和 Value 添加到 dict 这个字典表中&#xff0c;然后将 Key 和失效时间添加到 expires 这个字典表中。
简单地总结来说就是&#xff0c;设置了失效时间的key和具体的失效时间全部都维护在 expires 这个字典表中。
Redis的数据删除有定时删除、惰性删除和主动删除三种方式。
Redis目前采用惰性删除&#43;主动删除的方式。
定时删除
在设置键的过期时间的同时&#xff0c;创建一个定时器&#xff0c;让定时器在键的过期时间来临时&#xff0c;立即执行对键的删除操作。需要创建定时器&#xff0c;而且消耗CPU&#xff0c;一般不推荐使用。
惰性删除
在key被访问时如果发现它已经失效&#xff0c;那么就删除它。
调用expireIfNeeded函数&#xff0c;该函数的意义是&#xff1a;读取数据之前先检查一下它有没有失效&#xff0c;如果失效了就删除它。
主动删除
在redis.conf文件中可以配置主动删除策略,默认是no-enviction&#xff08;不删除)
maxmemory-policy allkeys-lru
LRU
LRU (Least recently used) 最近最少使用&#xff0c;算法根据数据的历史访问记录来进行淘汰数据&#xff0c;其核心思想是“如果数据最近被访问过&#xff0c;那么将来被访问的几率也更高"。
最常见的实现是使用一个链表保存缓存数据&#xff0c;详细算法实现如下&#xff1a;
1. 新数据插入到链表头部&#xff1b;
2. 每当缓存命中&#xff08;即缓存数据被访问&#xff09;&#xff0c;则将数据移到链表头部&#xff1b;
3. 当链表满的时候&#xff0c;将链表尾部的数据丢弃。
4. 在Java中可以使用LinkHashMap&#xff08;哈希链表&#xff09;去实现LRU
让我们以用户信息的需求为例&#xff0c;来演示一下LRU算法的基本思路&#xff1a;
1.假设我们使用哈希链表来缓存用户信息&#xff0c;目前缓存了4个用户&#xff0c;这4个用户是按照时间顺序依次从链表右端插入的
2.此时&#xff0c;业务方访问用户5&#xff0c;由于哈希链表中没有用户5的数据&#xff0c;我们从数据库中读取出来&#xff0c;插入到缓存当中。这时候,链表中最右端是最新访问到的用户5,最左端是最近最少访问的用户1。
3..接下来&#xff0c;业务方访问用户2&#xff0c;哈希链表中存在用户2的数据&#xff0c;我们怎么做呢&#xff1f;我们把用户2从它的前驱节点和后继节点之间移除&#xff0c;重新插入到链表最右端。这时候&#xff0c;链表中最右端变成了最新访问到的用户2&#xff0c;最左端仍然是最近最少访问的用户1。
4.接下来&#xff0c;业务方请求修改用户4的信息。同样道理&#xff0c;我们把用户4从原来的位置移动到链表最右侧&#xff0c;并把用户信息的值更新。这时候&#xff0c;链表中最右端是最新访问到的用户4&#xff0c;最左端仍然是最近最少访问的用户1。
5.业务访问用户6&#xff0c;用户6在缓存里没有&#xff0c;需要插入到哈希链表。假设这时候缓存容量已经达到上限&#xff0c;必须先删除最近最少访问的数据&#xff0c;那么位于哈希链表最左端的用户1就会被删除掉&#xff0c;然后再把用户6插入到最右端。
Redis的LRU 数据淘汰机制
在服务器配置中保存了 lru 计数器 server.lrulock&#xff0c;会定时&#xff08;redis 定时程序 serverCorn()&#xff09;更新&#xff0c;server.lrulock 的值是根据 server.unixtime 计算出来的。
另外&#xff0c;从 struct redisObject 中可以发现&#xff0c;每一个 redis 对象都会设置相应的 lru。可以想象的是&#xff0c;每一次访问数据的时候&#xff0c;会更新 redisObject.lru。
LRU 数据淘汰机制是这样的&#xff1a;在数据集中随机挑选几个键值对&#xff0c;取出其中 lru 最大的键值对淘汰。
不可能遍历key 用当前时间-最近访问 越大 说明 访问间隔时间越长
volatile-lru
从已设置过期时间的数据集&#xff08;server.db[i].expires&#xff09;中挑选最近最少使用的数据淘汰
allkeys-lru
从数据集&#xff08;server.db[i].dict&#xff09;中挑选最近最少使用的数据淘汰
LFU
LFU (Least frequently used) 最不经常使用&#xff0c;如果一个数据在最近一段时间内使用次数很少&#xff0c;那么在将来一段时间内被使用的可能性也很小。
可以配置&#xff1a;
volatile-lfu
allkeys-lfu
random
随机
volatile-random
从已设置过期时间的数据集&#xff08;server.db[i].expires&#xff09;中任意选择数据淘汰
allkeys-random
从数据集&#xff08;server.db[i].dict&#xff09;中任意选择数据淘汰
ttl
volatile-ttl
从已设置过期时间的数据集&#xff08;server.db[i].expires&#xff09;中挑选将要过期的数据淘汰
redis 数据集数据结构中保存了键值对过期时间的表&#xff0c;即 redisDb.expires
TTL 数据淘汰机制&#xff1a;从过期时间的表中随机挑选几个键值对&#xff0c;取出其中 ttl 最小的键值对淘汰
noenviction
禁止驱逐数据&#xff0c;不删除 默认
allkeys-lru &#xff1a; 在不确定时一般采用策略。 冷热数据交换
volatile-lru &#xff1a; 比allkeys-lru性能差 存 : 过期时间
allkeys-random &#xff1a; 希望请求符合平均分布(每个元素以相同的概率被访问)
自己控制&#xff1a;volatile-ttl 缓存穿透
案例
字典库 &#xff1a; Redis做DB使用&#xff0c;要保证数据的完整性
maxmemory设置较小&#xff0c;采用allkeys-lru&#xff0c;会对没有经常访问的字典库随机淘汰
当再次访问时会缓存击穿&#xff0c;请求会打到DB上。
解决方案&#xff1a;
1、不设置maxmemory
2、使用noenviction策略
Redis是作为DB使用的&#xff0c;要保证数据的完整性&#xff0c;所以不能删除数据。
可以将原始数据源&#xff08;XML&#xff09;在系统启动时一次性加载到Redis中。
Redis做主从&#43;哨兵 保证高可用