redis的使用

redis单线程模型或者实现原理
(1)、I/O多路复用
在服务端,有一段 I/O 多路复用程序,客户端发送的socket套接字请求将其置入队列之中。然后,文件事件分派器,依次去队列中取,转发到不同的事件处理器中。
Redis 单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块该使用多线程,仍会使用了多个线程
redis的io模型主要是基于epoll实现的,不过它也提供了 select和kqueue的实现,默认采用epoll技术实现方式。

linux系统中的select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作-----(多路复用器不负责数据的读写,只管理读写的事件)

select 是无差别拿到所有事件,在用户空间进行遍历选择

poll 基本和select 无区别,只是储存方式不同,用的链表,最大不限制,可以动态扩展

epoll是基于事件回调的方式处理,不用拷贝所有事件,通过epoll_wait函数等待监听即可
 

一文搞懂select、poll和epoll区别 - 知乎

(2)、mmap实现零拷贝
零拷贝技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽

传统IO:read() + write() 发生4次用户态和内核态的上下文切换,4次拷贝。
如图所示:
在这里插入图片描述
零拷贝方案1:mmap+write
mmap主要实现方式是将内核读缓冲区的地址和用户缓冲区的地址进行映射,内核缓冲区和应用缓冲区共享,从而减少了从读缓冲区到用户缓冲区的一次CPU拷贝
有点:使用mmap代替read,减少一次CPU拷贝,节省一半的内存空间

在这里插入图片描述
零拷贝方案2:sendfile
使用sendfile代替read+write,减少了一次CPU拷贝,而且还减少了2次上下文切换
如图所示:
在这里插入图片描述

可参考地址:mmap实现零拷贝_liuwp5的博客-CSDN博客_mmap实现零拷贝

1、redis的好处:
(1)、速度快;因为数据存在内存中,类似于hashmap,而且是单线程,减少了竞争,避免多线程上下文切换造成的开销;

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中
  2. 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  4. 使用多路I/O复用模型,非阻塞IO;
  5. 使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求

redis速度快的原因,如图所示:


(2)、支持丰富的数据类型:String,list,set,sorted set,hash;其中sorted set可以排序功能
(3)、支持事务,操作都是原子性;
(4)、丰富特性:可用于缓存,消息,按key设置过期时间,过期将会自动删除;
(4)、提供了分布式锁,在分布式服务当中可以防止并发

redis除了包含基本的数据类型,还包含bitmap、geohash(GEO)、HyperLogLog等等
1、bitmap:主要是为了大数据量而来的,主要是做数据统计
https://www.jb51.net/article/226274.htm

2、geohash:是一种地址编码方法,主要应用地图

数据格式应用场景:
string
1、计数器/api接口限流
string类型的incr和decr命令的作用是将key中储存的数字值加一/减一,这两个操作具有原子性,总能安全地进行加减操作,因此可以用string类型进行计数,如微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等。
  
2、存储对象
利用JSON强大的兼容性、可读性和易用性,将对象转换为JSON字符串,再存储在string类型中,是个不错的选择,如用户信息、商品信息等。

list
1、消息队列
Redis的lpush + brpop命令组合即可实现阻塞队列,生产者客户端使用lpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的争抢列表尾部的元素,多个客户端保证了消费的负载均衡和高可用;

2、最新列表
list类型的lpush命令和lrange命令能实现最新列表的功能,每次通过lpush命令往列表里插入新的元素,然后通过lrange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。

hash
1、购物车:hset [key] [field] [value] 命令, 可以实现以用户Id,商品Id为field,商品数量为value,恰好构成了购物车的3个要素。
2、存储对象:hash类型的(key, field, value)的结构与对象的(对象id, 属性, 值)的结构相似,也可以用来存储对象。


set
好友、关注、粉丝、感兴趣的人集合:
sinter命令可以获得A和B两个用户的共同好友;
sismember命令可以判断A是否是B的好友;
scard命令可以获取好友数量;
关注时,smove命令可以将B从A的粉丝集合转移到A的好友集合
首页展示随机:美团首页有很多推荐商家,但是并不能全部展示,set类型适合存放所有需要展示的内容,而srandmember命令则可以从中随机获取几个。
存储某活动中中奖的用户ID ,因为有去重功能,可以保证同一个用户不会中奖两次。

zset
zset 可以用做排行榜,但是和list不同的是zset它能够实现动态的排序,例如: 可以用来存储粉丝列表,value 值是粉丝的用户 ID,score 是关注时间,我们可以对粉丝列表按关注时间进行排序。
zset 还可以用来存储学生的成绩, value 值是学生的 ID, score 是他的考试成绩。 我们对成绩按分数进行排序就可以得到他的名次



