开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

架构设计原则与流程

发表于 2021-11-25

这是我参与12月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

架构设计原则

合适原则

合适优于业界领先。

开发是一件比较复杂的事情,一个项目最终的成功与否涉及到很多方面。每个不同的架构设计方案都需要有相应的条件。不顾实际,盲目选择业界领先的方案,最终的结果很可能是一地鸡毛。

做一个好的方案设计,需要考虑到:

  • 团队人员的技术水平与专长,能否hold住方案相关技术点
  • 能够投入的资源,包括人力资源、硬件资源等
  • 根据业务场景,判断方案是否适合该业务场景

对业务来说,只有合不合适,没有先不先进,最重要的是在当前的约束场景下,将业务快速落地,这才是一个架构方案的最大价值。

简单原则

简单优于复杂。

复杂方案会带来很多问题。不仅开发起来比较耗时,定位问题,后续迭代,都会有非常高的成本。所以我们做架构设计的时候,能用简单方案,尽量不要用复杂方案。

演化原则

演化优于一步到位。

对于软件开发而言,变化是永恒的主题。软件架构需要根据业务发展不断变化。我们无法遇见到后期可产生的所有业务变化,所以也无法设计一个能适应一切的架构。

所以我们应该优先满足眼前的需求,在开发过程中不断迭代,在必要的时候对业务进行重构,重写,不断优化满足新的业务需求。

架构设计流程

识别复杂度

我们需要先将业务中最复杂的部分找出来,有针对的进行方案设计。比如高性能、高可用、可扩展性,大部分情况下只需要满足其中一到两个。

识别复杂度需要从不同角度对需求进行分析。

设计备选方案

只做一个方案是一个常见的误区。没有哪个方案是完美的,并且可能会对现状进行误判,个人的知识也有局限性,而且只设计一个方案可能会导致钻牛角尖。

多设计几套差异较为明显的备选方案,并且不局限于熟悉的技术,这样评审的时候才会更加客观。

评估和选择备选方案

选择备选方案场景的误区是:选择最简单的、选择最熟悉的、选择技术上最先进的。这个选择维度过于单一,比较好的方式是,进行全面评估。

列出系统的复杂度,设计出系统的关键指标,从这些指标入手去评估每个方案。如:性能、可用性、成本、时间、复杂度、安全、可扩展性等。最后按评估结果的按优先级进行排列,综合考虑选出方案。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

458 可怜的小猪

发表于 2021-11-25

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

  1. 可怜的小猪

有 buckets 桶液体,其中 正好 有一桶含有毒药,其余装的都是水。它们从外观看起来都一样。为了弄清楚哪只水桶含有毒药,你可以喂一些猪喝,通过观察猪是否会死进行判断。不幸的是,你只有 minutesToTest 分钟时间来确定哪桶液体是有毒的。

喂猪的规则如下:

  1. 选择若干活猪进行喂养
  2. 可以允许小猪同时饮用任意数量的桶中的水,并且该过程不需要时间。
  3. 小猪喝完水后,必须有 minutesToDie 分钟的冷却时间。在这段时间里,你只能观察,而不允许继续喂猪。
  4. 过了 minutesToDie 分钟后,所有喝到毒药的猪都会死去,其他所有猪都会活下来。
  5. 重复这一过程,直到时间用完。
    给你桶的数目 buckets ,minutesToDie 和 minutesToTest ,返回在规定时间内判断哪个桶有毒所需的 最小 猪数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
ini复制代码示例 1:

输入:buckets = 1000, minutesToDie = 15, minutesToTest = 60
输出:5

示例 2:

输入:buckets = 4, minutesToDie = 15, minutesToTest = 15
输出:2

示例 3:

输入:buckets = 4, minutesToDie = 15, minutesToTest = 30
输出:2

提示:

  • 1 <= buckets <= 1000
  • 1 <= minutesToDie <= minutesToTest <= 100

解题思路

  • 老鼠试毒药的变种题目
    1024瓶毒药,10只老鼠,试出一瓶有毒的毒药,方案:给每瓶药从0-1023进行编号,老鼠从0-9进行编号,对于每一瓶药,根据其编号二进制表示中1出现的位置,给对应位置的小白鼠喂药,根据小白鼠的死亡结果,可以反推出毒药的二进制表示。

共通点:小白鼠只有两种状态0代表存活,1代表死亡,而本题中可以进行minutesToTest / minutesToDie轮,假如minutesToTest / minutesToDie=2的话,那么可以进行两轮,存在3种状态

  1. 第一轮死亡
  2. 第二轮死亡
  3. 第二轮还存活

老鼠试药的计算公式为 210=10242^{10}=1024210=1024

  • 1024 为毒药数量
  • 2为老鼠可能的状态数量
  • 10 代表老鼠个数

