redis事务

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,经济版 1GB 1个月
简介: redis事务

一、概述

redis事务是一个单独的隔离操作,事务中所有命令都会序列化、按顺序执行。事务执行过程中不会被其他客户端发来的命令请求打断,事务主要作用就是串联多个命令防止别的命令插队

二、事务执行命令

redis和mysql事务有本质区别,redis从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入了执行过程Exec后,redis会将之前的命令队列中的命令依次执行,组队的过程中可以通过discard放弃组队,如下图演示

演示对列

演示放弃组队

 

三、事务异常错误

第一种错误:组队阶段某个命令发生错误,执行时整个的队列都会被取消

第二种错误:执行时队列中的某个名列发生错误,只有发生错误的不会被执行,不会回滚,其他正常执行

四、事务冲突和处理

1、事务冲突概述

马上618了、假如一个场景:有很多人有你的账户并且该账户只有10000,同时去参加618抢购,同时发送了三个请求

一个请求想给金额减8000

一个请求想给金额减5000

一个请求想给金额减1000

这三个请求同时发出,假如都执行成功了,那么此时账户的余额就变成了10000-8000-5000-1000 = -4000,此时账户余额变成负的了,显然这在实际生活中是很不合理的。

这时我们能想到的就是加锁了。

2、解决事务冲突

2.1、悲观锁

顾名思义,就是很悲观的那种,喜欢胡思乱想每次去拿数据的时候总认为别人会改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

2. 2、乐观锁

和悲观锁相反在拿到数据时不会加锁,因为他相信这世界是美好的,别人不会修改,但出于严谨还是会在改数据的那一刻会检查一下有没有被人修改。可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

乐观锁比悲观锁的效率要高,因为悲观锁每次都会加锁而乐观锁只是在修改数据时检查一下版本号是不是刚拿到的版本号不是就不能执行,是就执行。

3、redis中WATCH

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

实例:

设置一个键为balance 值为10的字符串类型数据。

使用watch监控

开启两个客户端,分别监控后再开启事务对balance进行+100操作,客户端一先提交会成功,客户端二提交会失败,这就是WAYCH对事务监控,防止事务冲突。

客户端一:执行事务成功

客户端二:执行事务失败

总结

  在实际应用中很容易出现并发问题、所以我们要在redis事务中加锁解决事务的冲突问题

  在redis中使用的check-and-set乐观锁机制实现事务的

  redis中的乐观锁使用watch命令来实现的

4、redis事务锁机制案例---秒杀

4.1、概述

一个秒杀主要由两个操作商品数量减少和记录秒杀成功的用户

