Home
Posts
Categories
Tags
About
Redis
发布于: 2024-7-17   更新于: 2024-7-17   收录于: 数据库
文章字数: 538   阅读时间: 3 分钟   阅读量:

2.1 Redis的应用场景

  • 缓存:用作缓存层,减少数据库负载,提高数据读取速度
  • 实时系统:redis支持快速的数据写入和读取,非常适合实时分析
  • 消息队列:利用Redis的list和pub/sub,可以实现轻量级的消息队列
  • 分布式锁:Redis可以用作分布式锁的实现,确保在分布式系统中资源的安全访问,避免竟态条件
  • 计数器:Redis的原子性操作非常适合用作计数器

2.2 内存存储

2.2.1 redis为什么这么快

  • reids将数据存储在内存中,提供那个快速的读写速度,相比传统的磁盘数据库,内存访问速度快很多
  • redis使用单线程事件驱动模型结合I/O多路复用,提高了并发效率(6.0引入多线程:随着数据规模的增长,请求量的增加,redis的执行瓶颈主要在于网络I/O,引入多线程处理可以提高网络I/O的处理效率,减少阻塞)
  • 提供了多种高效数据结构

2.2.2 内存淘汰机制

redis的内存淘汰策略一共由8种

  • 不淘汰数据(默认):
  1. noeviction:当运行内存超过最大设置内存的时候,不会淘汰数据,而是直接返回报错禁止写入
  • 设置了过期事件的数据淘汰
  1. volatile_random:随机淘汰掉设置了过期时间的key
  2. volatile-ttl:优先淘汰掉较早过期的key
  3. volatile-lfu(3.0之前默认策略):淘汰掉所有设置了过期时间的,然后醉酒未使用的key
  4. volatile-lfu(4.0后新增):与上面类似,淘汰掉最少使用的key
  • 所有数据的数据淘汰:
  1. allkeys-random:随机淘汰掉任意的key
  2. allkeys-lru:淘汰掉缓存中最久没有使用的key
  3. allkeys-lfu(4.0后新增):淘汰掉缓存中最少使用的key

2.2.3 过期删除机制

  • 定期删除(每隔一段时间(默认100毫秒)随机检查一定数量的键,如果发现过期键则删除)
  • 惰性删除:只有查询到相关数据才执行检查和删除操作,容易造成内存泄漏

2.2.4 内存碎片化

redis的内存碎片化是指内存使用中出现小块空间被闲置,无法被有效利用

  • redis默认使用jemalloc作为内存分配器,它是按照固定大小来分配内存的
  • INFO memory:可以通过INFO memory来查看内存碎片率(mem_fragmentation_ratio)
# Memory
used_memory:1000000  ## 实际申请的内存空间
used_memory_human:977.54K
used_memory_rss:1200000  ##表示实际占用的物理内存空间(含内存碎片)
used_memory_rss_human:1.14M
mem_fragmentation_ratio:1.20

ratio大于1证明存在内存碎片(过大需要清理),小于一说明已经使用swap用上磁盘空间了。

  • 可以通过重启,重新分配内存解决,或者MEMORY PURGE

2.2.5 虚拟内存(VM)机制

Redis的VM机制曾经是redis早期版本(2.0之前)的一部分,用于将部分数据存储到磁盘,以扩展内存数据库的容量,当内存不足时,redis将冷数据(不经常访问的数据)移到磁盘,并将热数据(经常访问的数据)保留到内存中

  • VM机制虽然使能使用的数据变多了,但也降低了高并发的性能,所以2.0之后不在使用

2.2.6 缓存和数据库的数据一致性

  1. 先更新缓存,再更新数据库
  2. 先更新数据库存,再更新缓存
  3. 先删除缓存,再更新数据库,后续等查询把数据库的数据回种到缓存中
  4. 先更新数据库,再删除缓存,后续查询把数据库的数据回种到缓存中
  5. 缓存双删策略,更新数据库之前,删除一次 缓存,更新完数据库之后,再进行一次延迟删除
  6. 使用binlog异步更新缓存,监听数据库的binlog变化,通过异步方式更新redis缓存

