分布式锁(zookeeper)与接口幂等性实现

2023-10-27

背景

随着数据量的增大,用户的增多,系统的并发访问越来越大,传统的单机已经满足不了需求,分布式系统成为一种必然的趋势。分布式系统错综复杂,今天,我们着重对分布式系统的互斥性与幂等性进行分析与解决。

互斥性

互斥性问题也就是共享资源的抢占问题。如何解决呢?也就是锁,保证对共享资源的串行化访问。互斥性要如何实现?。在java中,最常用的是synchronized和lock这两种内置的锁,但这只适用于单进程中的多线程。对于在同一操作系统下的多个进程间,常见的锁实现有pv信号量等。然而,当问题扩展到多台机器的多个操作系统时,也就是分布式锁,情况就复杂多了。

  • 锁要存在哪里。必须提供一个所有主机都能访问到的存储空间
  • 加锁的进程在挂掉之后,如何确保锁被解开,释放资源。可以通过超时机制或者定时检测心跳来实现
  • 不同进程间如何获取相同的唯一标识来竞争锁。可以利用要保护的资源生成一个唯一的id
  • 获取锁操作的原子性。必须保证读取锁状态、加锁两步的原子性
  • 锁的可重入性。某个线程试图再次获取由自己持有的锁,这个操作会百分百成功,这就是可重入性。如果不能保证可重入性,就会有死锁的可能。
  • 阻塞锁与自旋锁。当获取不到锁时,阻塞锁就是线程阻塞自身,等待唤醒,自旋锁就是不断的尝试重新获取锁。
  • 公平锁与非公平锁。公平锁保证按照请求的顺序获取锁,非公平锁就是可以插队。公平锁一般要维持一个队列来实现,所以非公平锁的性能会更好一点。
  • 避免惊群效应。如果分布式锁是阻塞锁,当锁的占有者释放锁时,要避免同时唤醒多个阻塞的线程,产生惊群效应。

zookeeper实现

今天重点讲解使用zookeeper实现分布式锁。个人感觉zookeeper是最适合实现分布式锁。它的几个特性:

  • 顺序节点:可以避免惊群效应
  • 临时节点:避免机器宕机倒是锁无法释放
  • watch机制:可以及时唤醒等待的线程

zk实现分布式锁的流程如下
这里写图片描述
我这里用zk实现了一个可重入的、阻塞的、公平的分布式锁,代码如下:

package locks;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
import utils.ZkUtils;
import watcher.PredecessorNodeWatcher;
import watcher.SessionWatcher;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* Created by huangwt on 2018/3/21.
*/
@Slf4j
public class ReentrantZKLock {

    private final static String BASE_NODE = "/baseNode";
    private final static String CHILDREN_NODE = "/node_";

    private final Lock localLock;
    private final Condition condition;

    //用于重入检测
    private static ThreadLocal<AtomicInteger> threadLocal = new ThreadLocal<AtomicInteger>();

    private ZooKeeper zooKeeper = null;

    private String node = null;

    ReentrantZKLock(String addr, int timeout) {
        try {
            zooKeeper = new ZooKeeper(addr, timeout, new SessionWatcher());
            localLock = new ReentrantLock();
            condition = localLock.newCondition();
        } catch (IOException e) {
            log.error("get zookeeper failed", e);
            throw new RuntimeException(e);
        }
    }

