**redis事务:**是一个单独的隔离操作,事务中的所有命令都会序列化,按顺序地执行。事务在执行的过程中, 不会被其他客户端发来的命令请求所打断。主要作用是串联多个命令防止别的命令插队。
事务操作基本命令:
| 命令 | 作用 |
|---|---|
| MULTI | 开启事务, 将需要执行的命令组队 |
| EXEC | 执行事务 |
| DISCARD | 放弃组队 |
MULTI命令开启命令组队,将set k1 v1和 set k2 v2 命令放入执行队列中,通过EXEC命令执行队列中的命令。可以通过DISCARD命令取消MULTI开启的队列。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rjVcZJor-1634970996861)(C:UsersLenovoAppDataRoamingTyporatypora-user-imagesimage-20211023140407370.png)]
1.2 事务出错:- 组队阶段某个命令出现了报告错误, 执行时整个的所有队列都会被取消,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J50RkgGD-1634970996862)(C:UsersLenovoAppDataRoamingTyporatypora-user-imagesimage-20211023140808208.png)]
- 执行阶段某个命令报错, 只有报错命令不会被执行, 其他命令都会被执行,Redis不会进行事务回滚。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4vSm2zG-1634970996864)(C:UsersLenovoAppDataRoamingTyporatypora-user-imagesimage-20211023141019559.png)]
1.3 Redis事务三特性:-
单独的隔离操作: 事务中的所有命令都会序列化, 按顺序地执行。 事务在执行的过程中, 不会被其他客户端发送来的命令请求打断。
-
没有隔离级别的概念: 队列中的命令没有提交之前都不会实际被执行, 因为事务提交前任何命令都不会被实际执行。
-
不保证原子性: 事务中如果有一条命令执行失败, 其他的命令依旧被执行, 没有回滚
悲观锁: 认为每次拿取数据都会修改数据,对数据操作前先上锁, 如: 行锁, 表锁, 读锁, 写锁等。
乐观锁: 认为每次拿取数据都不会修改数据,操作前不上锁, 对数据添加一个版本号, 通过比较版本号判断是否发生修改, 多用于读。
2. 1 WATCH、UNWATCH命令WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令(事务中的命令是在EXEC之后才执行的,EXEC命令执行完之后被监控的键会自动被UNWATCH)。
UNWATCH命令可以在WATCH命令执行之后取消对某个键的监控。使用UNWATCH命令取消对某个键监控时需要在MULTI命令前执行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-namWIVTC-1634970996864)(C:UsersLenovoAppDataRoamingTyporatypora-user-imagesimage-20211023142325413.png)]
2.2 分布式锁上面介绍的Redis的WATCH、MULTI和EXEC命令,只会在数据被其他客户端抢先修改的情况下,通知执行这些命令的客户端,让它撤销对数据的修改操作,并不能阻止其他客户端对数据进行修改,所以只能称之为乐观锁(optimistic locking)。
分布式锁:分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。
Redis实现分布式锁原理:Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系,基于此,Redis中可以使用SETNX命令(SET if Not eXists)实现分布式锁。
语法:SETNX key value
功能:当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。
**解锁:**使用 del key 命令就能释放锁。
**解决死锁:**给锁设置一个过期时间,可以通过两种方法实现:通过命令 “setnx 键名 过期时间 “;或者通过设置锁的expire时间,让Redis去删除锁。
2.3 Java实现代码构建锁:
public String acquireLockWithTimeout(
Jedis conn, String lockName, long acquireTimeout, long lockTimeout)
{
String identifier = UUID.randomUUID().toString(); //锁的值
String lockKey = "lock:" + lockName; //锁的键
int lockExpire = (int)(lockTimeout / 1000); //锁的过期时间
long end = System.currentTimeMillis() + acquireTimeout; //尝试获取锁的时限
while (System.currentTimeMillis() < end) { //判断是否超过获取锁的时限
if (conn.setnx(lockKey, identifier) == 1){ //判断设置锁的值是否成功
conn.expire(lockKey, lockExpire); //设置锁的过期时间
return identifier; //返回锁的值
}
if (conn.ttl(lockKey) == -1) { //判断锁是否超时
conn.expire(lockKey, lockExpire);
}
try {
Thread.sleep(1000); //等待1秒后重新尝试设置锁的值
}catch(InterruptedException ie){
Thread.currentThread().interrupt();
}
}
// 获取锁失败时返回null
return null;
}
锁的释放:
public boolean releaseLock(Jedis conn, String lockName, String identifier) {
String lockKey = "lock:" + lockName; //锁的键
while (true){
conn.watch(lockKey); //监视锁的键
if (identifier.equals(conn.get(lockKey))){ //判断锁的值是否和加锁时设置的一致,即检查进程是否仍然持有锁
Transaction trans = conn.multi();
trans.del(lockKey); //在Redis事务中释放锁
List