2.2.7 缓存穿透,缓存击穿,缓存雪崩

  1. 缓存穿透
  • 概述:查询不存在的数据,导致请求直接打到数据库。
  • 原因:用户请求的 key 不在缓存中,攻击者使用大量不存在的 key。
  • 解决方案:使用布隆过滤器、缓存空值、请求限流(对异常流量进行限流和监控。)。
  1. 缓存击穿
  • 概述:热点 key 缓存过期,瞬间大量请求直接访问数据库。
  • 原因:热点数据的缓存过期,导致短时间内大量请求。
  • 解决方案:加互斥锁、预加载数据(在缓存即将过期之前,提前更新缓存,确保热点数据一直有效)、热点数据永不过期。
  1. 缓存雪崩
  • 概述:大量缓存同时过期,导致数据库压力激增。
  • 原因:缓存系统重启或多个 key 同时过期。
  • 解决方案:分散过期时间(为不同的缓存 key 设置不同的过期时间,避免集中失效。)、双缓存策略,将数据同时存储在两层缓存中

2.2.7.1 如何使用redis快速实现布隆过滤器

布隆过滤器是一种高效的概率数据结构,常用于检测一个 元素是否在一个集合中,可以有效减少数据库的查询次数,解决缓存穿透等问题 **1.可以使用位图来实现布隆过滤器

  • 使用位图的setbit和getbit操作来实现布隆过滤器,位图本质上是一个比特数组,用于标识元素是否存在
  • 对于给定的数据,通过多个哈希函数计算位置索引,将位图中响应位置设置为1,表示该元素可能存在 2.可以使用redisbloom模块
  • redis提供了官方模块redisbloom,封装了哈希函数、位图大小等操作,可以直接用于创建和管理布隆过滤器
  • 使用BF.ADD来向布隆过滤器添加元素,使用BF.EXISTS来检查某个元素是否可能存在

2.3 数据结构

2.3.1 常见数据类型

  • String (存储任意类型数据,最大长度512MB)(用于缓存和计数器,分布式锁,分布式session) 底层:基于SDS(simple dynamic string简单动态字符串)结构 并结合int、embstr、raw等不同的编码方式进行优化存储,SDS:使用len字段存储当前字符串的长度,因此可以在O(1)的时间复杂度下获取字符串长度 字符串长度小于44字节使用embs存储:redis使用jemalloc作为内存分配器,它以64字节作为内存单位进行内存分配,如果超过64字节,就超过了一个内存单位使用raw编码,而redis的字符串由redisobject和sdshdr这两个部分组成,redisobject大小为16字节,sdshdr(字符数组加上额外三字节存储信息,同时字符数组内还有’/0’)最后能用的只有44字节

  • List(任务调度和消息传递) 常用命令:lpush,rpush,lpop,rpop,lrange,lindex,llen,lset,lrem,ltrim 底层数据结构:linked list,ziplist(数据较少时使用)

  • Hash(快速检索) 一种键值对集合,适合存储对象类型的数据,适合小数据,增删查改效率高

    • redis6之前底层式ziplist+hashtable,redis7之后是Listpack+hashtable
  • Set(无序,使用哈希表实现,支持快速查找和去重操作)(标签系统,唯一用户集合)

  • Sorted set 又称Zset(每个元素都有一个score用于排序,底层使用跳表实现)(排行榜,任务调度) 跳表:使用 多层链表实现,底层保留所有元素,而每一层链表都死下一层的子集,(插入,删除,查找都是从最高层逐层查找,更新) 四种高级数据类型

  • BitMap

  • HyperLogLog:基数估计算法,可用于快速计算一个集合中的不同元素数量的近似值(可以用来统计页面uv,比如将访问者ip地址作为元素加入到hyperloglog中)(无论集合有多少元素,hyperloglog结构的大小固定为12kb,这种内存效率使他非常适合处理大数据集的基数估计)(标准误差是0.81%) 原理: 1. 哈希函数 2. 前导零计数 3. 存储最大值 4. 基数估计

  • GEO(geolocation的缩写,地理坐标)底层使用sorted set 结合了geohash编码算法

  • Stream

