HA(高可用)就像套娃,像胖子,剥掉一层还有一层

thbcm阅读(173)

文章转载自公众号:小姐姐味道

目前HTTP协议,乃至WebSocket协议,乃至采用了MQTT协议的WebSocket协议,都不可避免的使用了 Nginx 。所谓病从口入,祸从口出。作为入口,Nginx 承担的责任非常的重要。假如某个时刻不能用了,那可真是灾难。

如何保证 Nginx 的高可用呢?这是个问题。不论你用什么样的方案,到最后总是要归为单一,很让人苦恼。

所谓的高可用,无非两种方式。一种方式就是在组件自身上做文章,另外一种方式,就是加入一个中间层。我们通常希望在高可用的时候,同时还能够负载均衡,典型的猫和狗都想要,贪婪的很。

每当解决不了问题的时候,我们都会加入一个中间层,然后把希望寄托在这个新生的组件上。

如果这个中间层解决不了问题,我们就可以加入另外一个中间层。就这样一层套一层,到最后系统高可用架构就会变得非常复杂。

DNS保证高可用

第一种方式当然是要在 DNS 上做文章了。通过在 DNS 上,绑定多个 Nginx 的 IP 地址,即可完成高可用。不仅能够完成高可用,还能顺便完成负载均衡。

但这玩意有一个致命的问题,那就是故障的感知时间。

我们的浏览器在访问到真正的 Nginx 之前,需要把域名转化为真正的 IP 地址,DNS 就是干解析这个动作的,每次需要耗费20-20ms不等。

为了加快解析速度,一般都会有多级的缓存。比如浏览器就有 DNS 的缓存;你使用的 PC 机上也有这样的缓存;IPS 服务提供商,也会有缓存;再加上有的企业为了加速访问所自建的 DNS 服务器,中间的缓存层就更多了。

只有所有的缓存都不命中的情况下,DNS 才会查询真正的 IP 地址。所以,如果有一台 Nginx 当机了,这个故障的感知能力就会特别的差。总有一部分用户的请求,会落在这台已经死亡的机器上。

硬件保证高可用

我们前面说了。解决不了的问题,就可以加中间层,即使这个中间层是硬件,比如F5

这种架构一般的企业玩不起,只有那些采购有回扣有油水的公司,才会喜欢这个。互联网中用的很少,就不过多介绍了。

当然,F5同样有单点的问题。虽然硬件肯定要比软件稳定上一点,但是总归是一个隐患。就像 Oracle 无论再厉害,它还是有出问题的时候,到时候备机是必须的。

有的厂商在卖硬件的时候,推荐你一次买3个!为啥呢?这也有理由。

你的一台硬件正在服务,有两台备份机器。当你服务的这台机器出现问题时,就可以选取备份机中的其中一台作为主机,另一台依然是备机,集群还是高可用的。

这理由真让人陶醉。按照这个逻辑,碰到傻子,我可以卖出100台!

主备模式

硬的不行,就要来软的。采用主备的模式,使用软件来完成切换过程。

如图,使用keepalived组件,通过VRRP协议,即可完成最简单的高可用配置。

我们把DNS的地址绑定在VIP上,当正在服务的Nginx发生问题,VIP会发生漂移,转移到另外一台Nginx上。

可以看到,备份的Nginx,正常情况下是无法进行服务的,它也叫做影子节点,只有主Nginx发生问题的时候才有用。如果你的节点非常多,这种模式下,会有非常大的浪费。

除了浪费,还有一个非常大的问题。那就是,单台 Nginx 无论性能多么牛X,总是有上限的。当网卡的流量达到顶峰,接下来何去何从呢?

这种模式肯定是不满足需求的。

简单组合模式

这个时候,我们就可以配合 DNS 解析,以及主备模式做文章了。如下图,DNS 解析到两个 VIP 上,VIP 本身也做了高可用。这样就能够缩短故障时间,同时也能够保证每个组件的高可用。

这种架构模式思路是非常清晰的,但依然存在影子节点的浪费。

LVS+KeepAlived+Nginx

LVSLinux Virtual Server 的简称,也就是 Linux 虚拟服务器。现在 LVS 已经是 Linux 标准内核的一部分,从 Linux2.4 内核以后,已经完全内置了 LVS 的各个功能模块,无需给内核打任何补丁,可以直接使用 LVS 提供的各种功能。

LVS 工作在 OSI 模型的第4层:传输层,比如 TCP/UDP,所以像7层网络的 HTTP 协议,它是识别不出来的。也就是说,我们不能拿 HTTP 协议的一些内容来控制路由,它的路由切入层次更低一些。

如下图,LVS 架设的服务器集群系统有三个部分组成:

  • 最前端的负载均衡层,用 Load Balancer 表示
  • 中间的服务器集群层,用 Server Array 表示
  • 最底端的数据共享存储层,用 Shared Storage 表示

DR(直接路由)模式可将响应数据包直接返回给用户浏览器,避免负载均衡服务器网卡带宽成为瓶颈,是目前采用最为广泛的方式(数据不详,fullnat模式使用也比较广泛)。

所以,配合 DNS 的负载均衡,加上LVS的负载均衡,可以实现双层的负载均衡和高可用。

