在工作过程中,在同时操作数据库的时候,出现了相同数据插入多条的情况,针对这个问题,我能想到可以有如下几种解决方法:
加悲观锁
在方法前加锁(synchronized关键字),或者在方法里面加锁。但考虑到在集群情况下,依然可能存在问题,故没有采用该方案。
唯一约束
可以给数据库的表中的字段加上唯一约束,这样到执行insert语句时,当发现数据库中已经存在该记录,就会抛出异常!但有的DBA规定不能给字段加唯一约束,所以该方案有时候并不可用。
加分布式锁
由于项目中基本都使用了Redis,所以直接用Redis实现分布式锁。命令
SET key value NX EX max-lock-time
是一种在 Redis 中实现锁的简单方法。客户端执行以上的命令:
如果服务器返回 OK ,那么这个客户端获得锁。
如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
设置的过期时间到达之后,锁将自动释放。可以通过以下修改,让这个锁实现更健壮:
不使用固定的字符串作为键的值,而是设置一个不可猜测(non-guessable)的长随机字符串,作为口令串(token)。
不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。
这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。以下是一个简单的解锁脚本示例:

这个脚本可以通过 EVAL ...script... 1 resource-name token-value 命令来调用。
下面看一下代码中的获取和解锁方法:

下面的代码执行了获取锁:

上面使用的是springboot的1.5.10版本,connection.set方法返回类型为void,所以判断是否获取锁成功要根据键值判断一次,这种使用方式对值要求每次都不一样,最好是使用通过发号器生成的唯一id。
从2版本开始,connection.set方法返回类型变为 Boolean ,

如果使用redis分布式锁,建议使用springboot2以上的版本。
下面的代码执行了解锁:

可以看到解锁方法正是执行了lua脚本的内容。
2版本例子测试如下:

再次获取锁失败:

解锁:

再次解锁失败:

代码:https://gitee.com/blueses/spring-boot-demo
网友评论