简介:
非关系型数据库
Memcached + MySQL + 垂直拆分(读写分离)
分库分表 + 水平拆分 + MySQL集群
为什么需要NoSQL:
NoSQL四大分类:
Key V 键值对:
Memecache:
文档型数据库(bson,json):
MogoDB(): 基于分布式文件存储的数据库,关系型数据库和非关系型数据库中间的产品,非关系型数据库中功能最丰富,它是最像关系型数据的。
列存储数据库:
HBase:
分布式文件系统:
图关系型数据库:
存放的是关系,比如朋友圈…
Neo4j,InfoGrid
Redis概述
什么是Redis
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用
读的速度是110000次/s,写的速度是81000次/s 。
Redis可以干什么
- 内存存储,持久化,内存中是断电即失、所以持久化很重要(rdb、aof)
- 效率高,可用于告诉存储
- 发布订阅系统
- 地图信息分析
- 计时器
特性
- 多样的数据类型
- 持久化
- 集群
- 事物
Redis-benchmark
(Redis自带的压力测试工具)
// 测试100台服务100000次请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
Redis基础知识
Redis一共有16个数据库,默认使用的是第一个。
Redis的常用命令
# 切换数据库
select 1
# 查看数据库大小
DBSIZE
# 查看数据库所有的key
keys *
# 清空数据库
flushall(全部) flushdb(当前库)
# 判断当前key是否存在
exists key
# 移除key值
move key 1
# 设置过期时间
expipe name 秒
# 查看剩余时间
ttl key
# 查看key的类型
type key
Redis是基于内存操作,CPU不是Redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络的带宽,既然可以使用单线程来实现,就使用单线程了。
Redis是由C语言编写的,
Redis为什么是单线程的,但是效率还是这么快
- Redis是将所有的数据都放在内存中的,内存的读写速度非常快
- redis是单线程的,省去了很多上下文切换线程的时间;
- Redis是将所有数据全部放在内存中的,所以使用单线程是效率最高的,多线程会有上下文切换消耗,对于内存系统来说,没有上下文消耗就是效率最高的。
Redis的五大数据类型
Redis-key
String
字符串的一些操作
# 先key的值后追加字符串
append key ""
# 查看字符串的长度
strlen key
# 自增一 指定增加数量
incr key incrby key 10
# 自减一 指定减少数量
decr key decrby key 10
# 截取字符串,0 -1 为截取所有
getrange key 0 3
# 替换指定位置的字符串
setrange key 0 ""
# 设置过期时间, 设置key的值且30秒后过期
setex(set width expire) key 30 ""
# 不存在设置,如果key不存在则创建,存在则创建失败
setnx(set if not exist) key ""
# 同时设置多个值,msetnx同上(注:改操作为原子性操作,设置时只要有存在的key就会失败)
mset key1 "v1" key2 "v2" key3 "v3"
# 同时获取多个值
mget key1 key2
# 常用的key的名称规则 user:1:name user:1:age
# 组合命令,不存在返回nil,如果存在返回原来的值,再设置新的值
getset key ""
List
所有的list的命令都是l开头的
# 将一个值或者多个值插入到列表的头部
lpush key ""
# 将一个值或者多个值插入到列表的尾 部
rpush key ""
# 通过区间获取具体的值,0 -1 获取所有
lrange key 0 -1
# 移除列表的第一个元素,移除列表的最后一个
lpop key rpop key
# 通过下标获取list中的某个值
lindex key 1
# 获取list的长度
llen key
# 移除list中指定个数的值
lrem key 1(移除个数) 要移除的值(精确匹配)
# 通过下标截取指定位置的值,list会只剩下截取出来的值
ltrim key 0 1
# 移除列表中最后一个元素并将它添加到新的列表中
rpoplpush key1 key2
# 将list中该下标的值替换为另一个值,下标不存在会报错
lset key 1 ""
# 向list中的一个元素的前或后面插入值
linsert key before|after "list中的元素" "需要插入的值"
Set
set中的值是不可重复的
# 向set中插入值
sadd key ""
# 查看set中所有的值
smembers key
# 查看某一个值是不是在set集合中
sismember key ""
# 获取set的长度
scard key
# 移除set中的某个值
srem key ""
# 随机获取一个值,后面数字加上后为随机获取指定个数的元素
srandmember key 2
# 随机删除set中的一个元素
spop key
# 将一个set中指定的值,移动到另一个set中
smove key1 key2 ""
# 差集,查看两个set中不同的值
sdiff key1 key2
# 交集,查看两个set中相同的值
sinter key1 key2
# 并集查看连个set中所有的值
sunion key1 key2
Hash
Map集合, key-Map,hash更适合于对象的存储。
# 向一个map集合中添加值
hset key "id" "值"
# 取出map中的一个值
hget key "id"
# 同时添加多个值
hmset key "id" "值" "id" "值"
# 同时获取多个值
hmget key id1 id2 id3
# 获取所有值,列表显示格式为key value形式。
hgetall key
# 删除指定的key
hdel key "id"
# 获取map的长度
hlen key
# 判断map中指定的key是否存在
hexists key "id"
# 获取map中所有的key
hkeys key
# 获取所有的value
hvals key
# 自增
hincrby key "id" 1
# 自减
hdecrby key "id" 1
# 如果key不存在则可以添加
hsetnx key "id" "value"
Zset
在set的基础上增加了一个值
# 向zset中添加值,123是添加时需要带上的值
zadd key 1 one 2 two 3 three
# 查看所有的值
zrange 0 -1
# 排序,从最小值到最大值
zrangebyscore salary -inf +inf withscores
# 从大到小进行排序
zrevrange key 0 -1
# 移除key中的一个元素
zrem key "id"
# 查看zset的长度
zcard key
# 获取指定区间的成员数量
zcount key 1 3
三种特殊数据类型
geospatial地理位置
Redis的Geo,朋友定位,打车距离,附近的人
# 添加地理位置
geoadd key 经度 维度 名称
# 获取某个地方的经纬度
geopos key 名称
# 单位 m米 km千米 mi英里 ft英尺
# 两个地区的距离
geodist key 名称 名称 单位(默认米)
# 获取key下该经纬度半径中的值, withdist显示到中心距离的位置 withcoord经纬度 count 2 数量
georadius key 经度 维度 半径 单位 withdist withcoord count 2
# 获取以该名称为中心的半径中的所有元素
georadiusbymember key 名称 半径 单位
# 返回一个或多个位置元素的11位的geohash字符串,如果两个字符串越接近则两个位置越接近
geohash key 名称
# 查看所有
zrange key 0 -1
# 移除一个值
zrem key 值
Geo的底层原理其实就是zset,我们可以使用zset命令来操作Geo!
hyperloglog
一种数据结构,它是用来做基数统计的算法
应用场景:网页的uv(网页浏览量的统计)
占用的内存固定,2^64不同的元素数组,只需要废12kb内存。
会有0.81的错误率,对于一些场景来说是可以忽略不计的。
# 创建一组元素
pfadd key "" "" ""
# 查看长度
pfcount key
# 并集key1 key2到key中,
pfmerge key key1 key2
bitmaps
位存储,统计用户信息,活跃不活跃,对于之后两个状态的都可以使用。
bitmaps位图,数据结构,操作二进制位来进行记录,只有0,1两种状态。
# 向key中的第x个位置的状态为0|1
setbit key index 0|1
# 查看index的状态
getbit key index
# 查看key中状态是1的数量
bitcount key
事务
Redis事务的本质: 一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行的过程中会按照顺序执行。
(一次性,顺序性,排他性)
Redis的事务没有隔离级别的概念,所有的命令在事务中,并没有直接被执行,而是在发起执行命令的时候才会被执行。
Redis的单条命令是原子性的,但是事务不保证原子性!
编译时错误所有事务都不会被执行,但对于运行时错误Redis依旧会执行剩余命令。
# 开启事务
multi
#命令入队,进行事务的操作
#执行事务
exec
# 取消事务,队伍中的命令都不会执行。
discard
监控
悲观锁: 认为什么时候都会出问题,无论什么时候都会加锁
乐观锁: 认为什么时候都不会 出问题,所以不回去加锁,更新数据的时候去判断在此期间是否有人修改过数据。
# 监视对象(相当于加锁)
watch key
multi # 开启事务
# 执行事务
exec # 提交
# 放弃监视
nuwatch
使用watch然后开启事务时,如果在事务执行过程中该key被修改,则该事务会执行失败!
事务执行失败后可以使用nuwatch进行解锁然后再次使用watch重新监视即可。
使用watch可以当做Redis的乐观锁!
jedis
使用java来操作Redis
是么是jedis : 是Redis官方推荐的java连接开发工具。
idea使用jedis连接Redis:
# 1.导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
# 2. 创建jedis对象并进行连接
Jedis jedis = new Jedis("127.0.0.1",6379);
# 3. 进行操作。
# 4. 关闭连接
jedis.close();
springboot整合Redis
在springboot2.x之后jedis被替换为了lettuce
jedis: 底层采用的直连,多线程操作的话是不安全的,如果想要避免,需要jedis pool 连接池。 Bio
Lettuce: 底层采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况。
// springboot整合Redis的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
// RedisAutoConfiguration 配置文件
@Bean
@ConditionalOnMissingBean(name = {"redisTemplate"}) // 如果不存在则生效(可以进行自定义)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 默认的RedisTemplate模板,没有过多的配置(Redis的对象都需要进行序列化)
// 两个泛型都是object类型,使用时会需要进行类型转
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // 由于String是常用的类型,所以单独提取出来的
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
// 操作Redis
RedisTemplate redis = new RedisTemplate();
// opsFor...(Value(操作字符串),list(操作list)...)
redis.opsForValue()
// 对于常用的操作,可以直接使用,比如事务
// 获取连接对象
RedisConnection connection = redis.getConnectionFactory().getConnection();
操作Redis时需要注意的问题
// RedisTemplate 默认使用jsk的序列化方式
// set值时不能直接传递对象,需要先进行序列化
// 序列化的方式
String obj = new ObjectMapper().writeValueAsString(Object);
// 实体类继承Serializable接口
implements Serializable
// 重新配置RedisTempleate模板,对所有key,value进行序列化。
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 为了使用方便,直接使用<String,Object>
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory); // 进行连接
// JSON序列化配置
Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(om);
// String 序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key惊醒序列化
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
Redis.config
-
配置文件对带小写不敏感。
-
可以包含多个配置文件
include /路径
-
网络
bind IP # 绑定的IP protected-mode yes # 保护模式 prot 6379 # 端口
-
通用GENRAL
daemonize yes # 以守护进程的方式运行,默认是no,需要手动开启为yes pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个pid文件! ##### 日志 # Specify the server verbosity level. # This can be one of: # debug (a lot of information, useful for development/testing) 测试开发环境 # verbose (many rarely useful info, but not a mess like the debug level) 较多的日志 # notice (moderately verbose, what you want in production probably) 生产环境 # warning (only very important / critical messages are logged) loglevel notice logfile "" # 日志的文件位置名 databases 16 # 数据库的数量,默认是16个 always-show-logo no # 是否显示log
-
快照
持久化,在规定的时间内,执行了多少操作,则会持久化到文件, .rdb , aof
set-proc-title yes # redis是内存数据库,如果没有持久化,那么断电及失。 # 如果900秒内,如果至少有1个key进行了修改,则会进行持久化操作。 save 3600 1 save 300 100 save 60 10000 # 持久化如果出错,是否还要继续工作! stop-writes-on-bgsave-error yes # 是否压缩rdb文件,会消耗一些CPU资源。 rdbcompression yes # 保存rdb文件时进行错误校验 rdbchecksum yes # rdb文件保存的目录 dir ./
REPLICATION
SECURITY 安全
可以在这里设置密码
# 获取密码 config get requirepass # 设置密码 config set requirepass "密码" # 登录 anth "密码"
限制 CLIENTS
maxclients 10000 # 设置能连接上Redis的最大客户端的数量 maxmemory <bytes> # 配置最大的内存容量 maxmemory-policy noeviction # 内存到达上限之后的处理策略 1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 2、allkeys-lru : 删除lru算法的key 3、volatile-random:随机删除即将过期key 4、allkeys-random:随机删除 5、volatile-ttl : 删除即将过期的 6、noeviction : 永不过期,返回错误
APPEND ONLY 模式 aof配置
appendonly no # 默认是不开启aof模式,默认使用的是rdb模式,在大部分情况下aof就够用了 appendfilename "appendonly.aof" #持久化文件的名字 appendfsync always # 每次修改都会同步,消耗性能 appendfsync everysec # 每秒执行一次sync,可能会丢失这一秒的数据。 appendfsync no # 不执行sync,操作系统自己同步,速度最快
Redis持久化
RDB
RDB保存的文件是dump.rdb
触发机制
- save的规则满足的情况下,会自动触发rdb规则
- 执行flushall命令
- 退出Redis
如何恢复rdb文件
- 只需要将rdb文件放在Redis启动目录下就可以了,Redis启动的时候会自动检查dump.rdb然后恢复其中的数据。
优点
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点
- 需要一定的时间间隔进行操作,如果Redis意外宕机了,会丢失最后一次修改数据
- fork进程的时候,会占用一定的内存空间。
AOF (Append Only File)
将我们的所有命令都记录下来,history,恢复的时候将这些命令重新执行一遍
Appendonly.aof
所有的操作记录都在此文件下
Redis-check-aof
如果aof文件有错误,我们需要修复这个文件。
可以使用 redis-check-aof 工具来进行修复
redis-check-aof --fix appendonly.aof
AOF 的重写规则
如果aof文件太大了,会fork一个新的进程来将文件进行重写
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mSWNwv37-1688106514310)(/Users/macbook/Library/Application Support/typora-user-images/image-20220328110750401.png)]
优点:
1. 每一次修改都同步,文件的完整性会更好
缺点:
- 相对于数据文件来说,aof远远大于rdb,修复速度相对来说也慢
- 运行效率也比rdb慢,所以默认是rdb
Redis订阅与发布
发布/订阅(pub/sub)是一种消息通信模式,
publish channel message => 消息发布者
subscribe channel ... => 消息订阅者
onsubscribe channel => 退订
订阅某个频道后Redis-server里维护了一个字典,字典的键就是一个个频道,而字典的值就是一个链表。
链表中保存了所有订阅这个频道的客户端。
发布者编辑文章后发送到一个频道中 => 每个频道都有一个链表里面包含所有订阅的用户
使用场景
- 实时消息系统。
- 实时聊天(每个频道当做一个聊天室)
- 订阅与关注
稍微复杂的场景需要使用消息中间件来处理
Redis主从复制
将一台Redis服务器的数据,复制到其他的Redis服务器上。前者称为主节点(master/leader),后者称为从节点(Slave/follower);数据的复制是单向的,只能由主节点到从节点,master以写为主,slave以读为主。
默认情况下每台Redis服务器都是主节点,且一个主节点可以有多个从节点。
Redis最大使用内存不能超过20G
主从复制,读写分离来减缓服务器压力。
主要作用
- 数据冗余
- 故障恢复
- 负载均衡
- 高可用
环境配置
只配置从库,不用配置主库。
# 查看当前库的信息
info replication
role:master # 角色 master
connected_slaves:0 # 从机
master_failover_state:no-failover
master_replid:5c42dbd158f5dc0d02ecdfe9234fc8b7c8980bc6
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
复制配置文件,然后修改对应的信息
- 端口
- pid名称
- Log日志名称
- Dump.rdb 名称
通过 Redis-server kconfig/redis80.conf 命令来启动配置文件
集群最少需要三个Redis服务,一主二从
# 将 X 个Redis认成自己的主机,自己就会默认为从机。
slaveof 主机地址 端口号
真实的主从复制应该从配置文件中修改
主机用来写,从机只能用来读。
主机断开连接,从机依旧是连接到主机的,但是无法进行写操作。
原理
slave 启动成功连接到master后会发送一个sync同步命令
master接到命令后会启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完成后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制: slave服务在接收到数据文件后,将其存盘并加载到内存中
增量复制:master继续将新的所有收集到的修改命令一次传给slave来完成同步
重启从机并重新连接到master上后就会进行一次全量复制。
第二种模式:层层链路
# 79,80,81 三台Redis服务
79为主机,80为79的从机,81位80的从机
# 如果79断开连接了,可以使用该命令来使自己变成主机。
# 如果79恢复了,则需要重新配置,否则79只是一个单节点。
slaveof no one
哨兵模式
自动选取主节点的模式
Redis的集群方案大致有三种:1)redis cluster集群方案;2)master/slave主从方案;3)哨兵模式来进行主从替换以及故障恢复。
概述
Sentinel(哨兵)是用于监控redis集群中Master状态的工具,是Redis 的高可用性解决方案,sentinel哨兵模式已经被集成在redis2.4之后的版本中。sentinel是redis高可用的解决方案,sentinel系统可以监视一个或者多个redis master服务,以及这些master服务的所有从服务;当某个master服务下线时,自动将该master下的某个从服务升级为master服务替代已下线的master服务继续处理请求。
sentinel可以让redis实现主从复制,当一个集群中的master失效之后,sentinel可以选举出一个新的master用于自动接替master的工作,集群中的其他redis服务器自动指向新的master同步数据。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。
Redis-sentinel
- 创建并编写哨兵配置文件 sentinel.conf
# 1表示如果主机挂了,就会进行投票来决定谁称为新的主机
sentinel monitor myredis 127.0.0.1 6379 1
redis-server
如果主机回来了,只能归并到新的主机下,当做从机。
-
哨兵集群基于主从复制模式,所有的主从配置有点它全有
-
主从可以切换,故障可以转移,系统的可用性 更好
-
主从模式的升级,从手动到自动
-
不好在线扩容,集群容量一旦到达上线,在线扩容十分麻烦
-
实现哨兵模式的配置是很麻烦的
缓存穿透和雪崩
缓存穿透
穿过Redis和数据库
缓存穿透是指查询一个根本不存在的数据,缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。
# 客户端发送请求后发现缓存里面没有想要的数据,然后继续向数据库中查找,数据库中也不存在让后返回空值给客户端。
客户端 => 缓存 => 数据库
缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。
造成缓存穿透的基本原因
一: 自身业务代码或者数据出现问题(例如:set 和 get 的key不一致)
二: 一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)
解决方案
- 缓存空对象
缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)
缓存空对象会有两个问题:
(1) value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法 是针对这类数据设置一个较短的过期时间,让其自动剔除。
(2) 缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添 加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象
-
布隆过滤器
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
说明
布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
-
用户合法性效验
对用户的请求合法性进行效验,拦截恶意重复请求
缓存雪崩
Redis数据失效
由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不可用(宕机)或者大量缓存由于超时时间相同在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达存储层,存储层压力过大导致系统雪崩。
解决方案
-
可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。
-
采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底
-
缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)
缓存击穿
定点穿透
针对某一热点数据进行大量请求的操作
系统中存在以下两个问题时需要引起注意:
- 当前key是一个热点key(例如一个秒杀活动),并发量非常大。
- 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。
在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。
解决方案
- 分布式互斥锁
只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)
- 设置热点数据永不过期
- 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
- 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓
2种方案对比
-
分布式互斥锁
这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。
-
“永远不过期”:
这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。