Redis 分布式锁基础理论

引言

本文只是对分布式锁的一个简单的理论入门,不够完善,若是您想学习完整、系统的分布式锁,还请莫在本文浪费时间。html

原计划使用Redis做为系统缓存提高系统性能,在探究过程当中发现序列化过于耗时,在编辑到一半时将原计划做废。java

原计划

本地测试的时候,构造了大量数据,但MySQL表现却并不理想。面试

测试数据:262144条,单表查询。若是平均到每一个教师的出题数来讲,二十万很正常。redis

image.png

分页查询接口,每页十条,查询第一页数据,居然花费了惊人的12.09秒,太慢了。shell

决定使用Redis缓存进行查询优化。数据库

引入Spring Data Redis,安装Redis,配置Redis,配置Serializable接口,配置Cacheable注解。缓存

@Cacheable(value = "pageAllByCurrentUser")
public Page<Subject> pageAllByCurrentUser(Pageable pageable, Long courseId, Long modelId, Integer difficult) {
}

测试,首次请求花费了18.80秒,也就是说序列化10条数据花费了6秒,推测Hibernate应该关联出来了许多数据因此才须要这么长的时间去序列化。服务器

image.png

整到这直接放弃,这个优化方案不合适。原计划夭折。并发

分布式锁

既然挂在序列化上了,也不可否认Redis的高性能,就讲讲面试常考的Redis分布式锁的理论吧,具体实现去找开源项目一大堆。分布式

分布式锁听起来挺高大上,其实特别简单。

image.png

在集群环境下,多个后台服务去操做数据库,若是并发操做,某些场景下会产生问题。

假设数据表以下,又是余额的例子:

id balance
1 500

余额500

这是并发都会遇到的问题,下面进行统一描述。

两个任务:AB并发执行,AB能够是一台服务器上的两个线程,也能够是集群下的不一样服务器中的线程。

A执行取钱,取200

B执行存钱,存200

AB查询原余额,取到的都是500,并发执行完加减操做后。

A500 - 200 = 300

B500 + 200 = 700

因此AB将结果写回数据库时,不管谁先谁后,最终的结果都不正确。

因此须要经过加锁的方式来解决,A执行时,加锁,B再执行时,尝试获取锁失败,等待,A执行完成,解锁,B获取到锁,执行,解锁。反之亦然。

单机环境下的加锁方案请参照美团博客:不可不说的Java“锁”事 - 美团技术团队,面试必考,不会不行,学完使不上,两天就得忘。

集群环境下的并发问题,就须要分布式锁了。

由于集群环境下,各服务实例互不影响,不共享内存,传统的像ReentrantLock之类的普通加锁方式在分布式环境下无效。

三者经过共同的Redis实例实现加解锁。

image.png

大体流程以下:

ARedis中写入“A正在执行用户1的取钱操做,请其余操做用户1的任务等待”。

B看到了Redis中的数据,等待稍候重试,或直接结束使操做失败。

A执行,执行完成,清除在Redis中写入的数据。

若是B稍候从新执行,去Redis中查询,没有查询到互相冲突的实例在执行的消息,开始执行。

用专业的话来说,这就是分布式锁。

专业描述

ABRedis中执行SETNX命令。

SETNXSET if Not eXist。若是key不存在,进行SET,不然失败。

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

因此,谁在Redis中执行SETNX成功,就至关于那个服务实例加锁成功。

加锁成功的继续执行,失败的等待,稍后重试。

执行完成后,将Redis中的key删除,其余服务实例便可从新获取锁。

为何 Redis 不会产生并发问题?

由于Redis是单线程的,因此假设AB同时去Redis中加锁,由于Redis单线程,必然有前后顺序,不会出现AB同时加锁成功的状况。

以上只是理论,也是最基本最古老的分布式锁实现原理,一样存在诸多问题,Redis宕机了怎么办?解锁失败的时候怎么办?

真不知道去年的咱们是怎么学会这些东西的。

image.png

总结

吾生也有涯,而知也无涯。

相关文章
相关标签/搜索