Redis6事务
Multi
命令并未执行,而是进入排队阶段
当排队的命令存在错误,则执行阶段所有命令均执行失败
Exec
执行Multi队列中的命令
当执行过程中命令存在问题,只有存在问题的命令执行失败,其余命令执行成功
Discard
放弃执行Multi命令
// 排队
multi
// 加入命令
set k1 v1
set k2 v2
// 执行
exec
// 放弃执行
discard
事务冲突
悲观锁:每次获取资源都先上锁,禁止其他事务获取资源,关系型数据库的行锁、表锁、读锁、写锁都是悲观锁
乐观锁:获取资源时记录版本号,执行操作之前先判断版本号是否发生改变,发生改变则不执行,避免了ABA问题,因为是通过版本号记录资源状态,而非根据资源的值判断资源是否发生改变。
拓展:java中的 CAS(compare and swap)是对了乐观锁的一种实现,CAS是通过比较内存中的一个数据是否是预期值,如果是就将它修改成新值,如果不是则进行自旋,重复比较的操作,但是CAS存在ABA问题,ABA问题的根本在于cas在修改变量的时候,无法记录变量的状态,比如修改的次数,否修改过这个变量。这样就很容易在一个线程将A修改成B时,另一个线程又会把B修改成A,造成cas多次执行的问题。
Watch
作用就是监听版本号
unwatch 取消监听
案例:
下面是两个并发执行的客户端,其中只有一个客户端能够执行成功,因为两个客户端都监听到了k1相同的版本号,但是当其中一个客户端执行完操作后,k1的版本号发生改变,另一个客户端执行前,监听到k1的版本号发生改变,则执行失败
客户端1
watch k1
multi
incr k1
exec
客户端2
watch k1
multi
incr k1
exec
事务三特性
单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
秒杀简单案例
单线程安全,但是存在并发超卖
package service;
import redis.clients.jedis.Jedis;
public class SeckillService {
public static boolean doSecKill(String uid,String prodid) {
if(null == uid || null == prodid){
return false;
}
Jedis jedis = new Jedis("101.43.146.65", 6379);
String productCountStr = prodid+"count";
String productUserStr = prodid+"user";
String productCount = jedis.get(productCountStr);
if(null == productCount) {
System.out.println("秒杀还没有开始");
jedis.close();
return false;
}
if(jedis.sismember(productUserStr, uid)) {
System.out.println(uid + "用户已经秒杀成功");
jedis.close();
return false;
}
int prodCount = Integer.parseInt(productCount);
if(prodCount <= 0) {
System.out.println("秒杀结束");
jedis.close();
return false;
}
jedis.decr(productCountStr);
jedis.sadd(productUserStr, uid);
System.out.println(uid + "秒杀成功");
jedis.close();
return true;
}
}
Watch事务机制
存在存量遗留问题
JedisPollTool.java
public class JedisPollTool {
private static JedisPool pool = null;
/**
*
* 方法描述 构建redis连接池
*
* @return
*/
static {
if(null == pool) {
synchronized (JedisPollTool.class){
JedisPoolConfig config = new JedisPoolConfig();
//控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;
//如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
config.setMaxTotal(50);
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。
config.setMaxIdle(5);
//表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;单位毫秒
//小于零:阻塞不确定的时间, 默认-1
config.setMaxWaitMillis(1000*100);
//在borrow(引入)一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
config.setTestOnBorrow(true);
//return 一个jedis实例给pool时,是否检查连接可用性(ping())
config.setTestOnReturn(true);
//connectionTimeout 连接超时(默认2000ms)
//soTimeout 响应超时(默认2000ms)
pool = new JedisPool(config, "101.43.146.65", 6381, 2000);
}
}
}
/**
* 方法描述 获取Jedis实例
*
* @return
*/
public static JedisPool getInstance() {
return pool;
}
/**
*
* 方法描述 释放jedis连接资源
*
* @param jedis
*/
public static void release(Jedis jedis) {
if(null != jedis) {
jedis.close();
}
}
}
Seckill2Service.java
public class Seckill2Service {
public static boolean doSecKill(String uid,String prodid) {
JedisPool jedisPool = JedisPollTool.getInstance();
Jedis jedis = jedisPool.getResource();
String productCountStr = prodid+"count";
String productUserStr = prodid+"user";
jedis.watch(productCountStr); //开始监视
String productCount = jedis.get(productCountStr);
if(null == productCount) {
System.out.println("秒杀还没有开始");
JedisPollTool.release(jedis);
return false;
}
if(jedis.sismember(productUserStr, uid)) {
System.out.println(uid + "用户已经秒杀成功");
JedisPollTool.release(jedis);
return false;
}
int prodCount = Integer.parseInt(productCount);
if(prodCount <= 0) {
System.out.println("秒杀结束");
JedisPollTool.release(jedis);
return false;
}
Transaction transaction = jedis.multi();
transaction.decr(productCountStr);
transaction.sadd(productUserStr, uid);
List<Object> exec = transaction.exec();
if(exec == null || exec.size() == 0) {
System.out.println("秒杀失败,稍后重试");
JedisPollTool.release(jedis);
return false;
}
JedisPollTool.release(jedis);
System.out.println(uid + "秒杀成功");
return true;
}
}
LUA脚本
最好用
local userid=KEYS[1];
local prodid=KEYS[2];
local qtkey="sk:"..prodid..":qt";
local usersKey="sk:"..prodid.":usr';
local userExists=redis.call("sismember",usersKey,userid);
if tonumber(userExists)==1 then
return 2;
end
local num= redis.call("get" ,qtkey);
if tonumber(num)<=0 then
return 0;
else
redis.call("decr",qtkey);
redis.call("sadd",usersKey,userid);
end
return 1;