一、redis 基础知识

Mr.WyjJanuary 20, 2023About 28 min

一、redis 基础知识

redis 面试总结open in new window

redis 详解(三)-- 面试题open in new window

redis 和 Mysql 分别处理什么数据

MySQL 处理实时性数据,例如金融数据、交易数据

Redis 处理实时性要求不高的数据,例如网站最热贴排行榜,好友列表等

使用 redis 原因?redis 的好处?

  • 基于 redis 的特性:
  1. 性能极高:Redis 是基于内存的,故此具有较高的读写频率。(能支持超过每秒 10 万次读写操作)
  2. 丰富的数据类型:Redis 可以存储键和五种不同类型的值之间的映射,键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
  3. 原子性:Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作全并后的原子性执行
  4. 丰富的特性:Redis 还支持发布订阅(publish/subscribe), 通知, 键的过期时间等特性
  5. 支持数据的持久化。redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步
  • 基于项目:

    首页是系统的门户,也就是系统的入口,所以首页的访问量是这个系统最大的。如果每次展示首页都从数据库中查询首页的内容信息,那么势必会对数据库造成很大的压力,所以需要使用缓存来减轻数据库压力,redis 可以较好的实现缓存

Redis 的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此 Redis 适合的场景主要局限在较小数据量的高性能操作和运算上

Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。

Redis 集群

  • Redis 3.0 之后开始支持集群
  • Redis 集群没有统一的入口,客户端连接集群的时候连接集群中的任意节点即可,集群内部是通过 ping-pong 机制互相通信的

判断 Redis 集群是否挂掉方式:

  1. Redis 集群有一个投票容错机制,如果集群中超过半数的节点投票认为某个结点挂了,那么这个结点就挂了
  2. 判断 Redis 集群是否挂了:如果集群中任意一个结点挂了,而且这个结点没有从结点 slave,那么这个集群就挂了,原因是:
    集群里内置了 16384 个 slot(哈希槽),并且把所有的物理结点都映射到这 16384 个 slot 上(0~16383),当 Redis 集群中存放一个数据时,Redis 会先对这个 key 进行 crc16 算法,然后得到一个结果对 16384 进行求余,从而决定这个结点存储到那个结点中。所有一旦某一个节点挂了,该节点得 slot 就无法使用了。

redis 数据类型

Redis 支持五种数据类型:string(字符串),list(列表),set(集合),hash(哈希)及 zset(sorted set:有序集合)。redis 中的数据都是字符串,redis 是单线程,不适合存储比较大的数据

  • string:存储字符串、整数、浮点数

    1. 设置值:set key value
    2. 获取值:get key
    3. 删除键值:del key
    4. 值加一:incr key
    5. 值减一:decr key
  • list:数据结构中的:双链表+队列, 可作为链表 ,从左添加元素 也可以从右添加元素

    1. 从右边添加元素: rpush listKey value1 value2 ... valueN
    2. 从左边添加元素: lpush listKey value1 value2 ... valueN
    3. 获取指定范围内元素:lrange listKey 0 -1(-1 代表最后一个元素,-2 代表倒数第二个元素,以此类推)
    4. 从左边取值,删除:lpop listKey
    5. 从右边取值,删除:rpop listKey
  • set:无顺序,不能重复,集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)

    1. 添加元素:sadd setKey value1 value2 ... valueN
    2. 查询元素:smembers setKey
    3. 删除元素:srem keySet value
  • hash:相当于一个 key 对应一个 map (map 中又是 key-value)

    1. 设置值:hset hashKey sub-key value
    2. 获取值:hget hashKey sub-key
    3. 获取所有值:hgetall hashKey
    4. 删除值:hdel hashKey sub-key
  • zset:有顺序,不能重复

    1. 添加元素: zadd key score1 member1 [score2 member2]
    2. 查看分数:
      1. 从小到大:zrange key 0 -1 [withscores]
      2. 从大到小:zrevrange key 0 -1 [withscores]
    3. 对元素 member 增加 score:zincrby key score member

redis 和 memcached 的区别?为什么不用 memcached?

  1. 数据类型:Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。
  2. 数据持久化:Redis 支持 RDB 快照和 AOF 日志两种持久化策略,而 Memcached 不支持持久化。
  3. 分布式:Memcached 不支持分布式,Redis Cluster 实现了分布式的支持。
  4. 内存管理机制:
    1. 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。
    2. Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高。

redis 持久化方案

Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。redis 默认开启 RDB,同时开启两个持久化方案,则按照 AOF 的持久化放案恢复数据。

