Redis事务、锁机制、秒杀案例

1.Redis事务:
多个命令放到队列中,形成一个按顺序执行的命令,不会被其他客户端发送来的命令请求所打断
Redis事务隔离作用,不同于MySQL事务

Redis事务执行三个阶段:
开启:以MULTI开始一个事务;
入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面;
执行:由EXEC命令触发事务;

命令组队阶段:有一条命令失败则整个命令队列失败,执行会失败
命令执行阶段:只有报错的命令不会被执行,而其他的命令都会执行,没有回滚功能

Redis事务的三个特性:
1)单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
2)没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
3)不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,不会回滚

2.悲观锁、乐观锁
悲观锁:传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁:乐观锁适用于多读的应用类型,check-and-set,watch+multi实现乐观锁

WATCH key [key ...]
WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,数据被其他客户端抢先修改,报WatchError异常,但不会阻止其他客户端对数据的修改

秒杀案例:
并发对Redis中的共享数据读取并修改,会出现超卖问题

ab工具模拟并发:
yum install httpd-tools

vim postfile模拟表单提交参数,&符号结尾
ab -n 2000 -c 200 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.2.115:8081/Seckill/doseckill

连接池:
@Test
public void JedisPoolTest(){
    //1.创建配置对象
    JedisPoolConfig config = new JedisPoolConfig();
    //2 做配置-四个
    config.setMaxIdle(2);//最大空闲连接数
    config.setMaxTotal(10);//最大连接数
    config.setMaxWaitMillis(1*1000);//创建连接超时
    //连接地址
    String host = "127.0.0.1";
    //端口号
    int prot = 6379 ;
    //延迟时间 多少时间后连接Redis
    int timeout = 1000;
    //3.创建连接池
    JedisPool pool = new JedisPool(config,host,prot,timeout);
    //4.获取连接池对象
    Jedis jedis = pool.getResource();
    //输入密码
    jedis.auth("123456");
    //执行操作
    String username = jedis.set("username", "王天霸");
    System.out.println(jedis.get("username"));
    //关闭连接
    jedis.close();
}

解决超卖问题:
方式一:watch+multi乐观锁
//增加乐观锁
jedis.watch(qtkey);

//增加事务
Transaction multi = jedis.multi();
//减少库存
multi.decr(qtkey);
//执行事务
List<Object> list = multi.exec();

//判断事务提交是否失败
if(list==null || list.size()==0) {
	System.out.println("秒杀失败");
	jedis.close();
	return false;
}
System.err.println("秒杀成功");
jedis.close();

方式二:redis分布式锁
private static Jedis jedis = new Jedis("127.0.0.1");
private static final Long SUCCESS = 1L;
/**
  * 加锁
  */
public boolean tryLock(String key, String requestId, int expireTime) {
    //使用jedis的api,保证原子性
    //NX 不存在则操作EX设置有效期,单位是秒(不能出现死锁)
    String result = jedis.set(key, requestId, "NX", "EX", expireTime);
    //返回OK则表示加锁成功
    return "OK".equals(result);
}
//删除key的lua脚本,先比较requestId是否相等,相等则删除(保证上锁和解锁都是同一个客户端,防止误删)
//lua脚本保证删除的原子性
private static final String DEL_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
/**
  * 解锁
  */
public boolean unLock(String key, String requestId) {
    //删除成功表示解锁成功
    Long result = (Long) jedis.eval(DEL_SCRIPT, Collections.singletonList(key), Collections.singletonList(requestId));
    return SUCCESS.equals(result);
}

库存遗留问题:
出现原因:乐观锁导致很多请求都失败
通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

你可能感兴趣的:(Redis,redis)