Redis事务分析 和 Spring Boot 整合 Redis

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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值