听我的,Redis 看这一篇就够了!

599 阅读13分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、概述

1. 简介

Redis是一种以key-value形式存储的跨平台非关系型数据库。键包含字符串(string)、哈希(hash)、链表(list)、集合(set)、有序集合(zset) ,提供了丰富的操作。redis可以周期性地将数据写入磁盘或者将某些操作指令写入文件中。

redis在线入门 : try.redis.io/

redis 中文资料站: www.redis.cn/

www.runoob.com/redis/redis…

2. 优点

  • 速度快,能够高并发读写数据(直接操作内存)
  • 能够对海量数据进行高效率存储和访问
  • 在redis 6之前,只支持单线程操作,每个操作都是原子操作,没有并发相关问题;在redis 6之后,支持多线程操作,但本质上只是多了排队的队伍,内部处理依然是逐个处理。

3. 缺点

  • 由于redis是纯使用内存,存储成本非常高,所以不能将redis当做海量数据库使用
  • 主从数据在复制过程中,若主机数据未完全复制到从机,而主机宕机,则会造成数据丢失问题

4. 作用

  • 处理高并发读写数据或者读写海量数据的问题
  • 会话缓存(常用)
  • 消息队列(支付)
  • 排行榜或者计数(阅读数统计)
  • 消息发布、通知

redis的定位是缓存,提高数据读写速度,减轻对数据库存储与访问的压力

Redis默认端口是: 6379

二、数据类型命令

1. String

# 存入键值对
set key value
# 根据键,获取值
get key
# 将数值类型的值递增1
incr key
# 将数值类型的值递减1
decr key
# 根据键,删除键值对
del key
# 存入键值对,并设置失效时间(timeout,单位秒)
setex key timeout value
# 查询指定key的存活时间
ttl key
# 存入键值对,若已存在key,则不操作;否则直接添加(不能用于修改value值)
setnx key value
# 偏移值,即某个值递增多少,可以为负值
incrby key num
# 批量存入键值对
mset k1 v1 k2 v2 ...
# 批量取出键值
mget k1 k2 ...
# 原值后拼接新内容,value为需要拼接的内容(返回拼接后字符串长度)
append key value
# 修改键对应的值,index为开始修改位置,修改长度为value的长度(返回修改后字符串长度)
setrange key index value

应用场景:

  • 计数器:redis能够实现快速计数、查询缓存的功能,并且数据能够异步落地到其他数据库。

    视频播放数系统:使用redis作为视频播放数计数的基础组件

  • 共享session:出于负载均衡的考虑,分布式系统中会将用户的访问信息均衡到不同的服务器上,但这样很可能造成用户刷新一下页面就需要重新登录,为了避免这个问题可以使用redis将用户session进行集中管理,在这种情况,我们只需要保证redis的高可用性和高拓展性,每次需要用户信息就可以从redis中直接获取。

image.png

2. Hash

特别适合存储对象

image.png

# 存入一个hash对象(添加返回1,修改返回0)
hset key hashkey hashvalue
# 根据hash对象键获取值
hget key hashkey
# 判断hash对象是含有某个键(存在返回1,不存在返回0)
hexists key hashkey
# 根据hashkey删除hash对象键值对(成功返回1,失败返回0)
hdel key hashkey
# 递增值 -> 递增hashkey对应的值
hincrby key hashkey increment
# 获取hash对象键的数量
hlen key
# 获取hash对象的所有键
hkeys key
# 获取hash对象的所有值
hvals key
# 获取hash对象的所有数据
hgetall key
# 同样有hsetnx,其作用跟用法和setnx一样

哈希结构相对字符串序列化信息更加直观,更新操作更加便捷,常用于用户信息等管理

但哈希结构是稀疏的,关系型数据库则是完全结构化的,若让redis去模拟关系型数据库进行复杂查询,不仅开发困难,维护成本也高

hash类型应用.png

3. List

类似于Java中的双向队列

# 往列表左边添加数据(返回值为链表长度)
lpush key value
# 往列表右边添加数据(返回值为链表长度)
rpush key value
# 弹出列表最左边的数据(返回值为弹出的元素)
lpop key
# 弹出列表最右边的数据(返回值为弹出的元素)
rpop key
# 显示列表指定范围数据,若全显示则设置0 -1
lrange key start end
# 获取列表长度
llen key
# 参考值之前/后插入数据
linsert key before/after refVal newVal
# 根据索引修改数据
lset key index value
# 在列表中按照个数删除数据
lrem key count value
# 范围截取列表
ltrim key start end
# 根据索引取列表中数据
lindex key index

