
一. 前言
其实本人我也不是很了解redis,通过写文章的过程中,我也学习一下redis。
以下是redis的部分相关资料,大家可以了解一下。
redis的创始者:Salvatore Sanfilippo,
大佬的网名:antirez
大佬的博客:antirez.com
大佬的github地址:http://github.com/antirez
redis官网:https://redis.io/
最后送上大佬的照片一张:

居然还有点帅,真的是不让人活了。
二. 为什么要使用redis(缓存)?
1. 高性能
当有一个查询接口,请求的时候可能要花费1s钟的时间,那么如果我们把这个接口请求的数据放到redis中,那么我们可能请求这个接口的时间只需要20ms,那么我们为什么不用redis缓存呢?
2. 高并发
单机mysql的QPS顶天了也就2000左右,再多可能就要报警了,那么如果一个系统,它的高峰期一秒钟过来的请求数是几万条,那一个mysql单机绝对会挂掉。当然我们可以选择将msql扩容做主从复制等等。但是如果这些请求里面,大部分都是查询请求,那么我们为什么不把这些查询的数据放到redis里面呢,这样的话请求都是请求redis了,对mysql的压力就会很小了。
三. redis的用途
redis是一个开源的数据库,它是c语言编写的,是一个当前业界非常出名,也普遍都在使用的key-value数据库,它支持网络交互性,是基于内存存储数据的,当然也支持持久化的。
一般来说,我们使用redis都是用来做以下用途:
1.数据库
2.缓存
3.消息中间件
4.分布式锁
5.延迟队列
6.分布式全局唯一id
当然,除了这四种以外,也会被用来做其他的作用,大家如果感兴趣,可以自己去了解了解。
1.redis作为数据库
redis从广义数据库的定义来说,确实是可以当数据库来使用,但是我们都知道,我们在谈论数据库的时候,我们的标准是mysql,oracle这种关系型数据库,当然,我们存储数据的时候,并不是说一定要是关系型的数据库存储,也可以改换方式来存储,从这种意义上来讲,redis也确实可以当做数据存储的数据库来使用。
但是既然使用redis当作我们的存储数据库,那我们必须要考虑一下,当使用redis来作为我们的数据,那么都会出现那些问题呢?
1.当习惯了使用关系型数据库这种强逻辑性的数据库来进行开发的开发人员来说,使用redis当做存储数据库的时候,
是否能保证redis里面存储的数据库的正确性和低冗余呢?
2.因为redis是内存型的数据库,如果数据量特别大的情况下,这使用的成本是否需要考虑下?而且数据量太大的话,
redis的扩增和转移数据,是否有可靠的方案支撑?
3.当使用redis作为数据库,是否有可靠的事务方案保持数据的一致性呢?
4.当我们的业务需要的数据非常复杂的情况下,redis因为其薄弱的关系性和逻辑性,是否能满足业务对数据的查询呢?
5.由于redis是保存在内存中,一旦出现redis宕机或者断电的情况,如何保证数据不丢失?当然,redis也可以做持久
化,AOF和RDB这两种方案确实可以保证大部分数据的完好无损,但是还是会极少数的数据会丢失,那么我们的业务需求
是否能接受这部分的数据丢失呢?
问题提了这么多,那么,redis到底是否能够当作数据库来使用呢?
我觉得是可以的!
如果你能保证你的业务需求上,对数据没有极高的准确性要求,且数据的读取压力很大,而且数据之间的业务逻辑性不强,那么你就可以使用redis当作数据库使用。
其实,在业界,已经有了使用redis当做数据库的公司,这个公司就是新浪微博!而且一些游戏公司,视频公司,也都开始使用redis当做数据库,而这些公司的一部分需求,如果当作一个独立的项目来看,是可以符合我们对redis数据库的要求的。
还有一些方案,就是前端可以使用redis当作前端数据库,不过要前端需要封装一层lua的逻辑,用来封装数据库的业务逻辑。
2.redis作为缓存
redis当做缓存来使用,是redis在当前业界最常见的做法。
如果在系统中,有一部分数据,它的读的操作远远大于写的操作,那么我们就可以将这部分数据放到redis中,目的是为了减轻我们数据库的压力,而且redis的响应能力远远大于mysql和oracle,会大大的提高用户体验。而且,redis也可以用作登录的会话缓存,这是redis作为缓存的做常见的应用场景。
而且,如果数据库的压力太大的话,会造成数据库的崩溃宕机,而redis可以分担一部分访问请求,减轻了数据库的压力。
当然,我们也要注意redis可能产生的缓存雪崩等问题,如果真的出现这种情况,将会整个存储系统击溃,那么就会造成巨大的损失。
3.redis作为消息中间件
redis在创造的时候,从来不是用来当作中间件而存在的,但是在实际开发中,有太多的用户使用redis当作中间件,倒逼着redis的创始人antirez,基于redis的核心代码,另外实现了一个消息队列disque。部署、协议等方面都跟redis非常类似,并且支持集群,延迟消息等等。
那么,为什么这么多人会使用redis来做消息队列呢?
因为redis自带了两种和队列密切相关的机制,PUB/SUB模式和PUSH/POP模式,这两种模式,在大多数消息中间件中大规模的用到了,于是很多开发者基于redis这两种机制,把redis当作了消息中间件来使用。据我的经验来看,如果你的业务的日活是在百万级以下,那么可以使用redis来当消息中间件,但是超过了百万级别的话,那么使用redis就无法满足了,或者说使用redis就会变得很复杂了,如果这个时候转向使用kafka或者其他的MQ,其实也不麻烦,学习成本也不高。
所以,在业务是在百万日活以下的朋友们,可以放心大胆的使用redis当做消息中间件!
4.redis作为分布式锁
为什么可以使用redis作为分布式锁呢,其主要的原因是redis是单进程单线程的模式,所有的请求到了redis都是变成了队列,由并行模式变为了串行模式,因此,在redis中,各个请求是没有竞争关系的。
了解过分布式锁的朋友应该都知道,分布式锁是有其条件存在的,条件如下:
1.互斥性。在任意时刻,只有一个客户端能持有锁。
2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3.具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
而redis就恰好能满足我们的要求,引入Jedis开源组件,redis中有一个方法:
jedis.set(String key, String value, String nxxx, String expx, int time)
这个方法,就是redis中用来使用分布式锁的方法,
5.redis作为延迟队列
这个主要是借鉴了博文:https://juejin.im/post/6844904150703013901#heading-4。
5.1 Redis sorted set
Redis的数据结构Zset,同样可以实现延迟队列的效果,主要利用它的score属性,redis通过score来为集合中的成员进行从小到大的排序。

