为什么分布式一定要有Redis

一 、为什么使用Redis

我觉得在项目中使用Redis,主要是从两个角度去思考:性能和并发。

当然,Redis还诀别可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件,如Zookpeer等代替,并不是非要使用Redis。因此,这个问题主要从性能和并发两个角度去答。

性能

我们在碰到需要执行时特别久,且结果不频繁变动的sql,就特备适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。

曾经有人这么告诉我

在理想的状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。另外超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验。

根据《摩诃。。。》记载:

一刹那为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜未三十须臾。

那么经过周密计算,一瞬间未0.36秒、一刹那为0.018秒、一弹指长达7.2秒

并发

在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候就需要使用Redis做一个缓冲操作,让请求先访问到Redis,而不是直接访问数据库。

二、使用Redis有什么缺点

缓存和数据库双写一致性问题

缓存雪崩问题

缓存击穿问题

缓存的并发竞争问题

三、单线程的Redis为什么这么快

纯内存操作 

单线程操作避免了频繁的上下文切换

采用了非阻塞I/O多路复用机制

非阻塞I/O多路复用机制:

简单来说,就是我们的Redis-client 在操作的时候,会产生具有不同事件类型的Socket。在服务端,有一段I/O多路复用程序,将其置入队列之中,然后,文件事件分派器,依次去队列中取,转发到不同的事件处理中。

需要说明的是,这个I/O多路复用机制,Redis还提供了 select epoll evport kqueue 等多路复用函数库,大家可以自行去了解。

四、Redis的数据类型,以及每种数据类型的使用场景

(1)String  

最常规的get set 操作,value可以是String 也可以是数字。一般做一些复杂的计数功能的缓存。

(2)Hash

这里value 存放的是结构化的对象,比较方便的就是操作其中的某个字段。

我在做单点登录的时候,就是用这种数据结构存储用户信息,以CookieID作为Key,设置30分钟为缓存过期时间,能很好的模拟出类似Session的效果。

(3)List

使用List的数据结构,可以做简单的消息队列的功能,另外还有一个就是,可以利用lrang命令,做基于Redis的分页功能,性能极佳,用户体验好。

(4)Set

因为Set堆放的是一堆不重复值得集合,所以可以做全局去重得功能。为什么不用JVM自带得Set,比较麻烦,难道为了一个做全局去重,再起一个公共服务,太麻烦了。

另外,就是利用交集、并集,差集等操作,可以计算共同喜好,全部得喜好,自己独有得喜好等功能。

(5)Sorted Set

Sorted Set 多了一个权重参数 Score,集合中得元素能够按照Score进行排列。

可以做排行榜得应用,取TOP N操作。Sorted Set 可以用来做延时任务,最后一个应用就是可以做范围查找。

五、Redis 的过期策略以及内存淘汰机制

Redis 采用的是定期删除+惰性删除策略。

(1)为什么不用定时删除策略

定时删除,用一个定时器来负责监视Key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。

在大并发的请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略。

(2)定期删除+惰性删除 是如何工作

定期删除,Redis默认每个100ms检查,是否有过期的key,有过期的key则删除。

需要说明的是,Redis不是每个100ms将所有key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,Redis岂不是卡死)。

因此,如果只采用定期删除策略,会导致很多key到时间没有删除。于是,惰性删除派上用场。也就是说在你获取某个key的时候,Redis会检查一下,这个key如果设置了过期时间,那么是否过期了?如果过期了此时就会删除。

(3)采用定期删除+惰性删除就没有其他问题了么?

不是的,如果定期删除没删除的key。然后你也没即时去请求key。也就是说惰性删除也没生效。这样,Redis的内存会越来越高。那么就应该采用内存淘汰机制。

在redis.conf中有一行配置:

# maxmemory-policy volatile-lru

该配置就是配内粗花呢淘汰策略的(什么,你没配过?好好反省一下自己):

*noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧

*allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。

*allkeys-random:当内存不足以容纳新写入的数据时,在键空间中,随机溢出某个key。应该没人用把,你不删最少使用的key,去随机删

*volatile-lru:当内存不足以容纳新写入的数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把Redis既当缓存,又做持久化存储的时候采用。不推荐

*volatile-random:当内存不足以容纳新写入的数据时,在设置了过期时间的键空间中,随机移除某个key.依然不推荐

*volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除,不推荐

ps:如果没有设置expire的key,不满足先决条件(prerequisetes);那么volatile-lru,volatile-random,volatile-ttl策略的行为,和noeviction(不删除)基本上一致。

六、Redis和数据库双写一致性问题

一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。

答这个问题,先明白一个前提。就是如果对数据有强一致性要求,布恩那个放缓存。我们所做的一切,只能保证最终一致性。

另外我们所做的方案从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据不能放缓存。

答:首先,采取正确跟新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

七、如何应对缓存穿透和缓存雪崩问题

这两个问题说句实在话,一般中小型传统软件企业,很难碰到这个问题。如果有大并发的项目,流量有几百万左右。这两个问题一定要深刻考虑。

(1)缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

缓存穿透解决方案

*利用互斥锁,缓存失效的时候,先去获得锁,得到锁了再去请求数据库。没得到锁,则休眠一段时间再试。

*采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。

*提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的key是否合法有效,如果不合法直接返回。

(2)缓存雪崩 即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

缓存雪崩解决方案:

*给缓存的失效时间,加上一个随机值,避免集体失效。

*使用互斥锁,但是该方案脱兔两明显下降了。

*双缓存。我们有两个缓存,缓存A和缓存B。A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。

然后细分为以下几个小点:从缓存A读取数据库有则直接返回;A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程,更新线程同时更新缓存A和缓存B。

八 如何解决Redis的并发竞争Key问题

这个问题大致就是,同时有多个子系统去set一个key。这个时候大家思考过要注意什么么?

需要说明一下,我提前百度了下,发现答案基本都是推荐用Redis事务机制。

我并不推荐使用Redis的事务机制,因为我们的生产环境,基本都是Redis集群环境,做了数据分片操作。

你一个事务中有设计到多个key操作的时候,这多个key不一定都存在再同一个redis-server上。因此,Redis的事务机制,十分鸡肋

(1)如果对这个key操作,不要求顺序

这种情况下,准备一个分布式锁,大家去枪锁,抢到锁就做set操作即可,比较简单

(2)如果对这个key操作,要求顺序

假设有一个key1 ,系统A需要将key1 设置为ValueA ,系统B要将key1设置为valueB ,系统C要将key1设置为valueC 。期望按照key1的value值按照 valueA>valueB>valueC的顺序变化,这种时候我们在数据写入数据库的时候,需要保存一个时间戳。

假设时间戳如下

系统A  key1  {valyeA 3:00}

系统B key1 {valueB 3:05}

系统C key1{ valueC 3:10)}

那么假设这会系统B先抢到锁,将key1 设置为{ valueB 3:05 } 。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了,以此类推。

其他方法,比如利用队列,将set方法变成穿行访问也可以。总之灵活变通。

本文 非原创

你可能感兴趣的:(为什么分布式一定要有Redis)