应用场景

  • 用户收藏文章列表:xxxx_user_articles:uid [aid1, aid2, aid3.....]

4. Set

Set是一种无序集合,通过hashtable实现,能够获取交集、并集、差集

# set集合中添加元素(成功返回1,失败返回0)
sadd key value
# 列出set集合中的元素
smembers key
# 删除set集合中的元素(成功返回1,失败返回0)
srem key value
# 随机弹出集合中的元素(返回被弹出的元素)
spop key count
# 返回key1中特有元素(差集)
sdiff key1 key2
# 返回两个set集合的交集
sinter key1 key2
# 返回两个set集合的并集
sunion key1 key2
# 返回set集合中元素个数
scard key
sdiffstore var key1 key2 -> 返回key1中特有元素存入另一个set集合
sinterstore var key1 key2 -> 返回两个set集合的交集存入另一个set集合
sunionstore var key1 key2 -> 返回两个set集合的并集存入另一个set集合
smove key1 key2 value -> 把key1中的某元素移入key2中
sismember key value -> 判断集合是否包含某个值
srandmember key count -> 随机获取set集合中元素

应用场景:

  • 去重

  • 抽奖

    • 准备一个抽奖池:sadd luckydraw 1 2 3 4 5 6 7 8 9 10 11 12 13
    • 抽3个三等奖:spop luckydraw 3
    • 抽2个二等奖:spop luckydraw 2
    • 抽1个二等奖:spop luckydraw 1

5. Sorted_set

# 存入分数和名称
zadd key score column
# 偏移名称对应的分数
zincrby key score column
# 按照分数升序输出名称
zrange key start end 
# 按照分数降序输出名称
zrevrange key start end
# 升序返回排名
zrank key name
# 降序返回排名
zrevrank key name
# 返回元素个数
zcard key 
zrangebyscore key min max [withscores] -> 按照分数范围升序输出名称
zrevrangebyscore key max min [withscores] -> 按照分数范围降序输出名称
zrem key name -> 删除名称和分数
zremrangebyscore key min max [withscores] -> 根据分数范围删除元素
zremrangebyrank key start end -> 根据排名删除元素
zcount key min max -> 按照分数范围统计个数

应用场景:

  • 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。

6. 总结

Redis数据类型选用问题【偏redis原生数据结构】:

  • 如果需要排序,则选用Sorted_set
  • 如果数据是多个并且允许重复,则选用List
  • 如果数据是多个并且不允许重复,则选用Set
  • 其它选用String,对象类型也可以使用hash

hash ---> json格式

{

key1:value1

key2:value2 ------> "{key1:value1, key2:value2}"

}

JSON.toJsonString(map)

Redis数据类型选用问题【偏redis String类型json结构】:

  • 如果要排序选用zset
  • 剩下的使用string

在Java中操作中,若使用list、set或其它类型,则操作redis时需要明确指定泛型,会很麻烦,因此,有些公司就统一规范,统一使用字符串,从而减少泛型操作

key和value的设计:

  • key

    • 唯一性
    • 可读性:key加上前缀,保证见名知意
    • 灵活性
    • 时效性:需要考虑存入redis中数据的有效时间
  • value:由需求决定

三、Redis进阶

1. Redis高级指令

# 返回所有键(可以模糊查询)(keys articleView:*)
keys *
# 是否存在指定的key(存在返回1,不存在返回0)
exists key
# 设置某个key的过期时间,使用ttl查看剩余时间**
expire key
# 取消过期时间(成功返回1,失败返回0)
persist key
# 清空当前数据库
flushdb
# 清空所有数据库
flushall
# 查看数据库的key数量
dbsize 
# 选择数据库 数据库为0到15(一共16个数据库) 默认进入的是0数据库
select index
# 将当前数据中的key转移到其他数据库中,db为数据库下标
move key db
# 随机返回数据库里的一个key
randomkey
# 重命名key
key key newkey
# 打印
echo message
# 获取数据库信息
info
# 用来读取redis服务器的配置文件参数
config get *

2. Redis安全性

由于redis的速度非常快,所以在一台较好的服务器下,用户可以在一秒内进行15万次破解密码的尝试,这就意味着我们需要设置强大的密码来防止暴力破解。