    public void lock() {
        //重入检测
        if (checkReentrant()) {
            return;
        }
        try {
            node = zooKeeper.create(BASE_NODE + CHILDREN_NODE, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            while (true) {
                localLock.lock();
                try {
                    List<String> childrenNodes = zooKeeper.getChildren(BASE_NODE, false);
                    ZkUtils.childNodeSort(childrenNodes);
                    //当前节点的索引
                    int myNodeIndex = childrenNodes.indexOf(node);
                    //当前节点的前一个节点
                    int beforeNodeIndex = myNodeIndex - 1;
                    Stat stat = null;
                    while (beforeNodeIndex >= 0) {
                        stat = zooKeeper.exists(childrenNodes.get(beforeNodeIndex), new PredecessorNodeWatcher(condition));
                        if (stat != null) {
                            break;
                        }
                    }

                    if (stat != null) {  //前序节点存在,等待前序节点被删除,释放锁
                        condition.await();
                    } else { // 获取到锁
                        threadLocal.set(new AtomicInteger(1));
                        return;
                    }
                } finally {
                    localLock.unlock();
                }
            }
        } catch (Exception e) {
            log.error("lock failed", e);
            throw new RuntimeException(e);
        }

    }

    public void unlock() {
        AtomicInteger times = threadLocal.get();
        if (times == null) {
            return;
        }
        if (times.decrementAndGet() == 0) {
            threadLocal.remove();
            try {
                zooKeeper.delete(node, -1);
            } catch (Exception e) {
                log.error("unlock faild", e);
                throw new RuntimeException(e);
            }
        }

    }

    private boolean checkReentrant() {
        AtomicInteger times = threadLocal.get();
        if (times != null) {
            times.incrementAndGet();
            return true;
        }

        return false;
    }
}
package utils;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
* Created by huangwt on 2018/3/24.
*/
public class ZkUtils {

    /**
    * 对子节点排序
    *
    * @param node
    */
    public static void childNodeSort(List<String> node) {
        Collections.sort(node, new ChildNodeCompare());
    }

    private static class ChildNodeCompare implements Comparator<String> {

        public int compare(String childNode1, String childNode2) {
            return childNode1.compareTo(childNode2);
        }
    }

}
package watcher;

import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;

import java.util.concurrent.locks.Condition;

/**
* Created by huangwt on 2018/3/24.
*/
public class PredecessorNodeWatcher implements Watcher {
    private Condition condition = null;

    public PredecessorNodeWatcher(Condition condition) {
        this.condition = condition;
    }

    public void process(WatchedEvent event) {
        //前序节点被删除,锁被释放,唤醒当前等待线程
        if(event.getType() == Event.EventType.NodeDeleted){
            condition.signal();
        }
    }
}
package watcher;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;

/**
* Created by huangwt on 2018/3/24.
*/
@Slf4j
public class SessionWatcher implements Watcher {
    public void process(WatchedEvent event) {
        if (event.getState() == Event.KeeperState.SyncConnected) {
            log.info("get zookeeper success");
        }
    }
}

主要是使用了ThreadLocal实现了锁的可重入性,使用watch机制实现了阻塞锁,使用临时节点实现的公平锁。
这段代码只是一个demo供大家参考,还有很多问题没解决。比如当zookper挂掉的时候,阻塞的线程就无法被唤醒,这时候就需要监听zk的心跳。

幂等性

幂等性是系统接口对外的一种承诺,数学表达为:f(f(x)) = f(x)。
幂等性指的是,使用相同参数对同一资源重复调用某个接口的结果与调用一次的结果相同。

为什么需要幂等性?

假设现在有一个方法 :Boolean withdraw(account_id, amount) ,作用是从account_id对应的账户中扣除amount数额的钱,如果扣除成功则返回true,账户余额减少amount; 如果扣除失败则返回false,账户余额不变。
如以上流程,接口无法幂等,可能导致重复扣款。

解决

  • 请求获取ticketId
  • 请求扣款,传入ticketId
  • 根据ticketId查询此次操作是否存在,如果存在则表示该操作已经执行过,直接返回结果;如果不存在,扣款,保存结果
  • 返回结果到客户端

这里写图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分布式锁(zookeeper)与接口幂等性实现 的相关文章

  • 如何克服原语按值传递的事实

    我有一段很长的代码来计算两个值 doubles 对我来说 我在几个地方使用了这段代码 为了坚持 DRY 原则 我应该将这段代码重构为一个很好的单元测试方法 但是我不能让它返回两个双精度数 而双精度数是原始的 因此不能按值传递和操作 我能想到
  • 如何使用 Apache POI API 将图像添加到 pptx 中添加的图像占位符?