RDB 快照:

定期将当前时刻的数据保存磁盘中,会产生一个 dump.rdb 文件。特点是会存在数据丢失,性能较好,可以用于数据备份。

触发条件:

  1. 手动触发

    调用 savebgsave 命令。

    1. save 命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在Redis服务器阻塞期间,服务器不能处理任何命令请求。
    2. bgsave 命令会创建一个子进程,由子进程来负责创建RDB文件,父进程(即Redis主进程)则继续处理请求
  2. 自动触发

    save m n

    自动触发最常见的情况是在配置文件中通过 save m n ,指定当m秒内发生n次变化时,会触发 bgsave。Redis的默认配置文件(Linux下为Redis根目录下的redis.conf),可以看到如下配置信息:

    save 900 1
    save 300 10
    save 60 10000
    

    其中 save 900 1 的含义是:当时间到 900 秒时,如果 Redis 数据发生了至少1次变化,则执行 bgsavesave 300 10save 60 10000 同理。当三个save 条件满足任意一个时,都会引起 bgsave 的调用。

AOF 日志:

RDB 持久化是将进程数据写入文件,而 AOF 持久化(即 Append Only File 持久化),则是将 Redis 执行的每次写命令记录到单独的日志文件中(有点像 MySQL 的 binlog);当 Redis 重启时再次执行 AOF 文件中的命令来恢复数据。

特点是每秒保存,数据比较完整,耗费性能。

AOF 开启设置:修改 redis.conf 文件,将 appendonly 设置为 yes,默认关闭,数据存在 appendonly.aof 文件中

redis 底层如何实现,Redis 的高并发和快速原因

Redis 内部维护一个 db 数组,每个 db 都是一个数据库,默认情况下 Redis 会创建 16 个数据库。我们可以通过 select 命令来切换数据库,如 select 1 切换到数据库号为 1 的数据库。select 实现是通过修改客户端的 db 指针,通过指针指向不同的数据库来实现数据库的切换操作的。

Redis 的高并发和快速原因:

  1. redis 是基于内存的,内存的读写速度非常快;

  2. redis 是单线程的,省去了很多上下文切换线程的时间,不存在加锁释放锁操作,因为不会出现因为死锁而导致的性能消耗。

  3. redis 使用多路复用技术,Redis 使用 I/O 多路复用程序来监听多个套接字,并将到达的事件发送给文件事件分派器,分配器根据套接字产生的事件类型调用相应的事件处理器

    Redis 将所有的文件事件(读、写、异常)放在一个无序链表中,通过遍历查找已经到达的时间事件,并调用相应的事件处理器。

    多路指的是多个 Socket 连接,复用指的是复用一个线程。

redis 如何实现数据的同步与更新?

每次在键空间读取一个键之后,服务器会更新键的 LRU 时间,用于计算键的闲置时间。如果服务器在读取一个键时发现该键已经过期,那么服务器会先删除这个过期键,然后才执行后续操作。如果有客户端使用 watch 命令监视了某个键,那么服务器在对被监视的键进行修改之后,会将这个键标记为 dirty,从而让事务注意到这个键被修改过。服务器每次修改一个键之后,都会对键计数器的值+1,这个计数器用来触发服务器的持久化操作。如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。

为什么 redis 需要把所有数据放到内存中?

Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。 如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。

redis 常见性能问题和解决方案

  1. Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件。因为 Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的
  2. 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
  3. 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内
  4. 尽量避免在压力很大的主库上增加从库
  5. 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <- Slave3...。这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

Redis 并发竞争问题以及解决方案

Redis 为单进程单线程模式。采用并发模式将并发访问变为串行访问,因为本身没有锁的概念,所以并不存在资源竞争问题,但是在 Jedis 客户端对 Redis 并发访问会出现连接超时、数据转化错误、阻塞以及客户端关闭连接等问题。

解决方案:

  1. 客户端:对连接进行池化,同时对客户端读写 Redis 操作使用内部锁 synchronized

redis 数据淘汰策略?mySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证 redis 中的数据都是热点数据?

可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。Reids 具体有 6 种淘汰策略:

策略描述
volatile-lru从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru从所有数据集中挑选最近最少使用的数据淘汰
allkeys-random从所有数据集中任意选择数据进行淘汰
noeviction禁止驱逐数据

限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,留下热数据到内存。所以,计算一下 20W 数据大约占用的内存,然后设置一下 Redis 内存限制即可,并将淘汰策略为 volatile-lru 或者 allkeys-lru