通过zadd命令向队列delayqueue 中添加元素,并设置score值表示元素过期的时间;向delayqueue 添加三个order1、order2、order3,分别是10秒、20秒、30秒后过期。
zadd delayqueue 3 order3
复制代码消费端轮询队列delayqueue, 将元素排序后取最小时间与当前时间比对,如小于当前时间代表已经过期移除key。
/**
* 消费消息
*/
public void pollOrderQueue() {
while (true) {
Set<Tuple> set = jedis.zrangeWithScores(DELAY_QUEUE, 0, 0);
String value = ((Tuple) set.toArray()[0]).getElement();
int score = (int) ((Tuple) set.toArray()[0]).getScore();
Calendar cal = Calendar.getInstance();
int nowSecond = (int) (cal.getTimeInMillis() / 1000);
if (nowSecond >= score) {
jedis.zrem(DELAY_QUEUE, value);
System.out.println(sdf.format(new Date()) + " removed key:" + value);
}
if (jedis.zcard(DELAY_QUEUE) <= 0) {
System.out.println(sdf.format(new Date()) + " zset empty ");
return;
}
Thread.sleep(1000);
}
}
复制代码我们看到执行结果符合预期
2020-05-07 13:24:09 add finished.
2020-05-07 13:24:19 removed key:order1
2020-05-07 13:24:29 removed key:order2
2020-05-07 13:24:39 removed key:order3
2020-05-07 13:24:39 zset empty
5.2 Redis 过期回调
Redis 的key过期回调事件,也能达到延迟队列的效果,简单来说我们开启监听key是否过期的事件,一旦key过期会触发一个callback事件。
修改redis.conf文件开启notify-keyspace-events Ex
notify-keyspace-events Ex
Redis监听配置,注入Bean RedisMessageListenerContainer
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
编写Redis过期回调监听方法,必须继承KeyExpirationEventMessageListener ,有点类似于MQ的消息监听。
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Override
public void onMessage(Message message, byte[] pattern) {
String expiredKey = message.toString();
System.out.println("监听到key:" + expiredKey + "已过期");
}
}
到这代码就编写完成,非常的简单,接下来测试一下效果,在redis-cli客户端添加一个key 并给定3s的过期时间。
set xiaofu 123 ex 3
复制代码
在控制台成功监听到了这个过期的key。
监听到过期的key为:xiaofu
6.分布式全局唯一id
利用redis的 incr命令实现ID的原子性自增。
127.0.0.1:6379> set seq_id 1 // 初始化自增ID为1
OK
127.0.0.1:6379> incr seq_id // 增加1,并返回递增后的数值
(integer) 2
复制代码用redis实现需要注意一点,要考虑到redis持久化的问题。redis有两种持久化方式RDB和AOF
RDB会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。
AOF会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。
在文章的后面,我会详细讲解一下redis的持久化。
四. Redis的线程模型
Redis实际上是一个单线程工作模型。
Redis内部与使用文件事件处理器file event handler,这个文件事件处理器是单线程的,所以redis才叫做单线程的模型。它采用的io多路复用机制(netty也采用这个机制)监听多个socket,将产生事件的socket压入到内存队列中,事件分派器根据socket上的时间类型来选择对应的事件处理器进行处理。
文件事件处理器的结构包含四个部分:
1.多个socket
2.Io多路复用机制
3.文件事件分派器(这个是单线程的)
4.事件处理器(连接应答处理器,命令请求处理器,命令回复处理器)
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。
redis的内部处理流程如图所示:

