• redis事务


    事务是什么

    可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞。

    能干嘛?

    一个队列中,一次性、顺序性、排他性的执行一系列命令。

    常用命令

    命令描述
    DISCARD 取消事务,放弃执行事务块内的所有命令。
    EXEC 执行所有事务块内的命令。
    MULTI 标记一个事务的开始。
    UNWATCH 取消 WATCH 命令对所有 key 的监视。
    WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

    正常执行

    放弃事务

     

    全体连坐

    错误发生在exec之前,类似于编译器出错

    冤头债主

    错误发生在exec之后,类似于运行期出错

    redis事务的3个阶段

    开启:以multi开启事务

    入队:将多个命令入队到事务中,接到这些命令不会立刻执行,而是放到等待执行的事务队列里面

    执行:有exec命令触发事务

    redis事务的3个特性

    单独的隔离操作:事务中的所有命令都会序列化,按顺序的执行。事务在等待执行的时候,不会被其他客户端发送来的米命令请求打断

    没有隔离级别的概念:队列中的所有命令没有提交exec之前都是不会被执行的。

    不保证原子性:redis中如果一条命令执行失败,其后的命令仍然会被执行,没有回滚。

    悲观锁/乐观锁/CAS(Check And Set)

    • 悲观锁
      • 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
    • 乐观锁
      • 乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
      • 乐观锁策略:提交版本必须大于记录当前版本才能执行更新,否则报异常,说明在此期间有其他人的写操作修改了版本号。

    CAS(属于乐观锁)

    • Compare and Swap,即比较再交换。CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快
    • JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是悲观锁。
    • jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁

    对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

    public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }

    场景举例:

    假设t1,t2线程是同时更新同一变量56的值,因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。

    假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。t1线程去更新变量值改为57,然后写到内存中。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。

    此时对于t2来说,内存值变为了57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。

    就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。

    CAS的ABA问题

    线程intT2获取到的变量值A,尽管期望和当前的内存实际值相同,但可能存在的问题是:内存地址V中的变量已经经历了A->B->A的改变。

    解决方案:

    JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。如果当前引用 == 预期引用,并且当前标志 == 预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。源码如下:

    /**
     *expectedReference - 该引用的预期值
     *newReference - 该引用的新值
     *expectedStamp - 该标志的预期值
     *newStamp - 该标志的新值
     */
    public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }

    Watch锁监控

    watch指令,类似乐观锁,如果在本事务执行之前,key的值已经被修改了,那么整个事务队列都不会被执行,同时返回一个Nullmulti-bulk应答以通知调用者事务执行失败。

    注意:一旦执行了exec或者discard,之前加的所有监控锁都会被取消掉了。

    例子:刷信用卡

    初始化信用卡的可用余额和欠额:没执行watch,即没有加锁

     我们执行watch锁,无加塞篡改的情况:

     我们执行watch锁,有加塞篡改的情况,当watch的key被修改,后面的那个事务全部执行失败(意味着后面的事务必须拿到最新的300才可以执行)

     unwatch

  • 相关阅读:
    Mac 生成public_key
    OmniGraffler软件和激活码
    Maven将本地项目打包后引入本地另一个项目
    spring boot 项目启动无法访问,排查
    服务端推送
    使用IDEA进行commit合并(折叠)
    将map转为Object,支持 Date/Boolean
    mysql 删除同样记录只保留一条
    Springboot文件上传限制
    Springboot 上传文件
  • 原文地址:https://www.cnblogs.com/yanl55555/p/13468865.html
Copyright © 2020-2023  润新知