Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

41 | 如何设计更优的分布式锁?

刘超 2019-08-24
你好,我是刘超。
从这一讲开始,我们就正式进入最后一个模块的学习了,综合性实战的内容来自我亲身经历过的一些案例,其中用到的知识点会相对综合,现在是时候跟我一起调动下前面所学了!
去年双十一,我们的游戏商城也搞了一波活动,那时候我就发现在数据库操作日志中,出现最多的一个异常就是 Interrupted Exception 了,几乎所有的异常都是来自一个校验订单幂等性的 SQL。
因为校验订单幂等性是提交订单业务中第一个操作数据库的,所以幂等性校验也就承受了比较大的请求量,再加上我们还是基于一个数据库表来实现幂等性校验的,所以出现了一些请求事务超时,事务被中断的情况。其实基于数据库实现的幂等性校验就是一种分布式锁的实现。
那什么是分布式锁呢,它又是用来解决哪些问题的呢?
在 JVM 中,在多线程并发的情况下,我们可以使用同步锁或 Lock 锁,保证在同一时间内,只能有一个线程修改共享变量或执行代码块。但现在我们的服务基本都是基于分布式集群来实现部署的,对于一些共享资源,例如我们之前讨论过的库存,在分布式环境下使用 Java 锁的方式就失去作用了。
这时,我们就需要实现分布式锁来保证共享资源的原子性。除此之外,分布式锁也经常用来避免分布式中的不同节点执行重复性的工作,例如一个定时发短信的任务,在分布式集群中,我们只需要保证一个服务节点发送短信即可,一定要避免多个节点重复发送短信给同一个用户。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(25)

  • -W.LI-
    老师好!基于数据库的实现,我现在项目中直接不开事务,select后插入(oeder_no做唯一约束)。try_catch 异常,重试3次。如果查到了返回成功保证密等。这么做会有问题么?
    课后题:万一收到的N/2+1节点全部挂了肯定会有问题。不知道,从新选为master节点的算法不知,如果会选择没有收到的节点做master也会有问题。

    作者回复: 没有问题。

    问题的答案:redis实现的分布式锁,都是有一个过期时间,如果一旦服务A出现stop the world的情况,有可能锁过期了,而此时服务A中仍然存在持有锁,此时另外一个服务B又获取了锁,这个时候存在两个服务同时获取锁的可能。

    2019-08-24
    5
  • a、
    不一定,因为如果集群中有5个redis,abcde,如果发生网络分区,abc在一个分区,de在一个分区,客户端A向abc申请锁成功,在c节点master异步同步slave的时候,master宕机了,slave接替,然后c的slave又和de在一个分区里,这时候如果客户端B来申请锁,也就可以成功了。
    zk锁也会出现问题,如果客户端A申请zk锁成功,这时候客户端A和zk不在一个分区里,zk就会把临时节点删除,然后如果客户端B再去申请,也就可以申请成功

    作者回复: 对的,这种情况也是可能发生的,前提是c节点在宕机之前没有持久化锁。

    第二zk锁的问题,如果连接session已经断开,客户端的锁是会释放的,不会存在同时获取锁的情况。

    2019-08-24
    1
    4
  • 我已经设置了昵称
    不太懂redission机制,每个节点各自去获取锁。超过一半以上获取成功就算成功。那是不是还有这么一步:这些一半以上的机器获取了以后,是否还要决定谁真正拿到锁,才能真正执行这个任务

    作者回复: 都会设置锁对象

    2019-08-25
    1
  • neohope
    老师您好,我有两个问题:
    redisson会向所有的redis节点并发发起请求,获取一半以上的锁才会进行后续操作。那我的疑问是,
    1、这样不会让整体性能变得比redis集群要差一些吗?而且redis节点越多,redisson性能越差?
    2、redisson的客户端,如果无法获取到足够多redis节点的锁,其释放规则及防止冲突的规则又是如何处理的呢?如果没有合理的防冲突规则,感觉并发量越高,性能会越差。

    作者回复: 鱼和熊掌不可兼得,保证可靠性的前提下,会带来一定的性能损失。

    当在一定时间内没有获取到足够节点时,会通过定时任务将已经超时的锁通过lua脚本来释放。

    2019-11-26
  • 任鹏斌
    老师我从redis官方文档的setnx命令里并没发现支持过期时间呀,setex倒是可以,您文中的代码里jedis使用的是set,这是怎么回事呢?

    作者回复: 2.6.12版本中,使用SET代替SETNX ,相当于SETNX+EXPIRE实现了原子性,不必担心SETNX成功,而EXPIRE失败的问题。

    2019-11-19
  • 青春超无敌
    老师,redis和zk实现分布式锁。这两种除了性能区别,还有其他方面的差别吗

    作者回复: 实现原理不一样,性能不一样

    2019-11-17
  • 李豪
    Redis setnx方式设置分布式锁存在一定的缺陷——它加锁只作用在一个Redis节点上,如果通过sentinel保证高可用,如果master节点由于某些原因发生了主从切换,那么就会出现锁丢失的情况:
    1.客户端1在Redis的master节点上拿到了锁。
    2.Master宕机了,存储锁的key还没有来得及同步到Slave上。
    3.master故障,发生故障转移,slave节点升级为master节点。
    4.客户端2从新的Master获取到了对应同一个资源的锁。
    于是,客户端1和客户端2同时持有了同一个资源的锁。锁的安全性被打破了。
    2019-11-14
  • 风轻扬
    老师,redisson实现的分布式锁。您写的例子
     .setScanInterval(2000) //集群状态的扫描时间,单位是毫秒
    这个设置有什么用啊?

    作者回复: 这是官方给出的一种连接redis集群的参考方式,具体作用已经写出了,类似一个心跳机制:
    https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95#24-%E9%9B%86%E7%BE%A4%E6%A8%A1%E5%BC%8F

    2019-10-14
  • 风轻扬
    老师,我试了一下redisson实现的分布式锁。有两个问题请教您。
    1 .redis的集群模式,我在一台机器上建了一个伪集群。创建集群时,一共6个节点。3主3从。从节点是自动分配的。从节点的只读模式需要改成no吗?(不改成no,往从节点写锁,就会转移到主节点上去)
    2 .字数限制,没办法贴代码,我在您例子的基础上增加了3个节点,也就是6个节点。核心代码如下:
    final long waitTimeout = 10;
            final long leaseTime = 3;
    final RLock lock1 = redissonClient1.getLock("lock1");
            final RLock lock2 = redissonClient2.getLock("lock2");
            final RLock lock3 = redissonClient3.getLock("lock3");
            final RLock lock4 = redissonClient4.getLock("lock4");
            final RLock lock5 = redissonClient5.getLock("lock5");
            final RLock lock6 = redissonClient6.getLock("lock6");

    for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3, lock4, lock5, lock6);
                        try {
                            if (redLock.tryLock(waitTimeout, leaseTime, TimeUnit.SECONDS)) {
                                //业务逻辑
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } finally {
                            redLock.unlock();
                        }
                    }
                }).start();
            }
    使用6个节点来实现redisson,但是获取锁一直失败,怎么回事呢?老师
    2019-10-14
    1
  • 疯狂咸鱼
    老师,分布式锁到底锁什么呢,如果说是锁数据库表,分布式应用集群的情况下,如果是单机数据库,数据库自身的锁机制可以保证并发问题吧?难道是分布式锁只是用在数据库分库分表的情况下?

    作者回复: 分布式锁是在分布式服务的情况下保证原子性操作,而不是因为数据库产生的分布式锁。

    数据库可以实现分布式锁,是一种实现方式。

    2019-10-09
  • 风轻扬
    老师,我试了一下zookeeper的集群分布式锁。测试代码如下:
    public class TestZookeeperLock {
        private static int count = 10;

        public static void main(String[] args) {
            //重试策略,以下写法为:重试3次,每次间隔时间为3秒
            final RetryPolicy retryPolicy = new RetryNTimes(3,2000);
            final String connUrl = "192.111.111.111:2181,192.222.222.222:2181";
            for (int i = 0; i < 10; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //zookeeper分布式锁
                        CuratorFramework zk = CuratorFrameworkFactory.newClient(connUrl, retryPolicy);
                        zk.start();
                        InterProcessMutex lock = new InterProcessMutex(zk, "/opt/uams/zookeeper-3.4.7/locks");
                        try {
                            if (lock.acquire(3, TimeUnit.SECONDS)){
                                get();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        } finally {
                            try {
                                //释放锁
                                lock.release();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }).start();
            }
        }

        public static void get (){
            count --;
            if (count == 3) {
                try {
                    TimeUnit.SECONDS.sleep(3);//这里设置该线程睡眠2秒,已达到锁住效果
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(count);
        }
    }
    输出了:
    9
    8
    7
    6
    5
    4
    java.lang.IllegalMonitorStateException: You do not own the lock: /opt/uams/zookeeper-3.4.7/locks
    at org.apache.curator.framework.recipes.locks.InterProcessMutex.release(InterProcessMutex.java:140)
    at cn.org.test.TestZookeeperLock$1.run(TestZookeeperLock.java:47)
    at java.lang.Thread.run(Thread.java:745)
    3
    多次输出的结果一致,这是怎么回事呢?

    作者回复: 将以下代码提出到new Thread之外:
    //zookeeper分布式锁
                        CuratorFramework zk = CuratorFrameworkFactory.newClient(connUrl, retryPolicy);
                        zk.start();
                        InterProcessMutex lock = new InterProcessMutex(zk, "/opt/uams/zookeeper-3.4.7/locks");

    2019-09-26
  • 风轻扬
    老师,zk实现的锁,不会出现redis锁一样的问题吗?
    设想:
    应用1和应用2两个服务分别部署到不同的服务器上。是使用zookeeper实现分布式锁。应用1获取到锁,然后开始长时间gc,应用2也开始长时间gc。应用1的zk锁由于心跳超时释放了锁,应用2结束gc获取到锁,应用1结束gc开始执行任务,此时不就有两个任务在同时执行了吗?

    作者回复: 是的,这种情况也同样存在同时获取锁的可能

    2019-09-25
  • 风轻扬
    老师,互联网行业,多数都是redis集群啊,如果这样,基于redis实现的分布式锁是不是就不能用了?

    作者回复: 可以,使用Redisson就好了

    2019-09-24
  • godtrue
    我们的导入功能就是用的redis分布式锁,防止多个业务操作人员同时导入,超时时间一般为五分钟。
    出现网络分区只能二选一要A或者C,不过互联网企业基本都会选择A。
    2019-09-13
  • 木刻
    老师你好,我尝试了下第一个,模拟并发情况下发现会有概率抛数据库异常: Deadlock found when trying to get lock; try restarting transaction
    https://github.com/mygodmele/DbLock.git

    作者回复: 运行了代码,并没有出现死锁问题,麻烦贴出数据库脚本

    2019-09-11
    2
  • K
    老师好,课后问题还是没听懂,首先我理解redis集群可能同时获取锁,是因为锁时间超时了,别的线程也能拿到,是这个原因。Redlock 算法是怎样解决这个问题的呢?

    作者回复: RedLock算法是会去每一个节点获取锁,正常情况下,别的线程无法同时获取锁的。


    2019-09-08
  • 知行合一
    老师,想问个问题,redis集群已经分了槽,客户端写入根据算法应该写入一个节点啊,为啥要多个节点同时枷锁?

    作者回复: 写入一个单点只实现了高可用,没有实现集群式分布式锁。单点的问题会存在单个节点挂了的情况下,不同应用服务同时获取锁的可能。

    2019-09-05
  • Jxin
    1.锁超时,也会出现多个任务同时持有锁进行。
    2.解决方式,守护线程续航锁持有时间。
    3.弊端,浪费线程,开销太大。
    4.根据业务情况设置合理的超时时间是最棒的。

    5.集群环境还会导致事务失效(同时提交多个key,多个key在不同节点)挺蛋疼。
    2019-09-04
  • 再续啸傲
    Redisson的“看门狗”watch机制,解决了业务执行时间长于锁过期时间的问题。但是为每一个获取锁的线程设置监听线程,会不会在高并发的场景下耗费过多资源呢?

    作者回复: 应该是一个线程监听,具体需要看源码实现。

    2019-09-03
  • zero
    用etcd实现锁,是不是更好呢
    2019-08-28
收起评论
25
返回
顶部