如图,DNS 可以将请求绑定在 VIP 上。由于 LVS DR 模式的效率非常高,网卡要达到瓶颈也需要非常大的请求量(只有入口流量才走LVS),所以一般通过 LVS 做 nginx 的负载均衡就足够了。如果 LVS 还有瓶颈,那么就可以在 DNS 上再做文章。

还有哪些挑战?

其实,我们上面谈到的这些方案,大多数是在同机房的。如果在多个机房,如何让用户选择最快的节点、如何保证负载均衡,又是一个大的问题。另外,你可以看到数据包经过层层的转发和协调,还有多种负载均衡算法参与其中,如何保持会话,也是一个挑战。一般的,四层会话会通过 IP 地址去实现,七层会话会通过 cookie 或者头信息等去实现。

开发人员一般情况下接触不到这么入口级的东西,但一旦遇到了,可能会受忙脚乱。本文是xjjdog根据一些即有的经验进行整理,希望你在公司需要一些高可用方案的时候,能够助你一臂之力。

什么叫方案?你只需要 当时 把你的领导哄好,让他感觉很认同的样子就行了。至于要不要做,具体怎么做,那都是后面的事。君不见,扯了这么半天,很多企业其实一个nginx,就可以走天下。

以上就是W3Cschool编程狮关于HA(高可用)就像套娃,像胖子,剥掉一层还有一层的相关介绍了,希望对大家有所帮助。

Google鼓励的13条代码审查标准

thbcm阅读(186)

文章分享自公众号: DevOps云学堂

如何在代码审查方面表现出色

在本文中,我们将简要介绍13种代码审查标准,这些标准可以极大地帮助改善软件的运行状况并保持开发人员满意。

顾名思义,代码审查是一个过程,其中一个或多个开发人员审查或筛选另一位开发者(作者)编写的代码,以确保:

  • 代码没有任何错误或问题。
  • 符合所有质量要求和标准。
  • 代码执行了预期的测试。
  • 合并后,它将使代码库的运行状况保持更好。

这就是为什么代码审查是软件开发的关键部分的原因。代码审阅者充当代码库管理员,负责确定代码是否处于要成为代码库的一部分并进入生产的状态。

Google以其卓越的技术而著称,它们具有有效的代码审查标准,这些标准似乎突出了审查代码时要记住的一些要点。在Google,代码审查的主要目的是确保Google代码库的整体代码运行状况随着时间的推移而不断改善。

这是您在查看更改列表时要记住的事项列表。

审查标准

1.该代码改善了系统的整体运行状况

每个更改列表(Pull Request)都会改善系统的整体运行状况。想法是,由于进行了如此小的改进,每次合并后,软件或代码库的运行状况都会得到改善。

2.快速的代码审查,响应和反馈

首先,不要延迟推送(合并)更好的代码。不要指望代码是完美的。如果它的状况可以改善系统的整体运行状况,则请推送。

“这里的关键是没有’完美’的代码,只有更好的代码。”

如果您不在一项重点任务的中间,那么请在代码完成后立即进行检查;但是,一个工作日是响应拉取请求所需的最长时间。预计变更列表将在一天之内获得多轮的部分/完整代码审查。

3.在代码审查期间进行教育和启发

通过尽可能共享知识和经验,在代码审查期间提供指导。

4.审查代码时遵循标准

始终牢记,编码标准此类文档是代码审查期间的绝对权威。例如,要在制表符和空格之间保持一致性,可以引用编码约定。

5.解决代码审查冲突

通过遵循样式指南和编码标准文档中商定的最佳实践,并寻求其他在产品领域具有更多知识和经验的人的建议,来解决冲突。根据严重性,处理冲突有所不同。

如果您的评论是可选的或次要的,请在评论中进行说明,然后由作者决定是解决还是忽略它们。作为代码审阅者,您至少可以建议在没有样式指南或编码标准的情况下,更改列表(请求)与其余代码库保持一致。

6.演示UI更改是代码审查的一部分

如果更改列表(Pull Request)更改了用户界面,则除了代码查看之外,还必须进行演示以确保外观上的所有外观均符合预期并与模拟匹配。

对于前端变更列表(Pull Request),始终建议进行演示/演练,或确保变更列表还包括必要的UI自动化测试,以验证添加/更新的功能。

7.确保代码审查伴随所有测试

除非紧急情况,否则拉取请求(更改列表)应伴随所有必要的测试,例如单元,集成,端到端等。

紧急情况可能是需要尽快修复的错误或安全漏洞,以后可以添加测试。在这种情况下,请确保创建了适当的问题,并确保有人在完成热修复或部署后立即拥有所有权才能完成。

没有足够的理由跳过测试。如果由于时间限制,某些目标有无法实现的风险,那么解决方案不是跳过测试,而是要对可交付成果进行范围界定。

8.专注时,不要打扰自己进行代码审查

如果您正处于重点工作中,请不要打扰自己,因为这可能需要很长时间才能恢复正常。换句话说,打断专注的开发人员所付出的代价比让开发人员等待代码审查要高得多。在计划的休息时间(例如午餐,咖啡等)之后进行代码检查。

期望并非总是在同一天完成并合并整个代码审查过程。重要的是迅速给作者一些反馈。例如,您可能无法进行完整的检查,但是您可以快速指出一些可以研究的内容。这将极大地减少代码审查期间的挫败感。