    我已经预定义了带有文本和图像占位符的 pptx 模板 我如何从模板访问和修改这些占位符 我可以使用 POI pptx API 直接将图像和文本添加到幻灯片中 但如何将其添加到模板的占位符中 请参阅链接以了解如何添加占位符来创建固定模板 ht
  • Java中字符串中特殊字符的替换

    Java中如何替换字符串 E g String a adf sdf 如何替换和避免特殊字符 您可以删除除此之外的所有字符可打印的 ASCII 范围 http en wikipedia org wiki ASCII ASCII printab
  • 这个函数(for循环)空间复杂度是O(1)还是O(n)?

    public void check 10 for string i list Integer a hashtable get i if a gt 10 hashtable remove i 这是 O 1 还是 O n 我猜测 O n 但不是
  • Java:迭代 Collection 的最佳方法(此处为 ArrayList)

    今天 当我看到一段我已经使用了数百次的代码时 我很高兴地开始编码 迭代集合 此处为 ArrayList 出于某种原因 我实际上查看了 Eclipse 的自动完成选项 这让我想知道 在什么情况下以下循环比其他循环更好使用 经典的数组索引循环
  • OpenCV 中的 Gabor 内核参数

    我必须在我的应用程序中使用 Gabor 过滤器 但我不知道这个 OpenCV 方法参数值 我想对虹膜进行编码 启动 Gabor 过滤器并获取特征 我想对 12 组 Gabor 参数值执行此操作 然后我想计算 Hamming Dystans
  • Cassandra java驱动程序协议版本和连接限制不匹配

    我使用的java驱动程序版本 2 1 4卡桑德拉版本 dsc cassandra 2 1 10cql 的输出给出以下内容 cqlsh 5 0 1 Cassandra 2 1 10 CQL spec 3 2 1 Native protocol
  • 使用 AES SecretKey 的 Java KeyStore setEntry()

    我目前正在 Java 中开发一个密钥处理类 特别是使用 KeyStore 我正在尝试使用 AES 实例生成 SecretKey 然后使用 setEntry 方法将其放入 KeyStore 中 我已经包含了代码的相关部分 The KS Obj
  • 在 S3 中迭代对象时出现“ConnectionPoolTimeoutException”

    我已经使用 aws java API 一段时间了 没有遇到太多问题 目前我使用的是库 1 5 2 版本 当我使用以下代码迭代文件夹内的对象时 AmazonS3 s3 new AmazonS3Client new PropertiesCred
  • hibernate锁等待超时超时;

    我正在使用 Hibernate 尝试模拟对数据库中同一行的 2 个并发更新 编辑 我将 em1 getTransaction commit 移至 em1 flush 之后我没有收到任何 StaleObjectException 两个事务已成
  • 在 Netbeans 8 上配置 JBoss EAP 的问题

    我已经下载了 JBoss EAP 7 并正在 Netbeans 8 上配置它 我已经到达向导 实例属性 其中要求从选择框中选择 域 当我打开选择框时 它是空的 没有什么可以选择的 因此 完成 按钮也处于非活动状态 这使得无法完成配置 我通过
  • 将 SignedHash 插入 PDF 中以进行外部签名过程 -workingSample

    遵循电子书第 4 3 3 节 PDF 文档的数字签名 https jira nuxeo com secure attachment 49931 digitalsignatures20130304 pdf 我正在尝试创建一个工作示例 其中 客
  • 使用 SQLITE 按最近的纬度和经度坐标排序

    我必须获得一个 SQLite SQL 语句 以便在给定初始位置的情况下按最近的纬度和经度坐标进行排序 这是我在 sqlite 数据库中的表的例句 SELECT id name lat lng FROM items EXAMPLE RESUL
  • 很好地处理数据库约束错误

    再一次 它应该很简单 我的任务是在我们的应用程序的域对象中放置一个具有唯一约束的特定字段 这本身并不是一个很大的挑战 我刚刚做了以下事情 public class Location more fields Column unique tru
  • 以编程方式在java的resources/source文件夹中创建文件?