2.3.2 redis如何实现队列和栈

  • 可以通过List类型来实现队列和栈
  • 队列使用LPUSH和RPOP
  • 栈使用LPUSH和LPOP

2.3.3 redis如何快速实现排行榜

使用sorted set

  1. 使用sorted set存储分数和成员(Zadd命令)
  2. 获取排名(zrank)
  3. 获取前N名(Zrevrange)

2.3.3 Ziplist ,QuickList,listpack

  • ziplist存储在单个连续的内存块中,十分节省空间(存储元素可以是字符串或整数) ![[Pasted image 20241017175959.png]]

  • quicklist结合了ziplist和双端链表的优点,每个quicklist节点都是一个ziplist他限制了单个ziplist的大小,降低了级联更新产生的影响 ![[Pasted image 20241017181033.png]]

  • listpack是一种紧凑的序列化数据结构,用来替代ziplist(6.0引入,在list、hash、zset的内部实现中使用)(本质是字节数组)(因为ziplist的每个entry会记录之前entry的长度,所以会导致连锁更新,而listpack仅记录自己的长度)


2.4 持久化

2.4.1 RDB

  • (redis database backup)快照: rdb是通过生成某个时刻的数据快照来实现持久化的,可以在特定时间间隔内保存数据的快照,适合灾难恢复和备份,能生成紧凑的二进制文件,但可能会在崩溃丢失最后一次快照之后的数据 持久化命令 save:在主线程生成RDB文件,因此生成期间,主程序无法执行官读写命令 bgsave:利用fork操作得到子线程,在子线程执行RDB生成,不会阻塞主进程,默认使用bgsave

2.4.1.1 生成rdb文件时如何处理请求

在redis生成rdb文件是异步的(使用bgsave)使用子线程执行rdb的生成过程,主线程继续处理客户端请求(如果此时主线程需要修改数据,则通过写时复制,让主线程对复制的副本进行修改)

2.4.2 AOP

  • AOP(append only file) 日志(更可靠,每次写操作命令执行执行完毕后都会落盘存储,但恢复起来更耗时)(AOP机制生生成的AOP文件比RDB文件更大): aop通过每个写操作追加到日志文件中实现持久化,支持将所有写操作记录下来以便恢复,数据恢复更为精确,但文件体积较大,重写时可能会消耗更多的资源 redis4.0新增了rdb和aop的混合持久机制

2.4.2.1 写回策略

  1. always:每次写操作后立刻调用fsync,将数据同步到磁盘
  2. everysec:每秒调用一次fsync,将数据同步到磁盘
  3. no:有操作系统决定何时将数据写入磁盘,通常,磁盘会在一定时间后或缓冲区满时同步数据到磁盘
  • 即使是always也可能会导致数据丢失,因为redis是先执行命令,再写入aof

2.4.2.2 重写机制

  • aof文件随着写操作的增加会不断变大,过大的aof文件会导致恢复速度变慢,并消耗大量磁盘空间,所以,redis提供了aof重写机制,挤兑aof文件进行压缩,通过最少的命令来重新生成一个等效的aof文件
  • aof重写可以通过手动触发或自动触发
  1. 手动使用BGREWRITEAOF来触发
  2. 自动触发:通过配置文件中的参数控制自动触发条件 auto-aof-rewrite-min-size auto-aof-rewrite-percentage

2.4.2.3 AOF文件恢复

如果aof文件因系统崩溃等原因损坏,可以使用redis-check-aof工具修复,该工具会阶段文件中的不完整命令,使其恢复到一致状态

2.4.3 混合机制

  • rdb记录当前时间的数据快照,aof记录增量数据操作,恢复时先加载rdb快照,在加载aop来执行指令来恢复

2.5 事务

