锁机制
乐观锁:1)通过版本号来实现,先查询获取版本号,在更新的时候校验版本号并修改。
悲观锁:同步关键字就是悲观锁,也称为排它锁。
乐观锁还让用户查询当前版本号,悲观锁如果不释放,查都不让查询。
乐观锁存在多种实现方式:mysql数据库版本号,redis实现,CAS实现等。
在并发情况下,使用锁机制,防止争抢资源。
悲观锁是对数据的修改持悲观态度(认为数据在被修改的时候一定会存在并发问题),因此在整个数据处理过程中将数据锁定。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在应用层中实现了加锁机制,也无法保证外部系统不会修改数据)
在限量秒杀抢购的场景,一定会遇到抢购成功数超过限量的问题和高并发的情况影响系统性能
1、虽然能用数据库的锁避免,超过限量的问题。但是在大并发的情况下,大大影响数据库性能
2、为了避免并发操作数据库,我们可以使用队列来限制,但是并发量会让队列内存瞬间升高
3、我们又可以用悲观锁来实现,但是这样会造成用户等待,响应慢体验不好
因此我们可以利用redis来实现乐观锁
代码实现
案例一
这里我用java实现,我开20个线程模拟10000个人并发来抢购,其它语言也是一样的实现原理。
public static void main(String[] arg){
String redisKey = "redisTest";
ExecutorService executorService = Executors.newFixedThreadPool(20);
try {
Jedis jedis = new Jedis("47.107.221.219",6379);
jedis.set(redisKey,"0");
jedis.close();
}catch (Exception e){
e.printStackTrace();
}
for (int i=0;i<10000;i++){
executorService.execute(()->{
Jedis jedis1 = new Jedis("47.107.221.219",6379);
try {
jedis1.watch(redisKey);
String redisValue = jedis1.get(redisKey);
int valInteger = Integer.valueOf(redisValue);
String userInfo = UUID.randomUUID().toString();
if (valInteger<20){
Transaction transaction = jedis1.multi();
transaction.incr(redisKey);
List list = tx.exec();
if (list!=null){
System.out.println("用户:"+userInfo+",秒杀成功!当前成功人数:"+(valInteger+1));
}else {
System.out.println("用户:"+userInfo+",秒杀失败");
}
}else {
System.out.println("已经有20人秒杀成功,秒杀结束");
}
}catch (Exception e){
e.printStackTrace();
}finally {
jedis1.close();
}
});
}
executorService.shutdown();
}
案例二
public void testRedisSyn(int clientName,String clientList) {
//redis中存储商品数量为(goodsNum:100)
String key = "goodsNum";
Jedis jedis = new Jedis("192.168.140.98", 6379);
jedis.auth("redis密码");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
try {
jedis.watch(key);
System.out.println("顾客:" + clientName + "开始抢商品");
System.out.println("当前商品的个数:" + jedis.get(key));
//当前商品个数
int prdNum = Integer.parseInt(jedis.get(key));
if (prdNum > 0) {
//开启事务,返回一个事务控制对象
Transaction transaction = jedis.multi();
//预先在事务对象中装入要执行的操作
transaction.set(key, String.valueOf(prdNum - 1));
List<Object> exec = transaction.exec();
if (exec == null || exec.isEmpty()) {
//可能是watch-key被外部修改,或者是数据操作被驳回
System.out.println("悲剧了,顾客:" + clientName + "没有抢到商品");
} else {
//这个命令是做啥的。//抢到商品记录一下
jedis.sadd(clientList, clientName+"");
System.out.println("好高兴,顾客:" + clientName + "抢到商品");
break;
}
}
} catch (NumberFormatException e) {
e.printStackTrace();
}finally {
jedis.unwatch();
}
}
}