/**
     * 秒杀成功是否
     * @param uid 秒杀用户
     * @param pid 商品id
     * @return 秒杀成功
     */
    public Boolean doKill(String uid,Integer pid){
        //1.uid pid非空判断
        if(uid == null || pid == null){
            return false;
        }
        //2.连接jedis
        Jedis jedis = new Jedis("127.0.0.1","6379");
        //3.拼接key
        //库存key
        String kcKey = "kc"+pid;
        //用户key
        String userKey = "user"+pid;
 
        //4.判断库存是否为空
        String kc = jedis.get(kcKey;
        if(kc == null){
            jedis.close();
            return false;
        }
        //5.判断用户重复秒杀
        if(jedis.sismember(userKey,uid)){
            jedis.close();
            return false;
        }
        //6.库存大于0
        if(Integer.parseInt(kc)<=0){
            jedis.close();
            return false;
        }
        //7.秒杀
        jedis.decr(kcKey);
        jedis.sadd(userKey,uid);//用户id值时set类型的
        jedis.close();
        return true;
    }

4.2、并发问题

使用工具ab模拟并发或者jmeter模拟请求时出现两个问题

1、连接超时(大量请求redis导致阻塞)

解决方式:引入连接池,如下自定义一个连接池工具类

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
 
public class JedisPoolUtils {
    private static volatile JedisPool jedisPool = null;
 
    private JedisPoolUtils(){
 
    }
//懒汉式双重校验锁,线程安全
    public static JedisPool getJedisPoolInstance() {
        if(null == jedisPool) {
            synchronized (JedisPoolUtils.class) {
                if(null == jedisPool) {
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(200);
                    poolConfig.setMaxIdle(32);
                    poolConfig.setMaxWaitMillis(100*1000);
                    poolConfig.setTestOnBorrow(true);
                    jedisPool = new JedisPool(poolConfig,"192.168.235.128",6379);
                }
            }
        }
        return jedisPool;
    }
 
    public static void release(JedisPool jedisPool, Jedis jedis) {
        if(null != jedis) {
            jedisPool.returnResourceObject(jedis);
        }
    }
}

上面秒杀代码更改

Jedis jedis = new Jedis("127.0.0.1","6379");

更改

2、并发错误问题,库存变为负值

通过乐观锁解决,更改上面代码加上监视和事务

4.3、库存遗留问题

通过上面可以解决超卖问题,但是会出现库存 剩余问题,因为乐观锁是通过修改该版本来控制,比如第10个用户监视完之后版本改为1 后面改的用户版本不一致不会操作所以剩余

解决方法,通过lua脚本解决

lua概述

lua是将复杂的或者多步的redis操作写成一个脚本,一次提交给redis执行,减少反复连接redis的次数提升性能,lua脚本类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作,但是redis的lua脚本只有在redis2.6以上版本才能使用,利用lua脚本解决超卖问题,实际上是利用redis单线程特性用任务队列解决任务并发问题

实现类

4.4、扩展

对于秒杀操作spring boot ,redis template也可以实现

/**
 * 通过redis 事务 实现的秒杀
 * @param skuCode 商品编码
 * @param buyNum 购买数量
 * @return 购买数量
 */
@Service
public class GoodsServiceImpl {
 
  @Autowired
  StringRedisTemplate redisTemplate;
  //redis对任何不合法的值,都称为ERR。用RedisTemplate 序列化的数字不能转化
  //使用GenericToStringSerializer、StringRedisSerializer序列化器,都可以使用increment方法  保存的数字是  10 而不是"10" 字符串
  
/* 1 redisTemplate.excute(SessionCallback sessionCallback) 是执行事务的api
 * 2 所以要实现SessionCallback 来实现redis 事务。
 * 3 如果直接 通过redisTemplate 执行事务命令 会报错*/
  public Long flashSellByRedisWatch(String skuCode,int num){
    
    SessionCallback<Long> sessionCallback = new SessionCallback<Long>() {
 
      @SuppressWarnings("unchecked")
      @Override
      public Long execute(RedisOperations operations) throws DataAccessException {
        int result = num;
        //redis 乐观锁
        operations.watch(skuCode);
                ValueOperations<String, String> valueOperations = operations.opsForValue();
                String goodNumStr = valueOperations.get(skuCode);
                Integer goodNum = Integer.valueOf(goodNumStr);
                //标记一个事务块的开始。
                //事务块内的多条命令会按照先后顺序被放进一个队列当中,
                //最后由 EXEC 命令原子性(atomic)地执行。
                operations.multi();
                if (goodNum>=num) {
          valueOperations.increment(skuCode, 0-num);//负数就是需要减少几个商品数量
        }else{
          result = 0;
        }
                //多条命令执行的结果集合
                List exec = operations.exec();
                if(exec.size()>0){
                  System.out.println("执行成功: "+exec);
                }
        return (long) result;
      }
    };
    return redisTemplate.execute(sessionCallback);
  }
}


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5天前
|
负载均衡 NoSQL 算法
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
这篇文章是关于Java面试中Redis相关问题的笔记,包括Redis事务实现、集群方案、主从复制原理、CAP和BASE理论以及负载均衡算法和类型。
一天五道Java面试题----第十天(简述Redis事务实现--------->负载均衡算法、类型)
|
3月前
|
NoSQL Redis 数据库
10- 你们用过Redis的事务吗 ? 事务的命令有哪些 ?
```markdown Redis事务包括MULTI、EXEC、DISCARD、WATCH四个命令。虽具备事务功能,但在实际开发中使用较少。 ```
59 7
|
3月前
|
NoSQL Redis 数据库
什么是Redis的事务?
Redis事务提供原子性和顺序性,确保命令按顺序执行且不被打断。核心概念包括原子性、顺序性、隔离性和持久性。关键指令有MULTI、EXEC、DISCARD和WATCH,用于事务的开始、执行、取消和监视。这保障了命令的完整性,防止并发操作导致的数据不一致。
34 2
|
3月前
|
缓存 监控 NoSQL
Redis之事务
【1月更文挑战第7天】Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!一次性、顺序性、排他性!执行一些列的命令。
187 3
|
3月前
|
缓存 NoSQL 数据处理
Redis事务悄然而至:命令的背后故事
Redis事务悄然而至:命令的背后故事
44 0
|
3月前
|
消息中间件 移动开发 NoSQL
Redis 协议 事务 发布订阅 异步连接
Redis 协议 事务 发布订阅 异步连接
|
3月前
|
缓存 监控 NoSQL
Redis之事务
Redis之事务
|
3月前
|
NoSQL Redis
Redis事务:保证数据操作的一致性和可靠性
Redis事务:保证数据操作的一致性和可靠性
|
1月前
|
监控 NoSQL Redis
Redis事务和Redis管道
Redis事务和Redis管道
35 0
|
3月前
|
NoSQL 关系型数据库 MySQL
Redis(事务)
Redis(事务)
46 2