Redis原理及使用常见问题

目录

一、简介

二、数据类型

1.字符串(Strings)

2.列表(Lists)

3.集合(Sets)

4.哈希(Hashes)

5.有序集合(Sorted sets)

三、Redis动态字符串

四、Redis持久化

1.RDB快照(Snapshot)

2.AOF(append-only file)

3.混合持久化

五、IO多路复用

六、使用时碰到的问题及解决方案

1.前言

2.将会碰到的问题

3.解决方案

3.1 缓存穿透

3.2 缓存击穿

3.3 缓存雪崩


一、简介

Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets)与范围查询,bitmaps,hyperloglogs和地理空间(geospatial)索引半径查询。

二、数据类型

1.字符串(Strings)

字符串是一种最基本的Redis值类型。Redis字符串是二进制安全的,这意味着一个Redis字符串能包含任意类型的数据,例如:一张JPEG格式的图片或者一个序列化的Ruby对象。

一个字符串类型的值最多能存储512M字节的内容。

2.列表(Lists)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。Redis的列表是一个链表结构。

LPUSH命令插入一个新元素到列表头部,而RPUSH命令插入一个新元素到列表的尾部。当对一个空key执行其中某个命令时,将会创建一个新表。类似的,如果一个操作要清空列表,那么key会从对应的key空间删除。这是个非常便利的语义,因为如果使用一个不存在的key作为参数,所有的列表命令都会像在对一个空表操作一样。

一个列表最多可以包含2^32-1个元素(4294967295,每个表超过40亿个元素)。

3.集合(Sets)

Redis集合是一个无序的字符串合集。你可以以O(1)的时间复杂度(无论集合中有多少元素时间复杂度都为常量)完成添加、删除以及测试元素是否存在的操作。

Redis集合有着不允许相同成员存在的优秀特性。向集合中多次添加同一元素,在集合中最终只会存在一个此元素。实际上这就意味着,在添加元素前,你并不需要事先进行检验此元素是否已经存在的操作。

一个集合最多可以包含2^32-1个元素(4294967295,每个集合超过40亿个元素)。

4.哈希(Hashes)

Redis Hashes是字符串字段和字符串值之间的映射,所以它们是完美的表示对象的数据类型。

一个拥有少量(100个左右)字段的hash需要 很少的空间来存储,所有你可以在一个小型的 Redis实例中存储上百万的对象。

一个hash最多可以包含2^32-1 个key-value键值对(超过40亿)。

5.有序集合(Sorted sets)

Redis有序集合和Redis集合类似,是不包含 相同字符串的合集。它们的差别是,每个有序集合 的成员都关联着一个评分,这个评分用于把有序集 合中的成员按最低分到最高分排列。

使用有序集合,你可以非常快地(O(log(N)))完成添加,删除和更新元素的操作。因为元素是在插入时就排好序的,所以很快地通过评分(score)或者位次(position)获得一个范围的元素。访问有序集合的中间元素同样也是非常快的,因此你可以使用有序集合作为一个没用重复成员的智能列表。在这个列表中,你可以轻易地访问任何你需要的东西: 有序的元素,快速的存在性测试,快速访问集合中间元素!

三、Redis动态字符串

Redis类型中的基本类型是字符串。Redis是基于键-值存储的数据库。Redis中使用字符串作为它的键,同时字符串也是”值”所使用的最基本的数据类型。列表,集合,有序集合和哈希表是其它更复杂的值类型,不过即使是这些复杂的类型也是使用字符串来实现的。

Redis底层的字符串实现包含了SDS(Simple Dynamic Strings,简单动态字符串),其在C语言中的sds.h文件中结构如下:

struct sdshdr {
    long len;
    long free;
    char buf[];
};

参数说明:

  • len:保存了buf字段的长度,这使得读取字符串长度的操作花费为O(1);
  • free:保存了可供使用的额外字节数;
  • buf:字符串实际存储的字段。

len和free字段可以看作是保存buf字符数组的元数据。

四、Redis持久化

1.RDB快照(Snapshot)

在默认情况下, Redis 将内存数据库快照保存在名字为dump.rdb的二进制文件中。可以对 Redis 进行设置, 让它在N秒内数据集至少有M个改动这一条件被满足时, 自动保存一次数据集。如果redis.conf文件里面有默认的多种情况,多种是或的关系

