Redis的事务
使用multi开启事务,但是redis的事务只是对语法的检查,它的事务是非常弱的事务,无法解决运行时的错误,因此实际使用中不要使用redis的事务(也可以根据自己的实际业务场景选择是否使用事务,当然并不建议使用multi的事务方式)
Transaction multi = jedis.multi();
multi.set(RS_TRANS_NS+"test1","a1");
multi.set(RS_TRANS_NS+"test2","a2");
multi.set(RS_TRANS_NS+"test3","a3");
List<Object> execResult = multi.exec();
使用事务+Watch机制(compareAndSwap)
/*使用watch功能*/
String watchResult = jedis.watch(watchKeys);
if(!"OK".equals(watchResult)) {
throw new RuntimeException("执行watch失败:"+watchResult);
}
Transaction multi = jedis.multi();
multi.set(RS_TRANS_NS+"test1","a1");
multi.set(RS_TRANS_NS+"test2","a2");
multi.set(RS_TRANS_NS+"test3","a3");
List<Object> execResult = multi.exec();
if(execResult==null){
throw new RuntimeException("事务无法执行,监视的key被修改:"+watchKeys);
}
PipeLine+事务
Pipeline pipelined = jedis.pipelined();
pipelined.multi();//开启事务
//。。。。。等等命令
pipelined.exec();//提交事务
以上的方式都无法实现事务的原子性操作,因此引入了Lua脚本
Lua脚本
redis使用lua的好处:
- 节省网络开销,一个lua脚本中可以包含多条redis命令,通过网络一次发送给redis,节省了网络开销
- 原子性操作,由于redis是单线程的,一个lua脚本会被当作一条命令执行
- 复用性,lua脚本是存储在redis中的,这意味着其他客户端可以复用这个脚本来执行相应操作。
命令 | 说明 |
---|---|
eval | redis中提供的执行lua脚本的命令 |
redis.call(‘redis的指令’) | 用于在lua脚本中调用redis的指令,执行返回操作 |
script load “lua脚本” | 将返回一个sha1的一个字符串摘要,并且该lua脚本被换存在了redis中 |
evalsha “上方返回的sha1的摘要” | 执行redis缓存的lua脚本,可以减少lua脚本传递产生的网络开销。也可以实现lua脚本的复用性 |
使用场景:
-
Redis+Lua实现简单的计数器的限流
Lua脚本rateLimiter.lua
--java端送入三个参数(1个key,2个param )string --limitKey(redi中key的值) local key =KEYS[1]; --limit(次数) local times = ARGV[1]; --expire(秒S) local expire = ARGV[2]; --对key-value中的 value +1的操作 返回一个结果 local afterval= redis.call('incr',key); if afterval ==1 then --第一次 redis.call('expire',key,tonumber(expire) ) --失效时间(1S) TLL 1S return 1; --第一次不会进行限制 end --不是第一次,进行判断 if afterval > tonumber(times) then --限制了 return 0; end return 1;
服务端方法,判断是否被限流了
@Service public class IsAcquire { @Autowired private Jedis jedis; //引入一个Redis的Lua脚本的支持 private DefaultRedisScript<Long> getRedisScript; //判断限流方法---类似于RateLimiter public boolean acquire(String limitKey,int limit,int expire) throws Exception{ //连接Redis getRedisScript =new DefaultRedisScript<>(); getRedisScript.setResultType(Long.class);//脚本执行返回值 long getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua"))); Long result = (Long)jedis.eval(getRedisScript.getScriptAsString(), 1,limitKey,String.valueOf(limit),String.valueOf(expire)); if(result ==0){ return false; } return true; } }
控制层返回用户视图
@RestController public class Controller { @Autowired IsAcquire isAcquire;//手下的分布式限流 //秒杀接口 @RequestMapping("/order") public String killProduct(@RequestParam(required = true) String name) throws Exception{ //rateLimiter.tryAcquire(1); //调用 if(isAcquire.acquire("iphone",10,60)){//60秒只能进行10次 System.out.println("业务成功!"); return "恭喜("+name+"),抢到iphone!"; }else{ System.out.println("-----------业务被限流"); return "对不起,你被限流了!"; } } }