zookeeper详解
一 zookeeper介绍
首先需要了解zookeeper是什么,zookeeper是一个分布式协调服务。所谓分布式协调主要是来解决分布式系统中多个进程之间的同步限制,防止出现脏读,例如我们常说的分布式锁。
zookeeper中的数据是存储在内存当中的,因此它的效率十分高效。它内部的存储方式十分类似于文件存储结构,采用了分层存储结构。但是它和文件存储结构的区别是,它的各个节点中是允许存储数据的,需要注意的是zk的每个节点存储数据不能超过1M。它的内存数据结果如下图:
我们可以通过不同的路径访问到不同的节点,因为它是分层结构,我们也可以通过某一个父节点,获取到该节点下的所有子节点信息。
zk只提供了几个简单的api,但是我们可以通过灵活使用这些api的组合,来实现我们复杂的业务要求:
1)create:创建一个新节点,通过指定路径的方式创建节点,例如创建路径为/A/A1/demo,则会在A1节点下创建一个demo节点;
2)delete:删除节点,通过路径的方式删除节点,如果删除路径为/A/A1/demo,则会删除A1节点下的demo节点;
3)exists:判断指定路径下的节点是否存在,例如判断路径为/A/A1/demo,则会判断A1节点下的demo节点是否存在;
4)get:获取指定路径下某个节点的值是什么,例如获取路径为/A/A1/demo,则会获取A1节点下的demo节点的值什么;
5)set:为指定路径的节点进行赋值操作,例如修改路径为/A/A1/demo,则会修改A1节点下的demo节点的值;
6)get children:获取指定路径节点下的子节点信息,例如获取路径为/A,则会获取A节点下的A1和A2节点;
7)sync:获取到同步数据,这个涉及到了zk的原理,zk集群属于最终一致性,调用该方法,可以获取到最终的结果值,如果不使用该方法,在查询的时候可能获取到的值是中间值;
zk中创建的节点分为两种:永久性节点和临时性节点。永久性节点即创建以后,在不执行delete命令的前提下,该节点是永久存在的;而临时节点与session有关,每个客户端与zk建立链接的时候会生成一个session,这个session不会因为链接zk服务器节点的变化而变化,只有当客户端断开连接以后,该session才会消失,而临时节点会随着session的消失而消失。
zk拥有watch机制,也就是监视机制,可以支持响应式编程模式,它可以对某个路径的终节点及其子节点的变更进行监视,当其发生变更以后,会调用注册的callback方法,然后进行具体的业务逻辑。例如监测路径为/A/A1,那么它会加测A1节点,以及附属于A1的所有子节点,这个子不单单只一层子节点,是指所有层的子节点。
zk拥有以下几个重要特性:
1)顺序一致性:来自客户端的相关指令会按照顺序执行,不会出现乱序的情况,客户端发送到服务的指令1->2->3->4,那个这些指令就会按照顺序执行;
2)原子性:更新只有成功和失败,没有中间状态;
3)可靠性:也可以称之为持久性,节点更新以后,在下次更新之前,它的数据不会发生变更;
4)准实时性:也可以称之为最终一致性,在zk集群中,一个客户端修改了其中的一个节点,一定时间以后,所有可用的服务对应的节点都会变成更新以后的值。
二 zk选主流程
zk的设计目标就是高可用性,那么也就意味着,在使用zk的时候一般都是使用集群而不是单点模式。首先来看一下zk的集群模式,如下图:
该图为zk集群的可用状态,从上图中可以看到,zk的集群是主从集群,客户端可以随意与任何zk服务节点进行连接,并且各个客户端都可以进行读写操作,这是一个和redis主从集群的区别,redis的主从集群,如果客户端是写操作,那么只能连接redis的主节点才可以。zk的每个客户端是随机连接到zk服务节点的,并且每个客户端都可以进行读写操作,读操作都是在客户端连接的zk节点进行操作;而写操作是有区别的,如果该客户端连接的是leader节点,那么直接进行写操作;如果该客户端连接的是follower节点,那么zk的服务节点会自动将该写操作转到leader节点进行。
zk的集群为主从集群,那么也就意味着主节点只有一个,那么当主节点挂了以后,该zk集群则会处于不可用状态,既然zk的设计目的是高可用,也就意味着当主节点挂了以后,zk会有一定的方式来快速的选出主节点,让服务恢复可用状态,zk的官方文档中给出的压测报告,7台zk服务,选主耗时大概200ms。
介绍zk的选举流程之前需要先解释两个概念:zxid以及myid。zxid指的是当前节点的事物id,通俗点说就是当前节点完成的数据同步情况,该值越大,越能说明该节点的数据同步情况越完整,丢失数据的情况越小或者丢失数据越少。myid是在创建zk集群的时候,我们给它的赋值。
zk的follower节点和leader节点是通过心跳,来查看服务是否可用。在这其中,只要有有一台follower节点发现主节点挂掉,他就开始向其它follower节点发送选主请求,整个集群进入选主流程,不再向外提供服务。
先假设现在有4个zk节点,分别为node1,node2,node3,node4,他们的myid分别为1,2,3,4选主流程主要分为以下两种情况:
1.初始启动,在启动阶段时,此时各个服务节点的zxid都为0,只与myid有关。假设启动顺序为node1->node2->node3->node4,当启动动1和2的时候,该zk集群是不可用状态,因为zk的选主必须是过半服务节点同意(包含自己),最低需要启动三个节点才可以进行选举,因此只有node1和node2启动的时候,此时只有两台服务,不满足条件,当第三台节点启动以后,才满足了选主的最低条件,然后进入到选举流程,因为node3的myid最大,所以此时3号节点为leader,然后启动node4,由于此时已经选举出3位leader节点并且过半通过,则不再选取新的主节点。则该集群的leader节点为node3。
2.运行过程中,初始启动过程中的leader(node3)节点挂掉,假设此时只有node4节点发现leader已经挂掉,node1和node2的Zxid都是10,node4的Zxid为9,选主的时候需要比较zxid和myid,需要注意他们的优先级,zxid为第一优先级,myid为第二优先级,选举流程大致分为以下几步:
1)node4节点给自己投票,然后将自己的zxid和myid发送给node1和node2节点:
2)node1和node2通过比较zxid和myid,发现node4不能成为leader节点,将各自的zxid和myid发送给node4,然后node4接收到以后,发现node1和node2都比自己时候成为leader节点,会给它们进行投票
3)node1和node2反驳完node4的选主请求以后,开始进行各自的选主流程,起过程与node4的过程一致,通过上面的优先级,我们可以知道最终node2会成为leader节点,那么以node2为例说一下接下来的流程。node2首先给自己投票,然后将自己zxid和myid推送给node1和node4,此时会发现node2适合成为主节点,则会给node2节点进行投票,最终选出node2成为主节点,zk集群恢复成可用状态。
三 zk数据一致性
zk服务一般是以集群状态提供服务,多个zk节点之间的数据一致性是通过zap(原子广播)协议来保证的。zk的数据一致性为最终一致性,需要注意的是他不是实时的,比如node1,node2,node3,其中node3为leader,node1和node2为follower,当node1进行节点创建以后,leader节点肯定为实时更新,但是follower节点不一定为实时更新,因为只要过半通过就算节点已经创建成功,可能会有的节点当前的数据还不是最终态,但是它的更新指令是存在,只是可能还没执行。我们的客户端如果想要读取最终态的数据,那么可以通过使用上面的sync命令,来获取最终数据。
先看一下下面的流程图,然后再进行详细解释:
1)首先由客户端发送创建节点的指令给到zk节点,假设这个zk节点为follower1节点;
2)follower1节点发现是写操作节点,则将该指令通过2888端口转发到leader节点执行;
3)leader节点更新自己zxid信息,也就是事务id信息;
4)leader节点先将创建节点信息同步到log日志中,然后再follower1和follower2各自的队列中放入创建节点写日志的指令,当follower节点接收到指令以后,执行写日志操作,写入日志成功以后,告诉leader写入完成;leader会判断目前是否已经有过半的节点(包含自己)已经写入完成,如果完成,则先在自己的内存中创建节点,然后将在follower对应的节点中加入在内存中创建节点的指令,然后follower接收到指令以后进行内存操作,操作完成以后告诉leader写入完成,同样需要过半完成;
5)将创建结束的消息返回给调用的follower,然后返回给客户端,节点创建结束。
上面步骤中的第四步其实就是对原子广播协议的一个大致解释,原子广播协议可以看成两部分,首先原子就代表这只有成功或者失败,没有中间状态;而广播就是并不意味着所有节点都完成相关操作才算完成,只要过半节点是成功的,那么本次操作就算成功完成了。在第四步中提到的队列就是对最终一致性的一个解释,leader会将所有指令按照顺序放入每个follower对应的队列中,每个follower按顺序去执行队列中的指令,达到一个最终一致性的结果。
四 zk分布式锁
zk作为分布式协调服务,它的一个很大的作用就是用来实现分布式锁。zk节点存在临时节点,它的生命周期与session有关,它会随着session的消失而消失,这就比较完美的解决了使用redis作为分布式锁时可能出现的死锁问题。
下面看一下简单的分布式锁代码编写。
第一部分代码为连接zk时的watch代码,用于监测zk的连接情况,它只需要实现Watcher即可。可以根据不同连接状态,进行不同的处理,我们本次只关心连接状态,因为zk是异步连接,为了保证zk连接成功以后再做接下来的加锁操作,通过CountDownLatch进行阻塞。
/*** 连接watcher,主要用来监测zk连接状态*/
public class ConnectionWatch implements Watcher {/*** 由于zk获取信息为异步,通过countDownLatch进行阻塞,保证连接成功*/private CountDownLatch countDownLatch;public void setCountDownLatch(CountDownLatch countDownLatch){this.countDownLatch = countDownLatch;}@Overridepublic void process(WatchedEvent watchedEvent) {System.out.println(watchedEvent.toString());switch (watchedEvent.getState()){case Unknown:break;case Disconnected:break;case NoSyncConnected:break;case SyncConnected:// 连接成功,去除阻塞countDownLatch.countDown();break;case AuthFailed:break;case ConnectedReadOnly:break;case SaslAuthenticated:break;case Expired:break;case Closed:break;}}
}
第二部分代码为zk的工具类,用于获取zk实例,用于业务代码调用。
public class ZkUtils {private static volatile ZooKeeper zooKeeper;/*** zk服务器节点地址,以及锁的主目录*/private final static String url = "127.0.0.1:2181,127.0.0.2:2181,127.0.0.3:2181/orderLock";private static ConnectionWatch watch = new ConnectionWatch();private static CountDownLatch countDownLatch = new CountDownLatch(1);/*** 采创建zk* @return* @throws IOException* @throws InterruptedException*/public static ZooKeeper getInstance() throws IOException, InterruptedException {watch.setCountDownLatch(countDownLatch);// 创建zk实例,1000代表的是session过期时间zooKeeper = new ZooKeeper(url, 1000, watch);// 在zk连接成功之前进行阻塞countDownLatch.await();return zooKeeper;}
}
第三部分为在加锁过程中相关操作的watch以及callback操作,主要功能有创建节点,获取子节点,检查节点是否存在。zk的加锁过程就是创建节点的过程,当创建节点成功并且成功返回,则证明该线程加锁成功,继续进行业务逻辑处理,在加锁的时候,一定要考虑锁的可重入性。下面这段代码实现的是公平锁,谁先创建了临时节点,那么谁就能先获得锁。加锁的大致逻辑是:1)先创建带有序列的临时节点;2)在回调函数中获取父节点的所有子节点,判断当前线程创建的临时节点是否位于第一个,如果是则获取锁,如果不是则判断前一个节点是否存在,然后一直循环该逻辑。
public class LockWatch implements Watcher, AsyncCallback.StringCallback, AsyncCallback.Children2Callback, AsyncCallback.StatCallback {private ZooKeeper zooKeeper;/*** 当前线程名称*/private String threadName;/*** 当前线程创建的节点名称*/private String nodeName;/*** 用来进行锁阻塞,只有获取到锁,才放行,否则进行阻塞*/private CountDownLatch countDownLatch = new CountDownLatch(1);public void setZooKeeper(ZooKeeper zooKeeper) {this.zooKeeper = zooKeeper;}public void setThreadName(String threadName) {this.threadName = threadName;}/*** 加锁操作,也就是往zk的指定目录下插入带有序列的临时节点* 需要考虑锁的可重入*/public void tryLock() throws InterruptedException {zooKeeper.create("/lock", threadName.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL,this, "orderLock");countDownLatch.await();}/*** 解锁操作* @throws InterruptedException* @throws KeeperException*/public void unLock() throws InterruptedException, KeeperException {// -1代表不考虑版本号,在zk中获取,删除等相关操作允许版本号的传入zooKeeper.delete(nodeName, -1);}/*** 节点创建回调方法* @param i* @param path* @param ctx* @param name*/@Overridepublic void processResult(int i, String path, Object ctx, String name) {if (Objects.nonNull(name) && !"".equals(name)){System.out.println(threadName + " create node:" + name);nodeName = name.substring(1);zooKeeper.getChildren("/", false, this, ctx);}}/*** 获取子节点信息的回调方法* @param i* @param path* @param o* @param children* @param stat*/@Overridepublic void processResult(int i, String path, Object o, List<String> children, Stat stat) {if (children == null || children.isEmpty()){System.out.println("children is null......");return;}// 将子节点进行排序,找序号由低到高Collections.sort(children);// 获取当前创建节点排序以后的下标int index = children.indexOf(nodeName);// 如果当前节点为第一个节点,则加锁成功try {if (index < 1){System.out.println(threadName +" get lock...");// -1代表不考虑版本zooKeeper.setData("/", threadName.getBytes(), -1);countDownLatch.countDown();} else {System.out.println(threadName +" not get lock...");// 判断该节点的前一个节点是否存在,目的是为了注册节点监控事件,监测删除节点操作zooKeeper.exists("/" + children.get(i-1), this, this, o);}} catch (KeeperException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}/*** 判断节点是否存在的watch方法* @param watchedEvent*/@Overridepublic void process(WatchedEvent watchedEvent) {switch (watchedEvent.getType()){case None:break;case NodeCreated:break;case NodeDeleted:// 当节点删除的时候,代表着解锁,触发后续的抢锁操作zooKeeper.getChildren("/", false, this, "orderLock");break;case NodeDataChanged:break;case NodeChildrenChanged:break;case DataWatchRemoved:break;case ChildWatchRemoved:break;case PersistentWatchRemoved:break;}}/*** 校验节点是否存在回调方法* @param i* @param s* @param o* @param stat*/@Overridepublic void processResult(int i, String s, Object o, Stat stat) {}
}
第四部分则为锁的简单应用,使用了junit进行测试,代码如下:
public class ZkLock {private ZooKeeper zooKeeper;/*** 没有业务意义,只是为了阻塞主线程*/private CountDownLatch countDownLatch = new CountDownLatch(1);/*** 初始化的时候,首先保证获取到zk的链接实例* @throws IOException* @throws InterruptedException*/@BeforeAllpublic void connect() throws IOException, InterruptedException {zooKeeper = ZkUtils.getInstance();}/*** 使用完以后,关闭zk连接* @throws InterruptedException*/@AfterAllpublic void close() throws InterruptedException {zooKeeper.close();}/*** 使用10个线程模拟抢锁*/@Testpublic void testLock() throws InterruptedException {for(int i = 0; i < 10; i++){new Thread(){@Overridepublic void run() {String threadName =Thread.currentThread().getName();LockWatch lockWatch = new LockWatch();lockWatch.setThreadName(threadName);lockWatch.setZooKeeper(zooKeeper);try {lockWatch.tryLock();System.out.println(threadName + "deal business...");lockWatch.unLock();} catch (InterruptedException | KeeperException e) {e.printStackTrace();}}}.start();}countDownLatch.await();}
}
补充:zk服务中,2888端口用于follower调用leader进行写操作,3888端口为选主使用端口,2181端口为客户端连接zk服务节点端口。
相关文章:

zookeeper详解
一 zookeeper介绍 首先需要了解zookeeper是什么,zookeeper是一个分布式协调服务。所谓分布式协调主要是来解决分布式系统中多个进程之间的同步限制,防止出现脏读,例如我们常说的分布式锁。 zookeeper中的数据是存储在内存当中的,因…...

达索智能制造解决方案,敏捷电芯制造如何赋能企业竞争力 | 百世慧®
敏捷电芯制造赋能企业竞争力 全球电池市场正在快速扩大,为制造商带来巨大商机。 锂电行业的智能制造如何应用? 电池制造业的市场趋势是什么? 电池制造商面临哪些挑战? 特别是电池电芯制造方面,如何克服挑战获得竞…...
自然语言处理---迁移学习实践
1 微调脚本介绍 指定任务类型的微调脚本: huggingface研究机构提供了针对GLUE数据集合任务类型的微调脚本, 这些微调脚本的核心都是微调模型的最后一个全连接层。通过简单的参数配置来指定GLUE中存在任务类型(如: CoLA对应文本二分类,MRPC对应句子对文本二分类&…...

看得懂的——数据库中的“除”操作
通过一个例子来解释数据库中的“除”操作 R➗S其实就是判断关系R中X各个值的象集Y是否包含关系S中属性Y的所有值 求解步骤 第一步 找出关系R和关系S中相同的属性,即Y属性。在关系S中对Y做投影(即将Y列取出);所得结果如下&#x…...

el-input无法输入的问题和表单验证失败问题(亲测有效)-开发bug总结4
大部分无法输入的问题:基本都是没有进行v-model双向数据绑定,这个很好解决。 本人项目中遇到的bug问题如下: 点击添加,表单内可输入用户名 和 用户姓名,但有时会偶发出现无法这两个input框里面无法输入内容。 原因&a…...

OpenCV+QT实现的数字图像处理算法合集
源码下载地址: 基于OpenCV和QT的图像处理源码 图像预处理 灰度处理 灰度直方图 灰度均衡 梯度锐化 Laplace锐化 边缘检测 Roberts Sobel Laplace Prewitt canny Krisch 噪声 椒盐噪声 高斯噪声 滤波 均值滤波 中值滤波 双边滤波 形态学滤波 高斯滤波 图像变…...

想要查看员工与客户聊天记录和跟进情况,有什么工具推荐吗?
想要查看员工与客户 聊天记录和跟进情况 有什么工具推荐吗? 想要查看员工与客户聊天记录和每天新增客户,可以使用微信管理系统这个工具。 微信管理系统是一个能够同时登录多个微信,实现一个人管理多个微信的工具。它分为两大版块,…...
androdi知识笔记
jbr embed:android studio自带的jdk AGP(android gradle plugin) aar jar 利用java语言可以写应用程序(利用已有库加速开发过程),也可以自己开发库用于特定功能(供引用)。 循环啊是个࿰…...

华为数通方向HCIP-DataCom H12-831题库(多选题:21-40)
第21题 网络管理员A希望使用ACL匹配特定的路由条目,请问以下哪些路由条目将被图中的ACL规侧匹配? acl number 2000 rule 10 permit source 10.0.0.0 0.0.6.0A、10.0.0.1/32 B、10.0.0.0/24 C、10.0.1.0/24 D、10.0.2.0/24 答案: 解析: 通配符十进制6转换二进制为00000110,…...

数据要素安全流通:挑战与解决方案
文章目录 数据要素安全流通:挑战与解决方案一、引言二、数据要素安全流通的挑战数据泄露风险数据隐私保护数据跨境流动监管 三、解决方案加强数据安全防护措施实施数据隐私保护技术建立合规的数据跨境流动机制 四、数据安全流通的未来趋势01 数据价值与产业崛起02 多…...
【Mybatis源码】XMLConfigBuilder构建器 - 加载XML与创建Configuration对象的过程
XMLConfigBuilder是Mybatis中定义的进行构建Configuration对象的类,此类用于读取XML配置文件创建并初始化Configuration对象;本篇我们主要介绍加载XML文件与创建Configuration对象的过程。 一、Configuration对象的创建过程 下面是从Configuration类中取到的代码片段: pu…...

台灯显色指数多少好?推荐显色指数优秀的护眼台灯
台灯的显色指数是其非常重要的指标,它可以表示灯光照射到物体身上,物体颜色的真实程度,一般用平均显色指数Ra来表示,Ra值越高,灯光显色能力越强。常见的台灯显色指数最低要求一般是在Ra80以上即可,比较好的…...

【2024秋招】2023-8-5-小红书-数据引擎团队后端开发提前批面经
1 面试官介绍 OLAP引擎,离线引擎,大数据分析中间件 2 自我介绍 缺点: (1)面试官让重点介绍自己最在行的项目,我真的在自我介绍上扯了一些别的东西… (2)在面试的时候因为想看简…...

【Docker从入门到入土 4】使用Harbor搭建Docker私有仓库
私有仓库 一、Harbor简介1.1 什么是Harbor?1.2 Harbor的特性1.3 Harbor和docker registry的关系1.4 Harbor的构成1.4 Harbor 配置文件中的两类参数1.4.1 所需参数1.4.2 可选参数 二、Harbor部署2.1 部署Docker-Compose服务2.2 部署 Harbor 服务Step1 下载或上传 Harbor 安装程…...
监控易一体化运维:打造机房环境监控的卓越典范
随着信息技术的飞速发展,机房作为企业数据和业务的中心,其运行状态和管理的重要性日益凸显。为确保机房的稳定性和可靠性,越来越多的企业选择使用一体化运维管理软件来进行实时监控。在这方面,监控易品牌提供了一款全面而高效的机…...
【X3m】DDR压力测试
Index of /downloads/unittest/ 设置CPU模式和降频温度# 若设备重启需再次配置这两条指令 echo performance > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor echo 105000 > /sys/devices/virtual/thermal/thermal_zone0/trip_point_1_temp #1 cpu test ec…...

【Linux】32条指令带你玩转 Linux !
目录 1,whoami 2,who 3,pwd 4,ls 1,ls 2,ls -l 3,ls -a 4,ls -al 5,ls -d 6,ls -ld 5,clear 6,cd 1,cd 2&…...

高效恢复丢失的文件的10 款Android数据恢复工具
在当今快节奏的数字时代,从Android设备丢失重要数据可能是一场噩梦。 您需要一个可靠的恢复工具来取回您的数据,例如令人难忘的照片,重要的联系人,重要的工作文档等。 值得庆幸的是,有许多高效的Android数据恢复工具可…...

Python数据挖掘 | 升级版自动查核酸
📕作者简介:热爱跑步的恒川,致力于C/C、Java、Python等多编程语言,热爱跑步,喜爱音乐的一位博主。 📗本文收录于恒川的日常汇报系列,大家有兴趣的可以看一看 📘相关专栏C语言初阶、C…...

港联证券:哪家证券公司开户好?
在现代社会,出资理财已经成为了一个不可或缺的部分。出资者在进行股票生意时,不可避免地需求选择一家证券公司进行开户。可是,哪家证券公司开户好?这是每个出资者都需求考虑的问题。本文将从多个角度分析,为您供给一些…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...

深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...