Redis

Redis数据库数据是存在内存中的,所以存写速度非常快(高并发高性能),因此redis被广泛应用于缓存方向。也被用来做分布式锁。

使用Redis的原因:

1.本地缓存Map,guava主要特点是轻量快速,生命周期随JVM销毁而结束,在多实例的情况下,每个实例都要保存一份缓存不具备一致性。而且只有键值对形式保存数据,结构单一。而Redis作为分布式缓存,在多实例的情况下能保证共用一份缓存具有一致性。缺点是需要保持Redis高可用,架构相对复杂。

redis和memcached的区别:

1.redis支持更丰富的数据类型,Redis支持String,list,set,zset,hash等数据结构的存储。memcache支持简单的String类型。

2.Redis支持数据的持久化,而Memecache把数据全部存在内存之中

3.集群模式:memcached没有原生的集群模式,redis 目前是原生支持 cluster 模式的;

4.Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路IO复用模型

redis 常见数据结构以及使用场景分析:

String 常用命令set,get,decr,incr,mget 等。

Hash 常用命令: hget,hset,hgetall 等。

List 双向链表,可以支持反向查找和遍历,带来部分额外开销 常用命令: lpush,rpush,lpop,rpop,lrange等

Set 基于 set 轻易实现交集、并集、差集的操作 常用命令: sadd,spop,smembers,sunion 等

Sorted Set 增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 常用命令: zadd,zrange,zrem,zcard(数量)等

redis 设置过期时间:

通过expire设置过期时间,到期后,通过定期删除(每隔100ms就随机抽取一些过期数据删除,不全部遍历降低cpu负债)+惰性删除(再次使用过期的key)两种策略删除。

redis内存淘汰机制:

6种数据淘汰策略:

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

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

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

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

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

no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!

使用:

在/usr/local/redis/redis.config设置

# maxmemory

# volatile-ttl

Redis持久化方案:

两种方式:

快照RDB:

通过创建快照来获得内存中某个时间点上数据的副本,对快照进行备份,复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。

快照持久化是Redis默认采用的持久化方式.

在配置文件redis.config里面:

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中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。

与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。

默认不开启,开启需要在redis.config里面设置appendonly yes 默认文件名appendonly.aof

三种不同的 AOF 持久化方式:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度

appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘,采用这种性能没影响就算崩溃也就丢失一秒内数据

appendfsync no      #让操作系统决定何时进行同步

PS:

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

混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。

优点:

可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据.

AOF里面的RDB部分是压缩格式不再是AOF格式,可读性较差。

大概流程是:执行BGReWriteAOF命令会创建重写缓冲区,记录服务器执行的所有写命令。

创建完新AOF文件的工作之后,会将缓冲区数据追加到新AOF末尾,替换旧AOF文件。

redis事务:

Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。

将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,在执行期间服务器不会中断事务改去执行其他命令。

因为Redis是单线程所以总具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation)

缓存雪崩和缓存穿透问题解决方案:

缓存雪崩:

大量缓存同一时间失效,导致后面的请求直接请求数据库,造成数据库短时间承受大量请求崩掉。

解决方案:

一般缓存雪崩,一般发生在系统刚上线或者刚重启前,还未对缓存进行预热.或者缓存预热设置过期时间大量相同导致.

解决办法,再或者设置的缓存淘汰策略不合理导致.volatile-ttl

1.脚本化预热过期时间做一个均匀分布,避免同一时间过期.

2.考虑使用排斥锁(mutex)。即第一个线程过来读取cache,发现没有,就去访问DB。后续线程再过来就需要等待第一个线程读取DB成功,cache里的value变得可用,后续线程返回新的value。分布式系统采用分布式锁。

3.针对混村淘汰策略选择allkeys-lru移除最近最少使用的key。

缓存穿透:

大量请求同时请求缓存中不存在的数据,导致大量请求直接打到勒数据库。

解决办法:

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

2.一个查询返回的数据为空,仍然把空结果保存到缓存,并设置过期时间为10秒.