9.复习一切,不要做任何假设

查看分配给您检查的每一行代码。不要对人工编写的类和方法做任何假设,并且应该确保您了解代码在做什么。

确保了解您正在检查的代码。如果没有,请进行澄清或要求代码演练/解释。如果您有部分代码不具备审阅的资格,请确保还有其他合格的开发人员可以审阅代码的那些部分。

10.回顾代码时要顾全大局

从更广泛的背景来看变化通常是有帮助的。例如,更改了文件,并添加了四行代码。不要只查看四行代码;相反,请考虑查看整个文件并检查新添加的内容。它们会降低现有代码的质量,还是会使现有功能成为重构的候选对象?

如果不在函数/方法或类的上下文中检查此类简单的添加项,则随着时间的流逝,您将继承一个类,该类是不可维护的,超级复杂的,难以测试的,无法完成的所有工作,并且难以扩展或重构。

请记住,随着时间的推移,很少的改进加起来就可以产生具有最少数量缺陷的优质产品,同样,随着时间的流逝,轻微的代码降级或技术负担也会加重并导致产品难以维护和扩展。

11.认可并鼓励代码评审期间的良好工作

如果您在变更列表中看到了一些不错的东西,请别忘了喊出作者的出色作品并鼓励他们。这是我个人以前从未做过的事情。代码审查的目的不仅应该是发现错误,还应该鼓励和指导开发人员所做的出色工作。

12.在代码审查中要谨慎,尊重,友善和清晰

至关重要的是,在代码审阅期间,您要善良,清晰,礼貌和尊重,同时也要对作者非常清楚和乐于助人。查看代码时,请确保对代码而不是开发人员做出评论。

13.解释您的代码审查注释,并牢记范围

每当代码审阅意见提出替代方法或进行标记时,至关重要的是要解释原因并根据您的知识和经验提供示例,以帮助开发人员了解您的建议将如何帮助提高代码质量。

当建议修复或更改时,请在如何指导作者修复代码方面找到适当的平衡。例如,我很欣赏指导,解释,一些提示或建议,而不是整个解决方案。

以上就是W3Cschool编程狮关于Google鼓励的13条代码审查标准的相关介绍了,希望对大家有所帮助。

Java版本新发现:JDK15的14个新特性和变化

thbcm阅读(213)

Java Development Kit 15Oracle 对 Java SE(标准版)下一个版本的实现,已于8月初进入了发布候选阶段。JDK 15 的亮点包括文本块、隐藏类、外部内存访问 API ,以及密封类和记录的预览。

OpenJDK 15的新特性和变化包括:

1)外内存访问API(foreign-memory access API)

外内存访问 API 的第二个孵化器,它将使 Java 程序能够安全和有效地访问 Java 堆之外的外部内存。此 API 能够操作各种类型的外部内存,如本机、持久和托管堆。有许多 Java 程序是访问外部内存的,比如 IgniteMapDB。该API将有助于避免与垃圾收集相关的成本以及与跨进程共享内存以及通过将文件映射到内存来序列化和反序列化内存内容相关的不可预测性。该Java API目前没有为访问外部内存提供令人满意的解决方案。但是在新的提议中,API不应该破坏JVM的安全性。在JDK 14中,这个功能正在经历早期的孵化阶段,在JDK 15中还提供了改进。

2)密封类(sealed classes)的预览

与接口一样,密封类也限制其他类或接口可以扩展或实现它们。这个特性的目标包括——允许类或接口的作者来控制哪些代码负责实现、提供了比限制使用超类的访问修饰符声明方式更多选择,并通过支持对模式的详尽分析而支持模式匹配的未来发展。

3)相关支持的删除

删除对Solaris/SPARCSolaris/x64Linux/SPARC端口的源代码和构建支持,而在JDK 14中不赞成删除这些端口,但可在将来的版本中删除它们。许多正在开发的项目和功能(如Valhalla、Loom和Panama)需要进行重大更改以适应 CPU 架构和操作系统特定代码。放弃对SolarisSPARC端口的支持将使OpenJDK社区的贡献者加快开发新特性,从而推动平台向前发展。近年来,SolarisSPARC都被Linux操作系统和Intel处理器所取代。

4)记录结构(Records)

记录是充当不可变数据的透明载体类,在 JDK 14 中作为早期预览进行了首次调试之后,它将被包含在 JDK 15 的第二个预览版本中。计划的目标包括设计一个面向对象的结构,表达一个简单的聚合值,帮助程序员关注建模不可变的数据,而不是扩展行为,自动实现数据驱动的方法,如如equalsassessors,并保留 Java 中长期存在的原则,如名义类型和迁移兼容性 。Records(记录)可以被认为是名义元组。

5)数字签名算法

基于Edwards-Curve数字签名算法(EdDSA-Edwards-Curve Digital Signature Algorithm)的加密签名。EdDSA是一种现代的椭圆曲线方案,具有 JDK 中现有签名方案的优点。EdDSA将只在SunEC提供商中实现。EdDSA与其他签名方案相比,具有更高的安全性和性能,因此备受关注;它已经在OpenSSLBoringSSL等加密库中得到支持。

6)套接字的更新实现

