介绍
事务介绍
在传统的关系型数据库中,事务(Transaction)通常需要满足ACID特性(原子性、一致性、隔离性和持久性)。然而,在Redis中,事务的实现有所不同:
1、原子性(Atomicity):
- Redis的单个命令是原子的,意味着每个命令要么完全执行,要么完全不执行。
- 但Redis事务中的一组命令并不完全保证原子性。即使事务中的某个命令失败,其余命令仍然会继续执行。
2、一致性(Consistency):
- Redis事务可以通过将多个命令作为一个原子操作来保持一致性,但由于事务不保证完全的原子性,某些情况下可能会破坏一致性。
3、隔离性(Isolation):
- Redis事务没有隔离级别的概念。事务中的命令在执行时,不会被其他命令打断,但Redis事务并不提供严格的隔离机制。这意味着在MULTI/EXEC之间的命令会被缓冲,但在EXEC执行之前,其他客户端的写操作可能会影响事务结果。
4、持久性(Durability):
- Redis使用内存存储数据,持久性依赖于配置的RDB快照和AOF(Append-Only File)日志。事务中的数据变更在提交后,会根据配置持久化到磁盘。
乐观锁介绍
乐观锁是Redis中确保并发安全的一种机制,主要通过WATCH命令来实现。具体实现细节如下:
- 监视键(WATCH):在执行事务之前,使用WATCH命令监视一个或多个键。如果在执行EXEC之前这些键被其他命令修改,事务会中止。
- 执行事务(MULTI和EXEC):使用MULTI开始事务,然后添加多个命令。最后使用EXEC提交事务。
- 重试机制:如果事务由于监视的键被修改而失败,可以捕捉失败并重新尝试事务操作。
注意:没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
- 脏读(Dirty Read):在一个事务读取到另一个未提交事务的修改数据时,就会发生脏读。Redis事务不提供解决脏读的机制。
- 幻读(Phantom Read):幻读发生在一个事务在两次读取之间看到其他事务插入的新数据时。Redis的事务机制无法直接解决幻读问题。
- 不可重复读(Non-repeatable Read):当一个事务在读取同一数据时,另一个事务修改了该数据,导致两次读取结果不同。这在Redis中可以通过乐观锁来部分解决。
Spring boot中Redis如何实现事务?
配置Spring Boot项目
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置RedisTemplate
创建一个配置类,用于配置RedisTemplate,并启用事务支持:
package com.example.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** Redis配置类* 该类用于配置Redis模板,以便在应用程序中使用Redis进行数据操作。*/
@Configuration
public class RedisConfig {/*** 配置Redis模板** @param redisConnectionFactory Redis连接工厂,用于创建Redis连接。* @return RedisTemplate实例,用于执行Redis操作。** 该方法配置了Redis模板的序列化方式,并启用了事务支持。* 使用StringRedisSerializer对键和值进行序列化,确保数据在Redis中的存储格式一致。*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {// 创建一个RedisTemplate实例RedisTemplate<String, Object> template = new RedisTemplate<>();// 设置Redis连接工厂template.setConnectionFactory(redisConnectionFactory);// 设置键的序列化方式为StringRedisSerializertemplate.setKeySerializer(new StringRedisSerializer());// 设置值的序列化方式为StringRedisSerializertemplate.setValueSerializer(new StringRedisSerializer());// 启用事务支持template.setEnableTransactionSupport(true);// 返回配置好的RedisTemplate实例return template;}
}
使用Redis事务
在Service层使用Redis事务,如果在事务过程中发生异常,Spring的事务管理器会自动回滚事务:
package com.example.demo.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** RedisService类提供了对Redis数据库进行操作的方法,特别是在事务环境下。*/
@Service
public class RedisService {/*** 自动注入RedisTemplate,用于操作Redis数据库。*/@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 使用Spring事务管理执行Redis事务。* 这个方法演示了如何在Redis中执行一组操作作为事务。*/@Transactionalpublic void executeTransaction() {// 监视"key1"以确保在事务期间没有其他操作修改它redisTemplate.watch("key1");try {// 开始一个Redis事务redisTemplate.multi();// 在事务中设置"key1"和"key2"的值redisTemplate.opsForValue().set("key1", "value1");redisTemplate.opsForValue().set("key2", "value2");// 执行事务redisTemplate.exec(); // 提交事务} catch (Exception e) {// 如果发生异常,取消事务redisTemplate.discard(); // 放弃事务throw e;}}/*** 使用Spring事务管理执行Redis事务,并展示如何在发生异常时回滚事务。* 这个方法与executeTransaction类似,但增加了异常抛出的逻辑来模拟事务回滚。*/@Transactionalpublic void executeTransactionWithRollback() {// 监视"key1"以确保在事务期间没有其他操作修改它redisTemplate.watch("key1");try {// 开始一个Redis事务redisTemplate.multi();// 在事务中设置"key1"的值redisTemplate.opsForValue().set("key1", "value1");// 模拟一个异常,以展示事务回滚// 模拟异常,事务将回滚if (true) {throw new RuntimeException("模拟异常");}// 如果没有异常,设置"key2"的值并执行事务redisTemplate.opsForValue().set("key2", "value2");redisTemplate.exec(); // 提交事务} catch (Exception e) {// 如果发生异常,取消事务redisTemplate.discard(); // 放弃事务throw e;}}
}
测试事务
可以编写一个简单的Controller来测试事务功能:
package com.example.demo.controller;import com.example.demo.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** Redis控制器,负责处理与Redis相关的HTTP请求。*/
@RestController
@RequestMapping("/redis")
public class RedisController {/*** 自动注入RedisService,用于执行Redis操作。*/@Autowiredprivate RedisService redisService;/*** 测试Redis事务的执行。** @return 成功执行事务时返回"事务执行成功",执行失败时返回失败原因。*/@GetMapping("/transaction")public String testTransaction() {try {redisService.executeTransaction();return "事务执行成功";} catch (Exception e) {return "事务执行失败:" + e.getMessage();}}/*** 测试在Redis事务中触发回滚的情况。** @return 成功执行事务且未触发回滚时返回"事务执行成功,没有回滚",触发回滚时返回回滚原因。*/@GetMapping("/transaction-rollback")public String testTransactionWithRollback() {try {redisService.executeTransactionWithRollback();return "事务执行成功,没有回滚";} catch (Exception e) {return "事务因以下原因回滚:" + e.getMessage();}}
}
Spring boot中Redis如何实现乐观锁?
在Spring Boot中使用Redis实现乐观锁通常依赖于Redis的WATCH命令。WATCH命令可以监视一个或多个键,当这些键在事务执行之前被修改,事务会被中止。这个机制可以用来实现乐观锁。
配置Redis
确保已经配置好Redis连接和模板。
package com.example.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** Redis配置类* 该类用于配置Redis模板,以便在应用程序中使用Redis进行数据操作。*/
@Configuration
public class RedisConfig {/*** 配置Redis模板** @param redisConnectionFactory Redis连接工厂,用于创建Redis连接。* @return RedisTemplate实例,配置了字符串序列化器用于键和值的序列化。** 此方法通过Spring的@Bean注解来定义一个Bean,该Bean是一个配置了特定序列化器的RedisTemplate,* 它将被Spring容器管理并供其他组件使用。*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(redisConnectionFactory);// 使用StringRedisSerializer对键进行序列化template.setKeySerializer(new StringRedisSerializer());// 使用StringRedisSerializer对值进行序列化template.setValueSerializer(new StringRedisSerializer());return template;}
}
实现乐观锁的业务逻辑
在Service层中实现乐观锁的逻辑。
package com.example.demo.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class RedisOptimisticLockService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;public boolean updateValueWithOptimisticLock(String key, String value) {boolean transactionStarted = false;try {// 监视键redisTemplate.watch(key);// 获取当前值String currentValue = (String) redisTemplate.opsForValue().get(key);// 开启事务redisTemplate.multi();transactionStarted = true;// 更新值redisTemplate.opsForValue().set(key, value);// 执行事务List<Object> result = redisTemplate.exec();if (result == null || result.isEmpty()) {// 事务失败,表示键在事务执行前被修改,乐观锁失败return false;}// 事务成功,表示键在事务执行前没有被修改,乐观锁成功return true;} catch (Exception e) {if (transactionStarted) {redisTemplate.discard(); // 放弃事务}throw e; // 重新抛出异常} finally {redisTemplate.unwatch(); // 取消监视}}
}
创建控制器
创建一个控制器来调用乐观锁业务逻辑。
package com.example.demo.controller;import com.example.demo.service.RedisOptimisticLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;/*** 控制器类,处理与Redis相关的乐观锁操作。* 使用@RestController注解,表明这是一个处理HTTP请求的控制器。* @RequestMapping("/redis")注解指定了处理请求的根路径为/redis。*/
@RestController
@RequestMapping("/redis")
public class RedisOptimisticLockController {/*** 自动注入RedisOptimisticLockService,用于执行乐观锁更新操作。*/@Autowiredprivate RedisOptimisticLockService redisOptimisticLockService;/*** 自动注入RedisTemplate,用于直接操作Redis数据库。*/@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 初始化Redis中的键值对。* @param key Redis中的键。* @param value Redis中的值。* @return 初始化结果提示信息。*/@GetMapping("/key")public String initKeyValue(@RequestParam String key, @RequestParam String value) {redisTemplate.opsForValue().set(key, value);return "键值对初始化成功";}/*** 使用乐观锁更新Redis中的值。* @param key 要更新的键。* @param value 更新后的值。* @return 更新操作的结果提示信息。*/@GetMapping("/lock")public String updateValueWithOptimisticLock(@RequestParam String key, @RequestParam String value) {try {boolean success = redisOptimisticLockService.updateValueWithOptimisticLock(key, value);if (success) {return "乐观锁更新成功";} else {return "乐观锁更新失败,键值已被修改";}} catch (Exception e) {return "乐观锁更新失败:" + e.getMessage();}}
}
乐观锁和悲观锁的区别
悲观锁
悲观锁的基本思想是,在整个数据处理过程中,认为数据会被其他事务修改,因此在整个数据处理过程中将数据锁定,防止其他事务访问和修改。主要特点如下:
- 阻塞式锁定:悲观锁在读取数据时会将数据进行加锁,其他事务想要读取或修改数据时会被阻塞,直到当前事务释放锁。
- 性能开销大:因为需要持有锁的时间较长,可能导致其他事务需要等待,从而引起性能下降。
- 适用场景:适用于并发写入较多的场景,如高并发更新同一行数据的情况。
乐观锁
乐观锁的基本思想是,认为在数据处理过程中,数据不会被其他事务修改,直到真正更新数据时才会检查是否被修改。主要特点如下:
- 非阻塞式:乐观锁不会使用加锁机制,而是通过版本号或时间戳等方式记录数据的状态,在更新时检查数据的状态是否发生变化。
- 适用性强:适用于读操作多于写操作的场景,可以避免因为加锁而带来的性能损失。
- 处理冲突:如果在更新时发现数据已被修改,则根据具体情况选择重试或者放弃更新。
总结区别
- 性能开销:悲观锁由于需要持有锁较长时间,因此性能开销大;而乐观锁在正常情况下不会造成阻塞,性能开销较小。
- 并发控制策略:悲观锁是通过阻塞其他事务来保证数据一致性,而乐观锁是在更新时进行冲突检测来保证数据的正确性。
- 适用场景:悲观锁适合写操作频繁的场景,乐观锁适合读操作多、写操作少、冲突少的场景。