Redis(五)

13.事务

MULTI 、 EXEC 、 DISCARD和 WATCH是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:

  • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
  • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

EXEC命令负责触发并执行事务中的所有命令:

  • 如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
  • 另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。

当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。

然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。

如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。

使用redis-check-aof程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。

从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作,具体信息请参考文档的后半部分。

  1. 执行一个事务

    multi
    set k1 v1
    set k2 v2
    get k1
    get k2
    exec
    

  2. 取消事务

    multi
    set k1 v1
    set k2 v2
    discard
    

  3. 事务中有错误,语法错误,会导致整个事务都不执行

    multi
    getset k1
    set k1
    get k1
    exec
    

  4. 事务中有错误,运行时错误,只有那条语句执行错误,其它语句依然执行。

    multi
    incr k1
    set k2 v2
    set k3 v3
    get k2
    get k3
    exec
    

为什么 Redis 不支持回滚(roll back)

如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。

以下是这种做法的优点:

  • Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。

有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR, 回滚是没有办法处理这些情况的。

使用 check-and-set 操作实现乐观锁

WATCH命令可以为 Redis 事务提供 check-and-set (CAS)行为。

被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC执行之前被修改了, 那么整个事务都会被取消, EXEC返回nil-reply来表示事务已经失败。

一个正常的事务

set money 100#表示银行金额
set out 0#表示支出金额
multi#开启事务
decrby money 10#消费10
incrby out 10 #支出增加0
exec#执行事务

在多线程时候,可能出现下面情况

#线程1
watch money
multi
decrby money 10
incrby out 10
#在执行之前,线程2修改money
set money 1000
#线程1提交事务
exec

解决办法

在执行事务失败之后unwatch,然后再次watch

unwatch
watch money
multi
decrby money 10
incrby out 10
exec

14.Jedis

Jedis是Redis官方推荐的Java连接开发工具。要在Java开发中使用好Redis中间件,必须对Jedis熟悉才能写成漂亮的代码。

官方源码:https://github.com/redis/jedis

在IDEA中创建maven项目,导入有关依赖:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.32</version>
    <scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.78</version>
</dependency>

14.1使用Jedis连接远程Redis

首先需要修改配置文件中有关配置

服务器防火墙设置,放行6379端口

Jedis jedis = new Jedis("自己服务器公网ip",6379);//两个属性分别是ip地址和端口号
System.out.println(jedis.ping());

14.2 Jedis常用API测试

JedisAPI跟命令行界面各种命令是相同的,所以我们可以很快熟悉API

//测试连同
System.out.println(jedis.ping());
System.out.println(jedis.select(1));//选择第二个数据库
System.out.println(jedis.keys("*"));//查看所有的键值对
jedis.set("name","wyz");
System.out.println(jedis.get("name"));//获取name的值
System.out.println(jedis.dbSize());//查看当前数据库有多少条记录
System.out.println(jedis.flushDB());//清空当前数据库
System.out.println(jedis.dbSize());//再次查看数据库的记录数
jedis.close();//关闭连接

14.3Jedis中字符串操作

jedis.set("name","wyz");//设置K_V
jedis.set("age","20");
System.out.println(jedis.get("name"));
System.out.println("age");
//查看类型
System.out.println(jedis.type("name"));
//追加数据
jedis.set("k1","v1");
System.out.println(jedis.get("k1"));
jedis.append("k1","hello");
System.out.println(jedis.get("k1"));
//获取值的长度
System.out.println(jedis.strlen("k1"));

14.4 Jedis列表操作

System.out.println(jedis.ping());
jedis.flushDB();//清空当前数据库
jedis.lpush("list","one","two","three");
jedis.rpush("list","test1","test2","test3");
//查看
System.out.println(jedis.lrange("list",0,-1));

14.5 Jedis事务操作

System.out.println(jedis.ping());
jedis.flushDB();//清空当前数据库
Transaction multi = jedis.multi();//开启事务
try {
    multi.set("name","wyz");
    multi.set("age","20");
    //System.out.println(1/0);
    System.out.println(multi.get("name"));
    System.out.println(multi.del("name"));
    List<Object> exec = multi.exec();//执行事务
    System.out.println(exec);
} catch (Exception e) {
    e.printStackTrace();
    multi.discard();//取消事务
}

14.6 Jedis连接池

JedisPoolConfig config = new JedisPoolConfig();//配置连接池
config.setMaxIdle(8);
config.setMaxTotal(18);
JedisPool pool = new JedisPool(config, "服务器ip地址", 6379);
try(Jedis jedis = pool.getResource()){
    System.out.println(jedis.ping());
    jedis.flushDB();
    jedis.set("name","wyz");
    System.out.println(jedis.get("name"));;
}
pool.close();

posted @ 2021-12-14 20:51  无涯子wyz  阅读(70)  评论(0)    收藏  举报