通过将java.net.datagram.Socketjava.net.MulticastSocket API的底层实现替换为更简单、更现代的实现来重新实现遗留的DatagramSocket API。新的实现:

1.易于调试和维护;

2.与Project Loom中正在探索的虚拟线程协同。

新的计划是 JDK Enhancement Proposal 353的后续,该方案重新实现了遗留的套接字 API。java.net.datagram.Socketjava.net.MulticastSocket的当前实现可以追溯到 JDK 1.0,那时 IPv6 还在开发中。因此,当前的多播套接字实现尝试调和 IPv4 和 IPv6 难以维护的方式。

7)禁用偏向/偏置锁定

在默认情况下禁用偏向锁定,并弃用所有相关命令行选项。目标是确定是否需要继续支持偏置锁定的高维护成本的遗留同步优化,HotSpot虚拟机使用该优化来减少非竞争锁定的开销。尽管某些 Java 应用程序在禁用偏向锁后可能会出现性能下降,但偏向锁的性能提高通常不像以前那么明显。

8)instanceof模式匹配

此为第二个预览版,之前是 JDK 14 中的首次预览。模式匹配允许程序中的通用逻辑(主要是有条件地从对象中提取组件)得到更精确的表达。像Haskellc#这样的语言已经包含了模式匹配,因为它的简易性和安全性。

9)隐藏类(Hidden classes)

即不能被其他类的字节码直接使用的类,是为在运行时生成类并通过反射间接使用类的框架使用的。隐藏类可以定义为访问控制嵌套的成员,并且可以独立于其他类卸载。该提议将通过支持一个标准 API 来定义不可发现且生命周期有限的隐藏类,从而提高 JVM 上所有语言的效率。JDK 内部和外部的框架将能够动态生成类,而这些类可以定义隐藏类。许多构建在 JVM 上的语言都依赖动态类生成来获得灵活性和效率。这个提议的目标包括:允许框架将类定义为无法发现的框架实现细节,这样它们就不能被其他类链接,也不能通过反射被发现;支持扩展带有不可发现类的访问控制嵌套;并支持主动卸载不可发现的类,因此框架可以根据需要灵活地定义多个类。另一个目标是弃用非标准API misc.Unsafe::defineAnonymousClass,目的是为了在将来的版本中删除。另外, Java 语言不会因为这个建议而改变。

10)ZGC产品化

在这个提案下,Z垃圾收集器(ZGC-Z Garbage Collector)将从一个实验特性升级为产品。 ZGC 集成到2018年9月发布的JDK 11中,是一个可扩展的、低延迟的垃圾收集器。 ZGC 是作为一种实验性的功能引入的,因为 Java 开发人员决定应该小心地、逐步地引入这种规模和复杂性的特性。从那时起,添加了许多改进,从并发类卸载、未使用内存的解除提交、对类数据共享的支持到改进的 NUMA 感知和多线程堆预处理。此外,最大堆大小从4TB增加到16TB。支持的平台包括LinuxWindowsMacOS

11)文本块

JDK 14JDK 13中都预览版文本块,它旨在简化编写 Java 程序的任务,方法是简化表达跨越几行源代码的字符串,同时在常见情况下避免转义序列。文本块是一个多行字符串文字,它可以避免使用大多数转义序列、自动以可预测的方式格式化字符串,并在需要时为开发人员提供对格式的控制。文本块建议的一个目标是增强 Java 程序中表示用非J Java 语言编写的代码的字符串的可读性。另一个目标是通过规定任何新构造都可以将相同的字符串集表示为字符串文字,解释相同的转义序列,并以与字符串文字相同的方式操作,从而支持字符串文字的迁移。OpenJDK 开发人员希望添加转义序列来管理显式的空格和换行控件。

12)LPT GC正式可用

Shenandoah低暂停时间(low-pause-time)垃圾收集器将成为一个生产特性,不再处于实验阶段。它在一年前被集成到JDK 12中。

13)删除Nashorn

2014年3月在jdk8中首次亮相的Nashorn被移除,由于其被GraalVM等技术淘汰。OpenJDK 15 提议要求删除Nashorn APIs和用于调用Nashornjjs命令行工具。

14)RMI Activation进入不推荐期

不推荐 RMI 激活机制,以便将来删除。RMI 激活机制是 RMI 中一个过时的部分,自 Java 8 以来一直是可选的。RMI 激活机制增加了持续的维护负担。RMI 的其他部分将不被弃用。

JDK 15的早期访问版本可以在java.jdk.net网站中找到。JDK 15将是一个短期的特性发布,根据 Oracle 的6个月发布周期,它将被支持6个月。下一个长期支持(LTS-long-term support)版本是JDK 17,预计将于2021年9月发布,它将获得几年的支持。当前的LTS版本是JDK 11,是于2018年9月发布。

以上就是W3Cschool编程狮关于Java版本新发现:JDK15的14个新特性和变化的相关介绍了,希望对大家有所帮助。

Python 3中被忽视了的三大重要功能

thbcm阅读(168)

文章转载自公众号:读芯术

Python 3 上线已有一段时间,大多数开发人员(特别是那些首次编程的人)已经在使用它了。