redis支持事务,但它的事务与MySQL的事务有所不同,MyAQL中的事务主要支持ACID的特性,而redis中的事务主要保证 的是多个命令执行的原子性,及所有的命令在一个原子操作中执行,不会被打断

  • MySQL中的事务是支持回滚的,而redis中的事务不支持回滚的
  • 命令
  1. MULTI:开始事务,命令开始排队
  2. 添加命令
  3. EXEC:执行队列中的所有命令,确保原子性
  4. DISCARD:放弃事务,清空命令队列
  5. WATCH(实现乐观锁):使用watch命令可以监视一个或多个键,如果在事务执行前这些键被修改,则exex将不会执行,确保数据一致性;

2.5.1 redis事务与关系型数据库事务的主要区别

  • 原子性:redis提供部分原子性,因为如果一个命令执行失败,其他命令仍继续执行,事务不会自动回滚
  • 一致性:无法回滚,所以无法保证一致性
  • 隔离性:redis采用的单线程模型,在执行事务的过程中其他命令不会插入到事务执行的过程中,但无法设置不同隔离级别
  • 持久性:redis事务的持久性依赖于配置(如rdb和aop)但理论上依然会丢失数据

2.6 集群

2.6.1 实现原理

  • redis集群通过多个redis实例组哼,每个实例存储部分的数据(每个实例之间的数据是不重复的)
  • 具体是采用哈希槽(通常是一个简单的数组,主要用于映射,其中每个索引对应一个槽,而哈希桶通常是链表红黑树和其他动态数据结构组成)(hashslot)机制来分配数据,将整个空间划分为16384个槽,每个实例负责一定范围的哈希槽,数据的key经过哈希函数计算后对16384取余即可定位到响应节点 即使用hashslot对数据进行分片(每个片可以包含一个主节点和多个从节点)
  • 客户端发送请求时,会通过任意节点进行连接,存储则返回,反之则根据哈希槽机制确定节点后,路由到响应节点
  • 每个节点都会保存集群的完整拓扑信息,包括每个节点的IP,ID,端口,负责的哈希槽范围
  • 使用gossip协议: 特点
    1. 分布式信息传播:每个节点定期向其他节点发送其状态信息(通过发送心跳数据包实现)
    2. 低延迟和高效率:设计为轻量级的通信方式,能够快速传播信息,减少单点故障带来的风险
    3. 去中心化:没有中心节点,所有节点平等的参与信息传播

2.6.1.1 为什么哈希槽节点数为16384

  • 二的幂次:16384是2的14次方,这使得在计算哈希槽时(如取模运算)非常高效,因为可以使用位运算来加速计算。
  • 消息大小:心跳数据包中有一条发送节点负责的槽信息,myslots[sluster_slots/6],这个是最占空间的,如果奥=槽位是16384,这块的大小是2kb,再大就会占用更多空间, ping心跳包是消头就太大了,浪费带宽,
  • 集群规模,集群不太可能会扩展超过1000个节点,16384够用且能使每个分片下的槽又不会太少

2.6.2 主从复制

redis的主从复制是指一个redis实例(主节点)可以将数据复制到一个或多个从节点,从节点从主节点获取数据并保持同步 复制流程:

  • 连接:从节点向主节点发送psync命令建立连接
  • 全量复制:如果第一次连接或者之前的连接失效,从节点会请求全量复制,主节点将当前数据快照(rdb文件)发送给从文件(runid没值会触发,因为从节点第一次发送psync ? -1,?证明从节点不知道主节点的runid,-1为offset,表示复制进度)(主节点rdb文件生成过程中收到的写入命令会存储到replication buffer中,从节点先清空数据,在加载rdb,加载完毕后主节点才会把replication buffer发给从节点)
  • 增量复制:全量复制完毕后,主从之间会保持一个长连接,主节点会通过这个连接将后续的写操作传递到从节点执行,来保证数据的一致性

replication buffer和repl_backlog_buffer

  • 大小和数量:因为不同从节点同步速度不一样,主节点会为每个从节点都创建一个replication buffer,它用于实时传输写命令,大小是动态的,没有大小限制,可以通过client-output-buffer-limit简介控制缓冲区大小(当缓冲区大小超出限制,就会断开与客户端的连接) repl_backlog_buffer在主节点只有一个,是一个环形缓冲区,大小只有1m,超过时则会被覆盖,可以使用从节点的offset来判断当前命令是否在repl_backlog_buffer,从而执行增量查询,否则不行
  • 用处Replication Buffer:用于实时接收并缓存来自主节点的命令。 Repl Backlog Buffer:用于存储已经发送但尚未确认的命令,以便在从节点断开后重新连接时进行增量复制。

