Redis事务和锁机制(乐观锁+秒杀)

目录

命令

组队Multi错误(命令此时不会真正执行):

执行exec错误:

事务冲突

 解决方案

悲观锁:

乐观锁:

场景:

 演示乐观锁,watch key监控

Redis事务总结:

秒杀案例

ab测压使用教程

 连接超时问题:

超卖问题

Redis使用乐观锁库存遗留问题:


Redis事务和锁机制(乐观锁+秒杀)_第1张图片

Redis事务是一个单独的隔离操作,事务中的所有命令都会序列化,按顺序的执行,就像上述图一样,不会被别的客户端送来的命令请求所打断;

目的:串联多个命令,防止别的命令插队

命令

Redis事务和锁机制(乐观锁+秒杀)_第2张图片

Multi(组队阶段):将命令按顺序放到队列中,但是不会执行

Exec(执行阶段):提交,类似于commit,提交完后就代表事务已经结束了

discard(回滚阶段):如果你觉得执行的命令有问题,可以使用该阶段来放弃组队;

Redis事务和锁机制(乐观锁+秒杀)_第3张图片

 Redis事务和锁机制(乐观锁+秒杀)_第4张图片

discard:代表上述组队已经被放弃了 ,前面的命令作废

组队Multi错误(命令此时不会真正执行):

在组队时期(Multi)出现错误时,如果你提交事务exec,那么之前所有的命令都会作废; 

Redis事务和锁机制(乐观锁+秒杀)_第5张图片

执行exec错误:

Redis事务和锁机制(乐观锁+秒杀)_第6张图片

 我认为,Multi与exec发生的错误有点像编译错误和运行时错误,如果你语法啥的都错了,那程序直接挂了过不了

运行错误的话,你好歹还有class文件,所以说你能拿的东西还是能拿到,错的就死;


事务冲突

Redis事务和锁机制(乐观锁+秒杀)_第7张图片

 例子:Redis事务和锁机制(乐观锁+秒杀)_第8张图片

 解决方案

悲观锁:

Redis事务和锁机制(乐观锁+秒杀)_第9张图片

 操作之前先上锁(别人不能进行操作,阻塞了),等执行的人操作完了之后,会将锁释放,然后另外的人就会上锁,并且执行操作。。。:效率低下

Redis事务和锁机制(乐观锁+秒杀)_第10张图片

乐观锁:

 Redis事务和锁机制(乐观锁+秒杀)_第11张图片

顾名思义,就是很乐观,每次去拿数据的时候都会认为别人没有修改数据,所以不会上锁,但是自己在更新数据的时候会判断在此期间,别人到底有没有更新数据,(这里我们用版本号来表示),更新数据了的版本号和没更新数据的不一致;——>所以说,如果别人更新了,那么自己就更新不了了;但是别人还是可以继续更新的;

适用于多读类型,有利于提高吞吐量(因为时间快了,自然吞吐量就高了);

场景:

多个人抢一张票,可能都抢到票了,但是支付成功的就一个;

抢到票可以理解为入队操作Multi;

支付成功可以理解为事务提交exec;

Redis事务和锁机制(乐观锁+秒杀)_第12张图片

 演示乐观锁,watch key监控

 理解:一般是和事务一起用,当某个key进行watch之后,如果其他客户端对这个key进行了更改,那么本次事务将会被取消,事务的exec会返回null,jedis.watch(key)会返回ok

比如:

Redis事务和锁机制(乐观锁+秒杀)_第13张图片

已经有第一个人对这个key进行更改了,所以其他人再对这个key执行时(exec)会返回 null

Redis事务和锁机制(乐观锁+秒杀)_第14张图片 

Redis事务和锁机制(乐观锁+秒杀)_第15张图片


 

Redis事务和锁机制(乐观锁+秒杀)_第16张图片

第一个用户对balance执行命令 

1、watch balance:监控键balance

2、multi:开始排队

3、incrby 键 10:对键对应的value+10操作

4、exec:执行操作

此时反馈为110

 Redis事务和锁机制(乐观锁+秒杀)_第17张图片

第二个用户同样的操作会提示失败,为null,因为 第二个的版本号与第一个更新后的版本号不一致

Redis事务和锁机制(乐观锁+秒杀)_第18张图片

 


Redis事务总结:

Redis事务和锁机制(乐观锁+秒杀)_第19张图片

Redis是没有原子性的,在exec执行阶段如果出现错误,只是出现错误的命令不能执行;

秒杀案例

Redis事务和锁机制(乐观锁+秒杀)_第20张图片

利用ab命令进行测压, 设置并发

 Redis事务和锁机制(乐观锁+秒杀)_第21张图片

 并发花费时间

Redis事务和锁机制(乐观锁+秒杀)_第22张图片

两个键:用户秒杀成功key,储存商品key 

 

设置仓库商品数量 set 键名 数量 

 

并发完后,查询仓库商品信息,变成负数了 

ab测压使用教程

(9条消息) ab压测工具使用教程_u011585609的专栏-CSDN博客_ab工具使用方法 

 连接超时问题:

 当redis处理不了更多的请求,那么那些请求就会一直等待;

解决:用类似mysql中的连接池解决即可

 在JedisUtils配置Jedis连接池配置信息

Redis事务和锁机制(乐观锁+秒杀)_第23张图片

