一、Redis概述
1.1 什么是Redis?
redis是一个高性能的、开源的key-value数据库
,而且redis是一个NOSQL类型数据库(非关系型的数据库)
,是为解决高并发、高扩展,大数据存储
等一系列的问题而产生的数据库解决方案,它还可以用作:数据库、缓存和消息中间件
。但是,它也不能替代关系型数据库,只能作为特定环境下的扩充。
redis是一个以key-value存储的数据库结构型服务器,它支持的数据结构类型包括:字符串(String)、链表(lists)、哈希表(hash)、集合(set)、有序集合(Zset)等,也被人们称为数据结构服务器
。为了保证读取的效率,redis把数据对象都存储在内存当中
,它可以支持周期性的把更新的数据写入磁盘文件中 。 而且它还提供了交集和并集,以及一些不同方式排序的操作。
1.2 为什么要使用Redis?
平常做Web小项目时都是直接使用mysql等数据库,在磁盘上进行数据的存取,由于一般的系统任务中通常不会存在高并发的情况,所以没什么问题,但是一旦涉及大数据量的需求,比如商品抢购使得主页访问量瞬间极大的时候,如果仅仅使用数据库来保存数据,会因为磁盘读/写速度太慢而造成数据库系统瘫痪。
项目中使用Redis的目的,主要从性能和并发两个角度去考虑的:
- 性能:在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存,这样,后面的请求就去缓存中读取,请求使得能够迅速响应。
- 并发:在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用的的Redis的做一个缓冲操作,让请求先访问到的Redis的的,而不是直接访问数据库。
Redis的优势和特点
- redis数据读写速度非常快,Redis能读的速度是110000次/s,写的速度是81000次/s,因为它把数据都读取到
内存
当中操作,而且redis是用C语言编写
的,是最“接近“”操作系统的语言,所以执行速度相对较快。 - redis虽然数据的读取都存在内存当中,但是最终它支持数据持久化到磁盘当中,即可以将内存中的数据异步写入到硬盘中,同时不影响继续提供服务。
- redis提供了丰富的数据结构。
- redis的所有操作都是
原子性
,支持事务,所谓的 原子性就是对数据的更改要么全部执行,要么全部不执行。 - redis支持
主从复制
,主机会自动将数据同步到从机,可以进行读写分离
。
1.3 NoSQL 技术
当任务存在高并发涉及大数据量的需求
,比如天猫双11、抢红包、抢演唱会门票时候,单一使用数据库来保存数据的系统会因为面向磁盘,磁盘读/写速度比较慢的问题而存在严重的性能弊端,一瞬间成千上万的请求到来,需要系统在极短的时间内完成成千上万次的读/写操作,这个时候往往不是数据库能够承受的,极其容易造成数据库系统瘫痪,最终导致服务宕机
的严重生产问题。
为了克服上述的问题,项目通常会引入NoSQL技术
,这是一种基于内存的数据库
,并且提供一定的持久化功能
。Redis
和MongoDB
是当前使用最广泛的NoSQL,而就Redis技术而言,它的性能十分优越,可以支持每秒十几万此的读/写操作。
- MySQL(关系型数据库):需要把数据存储到库、表、行、字段里,查询的时候根据条件一行一行地去匹配,当查询量非常大的时候就很耗费时间和资源,尤其是数据是需要从磁盘里去检索。
- NoSQL(非关系型的数据库):
存储原理非常简单(典型的数据类型为k-v),不存在繁杂的关系链,不需要像mysql那样需要找到对应的库、表(通常是多个表)以及字段。
NoSQL数据可以存储在内存里,查询速度非常快。
NoSQL在性能表现上虽然能优于关系型数据库,但是它并不能完全替代关系型数据库。
NoSQL因为没有复杂的数据结构,扩展非常容易,支持分布式
1.4 Redis的应用
- 存储 缓存 用的数据;
- 需要高速读/写的场合使用它快速读/写
1、缓存
在日常对数据库的访问中,读操作的次数远超写操作。当我们使用SQL语句去数据库进行读写操作时,数据库就会去磁盘把对应的数据索引取回来,这是一个相对较慢的过程。
如果我们把数据放在 Redis
中,也就是直接放在内存
之中,让服务端直接去读取内存中的数据,那么这样速度明显就会快上不少,并且会极大减小数据库的压力,但是使用内存进行数据存储开销也是很大的,限于成本的原因,一般我们只是使用 Redis 存储一些常用和主要的数据,比如用户登录的信息、热点新闻、门户主页面等。
一般而言,在使用 Redis 进行存储的时候,我们从以下几个方面来考虑:
- 数据常用吗?命中率如何? 如果数据不常用,缓存命中率很低,就没有必要写入缓存;
- 是读操作多,还是写操作多? 如果写操作多,频繁需要写入数据库,也没有必要使用缓存;
- 数据大小如何? 如果要存储几百兆字节的文件,会给缓存带来很大的压力,这样也没有必要;
2、 Redis 读操作逻辑
- 当第一次读取数据的时候,读取 Redis 的数据就会失败,此时就会触发程序读取数据库,把数据读取出来,并且写入 Redis 中;
- 当第二次以及以后需要读取数据时,就会直接读取 Redis,读到数据后就结束了流程,这样速度就大大提高了。
3、Redis 写操作逻辑
可见写操作不仅要写入数据库,还要同时写入Redis,因此如果写次数远大于读次数那么就没有必要使用 Redis。
4、高速读/写(只使用Redis,事后再更新数据库)
高并发的情况为了追求极致的速度,只读写Redis,等高并发请求结束后再将Redis上的数据同步到数据库中,此时,一次请求操作的流程图
如下:
- 当一个请求到达服务器时,
只把业务数据在 Redis上进行读写,而没有对数据库进行任何的操作
,这样就能大大提高读写的速度,从而满足高速响应的需求
; - 但是这些缓存的数据仍然需要持久化,也就是存入数据库之中,所以在一个请求操作完 Redis 的读/写之后,会去
判断该高速读/写的业务是否结束
,这个判断通常会在秒杀商品为0,红包金额为0时成立,如果不成立,则不会操作数据库;如果成立,则触发事件将Redis 的缓存的数据以批量的形式一次性写入数据库
,从而完成持久化的工作。
1.5 缓存穿透、缓存击穿、缓存雪崩
1、缓存处理流程
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
2、缓存穿透
缓存穿透:缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,流量大时数据库会挂掉。
解决方案:
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存和数据库中没有取到,这时也可以将key-value对写为key-null(空值),缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
3、缓存击穿
缓存击穿:一个存在的key,在缓存过期的一刻,此时有大量的请求,同时读缓存没读到数据,又同时去数据库去取数据,这些请求就击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案:
- 设置热点数据永远不过期。
- 加互斥锁
4、缓存雪崩
缓存雪崩:缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机
解决方案:
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
- 设置热点数据永远不过期.
1.6 Redis读写使用单线程+多路I/O复用模型
原因:
- redis是基于内存的,内存的读写速度非常快(纯内存)。
- redis是
单线程
的,解决了数据存储的顽疾数据并发安全
,还省去了很多上下文切换线程
的时间(避免线程切换和竞态消耗)。 - redis使用
多路复用技术
,可以处理并发的连接(非阻塞IO)。
在mysql等数据库技术中需要进行磁盘I/O,等待I/O时间很长,应该把CPU让给其他线程进行存取,所以用多线程存取效率更高。而redis不需要磁盘I/O,性能瓶颈不在CPU而在内存大小和网络带宽上,因此用单线程+多路复用技术足够了。
1、并发安全
多个并发体在同一段时间内访问同一个共享数据,共享数据能被正确处理。
并发不安全的后果:
举个例子:卖票超售,设想有一家电影院,有两个售票窗口,售票员售票时候先看一下当前剩余票数是否大于0,如果大于0则售出票。此时票数剩下一张票,两个售票窗口同时来了顾客,两个售票人都看了一下剩余票数还有一张,不约而同地收下顾客的钱,余票还剩一张,但是却售出了两张票,就会出现致命的问题。
如何做到并发安全
最主流的办法就是 加锁,其实售票的整个操作同时间内只能一个人进行,在我看来归根到底加锁其实就是让查询和售票两个步骤原子化,只能一块执行,不能被其他程序中断
,让这步操作变成串行化
2、锁
锁:就是每次进入这段变量共享的程序片段,都要先获取一下锁,如果获取成功则可以继续执行,如果获取失败则阻塞,直到其他并发体把锁给释放,程序得到执行调度才可以执行下去。锁本质:就是让并发体创建一个程序临界区,临界区一次只能进去一个并发体。
读锁与写锁
读锁
也叫共享锁,写锁
也叫排它锁,锁的概念被发明了之后,人们就想着如果我很多个并发体大部分时间都是读,如果变量读取的时候也要建立临界区,那就大题小做了。于是人们发明了读锁,一个临界区如果加上了读锁,其他并发体执行到相同的临界区都可以加上读锁,执行下去,但不能加上写锁。这样就保证了可以多个并发体并发读取而又不会互相干扰。
3、切换上下文
线程每次执行需要把数据从主内存读到工作内存,然而当线程被调度到阻塞的时候,这些工作内存的数据需要被快照到线程上下文中,其实就是一个记录各个线程状态的存储结构,等到线程被唤醒的时候,再从上下文中读取,称之为上下文切换;减少上下文切换操作,也是使用单线程的奥妙
4、多路 I/O 复用
当一个请求来访问redis后,redis去组织数据要返回给请求,这个时间段,redis的请求入口不是阻塞的,其他请求可以继续向redis发送请求,等到redis io流完成后,再向调用者返回数据,这样一来,单线程也不怕会影响速度了
- 多路:指的是多个网络连接
- 复用:指的是复用同一个线程
1.7 Redis 的过期策略和内存淘汰机制
1、过期策略
Redis
采用的是 定期删除+惰性删除 策略,删除过期key。
定期删除,指redis默认
是每隔100ms
就随机抽取
一些设置了过期时间的key
,检查其是否过期,如果过期就删除。
但是,定期删除可能会导致很多过期key到了时间并没有被删除掉,所以就得靠惰性删除了。
惰性删除,key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?
大量过期key堆积在内存里,导致redis内存块耗尽,此时我们就需要内存淘汰机制了。
2、内存淘汰机制
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
noeviction:禁止驱逐数据,当内存使用达到阈值的时候,所有引起申请内存的命令会报错
设置方式
config set maxmemory-policy volatile-lru
1.8 Redis实现的技术
- 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
- 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
- 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷是写操作无法负载均衡;存储能力受到单机的限制。
- 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
二、 Mysql和Redis数据库的区别
mysql
:关系型数据库,主要用于存放持久化数据,将数据存储在硬盘中,读取速度较慢。
redis
:非关系型数据库(NoSQL),也是缓存数据库,即将数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是保存时间有限
- 类型上:
mysql是关系型数据库,redis是缓存数据库 - 作用上:
mysql用于持久化的存储数据到硬盘,功能强大,但是速度较慢
redis用于存储使用较为频繁的数据到缓存中,读取速度快 - 速度上:
redis的速度:单机读可达10000次/s 写可达5000/s
mysql 经过了这么多年优化 才1000次/S,500次/S - 需求上:
mysql和redis因为需求的不同,一般都是配合使用。
三、Memcached和Redis对比
对比内容 | 具体阐述 |
---|---|
性能 | 平均每一个核Redis在存储小数据时比Mencached性能更高,100k以上时Mencached性能高于redis |
内存空间和数据量大小 | Memcached可以修改最大内存,采用LRU算法,Redis增加了VM的特性,突破了物理内存的限制 |
操作方便性 | Memcached结构数据单一,仅用来缓存数据,而Redis支持更丰富的数据类型,也可以在服务器端进行丰富的操作,这样可以减少网络IO次数和数据体积 |
可靠性 | Memcached不支持数据持久化,断电或重启后数据消失,但其稳定性是有保证的,Redis支持数据持久化和数据恢复,允许单点故障,但是同时也会付出性能代价 |
应用场景 | Memcached:动态系统中减轻数据库的负担,提升性能,作缓存,适合多读少写,大数据量的情况。Redis:适合于对读写效率要求都很高,数据处理业务复杂和对安全性要求较高的系统 |
单个key-value的大小 | Memcached最大支持1MB。Redis最大支持512MB |
- redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。
- Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。
- 集群模式:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的。
- Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型.