2.6.2.1 主从复制常见拓扑结构

  1. 一主多从:最基本的拓扑结构,包括一个主节点和多个从节点,所有写操作在主节点上执行,而读操作可以在从节点上进行,以提高读取速度和负载均衡
  2. 树状主从结构(级联):从节点可以作为其他从节点的主节点,这样就形成了一个层次结构,主节点负责写操作,而从节点负责读操作,并将数据再次复制到更下一级的从节点,
    • 因为主从复制对主节点有压力,所以这样的结构可以减轻主节点的压力
  3. 主主结构(双主或多主):有两个或多个主节点,它们之间互相复制数据,这种结构提高了系统的写能力和容错性,但需要处理多节点之间的数据同步和冲突解决,管理复杂度高,redis默认不支持主主复制

2.6.2.2 redis复制延迟的常见原因

redis的复制延迟是指从节点同步主节点数据时可能出现时间延迟,在读写分离场景下,这个延迟会导致明明写入了数据,但是去从节点查的时候没查到

  1. 网络原因
  • 网络延迟(Latency)
    • 概念:数据从发送端到达接收端所需的时间,通常以毫秒(ms)为单位。
    • 影响:延迟越高,主节点与从节点之间的同步速度越慢,可能导致从节点数据滞后。
  • 带宽不足(Bandwidth Insufficiency)
    • 概念:单位时间内可以传输的最大数据量,带宽不足时传输速率变慢。
    • 影响:如果带宽不足,大量数据传输需要更多时间,从节点无法及时接收到主节点的数据,导致同步延迟。
  • 丢包(Packet Loss)
    • 概念:数据包在网络传输过程中丢失,通常需要重传。
    • 影响:丢包增加传输时间,影响从节点数据同步的及时性,可能导致更大的延迟甚至数据丢失。
  • 网络抖动(Jitter)
    • 概念:数据包传输时间的波动,即数据包到达时间不一致。
    • 影响:抖动会导致数据传输不稳定,需要额外的重传或重排序,从而影响从节点同步数据的稳定性,增加延迟。
  1. 主节点负载过高
  2. 复制缓存区溢出
  3. 主节点持久化,无法及时响应复制请求
  4. 从节点配置太差

2.6.2.3 哨兵机制

redis的哨兵机制是一种高可用性解决方案,用于监控redis主从集群,自动完成主从切换,以实现故障自动恢复和通知。 主要功能:

  • 监控:哨兵不断监控redis主节点和从节点的运行状态,定期发送ping请求检查节点是否正常
  • 自动故障转移:当主节点发生故障时,哨兵会选举一个从节点提升为新的主节点,并通知客户端更新主节点的地址,从而实现高可用
  • 通知:哨兵可以向系统管理员或其他服务发送通知,以便快速处理redis实例的状态变化
哨兵机制数据结构
  1. 哈希表(Hash Table)
  • 功能:存储主节点、从节点和哨兵节点的状态信息,包括节点地址、端口、故障检测状态等。
  • 作用:哨兵使用哈希表快速查找和更新节点状态,便于监控节点的健康状况。
  1. 发布/订阅(Pub/Sub)机制
  • 功能:实现哨兵节点之间的通信,发送心跳包、通知主节点故障和选举结果。
  • 作用:确保哨兵节点可以互相同步状态信息,及时响应主节点的故障,并选举新主节点。
  1. 列表(List)
  • 功能:存储选举过程中产生的投票信息。
  • 作用:在主从切换时,记录哨兵节点之间的投票顺序和结果,确保选举过程的有序性。
  1. 集合(Set)
  • 功能:管理参与监控的哨兵节点、主节点、从节点的列表。
  • 作用:确保哨兵节点能跟踪所有被监控的节点信息,并避免重复监控。
  1. 跳表(Skip List)
  • 功能:管理时间敏感的任务,如定期的健康检查。
  • 作用:跳表结构支持高效的排序和查找,便于管理周期性事件,例如定期对主从节点的健康检查。
  1. 队列(Queue)
  • 功能:管理异步任务和事件。
  • 作用:用于管理待处理的任务,比如检测到故障后,处理主从切换的各个步骤。

