1、redis分布式锁
1、使用redis客户端自带的jedis
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return true;
}
return false;
}
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
if (RELEASE_SUCCESS.equals(result)) {
return true;
}
return false;
}
jedis.set方法传入key+requestid+sx+px+过期时间,requestid可以是uuid,redis可以根据requestid判断是不是同一个请求
jedis.evel方法是将lua代码交给redis当成命令执行,只有这个命令执行完了才会执行其他命令,所以是原子性的。
2、使用redission
1、单机模式,在config中设置配置,如果是整合springboot,在新建个配置类获取配置文件的配置。
Config config = new Config();
config.useSingleServer().setAddress("redis://myredisserver:6379");
RedissonClient redisson = Redisson.create(config);
2、哨兵模式
Config config = new Config();
config.useSentinelServers()
.setMasterName("mymaster")
.addSentinelAddress("redis://127.0.0.1:26389", "redis://127.0.0.1:26379")
.addSentinelAddress("redis://127.0.0.1:26319");
RedissonClient redisson = Redisson.create(config);
3、主从模式
Config config = new Config();
config.useMasterSlaveServers()
.setMasterAddress("redis://127.0.0.1:6379")
.addSlaveAddress("redis://127.0.0.1:6389", "redis://127.0.0.1:6332", "redis://127.0.0.1:6419")
.addSlaveAddress("redis://127.0.0.1:6399");
RedissonClient redisson = Redisson.create(config);
4、集群模式
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000) // 集群状态扫描间隔时间,单位是毫秒
.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
5、redission锁原理
获取锁以及加锁解锁:使用RedissionClient.getLock(lockName)获取锁对象,锁对象拥有tryLock和unlock、lock方法
2、redis分段锁
package com.crazymaker.springcloud.standard.lock;
import com.crazymaker.springcloud.common.util.RandomUtil;
import com.crazymaker.springcloud.common.util.ThreadUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@Slf4j
@Data
@AllArgsConstructor
public class JedisMultiSegmentLock implements Lock {
public static final int NO_SEG = -1;
//拿到锁的线程
private Thread thread;
//拿到锁的状态
private volatile boolean isLocked = false;
//段数
private final int segAmount;
public static final int DEFAULT_TIMEOUT = 2000;
public static final Long WAIT_GAT = Long.valueOf(100);
//内部的锁
InnerLock[] innerLocks = null;
//被锁住的分段
int segmentIndexLocked = NO_SEG;
/**
* 默认为2000ms
*/
long expire = 2000L;
int segmentIndex = 0;
public JedisMultiSegmentLock(String lockKey, String requestId, int segAmount) {
this.segAmount = segAmount;
innerLocks = new InnerLock[segAmount];
for (int i = 0; i < this.segAmount; i++) {
//每一个分段,加上一个编号
String innerLockKey = lockKey + ":" + i;
innerLocks[i] = new InnerLock(innerLockKey, requestId);
}
segmentIndex = RandomUtil.randInModLower(this.segAmount);
}
/**
* 获取一个分布式锁 , 超时则返回失败
*
* @return 获锁成功 - true | 获锁失败 - false
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
//本地可重入
if (isLocked && thread == Thread.currentThread()) {
return true;
}
expire = unit != null ? unit.toMillis(time) : DEFAULT_TIMEOUT;
long startMillis = System.currentTimeMillis();
Long millisToWait = expire;
boolean localLocked = false;
int turn = 1;
InnerLock innerLock = innerLocks[segmentIndex];
while (!localLocked) {
localLocked = innerLock.lock(expire);
if (!localLocked) {
millisToWait = millisToWait - (System.currentTimeMillis() - startMillis);
startMillis = System.currentTimeMillis();
if (millisToWait > 0L) {
/**
* 还没有超时
*/
ThreadUtil.sleepMilliSeconds(WAIT_GAT);
log.info("睡眠一下,重新开始,turn:{},剩余时间:{}", turn++, millisToWait);
segmentIndex++;
if (segmentIndex >= this.segAmount) {
segmentIndex = 0;
}
innerLock = innerLocks[segmentIndex];
} else {
log.info("抢锁超时");
return false;
}
} else {
segmentIndexLocked = segmentIndex;
isLocked = true;
localLocked = true;
thread = Thread.currentThread();
}
}
return isLocked;
}
/**
* 抢夺锁
*/
@Override
public void lock() {
throw new IllegalStateException(
"方法 'lock' 尚未实现!");
}
//释放锁
@Override
public void unlock() {
if (segmentIndexLocked == NO_SEG) {
return;
}
this.innerLocks[segmentIndexLocked].unlock();
segmentIndexLocked = NO_SEG;
thread = null;
isLocked = false;
}
@Override
public Condition newCondition() {
throw new IllegalStateException(
"方法 'newCondition' 尚未实现!");
}
@Override
public void lockInterruptibly() throws InterruptedException {
throw new IllegalStateException(
"方法 'lockInterruptibly' 尚未实现!");
}
@Override
public boolean tryLock() {
throw new IllegalStateException(
"方法 'tryLock' 尚未实现!");
}
}
3、redis与mysql双写如何保证一致性
强一致性:用户写入的是什么,马上读到的就是什么
弱一致性:用户写入的数据,在一定时间后一定会一致。最终一致性是弱一致性的特殊。
3种方案保证数据库与缓存的一致性
延时双删策略
先删缓存,再更新数据库,延迟一秒,再次删除缓存。
删除缓存重试机制
缓存删除有可能会失败,所以将缓存删除失败的key放在消息队列中,消费消息队列,重试删除缓存。
读取binlog异步删除缓存
使用数据库的binlog代替删除缓存重试,效果会更好。