因此这题 计算公式为statesres=bucketsstates^{res}=bucketsstatesres=buckets

  • buckets 为毒药数量
  • states为小猪可能的状态数量,公式为minutesToTest / minutesToDie + 1;
  • res为需要的小猪数量,也是我们需要计算的最终结果

代码

1
2
3
4
5
6
7
cpp复制代码class Solution {
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
int states = minutesToTest / minutesToDie + 1;
return ceil(log(buckets)/log(states));
}
};

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Kerberos01:Kerberos基本概念和服务端安装

发表于 2021-11-25

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

前言

在大数据中,为了实现多租户环境下的数据、资源隔离,通常使用Kerberos认证来保证。常用于HDFS、Yarn、Kafka等大数据组件中。

基本概念

首先先了解一下Kerberos中常用的基本概念。

Kinit

请求缓存当前Principal的TGT,可以使用密码或者Keytab。

1
css复制代码kinit -kt/path/to/keytabname@realm

kdestroy

删除对当前缓存的TGT

Realm

Kerberos的一个认证管理域,其约束条件如下。

  1. 同一个域的用户使用同样的认证方案
  2. 在属于一个认证服务的边界内,这个认证服务才有权利认证一个用户,也有交叉认证
  3. realm的名字都用大写字母,通常realm名字和DNS域名一致
  4. 这个realms跟机器的host没有大关系

Principal

客户端和服务器唯一的名字,密码只有自己和认证服务器AS知道

  1. 用于引用认证服务数据库(AD)中的一个条目,密码存于AD和keytab
  2. 用户:Name[/Instance]@REALM
  3. 服务:Service/Hostname@REALM第一个是service名字,第二个是提供这个服务的机器的完全主机名(FQDN),DNS对应用服务器的IP地址进行逆向解析后得到的主机名
  4. 特例:krbtgt/REALM@REALM:TGT服务,用于申请Session Ticket

Keytab

以文件的形式呈现,存储了一个或多个Principal的长期的key用途和密码类似,用于kerberos认证登录;其存在的意义在于让用户不需要明文的存储密码,和程序交互时不需要人为交互来输入密码。

使用步骤

  1. 配置好krb5.conf(可以设置Ticket更新时间)
  2. 在KDC.conf中添加好principal,将密码dump回本地存为keytab给服务

服务器端安装

安装软件

1
vbscript复制代码yum install krb5-server krb5-libs krb5-auth-dialog

配置文件/etc/krb5.conf

  1. bdx.sd.cmcc = BDX.SD.CMCC:所有在bdx.sd.cmcc此domain下host都被归属为BDX.SD.CMCC这个realm中,个人认为domain是hosts中的全名
  2. bdx.sd.cmcc = BDX.SD.CMCC:所有hostname包含bdx.sd.cmcc归属后面realm
  3. realm中default_domin这个属性是将krb4中的xx.hostname的service principal转换成krb5中xx/hostname.domain的形式

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

为什么不建议使用 Map 传递参数

发表于 2021-11-25

这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战

前言

虽然我嘴上不建议使用 Map 作为接口参数,但是实际上我却在使用中,真的是…… 真香。下面我就说说在使用 Map 作为接口参数的时候遇到的坑以及我们为什么使用 Map 作为接口参数。

首先,不得不承认,Map 可以全包,所有的参数,各种参数都可以直接往 Map 中放,这样就很方便,接口不用改,新增参数也不用担心,随时加上去。灵活性非常高。扩展性也非常好。但是维护起来非常不方便,因为 Map 中有什么东西,只有看代码才知道,可读性也不好,还有一些类型转换的坑,下面重点说一下。

Long 类型转化为 Integer

map 作为接口参数在微服务之间传递时,会经过序列化和反序列化,问题就在于反序列化之后会把 Long 类型的值转化为 Integer 类型(这个数值在 int 范围内的话)。举个例子大家看一下。

1
2
3
4
5
6
7
8
9
10
javascript复制代码// 服务 1
Map<String, Object> map = new HashMap<>();
Long age = 18;
map.put("age", age);
feignClient.transfer(map);
​
// 服务 2
transfer(Map<String, Object> map){
Long age = (Long) map.get("age");
}

上面是一段伪代码哈,但是足以能够说明问题了,首先一个问题就是 Long 类型的值经过服务之间传递之后类型信息就变了,从 Long 变为 Integer,其次,这里还有一个隐患,就是可能会出现空指针异常,假如 map.get(“age”) 是空,这里就会报错。

Date 类型转化为 Long

直接上代码,大家看的清楚

1
2
3
4
5
6
7
8
9
10
11
javascript复制代码// 服务 1
Map<String, Object> map = new HashMap<>();
Date date = new Date();
map.put("date", date);
feignClient.transfer(map);
​
// 服务 2
transfer(Map<String, Object> map){
   Long timeLong = map.get("date");
   Date date = new Date(timeLong);
}