package com.atguigu;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolUtil {
	//初始化Jedis连接池
	private static volatile JedisPool jedisPool = null;

	private JedisPoolUtil() {
	}

	//配置连接池的配置信息
	public static JedisPool getJedisPoolInstance() {
		if (null == jedisPool) {
			synchronized (JedisPoolUtil.class) {
				if (null == jedisPool) {
					JedisPoolConfig poolConfig = new JedisPoolConfig();
					//配置最大连接次数
					poolConfig.setMaxTotal(200);
					poolConfig.setMaxIdle(32);
					poolConfig.setMaxWaitMillis(100*1000);
					poolConfig.setBlockWhenExhausted(true);
					poolConfig.setTestOnBorrow(true);  // ping  PONG
				 
					jedisPool = new JedisPool(poolConfig, "192.168.44.168", 6379, 60000,"123456");
				}
			}
		}
		return jedisPool;
	}

	public static void release(JedisPool jedisPool, Jedis jedis) {
		if (null != jedis) {
			jedisPool.returnResource(jedis);
		}
	}

}

超卖问题

明明东西卖完了, 但是还提示秒杀成功

Redis事务和锁机制(乐观锁+秒杀)_第24张图片

Redis事务和锁机制(乐观锁+秒杀)_第25张图片

 当库存为1时,同时有n个连接一起执行减少库存操作时,-n+1;

超卖问题,需要用乐观锁方式解决:

watch监控key,然后将命令加入排队,执行;

package com.atguigu;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.rolling.helper.IntegerTokenConverter;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;

/**
 *
 */
public class SecKill_redis {

	public static void main(String[] args) {
		Jedis jedis =new Jedis("192.168.44.168",6379);
		System.out.println(jedis.ping());
		jedis.close();
	}

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid) throws IOException {
//      1、uid和prodid非空判断
		if(uid==null|| prodid==null){
			return false;
		}

//		2、连接redis
//		Jedis jedis = new Jedis("192.168.184.131", 6379);
//        jedis.auth("123456");

//		2.2通过连接池获取jedis对象
		JedisPool jedisPoolInstance = JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis = jedisPoolInstance.getResource();

//      3、 拼接key,库存key
        String kcKey="sk:"+prodid+":qt";

//		3.1秒杀成功的用户id
		String userKey="sk:"+prodid+":user";

//		监视watch一下库存
		 jedis.watch(kcKey);

//		4.获取库存,如果库存==null,秒杀还没开始
		String kc = jedis.get(kcKey);
		if(kc==null){
			System.out.println("秒杀还没开始,请等待");
			jedis.close();
			return false;
		}

//		5.判断用户是否重复秒杀(用set),看用户秒杀清单(userKey)是否有该用户(uid)
		Boolean sismember = jedis.sismember(userKey, uid);
        if(sismember){
		   System.out.println("已经秒杀成功了,不能重复秒杀");
		   jedis.close();
		   return false;
	   }

//		6.判断商品数量,如果<1,秒杀结束
		if(Integer.parseInt(kc)<0){
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		}

//		7.秒杀过程
//		使用事务
		Transaction multi = jedis.multi();

//		组队操作:将库存中的商品数量-1,秒杀的用户uid添加到userkey中
		multi.decr(kcKey);
		multi.sadd(userKey,uid);

//		执行
		List results = multi.exec();

		if(results==null||results.size()==0){
			System.out.println("秒杀失败.....");
			jedis.close();
		}

//		7.1储存-1
//		jedis.decr(kcKey);

//		7.2把秒杀成功的用户添加到秒杀清单中
//		jedis.sadd(userKey,uid);
//		System.out.println("秒杀成功..");
//		jedis.close();

	return true;
	}
}


 
  

Redis使用乐观锁库存遗留问题:

比如说:当一个用户购买成功后,而商品此时版本号变更,那么其他用户就不能往下面进行了;

利用lua脚本:

package com.atguigu;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.joran.conditional.ElseAction;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;

public class SecKill_redisByScript {
	
	private static final  org.slf4j.Logger logger =LoggerFactory.getLogger(SecKill_redisByScript.class) ;

	public static void main(String[] args) {
		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
 
		Jedis jedis=jedispool.getResource();
		System.out.println(jedis.ping());
		
		Set set=new HashSet();

	//	doSecKill("201","sk:0101");
	}
	
	static String secKillScript ="local userid=KEYS[1];\r\n" + 
			"local prodid=KEYS[2];\r\n" + 
			"local qtkey='sk:'..prodid..\":qt\";\r\n" + 
			"local usersKey='sk:'..prodid..\":usr\";\r\n" + 
			"local userExists=redis.call(\"sismember\",usersKey,userid);\r\n" + 
			"if tonumber(userExists)==1 then \r\n" + 
			"   return 2;\r\n" + 
			"end\r\n" + 
			"local num= redis.call(\"get\" ,qtkey);\r\n" + 
			"if tonumber(num)<=0 then \r\n" + 
			"   return 0;\r\n" + 
			"else \r\n" + 
			"   redis.call(\"decr\",qtkey);\r\n" + 
			"   redis.call(\"sadd\",usersKey,userid);\r\n" + 
			"end\r\n" + 
			"return 1" ;
			 
	static String secKillScript2 = 
			"local userExists=redis.call(\"sismember\",\"{sk}:0101:usr\",userid);\r\n" +
			" return 1";

	public static boolean doSecKill(String uid,String prodid) throws IOException {

		JedisPool jedispool =  JedisPoolUtil.getJedisPoolInstance();
		Jedis jedis=jedispool.getResource();

		 //String sha1=  .secKillScript;
		String sha1=  jedis.scriptLoad(secKillScript);
		Object result= jedis.evalsha(sha1, 2, uid,prodid);

		  String reString=String.valueOf(result);
		if ("0".equals( reString )  ) {
			System.err.println("已抢空!!");
		}else if("1".equals( reString )  )  {
			System.out.println("抢购成功!!!!");
		}else if("2".equals( reString )  )  {
			System.err.println("该用户已抢过!!");
		}else{
			System.err.println("抢购异常!!");
		}
		jedis.close();
		return true;
	}
}

你可能感兴趣的:(Redis,redis,缓存,数据库)