Zookeeper概要、协议、应用场景
1. zookeeper产生背景:
zookeeper是为了解决分布式系统一致性的问题产生的,其使用了ZAB协议(Zookeeper Atomic Broadcast zookeeper原理广播协议)
项目从单体到分布式转变之后,将会产生多个节点之间协同的问题。
- 每天的定时任务由谁哪个节点来执行?
- RPC调用时的服务发现?
- 如何保证并发请求的幂等
- …
这些问题可以统一归纳为多节点协调问题,如果靠节点自身进行协调这是非常不可靠的,性能上也不可取。必须由一个独立的服务做协调工作,它必须可靠,而且保证性能。
分布式系统中的进程通信有两种选择:直接通过⽹络进⾏信息交换, 或读写某些共享存储。
ZooKeeper使⽤共享存储模型来实现应⽤间的协作和 同步原语。对于共享存储本⾝,又需要在进程和存储间进⾏⽹络通信。我们强调⽹络通信的重要性,因为它是分布式系统中并发设计的基础。
ZAB协议原理
分布式系统一致性算法应用于系统软件实现集群保持每个节点数据的同步性。保持我们的集群中每个节点的数据的一致性问题。
常用分布式系统一致性算法:raf协议、zab协议、paxos协议。
zookeeper使用主备模型架构来保持集群中各个节点的数据一致性,使用一个单节点(leader节点)来接受处理客户端的事务请求,并通过zab协议将数据变更以事务proposal的形式广播给其他子节点(follower)
zookeeper有两种运行状态:1. 崩溃恢复 2. 消息广播
崩溃恢复:
服务启动或者宕机重启会进行选举出新的leader同时leader和过半节点完成数据状态同步后会进行正常的消息广播模式。
1、选择保证数据一致
ZAB协议需要确保事务leade处理成功,则在所有机器都应该被处理成功(这里处理成功是指提出事务和commit都完成)
ZAB协议需要确保丢弃只在leader节点提出的事务
这两种情况可以使用ZXID实现 只要选举出来的具有最高的ZXID编号的,可以保证选举出来的leader一定具有所有已经提交的提案。同时也省去了leader去提交和丢弃提案的步骤。
2、数据同步
选举完成的leader会将自己提交的提案中没有被其他follower节点同步的提案以消息的形式发送到其对应的队列中,follower消费消息进行数据同步 并进行ack确认接收commit提交。类似于正常的消息广播
消息广播模式:
leader节点将客户事务提交转换成一个Proposal并分发给集群所有fllower节点,超过半数的节点提供给leader正确反馈,leader节点会再次向所有Follower服务器分发commit消息将前一个proposal进行提交。类似于现代民主投票
数据(广播协议)之间同步采用: *2pc两阶段提交协议* 同时使用FIFO顺序的TCP协议保证消息发送和接受的顺序性
2. zookeeper概要
ZooKeeper是用于分布式应用程序的协调服务。它公开了一组简单的API,分布式应用程序可以基于这些API用于同步,节点状态、配置等信息、服务注册等信息。其由JAVA编写,支持JAVA 和C两种语言的客户端。ZooKeeper顾名思意:动物园管理员
2.1设计模式来理解
ZK是一个基于观察者模式设计的分布式服务管理框架,
它负责存储和管理大家都关心的数据(集群下数据会保持一致性),
然后接受观察者的注册(watch机制),
一旦这些数据的状态发生变化,Zookeeper就将负责通知watch已经在Zookeeper上注册的那些观察者做出相应的反应,从而实现集群中类似Master/Slave管理模式
2.2 一句话
zookeeper=类似unix文件系统+通知机制+Znode节点
作用:服务注册+分布式系统的一致性通知协调
3. 数据结构 - znode 节点
zookeeper 中数据基本单元叫节点,节点之下可包含子节点,最后以树级方式呈现。每个节点拥有唯一的路径path。客户端基于PATH上传节点数据,zookeeper 收到后会实时通知对该路径进行监听的客户端。
(ZooKeeper数据模型的结构与Unix文件系统很类似,整体上可以看作是一棵树,每个节点称做一个ZNode。每一个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。)
4. 特点
- Zookeeper:一个领导者(leader),多个跟随者(follower)组成的集群。
- Leader负责进行投票的发起和决议,更新系统状态(写数据)
- Follower用于接收客户请求并向客户端返回结果(读数据),在选举Leader过程中参与投票
- 集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。
- 全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪个server,数据都是一致的。
- 更新请求顺序进行,来自同一个client的更新请求按其发送顺序依次执行。
- 数据更新原子性,一次数据更新要么成功,要么失败。
- 实时性,在一定时间范围内,client能读到最新数据。
5. 应用场景
提供的服务包括:统一命名服务dubbo注册中心、统一配置管理(zk实现的配置中心)、统一集群管理、服务器节点动态上下线、软负载均衡等。
5.1 统一命名服务(Name Service如Dubbo服务注册中心)
**命名服务是将一个名称映射到与该名称有关联的一些信息的服务。**电话目录是将人的名字映射到其电话号码的一个名称服务。同样,DNS 服务也是一个名称服务,它将一个域名映射到一个 IP 地址。在分布式系统中,您可能想跟踪哪些服务器或服务在运行,并通过名称查看其状态。ZooKeeper 暴露了一个简单的接口来完成此工作。也可以将名称服务扩展到组成员服务,这样就可以获得与正在查找其名称的实体有关联的组的信息。
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。
在Dubbo实现中:服务提供者在启动的时候,向ZK上的指定节点/dubbo/s e r v i c e N a m e / p r o v i d e r s 目录下写入自己的 U R L 地址,这个操作就完成了服务的发布。服务消费者启动的时候,订阅 / d u b b o / {serviceName}/providers目录下写入自己的URL地址,这个操作就完成了服务的发布。服务消费者启动的时候,订阅/dubbo/serviceName/provid**ers目录下写入自己的URL地址,这个操作就完成了服务的发布。服务消费者启动的时候,订阅/dubb**o/{serviceName}/providers目录下的提供者URL地址, 并向/dubbo/${serviceName} /consumers目录下写入自己的URL地址。
在分布式环境下,经常需要对应用/服务进行统一命名,便于识别不同服务。
- 类似于域名与ip之间对应关系,ip不容易记住,而域名容易记住。
- 通过名称来获取资源或服务的地址,提供者等信息。
一句话总结:path就是命名服务,客户端能够根据指定节点的名字来获取资源或者服务地址,然后进行下一步操作
5.2 统一配置管理(Configuration Management如淘宝开源配置管理框架Diamond)
在大型的分布式系统中,为了服务海量的请求,同一个应用常常需要多个实例。如果存在配置更新的需求,常常需要逐台更新,给运维增加了很大的负担同时带来一定的风险(配置会存在不一致的窗口期,或者个别节点忘记更新)。zookeeper可以用来做集中的配置管理,存储在zookeeper集群中的配置,如果发生变更会主动推送到连接配置中心的应用节点,实现一处更新处处更新的效果。
现在把这些配置全部放到zookeeper上去,保存在 Zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 Zookeeper 的通知,然后从 Zookeeper 获取新的配置信息应用到系统中就好。
分布式环境下,配置文件管理和同步是一个常见问题。
- 一个集群中,所有节点的配置信息是一致的,比如 Hadoop 集群。
- 对配置文件修改后,希望能够快速同步到各个节点上。
配置管理可交由ZooKeeper实现。
- 可将配置信息写入ZooKeeper上的一个Znode。
- 各个节点监听这个Znode。
- 一旦Znode中的数据被修改,ZooKeeper将通知各个节点。
5.3 统一集群管理
分布式环境中,实时掌握每个节点的状态是必要的。
- 可根据节点实时状态做出一些调整。
- 可交由ZooKeeper实现。
- 可将节点信息写入ZooKeeper上的一个Znode。
- 监听这个Znode可获取它的实时状态变化。
- 典型应用
- HBase中Master状态监控与选举。
5.4 服务器动态上下线
利用zk临时节点实现监听服务器上下线。
5.5 软负载均衡
5.6 分布式消息同步和协调机制
5.7 总结
Zoopkeeper提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数据结构,
并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型,作为分布式系统的沟通调度桥梁
Zookeeper六种常见应用场景
1. Zookeeper应用场景
ZooKeeper是⼀个典型的发布/订阅模式的分布式数据管理与协调框架,我们可以使用它来进行分布式数据的发布与订阅。另一方面,通过对ZooKeeper中丰富的数据节点类型进行交叉使用,配合Watcher事件通知机制,可以非常方便地构建⼀系列分布式应用中都会涉及的核心功能,如数据发布/订阅
、命名服务
、集群管理
、Master选举
、分布式锁
和分布式队列
等。那接下来就针对这些典型的分布式应用场景来做下介绍。
1.1 数据发布/订阅
数据发布/订阅(Publish/Subscribe)系统,即所谓的配置中心,顾名思义就是发布者将数据发布到ZooKeeper的⼀个或⼀系列节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和数据的动态更新。
发布/订阅系统⼀般有两种设计模式,分别是推(Push)模式和拉(Pull)模式。在推模式中,服务端主动将数据更新发送给所有订阅的客户端;而拉模式则是由客户端主动发起请求来获取最新数据,通常客户端都采用定时进行轮询拉取的方式。
ZooKeeper 采用的是推拉相结合的方式:客户端向服务端注册自己需要关注的节点,⼀旦该节点的数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接收到这个消息通知之后,需要主动到服务端获取最新的数据。
如果将配置信息存放到ZooKeeper上进行集中管理,那么通常情况下,应用在启动的时候都会主动到ZooKeeper服务端上进行一次配置信息的获取,同时,在指定节点上注册⼀个Watcher监听,这样⼀来,但凡配置信息发生变更,服务端都会实时通知到所有订阅的客户端,从而达到实时获取最新配置信息的目的。
下⾯我们通过⼀个“配置管理”的实际案例来展示ZooKeeper在“数据发布/订阅”场景下的使用方式。
在我们平常的应用系统开发中,经常会碰到这样的需求:系统中需要使用⼀些通用的配置信息,例如机器列表信息、运行时的开关配置、数据库配置信息等。这些全局配置信息通常具备以下3个特性。
- 数据量通常比较小。
- 数据内容在运行时会发生动态变化。
- 集群中各机器共享,配置⼀致。
对于这类配置信息,⼀般的做法通常可以选择将其存储在本地配置文件或是内存变量中。无论采用哪种方式,其实都可以简单地实现配置管理,在集群机器规模不大、配置变更不是特别频繁的情况下,无论刚刚提到的哪种方式,都能够非常方便地解决配置管理的问题。但是,⼀旦机器规模变大,且配置信息变更越来越频繁后,我们发现依靠现有的这两种方式解决配置管理就变得越来越困难了。我们既希望能够快速地做到全局配置信息的变更,同时希望变更成本足够小,因此我们必须寻求⼀种更为分布式化的解决方案。
接下来我们就以⼀个“数据库切换”的应用场景展开,看看如何使用ZooKeeper来实现配置管理:
配置存储
在进行配置管理之前,首先我们需要将初始化配置信息存储到Zookeeper上去,⼀般情况下,我们可以在Zookeeper上选取⼀个数据节点用于配置信息的存储,例如:/app1/database_config
配置管理的zookeeper节点示意图
我们将需要管理的配置信息写⼊到该数据节点中去,例如:
#数据库配置信息
#DBCP
dbcp.driverClassName=com.mysql.jdbc.Driver
dbcp.dbJDBCUrl=jdbc:mysql://127.0.0.1:3306/test
dbcp.username=root
dbcp.password=123456
dbcp.maxActive=30
dbcp.maxIdle=10
12345678
配置获取
集群中每台机器在启动初始化阶段,首先会从送上面提到的ZooKeeper配置节点上读取数据库信息,同时,客户端还需要在该配置节点上注册⼀个数据变更的 Watcher监听,⼀旦发生节点数据变更,所有订阅的客户端都能够获取到数据变更通知。
配置变更
在系统运行过程中,可能会出现需要进行数据库切换的情况,这个时候就需要进行配置变更。借助ZooKeeper,我们只需要对ZooKeeper上配置节点的内容进行更新,ZooKeeper就能够帮我们将数据变更的通知发送到各个客户端,每个客户端在接收到这个变更通知后,就可以重新进行最新数据的获取。
1.2 命名服务
命名服务(Name Service)也是分布式系统中比较常见的⼀类场景,是分布式系统最基本的公共服务之⼀。在分布式系统中,被命名的实体通常可以是集群中的机器、提供的服务地址或远程对象等——这些我们都可以统称它们为名字(Name),其中较为常见的就是⼀些分布式服务框架(如RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体
、服务地址
和提供者的信息
等。
ZooKeeper 提供的命名服务功能能够帮助应用系统通过⼀个资源引用的方式来实现对资源的定位与使用。另外,广义上命名服务的资源定位都不是真正意义的实体资源——在分布式环境中,上层应用仅仅需要⼀个全局唯⼀的名字,类似于数据库中的唯⼀主键。
所以接下来。我们来看看如何使用ZooKeeper来实现⼀套分布式全局唯⼀ID的分配机制。
所谓ID,就是⼀个能够唯⼀标识某个对象的标识符。在我们熟悉的关系型数据库中,各个表都需要⼀个主键来唯⼀标识每条数据库记录,这个主键就是这样的唯⼀ID。在过去的单库单表型系统中,通常可以使用数据库字段自带的auto_increment属性来自动为每条数据库记录生成⼀个唯⼀的ID,数据库会保证生成的这个ID在全局唯⼀。但是随着数据库数据规模的不断增大,分库分表随之出现,而auto_increment属性仅能针对单⼀表中的记录自动生成ID,因此在这种情况下,就无法再依靠数据库的auto_increment属性来唯⼀标识⼀条记录了。于是,我们必须寻求⼀种能够在分布式环境下生成全局唯⼀ID的方法。
⼀说起全局唯⼀ ID,相信⼤家都会联想到 UUID。没错,UUID 是通用唯⼀识别码(Universally Unique Identifier)的简称,是⼀种在分布式系统中广泛使用的用于唯⼀标识元素的标准。确实,UUID是⼀个非常不错的全局唯⼀ID生成方式,能够非常简便地保证分布式环境中的唯⼀性。⼀个标准的UUID 是⼀个包含 32 位字符和 4 个短线的字符串,例如“e70f1357-f260-46ff-a32d-53a086c57ade”。UUID的优势自然不必多说,我们重点来看看它的缺陷。
长度过长
UUID 最大的问题就在于生成的字符串过长。显然,和数据库中的 int 类型相比,存储⼀个UUID需要花费更多的空间。
含义不明
上⾯我们已经看到⼀个典型的 UUID 是类似于“e70f1357-f260-46ff-a32d-53a086c57ade”的⼀个字符串。根据这个字符串,开发人员从字⾯上基本看不出任何其表达的含义,这将会大大影响问题排查和开发调试的效率。
所以接下来,我们结合⼀个分布式任务调度系统来看看如何使用ZooKeepe来实现这类全局唯⼀ID的生成。 之前我们已经提到,通过调⽤调用ZooKeeper节点创建的API接口可以创建⼀个顺序节点,并且在API返回值中会返回这个节点的完整名字。利用这个特性,我们就可以借助ZooKeeper来生成全局唯⼀的ID了,如下图:
说明,对于⼀个任务列表的主键,使用ZooKeeper生成唯一ID的基本步骤:
- 所有客户端都会根据自己的任务类型,在指定类型的任务下面通过调⽤create()接口来创建⼀个
顺序节点,例如创建“job-”节点。 - 节点创建完毕后,create() 接口会返回⼀个完整的节点名,例如“job-0000000003”。
- 客户端拿到这个返回值后,拼接上 type 类型,例如“type2-job-0000000003”,这就可以作为⼀个全局唯⼀的ID了。
在ZooKeeper中,每⼀个数据节点都能够维护⼀份子节点的顺序顺列,当客户端对其创建⼀个顺序子节点的时候 ZooKeeper 会自动以后缀的形式在其子节点上添加⼀个序号,在这个场景中就是利用了ZooKeeper的这个特性。
1.3 集群管理
随着分布式系统规模的日益扩大,集群中的机器规模也随之变大,那如何更好地进行集群管理也显得越来越重要了。所谓集群管理,包括集群监控
与集群控制
两大块,前者侧重对集群运行时状态的收集,后者则是对集群进行操作与控制。
在日常开发和运维过程中,我们经常会有类似于如下的需求:
- 如何快速的统计出当前⽣产环境下⼀共有多少台机器
- 如何快速的获取到机器上下线的情况
- 如何实时监控集群中每台主机的运行时状态
在传统的基于Agent的分布式集群管理体系中,都是通过在集群中的每台机器上部署⼀个 Agent,由这个 Agent 负责主动向指定的⼀个监控中心系统(监控中心系统负责将所有数据进行集中处理,形成⼀系列报表,并负责实时报警,以下简称“监控中心”)汇报自己所在机器的状态。在集群规模适中的场景下,这确实是⼀种在生产实践中广泛使用的解决方案,能够快速有效地实现分布式环境集群监控,但是⼀旦系统的业务场景增多,集群规模变大之后,该解决方案的弊端也就显现出来了。
大规模升级困难
以客户端形式存在的 Agent,在大规模使用后,⼀旦遇上需要大规模升级的情况,就非常麻烦,在升级成本和升级进度的控制上面临巨大的挑战。
统⼀的Agent无法满足多样的需求
对于机器的CPU使用率、负载(Load)、内存使用率、网络吞吐以及磁盘容量等机器基本的物理状态,使用统一的Agent来进行监控或许都可以满足。但是,如果需要深入应用内部,对⼀些业务状态进行监控,例如,在⼀个分布式消息中间件中,希望监控到每个消费者对消息的消费状态;或者在⼀个分布式任务调度系统中,需要对每个机器上任务的执行情况进行监控。很显然,对于这些业务耦合紧密的监控需求,不适合由⼀个统⼀的Agent来提供。
编程语言多样性
随着越来越多编程语言的出现,各种异构系统层出不穷。如果使用传统的Agent方式,那么需要提供各种语言的 Agent 客户端。另一方面,“监控中心”在对异构系统的数据进行整合上面临巨大挑战。
Zookeeper的两大特性:
- 客户端如果对Zookeeper的数据节点注册Watcher监听,那么当该数据节点的内容或是其子节点
列表发生变更时,Zookeeper服务器就会向订阅的客户端发送变更通知。 - 对在Zookeeper上创建的临时节点,⼀旦客户端与服务器之间的会话失效,那么临时节点也会被自动删除
利用其两大特性,可以实现集群机器存活监控系统,若监控系统在/clusterServers节点上注册⼀个Watcher监听,那么但凡进行动态添加机器的操作,就会在/clusterServers节点下创建⼀个临时节点:/clusterServers/[Hostname],这样,监控系统就能够实时监测机器的变动情况。
下面通过分布式日志收集系统这个典型应用来学习Zookeeper如何实现集群管理。
分布式日志收集系统
分布式日志收集系统的核心工作就是收集分布在不同机器上的系统日志,在这里我们重点来看分布式日志系统(以下简称“日志系统”)的收集器模块。
在⼀个典型的日志系统的架构设计中,整个日志系统会把所有需要收集的日志机器(我们以“日志源机器”代表此类机器)分为多个组别,每个组别对应⼀个收集器,这个收集器其实就是⼀个后台机器(我们以“收集器机器”代表此类机器),用于收集日志。
对于大规模的分布式日志收集系统场景,通常需要解决两个问题:
- 变化的日志源机器
在生产环境中,伴随着机器的变动,每个应用的机器几乎每天都是在变化的(机器硬件问题、扩容、机房迁移或是网络问题等都会导致⼀个应用的机器变化),也就是说每个组别中的日志源机器通常是在不断变化的。 - 变化的收集器机器
日志收集系统自身也会有机器的变更或扩容,于是会出现新的收集器机器加入或是老的收集器机器退出的情况。
无论是日志源机器还是收集器机器的变更,最终都可以归结为如何快速、合理、动态地为每个收集器分配对应的日志源机器。这也成为了整个日志系统正确稳定运转的前提,也是日志收集过程中最大的技术挑战之⼀,在这种情况下,我们就可以引⼊zookeeper了,下面我们就来看ZooKeeper在这个场景中的使用。
使用Zookeeper的场景步骤如下:
(1)注册收集器机器
使用ZooKeeper来进行日志系统收集器的注册,典型做法是在ZooKeeper上创建⼀个节点作为收集器的根节点,例如/logs/collector(下文我们以“收集器节点”代表该数据节点),每个收集器机器在启动的时候,都会在收集器节点下创建自己的节点,例如/logs/collector/[Hostname]
(2)任务分发
待所有收集器机器都创建好自己对应的节点后,系统根据收集器节点下子节点的个数,将所有日志源机器分成对应的若干组,然后将分组后的机器列表分别写到这些收集器机器创建的子节点(例如/logs/collector/host1)上去。这样⼀来,每个收集器机器都能够从自己对应的收集器节点上获取日志源机器列表,进而开始进行日志收集工作。
(3)状态汇报
完成收集器机器的注册以及任务分发后,我们还要考虑到这些机器随时都有挂掉的可能。因此,针对这个问题,我们需要有⼀个收集器的状态汇报机制:每个收集器机器在创建完自己的专属节点后,还需要在对应的子节点上创建⼀个状态子节点,例如/logs/collector/host1/status,每个收集器机器都需要定期向该节点写⼊自己的状态信息。我们可以把这种策略看作是⼀种心跳检测机制,通常收集器机器都会在这个节点中写入日志收集进度信息。日志系统根据该状态子节点的最后更新时间来判断对应的收集器机器是否存活。
(4)动态分配
如果收集器机器挂掉或是扩容了,就需要动态地进行收集任务的分配。在运行过程中,日志系统始终关注着/logs/collector这个节点下所有子节点的变更,⼀旦检测到有收集器机器停止汇报或是有新的收集器机器加入,就要开始进行任务的重新分配。无论是针对收集器机器停止汇报还是新机器加入的情况,日志系统都需要将之前分配给该收集器的所有任务进行转移。为了解决这个问题,通常有两种做法:
- 全局动态分配
这是⼀种简单粗暴的做法,在出现收集器机器挂掉或是新机器加入的时候,日志系统需要根据新的收集器机器列表,立即对所有的日志源机器重新进行一次分组,然后将其分配给剩下的收集器机器。 - 局部动态分配
全局动态分配方式虽然策略简单,但是存在⼀个问题:⼀个或部分收集器机器的变更,就会导致全局动态任务的分配,影响面比较大,因此风险也就比较大。所谓局部动态分配,顾名思义就是在小范围内进行任务的动态分配。在这种策略中,每个收集器机器在汇报自己日志收集状态的同时,也会把自己的负载汇报上去。请注意,这里提到的负载并不仅仅只是简单地指机器CPU负载(Load),而是⼀个对当前收集器任务执行的综合评估,这个评估算法和ZooKeeper本身并没有太大的关系,这里不再赘述。
在这种策略中,如果⼀个收集器机器挂了,那么日志系统就会把之前分配给这个机器的任务重新分配到那些负载较低的机器上去。同样,如果有新的收集器机器加入,会从那些负载高的机器上转移部分任务给这个新加入的机器。
上述步骤已经完整的说明了整个日志收集系统的工作流程,其中有两点注意事项:
(1)节点类型
在/logs/collector节点下创建临时节点可以很好的判断机器是否存活,但是,若机器挂了,其节点会被删除,记录在节点上的日志源机器列表也被清除,所以需要选择持久节点来标识每⼀台机器,同时在节点下分别创建/logs/collector/[Hostname]/status节点来表征每⼀个收集器机器的状态,这样,既能实现对所有机器的监控,同时机器挂掉后,依然能够将分配任务还原。
(2)日志系统节点监听
若采用Watcher机制,那么通知的消息量的网络开销非常大,需要采用日志系统主动轮询收集器节点的策略,这样可以节省网络流量,但是存在⼀定的延时。
1.4 Master选举
Master选举是⼀个在分布式系统中非常常见的应用场景。分布式最核心的特性就是能够将具有独立计算能力的系统单元部署在不同的机器上,构成⼀个完整的分布式系统。而与此同时,实际场景中往往也需要在这些分布在不同机器上的独立系统单元中选出⼀个所谓的“老大”,在计算机中,我们称之为Master。
在分布式系统中,Master往往用来协调集群中其他系统单元,具有对分布式系统状态变更的决定权。例如,在⼀些读写分离的应用场景中,客户端的写请求往往是由 Master来处理的;而在另⼀些场景中,Master则常常负责处理⼀些复杂的逻辑并将处理结果同步给集群中其他系统单元。Master选举可以说是ZooKeeper最典型的应用场景了,接下来,我们就结合“⼀种海量数据处理与共享模型”这个具体例子来看看 ZooKeeper在集群Master选举中的应用场景。
在分布式环境中,经常会碰到这样的应用场景:集群中的所有系统单元需要对前端业务提供数据,比如⼀个商品 ID,或者是⼀个网站轮播广告的广告 ID(通常出现在⼀些广告投放系统中)等,而这些商品ID或是广告ID往往需要从⼀系列的海量数据处理中计算得到——这通常是⼀个非常耗费 I/O 和 CPU资源的过程。鉴于该计算过程的复杂性,如果让集群中的所有机器都执行这个计算逻辑的话,那么将耗费非常多的资源。⼀种比较好的方法就是只让集群中的部分,甚至只让其中的⼀台机器去处理数据计算,⼀旦计算出数据结果,就可以共享给整个集群中的其他所有客户端机器,这样可以大大减少重复劳动,提升性能。 这里我们以⼀个简单的广告投放系统后台场景为例来讲解这个模型。
整个系统大体上可以分成客户端集群、分布式缓存系统、海量数据处理总线和 ZooKeeper四个部分。
首先我们来看整个系统的运行机制。图中的Client集群每天定时会通过ZooKeeper来实现Master选举。选举产生Master客户端之后,这个Master就会负责进行⼀系列的海量数据处理,最终计算得到⼀个数据结果,并将其放置在⼀个内存/数据库中。同时,Master还需要通知集群中其他所有的客户端从这个内存/数据库中共享计算结果。
接下去,我们将重点来看 Master 选举的过程,首先来明确下 Master 选举的需求:在集群的所有机器中选举出⼀台机器作为Master。针对这个需求,通常情况下,我们可以选择常见的关系型数据库中的主键特性来实现:集群中的所有机器都向数据库中插⼊⼀条相同主键 ID 的记录,数据库会帮助我们自动进行主键冲突检查,也就是说,所有进行插⼊操作的客户端机器中,只有⼀台机器能够成功——那么,我们就认为向数据库中成功插⼊数据的客户端机器成为Master。
借助数据库的这种方案确实可行,依靠关系型数据库的主键特性能够很好地保证在集群中选举出唯⼀的⼀个Master。但是我们需要考虑的另⼀个问题是,如果当前选举出的Master挂了,那么该如何处理?谁来告诉我Master挂了呢?显然,关系型数据库没法通知我们这个事件。那么,如果使用ZooKeeper是否可以做到这⼀点呢? 那在之前,我们介绍了ZooKeeper创建节点的API接口,其中⼀个重要特性便是:利用ZooKeeper的强⼀致性,能够很好保证在分布式高并发情况下节点的创建⼀定能够保证全局唯⼀性,即ZooKeeper将会保证客户端无法重复创建⼀个已经存在的数据节点。也就是说,如果同时有多个客户端请求创建同⼀个节点,那么最终⼀定只有⼀个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行Master选举了。
在这个系统中,首先会在 ZooKeeper 上创建⼀个日期节点,例如“2020-11-11
客户端集群每天都会定时往ZooKeeper 上创建⼀个临时节点,例如/master_election/2020-11-11/binding。在这个过程中,只有⼀个客户端能够成功创建这个节点,那么这个客户端所在的机器就成为了Master。同时,其他没有在ZooKeeper上成功创建节点的客户端,都会在节点/master_election/2020-11-11 上注册⼀个子节点变更的 Watcher,用于监控当前的 Master 机器是否存活,⼀旦发现当前的 Master 挂了,那么其余的客户端将会重新进行Master选举。
从上面的讲解中,我们可以看到,如果仅仅只是想实现Master选举的话,那么其实只需要有⼀个能够保证数据唯⼀性的组件即可,例如关系型数据库的主键模型就是非常不错的选择。但是,如果希望能够快速地进行集群 Master 动态选举,那么就可以基于 ZooKeeper来实现。
1.5 分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的⼀种方式。如果不同的系统或是同⼀个系统的不同主机之间共享了⼀个或⼀组资源,那么访问这些资源的时候,往往需要通过⼀些互斥手段来防止彼此之间的干扰,以保证⼀致性,在这种情况下,就需要使用分布式锁
了。
在平时的实际项目开发中,我们往往很少会去在意分布式锁,而是依赖于关系型数据库固有的排他性来实现不同进程之间的互斥。这确实是⼀种非常简便且被广泛使用的分布式锁实现方式。然而有⼀个不争的事实是,目前绝大多数大型分布式系统的性能瓶颈都集中在数据库操作上。因此,如果上层业务再给数据库添加⼀些额外的锁,例如行锁、表锁甚至是繁重的事务处理,那么就会让数据库更加不堪重负,下面我们来看看使用ZooKeeper如何实现分布式锁,这里主要讲解排他锁
和共享锁
两类分布式锁。
1.5.1 Zookeeper实现排他锁
排他锁
排他锁(Exclusive Locks,简称 X 锁),又称为写锁或独占锁,是⼀种基本的锁类型。如果事务 T1对数据对象 O1加上了排他锁,那么在整个加锁期间,只允许事务 T1对 O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作——直到T1释放了排他锁。
从上面讲解的排他锁的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且仅有⼀个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。
下面我们就来看看如何借助ZooKeeper实现排他锁:
(1)定义锁
在通常的Java开发编程中,有两种常见的方式可以用来定义锁,分别是synchronized机制和JDK5提供的ReentrantLock。然而,在ZooKeeper中,没有类似于这样的API可以直接使用,而是通过 ZooKeeper上的数据节点来表示⼀个锁,例如/exclusive_lock/lock节点就可以被定义为⼀个锁,如图:
(2)获取锁
在需要获取排他锁时,所有的客户端都会试图通过调用 create()接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock。在前面,我们也介绍了,ZooKeeper 会保证在所有的客户端中,最终只有⼀个客户端能够创建成功,那么就可以认为该客户端获取了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock 节点上注册⼀个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。
(3)释放锁
在“定义锁”部分,我们已经提到,/exclusive_lock/lock 是⼀个临时节点,因此在以下两种情况下,都有可能释放锁。当前获取锁的客户端机器发生宕机,那么ZooKeeper上的这个临时节点就会被移除。正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。无论在什么情况下移除了lock节点,ZooKeeper都会通知所有在/exclusive_lock节点上注册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复“获取锁”过程。整个排他锁的获取和释放流程,如下图:
1.5.2 Zookeeper实现共享锁
共享锁
共享锁(Shared Locks,简称S锁),又称为读锁,同样是⼀种基本的锁类型。
如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁——直到该数据对象上的所有共享锁都被释放。
共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对⼀个事务可见,而加上共享锁后,数据对所有事务都可见。
下面我们就来看看如何借助ZooKeeper来实现共享锁。
(1) 定义锁
和排他锁⼀样,同样是通过 ZooKeeper 上的数据节点来表示⼀个锁,是⼀个类似于
“/shared_lock/[Hostname]-请求类型-序号”的临时顺序节点,例如/shared_lock/host1-R-0000000001,那么,这个节点就代表了⼀个共享锁,如图所示:
(2)获取锁
在需要获取共享锁时,所有客户端都会到/shared_lock 这个节点下⾯创建⼀个临时顺序节点,如果当前是读请求,那么就创建例如/shared_lock/host1-R-0000000001的节点;如果是写请求,那么就创建例如/shared_lock/host2-W-0000000002的节点。
判断读写顺序
通过Zookeeper来确定分布式读写顺序,大致分为四步:
- 创建完节点后,获取/shared_lock节点下所有子节点,并对该节点变更注册监听。
- 确定自己的节点序号在所有⼦节点中的顺序。
- 对于读请求:若没有比自己序号小的子节点或所有比自己序号小的子节点都是读请求,那么表
明自己已经成功获取到共享锁,同时开始执行读取逻辑,若有写请求,则需要等待。对于写请求:若自己不是序号最小的子节点,那么需要等待。 - 接收到Watcher通知后,重复步骤1
(3) 释放锁
其释放锁的流程与独占锁⼀致。
1.5.3 羊群效应
上面讲解的这个共享锁实现,大体上能够满足一般的分布式集群竞争锁的需求,并且性能都还可以——这里说的⼀般场景是指集群规模不是特别大,⼀般是在10台机器以内。但是如果机器规模扩大之后,会有什么问题呢?我们着重来看上面“判断读写顺序”过程的步骤3,结合下面的图,看看实际运行中的情况。
针对如上图所示的情况进行分析
- host1首先进行读操作,完成后将节点/shared_lock/host1-R-00000001删除。
- 余下4台机器均收到这个节点移除的通知,然后重新从/shared_lock节点上获取⼀份新的子节
点列表。 - 每台机器判断自己的读写顺序,其中host2检测到自己序号最小,于是进行写操作,余下的机器则继续等待。
- 继续…
可以看到,host1客户端在移除自己的共享锁后,Zookeeper发送了子节点更变Watcher通知给所有机器,然而除了给host2产生影响外,对其他机器没有任何作用。大量的Watcher通知和子节点列表获取两个操作会重复运行,这样不仅会对zookeeper服务器造成巨大的性能影响和网络开销,更为严重的是,如果同⼀时间有多个节点对应的客户端完成事务或是事务中断引起节点消失,ZooKeeper服务器就会在短时间内向其余客户端发送大量的事件通知,这就是所谓的羊群效应。
上面这个ZooKeeper分布式共享锁实现中出现羊群效应的根源在于,没有找准客户端真正的关注点。我们再来回顾⼀下上面的分布式锁竞争过程,它的核心逻辑在于:判断自己是否是所有子节点中序号最小的。于是,很容易可以联想到,每个节点对应的客户端只需要关注比自己序号小的那个相关节点的变更情况就可以了,而不需要关注全局的⼦列表变更情况。
可以有如下改动来避免羊群效应,改进后的分布式锁实现:
首先,我们需要肯定的⼀点是,上面提到的共享锁实现,从整体思路上来说完全正确。这里主要的改动在于:每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可,具体实现如下。
- 客户端调用create接口常见类似于/shared_lock/[Hostname]-请求类型-序号的临时顺序节点。
- 客户端调用getChildren接口获取所有已经创建的子节点列表(不注册任何Watcher)。
- 如果无法获取共享锁,就调用exist接口来对比自己小的节点注册Watcher。对于读请求:向比自己序号小的最后⼀个写请求节点注册Watcher监听。对于写请求:向比自己序号小的最后⼀个节点注册Watcher监听。
- 等待Watcher通知,继续进⼊步骤2。
此方案改动主要在于:每个锁竞争者,只需要关注/shared_lock节点下序号比自己小的那个节点是否存在即可。
注意: 相信很多人都会觉得改进后的分布式锁实现相对来说比较麻烦。确实如此,如同在多线程并发编程实践中,我们会去尽量缩小锁的范围——对于分布式锁实现的改进其实也是同样的思路。那么对于开发人员来说,是否必须按照改进后的思路来设计实现自己的分布式锁呢?答案是否定的。在具体的实际开发过程中,我们提倡根据具体的业务场景和集群规模来选择适合自己的分布式锁实现:在集群规模不大、网络资源丰富的情况下,第⼀种分布式锁实现方式是简单实用的选择;而如果集群规模达到⼀定程度,并且希望能够精细化地控制分布式锁机制,那么就可以试试改进版的分布式锁实现。
1.6 分布式队列
分布式队列可以简单分为两大类:⼀种是常规的FIFO先入先出队列
模型,还有⼀种是等待队列元素聚集后统⼀安排处理执行的Barrier
模型。
(1)FIFO先入先出
FIFO(First Input First Output,先入先出), FIFO 队列是⼀种非常典型且应用广泛的按序执行的队列模型:先进入队列的请求操作先完成后,才会开始处理后面的请求。
实用ZooKeeper实现FIFO队列,和之前提到的共享锁的实现非常类似。FIFO队列就类似于⼀个全写的共享锁模型,大体的设计思路其实非常简单:所有客户端都会到/queue_fifo 这个节点下面创建⼀个临时顺序节点,例如如/queue_fifo/host1-00000001。
创建完节点后,根据如下4个步骤来确定执行顺序。
- 通过调用getChildren接口来获取/queue_fifo节点的所有子节点,即获取队列中所有的元素。
- 确定自己的节点序号在所有子节点中的顺序。
- 如果自己的序号不是最小,那么需要等待,同时向比自己序号小的最后⼀个节点注册Watcher监听。
- 接收到Watcher通知后,重复步骤1。
(2)Barrier:分布式屏障
Barrier原意是指障碍物、屏障,而在分布式系统中,特指系统之间的⼀个协调条件,规定了⼀个队列的元素必须都集聚后才能统⼀进行安排,否则⼀直等待。这往往出现在那些大规模分布式并行计算的应用场景上:最终的合并计算需要基于很多并行计算的子结果来进行。这些队列其实是在 FIFO 队列的基础上进行了增强,大致的设计思想如下:开始时,/queue_barrier 节点是⼀个已经存在的默认节点,并且将其节点的数据内容赋值为⼀个数字n来代表Barrier值,例如n=10表示只有当/queue_barrier节点下的子节点个数达到10后,才会打开Barrier。之后,所有的客户端都会到/queue_barrie节点下创建⼀个临时节点,例如/queue_barrier/host1,如图所示。
创建完节点后,按照如下步骤执行。
- 通过调用getData接口获取/queue_barrier节点的数据内容:10。
- 通过调用getChildren接口获取/queue_barrier节点下的所有子节点,同时注册对子节点变更的
Watcher监听。 - 统计子节点的个数。
- 如果子节点个数还不足10个,那么需要等待。
- 接受到Wacher通知后,重复步骤2
相关文章:

SpringCloud Alibaba集成 Gateway(自定义负载均衡器)、Nacos(配置中心、注册中心)、Loadbalancer
要为未被某些网关路由谓词处理的请求提供相同的CORS配置,请将属性spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping设置为true。断言(Predicate):Java8中的断言函数,Spring Cloud Gateway中的断言函数输入类型是 Spring5.0框架中的ServerWebExchange。对于所有GET请求的路径,来自docs.spring.io的请求都将允许CORS请求。

并发编程下的集合:数组寻址、LinkedList、HashMap、ConcurrentHashMap
如果发现hash取模后的数组索引位下无元素则直接新增,若不是空那就说明存在hash冲突,则判断数组索引位链表结构中的第一个元素的key以及hash值是否与新的key一致则直接覆盖,若不一致则判断当前的数组索引下的链表结构是否为红黑树,若为红黑树则走红黑树的新增方法,若不为红黑树则遍历当前链表结构,遍历中发现某个节点元素的next为null是则直接将新元素指针与next进行关联,若在遍历到next为空前判断到,某个节点的key以及key的hash值与新的key与新的keyhash值一致时则走覆盖。

【日常开发之插件篇】IDEA plugins 神器助我!!
今早因为老代码的一些bug让我突然觉得Idea的一些插件特别好用,我准备将我平时所用到的一些插件做个推荐以及记录。

【日常开发之FTP】Windows开启FTP、Java实现FTP文件上传下载
FTP是一个专门进行文件管理的操作服务,一般来讲可以在任意的操作系统之中进行配置,但是如果考虑到简便性,一般来讲可以直接在Linux系统下进行安装。FTP (File Transfer Protocol、文件传输协议)是TCP/IP协议中的一部分,属于应用层协议。使用FTP最主要的功能是对文件进行管理,所以在FTP内部对于文件支持有两种传输模式:文本模式(ASCII、默认)和二进制模式(Binary),通常文本文件使用ASCIl模式,而对于图片、视频、声音、压缩等文件则会使用二进制的方式进行传输。

【Linux之升华篇】Linux内核锁、用户模式与内核模式、用户进程通讯方式
alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。字符设备描述符 struct cdev,cdev_alloc()用于动态的分配 cdev 描述符,cdev_add()用于注。外,还支持语义符合 Posix.1 标准的信号函数 sigaction(实际上,该函数是基于 BSD 的,BSD。从最初的原子操作,到后来的信号量,从。(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的。

【Mongdb之数据同步篇】什么是Oplog、Mongodb 开启oplog,java监听oplog并写入关系型数据库、Mongodb动态切换数据源
oplog是local库下的一个固定集合,Secondary就是通过查看Primary 的oplog这个集合来进行复制的。每个节点都有oplog,记录这从主节点复制过来的信息,这样每个成员都可以作为同步源给其他节点。Oplog 可以说是Mongodb Replication的纽带了。

zookeeper集群部署以及zookeeper原理
ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。ZooKeeper包含一个简单的原语集,提供Java和C的接口。

【日常开发之Windows共享文件】Java实现Windows共享文件上传下载
下拉框选择你选择的用户点击添加,然后共享确定。创建一个文件夹然后点击属性界面,点击共享。maven版本存在于SMB协议的兼容问题。首先开启服务,打开控制面板点击程序。点击启用或关闭Windows功能。我这边是专门创建了一个用户。SMB1.0选中红框内的。

CXFServlet类的作用
CXFServlet是Apache CXF框架中的一个核心组件,用于处理HTTP请求并将它们转换为Web服务调用。通过配置CXFServlet,你可以轻松地部署和管理SOAP和RESTful Web服务。

@Scheduled注解的scheduler属性什么作用
注解是 Spring Framework 提供的一种机制,用于定义计划任务,即周期性执行的任务。 注解可以应用于方法上,以指示 Spring 容器在特定的时间间隔或按照某种调度规则来调用该方法。 属性是 注解的一个可选属性,它的作用是允许开发者指定一个自定义的 对象来控制任务的调度方式。默认情况下, 注解使用 Spring 内部的 来执行任务,但如果需要更高级的定制化需求,可以通过 属性指定一个自定义的 实现。自定义调度器:共享调度器资源:高级调度需求:假设你想使用 作为调度器,并且希望所有带有

过滤器、拦截器、aop的先后顺序和作用范围&拦截器preHandle(),postHandle(),afterComplation()方法执行顺序
在Spring框架中,过滤器(Filter)、拦截器(Interceptor)和面向切面编程(AOP)都是用于处理请求和处理流程的组件,但它们的作用范围和触发时机有所不同。下面我会解释这三者的先后顺序和作用范围。执行顺序:请注意,这个顺序可能因具体的配置和使用的技术而有所不同。在实际应用中,建议根据项目的具体需求来合理配置和使用这些组件。拦截器执行流程图:实现拦截器需要实现这个接口,这个 接口中有三个默认方法,这三个方法的执行顺序:我们实现接口然后重写这三个方法,就会在对应的时机被自动执行。这里就是调用处理

spring.factories文件的作用
即spring.factories文件是帮助spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器中。在Spring Boot启动时,它会扫描classpath下所有的spring.factories文件,加载其中的自动配置类,并将它们注入到Spring ApplicationContext中,使得项目能够自动运行。spring.factories文件是Spring Boot自动配置的核心文件之一,它的作用是。

Spring事务七大传播机制与五个隔离级别,嵌套事务
如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。

常见的七种加密算法及实现
**数字签名**、**信息加密** 是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、`oauth` 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 **签名加密算法** 来达到业务目标。这里简单的给大家介绍几种常见的签名加密算法和一些典型场景下的应用。## 正文### 1. 数字签名**数字签名**,简单来说就是通过提供 **可鉴别** 的 **数字信息** 验证 **自身身份** 的一种方式。一套 **数字签名** 通常定义两种 **互补

7min到40s:SpringBoot 启动优化实践
然后重点排查这些阶段的代码。先看下。

SpringBoot系列教程之Bean之指定初始化顺序的若干姿势
之前介绍了@Order注解的常见错误理解,它并不能指定 bean 的加载顺序,那么问题来了,如果我需要指定 bean 的加载顺序,那应该怎么办呢?本文将介绍几种可行的方式来控制 bean 之间的加载顺序。

在Java中使用WebSocket
WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2011年成为标准。

3种方案,模拟两个线程抢票
在多线程编程中,资源竞争是一个常见的问题。资源竞争发生在多个线程试图同时访问或修改共享资源时,可能导致数据不一致或其他并发问题。在模拟两个线程抢票的场景中,我们需要考虑如何公平地分配票,并确保每个线程都有机会成功获取票。本篇文章将通过三种方式来模拟两个线程抢票的过程,以展示不同的并发控制策略。使用 Synchronized 来确保一次只有一个线程可以访问票资源。使用 ReentrantLock 来实现线程间的协调。使用 Semaphore 来限制同时访问票的线程数量。

替代Druid,HakariCP 为什么这么快?
这次源码探究,真的感觉看到了无数个小细节,无数个小优化,积少成多。平时开发过程中,一些小的细节也一定要“扣”。

Java中volatile 的使用场景有哪些?
volatile是一种轻量级的同步机制,它能保证共享变量的可见性,同时禁止重排序保证了操作的有序性,但是它无法保证原子性。所以使用volatilevolatile。

JDK22 正式发布了 !
Java 22 除了推出了新的增强功能和特性,也获得 Java Management Service (JMS) 的支持,这是一项新的 Oracle 云基础设施远程软件服务(Oracle Cloud Infrastructure, OCI) 原生服务,提供统一的控制台和仪表盘,帮助企业管理本地或云端的 Java 运行时和应用。使包含运行时计算值的字符串更容易表达,简化 Java 程序的开发工作,同时提高将用户提供的值编写成字符串,并将字符串传递给其他系统的程序的安全性。支持开发人员自由地表达构造器的行为。

Jackson 用起来!
你可以创建自定义序列化器和反序列化器以自定义特定字段或类的序列化和反序列化行为。为此,请创建一个实现或接口的类,并在需要自定义的字段或类上使用和注解。@Override// ...其他代码...优势性能优异:Jackson在序列化和反序列化过程中表现出优秀的性能,通常比其他Java JSON库更快。灵活性:通过注解、自定义序列化器/反序列化器等功能,Jackson提供了丰富的配置选项,允许你根据需求灵活地处理JSON数据。易于使用:Jackson的API设计简洁明了,易于学习和使用。

拜托!别再滥用 ! = null 判空了!!
另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空。如果你养成习惯,都是这样写代码(返回空collections而不返回null),你调用自己写的方法时,就能大胆地忽略判空)这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。最终,项目中会存在大量判空代码,多么丑陋繁冗!,而不要返回null,这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接。

详解Java Math类的toDegrees()方法:将参数从弧度转换为角度
Java Math 类的 toDegrees() 方法是将一个角度的弧度表示转换为其度表示,返回值为double类型,表示从弧度数转换而来的角度数。这就是Java Math 类的 toDegrees() 方法的攻略。我们已经了解了该方法的基本概念、语法、注意事项以及两个示例。希望这篇攻略对你有所帮助。

SpringBoot接口防抖(防重复提交)的一些实现方案
作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。啥是防抖所谓防抖,一是防用户手抖,二是防网络抖动。

公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!
前段时间,同事在代码中KW扫描的时候出现这样一条:上面出现这样的原因是在使用foreach对HashMap进行遍历时,同时进行put赋值操作会有问题,异常ConcurrentModificationException。于是帮同简单的看了一下,印象中集合类在进行遍历时同时进行删除或者添加操作时需要谨慎,一般使用迭代器进行操作。于是告诉同事,应该使用迭代器Iterator来对集合元素进行操作。同事问我为什么?这一下子把我问蒙了?对啊,只是记得这样用不可以,但是好像自己从来没有细究过为什么?

每天一个摆脱if-else工程师的技巧——优雅的参数校验
在日常的开发工作中,为了程序的健壮性,大部分方法都需要进行入参数据校验。最直接的当然是在相应方法内对数据进行手动校验,但是这样代码里就会有很多冗余繁琐的if-else。throw new IllegalArgumentException("用户姓名不能为空");throw new IllegalArgumentException("性别不能为空");throw new IllegalArgumentException("性别错误");

SpringBoot请求转发与重定向
但是可能由于B网址相对于A网址过于复杂,这样搜索引擎就会觉得网址A对用户更加友好,因而在重定向之后任然显示旧的网址A,但是显示网址B的内容。在平常使用手机的过程当中,有时候会发现网页上会有浮动的窗口,或者访问的页面不是正常的页面,这就可能是运营商通过某种方式篡改了用户正常访问的页面。重定向,是指在Nginx中,重定向是指通过修改URL地址,将客户端的请求重定向到另一个URL地址的过程,Nginx中实现重定向的方式有多种,比如使用rewrite模块、return指令等。使用场景:在返回视图的前面加上。

SSO 单点登录和 OAuth2.0 有何区别?
此方法的缺点是它依赖于浏览器和会话状态,对于分布式或者微服务系统而言,可能需要在服务端做会话共享,但是服务端会话共享效率比较低,这不是一个好的方案。在单点登录的上下文中,OAuth 可以用作一个中介,用户在一个“授权服务器”上登录,并获得一个访问令牌,该令牌可以用于访问其他“资源服务器”上的资源。首先,SSO 主要关注用户在多个应用程序和服务之间的无缝切换和保持登录状态的问题。这种方法通过将登录认证和业务系统分离,使用独立的登录中心,实现了在登录中心登录后,所有相关的业务系统都能免登录访问资源。

TCP协议-TCP连接管理
TCP协议是 TCP/IP 协议族中一个非常重要的协议。它是一种面向连接、提供可靠服务、面向字节流的传输层通信协议。TCP(Transmission Control Protocol,传输控制协议)。