    我有两个资源文件夹 src 这是我的 java 文件 资源 这是我的资源文件 图像 properties 组织在文件夹 包 中 有没有办法以编程方式在该资源文件夹中添加另一个 properties 文件 我尝试过这样的事情 public s
  • IntelliJ - 调试模式 - 在程序内存中搜索文本

    我正在与无证的第三方库合作 我知道有一定的String存储在库深处的某个字段中的某处 我可以预测的动态值 但我想从库的 API 中获取它 有没有一种方法可以通过以下方式进行搜索 类似于全文搜索 full程序内存处于调试模式并在某个断点处停止
  • Jersey 客户端请求中未设置 Content-Length-Header

    我正在使用 Jersey Client 访问网络服务 如下所示 response r accept MediaType TEXT PLAIN TYPE header content length 0 post String class 其中
  • Netty:阻止调用以获取连接的服务器通道?

    呼吁ServerBootstrap bind 返回一个Channel但这不是在Connected状态 因此不能用于写入客户端 Netty 文档中的所有示例都显示写入Channel从它的ChannelHandler的事件如channelCon
  • 将 Azure AD 高级自定义角色与 Spring Security 结合使用以进行基于角色的访问

    我创建了一个演示 Spring Boot 应用程序 我想在其中使用 AD 身份验证和授权 并使用 AD 和 Spring Security 查看 Azure 文档 我执行了以下操作 package com myapp contactdb c
  • 调整添加的绘制组件的大小和奇怪的摆动行为

    这个问题困扰了我好几天 我正在制作一个特殊的绘画程序 我制作了一个 JPanel 并添加了使用 Paint 方法绘制的自定义 jComponent 问题是 每当我调整窗口大小时 所有添加的组件都会 消失 或者只是不绘制 因此我最终会得到一个

随机推荐

  • javascript - 实现拍照功能(详细示例代码)

    介绍 HTML5 的 getUserMedia API 为用户提供访问硬件设备媒体 摄像头 视频 音频 地理位置等 的接口 基于该接口 开发者可以在不依赖任何浏览器插件的条件下访问硬件媒体设备 另外 主流浏览器 Firefox Chrome
  • MatLab 中计算开根号

    原文地址为 MatLab 中计算开根号 1 方法 例如 如果对4要开根号 可以输入函数如下 gt gt sqrt 4 2方法 根号其实是1 2次方 gt gt 4 1 2 同样可以得到结果 转载请注明本文地址 MatLab 中计算开根号
  • ANOMALY简记-ANOMALY LOCALITY IN VIDEO SURVEILLANCE

    创新点 提出了一个带有标记的异常检测库 总结 http imagelab ing unimore it UCFCrime2Local
  • linux编译安装kvm、qemu

    kvm作为主流虚拟化产品 其实它的用户层使用的是qemu 所以要安装使用kvm 一般需要安装kvm kmod以及qemu两部分 安装kvm kmod 1 首先下载kvm kmod源码并解压 2 进入源码目录 3 configure kern
  • Spring Web MVC框架(五) 文件上传

    Spring同样支持文件上传功能 不过该功能默认未开启 因为可能有些开发者可能希望自己处理文件上传过程 Spring的文件上传功能在org springframework web multipart包下 有两个MultipartResolv
  • gitLab清理大文件_包括历史记录中的大文件

    文章目录 gitLab清理大文件 包括历史记录中的大文件 前言 查看仓库大小 解除保护分支 操作 gitLab清理大文件 包括历史记录中的大文件 项目中经常有不小心提交的大文件 这个就是清理方法 后面发现了更好的方法 使用bfg快速清理gi
  • 02.01 服务器开机报错

    Dell PowerEdge T320服务器 开机显示 Fatal Errort all channells have been disabled due to DIMMS falled the memory 原因 内存自检失败 可能是太久
  • yolo算法的优缺点分析_【精选推荐】基于深度学习的单阶段目标检测算法研究综述...

