Redis

redis 简介

简单来说 redis 就是⼀个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度⾮常快,因此 redis 被⼴泛应⽤于缓存⽅向。另外,redis 也经常⽤来做分布式锁。redis 提供了多种数据类型来⽀持不同的业务场景。除此之外,redis ⽀持事务 、持久化、LUA脚本、LRU驱动事件、多种集群⽅案。

为什么要⽤ redis/为什么要⽤缓存

主要从“⾼性能”和“⾼并发”这两点来看待这个问题。

⾼性能:

假如⽤户第⼀次访问数据库中的某些数据。这个过程会⽐较慢,因为是从硬盘上读取的。将该⽤户访问 的数据存在缓存中,这样下⼀次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接 操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

image-20210504231828171

⾼并发:

直接操作缓存能够承受的请求是远远⼤于直接访问数据库的,所以我们可以考虑把数据库中的部分数据 转移到缓存中去,这样⽤户的⼀部分请求会直接到缓存这⾥⽽不⽤经过数据库。

image-20210504231842625

为什么要⽤ redis ⽽不⽤ map/guava 做缓存?

缓存分为本地缓存和分布式缓存。以 Java 为例,使⽤⾃带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,⽣命周期随着 jvm 的销毁⽽结束,并且在多实例的情况下,每个实例都需要各⾃保存⼀份缓存,缓存不具有⼀致性。

使⽤ redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共⽤⼀份缓存数据,缓存具有⼀致性。缺点是需要保持 redis 或 memcached服务的⾼可⽤,整个程序架构上较为复杂。

redis 的线程模型

redis 内部使⽤⽂件事件处理器 file event handler ,这个⽂件事件处理器是单线程的,所以redis 才叫做单线程的模型。它采⽤ IO 多路复⽤机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进⾏处理。 ⽂件事件处理器的结构包含 4 个部分: 多个 socket IO 多路复⽤程序 ⽂件事件分派器 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)

多个 socket 可能会并发产⽣不同的操作,每个操作对应不同的⽂件事件,但是 IO 多路复⽤程序会监听多个 socket,会将 socket 产⽣的事件放⼊队列中排队,事件分派器每次从队列中取出⼀个事件, 把该事件交给对应的事件处理器进⾏处理。

Memcache与Redis的区别都有哪些?

1) 、存储方式 Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小。 Redis有部份存在硬盘上,redis可以持久化其数据

2) 、数据支持类型 memcached所有的值均是简单的字符串,redis作为其替代者,支持更为丰富的数据类型 ,提供list,set,zset,hash等数据结构的存储

3) 、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

4). value 值大小不同:Redis 最大可以达到 1gb;memcache 只有 1mb。5)redis的速度比memcached快很多

6)Redis支持数据的备份,即master-slave模式的数据备份。

  1. redis⽀持更丰富的数据类型(⽀持更复杂的应⽤场景):Redis不仅仅⽀持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache⽀持简单的数据类型, String。
  2. Redis⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤,⽽Memecache把数据全部存在内存之中。

  3. 集群模式:memcached没有原⽣的集群模式,需要依靠客户端来实现往集群中分⽚写⼊数据;但 是 redis ⽬前是原⽣⽀持 cluster 模式的.

  4. Memcached是多线程,⾮阻塞IO复⽤的⽹络模型;Redis使⽤单线程的多路 IO 复⽤模型。

image-20210504232019619

redis的数据类型,以及每种数据类型的使用场景

回答:一共五种

(一)String

常⽤命令: set,get,decr,incr,mget 等

这个其实没啥好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计 数功能的缓存。常规计数:微博数,粉丝数等。

(二)hash

img常⽤命令: hget,hset,hgetall 等。

这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候, 就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟 出类似session的效果。存储⽤户信息,商品信息等

(三)list

img常⽤命令: lpush,rpush,lpop,rpop,lrange等

使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基 于redis的分页功能,性能极佳,用户体验好。本人还用一个场景,很合适—取行情信息。就也是个生产 者和消费者的场景。LIST可以很好的完成排队,先进先出的原则。微博的关注列表,粉丝列表,消息列表等功能

list 就是链表,Redis list 的应⽤场景⾮常多,也是Redis最重要的数据结构之⼀,⽐如微博的关注列表,粉丝列表,消息列表等功能都可以⽤Redis的 list 结构来实现。

Redis list 的实现为⼀个双向链表,即可以⽀持反向查找和遍历,更⽅便操作,不过带来了部分额外的内存开销。

