Redis(六)——Redis6的事务和锁机制(未完成,待补)

本文介绍了Redis中的事务处理和锁机制,包括基本操作、乐观锁与悲观锁的区别及应用场景,并通过秒杀案例深入探讨了超卖和超时问题及其解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

基本操作

事务冲突

悲观锁

乐观锁

演示乐观锁和事务特性

Redis事务三特性

秒杀案例

超卖和超时问题

库存遗留问题


基本操作

        Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

Redis事务的主要作用就是串联多个命令防止别的命令插队。

        从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,只是放在队列中等待执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。组队的过程中可以通过discard放弃组队。 

事务的错误处理(两种方式)

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。

 组队阶段报错,提交失败

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

组队成功,提交有成功有失败情况 

(一個編譯錯誤 一個運行錯誤 ?)

事务冲突

事务冲突问题,多个人同时操作这10000块钱:

悲观锁

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

        我拿到了10000块钱,先给他上锁,这样别人就无法操作这10000块钱了,只能看着我操作,当我减去8000块钱的时候,还剩下2000,这个过程block中别人不能操作,变成2000块钱之后就把它解锁,解锁之后别人就能拿到这2000进行操作,当别人拿到这两千的时候就会上锁,想减去5000,现两千块钱不够,最终不能操作。

        每次操作之前先上锁,当操作完之后再解锁,别人才能够操作,这种方式就叫做悲观锁方式,这种方式的缺点是效率低,只能单人操作。

乐观锁

        乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量Redis就是利用这种check-and-set机制实现事务的。

         我们在数据操作的时候,给数据加上一个字段叫做版本号表示数据版本,假如10000第一次的版本号是1.0,此时所有人都能得到这个版本的数据,这个数据的版本都是1.0(第一个人和第二个人都得到10000的数据,他们的版本都是1.0),第一个人比较快,减了8000块钱,最终就变为2000,如果数据改变的话,在改的时候它会自我操作,除了植被更改以外,它的版本号也同步进行更新,之前是1.0,当变为2000的时候就变为了1.1。此时第二个人拿着数据,就会检查当前数据的版本号跟数据库当中的版本号是否一致,因为数据库当中的版本号已经改为了1.1,而第二个人拿到的数据版本号还是之前的1.0,两个版本号相比较发现并不一样,如果不一致的话,那么就不能够继续操作

        抢票就是乐观锁典型的应用场景,比如在系统中只有一张票了,同时有很多人都去抢这张票,所有人都能去抢这张票,但只有一个人才能支付成功。

演示乐观锁和事务特性

WATCH key [key ...]

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

  • 在两个客户端添加监视:

  • 在两个客户端开启事务操作:

  • 在两个客户端分别改变balance的值:

在第一个客户端当中加10

在第二个客户端当中加20

 1中执行成功

2中执行失败

        AB同时判断version成功,谁先事务中赋值exec成功,未赋值的就会失败。

       unwatch取消 WATCH 命令对所有 key 的监视。如果在执行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被执行了的话,那么就不需要再执行UNWATCH 了。

Redis事务三特性

  • 单独的隔离操作

        事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

  • 没有隔离级别的概念

        队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。

  • 不保证原子性

        事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚(如果在组队中命令没有失败,在提交过程中失败的那个命令失败,而其他的正常执行)。

秒杀案例

        假如我现在拿出来十个商品进行秒杀,假如有100个人参与,假如100个人当中有一个人抢到了商品,那么商品库存就要减1变成9,然后这个抢到商品的用户就要添加到秒杀成功者清单当中,以此类推。

Servlet: 

public class SecKillServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

    public SecKillServlet() {
        super();
    }

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		String userid = new Random().nextInt(50000) +"" ;
		String prodid =request.getParameter("prodid");
		
		//boolean isSuccess=SecKill_redis.doSecKill(userid,prodid);
		boolean isSuccess= SecKill_redisByScript.doSecKill(userid,prodid);
		response.getWriter().print(isSuccess);
	}

}

        通过随机数生成用户ID,然后得到传过来的商品ID,然后调用秒杀方法, 判断成功或者失败,核心就是秒杀过程。

秒杀方法(基本实现):

package com.atguigu;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.LoggerFactory;

