redis事务
redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺执行
一次性、顺序性、排他性!执行一系列的命令!
Redis 单条命令保证原子性,但是单条事务不保证原子性。没有隔离级别的概念,所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!exec
redis的事务
- 开启事务(multi)
- 命令入队(…)
- 执行事务(exec)
说明:每一个事务执行完就结束了,如果要执行其他命令要重写开启事务,并执行事务。
正常的事务执行
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set key1 value1
QUEUED
127.0.0.1:6379(TX)> set key2 value2
QUEUED
127.0.0.1:6379(TX)> get key1
QUEUED
127.0.0.1:6379(TX)> get key2
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
3) "value1"
4) "value2"
放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set key1 value1
QUEUED
127.0.0.1:6379(TX)> set key2 value2
QUEUED
127.0.0.1:6379(TX)> set key3 value3
QUEUED
127.0.0.1:6379(TX)> discard # 取消事务
OK
127.0.0.1:6379> get key3 # 事务队列中命令都不会执行
(nil)
编译性异常(代码有问题!命令有错!),事务中所有的命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务报错,多有命令都报错
(error) EXECABORT Transaction discarded because of previous errors
运行时异常(0/1),如果事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行,错误时抛出异常
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range # 虽然第一条报错,但是其他可以正常执行
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
监控!watch
悲观锁:
- 认为什么时候都会出问题,无论什么时候都加锁
乐观锁: - 认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间,是否有人修改过这个数据
- 获取version
- 更新的时候比较version
redis监视测试
# 正常执行成功
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(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
# 测试多线程修改值,使用watch可以当做redis的乐观锁操作
#线程一执行
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
# 线程二执行
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
# 线程一执行
127.0.0.1:6379(TX)> exec
(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(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec # 比对监视的值是否发生了变化,如果有变化,那么可以执行成功,如果变了就执行失败
1) (integer) 990
2) (integer) 30
Jedis
使用java来操作redis
什么是jedis 是redis官方推荐java连接开发工具!使用java操作redis中间件!如果你要使用java操作redis,那么对jedis要十分的熟悉。
1、导入对应的依赖
<!-- 导入jedis的包 -->
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
<scope>compile</scope>
</dependency>
</dependencies>
2、编码测试:
如果连接报错:请参考:https://blog.csdn.net/weixin_43452467/article/details/108621355
1 切换到root用户,并输入命令:firewall-cmd --query-port=6379/tcp 确认端口是否开放
2 输入命令: firewall-cmd --get-active-zones 拿到zone名称
3 输入命令:firewall-cmd --zone=public --add-port=6379/tcp --permanent,永久开放6379端口
4 输入命令:firewall-cmd --reload ,重启防火墙
5 再次查看端口是否开放了:firewall-cmd --query-port=6379/tcp
- 连接数据库
- 操作命令
- 断开连接
public class TestPing {
public static void main(String[] args) {
// 1、new Jedis 对象即可
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx",6379);
System.out.println(jedis.ping());
}
}
输出:
常用的API:
String
List
Set
Hash
Zset
命令操作如下:
在这里插入代码片
事务
public static void main(String[] args) {
// 1、new Jedis 对象即可
Jedis jedis = new Jedis("xxx.xxx.xxx.xxx",6379);
System.out.println(jedis.ping());
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","zhang");
jsonObject.put("age",30);
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
// jedis.watch(result); // 监控,可加入乐观锁
try {
multi.set("user1",result);
multi.set("user2",result);
int i = 1/0; // 代码抛出异常执行失败
multi.exec(); // 执行事务
} catch (Exception e) {
multi.discard(); // 放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 关闭连接
}
SpringBoot整合
SpringBoot操作数据:spring-data jpa jdbc mongodb redis
说明:在springBoot2.x之后,原来使用的jedis被替换为lettuce
jedis:采用的是直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池!类似于BIO,有线程阻塞
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像是NIO模式。
源码分析:
@Bean
@ConditionalOnMissingBean(name = {"redisTemplate"}) // 我们可以自己定义一个RedisTemplate来替换这个默认的
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 默认的redisTemplate没有过多的设置,redis对象都是需要序列化
// 两个泛型都是object,object的类型,我们后续使用需要强制转换<String,object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // 由于 string 是redis中最常用的类型,所以说单独提出来一个bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
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、配置连接
# redis 配置
spring.redis.host=8.140.152.241
spring.redis.port=6379
3、测试
package com.zhang;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型,api和我们的指令是一样的
// opsForValue 操作字符串类似于string
// opsForSet
// opsForGeo
// opsForHash
// opsForList
// opsForZSet
// 除了基本的操作,我们常用的方法都可以用redisTemplate操作,比如事务,CRUD
// 获取redis的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushAll();
// connection.flushDb();
redisTemplate.opsForValue().set("name","zhang");
redisTemplate.opsForValue().set("age",30);
System.out.println(redisTemplate.opsForValue().get("name"));
System.out.println(redisTemplate.opsForValue().get("age"));
}
}
序列化之后就可以传输;
设置自己的RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(factory);
// 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);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也同样采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化采用Jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash 的value序列化方式采用Jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}