另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分⻚查询,这个很棒的⼀个功能,基于 redis 实现简单的⾼性能分⻚,可以做类似微博那种下拉不断分⻚的东⻄(⼀⻚⼀⻚的往下⾛),性能⾼。

(四)set

img常⽤命令: sadd,spop,smembers,sunion 等

因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去 重,再起一个公共服务,太麻烦了。

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

(五)sorted set

img常⽤命令: zadd,zrange,zrem,zcard等

sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。

在直播系统中,实时排⾏信息包含直播间在线⽤户列表,各种礼物排⾏榜,弹幕消息(可以理解为按消息维度的消息排⾏榜)等信息,适合使⽤ Redis 中的 Sorted Set 结构进⾏存储。

Redis持久化机制

Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持 久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。

实现:单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程 写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出, 内存释放。

RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二 进制文件。即Snapshot快照存储,对应产生的数据文件为dump.rdb,通过配置文件中的save参数来定 义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。)

在redis.conf配置⽂件中默认有此下配置:

save 900 1    #在900秒(15分钟)之后,如果⾄少有1个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。
save 300 10    #在300秒(5分钟)之后,如果⾄少有10个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。
save 60 10000    #在60秒(1分钟)之后,如果⾄少有10000个key发⽣变化,Redis就会⾃动触发BGSAVE命令创建快照。

AOF:与快照持久化相⽐,AOF持久化 的实时性更好,因此已成为主流的持久化⽅案,Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

可以通过appendonly参数开启:

appendonly yes

开启AOF持久化后每执⾏⼀条会更改Redis中的数据的命令,Redis就会将该命令写⼊硬盘中的AOF⽂件。 AOF⽂件的保存位置和RDB⽂件的位置相同,都是通过dir参数设置的,默认的⽂件名是appendonly.aof。

在Redis的配置⽂件中存在三种不同的 AOF 持久化⽅式,它们分别是:

appendfsync   always #每次有数据修改发⽣时都会写⼊AOF⽂件,这样会严重降低Redis的速度
appendfsync everysec    #每秒钟同步⼀次,显示地将多个写命令同步到硬盘
appendfsync no    #让操作系统决定何时进⾏同步

为了兼顾数据和写⼊性能,⽤户可以考虑 appendfsync everysec选项 ,让Redis每秒同步⼀次AOF⽂件,Redis性能⼏乎没受到任何影响。⽽且这样即使出现系统崩溃,⽤户最多只会丢失⼀秒之内产⽣的 数据。当硬盘忙于执⾏写⼊操作的时候,Redis还会优雅的放慢⾃⼰的速度以便适应硬盘的最⼤写⼊速度。

当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。

Redis 4.0 对于持久化机制的优化

Redis 4.0 开始⽀持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项开aof-use-rdb-preamble启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF ⽂件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF ⾥⾯的RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

补充内容:AOF 重写

AOF重写可以产⽣⼀个新的AOF⽂件,这个新的AOF⽂件和原有的AOF⽂件所保存的数据库状态⼀样,但体 积更⼩。

AOF重写是⼀个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序⽆须对现有AOF⽂件 进⾏任何读⼊、分析或者写⼊操作。

在执⾏ BGREWRITEAOF 命令时,Redis 服务器会维护⼀个 AOF 重写缓冲区,该缓冲区会在⼦进程创建新AOF⽂件期间,记录服务器执⾏的所有写命令。当⼦进程完成创建新AOF⽂件的⼯作之后,服务器会将 重写缓冲区中的所有内容追加到新AOF⽂件的末尾,使得新旧两个AOF⽂件所保存的数据库状态⼀致。最 后,服务器⽤新的AOF⽂件替换旧的AOF⽂件,以此来完成AOF⽂件重写操作

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题

一、缓存雪崩

我们可以简单的理解为:由于原有缓存失效,新缓存未到期间

(例如:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期),所有原本应该访 问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从 而形成一系列连锁反应,造成整个系统崩溃。

解决办法:

大多数系统设计者考虑用加锁( 最多的解决方案)或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓 存失效时间分散开。

事前:尽量保证整个 redis 集群的⾼可⽤性,发现机器宕机尽快补上。选择合适的内存淘汰策略。 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉

事后:利⽤ redis 持久化机制保存的数据尽快恢复缓存

image-20210504234107393

二、缓存穿透

image-20210504234132824

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在 缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请 求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

⼀般MySQL 默认的最⼤连接数在 150 左右,这个可以通过show variables like '%max_connections%'; 命令来查看。最⼤连接数⼀个还只是⼀个指标,cpu,内存,磁盘,⽹络等⽆⼒条件都是其运⾏指标,这些指标都会限制其并发能⼒!所以,⼀般 3000 个并发请求就能打死⼤部分数据库了。

解决办法;

缓存⽆效 key : 如果缓存和数据库都查不到某个 key 的数据就写⼀个到 redis 中去并设置过期时间,具体命令如下: SET key value EX 10086 。这种⽅式可以解决请求的 key 变化不频繁的情况,如果⿊客恶意攻击,每次构建不同的请求key,会导致 redis 中缓存⼤量⽆效的 key 。很明显, 这种⽅案并不能从根本上解决此问题。如果⾮要⽤这种⽅式来解决穿透问题的话,尽量将⽆效的 key 的过期时间设置短⼀点⽐如 1 分钟。

最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存 在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故 障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设 置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单 粗暴。

5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该 如何解决?如果是64bit的呢?

对于空间的利用到达了一种极致,那就是Bitmap和布隆过滤器(Bloom Filter)。Bitmap: 典型的就是哈希表

缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空 间、时间来完成了。

布隆过滤器(推荐)

image-20210504234327859

就是引入了k(k>1)k(k>1)个相互独立的哈希函数,保证在给定的空间、误判率下,完成元素判重的过 程。

它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。

Hash存在一个冲突(碰撞)的问题,用同一个Hash得到的两个URL的值有可能相同。为了减少冲突, 我们可以多引入几个Hash,如果通过其中的一个Hash值我们得出某元素不在集合中,那么该元素肯定 不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时,才能确定该元素存在于集合中。这 便是Bloom-Filter的基本思想。

Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

三、缓存预热

缓存预热这个应该是一个比较常见的概念,相信很多小伙伴都应该可以很容易的理解,缓存预热就是系 统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据 库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

解决思路:

1、直接写个缓存刷新页面,上线时手工操作下;

2、数据量不大,可以在项目启动的时候自动进行加载;

3、定时刷新缓存;

四、缓存更新

除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),我们还可以根据具体的 业务需求进行自定义的缓存淘汰,常见的策略有两种:

(1) 定时去清理过期的缓存;

(2) 当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数 据并更新缓存。

两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过 来都要判断缓存失效,逻辑相对比较复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡。

五、缓存降级

当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然 需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开 关实现人工降级。

降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结 算)。

以参考日志级别设置预案:

(1) 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

(2) 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级, 并发送告警;

(3) 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的 最大阀值,此时可以根据情况自动降级或者人工降级;

(4) 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要 的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查 询,而是直接返回默认值给用户。

热点数据和冷数据是什么

热点数据,缓存才有价值

对于冷数据而言,大部分数据可能还没有再次访问到就已经被挤出内存,不仅占用内存,而且价值不 大。频繁修改的数据,看情况考虑使用缓存

对于上面两个例子,寿星列表、导航信息都存在一个特点,就是信息修改频率不高,读取通常非常高的 场景。

对于热点数据,比如我们的某IM产品,生日祝福模块,当天的寿星列表,缓存以后可能读取数十万次。 再举个例子,某导航产品,我们将导航信息,缓存以后可能读取数百万次。

数据更新前至少读取两次,缓存才有意义。这个是最基本的策略,如果缓存还没有起作用就失效了,那 就没有太大价值了。

那存不存在,修改频率很高,但是又不得不考虑缓存的场景呢?有!比如,这个读取接口对数据库的压 力很大,但是又是热点数据,这个时候就需要考虑通过缓存手段,减少数据库的压力,比如我们的某助 手产品的,点赞数,收藏数,分享数等是非常典型的热点数据,但是又不断变化,此时就需要将数据同 步保存到Redis缓存,减少数据库压力。

MySQL⾥有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?

redis 提供 6种数据淘汰策略:
  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使⽤的数 据淘汰

  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘 汰

  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  4. allkeys-lru:当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的key(这个是最常⽤的)

  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  6. no-eviction:禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错。这个应该没⼈使⽤吧!

4.0版本后增加以下两种:

  1. volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使⽤的数据 淘汰

allkeys-lfu:当内存不⾜以容纳新写⼊数据时,在键空间中,移除最不经常使⽤的key

单线程的redis为什么这么快

(一)纯内存操作

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

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

redis的过期策略以及内存淘汰机制

Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置⼀个过期时间

我们 set key 的时候,都可以给⼀个 expire time,就是过期时间,通过过期时间我们可以指定这个key 可以存活的时间。

如果假设你设置了⼀批 key 只能存活1个⼩时,那么接下来1⼩时后,redis是怎么对这批key进⾏删除的?

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

