我是
方圆
,励志做一名优秀的博主
祝大家54青年节快乐!
目录
3. 事务
3.1 必备的知识
- Redis单条命令保证原子性,但是
事务不保证原子性
- 事务的本质:是一组命令的集合,在事务执行的过程中,
按照顺序执行
- Redis没有事务的隔离级别,不会出现脏读、误读等情况
- 所有的命令在事务中,只有发起执行命令(exec)后才会执行
- 事务的基本命令 ↓
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name xiaowang #命令入队
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> discard #放弃这个事务
OK
3.2 事务遭受异常时的两种情况
3.2.1 编译时异常
- 若出现的是
编译时异常
,也就是说,代码写的有问题,那么整个事务都不会被执行
3.2.2 运行时异常
- 若出现的是
语法问题
,那么事务中的该命令报错,其他命令正常执行
,这里我们也能看出它不保证原子性
3.3 用Redis事务实现乐观锁
3.3.1 先来铺垫一下概念,虽然大家对它们已经很熟悉了
- 悲观锁:这块儿锁很悲观,它认为什么时候都会出现问题,所以它干什么事儿都要加上锁,效率也就很慢了。
- 乐观锁:相反,这个锁它很乐观,认为什么时候不会出现问题,对数据进行更新的时候只是去验证一下数据的“版本”,检查在执行命令的期间,这个数据有没有被修改过(也是著名的ABA问题)
3.3.2 Redis代码实现
我来简单的解释下这个例子,money变量作为我们手里的钱,out变量代表我们花出去的钱,我们来实现"花钱"的事务
- 在正常情况下,在事务执行前没有其他线程对monry变量进行修改
127.0.0.1:6379> set money 100 #开始我们有100元
OK
127.0.0.1:6379> set out 0 #花出去0元
OK
127.0.0.1:6379> watch money #这里采取监视功能,保证乐观锁的实现
OK
127.0.0.1:6379> multi #开启“花钱”事务
OK
127.0.0.1:6379> DECRBY money 20 #花钱20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec #执行后,正常输出
1) (integer) 80
2) (integer) 20
- 下面就要演示乐观锁了,我们再开一个线程,在执行“花钱”事务之前对monry变量进行修改(下面两个线程,如图所示)
重点注意了,线程1开启事务后,没有执行(exec)事务
127.0.0.1:6379> unwatch #我们先把之前的监视释放掉
OK
127.0.0.1:6379> watch money #重新加上监视
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 30
QUEUED
127.0.0.1:6379> INCRBY out 30
QUEUED
# 到这里我们没有执行事务
我们在线程2中,对money变量进行修改,那么修改之后,再让线程1执行事务,应该出现花钱事务失败的预期
127.0.0.1:6379> INCRBY money 50 #增加50元
(integer) 130
好,下面我们让线程1中执行事务,重点看最后的代码
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 30
QUEUED
127.0.0.1:6379> INCRBY out 30
QUEUED
127.0.0.1:6379> exec #重点看这里!它失败了!看来我们成功实现了乐观锁!哈哈哈哈
(nil)
- 上面的就是乐观锁问题,若想解决,就要释放监视,重新加上监视即可(这也就是保证了,我们在事务执行的过程中,操作的数据没有被改变过)
#接上方代码继续
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> unwatch #先把之前的监视释放了
OK
127.0.0.1:6379> watch money #加上新的监视
OK
#此时我们应该知道,在线程2过后,money变量是130元
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 30 #花30
QUEUED
127.0.0.1:6379> INCRBY out 30
QUEUED
127.0.0.1:6379> exec #执行事务,发现正常输出
1) (integer) 100
2) (integer) 50
好勒,乐观锁就实现了,没事儿的话,要自己敲一边哦~
4. Jedis
使用Java操作Redis
4.1 导包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
4.2 简单的代码实现
- 测试连接
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("此处为IP地址",6379);
System.out.println(jedis.ping());
}
}
输出结果为PONG,连接成功
2. 简单代码测试
import redis.clients.jedis.Jedis;
public class Test {
public static void main(String[] args) {
Jedis jedis = new Jedis("此处为IP地址",6379);
//String类型
jedis.set("name","zhangsan");
System.out.println("输出的值应该为zhangsan:"+jedis.get("name"));
//Hash类型
jedis.hset("myhash","field1","1");
System.out.println("输出的值应该为1:"+jedis.hget("myhash", "field1"));
//Set类型
jedis.sadd("mykey","1","2","3");
System.out.println("输出的值应该为1,2,3:"+jedis.smembers("mykey"));
//List类型
jedis.lpush("list","1","3","5");
System.out.println("输出的值应该为5,3,1:"+jedis.lrange("list", 0, -1));
}
}
代码测试结果如下
4.3 用Jedis实现事务
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TransactionTest {
public static void main(String[] args) {
Jedis jedis = new Jedis("此处为IP地址",6379);
//开启事务
Transaction multi = jedis.multi();
try{
multi.set("num1","1");
multi.set("num2","2");
//执行任务
multi.exec();
System.out.println("应该输出2:" + jedis.get("num2"));
}catch (Exception e){
System.out.println("发生错误,放弃事务");
multi.discard();
}
finally {
//关闭事务
multi.close();
}
}
}
输出结果如下
参考
该系列其他文章
Redis之必备基础知识点,文读百变其意自现(一)
Redis之数据类型,好记性不如烂笔头(二)
Redis之redis.conf解析,了解了这些配置信息,才能说了解Redis(四)
Redis之主从复制和哨兵模式,差不多儿啦(五)
Redis之RDB和AOF持久化机制详解