不过,你确定已经对 Python 3 研究透彻了吗?事实上,其中还有许多新功能对大多数人来说仍处于未知状态。本文就将讨论 Python 3 中三个鲜为人知但非常有用的功能,我在其他语言中接触到并爱上了这些功能,它们的加入使得使 Python 3 体验更佳。

枚举

枚举是在 JavaSwift 中常使用到的功能,我把它扩展到Python中。在 Python 中创建枚举非常简单,在 Python 3 之前的版本中也可使用(尽管功能更受限):

from enum importEnum
             classState(Enum):
        AIR=0
        LAND=1
        SEA=2
        myState =State.AIR
             # Prints 0
      print(myState.value)
      # Prints AIR
      print(myState.name)

在上述代码中,可以看到通过构造一个类并使其成为枚举的子类,便可以轻松构造枚举。在这里只需在下面的行中定义每个状态。就我而言,我有 AIR、LAND、SEA。

Python 3 的新功能是运行.value.name。这能获得与状态相关联的整数值或与之相关联的字符串。

在上面的代码中,输入State.LAND.name 返回LAND,因此功能不仅仅是一个整数枚举。

当需要描述性常量时,代码中的枚举类型十分有用。例如,与其检查状态是 0 还是 1 ,不如检查它是否是State.MOVINGState.STATIONARY状态。常量可能会改变,如果有人在看你的代码,MOVING比 0 更有意义,代码的可读性也会大大提高。

格式

Python 3.6中添加的fstring是格式化文本的一种很好的方式。它们的可读性强,而且不容易出错。fstringPython以前使用的格式更易读。以下是使用格式的示例:

name = Brett 
       blog_title = Medium 
             # Hi, my name isBrett and I am writing on my Medium blog.
       a ="Hi, myname is {} and I am writing on my {} blog.".format(name,blog_title)

如上所示,在字符串中打入括号,然后按顺序列出每个变量的名称。相同代码任务很多,但fstring极大地增加了代码的可读性,尤其是类似于用Swift格式化字符串。

name = Brett 
       blog_title = Medium
             # Hi, my name isBrett and I am writing on my Medium blog.
       a =f"Hi, myname is {name} and I am writing on my {blog_title} blog."

为了完成这个更简洁的字符串,只需在引号前面加上字母 f ,然后将变量或数据直接放入括号中,而不是使用空括号。由于变量本身是写在括号内的,所以不必计算格式中写入的项的数量来确定变量的位置,变量就在应在的位置。

相比于字符串连接或格式化字符串,fstring可以生成更可读、更可靠的代码。

数据类

数据类可能要比上述所谈更为晦涩难懂,所以我将简要地解释一下。我在Kotlin中逐渐喜欢上了数据类,因此很想在Python中使用它们。

数据类实际上是一个类,其唯一目的是保存数据的类。类将具有可以访问和写入的变量,但上面没有额外的逻辑。

假设你有一个程序,在不同的类之间传递一个字符串和一个数字数组。使用pass(str,arr)这样的方法也可行,最好是创建一个只包含字符串作为字段和数组的数据类。通过创建一个数据类,你所做的将更加清晰,单元测试也将更加容易。

下面这个示例将说明如何创建一个表示三维向量的简单数据类,但这可以很容易地扩展为表示不同数据的任意组合:

from dataclasses import dataclass
             # Definedataclass
           @dataclass
           classVector3D:
              x: int
              y: int
              z: int


           # Create a vector
           u =Vector3D(1,1,-1)
             # Outputs: Vector3D(x=1,y=1, z=-1)
           print(u)

在这里,你可以看到数据类的定义与声明普通类非常相似,只是我们先用了@dataclass,然后每个字段的名称都是name:type

虽然我们创建的Vector3D功能有限,但是数据类的目的只是提高效率并减少代码中的错误,传递Vector3D比传递int变量要好得多。

以上就是W3Cschool编程狮关于Python 3中被忽视了的三大重要功能的相关介绍了,希望对大家有所帮助。

Go 1.15.1和Go 1.14.8发布:一个安全问题修复

thbcm阅读(162)

Go Team 发布了 Go 1.15.1Go 1.14.8,以解决最近报告的安全问题。建议所有受影响的用户更新到以下版本之一(如果不确定哪个版本,请选择 Go 1.15.1)。

如果处理程序未明确设置 Content-Type 响应头,则 net/http/cginet/http/fcgi包将默认设置为 “text/html”,如果攻击者可以控制响应的内容,则可能会导致跨站点脚本漏洞。

现在修改改为根据第一次 Write 的内容通过 http.DetectContentType 来设置Content-Type 响应头,这与 net/http 包的行为一致。

尽管这可以保护某些验证上传文件内容的应用程序,但未在攻击者控制的任何文件上明确设置 Content-Type 响应头是不安全的,应避免出现。也就是说,你应该总是明确设置 Content-Type 响应头。

RedTeam Pentesting GmbH 报告此问题。此问题为CVE-2020-24553,相应的 issue 见:github.com/golang/go/issues/40928

以前偶然发现的 Java Bug(JDK 9及之前仍未修复)

thbcm阅读(168)

文章转载自公众号:叨叨软件测试 作者: 蒋李恒

背景

15年在中信银行做持续集成时,由于当时的项目是基于三方采购的 Java 配置开发平台做的,平台自己基于 Ant 插件实现了增量和热部署。

