redis事务
redis事务本质: 一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行!
一次性、顺序性、排他性!执行一系列命令。
------ 队列 set set set 执行 -------
redis事务没有隔离级别的概念
所有的命令在事务中并没有直接执行,只有在发起执行命令时才执行!
redis单条命令是原子性的,但事务不保证原子性。
事务的操作
- 开启事务 (multi)
- 命令入队 (…)
- 执行事务 (exec)
正常执行事务!(exec)
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set ex1 aaa
QUEUED
127.0.0.1:6379(TX)> set ex2 bbb
QUEUED
127.0.0.1:6379(TX)> set ex1 ccc
QUEUED
127.0.0.1:6379(TX)> get ex1
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
3) OK
4) "ccc"
放弃事务!(discard)
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> LPUSH l1 aaa
QUEUED
127.0.0.1:6379(TX)> LPUSH l1 ddd
QUEUED
127.0.0.1:6379(TX)> DISCARD #取消事务
OK
127.0.0.1:6379> LRANGE l1
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> LRANGE l1 0 -1
(empty array)
127.0.0.1:6379>
编译型错误(代码语法错误)
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> setget k3 v3 # 不存在setget命令,直接报错
(error) ERR unknown command `setget`, with args beginning with: `k3`, `v3`,
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> EXEC # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 # 此时所有命令都没执行
(nil)
127.0.0.1:6379>
运行时错误(语法正确,其中一条命令报错,其他命令仍能执行。不能保证原子性)
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> INCR k1 # 对字符串进行加一操作
QUEUED
127.0.0.1:6379(TX)> set k2 1
QUEUED
127.0.0.1:6379(TX)> incr key2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> incr k3 # 对字符串进行加一操作
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range # 报错
2) OK
3) (integer) 1
4) OK
5) (error) ERR value is not an integer or out of range # 报错
127.0.0.1:6379> get k3 # 仍可以正常执行
"v3"
两种锁(mysql)
- 悲观锁
很悲观,认为什么时候都会出错,无论做什么都会加锁! - 乐观锁
很乐观,认为什么时候都不会出错,所以不加锁。在更新数据时判断在此期间是否有人修改过数据。通过获取version,在更新时去比较version。
redis监视(watch)面试常问
正常执行成功!
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set spend 0
OK
127.0.0.1:6379> watch money # 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 30
QUEUED
127.0.0.1:6379(TX)> INCRBY spend 30
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 70
2) (integer) 30
遇到线程插队修改数据!(使用watch,可以当做redis的乐观锁操作)
# 第一个客户端
127.0.0.1:6379> WATCH money # 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY spend 10
QUEUED
127.0.0.1:6379(TX)> exec # 执行之前在第二个客户端修改money值,导致事务执行失败
(nil)
# 第二个客户端模拟线程插队修改数据
127.0.0.1:6379> set money 100 # 修改money
OK
执行失败后可以通过解锁,再加锁去更新监视的值
127.0.0.1:6379> UNWATCH # 如果执行失败,先解锁
OK
127.0.0.1:6379> WATCH money # 再次对money上锁,此时money数据为100
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 500
QUEUED
127.0.0.1:6379(TX)> INCRBY spend 500
QUEUED
127.0.0.1:6379(TX)> exec # 执行成功
1) (integer) -400
2) (integer) 530
补充: 每次执行exec,不管有没有成功都会释放锁
Jedis
Jedis是Redis官方推荐的java连接开发工具!是Java操作redis的中间件
测试
- 导入对应的依赖
<dependencies>
<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.76</version>
</dependency>
</dependencies>
- 编码测试:
- 连接数据库
- 操作命令
- 断开连接
package com.Junior;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
public class TestPing {
public static void main(String[] args) {
JedisShardInfo jedisShardInfo = new JedisShardInfo("121.40.46.92", 6379);
jedisShardInfo.setPassword("Junior"); //通过此方法去加载密码
Jedis jedis = new Jedis(jedisShardInfo);
String ping = jedis.ping();
System.out.println(ping);
}
}
// 测试结果
PONG
常用的api
- String
- List
- Set
- Zset
- Hash
- Geo
- Hyperloglog
- Bitmap
jedis事务
package com.Junior;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.Transaction;
public class TestTx {
public static void main(String[] args) {
JedisShardInfo jedisShardInfo = new JedisShardInfo("121.40.46.92", 6379);
jedisShardInfo.setPassword("Junior");
Jedis jedis = new Jedis(jedisShardInfo);
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "zhangsan");
jsonObject.put("age",22);
jedis.flushDB(); //清空数据库
Transaction multi = jedis.multi(); //开启事务
String s = jsonObject.toJSONString();
try {
multi.set("key1",s);
//int i=1/0; // 异常代码
multi.set("key2",s);
multi.exec(); //执行事务
} catch (Exception e) {
multi.discard(); //放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
jedis.close(); //关闭连接
}
}