2.6.2.4 订阅发布功能(publish/subscibe)

订阅发布功能是一种消息通信机制,用于在不同客户端之间实现消息的实时传递和广播,使用pub/sub模型,客户端可以订阅一个或多个频道,当有其他客户端向这些频道发步消息时,所有订阅该频道的客户端都会立即受到消息

  • 发布
  • 订阅 ![[Pasted image 20241020110343.png]]订阅发布机制不会存储消息(rdb和aof都不会存储数据),实际上是维护了一个映射关系,当生产者往队列里发送一条消息,redis不做任何存储动作,立即查找映射关系,立马转发给具有映射关系的消费者
  • 所以分布订阅模型的缺点:会丢数据

2.6.3 脑裂问题

集群存在脑裂问题的风险,特别是在网络分区的情况下,可能会导致同一集群内出现多个主节点节点,导致数据不一致

  • 可以通过设置两个参数来减少脑裂的发生
  1. min-slaves-to-write
  2. min-slaves-max-lag

2.7 批处理

2.7.1 MSET/MGET(原生批处理命令)

  • MSET/MGET是redis提供的原生批处理命令,用于批量设置和获取多个键值
  • 它们是单个命令,可以一次操作多个键值对,因此只需要一次网络往返
MSET key1 value1 key2 value2 key3 value3
MGET key1 key2 key3
  • 无法处理其他类型的redis操作

2.7.2 pipeline

pipeline是redis的一种机制,允许客户端一次发送多个命令,redis会批量处理这些命令,然后将结果依次返回,可以大幅度减少网络延迟

  • 无法保证原子性
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

public class RedisPipelineExample {
    public static void main(String[] args) {
        // 创建 Redis 连接
        Jedis jedis = new Jedis("localhost", 6379);

        // 开始 Pipeline
        Pipeline pipeline = jedis.pipelined();

        // 批量设置键值对
        for (int i = 0; i < 1000; i++) {
            pipeline.set("key" + i, "value" + i);
        }

        // 执行所有命令
        pipeline.sync();

        // 关闭连接
        jedis.close();
    }
}

2.7.3 Lua脚本

  • Lua是一种轻量级的嵌入式脚本语言,通常用于嵌入到其他应用中。它设计得非常简洁,执行效率高。
  • Lua 脚本是在 Redis 服务器内部执行的,这意味着所有操作都在服务器上处理,减少了网络延迟。 redis的Lua脚本功能允许用户在redis在redis服务器执行自定义的Lua脚本,以实现原子操作和复杂逻辑: 其核心点包括
  • 原子性:本身不具备原子性,但redis的命令是单线程执行的
  • 减少网络往返次数:通过在服务器执行脚本,减少了客户端和服务器之间的网络往返次数,提高了性能
  • 复杂操作:可以在Lua脚本中执行复杂的逻辑,比如批量更新、条件更新等,超过了单个redis命令的能力 基本结构
  • 调用redis命令:使用redis.call调用redis原生命令,使用redis.pcall进行安全调用,如果脚本中的某些命令失败,可以捕获错误,而不是直接中断执行,这样剋在脚本内处理错误
  • 参数传递: keys:表示传入的键名数组 argv:表示传入的参数数组
EVAL 
"local value = redis.call('GET', KEYS[1]) 
if not value then redis.call('SET', KEYS[1], ARGV[1])
end 
return value" 1 mykey "default_value"

2.8 分布式锁