这里的原理是一样的,在反序列化之后 Date 类型的数据会被转化为 Long 的数值,我们想要得到 Date 类型,还是需要手动做一次处理,而且这里也会可能出现空指针异常的情况,如果服务 1 map.put(“date”, null),在服务 2 中就会报错,我们还需要手动的判空。

代码不可维护

上面的例子我们也可以感受出来使用 map 作为接口参数真是方便,但是更多的还有 hard code 的问题,map 中的 key 谁能知道,你知道一个知道多个嘛?今天这个 key 使用,明天可能就不用了,你有能清楚嘛。即使你知道了 key ,是不是还需要空判断。这一通下来,就是写的时候爽维护的时候骂娘。

假如我们使用 Object 不管是 DTO 还是 TO 作为参数,有什么参数我们都可以封装在类中,是不是就会比较好呢?是会的,比方说我们的校验就会方便很多,使用 spring 和 hibernate 一起提供的 validator 还有一些注解,可以很优雅的解决数据校验的问题。但是有一个问题就是,由于参数的类型或是个数可能会不断的变化,这个对象也需要不断的变化。

总结

在不是必须的情况,都不建议使用 map 来传递参数,我们完全可以创建一个类来封装参数,这样做的好处很明显,代码可读,看起来非常清晰,类型定义清晰,如果想要添加新的参数只需要在类中添加即可。而且类中还可以定义 getter setter 方法,使用起来更舒适。

反观使用 map 你要知道所有的 key 以及对应的类型。如果这个 map 不幸被多个层级使用,这些 key 要在每层都知道…… map 虽然在写的时候很爽,但是大概率在运行的时候会有问题,不信你试试看?

最后说一下我们为什么还要使用 map 呢,因为 key 太多了…… 而且 key 的数量一直不能确定,所以先就使用 map 了。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

什么是IS-IS中间系统到中间系统?网工、运维必看!

发表于 2021-11-25

简介

IS-IS,中文全称:中间系统到中间系统,是一种开放的标准路由协议,ISO 发布了该标准,将数据报作为其 OSI 堆栈的一部分进行路由,IETF 后来重新发布了该标准,并添加了 IP 路由支持。

IS-IS 和 OSPF 之间有很多相似之处:

  • 两者都是链路状态路由协议,这意味着它们都构建了网络的“地图”。
  • 它们都通过网络泛洪链路状态数据,并构建链路状态数据库(LSDB)。
  • 此外,他们都在 LSDB 上运行 Diikjastra 算法来计算最短路径。

那么,如果它像 OSPF,为什么要使用它呢?为什么不改用 OSPF?

IS-IS 有两个巨大的优势:

首先是它的可扩展性,使用 IS-IS 构建大型网络比使用 OSPF 容易得多,这使其成为服务提供商为其基础设施提供的常见选择。

第二个优势是它对其携带的数据采取不可知论的方法,IS-IS 携带可达性数据的有效载荷,但在大多数情况下,它并不关心有效载荷中的内容,这就是它对 FabricPath 等协议有用的原因,相比之下,OSPF 只承载 IP 路由。当 IPv6 出现时,它需要一个全新版本的 OSPF (OSPFv3) 来承载 IPv6 路由,IS-IS 中没有这样的问题。

术语

由于 IS-IS 的历史是在 OSI 堆栈中,因此某些组件与我们在 TCP/IP 世界中习惯的有些不同,我们来看一下IS-IS相关的术语:

IS 是一个 中间系统,这是路由器的 ISO 名称,以及 IS-IS 的名称,它是中间系统或路由器之间的通信。

ES 是一个 端系统,这是网络上的设备,例如服务器或工作站,在原始规范中,ES 将参与 IS-IS,它不需要 DHCP 或 FHRP,因为它已经有一个本地路由表。

CLNS (无连接网络服务)是 OSI 堆栈中的网络服务, CLNP (无连接网络协议)是实现 CLNS 的协议,如果您更熟悉 TCP/IP,这会感觉不寻常,TCP/IP 并没有以这种方式将服务和协议分开,随着时间的推移,这将更有意义。

NSAP 是一个 网络服务接入点,这是第 3 层地址,用于 CLNS 数据包,这就像 TCP/IP 堆栈中的 IP 地址,IS-IS 使用 NSAP 地址进行通信,而不是 IP 地址。

TLV

TLV 中文全称: 类型长度值,是 IS-IS 中的有效载荷字段,TLV 字段携带路由信息,IS-IS 不关心这些字段中的内容,这使得它与协议无关,这有点像集装箱;这艘船不在乎集装箱里有什么。