其中有几个项目在持续集成部署时,经常发现 Linux 平台部署成功后(Windows 不会出现,Linux 也是偶发现象),新版本代码并没有生效(反编译 class)。

起初我是在本地 windows 上跟踪调试基于 Ant 插件的代码,但始终重现不了(最后测试发现 Windows 无此 Bug)。

后来,通过分析代码逻辑,其中有段逻辑是通过文件的最后修改时间(File.lastModified())来判断要不要覆盖部署的,最后通过单测发现,是由于 JavaFile.lastModified() 方法在 WindowsLinux/Unix平台获取的精度不一样导致的,Windows 精度为毫秒,而 Linux/Unix 只能到秒(JDK Bug:JDK-8177809)。

所以也解释了,为什么是偶发现象,文件修改时间如果判断的两个值正好跨秒时,部署就是成功的,否则失败。

Bug 重现

测试代码:FileTest.java

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;


public class FileTest {
    private static final long LM = 1599276952718L;


    public static void main(String[] args) throws IOException {
        // java版本号
        System.out.println("Java Version:" + System.getProperty("java.version"));


        File f = new File("test.txt");
        f.createNewFile();


        // 设置最后修改时间
        f.setLastModified(LM);


        // 获取修改时间,存在 bug
        System.out.printf("Test f.lastModified [%s]: %b\n",
f.lastModified(), f.lastModified() == LM);
        // 格式化输出,正确不存在 bug
        System.out.printf("Test f.lastModified DateFormat [%s]\n",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(f.lastModified()));


        // Files.getLastModifiedTime() 获取修改时间,同样存在 bug
        System.out.printf("Test Files.getLastModifiedTime [%s]: %b\n",
Files.getLastModifiedTime(f.toPath()).toMillis(),
(Files.getLastModifiedTime(f.toPath()).toMillis() == LM));
        // 格式化输出,正确不存在 bug
        System.out.printf("Test Files.getLastModifiedTime DateFormat [%s]\n",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.sss").format(f.lastModified()));


        f.delete();
    }
}

命令行下编译、执行:

# 编译执行
$ javac FileTest.java && java FileTest 

Windows 执行结果

Windows 平台不存在此 Bug。

# 编译执行
$ javac FileTest.java && java FileTest
Java Version:1.8.0_202
Test f.lastModified [1599276952718]: true
Test f.lastModified DateFormat [2020-09-05 11:35:52.052]
Test Files.getLastModifiedTime [1599276952718]: true
Test Files.getLastModifiedTime DateFormat [2020-09-05 11:35:52.052]

Mac 执行结果

JDK 8 最新版本,目前仍然没有修复该问题。

# 编译执行
$ javac FileTest.java && java FileTest
Java Version:1.8.0_261
Test f.lastModified [1599276952000]: false
Test f.lastModified DateFormat [2020-09-05 11:35:52.052]
Test Files.getLastModifiedTime [1599276952000]: false
Test Files.getLastModifiedTime DateFormat [2020-09-05 11:35:52.052]

Linux 执行结果

# 编译执行
$ javac FileTest.java && java FileTest
Java Version:1.8.0_171
Test f.lastModified [1599276952000]: false
Test f.lastModified DateFormat [2020-09-05 11:35:52.052]
Test Files.getLastModifiedTime [1599276952000]: false
Test Files.getLastModifiedTime DateFormat [2020-09-05 11:35:52.052]

官方 Bug 链接

JDK10 已修复,但是之前版本目前仍然未修复。

