目录
一. Redis的事务支持
①Redis单条指令是原子性的,但是Redis的事务是不保证原子性的;
②一个事务中的命令会被序列化,并且在事务中执行顺序是按照顺序一次性执行的,并且不受其它事务的影响;
③没有隔离性没有隔离级别
④Redis事务是按照队列的方式,有序排队,事务在接受命令时并没有执行,而是在执行执行命令后,一次性执行。
1. 使用事务
1.1 开启执行事务
使用事务分为三个过程:启动事务、添加命令入队、执行命令
1.2 取消事务
开启事务后,添加命令,如果需要取消事务:discard,此时该事务的所有命令都会取消执行
执行discard后,该事务中所有命令将会被取消执行
1.3 事务的异常处理
①事务的执行语法存在问题(即指令有问题)
命令出现问题,事务在执行时会报错,所有的命令都不会执行。
②事务在执行过程中出现问题(setnx失败等)
命令语法正确,但是执行时失败了,例如,setnx k1 v1 失败了返回了0。事务执行后会正常执行,不会影响其他语句。所以它是不保证所有语句的原子性的。
2. 事务实现监控乐观锁:watch
通过watch来监视某个key,在启动事务后,如果另外一个线程修改了该key,事务在执行时,会比较该key值是否变更,如果变更了则事务会执行失败。
事务执行完成后,再次执行事务watch会失效
可以通过UNwatch解锁,后重新watch加锁。获取最新数据后执行事务。
通过watch锁定某个key后,就相当于获取到了这个key此时的version,当其他线程修改了该key,则该key在事务中就会执行失败。可以先将数据UNWATCH后重新watch再次执行事务。
二. spring/springboot的支持jedis
1. jedis
jedis是java连接Redis的jar包中间件,通过导入jar包后就可以直接操作Redis,方法与Redis的命令一模一样。
使用非常简单
public static void main(String[] args) {
//如果Redis开启了密码验证,需要借助JedisShardInfo设置密码
JedisShardInfo info = new JedisShardInfo("124.71.112.168", 6379);
info.setPassword("root");
Jedis jedis = new Jedis(info);
System.out.println(jedis.ping());
jedis.set("k1", "v1");
jedis.get("k1");
//压栈
jedis.lpush("list", "a", "b", "c");
//出栈
for (int i = 0; i < jedis.llen("list"); i++) {
System.out.println(jedis.lpop("list"));
}
}
2. jedis操作事务
public static void main(String[] args) {
//如果Redis开启了密码验证,需要借助JedisShardInfo设置密码
JedisShardInfo info = new JedisShardInfo("124.71.112.168", 6379);
info.setPassword("root");
Jedis jedis = new Jedis(info);
System.out.println(jedis.ping());
jedis.flushAll();
//jedis进行事务控制,启动事务,获取Redis的事务对象
Transaction multi = jedis.multi();
JSONObject u1 = new JSONObject();
u1.put("id", 1127);
u1.put("name", "张三");
JSONObject u2 = new JSONObject();
u2.put("id", 1128);
u2.put("name", "李四");
try {
multi.set("user:1127", u1.toString());
multi.set("user:1128", u1.toString());
int i = 1/0;
multi.exec(); //执行事务
} catch (Exception e) {
multi.discard(); //如果抛出异常,就回滚事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("user:1127"));
jedis.close();
}
}
3. springboot整合Redis
springboot中使用Redis,可以导入spring-data包下的jar包依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>${redis.verison}</version> </dependency>
- springboot从2.X后,连接Redis底层就不再使用jedis进行连接了,而是使用了lettuce作为底层进行连接Redis了
区别:
- jedis:采用直连模式,在多线程下是不安全的,需要借助jedisPool连接,造成了线程的浪费
- lettuce:底层是通过netty实现,能够实现多线程共享资源,减少了线程数,并且在多线程情况下是安全的。效率更高。连接池尽量使用lettuce的连接池
lettuce的相关介绍参考博文:https://www.cnblogs.com/throwable/p/11601538.html(引用,如有侵权请联系删除)
Redis的所有使用,都需要序列化
3.1 springboot中依赖配置的源码分析
通过JRE中springboot-auto-configure包下的META-INF下的spring.factories文件中,找到Redis相关的配置类
其中RedisAutoConfiguration源码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) //这是Redis的自动配置文件属性类
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") //该注解表示当不存在该bean时则会创建,存在不会创建,可自定义该bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//默认创建的泛型都是object类型的,使用时需要强转一下
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean //专门配置了一个string类型的bean,操作string类型时,可以直接使用该bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
3.2 使用
properties文件中配置了Redis的连接信息后,Redis启动时就自动注册成bean可以使用了。
Redis存储的key都是经过序列化转换之后的key。
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// opsForValue: 相当于操作string类型 opsFor**就是操作某某类型的意思。后面的API几乎与命令一致
redisTemplate.opsForValue().set("kkk1", "vvv1");
redisTemplate.opsForList().leftPush("list", 1223);
redisTemplate.opsForHash().put("user_1", "name", "张三");
//常用的操作可以直接通过redisTemplate对象获取API
redisTemplate.delete("");
redisTemplate.discard();
redisTemplate.rename("","");
redisTemplate.expire("","");
//获取连接对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushAll();
connection.ping();
System.out.println(redisTemplate.opsForValue().get("kkk1"));
}
3.3 redis 的序列化以及自定义配置bean
java中如果没有通过特殊设置,使用Redis存储对象时,会因为没有序列化导致无法存储报错。所以实体对象都需要进行序列化。
通过默认的RedisTemplate存储的KV都是乱码的,是因为Redis在存储的时候必须要进行序列化,此时没有特殊配置,使用的是默认的序列化,源码如下:
可以通过自定义RedisTemplate的bean,来自定义序列化。
①我们在Redis的源码中可以发现,
所以我们只需要在注册RedisTemplate时,将序列化指定初始化进去,就可实现自定义序列化。连接工厂还是自动读取配置
如果使用默认的JDK序列化方式,并且实体类没有实现JDK的序列化接口,则存储实体就会抛出异常,所以需要在使用Redis时,自定义序列化方式,代码如下:将KEY的序列化方式设置为string,value的序列化方式设置为json。此时就不需要实体类序列化了
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//注入连接工厂
template.setConnectionFactory(redisConnectionFactory);
//创建序列化类:实现了RedisSerializer接口的类都可以使用
Jackson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
fastJsonRedisSerializer.setObjectMapper(om);
//创建string类型的序列化对象
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key使用string的序列化
template.setKeySerializer(stringRedisSerializer);
//value使用json的序列化
template.setValueSerializer(fastJsonRedisSerializer);
//key使用string的序列化
template.setHashKeySerializer(stringRedisSerializer);
//value使用json的序列化
template.setHashValueSerializer(fastJsonRedisSerializer);
//使参数生效
template.afterPropertiesSet();
return template;
}