目录
命令
组队Multi错误(命令此时不会真正执行):
执行exec错误:
事务冲突
?解决方案
悲观锁:
乐观锁:
场景:
?演示乐观锁,watch key监控
Redis事务总结:
秒杀案例
ab测压使用教程
?连接超时问题:
超卖问题
Redis使用乐观锁库存遗留问题:
Redis事务是一个单独的隔离操作,事务中的所有命令都会序列化,按顺序的执行,就像上述图一样,不会被别的客户端送来的命令请求所打断;
目的:串联多个命令,防止别的命令插队;
命令
Multi(组队阶段):将命令按顺序放到队列中,但是不会执行
Exec(执行阶段):提交,类似于commit,提交完后就代表事务已经结束了
discard(回滚阶段):如果你觉得执行的命令有问题,可以使用该阶段来放弃组队;
?
discard:代表上述组队已经被放弃了?,前面的命令作废
组队Multi错误(命令此时不会真正执行):
在组队时期(Multi)出现错误时,如果你提交事务exec,那么之前所有的命令都会作废;?
执行exec错误:
?我认为,Multi与exec发生的错误有点像编译错误和运行时错误,如果你语法啥的都错了,那程序直接挂了过不了
运行错误的话,你好歹还有class文件,所以说你能拿的东西还是能拿到,错的就死;
事务冲突
?例子:
?解决方案
悲观锁:
?操作之前先上锁(别人不能进行操作,阻塞了),等执行的人操作完了之后,会将锁释放,然后另外的人就会上锁,并且执行操作。。。:效率低下
乐观锁:
?
顾名思义,就是很乐观,每次去拿数据的时候都会认为别人没有修改数据,所以不会上锁,但是自己在更新数据的时候会判断在此期间,别人到底有没有更新数据,(这里我们用版本号来表示),更新数据了的版本号和没更新数据的不一致;——>所以说,如果别人更新了,那么自己就更新不了了;但是别人还是可以继续更新的;
适用于多读类型,有利于提高吞吐量(因为时间快了,自然吞吐量就高了);
场景:
多个人抢一张票,可能都抢到票了,但是支付成功的就一个;
抢到票可以理解为入队操作Multi;
支付成功可以理解为事务提交exec;
?演示乐观锁,watch key监控
?理解:一般是和事务一起用,当某个key进行watch之后,如果其他客户端对这个key进行了更改,那么本次事务将会被取消,事务的exec会返回null,jedis.watch(key)会返回ok
比如:
已经有第一个人对这个key进行更改了,所以其他人再对这个key执行时(exec)会返回?null
?
?
第一个用户对balance执行命令?
1、watch balance:监控键balance
2、multi:开始排队
3、incrby 键 10:对键对应的value+10操作
4、exec:执行操作
此时反馈为110
?
第二个用户同样的操作会提示失败,为null,因为?第二个的版本号与第一个更新后的版本号不一致
?
Redis事务总结:
Redis是没有原子性的,在exec执行阶段如果出现错误,只是出现错误的命令不能执行;
秒杀案例
利用ab命令进行测压,?设置并发
?
?并发花费时间
两个键:用户秒杀成功key,储存商品key?
?
设置仓库商品数量 set 键名 数量?
?
并发完后,查询仓库商品信息,变成负数了?
ab测压使用教程
(9条消息) ab压测工具使用教程_u011585609的专栏-CSDN博客_ab工具使用方法?
?连接超时问题:
?当redis处理不了更多的请求,那么那些请求就会一直等待;
解决:用类似mysql中的连接池解决即可
?在JedisUtils配置Jedis连接池配置信息
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);
}
}
}
超卖问题
明明东西卖完了, 但是还提示秒杀成功
?当库存为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<Object> 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<HostAndPort> set=new HashSet<HostAndPort>();
// 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;
}
}
|