为了提高缓存命中率,保证缓存数据都是热点数据,可以修改 redis 配置文件 redis.conf,将内存最大使用量设置为热点数据占用的内存量,配置指令为 maxmemory,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰,配置指令为maxmemory-policy

Redis 高级事务

如何判断 Redis 里面的缓存添加成功了

添加缓存加入判断机制

Redis 缓存(Redis)和数据库(MySQL)间的数据一致性

Redis 和 mysql 数据怎么保持数据一致的?open in new window

分布式之数据库和缓存双写一致性方案解析open in new window

不管是先写 MySQL 数据库,再删除 Redis 缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况。

方案:延时双删策略+设置缓存过期时间

具体做法:

  • 先删除缓存
  • 再写数据库
  • 休眠 500 毫秒
  • 再次删除缓存

休眠的原因是可以将休眠期间造成的脏数据再次删除。休眠时间要结合项目的读数据业务逻辑的耗时,然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百 ms 即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据

设置缓存过期时间从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。

缺点:结合双删策略+缓存超时设置,这样最差的情况就是在超时时间内数据存在不一致,而且又增加了写请求的耗时。

出现的问题以及解决方案:

  1. 休眠导致写请求耗时,系统吞吐量降低

    解决方案:将第二次删除作为异步操作——另起一个线程异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。

  2. 第二次删除,如果删除仍旧会出现数据不一致问题

强一致性的,那么就需要悲观锁,使得一致。

同时还有 延时双写/延时双删 等策略。其实都是为了根据自身业务来进行的操作。

Redis 使用场景

1. 计数器

MySQL 等累加往往扛不住高并发,所以需要原子性操作的 redis 来统计数量,例如统计点击数。可以对 String 进行自增自减运算,从而实现计数器功能。

2. 缓存

将热点数据放到内存中,设置内存的最大使用量以及淘汰策略来保证缓存的命中率。

3. 消息队列

List 是一个双向链表,可以通过 lpop 和 lpush 写入和读取消息。 不过最好使用 Kafka、RabbitMQ 等消息中间件。

由于 redis 把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务的场景。

4. 分布式锁的实现

在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。 可以使用 Reids 自带的 SETNX 命令实现分布式锁,除此之外,还可以使用官方提供的 RedLock 分布式锁实现。

5. 其它

Set 可以实现交集、并集等操作,从而实现共同好友等功能。 ZSet 可以实现有序性操作,从而实现排行榜等功能。

Redis-Cluster 集群负载均衡

Redis 集群的负载均衡open in new window

概念:

Redis-Cluster 是在 Redis3.0 后推出的官方集群方案。它的实现负载均衡的原理本质上也是通过对槽位的分配。

设计结构:

每个节点保存对应槽位的数据和整个集群的状态,并且每个节点和其他所有节点连接。直接连接任意一个集群节点就可以和整个集群通信了。

高可用:

Redis-Cluster 为了保证高可用性要求每一个服务器都至少有一个或多个从服务器,主服务器提供服务,从服务器只保证数据的备份,当主服务器宕机后会自动切换到从服务器,但是如果主从都宕机了那么整个集群将无法正确的提供服务。

使用 Redis-Cluster 原因:

  1. 主从复制不能实现高可用
  2. 高并发下,主从复制中单机的 QPS 可能无法满足业务需求
  3. 数据量的考虑,现有服务器内存不能满足业务数据的需要时,单纯向服务器添加内存不能达到要求,此时需要考虑分布式需求,把数据分布到不同服务器上
  4. 网络流量需求:业务的流量已经超过服务器的网卡的上限值,可以考虑使用分布式来进行分流

数据分布方式:

  1. 顺序分布
  2. 哈希分布,可以使用一致性哈希算法代替普通哈希
  3. 虚拟槽分区,虚拟槽分区是 Redis Cluster 采用的分区方式

虚拟槽分区步骤:

  1. 把 16384 槽按照节点数量进行平均分配,由节点进行管理
  2. 对每个 key 按照 CRC16 规则进行 hash 运算
  3. 把 hash 结果对 16383 进行取余
  4. 把余数发送给 Redis 节点
  5. 节点接收到数据,验证是否在自己管理的槽编号的范围。如果在自己管理的槽编号范围内,则把数据保存到数据槽中,然后返回执行结果;如果在自己管理的槽编号范围外,则会把数据发送给正确的节点,由正确的节点来把数据保存在对应的槽中

