文档章节

Redis应用学习——Redis事务与实现分布式锁

江左煤郎
 江左煤郎
发布于 2018/11/13 17:42
字数 2733
阅读 164
收藏 9

1. Redis事务机制

    1. 与MySQL等关系数据库相同,Redis中也有事务机制,Redis的事务实质上是命令的集合,但Redis中的事务机制不保证事务的原子性,这与关系型数据库中的事务不同,在一个事务中要么所有命令都被执行,要么所有事物都不执行。 一个事务从开始到执行会经历以下三个阶段:

  • 开始事务。
  • 命令入队。
  • 执行事务。

在MySQL中使用START TRANSACTION 或 BEGIN开启一个事务,使用COMMIT提交一个事务;而在Redis中使用MULTI 开始一个事务,由 EXEC 命令触发事务, 一并执行事务中的所有命令。和关系型数据库中的事物相比,在redis事务中如果有某一条命令执行失败,其它的命令仍然会被继续执行,也就是Redis中不支持事务的回滚,也就不具备事务的原子性

    2. Redis事务机制的相关指令:

  • MULTI:用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子执行
  • EXEC:执行命令队列中的所有命令,但如果在一个事务内执行了WATCH命令,那么只有当WATCH所监控的keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,否则EXEC将放弃当前事务中的所有命令。
  • DISCARD:取消执行事务队列中的所有命令,同时再将当前连接的状态恢复为正常状态,即非事务状态。如果WATCH命令被使用,该命令将UNWATCH所有的keys。注意,该指令并不是Redis的回滚指令,Redis中不支持回滚,该指令只是取消事务中的所有指令的执行
  • WATCH  key[key...]:在MULTI命令执行之前,可以指定待监控的keys,在执行EXEC之前,如果被监控的keys发生修改,EXEC将放弃执行该事务队列中的所有指令。WATCH命令可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC或DISCARD命令。该命令可以保证某个key的CAS
  • UNWATCH:取消当前事务中指定监控的keys,如果执行了EXEC或DISCARD命令,则无需再手工执行该命令了,因为在此之后,事务中所有的keys都将自动取消监控

    3. 命令使用示例:

//正常执行
127.0.0.1:6379> redis-cli -h 127.0.0.1 -p 6379    //命令拼接redis服务器
ok
127.0.0.1:6379> get test                                      //获取test的键值
"hello world"
127.0.0.1:6379> multi          //生成事务
ok
127.0.0.1:6379> set test "hello mygod"               //修改指令
QUEUED
127.0.0.1:6379>exec                                           //提交事务
1) OK
127.0.0.1:6379>

 

2. 分布式锁

    1. 产生背景:分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。在很多场景中,我们为了保证数据的最终一致性,需要很多的技术方案来支持,比如分布式事务、分布式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在分布式场景中就无能为力了。也就是说单纯的Java Api并不能提供分布式锁的能力。所以针对分布式锁的实现目前有多种方案。

    2. 实现分布式锁的方案:典型的方案有以下几种

  • 基于数据库实现分布式锁 
  • 基于缓存(redis,memcached,tair)实现分布式锁
  •  基于Zookeeper实现分布式锁

    3. 分布式锁的要求:

  • 可以保证在分布式部署的应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行
  • 这把锁要是一把可重入锁(避免死锁)
  • 这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条)
  • 有高可用的获取锁和释放锁功能
  • 获取锁和释放锁的性能要好

3. 分布式锁的数据库实现方案

    1. 基于数据库表的实现:最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的数据来实现,当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。

  • 首先创建一个分布式锁的表,可以把里面存储的看做分布式锁
    CREATE TABLE `methodLock` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
      `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
      `desc` varchar(1024) NOT NULL DEFAULT '备注信息',
      `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
      PRIMARY KEY (`id`),
      UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
    
  • 如果想要对分布式执行的某个方法加锁,就使用这个方法名向表中插入数据
    insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)

    因为我们对method_name做了唯一性约束,这里如果有多个插入请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,可以执行方法体内容。

  • 当方法执行完毕之后,想要释放锁的话,需要执行以下Sql删除锁
    delete from methodLock where method_name ='method_name'

     

    2. 产生的问题:

  • 这把锁强依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
  • 这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得到锁。
  • 这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
  • 这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。

    3. 解决办法:

  • 数据库是单点?搞两个数据库,数据之前双向同步。一旦挂掉快速切换到备库上。
  • 没有失效时间?只要做一个定时任务,每隔一定时间把数据库中的超时数据清理一遍。
  • 非阻塞的?搞一个while循环,直到insert成功再返回成功。
  • 非重入的?在数据库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询数据库,如果当前机器的主机信息和线程信息在数据库可以查到的话,直接把锁分配给他就可以了。