  • bugs.openjdk.java.net/browse/JDK-8177809

以上就是W3Cschool编程狮关于以前偶然发现的 Java Bug(JDK 9及之前仍未修复)的相关介绍了,希望对大家有所帮助。

Linux/Unix 效率工具:快速路径切换 z 命令

thbcm阅读(152)

文章转载自公众号:叨叨软件测试

简介

z 是一个开源的 Linux 快速路径切换工具(类似工具还有z.luaautojumpfasd)。通过 Frecency 机制对日常访问的路径进行 Frecent 权重计算,z 会帮你切换到所有匹配正则关键字的路径中权重值最高的那条路径。

Frecency 是由 Mozilla 开发,将访问的频率和新近度(上一次访问到现在的时间差)合并为一个度量的启发式方法。

详细内容见:github.com/rupa/z

安装

# /usr/local 安装
$ cd /usr/local
$ sudo git clone https://github.com/rupa/z.git
$ sudo chmod +x z.sh


# zsh 配置变量
$ echo '. /usr/local/z/z.sh' >> ~/.zshrc
$ source ~/.zshrc


# bash 配置变量
$ echo '. /usr/local/z/z.sh' >> ~/.bash_profile
$ source ~/.bash_profile


# 安装 manpage
$ cp z.1 /usr/local/share/man/man1


# 验证安装
$ z -h
$ man z

使用

技巧z 命令可以使用 tab 来进行补齐,以提高切换效率。

# 查看帮助
$ man z
或
$ tldr z


# 显示记录的路径
$ z


# 切换到一个名字带有 "foo" 的路径
$ z foo


# 切换到一个名字带有 "foo" 并且后面带有 "bar" 的路径(例:fooesbar):
$ z foo bar


# 切换到名字带有 "foo" 并且拥有最高访问次数的路径
$ z -r foo


# 切换到最近使用的名字带有 "foo" 的路径
$ z -t foo


# 列出在 z 的数据库中名字带有 "foo" 的路径
$ z -l foo


# 将当前路径从 z 的数据库中移除
$ z -x .

以上就是W3Cschool编程狮关于Linux/Unix 效率工具:快速路径切换 z 命令的相关介绍了,希望对大家有所帮助。

(Add sumOf)一道字节前端原题

thbcm阅读(147)

文章转载自公众号:蓝色的秋风

前言

最近学弟去面了字节跳动,但是由于面试经验少,面试的时候紧张了,一时之间没有写出来,之后来我交流了一下。那我就来分析分析这道题目。

正文

这题的规则是这样的

给定有一个 Add 函数,要支持以下形式的调用


Add(1)(2)(3).sumOf(); // 输出 6
Add(1,2)(3)(4).sumOf(); // 输出 10
Add(1,2,...)(3)(4)(...).sumOf();  // ...

拿到这种题目,我先来说说我自己的做题流程,一般会去找它最简单的形态。我们一步一步来拆解。

先去掉 sumOf() 变成了以下形态

Add(1,2,...)(3)(4)(...)

嗯….有点熟悉…但是还是有点复杂,那我们再去掉无限调用这个限制。

Add(1,2,...)(3)(4)

唔,还是有点难呀…没关系,再砍, 不要传入多个参数。

Add(1)(2)(3)

有….有….有那味了….这….这不就是柯里化吗….

有些小朋友可能没有听过,对于大朋友而言耳熟能详,融会贯通。

我们还是来介绍一下。

在《javascript高级程序设计》这本书中有如下介绍:

与函数绑定紧密相关的主题是函数柯里化,它用于创建已经设置好的一个或者多个参数的函数。函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。

我们来写写看:

function Add(x) {
 return function (y) {
  return return functio (z) {
   return x + y + z;
  }
 }
}
// 简洁写法
const Add = x => y => z => x+y+z;

执行一下

Add(1)(2)(3) // 6 

是我们要的那味~

那么我们既然已经写出了这个形态,我们就一步一步反推。

这个时候千万别紧张,我们从最低级的形态出发,写出一个最基本的形态,能够有效地帮助我们建立自信心,吃下定心丸,按照这种方式,哪怕我们最终没有写出完美的结果,让面试官看到你思考解题的过程,也是一种加分。

好,接着说~

那我们接下来需要实现这个样子。

Add(1,2,...)(3)(4)

传入参数不止一个

我们知道,对于不确定参数个数,我们可以使用 arguments 这个对象来获取到所有的入参,但是 arguments 不是一个 Array,但是我们可以使用 ES6 中的 Spread syntax展开语法)去将他变成一个数组。表演继续。

function Add() {
 const nums = [...arguments];
 return function() {
  nums.push(...arguments);
  return function() {
   nums.push(...arguments);
   return nums.reduce((a, b) => a + b);
  }
 }
}

nice!已经离我们最终的形态越来越近了。接下来是这个函数能够无限的进行调用。

Add(1,2,...)(3)(4)(...)

那么怎么样才能无限调用呢?没错,用递归。

function Add() {
 const nums = [...arguments];
 function AddPro() {
  nums.push(...arguments);
    return AddPro;
 }
 return AddPro;
}

嗯,其实我们写到这里发现了… 由于是无限递归,我们没办法确定最后一次函数调用,因此我们需要最后显式调用一个结束的方法来打印出最后的数据。

很自然地,我们可以在 AddPro 添加一个方法 sumOf 来解决这个问题。

学弟就是卡在这里地方,被函数添加上一个方法搞懵了。你是否知道呢?

function Add() {
 const nums = [...arguments];
 function AddPro() {
  nums.push(...arguments);
    return AddPro;
 }
 AddPro.sumOf = () => {
  return nums.reduce((a, b) => a + b);
 }
 return AddPro;
}

好啦好啦,结束啦。

等等

在最后,我再来补充一种方案,function 不仅可以继续挂载 function ~ 还可以挂载变量哦~

function Add() {
 if (!Add.nums) {
   Add.nums = [];
  }
  Add.nums.push(...arguments);
  return Add;
}
Add.sumOf = () => {
 return Add.nums.reduce((a, b) => a + b);
}

我们总结一下,小小的面试题涉及到的基础知识。

闭包、递归、作用域、函数与对象

基础就是基础,永远是你爸爸,掌握好基础,以不变应万变。

后记

也许你觉得这题有点简单,通过简单的重复练习就能轻松记住,但是最主要的是思路,很多事情都是一样,掌握事情的方法和方向是最重要的。毕竟淘宝也不是一蹴而就的~ 但是只要方向正确了,都会好起来的。

以上就是W3Cschool编程狮关于(Add sumOf)一道字节前端原题的相关介绍了,希望对大家有所帮助。

webpack 构建进度条 ProgressPlugin 源码剖析

thbcm阅读(171)

文章转载自公众号:墨筝

前言