IS-IS 使用 CLNS 进行传输,每个路由器都有一个 NSAP 地址,用于发送和接收链路状态信息,链路状态信息可能包含几个 TLV 字段。

通常将 IP 路由信息放入 TLV 中,但是任何类型的数据都可以在那里,由接收路由器决定如何处理这些信息。

度量值

与 OSPF 一样,IS-IS 使用cost作为度量,规范说这可以是宽值或窄值,Cisco 路由器仅支持宽度量,窄度量不适用于 MPLS 流量工程,宽度量使用 24 位作为链路度量,使用 32 位作为路径度量。

默认情况下,所有链接的cost均为 10,这意味着在默认实现中,跳数构成度量,网络设计者可以选择更合适的成本方案。

area

与 OSPF 一样,IS-IS 使用区域,区域可以是骨干区域,也可以是普通区域,为了支持这个概念,路由器可能是以下类型之一:

  • Level 1 – 正常区域中的路由器不连接到另一个区域
  • Level 1-2 – 连接不同区域的普通或骨干区域中的路由器
  • Level 2 – 不连接到其他区域的骨干路由器

下面的拓扑显示了具有多个区域的网络示例。

在这个拓扑中有几个有趣的点,骨干区域并不像 OSPF 中那样被限制为“区域 0”,在 IS-IS 中,任何区域号都可以作为骨干网,另外可以对主干进行分区,在此拓扑中,区域 100 和区域 200 都是主干。

另一个有趣的点是区域边界不在路由器本身上,而是区域边界 在 路由器之间,这与 OSPF 不同,其中 ABR 或 ASBR 路由器是边界。

路由器级别

Level 1路由器将只与其他 Level 1路由器共享路由信息,这使它们成为 存根 路由器,在上面的拓扑中,区域 40 是一个 存根区域。区域内的路由是 1 级路由。

Level 2路由器只会与其他 Level 1路由器共享路由信息,这些路由器跟踪区域之间的路由信息,这是 Level 2路由。

Level 1-2 路由器很特殊,因为它们同时参与Level 1 和Level 2 路由,它们将区域连接在一起,这使它们成为使用汇总的理想场所,这些路由器为 Level 1和 Level 1路由维护一个单独的数据库。

区域可以相互连接,也可以使用专用的骨干区域进行传输,骨干网由执行 Level 1路由的路由器组成,包括 Level 1-2路由器,他们不会连接主机或 Level 1路由器。

小型网络根本不需要骨干网,它可以从只有 Level 1路由器的单个区域开始,作为替代方案,它可以是所有Level 1-2路由器,以便以后添加骨干网更容易。

域是一个完整的IS-IS的系统,这就像 BGP 中自治系统的概念。

区域间路由

要将数据包路由到区域外,Level 1路由器会将数据包发送到最近的具有 Level 2功能的路由器,无论目的地如何,始终使用最近的路由器,Level 1路由器没有足够的关于其他区域的信息来做出决定。

当数据包到达第 Level 2路由器时,它会通过骨干网转发到正确的区域,然后将其传递到该区域中的 Level 1路由器以进行本地交付。

为了优化路由,可以将路由从 Level 2数据库泄漏到 Level 1数据库中,此技术可防止区域外的次优路由,当泄漏路由时,他们添加了一个标志,这可以防止将它们通告回别处的 Level 2数据库。

在区域内路由数据包时,路由器使用路由器的 System-ID 进行传送,在区域之间路由数据包时,Area-ID 是地址。这些地址是NET Value 的一部分 。

NET值

网络实体名称,简称“NET”,是一个地址标识路由器,它由路由器的系统 ID 和区域地址组成,NET 是一种 NSAP 地址。

每个路由器的 NET 都是唯一的,它们的长度可以是 8 到 20 个字节,但通常只有 10 个字节。

NET 的一个示例是 49.0001.1720.1600.1001.00,如下例所示,这表示区域 ID、系统 ID 和 NSEL 值。

地址是一系列以点分隔的十六进制值,Area-ID 的长度可变,但必须至少为 1 个字节,它通常是一个三字节值,第一个字节( 在本例中为49)是 AFI或地址族标识符,对于 IS-IS,这通常设置为 49,这是私有寻址的 OSI 值,本例中的0001指的是区域 1。

System-ID 需要在区域内唯一,但在不同区域可能会重叠,将它与区域 ID 结合起来,可以使整个地址唯一,在 Cisco 的 IS-IS 实现中,System-ID 是 6 个字节。

该 NSEL 为N选择器,这在 OSI 堆栈中具有特殊含义,但对于 IS-IS,这将始终设置为零。

邻接

IS-IS 使用 协议数据单元或 PDU 进行通信,这就像 OSPF 中的数据包。