缓存击穿:

是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决方案:

1.互斥锁(mutex key)/分布式锁: 这种解决方案思路比较简单,就是只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了。

2.在获取缓存内部在设置一个子缓存过期时间比父缓存过期时间小一点,但获取父缓存时,发现子缓存失效为空,就延长父缓存时间,然后再从数据库中加载更新到父缓存。

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

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

使用分布式锁解决.分布式锁

解决方案:

加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。

SET lock_key random_value NX PX 5000

random_value 是客户端生成的唯一的字符串。

NX 代表只在键不存在时,才对键进行设置操作。

PX 5000 设置键的过期时间为5000毫秒。

解锁:先判断当前锁的字符串是否与传入的值相等,是的话就删除Key,解锁成功。为了保证原子性用LUA脚本。

if redis.call('get',KEYS[1]) == ARGV[1] then

  return redis.call('del',KEYS[1])

else

  return 0

end

存在锁不具有可重入性。

如果有方法的递归调用发生,方法中的锁必须是可重入锁,否则会造成死锁的发生。

可以采用redisson方式实现可重入锁。

锁文章参考:

https://www.jianshu.com/p/47fd7f86c848

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

发生场景:缓存在内存中,而内存时无法感知到数据在数据库的修改。这样就会造成数据库中的数据与缓存中数据不一致的问题。

系统不是严格要求缓存+数据库必须一致性.

如果需要保证双写,那就可以把读请求和写请求串行化,按队列顺序执行可以保证不会出现不一致。但系统的吞吐量会大幅度的降低。

或者考虑先更新数据库,然后再删除缓存。

Redis的主从复制:

持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障.

实现读写分离,分担读压力。

全量同步:

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。

-  从服务器连接主服务器,发送SYNC命令;

-  主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;

-  主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;

-  从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;

-  主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;

-  从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

增量同步:

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。

增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

注意点:

如果多个Slave断线了,需要重启的时候,因为只要Slave启动,就会发送sync请求和主机全量同步,当多个同时出现的时候,可能会导致Master IO剧增宕机。

如何实现主从复制:

全量同步:

master服务器会开启一个后台进程用于将redis中的数据生成一个rdb文件,与此同时,服务器会缓存所有接收到的来自客户端的写命令(包含增、删、改),当后台保存进程

处理完毕后,会将该rdb文件传递给slave服务器,而slave服务器会将rdb文件保存在磁盘并通过读取该文件将数据加载到内存,在此之后master服务器会将在此期间缓存的

命令通过redis传输协议发送给slave服务器,然后slave服务器将这些命令依次作用于自己本地的数据集上最终达到数据的一致性。

部分同步:

从redis 2.8版本以前,并不支持部分同步,当主从服务器之间的连接断掉之后,master服务器和slave服务器之间都是进行全量数据同步,但是从redis 2.8开

始,即使主从连接中途断掉,也不需要进行全量同步,因为从这个版本开始融入了部分同步的概念。部分同步的实现依赖于在master服务器内存中给每个slave服务器维护了

一份同步日志和同步标识,每个slave服务器在跟master服务器进行同步时都会携带自己的同步标识和上次同步的最后位置。当主从连接断掉之后,slave服务器隔断时间

(默认1s)主动尝试和master服务器进行连接,如果从服务器携带的偏移量标识还在master服务器上的同步备份日志中,那么就从slave发送的偏移量开始继续上次的同步

操作,如果slave发送的偏移量已经不再master的同步备份日志中(可能由于主从之间断掉的时间比较长或者在断掉的短暂时间内master服务器接收到大量的写操作),则

必须进行一次全量更新。在部分同步过程中,master会将本地记录的同步备份日志中记录的指令依次发送给slave服务器从而达到数据一致。

参考:

https://www.cnblogs.com/daofaziran/p/10978628.html

Redis哨兵模式

哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例.当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

参考:

https://www.jianshu.com/p/06ab9daf921d

你可能感兴趣的:(Redis)