Redis
一、缓存基础
1. 缓存的基本思想
- 缓存就是用空间换时间的基本思想。
- 比如:CPU Cache缓存的是内存数据,用于解决CPU和内存速度不匹配的问题;内存 缓存的是硬盘数据,用于解决硬盘数据访问过慢的问题。
- 为了避免用户请求数据库中的数据速度过于慢,可以在数据库之上再加一层缓存
- 不仅能够提高访问速度,而且缓存支持的并发量也更大了,数据库的压力也可以减少。
2. 缓存的分类
1)本地缓存
1什么是本地缓存
-
本地缓存位于应用内部,和应用存在同一个进程内部。
-
请求本地缓存的速度非常快,不存在额外的网络开销
2本地缓存的方案有哪些
-
JDK自带的 HashMap 和 ConcurrentHashMap
- 两者都是存k-v形式的数据,但是只提供了缓存的功能,没有提供其他像过期时间的功能。
- 一个较为完整的缓存框架至少包括:过期时间、淘汰机制、命中率统计三点
-
Guava Cache 和 Spring Cache(Guava用的更多一点)
-
后起之秀 Caffeine
3本地缓存优缺点
- 优点:低依赖,简单,轻量,低成本
- 缺点:本地缓存和应用耦合,对分布式架构不友好;而且本地缓存的容量会受到服务器部署的机器容量的限制
2)分布式缓存
1什么是分布式缓存
-
分布式缓存脱离于应用独立存在,多个应用可直接的共同使用同一个分布式缓存服务
-
缓存服务可以部署在一台单独的服务器上,即使同一个相同的服务部署在多台机器上,也是使用的同一份缓存
-
单独的分布式缓存性能、容量和提供的功能都更强大;但是系统复杂性和开发成本都增加了
2 分布式缓存的方案——Redis
3)多级缓存
-
即 本地缓存 + 分布式缓存 的方案。
-
本地缓存做L1,分布式缓存做L2,读取缓存数据的时候,先去L1找,L1没有再去L2找,L2没有最后再去DB找
-
为什么用了分布式缓存还要用本地缓存:因为本地缓存的访问速度要远大于分布式缓存
二、Redis基础
1. Nosql
-
Nosql(Not-Only SQL):作为关系型数据库的补充。用内存存储;不存储关系,只存数据
-
Nosql特征:
- 可扩容 可伸缩
- 大数据量下的高性能
- 灵活的数据模型
- 高可用
-
常见的Nosql数据库:Redis、memcache、HBase、MongoDB
2. 什么是Redis
- Redis是一款将数据存在内存中的,高速缓存数据库。
- 是一个key-value的存储系统, 支持丰富的数据类型,如:String、list、set、zset、hash。
3. 为什么要用Redis
【高性能 + 高并发】
-
由于海量用户,高并发 导致。
其中罪魁祸首就是关系型数据库:
- 性能瓶颈:磁盘IO性能低下
- 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群
-
用Redis来解决问题的思路:
- 用内存存储(降低磁盘IO次数,越低越好)
- 不存储关系,只存储数据(去除数据间关系,越简单越好)
-
而且,Redis数据是存在内存中的,比起关系型数据库存在磁盘中,访问速度更快,有高性能
4. Redis特征
-
数据间没有必然的关联关系,都是一个个key-value
-
内部采用单线程机制进行工作
redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。
-
高性能。
-
多数据类型支持
- 字符串类型 string
- 列表类型 list
- 散列类型 hash
- 集合类型 set
- 有序集合类型 sorted_set
-
持久化支持。(可以进行数据灾难恢复)
5. Redis的应用
- 为热点数据加速查询(Redis的主要场景),如热点商品、热点新闻、等高访问量信息
- 任务队列,如秒杀、抢购、购票排队等
- 即时信息查询,如排行榜、公交到站信息、网站在线人数等
- 时效性信息控制,如验证码、投票等
- 分布式数据共享,如分布式集群架构中的session分离
- 消息队列
- 分布式锁
【应用每一点都需要展开说说】 Redis入门 - Redis概念和基础 | Java 全栈知识体系 (pdai.tech)
6. Redis数据存储格式
- redis自身是一个Map,其中所有的数据都是采用key:value的形式存储
- 其中key永远是string类型,redis中说数据类型都指的是value的数据类型
7. Redis中key的命名规范
-
key的命名规范:(表名: 主键名: 主键值: 字段名)。eg:
set user:id:123:fansNum 200
-
在redis中以json格式存value值用户信息
set user:id:123 {id:123, blog:879, fans: 200}
8. Redis Linux版的下载安装
- Windows版主要是初学者不了解linux的人用于学习redis用的,真正要用redis还是要跑在Linux上 下载地址,下载完解压即可
1) 安装步骤
-
下载(这里下的是4.0.0的版本)
wget http://download.redis.io/releases/redis-4.0.0.tar.gz
-
解压
tar –xvf redis-4.0.0.tar.gz
-
编译 + 安装 (这一步要把目录切换到 cd redis-4.0.0/src 文件目录下)
make install //若安装不成功,用命令(可能是用户权限不够?) sudo apt install redis-server
到此已安装完成,可以启动server和cli测试一下
-
启动server和cli,然后set get测试一下
//1.在redis-4.0.0/src目录下启动服务端 redis-server //2.启动客户端 redis-cli
2)启动redis服务的不同方式
-
指定端口启动redis服务
(当要多个redis服务时,在启服务的时候就指定端口,通过指定不同的端口指定不同的redis服务)
redis-server --port 6380
redis客户端连接时也指定端口
redis-cli -p 6380
-
通过配置文件启动redis服务(实现启动多台redis服务)
-
先在redis目录下设置配置文件(类似原有的redis-4.0.0/redis.conf),可以对应每个服务有一个配置文件
-
然后启动redis服务时指定配置文件
//1.切换到redis服务下 cd redis-4.0.0 //2.创建一个文件夹conf,就用来存放redis各个服务的配置文件 mkdir conf //3.在redis-4.0.0/conf目录下,新建配置文件redis-6379.conf vim redis-6379.conf //打开redis-6379.conf后写一些配置 port 6379 daemonize yes logfile "6379.log" dir /redis-4.0.0/data //4.以配置文件形式启动redis服务 redis-server conf/redis-6379.conf //启动几个redis服务设置几个配置文件,然后几个redis服务就可以在同一个机器上跑
redis配置文件中的一些配置内容
- daemonize yes :以守护进程方式启动,使用本启动方式,redis将以服务的形式存在,日志将不再打印到命令窗口中
- port 6***: 设定当前服务启动端口号
- logfile "6***.log“ :设定日志文件名,便于查阅
- dir “/自定义目录/redis/data“ :设定当前服务文件保存位置,包含日志文件、持久化文件等
-
三、Redis数据结构
Redis常用的数据结构有哪些
-
5 种基础数据结构 :String(字符串)、Hash(散列)、List(列表)、Set(集合)、Zset(有序集合)。
-
3 种特殊数据结构 :HyperLogLogs(基数统计)、Bitmap (位存储)、Geospatial (地理位置)。
注: zset就是sorted set。为了避免sorted set简写sset导致命令冲突,所以改为zset。同理如class–>clazz
1. string类型
-
string类型,一个存储空间保存一个数据,最简单的数据类型,通常使用字符串,若字符串以整数的形式展示,可以作为数字操作使用,但他还是字符串
-
string的基本操作
Redis客户端cmd操作:
set key value //设置(原有的覆盖,没有的追加) get key //获取 del key //删除 mset key1 value1 key2 value2… //添加修改多个数据 multiple mget key1 key2… //获取多个数据 strlen key //获取数据字符个数(字符串长度) append key value //追加到原始信息后部(若原始信息存在就追加,否则新建)
-
string的单数据操作和多数据操作怎么选择(set 和 mset)
一条指令的执行时间 = 指令发送给redis的时间 + redis执行指令的时间 + redis返回执行结果的时间
根据消耗的时长,选择set还是mset
-
-
string的扩展操作
-
设置数值增加或减少指定范围的值
//设置数值增加或减少指定范围的值 incr key //+1 decr key //-1 incrby key increment //+increment decrby key increment //-increment incrbyfloat key increment //注意,若原始数据不能转为数值或超越了redis数值上线范围,将报错 //这个方案:可以用redis来控制数据库表主键id,为数据库表主键提供生成策略,保证主键唯一性
-
设置数据具有指定的生命周期
setex key seconds value psetex key millisecends value
2. hash类型
-
hash类型,一个存储空间保存多个键值对数据,底层使用哈希表结构实现数据存储。
hash类型,指key-value中的value是hash类型的,即value又是一个key-value类型。
即结构是key-{fields-values}。 key-hash{field1-value1; field2-value2;field3-value3…}
- 注意:hash类型下的value只能存字符串,不允许存其它类型的数据
-
适用场景:适用存一个表中的多个字段的值,把各个字段搞在一个里面,又要方便获取修改
- 应用场景eg:电商网站购物车:用户id做key,商品id做field,数量做value
-
hash的基本操作 (在redis客户端的cmd指令)
//1.添加修改数据 hset key field value //2.获取数据 hget key field hgetall key //3.删除数据 hdel key field1 [field2] //4.添加修改多个数据 hmset key field1 value1 field2 value2 … //5.获取多个数据 hmget key field1 field2 … //6.获取哈希表中字段的数量 hlen key //7.获取哈希表中是否存在指定的字段 hexists key field
-
hash数据的扩展操作
//8.获取哈希表中所有的字段名(field)或字段值(value) hkeys key hvals key //9.设置指定字段的数值数据 增加指定范围的值 hincrby key field increment hincrbyfloat key field increment
3. list类型
-
list类型 [ key - list ]
-
数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
-
需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
-
list类型:保存多个数据,底层使用双向链表存储结构实现
-
应用场景举例:(朋友圈的点赞列表存储)
朋友圈是key,点赞列表是:list类型的value,点赞操作是:rpush,取消点赞lrem
redis 应用于具有操作先后顺序的数据控制
-
-
list类型的操作
//1.添加/修改数据 lpush key value1 [value2] …… //从左边进数据 rpush key value1 [value2] …… //右边进 //2.获取数据 lrange key start stop //取一个范围内的多个数据 (注意有顺序的概念) //当不知道list总共有多少个数据时,可以用 lrange list1 0 -1 (-1表示倒数第一个) lindex key index llen key //list长度 //3.获取并移除元素 lpop key rpop key //4.规定时间内获取并移除数据 (b代表block阻塞) blpop key1 [key2] timeout brpop key1 [key2] timeout brpoplpush source destination timeout //阻塞的获取数据理解:现在的list中没有这个数据,不代表以后这个list中没有这个数据。 //blpop表示可以等timeout时间范围内只要有这个数据就获取并移除。 //key1 [key2] 表示可以从多个list中等待找这个数据 (理解像多个任务队列中取任务一样) //5.移除指定数据 lrem key count value //count表示移除几个相同的元素
-
list的注意事项
- list中保存的数据都是string类型的
- list具有索引的概念,操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
- 获取全部数据操作结束索引设置为-1
- list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载
4. set类型
-
set类型
-
存储大量的数据,在查询方面提供更高的效率(比起list存储效率高,因为list底层是链表,不利于查询)
-
set类型底层结构:与hash存储结构相同,哈希表,仅存储键,不存储值(nil),并且值是不允许重复的
理解底层结构,set整个是key - hash,其中hash的key-value中的key存真正的数据,value不存东西
同Java中的HashSet 的内部实现使用的是HashMap,只不过所有的value都指向同一个对象
Redis的Set结构也是一样,它的内部也是用hash结构,所有的value都指向同一个内部值
其实就是不允许重复的集合
-
-
set类型的基本操作
//1.添加数据 sadd key member1 [member2] //2.获取全部数据 smembers key //3.删除数据 srem key member1 [member2] //4.获取集合数据总量 (该set集合中有几个数据) scard key //5.判断集合中是否包含指定数据 sismember key member
-
set类型的扩展操作
//6.随机获取集合中指定数量的数据 (原集合数据不变) srandmember key [count] //7.随机获取集合中的某个数据并将该数据移出集合 (原集合数据改变) spop key [count] //8.求两个集合的交、并、差集 sinter key1 [key2] sunion key1 [key2] sdiff key1 [key2] //9.求两个集合的交、并、差集并存储到指定集合中 sinterstore destination key1 [key2] sunionstore destination key1 [key2] sdiffstore destination key1 [key2] //10.将指定数据从原始集合中移动到目标集合中 smove source destination member
-
set的注意事项
- set 类型不允许数据重复,若添加的数据在 set 中已经存在,将只保留一份(添加已存在的数据时失败)
- set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间
5. sorted_set类型
-
sorted_set类型
-
数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
-
sorted_set类型:在set的存储结构基础上添加可排序字段
底层数据类型:key - hash( value - nil - score),会根据score大小进行排序
sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果
-
-
sorted_set基本操作
//1.添加数据 zadd key score1 member1 [score2 member2] //2.获取全部数据 zrange key start stop [WITHSCORES] zrevrange key start stop [WITHSCORES] //不加withscores会展示排好序的数据,加withscores展示排好序的数据和分数 //zrange是正向从小到大的顺序,zrevrange是从大到小 //3.删除数据 zrem key member [member ...] //4.按条件获取数据 zrangebyscore key min max [WITHSCORES] [LIMIT] zrevrangebyscore key max min [WITHSCORES] //5.条件删除数据 zremrangebyrank key start stop zremrangebyscore key min max //min与max用于限定搜索查询的条件 //start与stop用于限定查询范围,作用于索引,表示开始和结束索引 //6.获取集合数据总量 zcard key zcount key min max //7.集合交、并操作 zinterstore destination numkeys key [key ...] zunionstore destination numkeys key [key ...] //8.获取数据对应的索引(排名) zrank key member zrevrank key member //9.score值获取与修改 zscore key member zincrby key increment member
6. HyperLogLogs 基数统计
-
什么是基数
举个例子,A = {1, 2, 3, 4, 5}, B = {3, 5, 6, 7, 9};那么基数(不重复的元素)= 1, 2, 4, 6, 7, 9; (允许容错,即可以接受一定误差)
-
HyperLogLogs 基数统计用来解决什么问题?
这个结构可以非常省内存的去统计各种计数,比如注册 IP 数、每日访问 IP 数、页面实时UV、在线用户数,共同好友数等。
-
操作
127.0.0.1:6379> pfadd key1 a b c d e f g h i # 创建第一组元素 (integer) 1 127.0.0.1:6379> pfcount key1 # 统计元素的基数数量 (integer) 9 127.0.0.1:6379> pfadd key2 c j k l m e g a # 创建第二组元素 (integer) 1 127.0.0.1:6379> pfcount key2 (integer) 8 127.0.0.1:6379> pfmerge key3 key1 key2 # 合并两组:key1 key2 -> key3 并集 OK 127.0.0.1:6379> pfcount key3 (integer) 13
7. Bitmap 位存储
-
Bitmap 即位图数据结构,都是操作二进制位来进行记录,只有0 和 1 两个状态。
-
用来解决什么问题?
比如:统计用户信息, 登录,未登录! 打卡,不打卡! 两个状态的,都可以使用 Bitmaps!
如果存储一年的打卡状态需要多少内存呢? 365 天 = 365 bit 1字节 = 8bit 46 个字节左右!
-
相关命令使用
# 1. 使用bitmap 来记录 周一到周日的打卡! 周一:1 周二:0 周三:0 周四:1 ...... 127.0.0.1:6379> setbit sign 0 1 (integer) 0 127.0.0.1:6379> setbit sign 1 1 (integer) 0 127.0.0.1:6379> setbit sign 2 0 (integer) 0 127.0.0.1:6379> setbit sign 3 1 (integer) 0 127.0.0.1:6379> setbit sign 4 0 (integer) 0 127.0.0.1:6379> setbit sign 5 0 (integer) 0 127.0.0.1:6379> setbit sign 6 1 (integer) 0 # 2. 查看某一天是否有打卡! 127.0.0.1:6379> getbit sign 3 (integer) 1 127.0.0.1:6379> getbit sign 5 (integer) 0 # 3. 统计操作,统计 打卡的天数! 127.0.0.1:6379> bitcount sign # 统计这周的打卡记录,就可以看到是否有全勤! (integer) 3
8. geospatial 地理位置
- redis的数据类型 geospatial能计算出geohash。redis使用geohash技术将实时上报的经度和纬度,通过一定的算法转化成最长12个字符的字符串,两个位置的经纬度计算的字符串的前缀越相同,则两个位置离得越近
四、Redis通用指令
1. key的通用指令
//1.删除指定key
del key
//2.获取key是否存在
exists key
//3.获取key的类型
type key
//4.为指定key设置有效期
expire key seconds
pexpire key milliseconds //pexpire和expire区别是时间单位不同
expireat key timestamp //3和4指令使用时间戳,linux系统中用的
pexpireat key milliseconds-timestamp
//5.获取key的有效时间
ttl key
pttl key
//6.切换key从时效性转换为永久性
persist key
//7.查询key
keys pattern
/**
pattern 写查询模式规则
* 匹配任意数量的任意符号
?配合一个任意符号
[] 匹配一个指定符号
举例:
keys * 查询所有
keys it* 查询所有以it开头
keys *heima 查询所有以heima结尾
keys ??heima 查询所有前面两个字符任意,后面以heima结尾
keys user:? 查询所有以user:开头,最后一个字符任意
keys u[st]er:1 查询所有以u开头,以er:1结尾,中间包含一个字母,s或t
*/
//8.为key改名
rename key newkey //redis中若已有新名字则会覆盖
renamenx key newkey //若新名字不存在的时候改成功,若redis里已有新名字则不成功
//9.对所有key排序
sort
//10.其他key的通用操作
help @generic
2. redis数据库的通用操作
-
Redis数据库
-
Redis为每个服务提供有16个数据库,编号index从0到15,每个数据库之间的数据相互独立
(感觉类似mysql中的schema的概念)
-
-
Redis数据库的操作
//1.切换数据库 (index是数据库编号,从0开始) select index //2.其他操作 quit ping //测试redis服务器是否连通 echo message //输入什么message,控制台就会输出什么message //3.数据移动 move key db //把key移动到db数据库中,原数据库不存在了(剪切操作) //4.数据清除 dbsize //看当前的数据库下有多少个数据 flushdb //删掉当前数据库下的所有数据 flushall //删掉所有数据库中的所有数据
五、Java连Redis服务
Java语言连接redis服务的工具有:Jedis、SpringData Redis、Lettuce等。其他语言也有自己连redis的工具
1. Jedis(Redis提供的工具)
-
Jedis:使用java语言连接redis服务
-
Jedis使用步骤
-
引入jedis的jar包—— redis.clients.jedis
-
写代码
- 连接redis
- 操作redis
- 关闭连接
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
@Test public void testJedis(){ //1.连接redis Jedis jedis = new Jedis("127.0.0.1", 6379); //2.操作redis jedis.set("name","itheima"); String name = jedis.get("name"); System.out.println(name); //3.关闭连接 jedis.close(); }
-
-
Jedis工具类开发
上述代码中,Jedis对象是手工管理的,不应该。
应该从Jedis连接池获取Jedis对象,而Jedis已经为我们提供好了连接池技术JedisPool
public class JedisUtils { private static JedisPool jedisPool = null; //把连接池的创建过程放在静态代码块中,就加载一次,也就创建一次 static { /** * JedisPool的三个参数 * 1.poolConfig:连接池配置对象 * 2.host:redis服务地址 * 3.port:redis服务端口号 */ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); //设置最大连接数 jedisPoolConfig.setMaxTotal(30); //设置活动连接数 jedisPoolConfig.setMaxIdle(10); //设置redis服务地址 String host = "127.0.0.1"; //设置redis服务的端口号 int port = 6379; //定义一个Jedis连接池 jedisPool = new JedisPool(jedisPoolConfig, host, port); } public static Jedis getJedis() { //getResource()即获取Jedis对象 return jedisPool.getResource(); } //测试一下 public static void main(String[] args) { Jedis jedis = JedisUtils.getJedis(); } }
2. SpringData Redis (Spring提供的操作Redis)
位于spring-data-redis-x.x.x.jar包中,[Spring Data Redis官网](https://docs.spring.io/spring-data/redis/docs/current/reference/html/)。使用方法如下
-
引入jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
代码中
@Service public class LoginServiceImpl implements LoginService { @Autowired private RedisTemplate redisTemplate; …… //把token放在redis里,过期时间1天 ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set(key, value, 1, TimeUnit.DAYS); …… }
------------------------------------------
六、Redis线程模型
-
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
-
既然是单线程,那怎么监听大量的客户端连接呢?
Redis 通过 IO 多路复用程序 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
这样的好处非常明显: I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗(和 NIO 中的
Selector
组件很像)。 -
不过,
在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作,
Redis 6.0 版本之后引入了多线程来处理网络请求(提高网络 IO 读写性能)。
-
为什么处理网络请求要引入多线程
- 首先, Redis 的瓶颈并不在 CPU,而在内存和网络。
- 内存不够的话,可以加内存或者做数据结构优化和其他优化等,但网络的性能优化才是大头,网络 IO 的读写在 Redis 整个执行期间占用了大部分的 CPU 时间,如果把网络处理这部分做成多线程处理方式,那对整个 Redis 的性能会有很大的提升。
-
虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
-
拓展:什么是Reactor模式?
Reactor模式称为反应器模式或应答者模式,是基于事件驱动的设计模式,拥有一个或多个并发输入源,有一个服务处理器和多个请求处理器,服务处理器会同步的将输入的请求事件以多路复用的方式分发给相应的请求处理器。
七、Redis内存管理
1. Redis 给缓存数据设置过期时间有啥用?
- 有助于缓解内存的消耗: 内存是有限的,如果缓存中的所有数据都是一直保存的话,直接 Out of memory。
- 业务场景的需要:要某个数据只在某一时间段内存在,比如我们的短信验证码可能只在 1 分钟内有效,用户登录的 token 可能只在 1 天内有效。
2. Redis如何判读数据是否过期
- Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。
- 过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
3. 过期数据的删除策略
如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
-
常用的过期数据的删除策略就两个:
- 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。
- 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
-
定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,
所以 Redis 采用的是 定期删除+惰性/懒汉式删除 。
-
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况(因为定期删除策略是随机抽取)。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
怎么解决这个问题呢?答案就是:Redis 内存淘汰机制。
4. Redis内存淘汰机制
Redis 提供 6 种数据淘汰策略:
- volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
4.0 版本后增加以下两种:
- volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
- allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
八、Redis持久化
1. 怎么保证 Redis 挂掉之后再重启数据可以进行恢复?
-
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
-
为了防止数据丢失以及服务重启时能够恢复数据,Redis支持数据的持久化,主要分为两种方式,分别是RDB和AOF。一种方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)。
2. RDB持久化
1)什么是 RDB 持久化?
-
Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。
-
Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
-
快照持久化是 Redis 默认采用的持久化方式,在
redis.conf
配置文件中默认有此下配置:save 900 1 #在900秒(15分钟)之后,若至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。 save 300 10 #在300秒(5分钟)之后,若至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照。 save 60 10000 #在60秒(1分钟)之后,若至少有10000个key发生变化,就会自动触发bgsave命令创建快照。
2)RDB 创建快照时会阻塞主线程吗?
Redis 提供了两个命令来生成 RDB 快照文件:
save
: 主线程执行,会阻塞主线程;bgsave
: 子线程执行,不会阻塞主线程,默认选项。
3. AOF持久化
1)什么是AOF持久化
-
与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。
默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
appendonly yes
-
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存
server.aof_buf
中,然后再根据appendfsync
配置来决定何时将其同步到硬盘中的 AOF 文件。 -
三种不同的AOF持久化方式
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘 appendfsync no #让操作系统决定何时进行同步
2)AOF日志
-
AOF日志采用“写后日志”,即先执行命令,把数据写入内存,然后才记录日志。(mysql是写前日志)
-
日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。
-
为什么采用写后日志?
-
避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。
如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,用日志恢复数据时,就可能会出错。
-
在命令执行完之后再记录, 不会阻塞当前的命令执行
但这种方式存在潜在风险:
- 如果命令执行完成,写日志之前宕机了,会丢失数据。
- 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
-
3)AOF重写
-
为何要有AOF重写:AOF会记录每个写命令到AOF文件,随着时间越来越长,AOF文件会变得越来越大。如果不加以控制,会对Redis服务器,甚至对操作系统造成影响,而且AOF文件越大,数据恢复也越慢。为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”。
-
理解AOF重写机制: Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个AOF文件保存的数据相同,但新AOF文件没有了冗余命令。
九、Redis事务
1. 什么是Redis事务
-
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,按照顺序串行化执行队列中的命令,其他客户端提交的命令不会插入到事务执行命令序列中。
-
总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。
2. Redis事务的基本操作
# 1.开启事务:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
multi
# 2.执行事务:设定事务的结束位置,同时执行事务的所有操作。与multi成对出现,成对使用
exec
# 3.取消事务:终止当前事务的定义,发生在multi之后,exec之前
discard
# 4.监视一个或多个key,如果事务在执行前,这个key被其他命令修改,则事务被中断,不会执行事务中的任何命令。
watch
3. Redis事务是否支持原子性
-
Redis 事务在运行错误的情况下,除了执行过程中出现错误的命令外,食物中其他命令都能正常执行。并且,Redis 是不支持回滚(roll back)操作的。
-
因此,Redis 事务其实是不满足原子性。
-
除了不满足原子性之外,事务中的每条命令都会与 Redis 服务器进行网络交互,这是比较浪费资源的行为。
因此,Redis 事务是不建议在日常开发中使用的。
十、Redis生产问题
1. 缓存穿透
- 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
2. 缓存雪崩
-
缓存雪崩描述的就是这样一个简单的场景:缓存在同一时间大面积的失效,后面的请求都直接落到了数据库上,造成数据库短时间内承受大量请求。
-
举个例子:系统的缓存模块出了问题比如宕机导致不可用。造成系统的所有访问,都要走数据库。
还有一种缓存雪崩的场景是:有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。
举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。
解决缓存雪崩的办法
针对 Redis 服务不可用的情况:
- 采用 Redis 集群,避免单机出现问题整个缓存服务都没办法使用。
- 限流,避免同时处理大量的请求。
针对热点缓存失效的情况:
- 设置不同的失效时间比如随机设置缓存的失效时间。
- 缓存永不失效。
11、Redis集群
上面的待整理:
Redis应用