1、什么是Redis?redis
Redis 是一个高性能的开源的、C语言写的Nosql(非关系型数据库),数据保存在内存中。Redis 是以key-value形式存储。sql
2、Redis的优点?
typescript
1.数据保存在内存,存取速度快,并发能力强
2.它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、 zset(sorted set --有序集合)和hash(哈希类型)。
3.redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合能够对关系数据库(如MySQL)起到很好的补充做用。
4.它提供了Java,C/C++,C#,PHP,JavaScript等客户端,使用很方便。
5.Redis支持集群(主从同步、负载均衡)。数据能够主服务器向任意数量从的从服务器上同步,从服务器能够是关联其余从服务器的主服务器。
6.支持持久化,能够将数据保存在硬盘的文件中
7.支持订阅/发布(subscribe/publish)功能 数据库
3、redis基本数据类型后端
String(字符串):是redis最基本的类型.缓存
示例:set name value 取值:get name 注:(键最大能存储512MB)服务器
Hash(哈希):是一个string类型的field和value的映射表.微信
示例:HMSET name key value 取值:HGET name key并发
List(列表):是简单的字符串列表,按照插入顺序排序。app
你能够添加一个元素到列表的头部(左边)或者尾部(右边)
示例:LPUSH name value
Set(集合):是string类型的无序集合
示例:SADD name value
zset(有序集合):
示例:ZADD name 下标 value
4、为何使用redis,何时使用?
把常常查询的数据,不多修改的数据存放到缓存中,减小访问数据库,下降数据库压力而且缓存通常都是内存,访问速度比较快。
不须要实时更新可是又极其消耗数据库的数据。
须要实时更新,可是更新频率不高的数据。
在某个时刻访问量极大并且更新也很频繁的数据。
典型场景:用户消息,评论,订单列表、地区数据、商品分类、数据字典 菜单等常常查询或者不多修改的数据
5、Redis持久化
Redis 提供了两种不一样级别的持久化方式:RDB和AOF,能够经过修改redis.conf来进行配置。
1、RDB(快照)持久化:保存某个时间点的全量数据快照。
触发机制:
A.手动触发分别对应save和bgsave命令
·save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会形成长时间阻塞,线上环境不建议使用
·bgsave命令:Redis进程执行fork操做建立子进程,RDB持久化过程由子 进程负责,完成后自动结束。阻塞只发生在fork阶段,通常时间很短
B.自动触发:使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改 时,自动触发bgsave。
优势
(1)RDB文件紧凑,全量备份,很是适合用于进行备份和灾难恢复。
(2)生成RDB文件的时候,redis主进程会fork()一个子进程来处理全部保存工做,主进程不须要进行任何磁盘IO操做。
(3)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
缺点
RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上很是紧凑。Redis版本演进过程当中有多个格式 的RDB版本,存在老版本Redis服务没法兼容新版RDB格式的问题。
2、AOF(Append -Only - File)持久化:日志追加模式。默认是关闭的。
以独立日志的方式记录每次写命令, 重启时再从新执行AOF文件中的命令达到恢复数据的目的。AOF的主要做用是解决了数据持久化的实时性
开启AOF功能须要设置配置:appendonly yes,默认不开启。AOF文件名 经过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同 RDB持久化方式一致,经过dir配置指定。
AOF的工做流程操做:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load)
优势
(1)AOF能够更好的保护数据不丢失,通常AOF会每隔1秒,经过一个后台线程执行一次fsync操做,最多丢失1秒钟的数据。
(2)AOF日志文件没有任何磁盘寻址的开销,写入性能很是高,文件不容易破损。
(3)AOF日志文件即便过大的时候,出现后台重写操做,也不会影响客户端的读写。
(4)AOF日志文件的命令经过很是可读的方式进行记录,这个特性很是适合作灾难性的误删除的紧急恢复。好比某人不当心用flushall命令清空了全部数据,只要这个时候后台rewrite尚未发生,那么就能够当即拷贝AOF文件,将最后一条flushall命令给删了,而后再将该AOF文件放回去,就能够经过恢复机制,自动恢复全部数据
缺点
(1)对于同一份数据来讲,AOF日志文件一般比RDB数据快照文件更大
(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,由于AOF通常会配置成每秒fsync一第二天志文件,固然,每秒一次fsync,性能也仍是很高的
(3)之前AOF发生过bug,就是经过AOF记录的日志,进行数据恢复的时候,没有恢复如出一辙的数据出来。
3、RDB-AOF 混合持久化方式:BGSAVE 全量持久化,AOF作增量持久化
RDB与AOF区别
根据本身须要选择合适的持久化
6、Redis常见的问题及解决方案
1.缓存穿透
key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。好比一个不存在的用户id获取用户信息,不论缓存仍是数据库都没有,若黑客利用此漏洞进行攻击就可能压垮数据库,致使系统崩溃。
解决方案:
(1)采用布隆过滤器,将全部可能存在的数据哈希到一个足够大的bitmap中,一个必定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
(2)另外也有一个更为简单粗暴的方法,若是一个查询返回的数据为空(不论是数据不存在,仍是系统故障),咱们仍然把这个空结果进行缓存,但它的过时时间会很短,最长不超过五分钟。
public object GetRedis() { int cacheTime = 10; String cacheKey = "key";
String cacheValue = CacheHelper.Get(cacheKey); if (cacheValue != null) { return cacheValue; }
cacheValue = CacheHelper.Get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //数据库查询不到,为空 cacheValue = GetProductListFromDB(); if (cacheValue == null) { //若是发现为空,设置个默认值,也缓存起来 cacheValue = string.Empty; } CacheHelper.Add(cacheKey, cacheValue, cacheTime); return cacheValue; }}
2.缓存击穿
key对应的数据存在,但在redis中过时,此时如有大量并发请求过来,这些请求发现缓存过时通常都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:
使用互斥锁(mutex key)
比较经常使用的作法,是使用mutex。在缓存失效的时候(判断拿出来的值为空),不是当即去load db,而是先使用缓存工具的某些带成功操做返回值的操做去set一个mutex key,当操做返回成功时,再进行load db的操做并回设缓存;不然,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,能够利用它来实现锁的效果。
public String get(key) { String value = redis.get(key); if (value == null) { //表明缓存值过时 //设置3min的超时,防止del操做失败的时候,下次缓存过时一直不能load db if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //表明设置成功 value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex); } else { //这个时候表明同时候的其余线程已经load db并回设到缓存了,这时候重试获取缓存值便可 sleep(50); get(key); //重试 } } else { return value; } }
3.缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(好比DB)带来很大压力。
解决方案:
时间岔开,确保你们的key不会落在同一个expire点上。
使用锁或队列、设置过时标志更新缓存、为key设置不一样的缓存失效时间,还有一种被称为“二级缓存”的解决方法。
考虑用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开,好比咱们能够在原有的失效时间基础上增长一个随机值,好比1-5分钟随机,这样每个缓存的过时时间的重复率就会下降,就很难引起集体失效的事件。
加锁排队//伪代码public object GetRedis() { int cacheTime = 30; String cacheKey = "key"; String lockKey = cacheKey;
String cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { synchronized(lockKey) { cacheValue = CacheHelper.get(cacheKey); if (cacheValue != null) { return cacheValue; } else { //这里通常是sql查询数据 cacheValue = GetProductListFromDB(); CacheHelper.Add(cacheKey, cacheValue, cacheTime); } } return cacheValue; }}
加锁排队的解决方式分布式环境的并发问题,有可能还要解决分布式锁的问题;线程还会被阻塞,用户体验不好!
随机值伪代码://伪代码public object GetRedis() { int cacheTime = 30; String cacheKey = "key"; //缓存标记 String cacheSign = cacheKey + "_sign";
String sign = CacheHelper.Get(cacheSign); //获取缓存值 String cacheValue = CacheHelper.Get(cacheKey); if (sign != null) { return cacheValue; //未过时,直接返回 } else { CacheHelper.Add(cacheSign, "1", cacheTime); ThreadPool.QueueUserWorkItem((arg) -> { //这里通常是 sql查询数据 cacheValue = GetProductListFromDB(); //日期设缓存时间的2倍,用于脏读 CacheHelper.Add(cacheKey, cacheValue, cacheTime * 2); }); return cacheValue; }}
本文分享自微信公众号 - JAVA技术摘要(lei_culture)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。