因为一次redis缓存穿透,全面探究redis能做什么

问题背景:

网关支付,支付之前需要根据商户去获取支持的银行列表,银行列表是商家首次在我们系统里申请开户的时候配置进去的,第一次获取列表是通过调远程接口查询数据库获取的,获取成功后,结果保存在redis缓存中,有效期设置6小时,以后每次查询都直接访问redis缓存即可。


出现问题是有一批新商户没有配置银行列表,发起支付获取银行列表时,首次调用接口获取结果失败,没有放置缓存;商户反复提交了4次,每次访问缓存不存在,然后远程调用接口服务,接口返回同样不存在的错误,导致第5次远程调用返回500错误。

这是典型的缓存穿透问题。缓存穿透是指用户不断发起请求缓存和数据库中都没有的数据,这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决办法:

从缓存和数据库中都取不到的值,这时可以将key-value键值对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样至少30秒内访问redis能获取到空值,可以防止用户反复用同一个id暴力攻击数据库。

缓存除了穿透问题还有击穿和雪崩问题。

1)、缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读一个热点key,而这个key缓存没读到数据,又同时去数据库取数据,引起数据库压力瞬间增大。

最好的例子就是爆款微博热搜,大家同时去访问,造成的微博瘫痪。

解决办法:

  1. 设置热点数据永远不过期。
  2. 加互斥锁,用到redis的分布式锁,即setnx方法,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的情况下,只需要阻塞等待即可。

2)、缓存雪崩是指缓存中数据大批量到了过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

举例比如双11,所有商品都放入缓存,设置过期时间为1小时,到了凌晨1点,商品信息集体失效,此刻所有的查询会全部落到数据库上。

解决方案:

  1. 数据预热,在即将发生大并发访问之前,手动触发加载缓存不同的key,过期时间随机设置,让缓存失效时间点尽量均匀,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
  3. 设置热点数据永远不过期。

至此,步入正题,全面了解一下redis都可以做什么事?

redis的中文网站(http://www.redis.cn/对redis)的介绍如下:

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

和Memcached类似,它支持存储的value类型相对更多,这些类型大多数支持add/remove、list支持push/pop、set支持取交集并集和差集,而且这些操作都是原子性的。在此基础上,zset支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据持久化,并且在此基础上实现了master-slave(主从)同步。

由此可以总结,redis有以下几特性:

  • 内存存储,支持更丰富的数据类型
  • Zset数据结构可以用来做排行榜,Zset是一种排序的set集合,可以快速定位到top N
  • 利用set集合的一些命令,可以获取好友关系,比如求交集、并集、差集等。
  • 支持持久化(rdb、aof):RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储;AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
  • 发布订阅,可以用作队列,list的push和pop可以实现简单的队列或栈结构。比如lpush从左边插入,lpop从左边取,就是后进先出的一种栈结构;如果lpush进入,rpop出去,又是队列结构。

因为一次redis缓存穿透,全面探究redis能做什么

因为一次redis缓存穿透,全面探究redis能做什么

  • 地图分析geospatial

GEOADD添加经纬度

GEODIST计算两个城市间距离,可以指定单位千米或者英尺

redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> 。 Sicily Palermo Catania
"166274.15156960039"
redis> GEODIST Sicily Palermo Catania km
"166.27415156960038"
redis> GEODIST Sicily Palermo Catania mi
"103.31822459492736"
redis> GEODIST Sicily Foo Bar
(nil)
redis>
  • 计时器、计算器/限速器,利用Redis中原子性的自增操作,我们可以统计类似用户点赞数、用户访问数等,这类操作如果用MySQL,频繁的读写会带来相当大的压力
  • 主从复制-哨兵模式
  • 分布式锁
/**
* 加锁
* @param key redis key
* @param expire 过期时间,单位秒
* @return true:加锁成功,false,加锁失败
*/
public boolean lock(String key, int expire) {
byte [] byteKey = fst.serialize(DEFAULT_CACHE_NAME+ "_" +key);
byte [] byteVal = fst.serialize("1");
long status = jedisCluster.setnx(byteKey, byteVal);//1.setnx,成功返回1失败返回0

if(status == 1) {//2.返回结果为1,设置超时时间,加锁成功
jedisCluster.expire(byteKey, expire);
return true;
}
//3.返回结果为0,加锁失败
return false;
}

Redis为什么能做这些

上面说了Redis的一些使用场景,那么这些场景的解决方案也有很多其它选择,比如缓存可以用Memcache,Session共享还能用MySql来实现,消息队列可以用RabbitMQ,我们为什么一定要用Redis呢?

那是因为Redis执行速度快:

速度快,完全基于内存;

使用C语言实现,网络层使用epoll解决高并发问题;

单线程模型避免了不必要的上下文切换及竞争条件;

注意:单线程仅仅是说在网络请求这一模块上用一个请求处理客户端的请求,像持久化它就会重开一个线程/进程去进行处理。

#Redis#

您可能还会对下面的文章感兴趣: