Redis

Redis是开源的基于C语言实现的可持久化的日志型、K-V模型的No-SQL。高性能且有失效时间。

应用场景

缓存、排行榜、计数器、消息队列、分布式锁、社交会话(共同好友)、最新列表

数据类型

①String

②List 元素可以重复,保证添加的顺序、有索引

③Set 无序且不可重复、没有索引

④ZSet 有序集合(相对于Set类型多了个score分数值:double类型 分数可以执行)

⑤Hash 散列存储 字段唯一不可重复,值可以重复

过期删除机制

如果一个键是过期的,那它到了过期时间之后是不是马上就从内存中被被删除呢??如果不是,那过期后到底什么时候被删除呢??

其实有三种不同的删除策略:

(1)立即删除。在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。

(2)惰性删除。键过期了就过期了,不管。每次从dict字典中按key取值时,先检查此key是否已经过期,如果过期了就删除它,并返回nil,如果没过期,就返回键值。

(3)定时删除。每隔一段时间,对expires字典进行检查,删除里面的过期键。

可以看到,第二种为被动删除,第一种和第三种为主动删除,且第一种实时性更高。下面对这三种删除策略进行具体分析。

立即删除

立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。但是立即删除对cpu是最不友好的。因为删除操作会占用cpu的时间,如果刚好碰上了cpu很忙的时候,比如正在做交集或排序等计算的时候,就会给cpu造成额外的压力。

而且目前redis事件处理器对时间事件的处理方式--无序链表,查找一个key的时间复杂度为O(n),所以并不适合用来处理大量的时间事件。

惰性删除

惰性删除是指,某个键值过期后,此键值不会马上被删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。所以惰性删除的缺点很明显:浪费内存。dict字典和expires字典都要保存这个键值的信息。

举个例子,对于一些按时间点来更新的数据,比如log日志,过期后在很长的一段时间内可能都得不到访问,这样在这段时间内就要白白浪费这么多内存来存log。这对于性能非常依赖于内存大小的redis来说,是比较致命的。

定时删除

从上面分析来看,立即删除会短时间内占用大量cpu,惰性删除会在一段时间内浪费内存,所以定时删除是一个折中的办法。

定时删除:每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对cpu的影响。另一方面定时删除也有效的减少了因惰性删除带来的内存浪费。

redis使用的过期键值删除策略是:惰性删除加上定期删除,两者配合使用。

淘汰机制

Redis中的淘汰机制都是几近于算法实现的(LRU),主要从性能和可靠性上做平衡,所以并不是完全可靠,所以开发者们在充分了解Redis淘汰策略之后还应在平时多主动设置或更新key的expire时间,主动删除没有价值的数据,提升Redis整体性能和空间。

Redis持久化

Redis是基于内存存储的,一旦服务器奔溃,就会导致数据丢失。而Redis持久化策略保证了数据的持久化存储,将数据从内存存储到磁盘中。这也是为什么Redis宕机后重启可以恢复数据的原因。(AOF和RDB文件不出现丢失/损坏)详细可以自行了解下

    支持两种格式:

        1、rdb 冷备 默认开启

            同时需要满足时间和次数两个条件限制

        2、aof 热备 默认关闭

            格式:appendfsync ecerysec 每秒做一次持久化

            appendfsync always 总是做持久化,只要操作就会进行持久化

            不管操作次数,只需要关注时间

        持久化只需开启,无需关心加载,Redis每次启动的时候检测持久化文件自动加载

缓存穿透 雪崩 击穿原因及解决方案

redis缓存穿透:查询一个数据库中不存在的数据,比如商品详情,查询一个不存在的ID,每次都会访问DB,如果有人恶意破坏,很可能直接对DB造成过大的压力

解决方案:当通过某一个key去查询数据的时候,如果对应在数据库中的数据都不存在,我们将此key对应的value设置为一个默认的值,比如“NULL”,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被缓存挡住了。后面如果此key对应的数据在DB中存在时,缓存失效之后,通过此key再去访问数据,就能拿到新的value了。

reds缓存雪崩:是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案:将系统中key的缓存失效时间均匀地错开,防止统一时间点有大量的key对应的缓存失效。比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

redis缓存击穿(热点Key):缓存中的一个Key(比如一个促销商品),在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案:使用互斥锁(mutex key)。对缓存查询加锁,如果key不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询

redis缓存击穿代码实现如下:

publicStringget(key){Stringvalue= redis.get(key);if(value==null) {//代表缓存值过期//假设设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load dbif(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{returnvalue;          }}

主从复制模式

主从模式是三种模式中最简单的,在主从复制中,数据库分为两类:主数据库(master)和从数据库(slave)。// 现由于美国黑人事件可能会存在改名一说认为master slave有种族歧视的成分包含在内

其中主从复制有如下特点:

* 主数据库可以进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库* 从数据库一般都是只读的,并且接收主数据库同步过来的数据* 一个master可以拥有多个slave,但是一个slave只能对应一个master* slave挂了不影响其他slave的读和master的读和写,重新启动后会将数据从master同步过来* master挂了以后,不影响slave的读,但redis不再提供写服务,master重启后redis将重新对外提供写服务* master挂了以后,不会在slave节点中重新选一个master

工作机制:

当slave启动后,主动向master发送SYNC命令。master接收到SYNC命令后在后台保存快照(RDB持久化)和缓存保存快照这段时间的命令,然后将保存的快照文件和缓存的命令发送给slave。slave接收到快照文件和命令后加载快照文件和缓存的执行命令。复制初始化后,master每次接收到的写命令都会同步发送给slave,保证主从数据一致性。

缺点:

从上面可以看出,master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务。

Sentinel 哨兵模式

主从模式的弊端就是不具备高可用性,当master挂掉以后,Redis将不能再对外提供写入操作,因此sentinel应运而生。

sentinel中文含义为哨兵,顾名思义,它的作用就是监控redis集群的运行状况,特点如下:

sentinel模式是建立在主从模式的基础上,如果只有一个Redis节点,sentinel就没有任何意义* 当master挂了以后,sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master* 当master重新启动后,它将不再是master而是做为slave接收新的master的同步数据* sentinel因为也是一个进程有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群* 多sentinel配置的时候,sentinel之间也会自动监控* 当主从模式配置密码时,sentinel也会同步将配置信息修改到配置文件中,不需要担心* 一个sentinel或sentinel集群可以管理多个主从Redis,多个sentinel也可以监控同一个redis* sentinel最好不要和Redis部署在同一台机器,不然Redis的服务器挂了以后,sentinel也挂了

工作机制:

每个sentinel以每秒钟一次的频率向它所知的master,slave以及其他sentinel实例发送一个 PING 命令* 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被sentinel标记为主观下线。* 如果一个master被标记为主观下线,则正在监视这个master的所有sentinel要以每秒一次的频率确认master的确进入了主观下线状态* 当有足够数量的sentinel(大于等于配置文件指定的值)在指定的时间范围内确认master的确进入了主观下线状态, 则master会被标记为客观下线* 在一般情况下, 每个sentinel会以每 10 秒一次的频率向它已知的所有master,slave发送 INFO 命令* 当master被sentinel标记为客观下线时,sentinel向下线的master的所有slave发送 INFO 命令的频率会从 10 秒一次改为 1 秒一次* 若没有足够数量的sentinel同意master已经下线,master的客观下线状态就会被移除;* 若master重新向sentinel的 PING 命令返回有效回复,master的主观下线状态就会被移除

当使用sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。

redis cluster

sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中。cluster模式的出现就是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。

cluster可以说是sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容。

使用集群,只需要将redis配置文件中的cluster-enable配置打开即可。每个集群中至少需要三个主数据库才能正常运行,新增节点非常方便。一组Redis Cluster是由多个Redis实例组成,官方推荐我们使用6实例,其中3个为主节点,3个为从节点。当然我们也可以根据实际的业务需求自己去设置。单机redis能够承载的并发量根据机器性能及内存一般是5-8W

集群采用P2P的Gossip 流言协议进行通信

实现分布式锁

实现分布式锁主要方式Jedis.setNX方法来实现

在实现的时候要注意的几个关键点:

1、锁信息必须是会过期超时的,不能让一个线程长期占有一个锁而导致死锁;2、同一时刻只能有一个线程获取到锁。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容