Redis的线程模型和事务( 四 )


持久性(Durability)
持久性是指一个事务一旦被提交了 , 那么对数据库中的数据的改变就是永久性的 , 即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作 。
Redis 是否具备持久化 , 这个取决于 Redis 的持久化模式:

  • 纯内存运行 , 不具备持久化 , 服务一旦停机 , 所有数据将丢失 。
  • RDB 模式 , 取决于 RDB 策略 , 只有在满足策略才会执行 Bgsave , 异步执行并不能保证 Redis 具备持久化 。
  • AOF 模式 , 只有将 appendfsync 设置为 always , 程序才会在执行命令同步保存到磁盘 , 这个模式下 , Redis 具备持久化 。 (将 appendfsync 设置为 always , 只是在理论上持久化可行 , 但一般不会这么操作)
简单总结:
  • Redis 具备了一定的原子性 , 但不支持回滚 。
  • Redis 不具备 ACID 中一致性的概念 。 (或者说 Redis 在设计时就无视这点)
  • Redis 具备隔离性 。
  • Redis 通过一定策略可以保证持久性 。
当然 , 我们也不应该拿传统关系型数据库事务的ACID特性去要求Redis , Redis设计更多的是追求简单与高性能 , 不会受制于传统 ACID 的束缚 。
4.3. 代码这里结合springboot代码做示例 , 加深我们对Redis事务的应用开发 。 在springboot中构建Redis客户端 , 一般通过 spring-boot-starter-data-redis 来实现 。
jedis 和 lettuce
Lettuce和Jedis的都是连接Redis Server的客户端程序 。 Jedis在实现上是直连redis server , 多线程环境下非线程安全 , 除非使用连接池 , 为每个Jedis实例增加物理连接 。 Lettuce基于Netty的连接实例(StatefulRedisConnection) , 可以在多个线程间并发访问 , 且线程安全 , 满足多线程环境下的并发访问 , 同时它是可伸缩的设计 , 一个连接实例不够的情况也可以按需增加连接实例 。
可见Lettuce是要优于Jedis的 , 在 spring-boot-starter-data-redis 早期版本都是使用Jedis连接的 , 但到了2.x版本 , Jedis就直接被替换成Lettuce 。
下面直接看代码吧 。
pom
pom文件主要是引入了 spring-boot-starter-data-redis。
org.springframework.bootspring-boot-starter-data-rediscontroller
controller中定义了两个接口:
  • 接口1 watch:watch键A , 在事务中修改键A和B的值 , 在阻塞3秒后 , 提交事务 。
  • 接口2 change:修改键A 。
@RestControllerpublic class DemoController {public final static String STR_KEY_A="key_a";public final static String STR_KEY_B="key_b";private final StringRedisTemplate stringRedisTemplate;public DemoController(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@GetMapping("/watch")public void watch(){stringRedisTemplate.setEnableTransactionSupport(true);stringRedisTemplate.watch(STR_KEY_A);stringRedisTemplate.multi();try {stringRedisTemplate.opsForValue().set(STR_KEY_A, "watch_a");stringRedisTemplate.opsForValue().set(STR_KEY_B, "watch_b");Thread.sleep(3000);}catch (Exception e){e.printStackTrace();stringRedisTemplate.discard();}stringRedisTemplate.exec();stringRedisTemplate.unwatch();}@GetMapping("/change")public void change(){stringRedisTemplate.opsForValue().set(STR_KEY_A,"change_a");}}测试用例
我们写一个测试用例 , 大致逻辑是:先调用接口1 , 0.5秒后(为了保证接口1先于接口2执行 , 因为线程实际执行顺序不一定按照业务代码顺序来) , 再调用接口2 , 并且在两个接口的线程中 , 都会将键A和B的值打印出来 。


推荐阅读