Redis修改密码的步骤:

  • 在Redis的安装目录下找到配置文件redis.windows.conf,将里面requirepass [密码]的注释去掉并设置密码
  • 重启服务器pkill redis-server
  • 重新进入Redis,输入key *命令,报错:(error)NOAUTH Authentication required.表示密码生效
  • 通过输入auth [密码]进入或运行窗口输入redis-cli -a [密码]进入

3. Redis事务

使用事务的步骤:

  • 通过multi命令打开事务
  • 进行相关设置(设置的数据存放在队列中)
  • 通过exec命令执行,数据将会依次存储到redis中
  • 通过discard命令取消事务

Redis 事务可以一次执行多个命令,并且带有以下三个重要的保证:

  • 批量操作在发送 EXEC 命令前被放入队列缓存。
  • 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
  • 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。

面试题:Redis是否支持事务?

可以说支持,也可以说是不支持。因为在redis中启动事务到执行,其实是将指令打包之后进行批量操作,虽然每一个指令都具有原子性,但打包后的指令包并不是原子性的,其中的任意一条指令发生错误都无法进行回滚,所以redis中的事务并不是真正意义上的事务。

4. Redis持久化机制

Redis是一个支持持久化的数据库,即Redis需要将内存中的数据同步到硬盘来保证持久化,因此,Redis数据存放在两个地方:内存和硬盘。第一次启动会从硬盘中读取数据到内存,之后的操作都是在内存中进行的,不过,根据采用的持久化方式,每隔一段时间,就会将内存中的数据存到硬盘中。

Redis持久化的方式有两种:

  • RDB方式(默认使用)
  • AOF方式

面试题:你们项目中使用Redis,那么持久化配置是怎么配置的?

Redis的持久化配置并不是固定的,需要根据实际情况进行变动,比如数据量、数据的重要性以及增长频率(短时间数据的变化)。如果这几个因素都差不多或者说无所谓,那么可以直接使用默认配置。

5. Redis内存淘汰机制及过期Key处理

Redis中的内存如果满了,再来新的请求的话,应该如何处理?

Redis中存在着内存淘汰机制,这种内存淘汰机制是指内存使用达到上限,为了保证其他请求能够正常处理,需要根据一定的算法来淘汰掉某些数据。这个内存使用上限可以通过maxmemory进行配置,0为不限制。

常见的内存淘汰机制分为四大类:

  • LRU(最近最少使用算法):从数据库中删除掉长期不被使用的数据。该算法认为,对于长期不用的数据,再次被访问的概率就很小了。淘汰的数据为最长时间没有被使用,仅与时间相关。
  • LFU(最不经常使用算法):简单理解,就是淘汰掉最近一段时间内,使用次数最少的数据,仅与频次和时间相关。
  • TTL:在Redis中,有些数据能够设置失效时间,对于即将过期的数据,该算法就会认为,既然你快要过期了,那我就提前处理掉,反正也不太可能会再被使用到。
  • 随机淘汰:随机淘汰某些数据(基本不会使用该方式)

对某些数据设置失效时间,当被访问时,失效时间重置,否则,失效时间一到,立即清除?

通过maxmemroy-policy可以配置具体的淘汰机制:

命令说明
volatile-lru/allkeys-lru找出已经设置过期时间的数据集/全体数据,将最近最少使用(被访问到)的数据干掉
volatile-ttl/找出已经设置过期时间的数据集,将即将过期的数据干掉
volatile-random/allkeys-random找出已经设置过期时间的数据集/全体数据,进行无差别攻击,随机干掉数据
volatile-lfu/allkeys-lfu找出已经设置过期时间的数据集/全体数据,将一段时间内,使用次数最少的数据干掉
no-enviction只报错显示内存不足,不做任何处理,好处是保证数据不丢失(系统默认)

6.Redis过期Key清除策略

  • 惰性删除:当访问key时,才去判断该key是否过期,如果过期了,那就直接干掉。

    • 优点:对CPU友好,但key可能一直存在内存中,会造成内存浪费
  • 定时删除:设置key的过期时间的同时,创建一个定时器,但到达过期时间点,则立即删除该key。(最不友好)

  • 定期删除:每隔一段时间,就对数据进行一次检查,并删除里面过期的key,至于检查的数量以及删除的数量,则由算法决定。

    • 举个例子:假设Redis每秒随机取100个数据进行过期检查,并删除检查数据中的所有已经过期的key,如果过期key的数量占比大于总数的25%,即大于25个,则重复以上步骤进行检查。

一般情况下,Redis服务器实际使用的是惰性删除和定期删除两种策略,通过两种策略的互相配合,可以在合理使用CPU和避免内存浪费之间取得平衡。