2、性能问题;
(1)、Master最好不要做任何持久化的工作,如RDB内存快照和AOF日志文件;如果数据比较重要,可以某个slave开启AOF备份,策略设置每秒同步一次;
(2)、master和slave最好在同一个局域网内,为了主从的复制速度以及连接的稳定性;
(3)、尽量避免在压力很大的主库上添加从库;
(4)、主从复制不要使用图状结果,用单向链表结构更为稳定;即Master <- Slave1 <- Slave2 <- Slave3...,这样方便解决单点故障的问题,实现slave对master的替换

3、redis key过期策略:

  • 定时删除
    • 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
    • 优点:保证内存被尽快释放
    • 缺点:
      • 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
      • 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
      • 没人用
  • 惰性删除
    • 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
    • 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
    • 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
  • 定期删除
    • 含义:每隔一段时间执行一次删除过期key操作
    • 优点:
      • 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用--处理"定时删除"的缺点
      • 定期删除过期key--处理"惰性删除"的缺点
    • 缺点
      • 在内存友好方面,不如"定时删除"
      • 在CPU时间友好方面,不如"惰性删除"
    • 难点
      • 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)

注意

  • 上边所说的数据库指的是内存数据库,默认情况下每一台redis服务器有16个数据库(关于数据库的设置,看下边代码),默认使用0号数据库,所有的操作都是对0号数据库的操作,关于redis数据库的存储结构
  • memcached只是用了惰性删除,而redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点)

4、redis的淘汰策略:

    volatile-lru:从已设置的过期数据集中挑选最近最少使用的数据淘汰;

    volatile-ttl:从已设置过期数据集中挑选要过期的数据淘汰;

    volatile-random:从已设置的过期数据集中任意挑选数据淘汰;

    allkeys-lru:从数据集中挑选最近最少使用的数据淘汰;   重点推荐使用,其他的不推荐使用

    allkeys-ttl:从数据集中挑选要过期的数据淘汰;

    allkeys-random:从数据集中随意挑选数据淘汰;

    no-enviction:禁止驱逐数据;    默认策略

配置方法:在redis.conf中有一行配置
# maxmemory-policy alikeys-lru

5、redis的同步机制:
redis中,我们可以通过slaveof命令去将master服务器中的数据复制到slave中;
同步机制:全同步+部分同步;slave初始化时(全同步),向master发送一条sync指令,master会将备份的所有数据都写进rdb文件中,更新master状态,再讲rdb文件内容发送给等待中的salve。运行过程中(部分同步),master收到一个操作后,判断是否需要同步到salve,如果需要同步,将操作记录到aof文件中,遍历所有slave,将操作的指令写入到slave的回复缓存中,一旦slave对应的socket发送缓存中有空间写入数据,即将数据通过socket发送过去。

6、redis经常使用到的用法
String存储
(1)、.set key value:设定key持有指定的字符串value,如果该key存在则进行覆盖操作,总是返回OK
(2)、get key: 获取key的value。如果与该key关联的value不是String类型,redis将返回错误信息,因为get命令只能用于获取String value;如果该key不存在,返回null。。
(3)、incr key:将指定的key的value原子性的递增1. 如果该key不存在,其初始值为0,在incr之后其值为1。如果value的值不能转成整型,如hello,该操作将执行失败并返回相应的错误信息
(4)、decr key:将指定的key的value原子性的递减1. 如果该key不存在,其初始值为0,在incr之后其值为-1。如果value的值不能转成整型,如hello,该操作将执    行失败并返回相应的错误信息。
(5)、append key value:如果该key存在,则在原有的value后追加该值;如果该key不存在,则重新创建一个key/value

新增
sinter key [key …] 查看一个集合的全部成员,该集合是所有给定集合的交集
sunion key [key …] 查看一个集合的全部成员,该集合是所有给定集合的并集
sdiff key [key …] 查看所有给定 key 与第一个 key 的差集
sinterstore destination key [key …] 将 交集 数据存储到某个对象中
sunionstore destination key [key …] 将 并集 数据存储到某个对象中
sdiffstore destination key [key …] 将 差集 数据存储到某个对象中

