盲猜大家是碰到了高并发的问题才来看这边文章的(狗头!!) 不知道大家是不是反正我是,被吐槽不会处理并发的开发不是一个合格的开发
。。(心里有个MMP )
遇到的问题就是经典的抢库存问题,有多人一起同时抢一件商品,如果这是不错特殊处理,我只能说走人也是迟早的事,所以保饭碗要紧好好看好好学
其实处理并发问题不只有这一种处理方式,也可以用redis的特性来处理,或是基于zookeeper实现分布式锁,基于mysql数据库的分布式锁
都可以实现
第一步:注入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.0</version>
</dependency>
第二步:添加配置文件
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "redisson")
public class RedissonConfig {
private String address;
@Bean
public RedissonClient getRedisson() throws Exception {
Config config = new Config();
config.useSingleServer()
.setAddress(address);
RedissonClient redisson = Redisson.create(config);
System.out.println(redisson.getConfig().toJSON());
return redisson;
}
}
这里我配的redis 是单实例模式
config.useSingleServer().setAddress(address);
这玩意还是要根据你的reids环境来配置的,但并不影响你处理并发时候碰到的问题,所以这里我用的是单实例模式。
集群模式
config.useClusterServers().setScanInterval(2000)
.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002");
哨兵模式
config.useSentinelServers()
.setMasterName("mymaster")
// use "rediss://" for SSL connection
.addSentinelAddress("redis://127.0.0.1:26389", "redis://127.0.0.1:26379")
.addSentinelAddress("redis://127.0.0.1:26319");
主从模式
config.useMasterSlaveServers()
// use "rediss://" for SSL connection
.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");
第三步:添加redissong工具类
对 RedissonClient 类做了一下封装处理。
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedissonUtil {
@Autowired
private RedissonClient redissonClient;
/**
* 锁住不设置超时时间(拿不到lock就不罢休,不然线程就一直block)
* @author lst
* @date 2020-5-24 16:23
* @param lockKey
* @return org.redisson.api.RLock
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* leaseTime为加锁时间,单位为秒
* @author lst
* @date 2020-5-24 16:23
* @param lockKey
* @param leaseTime
* @return org.redisson.api.RLock
*/
public RLock lock(String lockKey, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return null;
}
/**
* timeout为加锁时间,时间单位由unit确定
* @author lst
* @date 2020-5-24 16:24
* @param lockKey
* @param unit
* @param timeout
* @return org.redisson.api.RLock
*/
public RLock lock(String lockKey, TimeUnit unit, long timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 尝试获取锁
* @author lst
* @date 2020-5-24 16:24
* @param lockKey
* @param unit
* @param waitTime
* @param leaseTime
* @return boolean
*/
public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/**
* 通过lockKey解锁
* @author lst
* @date 2020-5-24 16:24
* @param lockKey
* @return void
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
/**
* 直接通过锁解锁
* @author lst
* @date 2020-5-24 16:25
* @param lock
* @return void
*/
public void unlock(RLock lock) {
lock.unlock();
}
}
第四步:业务层处理
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean test() {
try {
logger.info("============={} 线程访问开始============", Thread.currentThread().getName());
boolean lock = redissonUtil.tryLock("vvv", TimeUnit.SECONDS, 3L, 5L);
logger.info("锁的状态" + lock + "============={} 线程访问开始============", Thread.currentThread().getName());
if (lock) {
logger.info("线程:{},获取到了锁", Thread.currentThread().getName() + "+++++++++++++++++++++++++++++");
//TODO 添加上自己要锁的代码 锁的行数越少越好
Thread.sleep(100);
} else {
logger.info("todo ++++++");
}
} catch (Exception e) {
logger.info("错误信息:{}", e.toString());
logger.info("线程:{} 获取锁失败", Thread.currentThread().getName());
throw new VException("退出");
}finally {
logger.info("======释放锁======" + Thread.currentThread().getName() + "——————————————————————————————————————————————————————————————————————————");
redissonUtil.unlock("vvv");
return true;
}
}
重点!!!! 重点!!!! 重点!!!!
重点!!!! 重点!!!! 重点!!!!
重点!!!! 重点!!!! 重点!!!!
重要点事情说九遍
在上描业务代码中
boolean lock = redissonUtil.tryLock("vvv", TimeUnit.SECONDS, 3L, 5L);
if (lock) {
} else {
}
一定要做else 处理。判断当lock 为false 时抛出来,不然程序会不走加锁的代码 。 如果不做判断会造成什么 比如:
你在扣库存的代码上 加了分布式锁,此外没锁的地方,程序会继续执行,如果涉及到增加修改的操作,你就会很迷惑。明明加了锁,为什么还会跑代码。
.
.
.
.
.
.
.
.
.
.
.
到这里一个分布式缓存锁就实现了
redisson 介绍可以去看看
https://github.com/redisson/redisson/wiki/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D
这张图可以快速的帮助大家理解redisson的工作机制