meet 操作:

节点之间会相互通信,meet 操作是节点之间完成相互通信的基础,meet 操作有一定的频率和规则

moved 重定向:

  1. 每个节点通过通信都会共享 Redis Cluster 中槽和集群中对应节点的关系
  2. 客户端向 Redis Cluster 的任意节点发送命令,接收命令的节点会根据 CRC16 规则进行 hash 运算与 16383 取余,计算自己的槽和对应节点
  3. 如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端
  4. 如果保存数据的槽不在当前节点的管理范围内,则向客户端返回 moved 重定向异常
  5. 客户端接收到节点返回的结果,如果是 moved 异常,则从 moved 异常中获取目标节点的信息
  6. 客户端向目标节点发送命令,获取命令执行结果

需要注意的是:客户端不会自动找到目标节点执行命令

ask 重定向:

概念:

在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移;当客户端向某个节点发送命令,节点向客户端返回 moved 异常,告诉客户端数据对应的槽的节点信息。如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁移到别的节点了,就会返回 ask,这就是 ask 重定向机制

步骤:

  1. 客户端向目标节点发送命令,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回 ask 转向给客户端
  2. 客户端向新的节点发送 Asking 命令给新的节点,然后再次向新节点发送命令
  3. 新节点执行命令,把命令执行结果返回给客户端

多节点命令:

Redis Cluster 不支持使用 scan 命令扫描所有节点,多节点命令就是在在所有节点上都执行一条命令,批量操作优化

  1. 串行 meget:

    定义 for 循环,遍历所有的 key,分别去所有的 Redis 节点中获取值并进行汇总,简单,但是效率不高,需要 n 次网络时间

  2. 串行 IO:

    对串行 mget 进行优化,在客户端本地做内聚,对每个 key 进行 CRC16hash,然后与 16383 取余,就可以知道哪个 key 对应的是哪个槽;

    本地已经缓存了槽与节点的对应关系,然后对 key 按节点进行分组,成立子集,然后使用 pipeline 把命令发送到对应的 node,需要 nodes 次网络时间,大大减少了网络时间开销

  3. 并行 IO:

    并行 IO 是对串行 IO 的一个优化,把 key 分组之后,根据节点数量启动对应的线程数,根据多线程模式并行向 node 节点请求数据,只需要 1 次网络时间

  4. hash_tag:

    将 key 进行 hash_tag 的包装,然后把 tag 用大括号括起来,保证所有的 key 只向一个 node 请求数据,这样执行类似 mget 命令只需要去一个节点获取数据即可,效率更高

故障发现:

包括主观下线和客观下线

主观下线流程:

  1. 节点 1 定期发送 ping 消息给节点 2
  2. 如果发送成功,代表节点 2 正常运行,节点 2 会响应 PONG 消息给节点 1,节点 1 更新与节点 2 的最后通信时间
  3. 如果发送失败,则节点 1 与节点 2 之间的通信异常判断连接,在下一个定时任务周期时,仍然会与节点 2 发送 ping 消息
  4. 如果节点 1 发现与节点 2 最后通信时间超过 node-timeout,则把节点 2 标识为 pfail 状态

Redis-Cluster 缺点,常见问题:

  1. 集群完整性

    cluster-require-full-coverage 默认为 yes,即是否集群中的所有节点都是在线状态且 16384 个槽都处于服务状态时,集群才会提供服务。集群中 16384 个槽全部处于服务状态,保证集群完整性

    当某个节点故障或者正在故障转移时获取数据会提示:(error)CLUSTERDOWN The cluster is down

    建议把 cluster-require-full-coverage 设置为 no

  2. 宽带消耗

    Redis Cluster 节点之间会定期交换 Gossip 消息,以及做一些心跳检测

    官方建议 Redis Cluster 节点数量不要超过 1000 个,当集群中节点数量过多时,会产生不容忽视的带宽消耗

  3. 集群倾斜

    集群倾斜也就是各个节点使用的内存不一致

缓存穿透、缓存击穿和缓存雪崩

一致性 Hash 算法

一致性哈希(hash)算法open in new window

redis 一致性 hash 算法理解open in new window

原因:

使用普通的 Hash 算法是对服务器数量进行取模,服务器数量变动的时候,缓存的位置都需要发生变化,缓存数据需要重新 hash 来确定其存储的具体服务器,因此出现一致性 hash 算法