一个 IS-IS Hello PDU中,或 IIH,就像是OSPF Hello报文,路由器交换 IIH 以形成邻居关系,并共享区域地址。

IS-IS 使用多播 MAC 地址在第 2 层发送 IIH PDU,这就是为什么每个路由器只需要一个地址(NET 或 NSAP)的原因,每个接口不需要地址,这也说明接口上的IP地址与IS-IS无关。

Level 1路由器需要匹配区域 ID,此外,Level 2路由器将忽略来自 Level 1路由器的 IIH。身份验证和网络类型也必须匹配才能形成邻居。

Level 2 和Level 1-2 路由器不需要Level 2 IIH 具有相同的区域 ID

一个 链接状态PDU,或 LSP,包含路由信息,并通告给邻居。LSP 就像 OSPF 的 LSA。有两种类型的 LSP;Level 1 LSP 和 Level 2 LSP,Level 1-2路由器发送和接收这两种类型。

LSP 包含头和 TLV 字段。TLV 字段包含正在通告的信息,例如 IP 路由。额外的 TLV 包含其他数据,如邻居信息和身份验证信息。

当网络发生变化时,路由器会将 LSP 泛洪出去,其他路由器接收这些 LSP,并使用它们来构建它们的 链路状态数据库 (LSDB)。IS-IS 在这方面比 OSPF 更有效,它将多个网络组合成一个 LSP,而不是发送许多小的 LSA,这增加了 IS-IS 的可扩展性,因为网络上可以存在更多路由器而不会过度泛洪。

当 IS-IS 在广播媒体上时,一台路由器是 指定中间系统,或 DIS,DIS 会将 LSP 泛洪出去,而不是让所有路由器泛洪该段。这就像 OSPF 中的指定路由器。

路由器举行选举来选择 DIS,如果平局,则 MAC 最高的路由器获胜,DIS 使用抢占,所以如果有更高优先级的路由器出现,它将成为新的 DIS。

没有备份 DIS,这与具有 BDR 角色的 OSPF 不同。

配置

在本实验中,我们将配置 IS-IS 来承载 IP 路由。在 IS-IS实验文件 包括拓扑最初和最后的CONFIGS。

路由器 R2 会将环回接口的 IP 汇总到 /22 网络中。

拓扑如下:

第一步是启用 IS-IS 进程,每个 IS-IS 区域使用一个单独的进程,在下面的示例中,系统 ID 在 NET 值中使用环回 0 的 IP 地址,这可以是任何值,但重新格式化环回 IP 会使故障排除更容易。

被动接口的使用与任何其他路由协议相同。

Cisco 路由器上的第一个 IS-IS 进程设置为级别 1-2,默认情况下,所有后续进程都是级别 1,使用is-type 命令更改此设置 。

然后,在接口上启用 IS-IS。

1
csharp复制代码[rtbs name="isis_intro-configure"]
  • Show clns neighbor 确认邻居关系已经建立。
  • Show isis database 显示LSDB。这是路由器可达性信息。
  • Show clns interface 显示接口的 IS-IS 设置。
1
csharp复制代码[rtbs name="isis_intro-adjacency"]

可以看到IS-IS已经学习到路由,并安装到路由表中。

1
csharp复制代码[rtbs name="isis_intro-routingtable"]

在 IS-IS 进程下,可以配置汇总地址。

如果需要默认路由,则可以使用default-information originate进行通告 。

配置汇总

1
2
arduino复制代码R2(config)#router isis 
R2(config-router)#summary-address 10.0.0.0 255.255.252.0 level-2

可以看到 R3 现在正在学习汇总地址,而不是每个单独的网络。

验证 R3 路由表

1
2
3
4
5
6
7
8
9
10
less复制代码R3#show ip route isis
Gateway of last resort is not set

      10.0.0.0/22 is subnetted, 1 subnets
i L2     10.0.0.0 [115/20] via 192.168.20.1, 00:03:15, GigabitEthernet0/1
      172.16.0.0/32 is subnetted, 3 subnets
i L2     172.16.0.1 [115/20] via 192.168.20.1, 00:19:51, GigabitEthernet0/1
i L2     172.16.10.1 [115/10] via 192.168.20.1, 00:19:51, GigabitEthernet0/1
      192.168.10.0/30 is subnetted, 1 subnets
i L2     192.168.10.0 [115/20] via 192.168.20.1, 00:19:51, GigabitEthernet0/1

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

二叉树刷题记(九-二叉搜索树中的中序后继-中序遍历)

发表于 2021-11-25

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