list数据存储
(1)、lpush key value1 value2...:在指定的key所关联的list的头部插入所有的values,如果该key不存在,该命令在插入的之前创建一个与该key关联的空链表,之后再向该链表的头部插入数据。插入成功,返回元素的个数。
(2)、rpush key value1、value2…:在该list的尾部添加元素
(3)、lrange key start end:获取链表中从start到end的元素的值,start、end可为负数,若为-1则表示链表尾部的元素,-2则表示倒数第二个,依次类推… 
(4)、lpushx key value:仅当参数中指定的key存在时(如果与key管理的list中没有值时,则该key是不存在的)在指定的key所关联的list的头部插入value。
(5)、rpushx key value:在该list的尾部添加元素
(6)、lpop key:返回并弹出指定的key关联的链表中的第一个元素,即头部元素
(7)、rpop key:从尾部弹出元素
(8)、rpoplpush resource destination:将链表中的尾部元素弹出并添加到头部
(9)、llen key:返回指定的key关联的链表中的元素的数量。
(10)、lset key index value:设置链表中的index的脚标的元素值,0代表链表的头元素,-1代表链表的尾元素。

其他sort,sorted set,hash等不再这里一一讲解。具体找文档

redis长干的事情:
在这里插入图片描述
(1)、排行榜,如果使用传统的关系型数据库来做,非常麻烦,而利用Redis的SortSet数据结构能够非常方便搞定;
(2)、计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力;限速器比较典型的使用场景是限制某个用户访问某个API的频率,常用的有抢购时,防止用户疯狂点击带来不必要的压力;
(3)、好友关系,利用集合的一些命令,比如求交集、并集、差集等,可以方便搞定一些共同好友、共同爱好之类的功能;
(4)、简单消息队列,除了Redis自身的发布/订阅模式,我们也可以利用List来实现一个队列机制,比如到货通知、邮件发送之类的需求,不需要高可靠,但是会带来非常大的DB压力,完全可以用List来完成异步解耦;
(5)、Session共享,在很多后台管理系统用户登陆就会使用到,默认Session是保存在服务器的文件中,如果是集群服务,同一个用户过来可能落在不同机器上,这就会导致用户频繁登陆;采用Redis保存Session后,无论用户落在那台机器上都能够获取到对应的Session信息。

为什么要用lua脚本操作redis数据库?
1.减少开销–减少向redis服务器的请求次数,通过lua脚本可以一次性的将多个请求合并成一个请求
2.原子操作–redis将lua脚本作为一个原子执行
3.可复用–其他客户端可以使用已经执行过的lua脚本
4.增加redis灵活性–lua脚本可以帮助redis做更多的事情

lua脚本本身体积小,启动速度快.

因此,从redis 2.6.0开始,redis在服务器端内置lua解释器

redis分布式锁实现原理?
通过Redisson实现分布式锁,如图所示:
 


Redssion也是通过lua脚本实现的,不同的是它在lua脚本的基础上增加了线程id,锁会和用户线程绑定在一起,其他线程没有办法去操作这个锁。同时锁也具有了可重入性。锁存储在Redis的数据类型是Hash,Hash数据类型的key包含了当前线程信息。同时redisson引入了watch dog的自动延时机制,key加锁默认生存时间是30秒,而在这30秒中,watch dog后台线程会每10秒去检查一下,如果线程还持有锁的话,就会不断延长key的存活时间
来自:Redisson实现分布式锁(1)---原理 - 雨点的名字 - 博客园


集群加分布式锁出现的问题?
Redis集群方式共有三种:主从模式,哨兵模式,cluster(集群)模式

但是,在集群模式下也会出现一些问题,由于节点之间是采用异步通信.如果你刚才在master节点上加了锁,但数据又没有同步到slaver节点,而此时的master节点正好挂掉了,它上面的锁就木得了,等到新的master起来的时候,(主从模式的手动切换或者哨兵模式的一次 failover 的过程),就有可能再起获取到同样的锁,出现了一个锁被拿了2次的情况

锁都被拿了两次了,也就不满足安全性了。一个安全的锁,不管是不是分布式的,在任意一个时刻,都只有一个客户端持有。


解决方法:为了解决上面的问题,Redis 的作者提出了名为 Redlock 的算法----Red Lock(红锁)
Red Lock(红锁)

用Redis中的多个master实例,来获取锁,只有大多数实例获取到了锁,才算是获取成功。具体的红锁算法分为以下五步:

1、获取当前的时间(单位是毫秒)。
2、使用相同的key和随机值在N个节点上请求锁。这里获取锁的尝试时间要远远小于锁的超时时间,防止某个masterDown了,我们还在不断的获取锁,而被阻塞过长的时间。
3、只有在大多数节点上获取到了锁,而且总的获取时间小于锁的超时时间的情况下,认为锁获取成功了。
4、如果锁获取成功了,锁的超时时间就是最初的锁超时时间减去获取锁的总耗时时间。
5、如果锁获取失败了,不管是因为获取成功的节点的数目没有过半,还是因为获取锁的耗时超过了锁的释放时间,都会将已经设置了key的master上的key删除。


