如何保证接口幂等性:
1,查询与删除操作是天然幂等操作。
2,新增时,根据某个字段做唯一性判断,比如某个用户只会有一条记录这样的类似判断。(实用性差)
3,悲观锁,select from where 主键=’’ for update
4,乐观锁,update user set money = money + 1 where id=2 and money = 1;在字段自加一的时候,加上该字段的等值索引
3与4,如果没有加主键id做索引,会锁表,会影响该表其他功能。
【上诉两种不好用,并发会使程序报错,如同时要买掉一件商品,一件没扣成功则进程失去反应】
5,分布式锁:使用Redis、Memcached、Zookeeper、Chubby等第三方程序
Redis:
①set(key, value, “NX”,“EX”,internalLockLeaseTime):使用set设置键值,以“id_后缀”等规范作为key,以当前线程id作为value。插入一条记录,并设置锁超时时间30秒。
如果插入成功,返回1,以此声明成功获得锁,其他进程插入返回0说明该key已存在,获取锁失败。成功则进入插入或者更新操作。
②释放锁,为了避免误删其他线程的锁,删除时增加判断if(threadId .equals(redisClient.get(key))){ del(key) }删除键值对,del(id_后缀)
③守护线程,同线程只要不崩溃,会一直为其增加有效期,如果崩了,则不会增加,redis到时间自动释放
实操:
一个商品多人同时购买,同时触发buyCommodity()方法,两个进程并发,此时,上锁对象为商品(判断清楚对象很重要,涉及到key的设计,处理并发),即,key值为商品id+"_buy_lock",此时,A线程比B线程提前得到锁(提前set好了),那么B线程set失败,进入循环获取,持续时间内没有获取到,则视为失败。
本人封装一个redis锁工具,并展示测试类:
package com.fjhb;
import redis.clients.jedis.Jedis;
/**
* redis锁
* Author:FangKunSen
* Time:2020-05-29,15:35
*/
public class JLock {
private String HOST;
Jedis jedis;
protected long internalLockLeaseTime = 30;//锁过期时间
private long timeout = 20000; //获取锁的超时时间
private long SLEEP = 2000;//休息2秒重新获取
public JLock(String HOST) {
this.HOST = HOST;
jedis = new Jedis(HOST);
}
/**
* 上锁
* @param key 建议以上锁对象id为标识,加以识别后缀,例如 123456_commodityId_lock
* @param value 以线程id为值,防止解锁时解到其他线程的锁
* @return
*/
public boolean lock(String key,String value){
Long start = System.currentTimeMillis();
try{
for(;;){
//SET命令返回OK ,则证明获取锁成功
String lock = jedis.set(key, value, "NX","EX",internalLockLeaseTime);
if("OK".equals(lock)){
return true;
}
System.out.println("未取到锁,正在重新取锁...");
//否则循环等待,在timeout时间内仍未获取到锁,则获取失败
long l = System.currentTimeMillis() - start;
if (l>=timeout) {
System.out.println("规定时间内未取到锁,失败");
return false;
}
try {
Thread.sleep(SLEEP);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
jedis.close();
}
}
/**
* 获取锁值,校验用,目前值为进程id
* @param key 建议以上锁对象id为标识,加以识别后缀,例如 123456_commodityId_lock
* @return
*/
public String get(String key){
return jedis.get(key);
}
/**
* 解指定线程id的锁
* @param key 建议以上锁对象id为标识,加以识别后缀,例如 123456_commodityId_lock
* @param value 以线程id为值,防止解锁时解到其他线程的锁
* @return
*/
public boolean unLock(String key, String value){
String threadId=get(key);
if(threadId.equals(value)){
jedis.del(key);
System.out.println("解锁成功,下个继续");
return true;
}
System.out.println("解锁失败");
return false;
}
}
@Test
public void test() {
JLock jLock = new JLock("127.0.0.1");
String key="10101010_commodityId";
String threadId = String.valueOf(Thread.currentThread().getId());
if(jLock.lock(key,threadId)){
System.out.println("取锁成功,执行代码,线程id:"+threadId);
for(int i = 0;i<10;i++){
System.out.println(i+1);
}
System.out.println("代码执行完毕,正在关闭锁");
jLock.unLock(key,threadId);
}else{
System.out.println("超时未取到");
}
}