redisson是基于redis实现的分布式锁,实际上是使用redis的原子操作来确保多线程、多进程或多节点系统中,只有一个线程能获得锁,避免并发操作导致的数据不一致问题

  1. 锁的获取:redisson使用Lua脚本,利用exists+hexists(Hash Exists)+hincrby(Hash Increment By)命令来保证只有一个线程能成功设置键(表示获得锁),pexpire设置锁过期时间
  2. 锁的续期:为了防止锁持有过程中过期导致其他线程抢占锁,redisson实现了锁自动续期,即更新锁的过期时间,确保任务没有完成时锁不会失效
  3. 锁的释放:锁释放时,redisson也是通过Lua脚本保证释放操作的原子性,利用del确保只有持有锁的线程才能释放锁,防止五释放锁的情况;Lua同时利用publish名令,广播唤醒其他等待的线程。
  4. 可重入锁:redisson支持可重如锁,持有锁的线程可以多次获取同一把锁而不被阻塞,具体是利用redis中的哈希结构,哈希中的key为线程ID,如果重入则value+1,如果释放则value-1,减到0则说明锁被释放,则del锁
    <T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, command,
                "if ((redis.call('exists', KEYS[1]) == 0) " +
                            "or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then " +
                        "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                        "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                        "return nil; " +
                    "end; " +
                    "return redis.call('pttl', KEYS[1]);",
                Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));
    }

2.8.1 redis如何实现分布式锁

在redis中实现分布式锁的常见方法是通过set ex(expire)(过期) nx(Not exists)命令+lua脚本组合使用,确保多个客户端不会获得同一个资源锁的同时,也保证了安全解锁和意外情况下锁的自动释放 一般业务使用主从+哨兵来实现分布式锁

2.8.2 redlock

redlock是redis官方推荐的一种实现分布式锁的算法,适用于集群环境下 基本思想

  • 部署多个redis实例(通常为5个)
  • 客户端在大多数实例(至少3个)上请求锁,并在一定时间内获得成功,表示加锁成功
  • 适用redlock可以提供更高的容错性,即使部分redis实例故障,仍然可以获得锁 缺点:需要多个redis实例,时间同步依赖,不适合高并发,锁的续期问题

2.9 问题

bigkey 热点key

