为什么要用分布式锁
使用场景
xx游戏平台举办了一个回馈用户活动,只要等级达到x等级,即可领取xx装备。可能有些人,有过这样的想,是不是,只要我速度够快,就能领到多份奖品啊,想想都美滋滋。结果,系统显示,当前操作过于频繁,请稍后再试。what???
这个的话,就涉及到了锁。同个账号,当我们的第一次请求到服务器的时候,已经被加上了锁,当还没释放锁的时候,再次请求,则不能拿到锁,只能继续等待。(当然这种场景不一定要用分布式锁进行实现,比如数据库的索引唯一性也可实行,只是举个例子)
说到锁的话,则要从公司系统的架构说起了。一开始,很多公司的系统不是很庞大,为了节省资源,单机即可满足需求。对于这种单机模式的系统,我们可以用java原生的synchronized和lock这两种锁,只要锁住相应的类、对象或者方法等即可简单的时间锁机制。
但随着公司的不断发展,用户量的增加,需要对业务进行拆分。这个时候,微服务就出现了,服务拆分了,同时为了保证高可用,每个服务会根据负载等因素,选择部署不同数量的机器,进行集群管理。
在分布式系统的话,则出现了同个服务,部署在不同的机器上。因为我们上面采用的是基于JVM的锁机制,没法保证同个用户请求的锁都是同个JVM(其实可以通过设置负载均衡策略,根据用户id进行hash之类,让用户落到同一台机器上,但这种很少用,毕竟这个是属于业务的问题),则需要使用分布式锁进行控制。
常见的分布式锁的解决方案
- 基于数据库的分布式锁
- 基于缓存的分布式锁,比如redis
- 基于zookeeper的分布式锁
本文的话,则主要介绍 redis分布式锁的实现 、潜在的问题 以及 解决方法
基于redis分布式锁的实现
1.加锁
最简单的方法是使用setnx命令。key是锁的唯一标识,一般根据业务来命名。比如想给某个活动的某个用户加锁,则key可以命名为lock_xx(活动id)_XX(用户id),而value的话,这里的话,我们暂且设置为1。
jc.setnx(key, 1);
当一个线程执行setnx返回1,说明key不存在,获取锁成功;当返回结果为0,说明key已经存在,获取锁失败。
2.解锁
有加锁,就得释放锁。当得到锁的线程执行完任务之后,需要释放锁,以便其他线程能够拿到锁,执行任务。
jc.del(key)