@Service
public class TicketService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private StringRedisTemplate stringRedisTemplate;
public String productSoket(){
// 获取车票库存
String ticket = stringRedisTemplate.opsForValue().get("ticket");
int soket = Integer.parseInt(ticket);
// 判断库存是否大于0,如果大于0.则给库存减一
if (soket > 0){
soket = soket - 1 ;
stringRedisTemplate.opsForValue().set("ticket",String.valueOf(soket));
return "购买票成功"+soket ;
}else{
logger.info("车票已经卖完");
return "车票已经卖完" ;
}
}
}
// 分析上述代码:在高并发环境下,在执行到 stringRedisTemplate.opsForValue().set("ticket",String.valueOf(soket));这个语句之前,有很多请求已经进入这个if语句,会出现余票成负数的问题,也就是说会出现超卖
//改进:加上同步锁 ,但是还有一个弊端,就是在高并发环境下,为了减少服务器的压力,会有多个相同的业务类,而同步锁,并不能锁住其他的相同的业务类,因此还是会出现超买问题
// 继续该进:使用我们呢的setnx命令,如果有一个请求进入到我们的服务器,我们马上使用这个命令,给服务器写入一条数据,表示已经有请求来到了这里,其他请求必须等待
@Service
public class TicketService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private StringRedisTemplate stringRedisTemplate;
String lock ;
public synchronized String productSoket(){
Boolean exist = stringRedisTemplate.opsForValue().setIfAbsent(lock, "");
try {
if (!exist){
return "说明已经有买票的这个请求进入到服务器";
}
// 获取车票库存
String ticket = stringRedisTemplate.opsForValue().get("ticket");
int soket = Integer.parseInt(ticket);
// 判断库存是否大于0,如果大于0.则给库存减一
if (soket > 0){
soket = soket - 1 ;
stringRedisTemplate.opsForValue().set("ticket",String.valueOf(soket));
return "购买票成功"+soket ;
}else{
logger.info("车票已经卖完");
return "车票已经卖完";
}
} finally {
stringRedisTemplate.delete(lock);
}
}
}
// 继续分析,如果程序执行到这里 Boolean exist = stringRedisTemplate.opsForValue().setIfAbsent(lock, "");突然挂掉,那么后续的请求进来之后,都会返回false,这个时候,我们可以给这个lock设置一个过期时间,假如说,给这个key设置了30s的过期时间,但是30s后,程序还是没有执行完,那么第二个请求就会执行服务器的finally代码块,删除掉不属于他的这个key,因此,继续改进,设置键值对的时候,给定一个具有代表性的值,在最后进行判断,如果值相等,说明是同一个请求
public class TicketService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private StringRedisTemplate stringRedisTemplate;
String lock ;
public String productSoket(){
String value = UUID.randomUUID().toString();
// 充分的利用了 redis和lua脚本 在C的层面上来实现原子性的a
// expire 和 set 形成原子性
Boolean exist = stringRedisTemplate.opsForValue().setIfAbsent(lock, value,30, TimeUnit.SECONDS);
try {
if (!exist){
return "说明已经有买票的这个请求进入到服务器";
}
// 获取车票库存
String ticket = stringRedisTemplate.opsForValue().get("ticket");
int soket = Integer.parseInt(ticket);
// 判断库存是否大于0,如果大于0.则给库存减一
if (soket > 0){
soket = soket - 1 ;
stringRedisTemplate.opsForValue().set("ticket",String.valueOf(soket));
return "购买票成功"+soket ;
}else{
logger.info("车票已经卖完");
return "车票已经卖完";
}
} finally {
if(value.equals(stringRedisTemplate.opsForValue().get(lock))){
stringRedisTemplate.delete(lock);
}
}
}
}
//这个程序,还有一个bug,如果一个请求在30s内没有执行完?使用后台线程进行续命,也叫做守护线程
@Service
public class TicketService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private StringRedisTemplate stringRedisTemplate;
String lock = "lock" ;
public String productSoket(){
String value = UUID.randomUUID().toString();
// 充分的利用了 redis和lua脚本 在C的层面上来实现原子性的a
// expire 和 set 形成原子性
Boolean exist = stringRedisTemplate.opsForValue().setIfAbsent(lock, value,30, TimeUnit.SECONDS);
//开一个守护线程
MyThred myThread = new MyThred(lock);
myThread.setDaemon(true);
myThread.start();
try {
if (!exist){
// 让排队的人执行这个方法
productSoket();
return "说明已经有买票的这个请求进入到服务器";
}
// 获取车票库存
String ticket = stringRedisTemplate.opsForValue().get("ticket");
int soket = Integer.parseInt(ticket);
// 判断库存是否大于0,如果大于0.则给库存减一
if (soket > 0){
soket = soket - 1 ;
stringRedisTemplate.opsForValue().set("ticket",String.valueOf(soket));
return "购买票成功"+soket ;
}else{
logger.info("车票已经卖完");
return "车票已经卖完";
}
} finally {
if(value.equals(stringRedisTemplate.opsForValue().get(lock))){
stringRedisTemplate.delete(lock);
}
}
}
class MyThred extends Thread{
String lock;
public MyThred(String lock){
this.lock = lock;
}
@Override
public void run() {
while (true){
//干这个事情、
try{
Thread.sleep(10000);
}catch (Exception err){
}
//假设程序还活着 那么说明需要续命
stringRedisTemplate.expire(lock,30,TimeUnit.SECONDS);
}
}
}
}
针对以上复杂的问题,redis提供了一个分布式锁的概念,来解决这个问题
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.11.0version>
dependency>
@SpringBootConfiguration
public class RedisConfig {
@Bean
public RedissonClient redissonClient(){
RedissonClient redissonClient = null;
Config config = new Config();
// 设置请求的URl地址
String url = "redis://101.200.149.246:6379";
// 设置config
config.useSingleServer().setAddress(url);
// 通过redisson来创建一个客户端对象
try {
redissonClient = Redisson.create(config);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return redissonClient;
}
}
@Component
public class DistributLock {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private RedissonClient redissonClient;
/**
* 加锁的方法
* @param lockName
* @return
*/
public boolean Lock(String lockName){
try {
if (redissonClient==null){
logger.info("枷锁失败");
return false;
}
// 如果对象注入,则加锁
RLock lock = redissonClient.getLock(lockName);
lock.lock(30, TimeUnit.SECONDS);
logger.info("加锁成功");
return true;
} catch (Exception e) {
e.printStackTrace();
logger.info("发生不可预期的失误");
return false;
}
}
public boolean unLock(String lockName){
try {
if (redissonClient==null){
logger.info("释放失败");
}
// 获取锁
RLock lock = redissonClient.getLock(lockName);
// 释放锁
if (lock!=null){
lock.unlock();
return true;
}
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
@Service
public class TicketService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private StringRedisTemplate stringRedisTemplate;
String lock = "lock" ;
@Autowired
private DistributLock distributLock;
public String RedisProdcut(){
// 判断加锁是否成功
boolean lock = distributLock.Lock(this.lock);
try {
if (lock){
//说明加锁成功
// 获取车票库存
String ticket = stringRedisTemplate.opsForValue().get("ticket");
int soket = Integer.parseInt(ticket);
// 判断库存是否大于0,如果大于0.则给库存减一
if (soket > 0){
soket = soket - 1 ;
stringRedisTemplate.opsForValue().set("ticket",String.valueOf(soket));
return "购买票成功"+soket ;
}else{
logger.info("车票已经卖完");
return "车票已经卖完";
}
}else {
return "当前正在排队";
}
} finally {
distributLock.unLock(this.lock);
}
}
}