目录
相关文章
MySQL
1.1 SQL语句执行流程 先通过连接器校验权限 利用分析器进行SQL语句的词法分析和语法饭呢西,构建解析树 使用优化器选择合适的索引和表连接顺序,最终选择一个最佳的执行计划 利用执行器,调用引擎层查询数据,返回结果集中给客户端 1.2 MYSQL的存储引擎 InnoDB:是mysql默认的存储引擎,支持事务,表级锁和力度更小的行级锁,具有事务提交,回滚和数据崩溃恢复的功能 changebuffer:是Innodb存储引擎中的一个机制,用于暂存对二级索引的插入和更新操作的变更,而不立即去执行,当合适条件时,再写入到二级索引中, changebuffer:提高写入性能,减少对磁盘的频繁写入,批量操作 MyISAM:是之前Mysql的默认引擎,不支持事务和行级锁,支持表级锁,锁的粒度较大,更新性能较差,更适合读多写少的场景 Memory:相较于InnoDB和MyISAM,Memory是存在内存中的,速度更快,但是不具备持久化能力,适合临时存储 1.3 数据排序 1.3.1索引排序 1.3.1.1 索引的数据结构: B+树(又可分为聚簇索引和非聚簇索引): B+树(所有实际数据(值))都存在叶子节点,所有叶子节点又通过链表连接内部节点不存储数据,用于存储指向子节点的指针和索引信息(关键字(它们通常对应于数据库表中某些字段的值)) 定义:聚簇索引是一种将表中的数据行按索引的顺序存储的索引。数据的物理存储顺序与索引顺序相同。 特点: 每个表只能有一个聚簇索引,因为数据只能按一种顺序存放。 主要用于主键(Primary Key)。 查询速度快,因为可以直接访问存储的数据。 例子:在一个员工表中,使用员工ID作为聚簇索引,员工记录将按照员工ID的顺序存储。 定义:非聚簇索引是一种将索引结构与表中的数据分开的索引。索引存储的是指向数据行的指针,而不是数据本身。 特点: 一个表可以有多个非聚簇索引,用于不同的列。 查询时需要先查找非聚簇索引,再通过指针找到实际的数据,速度相对较慢。 例子:在同一个员工表中,如果为姓氏创建非聚簇索引,索引将存储姓氏的值和对应员工ID的指针。 哈希,倒排,R-树 1.3.1.2联合索引 联合索引(Composite Index)是数据库中的一种索引类型,它由多个列组成。与单列索引不同,联合索引可以提高对多个列的查询性能,特别是在涉及到这些列的组合条件时。 特点 由多个列组成:联合索引可以包含两个或更多的列,这使得它适用于复杂查询。 顺序敏感:联合索引中列的顺序非常重要。在创建索引时,最左边的列会影响索引的使用方式。 提高查询性能:联合索引可以加速对涉及多个列的查询,例如,使用WHERE条件的查询、ORDER BY排序等。 1.3.1.2.1 最左前缀匹配原则 1.3.1.2.2 覆盖索引 1.3.1.2.3 索引下推 应用在联合索引上 1.3.2 文件排序 sort_buffer
2024-7-17
Leetcode刷题心得
哈希 字母异位词 先将字符数组排序把异位词处理成相同格式,方便分类 getOrDefault(key,new class)方法,当集合中存在相应键值对,则取出相应的值,若没有,则创建新的键值对并返回相应的值 class Solution { public List<List<String>> groupAnagrams(String[] strs) { Map<String,List<String>> reflect=new HashMap<>(); boolean contain =false; for(String str:strs){ char[] array=str.toCharArray(); Arrays.sort(array); String key=new String(array); List<String> list=reflect.getOrDefault(key,new ArrayList<String>()); list.add(str); reflect.put(key,list); } return new ArrayList<List<String>>(reflect.values()); } } 双指针 盛最多水的容器 设计两个指针指向数组两端,移动较小的那个元素,因为水桶盛水量取决于较小的那一块,每移动一次就舍弃一个元素,因为当前计算结果就是当前以此元素为边的最大容量(不算舍弃过的元素(并不是真的舍弃,只是后续不再使用)) class Solution { public int maxArea(int[] height) { int length=height.
2024-7-17
心理学与生活
感知与记忆 颜色 *超越视觉 颜色不仅仅是我们对光波的一种感知,更是一种视觉经验 视网膜上对颜色识别起作用的细胞叫视锥细胞 知觉 *超越理解 知觉分为觉察、分辨、确定 知觉特性: 选择性(selectivity) 解释性(comprehension) 整体性(integrality) 恒常性(permanent) 错觉 *超越事实 外界纷繁的刺激,我们丰富的生活,都不断的帮助我们建立一个更好的视觉系统,让它发挥更好的作用 我们视觉系统的建立,伴随着大量的经验堆叠,以及视觉神经元在此过程中建立的联系 我们常说 耳听为虚,眼见为实,但实际上,眼见也未必为实 记忆 *给我一杯忘情水 记忆分为 外显记忆(explicit memory): 内隐记忆(implicit memory):不需要意志控制,不需要意志努力 陈述性记忆(declarative memory):一种依靠语言描述来进行的记忆 程序性记忆(procedural memory):一种关于怎么做事情的过程、步骤等具体操作的记忆 情景记忆(episodic memory):一种对于过去经验中时间、地点、过程等的记忆 语义记忆(semantic memory):一种对抽象符号的记忆 时间 感觉记忆(sensory memory):0.5~4秒 短时记忆(short-term memory):5秒到1分 长时记忆(long-term memory):1分钟以上 最容易遗忘的记忆 语义记忆 外显记忆 陈述性记忆 难以遗忘的记忆 情景记忆 程序性记忆 内隐记忆 重构 *那些不真实的回忆 记忆重构(memory reconstruction) 记忆的存储过程是一个动态过程,在这个过程中一些已经有的经验会发生变化 人们会利用概括、归类等方式来重构信息 重构过程中,信息变得更加简单,不重要的细节完全被忽略,,突出和强调哪些重要的细节,这个故事就变得更加合理、符合背景和常识
2024-7-17