示例如下:

save N M

2.AOF(append-only file)

快照功能并不是非常耐久(durable):如果 Redis因为某些原因而造成故障停机,那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。从 1.1 版本开始,Redis增加了一种完全耐久的持久化方式:AOF持久化,将修改的每一条指令记录进文件。

可以配置Redis多久才将数据fsync到磁盘一次。共有三种选项:

  1. 每次有新命令追加到AOF文件时就执行一次fsync:非常慢,也非常安全;
  2. 每秒fsync一次:足够快(和使用RDB持久化差不多),并且在故障时只会丢失1秒钟的数据;
  3. 从不fsync:将数据交给操作系统来处理。更快,也更不安全的选择。

3.混合持久化

重启Redis时,我们很少使用RDB来恢复内存状态,因为会丢失大量数据。我们通常使用AOF日志重放,但是重放AOF日志性能相对RDB来说要慢很多,这样在Redis实例很大的情况下,启动需要花费很长的时间。Redis4.0为了解决这个问题,带来了一个新的持久化选项——混合持久化。

AOF在重写(AOF文件里可能有太多没用指令,所以AOF会定期根据内存的最新数据生成AOF文件)时将重写这一刻之前的内存rdb快照文件的内容和增量的AOF修改内存数据的命令日志文件存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换。

AOF根据配置规则在后台自动重写,也可以人为执行命令bgrewriteaof重写AOF。 于是在Redis重启的时候,可以先加载RDB的内容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放,重启效率因此大幅得到提升。

五、IO多路复用

“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。多路I/O复用模型是利用select、poll和epoll。

 

原理:使用一个线程来管理多个IO流,在IO流为空,线程空闲的时候会阻塞,当有一个或多个IO流进来时就会从阻塞状态中唤醒,轮训一遍所有的IO流,并且依次处理。这种处理方法在很多地方都被用到了,如Tomcat接收用户请求就是使用了IO多路复用,使用Poller轮询器来轮询是否有请求事件,如果有则进行相应的处理。

保证redis性能很快的三个点:

  1. 单线程:避免了锁和线程之间的互相竞争,减少线程切换的消耗;
  2. 多路复用:因为内存读取很快,因此使用单个线程管理各个IO流不会造成IO阻塞;
  3. 读取内存:数据存在内存中,读取速度是各种读取形式最快的一种。

六、使用时碰到的问题及解决方案

1.前言

为什么会使用到Redis?当并发请求量非常大时,单一使用数据库来保存查询数据的系统会因为磁盘读/写速度较慢的问题而导致严重的性能问题,严重的经常会造成数据库系统瘫痪导致服务器宕机的问题。因此通常会使用NoSQL技术在数据库前面当一层壁垒,把内存当成数据库,保证访问同样的数据会优先访问内存中的数据,而不会直接访问到数据库。从而做到帮助数据库减轻压力。

2.将会碰到的问题

  1. 缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库;
  2. 缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮;
  3. 缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。

3.解决方案

3.1 缓存穿透

一个一定在缓存查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

既然本质是redis中没有这个key缓存,需要解决的便是在获取key为空之后再进行一层逻辑判断即可。方法有很多其中一种简单的方法便是类似防重放的操作,把这个key再缓存到redis中,同时存在固定的时间或者设置一段时间内固定可访问多少次,达到次数后便直接抛异常。

3.2 缓存击穿

单个key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。

针对这种情况类似降级和再恢复的策略来防止,大致的操作便是这一段时间内允许部分的请求请求到后台查询数据库(需要评估多少的访问量系统可以轻松承受得住),而大部分使用互斥锁暂时拦截或抛异常,在从数据库查出数据后设置key的值为有效后再允许访问。

3.3 缓存雪崩

与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。

缓存失效时的雪崩效应对底层系统的冲击非常可怕!大多数系统设计者考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。

其问题的本质便是突然一大批key失效导致很多查询蜂拥而至,最终压垮数据库。因此要解决这个问题最简单的方法便是在设置key的有效期时尽量随机到各个时间片上。或者还有另外一个做法便是定义一个线程,这个线程用来隔一段时间监听各个key的有效期,当这个key在某个时间临界点要失效时便根据key手动刷新,也能防止缓存雪崩。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值