一致性 Hash 算法主要是考虑到分布式系统每个节点都有可能失效,并且新的节点很可能动态的增加进来的情况,保证了当系统的节点数目发生变化的时候,我们的系统仍然能够对外提供良好的服务

原理:

一致性 hash 算法也是通过取模的方式,只是是对 2^32 取模,一致性 Hash 算法将整个哈希值空间组织成一个虚拟的 Hash 环,范围大小为 0-2^32-1

之后通过 hash 算法对服务器的 IP 地址或其他因子进行 hash 之后在对 2^32 取模,将不同服务器映射到 hash 环 0 到 2^32-1 之间

Redis 存数据时,将数据 key 使用相同的函数 Hash 计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务

为什么是 2^32:

int 的最大值最小值范围设定是因为一个 int 占 4 个字节,一个字节占 8 位,二进制中刚好是 32 位。

一致性 Hash 的容错性和可扩展性:

  1. 如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响
  2. 如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。

Redis 主从复制与哨兵

Redis 主从复制与哨兵(原理篇)open in new window

特点:

  1. 主服务器负责处理写请求
  2. 从服务器负责处理读请求
  3. 主从服务器的数据保持一致

复制分为两部分:

  1. 同步:将主服务器的数据全部同步到从服务器中
  2. 命令传播:当主服务器中执行写命令时,将写命令传输一份到从服务器执行,保证一致性。

同步又可以分成两个情况:

  1. 首次同步:这个从服务器还没有复制过任何主服务器,或从服务器此次复制的主服务器和上一次的不同。
  2. 部分同步:处于命令传播阶段的从服务器出现了问题导致了中断,在恢复正常后自动重连进行的同步。

部分同步:

  • 主从服务器的复制偏移量:主从服务器上都会维护一个变量叫复制偏移量,主服务器每发送出 N 条命令它的偏移量就会+N,从服务器每接收到 M 条命令它的偏移量也会+M
  • 服务器 ID:  服务器 ID 是用来判断请求同步的从服务器是新来的或者是断线重连的

哨兵 Sentinel:

Redis 的哨兵模式用于为 Redis 实现高可用,在主从分离的模型中,如果主服务器宕机了,那么哨兵将会选举主服务器下的一台从服务器升级为主服务器提供服务。

判断服务器是否下线:

  1. 主观下线: Sentinel 会每隔一秒向主从服务器以及其他 Sentinel 发送 Ping 命令,如果超过指定的时间没有回复,那么该 Sentinel 就会认为该服务器下线。
  2. 客观下线: 当一个 Sentinel 判断一个服务器下线后,它为了确定是否真的下线,会向其他 Sentinel 确认是否真的下线了,如果足够多的 Sentinel 认为该服务器下线了,那么就判断该服务器客观下线了,如果没有足够的 Sentinel 同意该服务器是下线的,那么 Master 的客观下线状态会被移除。

故障转移:

  • 选举领头 Sentinel:当一个 master 被认为客观下线后,监视此 master 的 sentinel 会进行协商,选出一个领头 sentinel 进行故障转移。
  • 主从切换:选出的领头 sentinel 会对已经下线的 master 执行转移操作:

ActiveMQ

如何保证消息不被重复消费

二、分布式基础知识

分布式概念

分布式结构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在 web 容器中,它们之间通过 RPC 方式通信。

分布式锁

使用原因:

为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用Java并发处理相关的 API(如 ReentrantLock 或Synchronized)进行互斥控制。

但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 Java API 并不能提供分布式锁的能力。为了解决这个问题就需要一种跨 JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。

有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行,因此使用到分布式锁

分布式锁入门以及三种实现方式open in new window

实现方式:

  • 基于数据库实现分布式锁

    在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。

    缺点:锁没有失效时间,不可重入

  • 基于 redis 实现

    使用 Redis 实现分布式锁的时候,主要会使用到三个命令:

    SETNX key val

    当且仅当 key 不存在时,set 一个 key,值为 val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0;类似于数据库的唯一索引。

    expire key timeout

    为 key 设置一个过期时间,单位为 second,超过这个时间锁会自动释放,避免死锁。避免数据库唯一索引实现方式中释放锁失败的问题。

    delete key

    删除 key。

    思想:

    1. 获取锁的时候,使用 setnx 加锁,并使用 expire 命令为锁添加一个过期时间,超过该时间则自动释放锁,锁的 value 值为一个随机生成的 UUID,通过此在释放锁的时候进行判断。
    2. 获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
    3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。
  • 基于 ZooKeeper 的实现方式

Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.13.0