前言

  • 大家好,小嘟来啦,嘿嘿嘿,有没有想小嘟啊!,今天小嘟带来的还是我们的二叉树专题题目。
  • 读者可以往下翻一下,可以看到今天带来的题可是个中等哦!
  • 小嘟在这里猜一下,大家是不是刷题的时候一看到,中等、困难是不是就慌了(嘿嘿嘿),其实大家都一样,小嘟也是这样的。
  • 困难咱们可以先不尝试,中等的我们还可以试试,今天就和小嘟看看这道中等题的庐山真面目吧!

正文

初遇

  • 题目

image.png

  • 示例1

image.png

  • 示例2

image.png

了解

  • 看完这道题,小嘟就一个感受,这不就是又要我遍历二叉树,怎样遍历呢?小嘟我在看一遍题目,这不就是要中序遍历呗!然后遍历完,在来一个循环不就结束喽,读者请看代码。

熟悉

  • 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ini复制代码var inorderSuccessor = function(root, p) {
let res = [];
let stack = [];
//中序遍历序列的构造过程
while(root||stack.length){
while(root){
stack.push(root);
root = root.left;
}
let target = stack.pop();
res.push(target);
root = target.right;
}
//在中序序列中和p.val相等的二叉树结点
//然后进行如下的判断即可
for(let i=0;i<res.length;i++){
if(i == res.length-1) return null;
if(res[i].val == p.val) return res[i+1];
}
};

image.png

知彼

  • 总结:做了这将近9道题目了,我们发现,什么操作都离不开遍历,所以我们得好好掌握遍历,小嘟将三种基本的遍历放到了底部,请读者自行学习。

