(1)String(字符串)
(2)Hash(哈希)
(3)List(列表)
(4)Set(集合)
(5)zset(sorted set:有序集合)
它还有三种特殊的数据结构类型
(1)RDB:全量备份,节省磁盘空间,恢复速度快。但是可能会丢失数据。
(2)AOF:日志增量备份,数据保存完整。但是整体的恢复效率低,占空间。
(3)如果数据比较敏感的情况下,建议两个都开启。如果只是做纯缓存,数据不敏感,可以都不开启
(4)默认开启的是RDB。
(1)可以存储,项目中使用的redisTemplate默认的是将Vule值序列化后存储到redis。
(2)也可以将Java对象转成json字符串存储到redis。
(1)StringRedisTemplate继承了RedisTemplate但是两者的数据不能共用,RedisTemplate存储的数据只能通过RedisTemplate获取。
(2)StringRedisTemplate和RedisTemplate最大的区别就是默认采用的序列化策略不同,StringRedisTemplate默认的是String序列化,RedisTemplate默认使用的是JDK的序列化方式。
(3)StringRedisTemplate存储效率比RedisTemplate高,同时存储占用空间小。但是不如RedisTemplate灵活方便。当操作的数据是复杂的Java对象而不想做任何数据类型转换的时候那么建议使用RedisTemplate
(1)设置redis的最大存储空间,建议设置为服务器内存的3/4。
(2)设置redis的内存淘汰策略,设置为volatile-lru或allkeys-lru。
(3)到达redis的最大值触发内存淘汰机制:
是针对缓存中没有但数据库有的数据。场景是,当一个热Key失效后,假如瞬间突然涌入大量的请求,来请求同一个Key,这些请求不会命中Redis,都会请求到DB,导致数据库压力过大,甚至扛不住,挂掉。
(1)是指大量的热Key同时失效,对这些Key的请求就会瞬间打到DB上,同样会导致数据库压力过大甚至挂掉。
(2)解决方案:
(1)Redis缓存穿透指的是,在Redis缓存和数据库中都找不到相关的数据。也就是说这是个非法的查询,客户端发出了大量非法的查询 比如id是负的 ,导致每次这个查询都需要去Redis和数据库中查询。导致MySql直接爆炸!
(2)解决方案:
(1)完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
(2)数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
(3)采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
(4)使用多路I/O复用模型,非阻塞IO;所以可以一次性接收多个客户端请求,然后放到队列中;
(1)Redis 在设计上采用将网络数据读写和协议解析通过多线程的方式来处理,对于命令执行来说,仍然使用单线程操作。
(2)从Redis6 版本开始,已经引入了多线程。
(1)定时删除:在设置某个 key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。
(2)惰性删除:设置该 key 过期时间后,我们不去管它,当需要该 key 时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该 key。
(3)定期删除:每隔一段时间,我们就对一些 key 进行检查,删除里面过期的 key。
在配置文件 redis.conf 中,可以通过参数 maxmemory 来设定最大内存:
# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
#
maxmemory <bytes>
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
#
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
当使用的内存大于 maxmemory 时,就会触发 Redis 主动淘汰内存方式:
(1)volatile-lru:利用 LRU 算法移除设置过过期时间的 key (LRU:最近使用 Least Recently Used );
(2)allkeys-lru:利用 LRU 算法移除任何key (和上一个相比,删除的 key 包括设置过期时间和不设置过期时间的),通常使用该方式;
(3)volatile-random:移除设置过过期时间的随机key ;
(4)allkeys-random:无差别的随机移除;
(5)volatile-ttl:移除即将过期的 key (minor TTL);
(6)noeviction:不移除任何 key,只是返回一个写错误 ,默认选项,一般不会选用。
在 redis.conf 配置文件中,可以设置 maxmemory-policy 来设置内存淘汰方式:
# Note: with any of the above policies, Redis will return an error on write
# operations, when there are no suitable keys for eviction.
#
# At the date of writing these commands are: set setnx setex append
# incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
# sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
# zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
# getset mset msetnx exec sort
#
# The default is:
#
maxmemory-policy noeviction
Redis 的定期删除策略并不是一次运行就检查所有的库、所有的键,而是随机检查一定数量的键。
定期删除函数的运行频率,在 Redis2.6 版本中,规定每秒运行 10 次,大概 100ms 运行一次。在 Redis2.8 版本后,可以通过修改配置文件 redis.conf 的 hz 选项来调整这个次数:
# The range is tetween 1 and 500, however a value over 100 is usually not
# a good idea. Most users should use the default of 10 and raise this up to
# 100 only in environments where very low latency is requiried.
hz 10
才这个参数的上面注释可以看出,建议不要将这个值设置超过100,一般使用默认的10,只有当在需要非常低延迟的场景才设置为100。
(1)slave节点请求增量同步
(2)master节点判断replid,发现不一致,拒绝增量同步,使用全量同步
(3)master将完整数据生成RDB,发送RDB到slave
(4)slave清空本地数据,加载RDB文件
(5)master将RDB期间产生的命令记录在repl_baklog,并持续性的将repl_baklog发送到slave
(6)slave接收到这些命令,保持与master数据同步。
(1)slave节点请求增量同步
(2)master节点判断replid,如果一致返回continue
(3)从repl_baklog中获取增量数据
(4)发送offset后面的命令
repl_baklog的容量是有上线的,写满后会覆盖最早的数据。如果slave断开的时间太久,导致尚未备份的数据被覆盖(offset会标记repl_baklog中备份的数据),就无法做增量同步,只能去做全量同步。
(1)布隆过滤器(英语:Bloom Filter)是1970年由布隆
提出的。它实际上是一个很长的二进制向量
和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。
(2)布隆过滤器的结构
(1)给Redis设置持久化,这样服务器重启能够快速恢复到工作状态。
(2)搭建Redis集群实现高可用。
我们在项目中使用Redis,肯定不会是单点部署Redis服务的。因为,单点部署一旦宕机,就不可用了。为了实现高可用,通常的做法是,将数据库复制多个副本以部署在不同的服务器上,其中一台挂了也可以继续提供服务。
Redis 实现高可用有三种部署模式:
- 主从模式
- 哨兵模式
- 集群模式。
主从模式中,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址。显然,多数业务场景都不能接受这种故障处理方式。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。
哨兵模式,由一个或多个Sentinel实例组成的Sentinel系统,它可以监视所有的Redis主节点和从节点,并在被监视的主节点进入下线状态时,自动将下线主服务器属下的某个从节点升级为新的主节点。但是呢,一个哨兵进程对Redis节点进行监控,就可能会出现问题(单点问题),因此,可以使用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。
简单来说,哨兵模式就三个作用:
(1)发送命令,等待Redis服务器(包括主服务器和从服务器)返回监控其运行状态;
(2)哨兵监测到主节点宕机,会自动将从节点切换成主节点,然后通过发布订阅模式通知其他的从节点,修改配置文件,让它们切换主机;
(3)哨兵之间还会相互监控,从而达到高可用。
(1)每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他Sentinel实例发送一个 PING命令。
(2)如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel标记为主观下线。
(3)如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
(4)当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线。
(5)在一般情况下, 每个 Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
(6)当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
(7)若没有足够数量的 Sentinel同意Master已经下线, Master的客观下线状态就会被移除;若Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
哨兵模式基于主从模式,实现读写分离,它还可以自动切换,系统可用性更高。但是它每个节点存储的数据是一样的,浪费内存,并且不好在线扩容。因此,Cluster集群应运而生,它在Redis3.0加入的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它也提供复制和故障转移的功能。
一个Redis集群由多个节点组成,各个节点之间是通过Gossip协议!
Redis Cluster集群通过Gossip协议进行通信,节点之前不断交换信息,交换的信息内容包括节点出现故障、新节点加入、主从节点变更信息、slot信息等等。常用的Gossip消息分为4种,分别是:ping、pong、meet、fail。
特别的,每个节点是通过集群总线(cluster bus) 与其他的节点进行通信的。通讯时,使用特殊的端口号,即对外服务端口号加10000。例如如果某个node的端口号是6379,那么它与其它nodes通信的端口号是 16379。nodes 之间的通信采用特殊的二进制协议。
Hash Slot插槽算法
既然是分布式存储,Cluster集群使用的分布式算法是一致性Hash嘛?并不是,而是Hash Slot插槽算法。
插槽算法把整个数据库被分为16384个slot(槽),每个进入Redis的键值对,根据key进行散列,分配到这16384插槽中的一个。使用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都可以处理这16384个槽。
集群中的每个节点负责一部分的hash槽,比如当前集群有A、B、C个节点,每个节点上的哈希槽数 =16384/3,那么就有:
这是一个redis双写一致性的问题,我们的策略是**先删缓存,再写数据库,数据更新完成后,再删缓存**。
参考文档: