本文只是对分布式锁的一个简单的理论入门,不够完善,若是您想学习完整、系统的分布式锁,还请莫在本文浪费时间。html
原计划使用Redis
做为系统缓存提高系统性能,在探究过程当中发现序列化过于耗时,在编辑到一半时将原计划做废。java
本地测试的时候,构造了大量数据,但MySQL
表现却并不理想。面试
测试数据:262144
条,单表查询。若是平均到每一个教师的出题数来讲,二十万很正常。redis
分页查询接口,每页十条,查询第一页数据,居然花费了惊人的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
应该关联出来了许多数据因此才须要这么长的时间去序列化。服务器
整到这直接放弃,这个优化方案不合适。原计划夭折。并发
既然挂在序列化上了,也不可否认Redis
的高性能,就讲讲面试常考的Redis
分布式锁的理论吧,具体实现去找开源项目一大堆。分布式
分布式锁听起来挺高大上,其实特别简单。
在集群环境下,多个后台服务去操做数据库,若是并发操做,某些场景下会产生问题。
假设数据表以下,又是余额的例子:
id | balance |
---|---|
1 | 500 |
余额500
。
这是并发都会遇到的问题,下面进行统一描述。
两个任务:A
和B
并发执行,AB
能够是一台服务器上的两个线程,也能够是集群下的不一样服务器中的线程。
A
执行取钱,取200
。
B
执行存钱,存200
。
A
、B
查询原余额,取到的都是500
,并发执行完加减操做后。
A
:500 - 200 = 300
B
:500 + 200 = 700
因此A
、B
将结果写回数据库时,不管谁先谁后,最终的结果都不正确。
因此须要经过加锁的方式来解决,A
执行时,加锁,B
再执行时,尝试获取锁失败,等待,A
执行完成,解锁,B
获取到锁,执行,解锁。反之亦然。
单机环境下的加锁方案请参照美团博客:不可不说的Java“锁”事 - 美团技术团队,面试必考,不会不行,学完使不上,两天就得忘。
集群环境下的并发问题,就须要分布式锁了。
由于集群环境下,各服务实例互不影响,不共享内存,传统的像ReentrantLock
之类的普通加锁方式在分布式环境下无效。
三者经过共同的Redis
实例实现加解锁。
大体流程以下:
A
在Redis
中写入“A
正在执行用户1
的取钱操做,请其余操做用户1
的任务等待”。
B
看到了Redis
中的数据,等待稍候重试,或直接结束使操做失败。
A
执行,执行完成,清除在Redis
中写入的数据。
若是B
稍候从新执行,去Redis
中查询,没有查询到互相冲突的实例在执行的消息,开始执行。
用专业的话来说,这就是分布式锁。
专业描述
A
、B
在Redis
中执行SETNX
命令。
SETNX
:SET 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
是单线程的,因此假设A
和B
同时去Redis
中加锁,由于Redis
单线程,必然有前后顺序,不会出现A
与B
同时加锁成功的状况。
以上只是理论,也是最基本最古老的分布式锁实现原理,一样存在诸多问题,Redis
宕机了怎么办?解锁失败的时候怎么办?
真不知道去年的咱们是怎么学会这些东西的。
吾生也有涯,而知也无涯。