1 事务ACID
事务要么全部执行成功,要么全部不执行
事务执行的结果必须是使数据库从一个一致性状态转变到另一个一致性状态,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态.
并发的事务是相互隔离的,一个事务的执行不能被其他事务干扰
指一个事务一旦提交,它对数据库中对应数据的状态变更就应该是永久性的
2 lua脚本嵌入redis的优势
- 减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
- 原子性: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;
- 复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.
3 lua脚本实现分布式事务锁
- 实现一: 该实现主要是因为高版本的redis已经可以支持set直接设置过期时间 ,如果存在返回nil
public boolean tryLock(String lockKey, String value, int expireTime) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, value, expireTime, TimeUnit.SECONDS);
}
- 实现二 使用lua脚本
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private GenericToStringSerializer genericToStringSerializer;
private static final Long SUCCESS = 1L;
public boolean tryLock(String lockKey, String value, int expireTime) {
String script = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('expire', KEYS[1], ARGV[2]) return 1 else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript, genericToStringSerializer,genericToStringSerializer,
Collections.singletonList(lockKey),
value, expireTime);
if (SUCCESS.equals(result)) {
return true;
}
return false;
}
- 解锁语句
private static final Long SUCCESS = 1L;
public boolean unLock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript,genericToStringSerializer,genericToStringSerializer, Collections.singletonList(key), value);
if (SUCCESS.equals(result)) {
return true;
}
return false;
}
4 lua脚本部分解释
-
KEYS[1]: 标识作为键值的占位符
-
ARGV[1]: 标识作为值的占位符
-
redis.call('命令', 参数1, 参数2, ...): 在lua脚本中执行redis命令,返回值可以直接获取到
-
1==tonumber('1'): 字符串转数字,返回true
-
'1'==tostring(1): 转字符串,返回true
-
local len=#KEYS: 定义变量len = 所有key的总数
-
for i=1,10 do print(tostring(i)) end: 循环print在redis执行时无法打印,看不出来效果
-
a..b: 表示a连接b 以下实例使用
-
ipairs: 为数组创建迭代器
5 其他lua脚本
- m秒内192.168.1.1访问是否超过了n次
public boolean repeatVisit(String ipAddress, int visitNumber,int expireTime){
String script = "local cnt = redis.call('INCR',KEYS[1]) if cnt > tonumber(ARGV[1]) then return 1 end if cnt == 1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) end return 0";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Object result = redisTemplate.execute(redisScript,genericToStringSerializer,genericToStringSerializer, Collections.singletonList(IP+ipAddress), visitNumber, expireTime);
if (SUCCESS.equals(result)) {
return true;
}
return false;
}
- 批量增加数据
public boolean puts(Map<String,String> map){
AssertUtils.notNull(map,"redis values is null!");
String[] keys = new String[map.size()];
String[] values = new String[map.size()];
int index = 0;
for(Map.Entry<String,String> m : map.entrySet()){
keys[index++] = m.getKey();
values[index++] = m.getValue();
}
String script = "local keys,values=KEYS,ARGV for i,v in ipairs(keys) do redis.call('SET',keys[i],values[i]) end return true";
RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
List<String> keysList = Arrays.asList(keys);
Object result = redisTemplate.execute(redisScript,genericToStringSerializer,genericToStringSerializer, keysList,values);
if(SUCCESS.equals(result)){
return true;
}
return false;
}