4. 基于Redis缓存实现的分布式锁

    1. 相比较于基于数据库实现分布式锁的方案来说,基于缓存来实现在性能方面会表现的更好一点(连接数据库进行读写操作性能耗费比缓存大)。而且很多缓存是可以集群部署的,可以解决单点问题。

    2. Redis中有直接的命令支持,而且Redis的本身命令执行是一个单线程的,这就为分布式锁提供了很好的实现,实现命令如下

  • SETNX key val:当且仅当key不存在时,set才会成功,返回1;若key存在,操作失败,返回0。
  • expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
  • delete key:删除key,即释放锁

    3. Jedis客户端也提供了相应的方法,主要就是setnx(String key,String value)方法,简单实现思想伪代码如下:

String get(String key) {
//首先尝试从redis(或redis集群)中获取key对应的数据  
   String value = redis.get(key); 
//如果为null,则使用redis中的分布式锁
   if (value  == null) {  
//通过setnx方法创建分布式锁
    if (redis.setnx(key_mutex, "1")) {  
        // 设置分布式锁的过期时间,可以避免死锁 
        redis.expire(key_mutex, 3 * 60)  
        value = db.get(key); //从数据库中取得数据 
        redis.set(key, value);//回写到缓存中  
        redis.delete(key_mutex);//释放锁  
    } else {  
        //其他线程休息50毫秒后重试  
        Thread.sleep(50);  
        get(key);  
    }  
  }  
}  

5. 基于Zookeeper实现分布式锁

    1. ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

(1)创建一个目录mylock; 
(2)线程A想获取锁就在mylock目录下创建临时顺序节点; 
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁; 
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点; 
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式

© 著作权归作者所有

江左煤郎

江左煤郎

粉丝 26
博文 97
码字总数 238387
作品 0
西安
后端工程师
私信 提问
【分布式缓存系列】Redis实现分布式锁的正确姿势

一、前言   在我们日常工作中,除了Spring和Mybatis外,用到最多无外乎分布式缓存框架——Redis。但是很多工作很多年的朋友对Redis还处于一个最基础的使用和认识。所以我就像把自己对分布式...

编辑之路
01/21
4.9K
18
分布式锁与实现(一)——基于Redis实现

概述 目前几乎很多大型网站及应用都是分布式部署的,分布式场景中的数据一致性问题一直是一个比较重要的话题。分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consisten...

飓风2000
2018/09/12
40
0
使用Redis实现分布式锁及其优化

使用Redis实现分布式锁及其优化 Mz的博客2017-11-0128 阅读 RedisJava分布式 目前实现分布式锁的方式主要有数据库、Redis和Zookeeper三种,本文主要阐述利用Redis的相关命令来实现分布式锁。...

Mz的博客
2017/11/01
0
0
Lind.DDD.Repositories.Redis层介绍

之前已经发生了 大叔之前介绍过关于redis的文章,有缓存,队列,分布式pub/sub,数据集缓存以及仓储redis的实现等等,而今天在Lind.DDD的持久化组件里,redis当然也有一席之地,作为当今最红...

mcy247
2017/12/07
0
0
Redis常见的应用场景解析

Redis是一个key-value存储系统,现在在各种系统中的使用越来越多,大部分情况下是因为其高性能的特性,被当做缓存使用,这里介绍下Redis经常遇到的使用场景。 Redis特性 一个产品的使用场景肯...

IT米粉
2017/09/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
717
10
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
40
0
spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
26
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
75
0
maven 环境隔离

解决问题 即 在 resource 文件夹下面 ,新增对应的资源配置文件夹,对应 开发,测试,生产的不同的配置内容 <resources> <resource> <directory>src/main/resources.${deplo......

之渊
昨天
74
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部