事实上,多个socket可能会并发产生不同的操作,每个操作对应不同的文件事件,但是io多路复用程序会监听多个socket,会将产生事件的socket放入到队列中排队,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字,事件分派器根据socket的时间类型交给对用的时间处理器进行处理。
从上图可以看到,redis为未见时间编写了多个处理器,这些处理器分别用于实现不同的网络通讯需求,常用的处理器如下:
1.为了对连接服务器的各个客户端进行应答, 服务器要为监听套接字socket关联连接应答处理器。
2.为了接收客户端传来的命令请求, 服务器要为客户端套接字socket关联命令请求处理器。
3.为了向客户端返回命令的执行结果, 服务器要为客户端套接字socket关联命令回复处理器。
说白了,所谓的单线程模型,指的就是io多路复用机制,因为io多路复用程序会监听所有的socket,但是io多路复用程序却是单线程的。
五. 为什么redis能支撑高并发?
那么,为什么io多路复用程序是单线程的,但是redis的性能仍然是如此的好呢?
1.因为redis是纯内存操作的
因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
2.Io多路复用机制
Io多路复用机制虽然是单线程的,但是它是非阻塞的。
而且Redis 对于 I/O 多路复用模块的设计非常简洁,通过宏保证了 I/O 多路复用模块在不同平台上都有着优异的性能,将不同的 I/O 多路复用函数封装成相同的 API 提供给上层使用。
整个模块使 Redis 能以单进程运行的同时服务成千上万个文件描述符,避免了由于多进程应用的引入导致代码实现复杂度的提升,减少了出错的可能性。
3.Redis是c语言实现的。
因为c语言是最接近机器语言的一种程序语言,它的性能自然是比较高的。
4.单线程的io多路复用机制
避免了多线程的频繁切换上下文的问题,也预防了多线程可能产生的竞争问题。而且在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
六. Redis的过期策略是什么?
Redis的过期策略是:定期删除+惰性删除
定期删除,指的是redis默认每隔100ms就会随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就会删除。肯定不会去抽取所有的过期时间的key,因为redis上存储的数据太大了,如果每一个设置了过期时间的key都会去抽取,那么肯定会对redis的性能造成了巨大的影响的。
惰性删除,指的是我们在实际上使用redis的时候,当我们通过一个key去查询redis的时候,redis会先判断这个key是否过期了,如果过期了就会删除。
但是,如果存在这么一部分数据,它既没有被redis抽取的时候抽取到,也没有被业务使用到,但是它又过期了,那么是不是它就会永远的存储在了redis里面呢,越存越多,最后导致了redis的内存块耗尽了?
这个时候,可能就需要redis另外一个机制了,内存淘汰机制。
七. 内存淘汰机制
redis 内存淘汰机制有以下几个:
1.noeviction
当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
2.allkeys-lru
当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
3.allkeys-random
当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
4.volatile-lru
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
5.volatile-random
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
6.volatile-ttl
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
一般来说,我们用的最多的是第二种,allkeys-lru,这种是最符合我们的要求的。
八. 如何保证 redis 的高并发和高可用?
高并发:一主多从,读写分离,主负责写入,从负责读取。
redis 采用异步方式复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
一个 master node(主节点) 是可以配置多个 slave node (从节点)的;
slave node (从节点)也可以连接其他的 slave node(从节点);
slave node (从节点)做复制的时候,不会 block master node 的正常工作;
slave node(从节点) 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;
但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
slave node (从节点)主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
因为开启了主从复制,那么我们必须开启redis的持久化策略。
九. Redis的持久化
Redis的持久化有两种,RDB和AOF。
1.RDB
1.1 优点
是对redis中的数据执行周期化的持久化。
RDB会生成多个.RDB数据文件,每个数据文件都代表了某一个时刻中的redis的数据,这个多个数据文件的方式,非常适合做冷备份。
RDB对redis进行的持久化的文件,健壮性是非常强的,简单来说,它就是简单粗暴的给redis创建了一个有一个的快照。因此如果redis宕机了,用RDB来还原数据,会很简单,Redis加载RDB恢复数据远远快于AOF的方式。
1.2 缺点
RDB的持久化,redis中默认是5分钟一次,也就是说,如果只用RDB做持久化,那么redis最多有可能丢失5分钟的数据,这点还是很恐怖的,而且Redis版本演进过程中有多个格式 的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。
2.AOF
AOF机制是对redis的每一条写入命令作为日志,以“append-only”的模式写入一个日志文件中,在redis重启的时候,会回放AOF日志中的写入指令来重新构建整个数据集。
AOF一般都是一秒一次,也就是AOF最多会丢失一秒钟的数据。
本文介绍了Redis的相关知识。阐述了使用Redis的原因,包括高性能和高并发。说明了Redis的多种用途,如数据库、缓存等。还讲解了Redis的线程模型、高并发支撑原因、过期策略、内存淘汰机制,以及保证高并发和高可用的方法,最后介绍了RDB和AOF两种持久化方式。
8574

被折叠的 条评论
为什么被折叠?



