1. 设置缓存过期时间
2. 数据的更新操作,先删除缓存,再更新数据库。如果我们先更新数据库,会导致其他业务线程读到缓存中的脏数据,所以数据库的更新操作一般是先删缓存
3. 延时双删策略,在第二步的基础上,更新完数据库,让当前线程sleep 0.1秒,然后再删除一次缓存,这样即使出现线程B在更新完数据库之前把数据库中的脏数据读到缓存中,也可以保证缓存和数据库的一致性
1. 一般的缓存系统,都是按照key去缓存查询,如果不存在对用的value,就应该去后端系统查找(比如DB数据库)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
网上很多说对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者对一定不存在的key进行过滤。但是!我个人观点,对这种问题,解决根源还是1. 做好鉴权防止非法用户的攻击,2. 对单个用户、单接口请求进行限流,防止攻击
2. 缓存击穿:某个 key 对应的数据库中存在,但在 redis 中的某个时间节点过期了,此时若有大量并发请求过来,这些请求发现缓存过期,都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。
解决方案:1.热点数据设置永不过期;2.加上互斥锁:上面的现象是多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它 其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后将 数据放到 redis 缓存起来。后面的线程进来发现已经有缓存了,就直接走缓存
3. 缓存雪崩:在高并发情况下,大量的缓存失效,或者缓存层出现故障。于是所有的请求都会达到数据库,数据库的调用量会暴增,造成数据库也会挂掉的情况。
解决方案:1.随机设置 key 失效时间,避免大量 key 集体失效;2.不设置过期时间;3.跑定时任务,在缓存失效前刷进新的缓存
要说redis的高可用机制,我觉得主要分两大类,第一,单机节点进程异常,能够保证恢复后内存中数据可恢复;第二,单集群异常,需要能够自动切换到备集群。这两点都能做到,才能保证redis的高可用
为了解决上面两个问题,redis提供了以下四个机制,从而保证了redis的高可用
redis持久化。Redis是内存数据库,数据都是存储在内存中,为了避免服务器断电等原因导致Redis进程异常退出后数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。
redis持久化方式我在上一篇文章有介绍,一种是定时对数据生成快照保存,当故障发生的时候,则从故障时间点最接近的快照恢复到内存中;另一种是通过把redis的修改操作记录下来,一旦发生故障,则把修改操作按照顺序执行一遍,就恢复到内存中了(有点类似mysql的binlog)
当然,上面两种持久化方式,也可以一起使用,固定时间点生成快照,然后从该时间点开始记录redis的修改操作,这样就可以只保留一段时间的快照和修改操作记录即可,不用从头到尾记录。
redis主从复制。主从复制,是指将一台服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(Slave);数据的复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点,但一个从节点只能有一个主节点。
通过主从复制,我们能够做到数据的远程备份、单节点故障恢复(主节点down机,备节点能够立马启用提供服务)、读写分离(通过把读请求转到备节点,写请求在主节点,实现负载均衡)。redis的主从复制,是redis高可靠的基石
redis哨兵模式。从上面redis主从复制我们可以知道,一个主节点,可以有多个从节点,主节点down了,可以切到从节点继续提供服务,从而实现高可用的机制,但是怎么自动切到从节点呢?这就要用到redis提供的哨兵模式。
哨兵(sentinel)是一个分布式系统,用于对主从结构中每台服务器进行监控,当出现故障时通过投票机制选择新的Master并将所有Slave连接到新的Master。所以整个运行哨兵的集群的数量不得少于3个节点。
哨兵模式的结构一般分为两部分,第一部分是哨兵节点,由一个或多个节点组成,哨兵节点是特殊的redis节点,不存储数据。第二部分是数据节点,就是主从复制中的主节点和从节点都是数据节点
redis集群模式。Redis 3.0开始引入的分布式存储方案,集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点;只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。
集群模式最大的作用就是将数据分区,集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增加;另一方面每个主节点度可以对外提供读服务和写服务,大大提高了集群的响应能力。
Redis单机内存大小受限问题,在介绍持久化和主从复制时都有体积;例如,如果单机内存太大,bgsave和bgrewriteaof的fork操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出。
redis数据分片。要做到集群模式,最重要的就是数据的分片,把这些存储进来的数据,分散到各个redis的主节点,然后主节点通过主从复制再同步到各个从节点。
为了实现数据分片,redis引入了哈希槽的概念。redis集群有16384个哈希槽,每个主节点负责一个或几个哈希槽,redis将每个key进行CRC16校验后对16384取余,按照余值找到对应的哈希槽以及负责该哈希槽的主节点,然后将数据交给该节点进行存取操作等处理。
关于redis高可用机制的实现原理,以及启动redis高可用机制的实践操作,可以参考下面这篇博文,写的非常详细,设置还有操作命令,分享给大家参考:Redis高可用(持久化、主从复制、哨兵、集群) - 玖拾一 - 博客园
1、缩短键值的长度
首先应该在业务上进行精简,去掉不必要的属性,避免存储一些没用的数据;
其次是序列化的工具选择上,应该选择更高效的序列化工具来降低字节数组大小;
以JAVA为例,内置的序列化方式无论从速度还是压缩比都不尽如人意,这时可以选择更高效的序列化工具,如: protostuff,kryo等
2、共享对象池
对象共享池指Redis内部维护[0-9999]的整数对象池。创建大量的整数类型redisObject存在内存开销,每个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。所以Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其他类型如list,hash,set,zset内部元素也可以使用整数对象池。因此开发中在满足需求的前提下,尽量使用整数对象以节省内存。
3、字符串优化
4、编码优化
5、控制key的数量
利用redis set命令的原子性,可以很简单的设计redis内存分布式锁,参考如下代码:
public void testLock() throws InterruptedException {
String uuid = UUID.randomUUID().toString();
Boolean b_lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
if(b_lock){
Object value = redisTemplate.opsForValue().get("num");
if(StringUtils.isEmpty(value)){
return;
}
int num = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num",++num);
Object lockUUID = redisTemplate.opsForValue().get("lock");
if(uuid.equals(lockUUID.toString())){
redisTemplate.delete("lock");
}
}else{
Thread.sleep(100);
testLock();
}
}
在set一个锁的时候如果set成功,则表示抢锁成功,可以进行对该上锁内存段进行处理,否则抢锁失败,sleep 0.1秒再次尝试抢锁,直到抢锁成功为止
分布式锁最大的问题在于超时时间,如果任务在超时时间内还没执行完,就会发生锁定失败而被其他线程入侵的情况;同时,在执行异常的时候,会出现锁未释放的场景(当然,这可以把锁的释放放在finally里面执行避免,但是仍然无法避免线程直接被杀死的情况下会出现这个问题);redis主从复制的问题?