定期删除:redis默认是每隔 100ms 就随机抽取⼀些设置了过期时间的key,检查其是否过期, 如果过期就删除。注意这⾥是随机抽取的。为什么要随机呢?你想⼀想假如 redis 存了⼏⼗万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很⼤的负载!

惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存⾥,除⾮你的系统去查⼀下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈!

为什么不用定时删除策略?

定时删除,用一个定时器来负责监视key,过期则自动删除。虽然内存及时释放,但是十分消耗CPU资源。在大并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.

定期删除+惰性删除是如何工作的呢?

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是 每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂 不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了 过期时间那么是否过期了?如果过期了此时就会删除。

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

不是的,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,

redis的内存会越来越高。那么就应该采用内存淘汰机制。在redis.conf中有一行配置

maxmemory-policy volatile-lru

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

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)中任意选择数据淘汰no-enviction(驱逐):禁止驱逐数据,新写入操作会报错

ps:如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和

volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

Redis 为什么是单线程的

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内 存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的 方案了(毕竟采用多线程会有很多麻烦!)

Redis利用队列技术将并发访问变为串行访问

1)绝大部分请求是纯粹的内存操作(非常快速)

2)采用单线程,避免了不必要的上下文切换和竞争条件

3)非阻塞IO优点:

  • 速度快,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度 都是O(1)
  • 支持丰富数据类型,支持string,list,set,sorted set,hash
  • 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
  • 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除如何解决redis的并发 竞争key问题

同时有多个子系统去set一个key。这个时候要注意什么呢? 不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候, 这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。

(1) 如果对这个key操作,不要求顺序: 准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可

(2) 如果对这个key操作,要求顺序: 分布式锁+时间戳。 假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

(3) 利用队列,将set方法变成串行访问也可以redis遇到高并发,如果保证读写key的一致性对redis的操作都是具有原子性的,是线程安全的操作,你不用考虑并发问题,redis内部已经帮你处理好并 发的问题了。

Redis 常见性能问题和解决方案?

(1) Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件

(2) 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内

(4) 尽量避免在压力很大的主库上增加从库主从复制不要用图状结构,用单向链表结构更为稳定,即: Master <- Slave1 <- Slave2 <-Slave3…

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

所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对⼀个 key 进⾏操作,但是最后执⾏的顺序和我们期望的顺序不同,这样也就导致了结果的不同!

推荐⼀种⽅案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使⽤分布式锁,这样会影响性能)

基于zookeeper临时有序节点可以实现的分布式锁。⼤致思想为:每个客户端对某个⽅法加锁时,在zookeeper上的与该⽅法对应的指定节点的⽬录下,⽣成⼀个唯⼀的瞬时有序节点。 判断是否获取锁的⽅式很简单,只需要判断有序节点中序号最⼩的⼀个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁⽆法释放,⽽产⽣的死锁问题。完成业务流程后,删除对应的⼦节点释放锁。

在实践中,当然是从以可靠性为主。所以⾸推Zookeeper。

如何保证缓存与数据库双写时的数据⼀致性?

⼀般情况下我们都是这样使⽤缓存的:先读缓存,缓存没有的话,就读数据库,然后取出数据后放⼊缓存,同时返回响应。这种⽅式很明显会存在缓存和数据库的数据不⼀致的情况。

你只要⽤缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就⼀定会有数据⼀致性的问 题,那么你如何解决⼀致性问题?

⼀般来说,就是如果你的系统不是严格要求缓存+数据库必须⼀致性的话,缓存可以稍微的跟数据库偶 尔有不⼀致的情况,最好不要做这个⽅案,读请求和写请求串⾏化,串到⼀个内存队列⾥去,这样就可 以保证⼀定不会出现不⼀致的情况

串⾏化之后,就会导致系统的吞吐量会⼤幅度的降低,⽤⽐正常情况下多⼏倍的机器去⽀撑线上的⼀个 请求。

为什么Redis的操作是原子性的,怎么保证原子性的?

对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。

Redis的操作之所以是原子性的,是因为Redis是单线程的。

Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。多个命令在并发中也是原子性的吗?

不一定, 将get和set改成单命令操作,incr 。使用Redis的事务,或者使用Redis+Lua==的方式实现.

Redis事务

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

Redis会将一个事务中的所有命令序列化,然后按顺序执行。

  1. redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。

  2. 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;

  3. 如果在一个事务中出现运行错误,那么正确的命令会被执行。

1)MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中 的命令才会被执行。

2)EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil。

3)通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。

4)WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。

results matching ""

    No results matching ""