我们在使用 webpack 的时候经常会用到 webpackbar 或者 progress-bar-webpack-plugin 之类的 webpack 插件通过进度条等方式来展示 webpack 的构建进度,以提升构建过程中的反馈体验。对于不同的插件来说,它们只是进度条的 UI 展示形式不同而已,但最核心的 webpack 构建的实时进度的数据来源却是一致的,均由 webpack 内部的 ProgressPlugin 这个插件提供。下面我会结合源码来讲解该插件是如何计算 webpack 的构建进度并将进度数据暴露给第三方的进度条插件。在阅读下文之前可以试着问下自己:如果是你,你会如何计算 webpack 的构建进度。

构建进度的计算

该插件主要根据 webpack 的构建阶段来定义当前进度值。webpack 的构建过程分为很多不同的阶段,在每个阶段 webpack 都暴露了对应的事件钩子。ProgressPlugin 正是通过这些事件钩子对每个阶段都定义了一个基础进度值,代码如下所示:

上述代码中的 interceptHook 方法可以先忽略,这个后续会提到。 通过上述代码你会发现 ProgressPlugincompiler 中的每个钩子都设置了一个指定的进度值。但这些进度值还不够细致到反映 webpack 的详细构建过程,中间还差了 0.06 到 0.69 以及 0.69 到 0.95 两个阶段的数值。webpack构建的具体执行过程主要在 compilation 中,这两个阶段的数值由 compilation 的钩子填充。

0.06~0.69

update 方法的调用由 compilation 的钩子触发,如下所示:

这个阶段的主要工作是 module,entry 以及 dependencies 的处理和构建。换个角度从 ProgressPlugin 给该阶段设置的进度值来看,这部分工作也是 webpack 最耗时的地方。

0.69~0.95

从上述代码中可以看出这个间隔段就完全是根据 compilation 的 hooks 来计算和指定当前的构建进度值,从hook 的描述中可以看出这个阶段主要是 module, chunk 以及assets等资源的优化工作。 基本上整个 webpack 构建过程的进度值就是根据上述中的 compilercompilation 的 hooks 来设置的。

进度数据的透出

webpack 的构建进度确定之后剩下的任务就是将进度数据透出给第三方的进度条插件进行展示。要完成该任务需要ProgressPlugin完成两件事情,一是提供回调函数的切入口;二是需要能在对应的 hook 节点执行该回调函数进行进度的百分比值的传入。以下是这两点的实现原理

回调函数

ProgressPlugin 定义了 handler 函数来作为回调函数切入,代码如下所示:

hook 劫持

hook 劫持的实现非常简单,主要利用 webpack hook 原生提供的intercept方法,前文中提到的interceptHook方法只是对于 intercept 方法的封装,示例代码如下:

结语

webpack 构建的进度条实现原理就是如此简单,给每个构建阶段对应的 hook 设置一个进度值,然后通过 handler 回调和 hook 劫持切入到构建环节将进度信息传入回调函数,最终第三插件通过 handler 获取到进度值后将其展示出来。 webpack 的很多其他功能其实也没有想象中的那么复杂和高大上,通过阅读其源代码了解其实现原理后,你很可能会一拍大腿,大叹一声原来如此,这本身就是一件挺有意思的事儿。

以上就是W3Cschool编程狮关于webpack 构建进度条 ProgressPlugin 源码剖析的相关介绍了,希望对大家有所帮助。

do-while,Java中容易被忽略的语句

thbcm阅读(159)

文章转载自公众号:Java中文社群

最近在看 Java 的基础知识,其中有部分是关于循环的,在 Java 中,循环的语法总共分为 3 种:forwhiledo-while,如下图所示:

但我惊奇的发现,在之前的职业生涯中(11 年),竟从未用过 do-while(尴尬),于是问了群里的小伙伴,发现也是鲜有人用。

do-while 语法分析

我们先来了解一下 do-while 的语法:

do {
     // statements
} while (expression);

注意:最后一个分号不能省略,否则会提示编译出错

它的执行流程如下图所示:

那它究竟在什么地方用呢?

do-while 使用场景

在我多次的搜索和请教下,终于找到了两个相对满意的使用场景,接下来一起来看。

使用场景一:抢票

对于抢票业务来说,无论三七二十一,先抢了再说,然后在判断是否抢票成功,如果抢票成功则退出循环,否则继续执行抢票,实现的伪代码如下所示:

do {
    // 抢票代码...
} while (没抢到票);

思路提供者:贾鲲

使用场景二:进制转换

经过大量搜索发现,在 JDK 的源码中,也存在少量使用 do-while 的场景,比如 Integer 中进制转换,相关源码如下:

static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
    int charPos = len;
    int radix = 1 << shift;
    int mask = radix - 1;
    do {
        buf[offset + --charPos] = Integer.digits[val & mask];
        val >>>= shift;
    } while (val != 0 && charPos > 0);


    return charPos;
}

比如十进制转二进制就会执行此方法,在进制转换的业务中,无论任何情况,一定会至少执行一次进制转换的,因此这种业务场景就非常适合 do-while

总结

孔子说:温故而知新。当我们学完很多知识之后,回过头来再琢磨这些知识,发现很有趣,这就是知识的一大乐趣吧。本文我们介绍了两种 do-while 的使用场景,抢票和进制转换,你还知道哪些 do-while 的使用场景吗?

以上就是W3Cschool编程狮关于do-while,Java中容易被忽略的语句的相关介绍了,希望对大家有所帮助。

联系我们