    DOI 10 12132 ISSN 1673 5048 2019 0100 引用格式 刘俊明 孟卫华 基于深度学习的单阶段目标检测算法研究综述 J 航空兵器 2020 27 3 44 53 Liu Junming Meng Weihua R
  • 【Python深度学习】RNN循环神经网络结构讲解及序列回归问题实战(图文解释 附源码)

    需要全部代码请点赞关注收藏后评论区留言私信 循环神经网络 循环神经网络 Recurrent Neural Network RNN 是用于对序列的非线性特征进行学习的深度神经网络 循环神经网络的输入是有前后关联关系的序列 循环神经网络可以用来
  • Spring Cache常用注解

    EnableCaching 开启缓存注解功能 Cacheable 在方法执行前spring先查看缓存中是否有数据 如果有数据 则直接返回缓存数据 若没有数据 调用方法并将方法返回值放到缓存中 CachePut 将方法的返回值放到缓存中 Ca
  • JS 实现 AES 加解密(十六进制)

    引入 aes min js js文件 aes min js Javascript文档类资源 CSDN下载js文件 aes min js更多下载资源 学习资料请访问CSDN下载频道 https download csdn net downlo
  • Python中用户管理(用户的登陆、用户的增删改查)

    一 用户登陆 题目要求 1 系统里面有多个用户 用户的信息目前保存在列表里面 users root westos passwd 123 456 2 用户登陆 判断用户登陆是否成功 1 判断用户是否存在 2 如果存在 1 判断用户密码是否正确
  • C: GNU regex library (regex.h)正则表达式调用示例

    GNU regex是GNU提供的跨平台的POSIX 正则表达式库 C语言 我也是最近才接触这个相对于C Java实现来说非常简陋 勉强够用的正则表达式库 不算GNU提供的扩展函数 POSIX标准的regex库总共就4个函数regcomp r
  • 第一章 数据结构绪论

    一 数据结构的基本概念 数据结构的三要素 逻辑结构 定义 存储结构 数据的运算 基本运算 增删改查 二 算法和算法评价 时间复杂度 时间复杂度的关键 就是想办法求出次数
  • 在虚拟机中安装Linux系统CentOS7详细教程

    虚拟机 VMware Workstation Linux CentOS 7 x86 64 DVD 1708 iso镜像文件 下载 虚拟机所在电脑系统 windows 安装步骤 安装VMware Workstation虚拟机 略 下载Linu
  • swiper 滚回第一个数据_数据库发展史(上)

    数据库技术是信息技术领域的核心技术之一 几乎所有的信息系统都需要使用数据库系统来组织 存储 操纵和管理业务数据 数据库领域也是现代计算机学科的重要分支和研究方向 目前 在数据库领域已经产生了四位图灵奖得主 他们在数据库理论和实践领域均有突出
  • DocArray x Weaviate

    Weaviate 作为 DocArray 中的 Document Store 可以使得 Document 在云端的处理和检索更加迅速 DocArray Weaviate 大起底 DocArray Data structure for uns
  • Jetpack App Startup——SDK自动初始化,告别Init

    一个项目在开发过程中 常常都伴随着很多sdk的依赖 大部分的sdk在使用时都需要在应用启动时进行初始化才能正常工作 所以在集成sdk时通常需要做如下操作 在Application的onCreate内调用对应sdk的初始化方法 目的是 保证在
  • invokeMethod的介绍

    QMetaObject invokeMethod 的介绍 在Qt框架中 QMetaObject类提供了一些反射机制可以实现类似于Java反射机制的功能 其中一个函数就是QMetaObject invokeMethod 它可以通过字符串调用对
  • 分布式锁(zookeeper)与接口幂等性实现

    背景 随着数据量的增大 用户的增多 系统的并发访问越来越大 传统的单机已经满足不了需求 分布式系统成为一种必然的趋势 分布式系统错综复杂 今天 我们着重对分布式系统的互斥性与幂等性进行分析与解决 互斥性 互斥性问题也就是共享资源的抢占问题