Redis学习记录

简介:

​ 非关系型数据库

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可以干什么
  1. 内存存储,持久化,内存中是断电即失、所以持久化很重要(rdb、aof)
  2. 效率高,可用于告诉存储
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器
特性
  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事物
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为什么是单线程的,但是效率还是这么快
  1. Redis是将所有的数据都放在内存中的,内存的读写速度非常快
  2. redis是单线程的,省去了很多上下文切换线程的时间;
  3. 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
  1. 配置文件对带小写不敏感。

  2. 可以包含多个配置文件

    	include /路径
    
  3. 网络

    	bind IP		# 绑定的IP
    	protected-mode yes  # 保护模式
    	prot 6379   # 端口
    
  4. 通用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
    
  5. 快照

    持久化,在规定的时间内,执行了多少操作,则会持久化到文件, .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

触发机制

  1. save的规则满足的情况下,会自动触发rdb规则
  2. 执行flushall命令
  3. 退出Redis

如何恢复rdb文件

  1. 只需要将rdb文件放在Redis启动目录下就可以了,Redis启动的时候会自动检查dump.rdb然后恢复其中的数据。

优点

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

缺点

  1. 需要一定的时间间隔进行操作,如果Redis意外宕机了,会丢失最后一次修改数据
  2. 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. 每一次修改都同步,文件的完整性会更好

缺点:

  1. 相对于数据文件来说,aof远远大于rdb,修复速度相对来说也慢
  2. 运行效率也比rdb慢,所以默认是rdb
Redis订阅与发布
	发布/订阅(pub/sub)是一种消息通信模式,
	publish channel message => 消息发布者
	subscribe channel ... => 消息订阅者
    onsubscribe channel  => 退订

订阅某个频道后Redis-server里维护了一个字典,字典的键就是一个个频道,而字典的值就是一个链表。

链表中保存了所有订阅这个频道的客户端。

	发布者编辑文章后发送到一个频道中 => 每个频道都有一个链表里面包含所有订阅的用户

使用场景

  1. 实时消息系统。
  2. 实时聊天(每个频道当做一个聊天室)
  3. 订阅与关注

稍微复杂的场景需要使用消息中间件来处理

Redis主从复制

将一台Redis服务器的数据,复制到其他的Redis服务器上。前者称为主节点(master/leader),后者称为从节点(Slave/follower);数据的复制是单向的,只能由主节点到从节点,master以写为主,slave以读为主。

默认情况下每台Redis服务器都是主节点,且一个主节点可以有多个从节点。

Redis最大使用内存不能超过20G

主从复制,读写分离来减缓服务器压力。

主要作用
  1. 数据冗余
  2. 故障恢复
  3. 负载均衡
  4. 高可用
环境配置

只配置从库,不用配置主库。

	# 查看当前库的信息
	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

复制配置文件,然后修改对应的信息

  1. 端口
  2. pid名称
  3. Log日志名称
  4. 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

  1. 创建并编写哨兵配置文件 sentinel.conf
	# 1表示如果主机挂了,就会进行投票来决定谁称为新的主机
	sentinel monitor myredis 127.0.0.1 6379 1
	
	redis-server 

如果主机回来了,只能归并到新的主机下,当做从机。

  1. 哨兵集群基于主从复制模式,所有的主从配置有点它全有

  2. 主从可以切换,故障可以转移,系统的可用性 更好

  3. 主从模式的升级,从手动到自动

  4. 不好在线扩容,集群容量一旦到达上线,在线扩容十分麻烦

  5. 实现哨兵模式的配置是很麻烦的

缓存穿透和雪崩
缓存穿透

穿过Redis和数据库

缓存穿透是指查询一个根本不存在的数据缓存层和持久层都不会命中。在日常工作中出于容错的考虑,如果从持久层查不到数据则不写入缓存层,缓存穿透将导致不存在的数据每次请求都要到持久层去查询,失去了缓存保护后端持久的意义。

	# 客户端发送请求后发现缓存里面没有想要的数据,然后继续向数据库中查找,数据库中也不存在让后返回空值给客户端。
	客户端 => 缓存 => 数据库

缓存穿透问题可能会使后端存储负载加大,由于很多后端持久层不具备高并发性,甚至可能造成后端存储宕机。通常可以在程序中统计总调用数、缓存层命中数、如果同一个Key的缓存命中率很低,可能就是出现了缓存穿透问题。

造成缓存穿透的基本原因

一: 自身业务代码或者数据出现问题(例如:set 和 get 的key不一致)

二: 一些恶意攻击、爬虫等造成大量空命中(爬取线上商城商品数据,超大循环递增商品的ID)

解决方案

  1. 缓存空对象

缓存空对象:是指在持久层没有命中的情况下,对key进行set (key,null)

缓存空对象会有两个问题:

​ (1) value为null 不代表不占用内存空间,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间,比较有效的方法 是针对这类数据设置一个较短的过期时间,让其自动剔除。

​ (2) 缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添 加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象

  1. 布隆过滤器

    ​ 在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。

    说明

    ​ 布隆过滤器实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。

  2. 用户合法性效验

    对用户的请求合法性进行效验,拦截恶意重复请求

缓存雪崩

Redis数据失效

由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不可用(宕机)或者大量缓存由于超时时间相同在同一时间段失效(大批key失效/热点数据失效),大量请求直接到达存储层,存储层压力过大导致系统雪崩。

解决方案

  1. 可以把缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。利用sentinel或cluster实现。

  2. 采用多级缓存,本地进程作为一级缓存,redis作为二级缓存,不同级别的缓存设置的超时时间不同,即使某级缓存过期了,也有其他级别缓存兜底

  3. 缓存的过期时间用随机值,尽量让不同的key的过期时间不同(例如:定时任务新建大批量key,设置的过期时间相同)

缓存击穿

定点穿透

针对某一热点数据进行大量请求的操作

系统中存在以下两个问题时需要引起注意:

  • 当前key是一个热点key(例如一个秒杀活动),并发量非常大。
  • 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。

在缓存失效的瞬间,有大量线程来重建缓存,造成后端负载加大,甚至可能会让应用崩溃。

解决方案

  1. 分布式互斥锁

只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。set(key,value,timeout)

  1. 设置热点数据永不过期
  • 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
  • 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去更新缓

2种方案对比

  1. 分布式互斥锁

    ​ 这种方案思路比较简单,但是存在一定的隐患,如果在查询数据库 + 和 重建缓存(key失效后进行了大量的计算)时间过长,也可能会存在死锁和线程池阻塞的风险,高并发情景下吞吐量会大大降低!但是这种方法能够较好地降低后端存储负载,并在一致性上做得比较好。

  2. “永远不过期”:

    ​ 这种方案由于没有设置真正的过期时间,实际上已经不存在热点key产生的一系列危害,但是会存在数据不一致的情况,同时代码复杂度会增大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值