Redis事务 和 Spring Boot 整合 Redis
redis 单条命令是保证原子性,但事务不保证原子性!
redis 事务本质:一组命令集合,一个事务中的所有命令都会被序列化,在执行过程中按顺序执行,一次性,顺序性,排他性的执行一系列命令。
redis 事务没有隔离级别的概念
所有命令在事务中,并没有直接执行,只有发起执行命令才会执行
redis 事务:
1.开启事务(mutlti)
2.命令入队
3.执行事务(exec)
127.0.0.1:6379> MULTI # 开启事务
OK
# 命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
#######################################
# DISCARD 放弃事务
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD # 取消事务 执行队列的命令都不执行
OK
127.0.0.1:6379> get k4
(nil)
# 命令错误, 事务中所有的命令都不执行
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> getset k3 # 命令错误
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k5 v5
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI # 所有的都不执行
# 运行错误 , 事务队列中存在语法性错误,执行命令队列时,错误命令抛异常,其他命令正常执行
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> incr k1 # 值是字符串,不可以自增,运行时错误
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range# 第一条报错,其他正常执行
2) OK
3) "v2"
监控 : Watch
悲观锁:
总是认为会出问题,所以做什么都上锁
乐观锁:
总是认为不出问题,所以不上锁,更新数据判断一下期间是否有人修改
获取version,更新时比较version
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> WATCH money #监视 money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec # 事务正常结束,数据期间没有变动
1) (integer) 80
2) (integer) 20
(error) MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.
中文解释:
Redis被配置为保存数据库快照,但它目前不能持久化到硬盘。用来修改集合数据的命令不能用。请查看Redis日志的详细错误信息。
# 解决方案:将stop-writes-on-bgsave-error设置为no
config set stop-writes-on-bgsave-error no
测试多线程修改值,使用watch 可以当redis 的乐观锁
127.0.0.1:6379> WATCH money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec # 执行前另一个线程money修改值,会导致执行失败
(nil)
# 解决办法
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 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> exec # 对比监视值,没改变则成功,否则失败
1) (integer) 990
2) (integer) 30
Jedis
java操作Redis的中间件
1.创建项目,导入相关依赖
<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.62</version>
</dependency>
</dependencies>
2.测试
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
}
Jedis-事务
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB();
JSONObject obj = new JSONObject();
obj.put("name","wanger");
obj.put("age","18");
Transaction multi = jedis.multi(); // 开启事务
String res = obj.toJSONString();
try {
multi.set("user1",res);
multi.set("user2",res);
int i = 5/0;
multi.set("user3",res);
multi.exec(); // 执行事务
}catch (Exception e){
multi.discard(); //操作则放弃事务
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
}
SpringBoot整合
Jedis: 采用的直连,多个线程操作的话不安全,要想避免须使用jedis pool 连接池,像BIO型
lettuce: 采用netty,实例可以多个线程进行共享,不存在线程不安全,可以减少线程数量,像NIO型
源码分析:
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}// 可以自定义来替换默认的
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 默认的RedisTemplate 没有过多地设置,redis对象都需要序列化
// 两个参数都输泛型,使用需要转换
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
// String是最常使用的,所以单独提出来,可以直接使用
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置连接
spring.redis.host=127.0.0.1
spring.redis.port=6379
3.测试
// opsForValue 操作String
// opsForList 操作List
// opsForSet 操作Set
// 常用的方法可以直接通过redisTemplate操作
// 获取redis连接对象
// RedisConnection connection = redisTemplate.getRequiredConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("mykey","k1");
System.out.println(redisTemplate.opsForValue().get("mykey"));
Redis序列化模块:
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;
// 默认序列化使用的JDK序列化,使字符串会被编译
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
关于对象的保存需要序列化,否则对象不可以传输
编写自己的RedisTemplate
@Configuration
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
// 配置具体的Json序列化方式
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// HashKey采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// Value采用jackson的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
// HashValue采用jackson的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}