当客户端挂了,当时在加锁成功后,无法返回客户端,只能释放锁
释放锁操作是需要向所有的节点发起释放锁操作,这样的目的是为了让某些节点,它们在加锁期间确实有收到加锁请求并且set成功,但是由于网络波动或者别的原因,导致返回给客户端的相应丢包了,导致客户端以为没有加锁成功.即让所有的节点都释放锁.所以,释放锁的时候要向所有节点发起释放锁的操作。

分布式锁代码实现操作:Redis分布式锁之红锁_姜秀丽的博客-CSDN博客_redis红锁

面试遇到的问题:
(1)、在面试的时候,常被问比较下Redis比Memcache的速度快,因为redis是单线程,而memcache是多线程,所以说单线程,减少了竞争,避免多线程上下文切换造成的开销,而且采用了 IO多路复用,所以快

(2)、redis和数据库双写一致性问题
分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。
回答:首先,采取双删策略,先删缓存,在更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

(3)、redis缺点是啥
1、redis是单线程的,单台服务器无法充分利用多核服务器的CPU
2、由于 Redis 是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然 Redis 本身有 Key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据。

(4)、分布式锁问题
1:必要的超时机制:获取锁的客户端一旦崩溃,一定要有过期机制,否则其他客户端都降无法获取锁,造成死锁问题。
2:分布式锁,多客户端的时间戳不能保证严格意义的一致性,所以在某些特定因素下,有可能存在锁串的情况。要适度的机制,可以承受小概率的事件产生

(5)、用了redis一定可以保证原子性吗
批量操作的原子性就不会保证原子性

根据业务判定,当对库存操作时就会发生数据不一致性问题,就是因为线程顺序问题,不保证原子性,导致如下所示:

库存问题

假设现在redis有个叫stock的key,用于存放商品的库存数据,只要进货了库存就加一,出货了库存就减一。于是这时候机智的程序员小王这样设计了一下,每次通过get获取stock的值,然后再set stock=stock+1。这时候有两个业务员A、B分别进货了,由于时间线的问题,程序大概是这样跑的:

  1. A先获取value=100
  2. B先获取value=100
  3. A设置value=101
  4. B设置value=101

整个流程下来,发现库存丢了一个,这正是因为整个操作不是原子性的,导致A影响了B,或者B影响了A。
例如:用了redis一定可以保证原子性吗 - 知乎
 


(6)、redis事务

Redis事务是一组命令的集合,将多个命令进行打包,然后这些命令会被顺序的添加到队列中,并且按顺序的执行这些命令。

「Redis事务中没有像Mysql关系型数据库事务隔离级别的概念,不能保证原子性操作,也没有像Mysql那样执行事务失败会进行回滚操作」

这个与Redis的特点:「快速、高效」有着密切的关联,「因为一些列回滚操作、像事务隔离级别那这样加锁、解锁,是非常消耗性能的」。所以,Redis中执行事务的流程只需要简单的下面三个步骤:

  1. 开始事务(MULTI)
  2. 命令入队
  3. 执行事务(EXEC)、撤销事务(DISCARD )

     

redis 5.0新特性:
1.新的流数据类型(Stream data type)
2.新的 Redis 模块 API:定时器、集群和字典 API(Timers, Cluster and Dictionary APIs)
3.RDB 增加 LFU 和 LRU 信息
4.集群管理器从 Ruby (redis-trib.rb) 移植到了redis-cli 中的 C 语言代码
5.新的有序集合(sorted set)命令:ZPOPMIN/MAX 和阻塞变体(blocking variants)
6.升级 Active defragmentation 至 v2 版本
7.增强 HyperLogLog 的实现
8.更好的内存统计报告
9.许多包含子命令的命令现在都有一个 HELP 子命令
10.客户端频繁连接和断开连接时,性能表现更好
11.许多错误修复和其他方面的改进
12.升级 Jemalloc 至 5.1 版本
13.引入 CLIENT UNBLOCK 和 CLIENT ID
14.新增 LOLWUT 命令 http://antirez.com/news/123
15.在不存在需要保持向后兼容性的地方,弃用 "slave" 术语
16.网络层中的差异优化
17.Lua 相关的改进
18.引入动态的 HZ(Dynamic HZ) 以平衡空闲 CPU 使用率和响应性
19.对 Redis 核心代码进行了重构并在许多方面进行了改进

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值