import ch.qos.logback.core.rolling.helper.IntegerTokenConverter;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;

/**
 *秒杀方法
 */
public class SecKill_redis {

	public static void main(String[] args) {
		Jedis jedis =new Jedis("192.168.x.xxx",6379);
		System.out.println(jedis.ping());
		jedis.close();
	}

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid) throws IOException {
		//1 uid和prodid非空判断
		if(uid == null || prodid == null) {
			return false;
		}

		//2 连接redis
		Jedis jedis = new Jedis("192.168.x.xxx",6379);

		//3 拼接key
		// 3.1 库存key
		String kcKey = "sk:"+prodid+":qt";
		// 3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";

		//4 获取库存,如果库存null,秒杀还没有开始
		String kc = jedis.get(kcKey);
		if(kc == null) {
			System.out.println("秒杀还没有开始,请等待");
			jedis.close();
			return false;
		}

		// 5 判断用户是否重复秒杀操作
		if(jedis.sismember(userKey, uid)) {
			System.out.println("已经秒杀成功了,不能重复秒杀");
			jedis.close();
			return false;
		}

		//6 判断如果商品数量,库存数量小于1,秒杀结束
		if(Integer.parseInt(kc)<=0) {
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		}

		//7.1 库存-1
		jedis.decr(kcKey);
		//7.2 把秒杀成功用户添加清单里面
		jedis.sadd(userKey,uid);

		System.out.println("秒杀成功了..");
		jedis.close();
		return true;
	}
}

ab工具模拟高并发

在linux中 yum install httpd-tools 命令安装

-n:请求数量

-c:请求中的并发数量

-p:提交的参数(POST提交)

-T:参数类型

vim postfile 模拟表单提交参数,以&符号结尾;存放当前目录。

内容:prodid=0101&

ab -n 1000 -c 100 -k -p ~/postfile -T application/x-www-form-urlencoded http://192.168.x.xxx:8081/Seckill/doseckill

模拟并发

两个问题:

redis中的数据:

两个问题:

  • 连接超时

        当请求过多时,redis无法处理这么多请求,有的请求不能处理,就需要等待,如果等待的时间过长,还没有连接上就会超时。

  • 超卖

        商品已经秒杀完成了,但是还提示秒杀成功。

 26-事务和锁机制-秒杀案例-超卖和超时问题解决_哔哩哔哩_bilibili

超卖和超时问题

库存遗留问题

### Redis 事务机制概述 Redis 使用 `MULTI`、`EXEC`、`DISCARD` `WATCH` 命令来实现事务功能[^2]。这些命令使得一组命令可以作为一个整体被执行,确保它们要么全部成功执行,要么都不执行。 #### 事务特点 - **原子性**:虽然 Redis事务不提供严格的 ACID 中的原子性,但在执行过程中,所有命令会按照顺序依次执行,不会被打断。 - **一致性**:当命令语法错误、命令执行错误或 Redis 发生意外故障时,Redis 事务机制能够保证数据的一致性[^3]。 - **隔离性**:事务中的所有命令会被序列化并按顺序执行,在整个过程里不会受到其他客户端的影响[^4]。 #### 主要命令解释 - **MULTI**:标记一个事务块的开始。之后所有的命令都将被放入队列中等待执行。 - **EXEC**:触发之前排队的所有命令一次性执行,并返回各个命令的结果列表。 - **DISCARD**:取消当前事务,清空已经入队但是还未执行的命令。 - **WATCH**:监视键的变化情况;如果在 WATCH 后有任何修改,则 EXEC 执行失败,以此支持乐观锁的功能[^1]。 #### 错误处理与回滚特性 需要注意的是,即使某个命令在事务内发生了运行期错误(比如除零),这并不会导致整个事务自动回滚——只有那些确实能完成的操作才会生效,而无法继续下去的部分则停止尝试。因此开发者应该自行设计合理的逻辑去应对可能出现的问题。 ```bash # 开始一个新的事务 MULTI # 将几个命令加入到事务队列中 SET key1 "value" INCR key2 LPUSH mylist "item" # 提交事务 EXEC ``` 对于更复杂的业务需求来说,还可以利用 Lua 脚本来编写更加紧凑高效的程序片段来进行批量操作,从而达到类似的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值