什么是Redis?
Redis是一个使用C语言编写的、开源的、高性能的、非关系的、键值对数据库。
Redis中可以存储 键 和五种数据类型的值,其中键只能是字符串、值可以是字符串、列表、哈希、集合、有序集合。
Redis与传统的数据库不同的点是它存储在内存中,所以读写速度非常快,可以达到每秒处理10万次读/写的操作,是一只性能最快的key-value数据库,因此它经常被来用作缓存。同时它也被做来当分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
Redis的优缺点
优点
- 读写性能优异,可以达到每秒10万次读写操作。
- 支持数据持久化,支持AOF和RDB两种方式。
- 支持事务。
- 操作原子性。
- 有丰富的数据类型
- 支持主从、集群。
缺点
- 受到内存容量的限制,不能用作大数据的读写操作。
- 比较难在线扩容。
为什么使用缓存
现在的WEB系统的读写比是1:9或者3:7,所以读的次数是大于写的次数。因此如果我们将数据放置在内存中,就能够让服务端直接去内存中读取数据,提高系统的响应速度(高性能),并且能够减轻数据库的负担(高并发)。
使用Redis做缓存时需要注意什么
由于内存的容量限制问题或者说是成本的问题,我们使用Redis存储数据时一般只存储主要的数据,在存储数据时我们要考虑几个方面。
- 该业务数据经常使用嘛?命中率如何?
- 该业务数据是读操作多吗?写操作多吗?
- 业务数据的大小如何?
使用缓存会带来什么问题?
缓存雪崩
缓存同一时间大量失效,因此请求访问都访问去数据库中,大量的请求到数据库上,会导致数据库被打死因而宕机。
我们可以通过设置随机数,使得缓存不在同一时间失效。
缓存穿透
用户大量查询不在缓存中的数据,因此请求都发送到数据库中,如果请求多了可能会将数据库打死因而宕机。
我们可以通过布隆过滤器来过滤拦截,或者将查询的对象也存储在缓存中并设置较短的时间。
缓存与数据库双写一致性的问题
对于更新操作来说、可以分为两部分:操作缓存和操作数据库。根据先后顺序又分为两种操作:
- 先操作缓存、再操作数据库。
- 先操作数据库、再操作缓存。
但无论是那种,这都是两步操作,他不能保证原子性,因此可能会出现一步操作成功、一步操作失败的情况。
如果第一步已经失败了,我们直接返回Exception出去就好了,第二步根本不会执行。
操作缓存
操作缓存可以分为两种:更新缓存、删除缓存。但在一般情况下我们都选择删除缓存,因为如果再频繁更新数据的情况下,我们选择更新缓存的话,就会降低效率同时命中率也会很低,另外我们只在需要的时候再加载进缓存也可以减少内存占用(懒加载的思想)。
先更新数据库、再删除缓存
最理想的情况下:
- 更新数据库成功。
- 删除缓存成功。
不理想的情况下:
更新数据库成功。
删除缓存失败
更新数据库失败。
关于第一种不理想的情况,我们可以将要删除的key放入消息队列中,自己消费,不断重试,直到操作成功。
关于第二中不理想的情况,我们直接在应用层抛出异常就OK了。
先删除缓存,再更新数据库
最理想的情况下:
- 删除缓存成功。
- 更新数据库成功。
不理想的情况下:
删除缓存成功。
更新数据库失败。
删除缓存失败。
关于两种不理想的情况下,大多数情况下缓存和数据库的一致性都是一样的。但还是会出现不一致的情况例如下面:
- 线程A删除了缓存
- 线程B查询,发现缓存已不存在
- 线程B去数据库查询得到旧值
- 线程B将旧值写入缓存
- 线程A将新值写入数据库
解决方法是:通过队列串行化,将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。
总结
先删除缓存,再更新数据库
- 在高并发下表现不如意,在原子性被破坏时表现优异
先更新数据库,再删除缓存(
Cache Aside Pattern设计模式)
- 在高并发下表现优异,在原子性被破坏时表现不如意
为什么Redis早期版本选择使用单线程
根据官方的解释,由于Redis是基于内存的操作,因此CPU不是Redis的瓶颈,Redis的瓶颈最有可能是内存的大小容量或者网络带宽。既然单线程容易实现而且不会导致性能问题,Redis开发人员就选择采用单线程方案了。
- 使用单线程模型能带来更好的 可维护性,方便开发和调试;
- 使用单线程模型也能 并发 的处理客户端的请求;(I/O 多路复用机制)
- Redis 服务中运行的绝大多数操作的 性能瓶颈都不是 CPU;
Redis为什么那么快
- 纯内存操作。
- 单线程,无锁竞争。
- 多路I/O复用技术,非堵塞IO。
- 高效的数据结构,底层有大量的优化。