结尾

  • 今天小嘟给大家留一个思考题,请看题:还是上边这个题的思想不变,这个时候我要求中序遍历的前一个结点`,,该怎么做呢?。
  • 若有问题,欢迎留言!!!,小嘟会尽自己最大的努力解决问题。
  • 小嘟今天有点课程有点多,所以更新的比较晚,不好意思啦!
  • 等到周末,小嘟会回过头来修改文章哒!
  • 希望读者看完能有所收获!
  • 谢谢读者点赞点赞点赞啦!
  • 小嘟溜啦溜啦...

附件

  • 二叉树前序遍历 juejin.cn/post/702968…
  • 二叉树中序遍历
+ 递归版[juejin.cn/post/702748…](https://dev.newban.cn/7027482621089153038)
+ 迭代版[juejin.cn/post/702774…](https://dev.newban.cn/7027747932711419935)
  • 二叉树后序遍历 juejin.cn/post/702911…
  • 题目直通车leetcode-cn.com/problems/P5…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

TCP/IP协议 入门 IP协议 TCP 是如何做传输的?

发表于 2021-11-25

本文正在参与 “网络协议必知必会”征文活动

IP协议

TCP和UDP是两种最为著名的传输层协议

他们都是使用IP作为网络层协议。

TCP协议是一种面向连接的协议,在使用TCP

进行通信之前,两个应用程序之间需要建立一个TCP连接

TCP 是如何做传输的?

由于TCP协议是一种可信的传输协议,所以在传输之

前,需要通过三次握手建立一个连接,所谓的三次握手,

就是在建立TCP链接时,需要客户端和服务端总共发送 3

个包来确认连接的可靠建立

TCP分层

TCP/IP有四层协议系统:

应用层

Telnet(远程登录)、FTP(文件传输协议)和e_mail等

运输层

TCP(传输控制协议)和UDP(用户数据协议)

网络层

IP(网际协议)、ICMP(Internet互联网控制报文协议)和IGMP(Internet组管理协议)

链路层

设备驱动程序及接口卡

参考内容:TCP-IP详解卷1:协议

每一层都由特定的协议与对方进行通信,而协议之间的通信最终都要转化为二进制的信号,通过某种物理介质进行传输才能进行通讯

TCP三次握手

1.建立连接。发送端发送连接请求报文段(可以理解为暗号)

2.接收端收到发送端的发报文段(暗号)

3.发送端收到接收端返还的(确认暗号)

TCP四次挥手

1.发送端设置seq和 ACK ,向接收端发送一个 FIN(终结)报文段。

2.接收端收到了发送端发送的 FIN 报文段,向发送端回了一个 ACK 报文段。

3.接收端向发送端发送FIN 报文段,请求关闭连接,同时接收端进入 LAST_ACK 状态。

4.发送端收到接收端发送的 FIN 报文段后,向接收端发送 ACK 报文段,然后发送端进入 TIME_WAIT 状态。
接收端收到发送端的 ACK 报文段以后,就关闭连接。
此时,发送端等待 2MSL(指一个片段在网络中最大的存活时间)后依然没有收到回复,则说明接收端已经正常关闭,这样发送端就可以断开连接

安卓

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

39 Redis 60的新特性:多线程、客户端缓存与安

发表于 2021-11-25

Redis6.0的新特性

  • 面向网络处理的多IO线程:提升网络请求处理速度,提升性能
  • 客户端缓存:直接读取本地缓存,提升性能
  • 细粒度权限控制:安全考虑
  • RESP3协议:增强客户端功能

面向网络处理的多IO线程

  • 老版redis
+ 单线程处理命令(后台线程:数据库删除,快照生成,AOF重写)
+ 单线程处理网络请求 epoll


    - 解决思路:


        * 1.用户态网络协议栈(例如 DPDK)取代内核网络协议栈  (使用用户态网络协议)
        * 2.采用多个IO线程处理网络请求,提升网络请求处理的并行度
  • Redis 的多 IO 线程只是用来处理网络请求的,对于读写命令,Redis 仍然使用单线程来处理\
  • 具体实现
+ 服务端和客户端建立socket连接,并分配处理线程
+ IO 线程读取并解析请求\
+ 主线程执行请求操作\
+ IO 线程回写 Socket 和主线程清空全局队列\
  • 1

如何使用多线程

  • 开启多线程:io-thread-do-reads 配置项为 yes
  • 线程个数要小于 Redis 实例所在机器的 CPU 核个数 io-threads 6\

客户端缓存

  • redis客户端将数据缓存在业务服务器中,实现快速读取数据
  • 特殊场景:如果数据别修改或失效该如何处理
+ 服务端会记录客户端获取的key
+ 普通模式:如果key发生修改则会发送key失效的消息(但是只会发送一次失效信息) CLIENT TRACKING ON|OFF使用
+ 广播模式:服务端会给客户端广播所有 key 的失效情况 ,RESP 3支持 RESP 2则通过监听\_redis\_:invalidate 频道,获得失效消息

细粒度的权限控制

  • 高风险的命令(例如 KEYS、FLUSHDB、FLUSHALL 等)可以限制管理权限\
  • 1

\

RESP3协议

  • 在 RESP 2 中,客户端和服务器端的通信内容都是以字节数组形式进行编码的,客户端需要根据操作的命令或是数据类型自行对传输的数据进行解码,增加了客户端开发复杂度\
  • RESP 3 直接支持多种数据类型的区分编码,包括空值、浮点数、布尔值、有序的字典集合、无序的集合(利用不同的数据类型进行区分)\

总结

  • 稳定性有待考证

\

\

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Spring Cloud / Alibaba 微服务架构

发表于 2021-11-25

这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战。

上篇文章我们介绍了SpringCloud Gateway Filter的相关概念,并解读了一个全局过滤器——RouteToRequestUrlFilter的filter方法,这篇文章我们来介绍一下两个局部过滤器。

解读SpringCloud Gateway Filter(下)

全局过滤器是实现GlobalFilter, Ordered接口,局部过滤器是实现GatewayFilter, Ordered接口,然后再去实现对应的apply方法。

1、添加前缀的局部过滤器 PrefixPathGatewayFilterFacotry

可以看出来,这是一个局部过滤器的工厂,在工厂内实现了一个局部过滤器。有一个属性 PREFIX_KEY,是指明需要在url上增加的前缀。

接下来看下代码实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
scss复制代码public GatewayFilter apply(PrefixPathGatewayFilterFactory.Config config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
boolean alreadyPrefixed = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.GATEWAY_ALREADY_PREFIXED_ATTR, false);
if (alreadyPrefixed) {
return chain.filter(exchange);
} else {
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ALREADY_PREFIXED_ATTR, true);
ServerHttpRequest req = exchange.getRequest();
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, req.getURI());
String newPath = config.prefix + req.getURI().getRawPath();
ServerHttpRequest request = req.mutate().path(newPath).build();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, request.getURI());
if (PrefixPathGatewayFilterFactory.log.isTraceEnabled()) {
PrefixPathGatewayFilterFactory.log.trace("Prefixed URI with: " + config.prefix + " -> " + request.getURI());
}

return chain.filter(exchange.mutate().request(request).build());
}
}

public String toString() {
return GatewayToStringStyler.filterToStringCreator(PrefixPathGatewayFilterFactory.this).append("prefix", config.getPrefix()).toString();
}
};
}

alreadyPrefixed是boolean类型,用来校验一下是否已经添加前缀了,通过exchange.getAttributeOrDefault中的GATEWAY_ALREADY_PREFIXED_ATTR属性来判断,默认值为false。

接下来使用config的Prefix去构造新的路径,新的HttpRequest。先根据exchange.getRequest()获取请求对应的request,然后根据request获取到对应的url,然后在url上去添加一个前缀生成新的path,用这个新的path去构造新的ServerHttpRequest。

举个例子,假如有个简单的静态路由配置如下:

1
2
3
4
5
6
7
yaml复制代码spring:
cloud:
routes:
-id: sheep
uri: http//example/org
filters:
- PrefixPath=/mypath

假如现在有个请求/hello,命中了上方的配置,那么这个添加前缀的局部过滤器就会将这个请求变成:http://example/org/mypath/hello。

2、去除前缀的局部过滤器 StripPrefixGatewayFilterFactory

strip是跳过的意思,所以该过滤器是用来去掉前缀的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scss复制代码public GatewayFilter apply(StripPrefixGatewayFilterFactory.Config config) {
return new GatewayFilter() {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, request.getURI());
String path = request.getURI().getRawPath();
String newPath = "/" + (String) Arrays.stream(StringUtils.tokenizeToStringArray(path, "/")).skip((long)config.parts).collect(Collectors.joining("/"));
newPath = newPath + (newPath.length() > 1 && path.endsWith("/") ? "/" : "");
ServerHttpRequest newRequest = request.mutate().path(newPath).build();
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());
return chain.filter(exchange.mutate().request(newRequest).build());
}

public String toString() {
return GatewayToStringStyler.filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()).toString();
}
};
}

通过request.getURI().getRawPath()去获取原始的URI,然后根据配置的parts部分去执行.skip(config.parts)去掉前缀。parts是int类型,也就是指定一个数字,去掉几个前缀的部分。

然后将获取到的新path去构造一个新的request再进行转发。

举个例子,假如有个简单的静态路由配置如下:

1
2
3
4
5
6
7
8
9
yaml复制代码spring:
cloud:
routes:
-id: sheep
uri: http//example/org
predicates:
- Path=/name/**
filters:
- StripPrefix=2

如果这时候有个请求是/name/bar/foo命中了上方的配置,那么这个请求就会变成/foo,要注意的是,这个stripPrefix=2指的是去掉两个”/xxx”,而不是字符!

SpringCloud Gateway Filter 的执行流程

1、过滤器有优先级之分,Order越大,优先级就越低,越晚被执行。可以通过getOrder方法获取Order。

2、全局过滤器所有的请求都会执行,这是默认的行为,不需要做额外的操作,针对自定义的全局过滤器也是如此。

3、局部过滤器只有配置的请求才会执行,配置在路由配置中,之后文章会介绍如何配置才会使其生效。

自定义局部过滤器的步骤

1、自定义局部过滤器实现GatewayFilter, Ordered 这两个接口中的相应方法。

2、将自定义的局部过滤器加入到过滤器工厂中,并将工厂加入到Spring IOC容器中,工厂继承 AbstractGatewayFilterFactory 抽象类。

3、配置文件中配置过滤器,使其生效。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【dubbo学习笔记1】——分布式基础理论&dubbo概念

发表于 2021-11-25

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」

分布式基础理论

分布式系统是若干个独立计算机的集合,这些计算机对于用户来说就像单个相关系统

互联网发展,网站应用的规模不断扩大,常规的垂直架构无无法应对

应用架构的演变

单一架构

扩展不容易

协调开发不容易

垂直应用架构


把一个大应用拆成几个小应用


\

1.缺点,每一个应用都是完整的,包括界面,页面逻辑,无法实现分离

2.应用之间需要交互,不能完全独立,比如订单需要查询用户应用

分布式服务架构


把页面和核心业务逻辑服务

不同服务器不同tomcat之间怎么调用服务

分布式架构架构下最核心的是如何进行远程过程调用,如何拆分业务

分布式服务框架(RPC)帮我们解决这个问题

基于访问压力的调度中心,基于访问压力实时管理集群容量,提高集群利用率

决定rpc框架的通信速度

1.能否快速建立连接

2.传输数据的序列化和反序列化效率

dubbo

什么是dubbo?

Apache Dubbo(incubating)是一款高性能、轻量级的开源Java RPC框架,它提供了三个核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

什么是灰度发布?

比如需要做用户服务的升级,一共有100台机器,先让其中的20台使用新服务,80台使用旧服务,(20台用的没问题)然后慢慢过渡到所有机器使用新服务

通过配置路由规则,请求进来的时候一部分请求请求新服务,一部分请求旧服务,慢慢从旧服务过渡到新服务的过程

  • 面向接口代理的高性能RPC调用
  • 智能负载均衡
  • 服务自动注册与发现
  • 高度可扩展能力
  • 运行期流量调度
  • 可视化的服务治理和运维

注册中心

  • 容器启动的时候,服务提供者会向注册中心进行注册并且说明自己能够提供的服务
  • 服务消费者启动的时候就会从注册中心订阅它所需要服务
  • 服务提供者信息发生变更的时候,注册中心会和服务消费者会变更基于长连接通知,
  • 调度信息一定时间会定时统计发布到监控中心,服务消费者调用服务提供者的服务是一个同步的过程
  • 服务提供方有好几台相同的服务,会根据负载均衡算法进行选择

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…185186187…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%