php开源优化中文网 – 中国第一档PHP资源分享门户 php开源优化中文网是国内第一家以PHP资源分享为主的专业网站,也提供了PHP中文交流社区。面向PHP学习研究者提供:最新PHP资讯、原创内容、开源代码和PHP视频教程等相关内容 Fri, 11 Oct 2019 02:06:37 +0000 zh-CN hourly 1 https://wordpress.org/?v=5.2.3 如何阅读 Java 源码? /9/java/87282.html /9/java/87282.html#respond Fri, 11 Oct 2019 02:06:36 +0000 /?p=87282 阅读 Java 源码的前提条件:

1、技术基础

在阅读源码之前,我们要有一定程度的技术基础的支持。

假如你从来都没有学过Java,也没有其它编程语言的基础,上来就啃《Core Java》,那样是很难有收获的,尤其是《深入Java虚拟机》这类书,或许别人觉得好,但是未必适合现在的你。

比如设计模式,许多Java源码当中都会涉及到。再比如阅读Spring源码的时候,势必要先对IOC,AOP,Java动态代理等知识点有所了解。

2、强烈的求知欲

强烈的求知欲是阅读源码的核心动力!

大多数程序员的学习态度分为如下几个层次:

  • 完成自己的项目就可以了,遇到不懂的地方就百度一下。
  • 不仅做好项目,还会去阅读一些和项目有关的书籍。
  • 除了阅读和项目相关的书籍之外,还会阅读一些IT行业相关的书籍。
  • 平时会经常逛逛GitHub,找一些开源项目看看。
  • 阅读基础框架、J2EE规范、源码。

大多数程序员的层次都是在第一层,到第五层的人就需要有强烈的求知欲了。

3、足够的耐心

通过阅读源码我们可以学习大佬的设计思路,技巧。还可以把我们一些零碎的知识点整合起来,从而融会贯通。总之阅读源码的好处多多,想必大家也清楚。

但是真的把那么庞大复杂的代码放到你的眼前时,肯定会在阅读的过程中卡住,就如同陷入了一个巨大的迷宫,如果想要在这个巨大的迷宫中找到一条出路,那就需要把整个迷宫的整体结构弄清楚,比如:API结构、框架的设计图。而且还有理解它的核心思想,确实很不容易。

刚开始阅读源码的时候肯定会很痛苦,所以,没有足够的耐心是万万不行的。

如何读Java源码:

团长也是经历过阅读源码种种痛苦的人,算是有一些成功的经验吧,今天来给大家分享一下。

如果你已经有了一年左右的Java开发经验的话,那么你就有阅读Java源码的技术基础了。

1、建议从JDK源码开始读起,这个直接和eclipse集成,不需要任何配置。

可以从JDK的工具包开始,也就是我们学的《数据结构和算法》Java版,如List接口和ArrayList、LinkedList实现,HashMap和TreeMap等。这些数据结构里也涉及到排序等算法,一举两得。

面试时,考官总喜欢问ArrayList和Vector的区别,你花10分钟读读源码,估计一辈子都忘不了。

然后是core包,也就是String、StringBuffer等。如果你有一定的Java IO基础,那么不妨读读FileReader等类。

建议大家看看《Java In A Nutshell》,里面有整个Java IO的架构图。Java IO类库,如果不理解其各接口和继承关系,则阅读始终是一头雾水。

Java IO 包,我认为是对继承和接口运用得最优雅的案例。如果你将来做架构师,你一定会经常和它打交道,如项目中部署和配置相关的核心类开发。

读这些源码时,只需要读懂一些核心类即可,如和ArrayList类似的二三十个类,对于每一个类,也不一定要每个方法都读懂。像String有些方法已经到虚拟机层了(native方法),如hashCode方法。

当然,如果有兴趣,可以对照看看JRockit的源码,同一套API,两种实现,很有意思的。

如果你再想钻的话,不妨看看针对虚拟机的那套代码,如System ClassLoader的原理,它不在JDK包里,JDK是基于它的。JDK的源码Zip包只有10来M,它像是有50来M,Sun公司有下载的,不过很隐秘。我曾经为自己找到、读过它很兴奋了一阵。

2、Java Web项目源码阅读

步骤:表结构 → web.xml → mvc → db → spring ioc → log→ 代码

① 先了解项目数据库的表结构,这个方面是最容易忘记的,有时候我们只顾着看每一个方法是怎么进行的,却没有去了解数据库之间的主外键关联。其实如果先了解数据库表结构,再去看一个方法的实现会更加容易。

② 然后需要过一遍web.xml,知道项目中用到了什么拦截器,监听器,过滤器,拥有哪些配置文件。如果是拦截器,一般负责过滤请求,进行AOP等;如果是监听器,可能是定时任务,初始化任务;配置文件有如 使用了spring后的读取mvc相关,db相关,service相关,aop相关的文件。

③ 查看拦截器,监听器代码,知道拦截了什么请求,这个类完成了怎样的工作。有的人就是因为缺少了这一步,自己写了一个action,配置文件也没有写错,但是却怎么调试也无法进入这个action,直到别人告诉他,请求被拦截了。

④ 接下来,看配置文件,首先一定是mvc相关的,如springmvc中,要请求哪些请求是静态资源,使用了哪些view策略,controller注解放在哪个包下等。然后是db相关配置文件,看使用了什么数据库,使用了什么orm框架,是否开启了二级缓存,使用哪种产品作为二级缓存,事务管理的处理,需要扫描的实体类放在什么位置。最后是spring核心的ioc功能相关的配置文件,知道接口与具体类的注入大致是怎样的。当然还有一些如apectj等的配置文件,也是在这个步骤中完成。

⑤ log相关文件,日志的各个级别是如何处理的,在哪些地方使用了log记录日志。

⑥ 从上面几点后知道了整个开源项目的整体框架,阅读每个方法就不再那么难了。

⑦ 当然如果有项目配套的开发文档也是要阅读的。

3、Java框架源码阅读

当然了,就是Spring、MyBatis这类框架。

在读Spring源码前,一定要先看看《J2EE Design and Development》这本书,它是Spring的设计思路。注意,不是中文版,中文版完全被糟蹋了。

想要阅读MyBatis的源码就要先了解它的一些概念,否则云里来雾里去的什么也不懂。有很多人会选择去买一些书籍来帮助阅读,当然这是可取的。那么如果不想的话,就可以去官网查看它的介绍(MyBatis网站:http://www.mybatis.org/mybatis-3/zh/getting-started.html),团长也是按照官网上面的介绍来进行源码阅读的。团长认为MyBatis的亮点就是管理SQL语句。

总结

没有人一开始就可以看得懂那些源码,我们都是从0开始的,而且没有什么捷径可寻,无非就是看我们谁愿意花时间去研究,谁的求知欲更强烈,谁更有耐心。阅读源码的过程中我们的能力肯定会提升,可以从中学到很多东西。在我们做项目的时候就会体现出来了,的确会比以前顺手很多。

]]>
/9/java/87282.html/feed 0
程序员的百宝箱:提升工作效率的七大神器 /31/112/87279.html /31/112/87279.html#respond Fri, 11 Oct 2019 02:04:12 +0000 /?p=87279 Perl之父Larry Wall曾在 Programming Perl 一书中提到:程序员的三个美德是懒惰、不耐烦和傲慢。

懒惰,是程序员美德的第一要素。Larry Wall所说的“懒惰”,并不是安于现状和不思进取,而是付出最少的时间或者精力来达到同样甚至更好的目标。“懒惰”的程序员会尽量使自己的代码既实用又有很好的可读性,这样可以节省后面的很多维护成本;还会尽力完善代码中的注释及文档,以免别人问自己太多问题,更擅长使用各种工具,从方方面面提升自己的效率。

懒惰程序员的百宝箱:提升工作效率的七大神器

对于开发者,尤其是Java开发者来说,使用开发工具编写代码真的可以大大提升工作效率,因为现在很多IDE都有很强大的工具,不仅提供了代码补全、错误提示、自动编译等功能,还提供了各种插件,方便和其他工具融合,能大大提升写代码的效率及代码质量。

目前市面上主要有两款Java代码开发工具:Eclipse和IntelliJIDEA,这两款开发工具之争由来已久,不过最近几年,IntelliJIDEA逐渐撼动了Eclipse的霸主地位,成为开发者的首选开发工具。在2013年的Google I/O大会上,Google推出了新的Android集成IDE“AndroidStudio”,其最大的转变就是从Eclipse切换到了IntelliJ IDEA。

笔者也经历过从Eclipes转向IntelliJ IDEA的过程,相比之下,Intellij IDEA在某些方面确实比Eclipse更加出色一些,配合Mac OS操作系统上的快捷键,真的可以在开发效率上提升一大截。

关于开发者使用的IDE,Jet Brains做过一项调查,结果显示:有59%的开发者使用IntelliJ,有30%的开发者使用Eclipse,只有不到5%的开发者使用Sublime和Vim等普通文本编辑器。

懒惰程序员的百宝箱:提升工作效率的七大神器

程序员在协作开发时不可避免地要使用版本控制工具对代码进行管理,版本控制工具主要用于存储和追踪被管理的文件的修改历史,是软件开发者的必备工具。

目前市面上比较成熟的版本控制工具有CVS、Visual SourceSafe、PVCS、Subversion(SVN)和Git等,SVN和Git的使用较为广泛。

无论是SVN还是Git,都提供了很好的版本控制功能,例如对代码的统一管理、冲突解决、代码权限控制、分支开发和合并等。开发者应该熟练掌握SVN和Git,比如代码拉取、代码提交、代码合并和冲突解决等,尤其要掌握一些Shell命令,这可以节省大量的时间。

虽然目前有很多开源代码及很多公司的内部代码都倾向于使用Git进行代码托管,但我们不能保证所遇到的所有代码都使用了Git。公司规模越大,使用SVN进行代码托管的可能性便越大,毕竟迁移也是需要一定成本的。

懒惰程序员的百宝箱:提升工作效率的七大神器

自动化构建指自动创建软件组建的一组进程,包括将计算机源代码编译成二进制码、将二进制码包装成软件包并对其进行自动化测试。很早以前的自动化构建都是通过创建Make来完成的,后来发展为GNU Make。随着时间的推移,如今的软件开发主要使用更优秀的自动化构建工具来完成,例如Java世界中的三大构建工具Ant、Maven和Gradle,如今,Ant几乎要退出历史的舞台了。

Maven和Gradle之所以能够赢得众多程序员的青睐,主要是因为它们在依赖管理、冲突解决、项目构建、项目结构管理和插件机制等方面的出色表现。

懒惰程序员的百宝箱:提升工作效率的七大神器

所谓一图胜千言,无论是自己梳理思路,还是给别人讲解,图案都是很好的形式。而且,程序员还经常要做一些PPT等,有了这些画图工具,就能大显身手。

1)思维导图——XMind

人类大脑的最自然思考方式是放射性思考,也就是我们常说的发散性思维。我们通常在思考一个问题的时候,都是以一个思维点为起点,然后不断进行发散式展开的。如果我们将这个思考过程通过图形化的方式表达出来,就是一张思维导图。思维导图可以有效地把思维模式表现出来,有利于人脑的扩散思维的展开。如图所示是一张关于“提升工作效率的软件”的思维导图。

XMind是一款非常实用的商业思维导图软件,采用Java语言开发而成,具备跨平台运行的性质,且基于EclipseRCP体系结构,支持插件通过编写XML清单文件扩展系统定义好的扩展点,帮助用户在真正意义上提高生产效率,被著名的互联网媒体Lifehacker评选为“最佳头脑风暴和思维导图工具”及“最受欢迎的思维导图软件”。

XMind的程序主体由一组插件构成,包括一个核心主程序插件、一组Eclipse运行时插件、一个帮助文档插件和一组多语种资源文件插件,对Eclipse用户非常友好。

XMind不仅可以绘制思维导图,还可以绘制鱼骨图、二维图、树形图、逻辑图和组织结构图(Org、Tree、LogicChart、Fishbone),并且可以方便地在这些展示形式之间进行转换。用户可以导入MindManager、FreeMind数据文件,灵活定制节点外观、插入图标、丰富的样式和主题。其输出格式有HTML和图片。

2)UML画图工具

UML(Unified Model Language)即统一建模语言,又被称为标准建模语言,是用来对软件密集系统进行可视化建模的一种语言。开发人员在做详细设计时,免不了要和各种UML图打交道,例如用例图、类图、对象图、时序图、活动图和状态图等。

简单来说,开发人员在写代码之前就需要了解需求,在这之后要对软件系统进行建模,将抽象的语言描述转换成可视化的模型。而在建模过程中需要一种标准的语言,这种语言就是UML。

目前市面上有很多UML画图工具可供选择,它们均能满足基本的画图功能,下面简单列举几种。

  • Rational Rose:是Rational公司出品的一种面向对象的统一建模语言的可视化建模工具,主要用于可视化建模。
  • Visual Paradigm for UML(VP-UML):是一种功能强大、跨平台、使用便捷、直观的UML建模和CASE工具,它可以被整合在其他CASE工具或者其他IDE工具中。Visual Paradigm for UML支持UML建模、数据库建模、对象关系映射、逆向工程和Java双向工程等,功能异常强大。
  • StarUML(SU):是一款开源的UML开发工具,支持绘制用例图、类图、序列图、状态图等9种常用的UML图,具有发展快、灵活、可扩展性强等优点,唯一的不足之处是在反向工程时只能生成类图,不能生成类之间的关系。
懒惰程序员的百宝箱:提升工作效率的七大神器

Markdown是一种轻量级标记语言,创始人为约翰·格鲁伯(John Gruber),允许人们使用易读易写的纯文本格式编写文档,然后将文本格式转换成有效的XHTML(或者HTML)文档。

作为程序员,我们除了要具备编程的硬技能,还要具备写作的软件,而首选的写作方式就是Markdown。目前,很多大型博客网站都开始支持使用Markdown写文章,这无疑是对程序员的一种示好行为,著名的代码托管网站GitHub也支持使用Markdown语法来编辑README文件等。

关于Markdown的编辑器,目前市面上也有很多选择,主要有Mou、MacDown、Typed和Bear等,还有很多在线的Markdown编辑器,在后面的在线工具章节中会详细介绍。

还需要提及的一点是,其实使用哪种Markdown编辑器并不很重要,重要的是程序员应该掌握常用的Markdown语法。这样写出来的文章或者文档,可以在任意支持MD语法的平台上查看。

懒惰程序员的百宝箱:提升工作效率的七大神器

前面介绍过Java开发常用的IDE,但是在某些情况下我们仅需要打开一个单独的文本文件,比如打开一个XML格式的文件、一个JSON格式的文件等,这时一款好用的文本编辑器就派上用场了。

SublimeText是一款流行的代码编辑器软件,可运行在Linux、Windows和Mac OS X等操作系统之上,是许多程序员喜欢使用的一款文本编辑器软件。它具有代码高亮、语法提示、自动完成且反映快速的编辑器软件,不仅界面华丽,还支持插件扩展机制。

Atom是GitHub专门为程序员推出的跨平台的文本编辑器,因为有简洁、直观的图形界面而受到了广大程序员的青睐,而且它有很多有趣的特点:支持CSS、HTML、JavaScript等网页编程语言;支持宏,自动完成分屏功能,集成了文件管理器;支持Mac OS、Windows和Linux操作系统,支持Node.js所写的插件,并内置由GitHub提供的Git版本控制系统。多数的延伸包皆为开放源代码授权,并由社区建置与维护。

Notepad++也是一套被广大程序员选用的纯文本编辑器,基于同样开放源码的Scintilla文本编辑组件研发而成,整个项目起初被托管于SourceForge.net,曾经两度获得SourceForge社区选择奖——最佳开发工具。Notepad++在2010年6月被托管于TuxFamily,现被托管于GitHub。

懒惰程序员的百宝箱:提升工作效率的七大神器

除了以上介绍的一些必备软件,还有些常用工具也是程序员在日常工作中必不可少的,例如Host绑定、HTTP调试等。

1)Host绑定

在日常开发工作中,我们对Host绑定肯定不陌生,因为我们几乎每天都要和它打交道。我们可能经常要在不同的Host绑定之间来回切换,尤其是在Java Web开发中,在本地开发测试时可能使用的是一套Host绑定,在测试环境部署之后提交给测试人员进行测试时又要使用另一套Host绑定。在测试之后,要经历预发布、灰度发布等,都需要一套新的Host绑定。采用好用的Host绑定工具可以大大节省Host绑定切换的时间。

SwitchHosts是一个非常方便、快捷的Host绑定切换工具,免费、开源,支持预发高亮、方案多选、单击行号快速切换注释、系统菜单栏快捷切换、方案导入导出等功能。

2)HTTP调试

在日常的Web开发中,我们除了会和Host绑定打交道,还经常会和RESTful API打交道,有时我们需要一个工具来帮我们发送HTTP请求,或者说是模拟HTTP请求,比如帮我们发送POST请求等,这时就需要一款可以模拟发送HTTP请求的工具。

Postman是Google开发的一款功能强大的网页调试与发送网页HTTP请求,并能运行测试用例的Chrome插件。除插件外,也提供软件下载。它可以模拟各种HTTP请求,从常用的GET、POST到RESTful的PUT、DELETE等,甚至可以发送文件及额外的Header等。Postman可以将Response内容的格式自动美化,将JSON、XML或者HTML都整理成我们可以阅读的格式。Postman还支持编写测试脚本,可以快速检查Request结果,并返回测试结果。总之,Postman是一款非常不错的可以用来做HTTP调试的软件。

]]>
/31/112/87279.html/feed 0
Python 编码风格指南 /uncategorized/87277.html /uncategorized/87277.html#respond Thu, 10 Oct 2019 02:50:55 +0000 /?p=87277 访问Python官方网站的文档部分并搜索PEP,就可以获得PEP 8全文及Python历史上发布的所有其他PEP。PEP既是Python历史和经验的绝佳来源,也是当前议题和将来计划的解释。

访问Python官方网站的文档部分并搜索PEP,就可以获得PEP 8全文及Python历史上发布的所有其他PEP。PEP既是Python历史和经验的绝佳来源,也是当前议题和将来计划的解释。

01 简介

本文档给出的Python编码约定,适用于由Python主发行版本中的标准库构成的代码。有关Python的C实现中的C代码风格指南,参见相应的PEP。本文档改编自Guido最初的Python风格指南文章,并加入了Barry风格指南的一些内容。如果与Guido的风格规则存在冲突,应该遵从本PEP。本PEP可能尚未完结(其实可能永远不会完结)。

01

盲目的一致性是头脑简单的表现

Guido的一个重要观点是,代码被阅读的次数远多于被编写的次数。本指南旨在提高代码的可读性,使各种各样的Python代码能保持风格一致。正如PEP 20所述,“Readability counts”(注重可读性)。

风格指南是讨论一致性的。与风格指南保持一致很重要。维持同一个项目内部的一致性更加重要。而保证同一个模块和函数内部的一致性则最重要。

然而最最重要的是,知道何时应打破一致性,有时风格指南并不适用。如果心存疑虑,请采用自己的最佳判断。请看看别人的例子并做出最佳决定。不要犹豫,尽管发出疑问。

以下是两个打破规范的好理由。

  • 如果应用风格指南会让代码的可读性变差,甚至对于习惯阅读遵守本规范代码的人来说也是如此。
  • 需要与周边的代码保持一致,而这些代码并未遵守规范(可能是历史原因造成的),尽管这也可能是个收拾别人烂摊子的机会(真正的极限编程风格)。

01 代码布局

1.

缩进

每级缩进采用4个空格。

为了对付那些确实陈旧的代码,又不愿做出清理,那么可以继续沿用8个空格长度的制表符。

2.

制表符还是空格

绝对禁止制表符和空格的混用。

最流行的Python缩进方式是只使用空格。第二流行的方式是只使用制表符。混合使用制表符和空格进行缩进的代码,应该转换为只使用空格的方式。如果调用Python命令行解释器时带上-t参数,它就会对非法混用制表符和空格的代码发出警告。如果用了-tt参数,这些警告就会上升为错误。强烈推荐使用这些参数!

对全新的项目而言,强烈建议只用空格缩进,换掉所有的制表符。大部分编辑器都具备将制表符替换为空格的便捷功能。

3.

最大行长

所有行都应限制在79个字符以内。

将行长限制在80个字符的设备还有很多,而且将窗口限制为80个字符宽就可以并排放置多个窗口。这些设备上的默认换行会破坏代码的外观,增加理解的难度。因此,请将所有行都限制在79个字符以内。对于连续的大段文字(文档字符串或注释),建议将行长限制在72个字符以内。

对长行进行换行的首选方案,是利用Python隐含的行连接特性,在圆括号、方括号和大括号内部进行断行。必要时可以在表达式外面多加一对圆括号,不过有时候用反斜杠会更好看些。请确保对后续行进行适当的缩进。打断二元运算符的首选位置是在运算符之后,而不是运算符之前。下面给出一些例子:

1 class  Rectangle(Blob):2 def __init__(self, width, height,3 color='black', emphasis=None, highlight=0):4 if (width == 0 and height == 0 and\5 color == 'red' and emphasis == 'strong' or \6 highlight > 100):7 raise ValueError("sorry, you lose")8 if width == 0 and height == 0 and (color == 'red' or9 emphasis is None):10 raise ValueError("I don't think so -- values are %s, %s" %11 (width, height))12 Blob.__init__(self, width, height,13 color, emphasis, highlight)

4.

空行

顶级函数和类定义之间,请用两个空行分隔。

类内部的各个方法定义之间,请用1个空行分隔。

为了让有关联的函数成组,可以在各函数组之间有节制地添加空行。相互关联的一组单行函数之间,可以省略空行,如一组函数的伪实现(dummy implementation)。

函数内部可以有节制地用空行来区分出各个逻辑部分。

Python可将Ctrl+L(^L)换页符接受为空白符。很多工具都将其视为分页符,所以可以利用其进行分页,使得文件中的关联部分单独成页。

5.

导入

导入语句通常应单独成行,例如:

1import os2import sys

不要像下面这样写在一起:

1import sys, os

不过下面的写法没有问题:

1from subprocess import Popen, PIPE

导入语句通常位于文件的顶部,紧挨着模块注释和文档字符串后面,在模块全局变量和常量定义之前。

导入语句应按照以下顺序进行分组。

(1)标准库的导入。

(2)相关第三方库的导入。

(3)本地应用程序/库——特定库的导入。

每组导入语句之间请加入1个空行。

任何对应的__all__声明都应位于导入语句之后。

非常不推荐对内部包的导入使用相对导入语法。请始终对所有导入都使用绝对包路径。即便Python 2.5现在已完全实现了PEP 328,它的显式相对导入语法也是强烈不推荐的。绝对导入的可移植性更好,通常可读性也会更好。

如果是从包含类的模块中导入类,通常可以采用如下写法:

1from myclass import MyClass2from foo.bar.yourclass import YourClass

如果上述写法会导致本地命名冲突,就采用如下写法:

1import myclass2import foo.bar.yourclass

然后用myclass.MyClassfoo.bar.yourclass.YourClass表示类。

6.

表达式和语句内的空白符

讨厌之事——以下场合应避免使用多余的空白符。

  • 紧靠小括号、中括号或大括号内部。

正确:

1spam(ham[1], {eggs: 2})

错误:

1spam( ham[ 1 ], { eggs: 2 } )
  • 紧挨着逗号、分号或冒号之前。

正确:

1if x == 4: print x, y; x, y = y, x

错误:

1if x == 4 : print x , y ; x , y = y , x
  • 紧挨着函数参数列表的左括号之前。

正确:

1spam(1)

错误:

1spam (1)
  • 紧挨着索引或切片操作的左括号之前。

正确:

1dict['key'] = list[index]

错误:

1dict ['key'] = list [index]
  • 为了与另一条赋值或其他语句对齐,在运算符两边使用多个空格。

正确:

1x = 12y = 23long_variable = 3

错误:

1x = 12y = 23long_variable = 3

7.

其他建议

始终在以下二元操作符两侧各放1个空格:赋值(=)、增量赋值(+=-=等)、比较(==<>!=<><=>=innotinisis not)、布尔(andornot)。

  • 在数学运算符两侧放置空格。

正确:

1i = i + 12submitted += 13 4x = x * 2 – 1 5hypot2 = x * x + y * y 6c = (a + b) * (a - b)

错误:

1i=i+12submitted +=13x = x*2 – 14hypot2 = x*x + y*y5c = (a+b) * (a-b)
  • 在用于指定关键字参数或默认参数值时,请勿在=两边使用空格。

正确:

1def complex(real, imag=0.0):2 return magic(r=real, i=imag)

错误:

1def complex(real, imag = 0.0):2 return magic(r = real, i = imag)
  • 通常不鼓励使用复合语句,也就是在同一行放置多条语句。

正确:

1if foo == 'blah':2 do_blah_thing3do_one4do_two5do_three

最好不要:

1if foo == 'blah': do_blah_thing2do_one; do_two; do_three
  • 虽然有时候将小块代码和if/for/while放在同一行没什么问题,但多行语句绝对不能如此。同时还要避免过长代码行的折叠!

最好不要:

1if foo == 'blah': do_blah_thing2for x in lst: total += x3 while t < 10: t = delay

绝对不要:

1if foo == 'blah': do_blah_thing2else: do_non_blah_thing3try: something4finally: cleanup5do_one; do_two; do_three(long, argument, 6 list, like, this)7if foo == 'blah': one; two; three
]]>
/uncategorized/87277.html/feed 0
14 个实用的数据库设计技巧 /9/11/87274.html /9/11/87274.html#respond Thu, 10 Oct 2019 02:13:22 +0000 /?p=87274 1. 原始单据与实体之间的关系

可以是一对一、一对多、多对多的关系。在一般情况下,它们是一对一的关系:即一张原始单据对应且只对应一个实体。在特殊情况下,它们可能是一对多或多对一的关系,即一张原始单证对应多个实体,或多张原始单证对应一个实体。这里的实体可以理解为基本表。明确这种对应关系后,对我们设计录入界面大有好处。

〖例1〗:一份员工履历资料,在人力资源信息系统中,就对应三个基本表:员工基本情况表、社会关系表、工作简历表。这就是“一张原始单证对应多个实体”的典型例子。

2. 主键与外键

一般而言,一个实体不能既无主键又无外键。在E—R 图中, 处于叶子部位的实体, 可以定义主键,也可以不定义主键(因为它无子孙), 但必须要有外键(因为它有父亲)。

主键与外键的设计,在全局数据库的设计中,占有重要地位。当全局数据库的设计完成以后,有个美国数据库设计专家说:“键,到处都是键,除了键之外,什么也没有”,这就是他的数据库设计经验之谈,也反映了他对信息系统核心(数据模型)的高度抽象思想。因为:主键是实体的高度抽象,主键与外键的配对,表示实体之间的连接。

3. 基本表的性质

基本表与中间表、临时表不同,因为它具有如下四个特性:

  • 原子性。基本表中的字段是不可再分解的。
  • 原始性。基本表中的记录是原始数据(基础数据)的记录。
  • 演绎性。由基本表与代码表中的数据,可以派生出所有的输出数据。
  • 稳定性。基本表的结构是相对稳定的,表中的记录是要长期保存的。

理解基本表的性质后,在设计数据库时,就能将基本表与中间表、临时表区分开来。

4. 范式标准

基本表及其字段之间的关系, 应尽量满足第三范式。但是,满足第三范式的数据库设计,往往不是最好的设计。为了提高数据库的运行效率,常常需要降低范式标准:适当增加冗余,达到以空间换时间的目的。

〖例2〗:有一张存放商品的基本表。“金额”这个字段的存在,表明该表的设计不满足第三范式,因为“金额”可以由“单价”乘以“数量”得到,说明“金额”是冗余字段。但是,增加“金额”这个冗余字段,可以提高查询统计的速度,这就是以空间换时间的作法。

在Rose 2002中,规定列有两种类型:数据列和计算列。“金额”这样的列被称为“计算列”,而“单价”和“数量”这样的列被称为“数据列”。

5. 通俗地理解三个范式

通俗地理解三个范式,对于数据库设计大有好处。在数据库设计中,为了更好地应用三个范式,就必须通俗地理解三个范式(通俗地理解是够用的理解,并不是最科学最准确的理解):

  • 第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;
  • 第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;
  • 第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。

没有冗余的数据库设计可以做到。但是,没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。

具体做法是:在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑。降低范式就是增加字段,允许冗余。

6. 要善于识别与正确处理多对多的关系

若两个实体之间存在多对多的关系,则应消除这种关系。消除的办法是,在两者之间增加第三个实体。这样,原来一个多对多的关系,现在变为两个一对多的关系。要将原来两个实体的属性合理地分配到三个实体中去。这里的第三个实体,实质上是一个较复杂的关系,它对应一张基本表。一般来讲,数据库设计工具不能识别多对多的关系,但能处理多对多的关系。

〖例3〗:在“图书馆信息系统”中,“图书”是一个实体,“读者”也是一个实体。这两个实体之间的关系,是一个典型的多对多关系:一本图书在不同时间可以被多个读者借阅,一个读者又可以借多本图书。为此,要在二者之间增加第三个实体,该实体取名为“借还书”,它的属性为:借还时间、借还标志(0表示借书,1表示还书),另外,它还应该有两个外键(“图书”的主键,“读者”的主键),使它能与“图书”和“读者”连接。

7. 主键PK的取值方法

PK是供程序员使用的表间连接工具,可以是一无物理意义的数字串, 由程序自动加1来实现。也可以是有物理意义的字段名或字段名的组合。不过前者比后者好。当PK是字段名的组合时,建议字段的个数不要太多,多了不但索引占用空间大,而且速度也慢。

8. 正确认识数据冗余

主键与外键在多表中的重复出现, 不属于数据冗余,这个概念必须清楚,事实上有许多人还不清楚。非键字段的重复出现, 才是数据冗余!而且是一种低级冗余,即重复性的冗余。高级冗余不是字段的重复出现,而是字段的派生出现。

〖例4〗:商品中的“单价、数量、金额”三个字段,“金额”就是由“单价”乘以“数量”派生出来的,它就是冗余,而且是一种高级冗余。冗余的目的是为了提高处理速度。只有低级冗余才会增加数据的不一致性,因为同一数据,可能从不同时间、地点、角色上多次录入。因此,我们提倡高级冗余(派生性冗余),反对低级冗余(重复性冗余)。

9. E–R图没有标准答案

信息系统的E–R图没有标准答案,因为它的设计与画法不是惟一的,只要它覆盖了系统需求的业务范围和功能内容,就是可行的。反之要修改E–R图。尽管它没有惟一的标准答案,并不意味着可以随意设计。好的E—R图的标准是:结构清晰、关联简洁、实体个数适中、属性分配合理、没有低级冗余。

10. 视图技术在数据库设计中很有用

与基本表、代码表、中间表不同,视图是一种虚表,它依赖数据源的实表而存在。视图是供程序员使用数据库的一个窗口,是基表数据综合的一种形式, 是数据处理的一种方法,是用户数据保密的一种手段。为了进行复杂处理、提高运算速度和节省存储空间, 视图的定义深度一般不得超过三层。若三层视图仍不够用, 则应在视图上定义临时表, 在临时表上再定义视图。

这样反复交迭定义, 视图的深度就不受限制了。对于某些与国家政治、经济、技术、军事和安全利益有关的信息系统,视图的作用更加重要。这些系统的基本表完成物理设计之后,立即在基本表上建立第一层视图,这层视图的个数和结构,与基本表的个数和结构是完全相同。并且规定,所有的程序员,一律只准在视图上操作。只有数据库管理员,带着多个人员共同掌握的“安全钥匙”,才能直接在基本表上操作。请读者想想:这是为什么?

11. 中间表、报表和临时表

中间表是存放统计数据的表,它是为数据仓库、输出报表或查询结果而设计的,有时它没有主键与外键(数据仓库除外)。临时表是程序员个人设计的,存放临时记录,为个人所用。基表和中间表由DBA维护,临时表由程序员自己用程序自动维护。

12. 完整性约束表现在三个方面

域的完整性:用Check来实现约束,在数据库设计工具中,对字段的取值范围进行定义时,有一个Check按钮,通过它定义字段的值城。参照完整性:用PK、FK、表级触发器来实现。用户定义完整性:它是一些业务规则,用存储过程和触发器来实现。

13. 防止数据库设计打补丁的方法是“三少原则”

  • 一个数据库中表的个数越少越好。只有表的个数少了,才能说明系统的E–R图少而精,去掉了重复的多余的实体,形成了对客观世界的高度抽象,进行了系统的数据集成,防止了打补丁式的设计;
  • 一个表中组合主键的字段个数越少越好。因为主键的作用,一是建主键索引,二是做为子表的外键,所以组合主键的字段个数少了,不仅节省了运行时间,而且节省了索引存储空间;
  • 一个表中的字段个数越少越好。只有字段的个数少了,才能说明在系统中不存在数据重复,且很少有数据冗余,更重要的是督促读者学会“列变行”,这样就防止了将子表中的字段拉入到主表中去,在主表中留下许多空余的字段。所谓“列变行”,就是将主表中的一部分内容拉出去,另外单独建一个子表。这个方法很简单,有的人就是不习惯、不采纳、不执行。

数据库设计的实用原则是:在数据冗余和处理速度之间找到合适的平衡点。“三少”是一个整体概念,综合观点,不能孤立某一个原则。该原则是相对的,不是绝对的。“三多”原则肯定是错误的。试想:若覆盖系统同样的功能,一百个实体(共一千个属性) 的E–R图,肯定比二百个实体(共二千个属性)的E–R图,要好得多。

提倡“三少”原则,是叫读者学会利用数据库设计技术进行系统的数据集成。数据集成的步骤是将文件系统集成为应用数据库,将应用数据库集成为主题数据库,将主题数据库集成为全局综合数据库。

集成的程度越高,数据共享性就越强,信息孤岛现象就越少,整个企业信息系统的全局E—R图中实体的个数、主键的个数、属性的个数就会越少。

提倡“三少”原则的目的,是防止读者利用打补丁技术,不断地对数据库进行增删改,使企业数据库变成了随意设计数据库表的“垃圾堆”,或数据库表的“大杂院”,最后造成数据库中的基本表、代码表、中间表、临时表杂乱无章,不计其数,导致企事业单位的信息系统无法维护而瘫痪。

“三多”原则任何人都可以做到,该原则是“打补丁方法”设计数据库的歪理学说。“三少”原则是少而精的原则,它要求有较高的数据库设计技巧与艺术,不是任何人都能做到的,因为该原则是杜绝用“打补丁方法”设计数据库的理论依据。

14. 提高数据库运行效率的办法

在给定的系统硬件和系统软件条件下,提高数据库系统的运行效率的办法是:

  • 在数据库物理设计时,降低范式,增加冗余, 少用触发器, 多用存储过程。
  • 当计算非常复杂、而且记录条数非常巨大时(例如一千万条),复杂计算要先在数据库外面,以文件系统方式用C++语言计算处理完成之后,最后才入库追加到表中去。这是电信计费系统设计的经验。
  • 发现某个表的记录太多,例如超过一千万条,则要对该表进行水平分割。水平分割的做法是,以该表主键PK的某个值为界线,将该表的记录水平分割为两个表。若发现某个表的字段太多,例如超过八十个,则垂直分割该表,将原来的一个表分解为两个表。
  • 对数据库管理系统DBMS进行系统优化,即优化各种系统参数,如缓冲区个数。SQL 数据库小技巧,这个推荐看一下。
  • 在使用面向数据的SQL语言进行程序设计时,尽量采取优化算法。

总之,要提高数据库的运行效率,必须从数据库系统级优化、数据库设计级优化、程序实现级优化,这三个层次上同时下功夫。

上述十四个技巧,是许多人在大量的数据库分析与设计实践中,逐步总结出来的。对于这些经验的运用,读者不能生帮硬套,死记硬背,而要消化理解,实事求是,灵活掌握。并逐步做到:在应用中发展,在发展中应用。

]]>
/9/11/87274.html/feed 0
GO是更好的编程语言吗? /9/server/87271.html /9/server/87271.html#respond Wed, 09 Oct 2019 02:15:19 +0000 /?p=87271 #引言

团队有项目考虑用GO重写,所以花了些时间调研GO。

GO是更好的编程语言吗?

第一次接触GO是5年前,14年4月份,也是在我司,全职钻研一周,彼时C++中毒太深,内心排斥其他编程语言,看其他语法总觉得有点怪,而且有“C/C++能做任何事,故无用其他语言之必要”的思想在作祟。

我读研阶段用过几年Java,工作以来一直使用C++/C,况且教出几个非计算机系(包括英语系)的职业程序员,所以,我就浮夸一回,声称熟练掌握C++吧。

GO是更好的编程语言吗?

人都有思维定势,受限于自己的经验和认知,我亦不能例外,但好在我意识到这一点,所以在调研过程中,努力摒弃成见,尽量摆脱惯性,查阅关于GO的各种(包括核心设计师)文章,倾听拥趸和批评者的不同声音,结合自己的思考和分析,力求客观公正去评价GO

#GO语言简介

GO是Google开发的一种静态、强类型、编译型、并发型,并具有垃圾回收功能的类C编程语言。2009以开源项目的形式发布,2012年发布1.0稳定版本,距今已经十年了。

发明一种新的编程语言,首先得找到必要性,不然肯定会被质疑重复造轮子,方法嘛?无非是先找某种语言的一些茬,吐槽一番,复杂、笨拙、低效,太TM沙雕了,不能忍,劳资要立刻马上分分钟撸出一种新的编程语言,完美解决所有问题,不然对不起我卓尔不群的智商。

GO是更好的编程语言吗?

GO的故事也很套路,G公司的Pike大牛听完C++0x的演讲,回到办公室,开始编译C++,等待编译过程中,转过椅子面向Robert,讨论语言的问题,然后拉上Ken爷爷一起合计,群嘲之后,受不了C++某些沙雕设计,还没等编译完成,三个老男人便一拍即合,决定一起搞点change the world的伟大事情,于是乎,GO诞生了。

GO语言之父Pike提到:GO语言是以C为原型,以C++为目标而设计的,希望C++程序员能以GO作为替代品。因为他觉得C++忒复杂了,要解救程序员于水火。

虽然GO以C++为目标而设计,但尴尬的是,Pike坦承GO并没有吸引来多少C++程序员,反而是吸引了不少Python、Ruby程序员。这、这、这、这。

#GO核心团队

G公司不差钱不缺人,GO团队更是群星荟萃、大咖云集,不废话,直接上图:

GO是更好的编程语言吗?

核心设计师Pike和Ken都是出身自贝尔实验室,Ken之于Pike,亦师亦友,共同发明了UTF-8,还基情四射地结对编程过,感情好的穿一条裤子。

Pike是Unix先驱,贝尔实验室最早跟Ken、Dennis一起开发Unix的猛人,Plan9 OS的灵魂人物。大胡子Ken爷爷则是Unix之父,和Dennis一起发明了C语言,殿堂骨灰级程序员,早已是名满天下。

技术实力毋容置疑,不过这哥俩都是玩Kernel的,经历相同,理念相近,分歧会比较少,他们也都坦承C用得最多最熟,所以注定了GO的类C特性,不过这会不会导致GO设计上的思维火花不足,对OOP以及现代编程思想的支持不足,亦未可知。

#GO的哲学

哲学是难分对错的,GO有GO的哲学,有它的取舍和审美,不一定每个人都认同,我觉得还挺有道理的,罗列如下:

##少即是多

GO信奉:Less Is More,大道至简,臆测是乔帮主的信徒。

##世界是并行的

世间万物是并行发生的,所以GO遵照这个规律,对并发的原生支持让GO更易于描述并行世界。

##世界是物质组成的

微观世界由小的粒子组合成大的粒子;宏观世界由小的物体组合成大的物体。继承只能描述现实世界的一小部分,使用继承是不全面的;GO的设计选择的是组合,这个和现实世界比较吻合的设计,表现力更强。

##世界是标准化的

硬件是标准化的,软件也应如此,GO的接口是DUCK模型,接口是非侵入式的。

##正交性

GO的多个特性都是正交性的,正交性是保持事物稳定和简单的最好设计。

##二八定律

80%代码只使用20%特性,增加语言特性,并不能提升效率,反而会增加复杂性,提高犯错率,加重程序员心智负担。

##统一格式化

C++语法自由自在,于是乎一群吊丝为tab or space、大括号要不要换行等诸如此类的格式问题吵得不可开交。GO设计师认为,都是吃饱了撑的,你们太愚蠢了。

于是GO规定左大括号{不能换行放置,没有为什么,对着干直接编译不过。

GO编译器内建工具gofmt强制源码格式化。对不起,没有选项,我的地盘听我的,把精力focus到真正重要的事情上来,停止无意义的争吵。

这其实也是一种哲学:给你(我认为)最好的,而不是给你选择。就像iPhone一样,用户太笨了,他们根本不知道自己需要什么,就让帮主替你安排好一切吧。

不过GO强加个人喜好的一刀切做法,也招致批评和厌恶。有比较刚的程序员,直接因为大括号不让换行而抛弃GO。

作为一个经历过各种妖媚代码格式要求的程序员,我发出了杠铃般的笑声。

#GO的特色

GO是介于C与C++之间的语言,比C抽象层次高,比C++抽象层次低。

因为是一门新的编程语言,站在巨人的肩膀,博采众长,规避了一些已知的问题,开发了一些优秀的特征,相比C/C++,GO的核心特征包括以下几个方面:

1. 原生并发,以东尼·霍尔的通信顺序进程(CSP)为基础的goroutine,适合现代多核机器

2. 垃圾回收,非常高效(请来世界顶级内存管理专家设计)

3. 强大的标准库,对网络编程等的良好支持

4. CGO提供了GO调用C机制,扩展了GO的能力边界

5. 内嵌关联数组

6. 非侵入式的接口设计

7. 简单清晰的语法,以及强编码规则,好处可能远超想象

#GO vs C/C++

GO是更好的编程语言吗?

[GO与C语法详细对比](http://hyperpolyglot.org/c)

#性能对比

虽然GO号称兼备C++的运行效率和PHP的开发效率,但benchmarks好像打脸了,从数据上看,GO的运行效率接近却略低于Java

GO是更好的编程语言吗?

#研发效率

GO是更好的编程语言吗?

我乐观预计GO的研发效率上优于C/C++,特别是*nix环境下。

#流行度

GO获得TIOBE 2016年度最佳,2017年10月获得第10,历史最高排名。

GO诞生10年,虽然背靠Google,但依然没有挺进编程语言第一阵营 ,属于外围三线。

近一年多流行度排名有所下滑,铁打的Java、C/C++,流水的编程语言。

GO是更好的编程语言吗?

#工程化水平

GO是更好的编程语言吗?

知名项目:

Docker:大名鼎鼎的开源应用容器引擎

K8S:容器编排管理系统的事实标准

GO更适合开发服务器端大型软件,高性能分布式系统领域,网络编程,并发编程,被誉为云时代的C语言。

GO成为云计算时代流行起来,促进了云计算的发展,Google用GO的多,今日头条、Uber等公司也用GO对业务进行了彻底的重构,golang.org YouTube.com也在使用GO开发。

美国市值TOP20有一半在使用GO,国外很多初创公司选择GO,国内关注高,但还未得到广泛应用,应用上呈现国外热国内冷的特点

Go语言目前所面临的最大问题在于,还没有足够的经验来证明GO是否真的是一个成功的产品,缺少足够多超大型应用的实践。

总体而言,GO的工程化水平低于C/C++和Java等第一梯队语言。

#争议和不足

GO最初声称为了解决Google的问题而设计,为了帮助人们阅读、调试和维护软件而生,但目前为止,难言圆满。

GO的异常处理经常被吐槽,GC提高了安全性却失去了控制力,组合代替继承真的好吗?包管理做的好吗?

摒弃先入为主的观念影响,重新客观审视GO语言,我觉得在语言设计层面,GO确实更自然、更简约。比如摒弃行尾的分号,比如if/for不需要圆括号包裹条件,放空内心去想,好像真的更合理。

GO抖掉了C++的诸多包袱,让程序简单,也更容易理解(特别是相对于C),但是随着GO的发展,语法也有可能变重,比如GO 2.0版又把它之前批评的泛型引入了,当初GO批评别人的点又有可能反过来被别人批评。

而关于语法层面是否真的更简洁,也是有争议的,三目运算符不支持+强制大括号让一行C代码变成多行GO真的更简单了吗?比如编程语言专家庄晓立(Liigo)在CSDN上有吐槽的文章,仔细读来,也有一定道理,我贴一个链接,可以参考一下。

原文链接:https://blog.csdn.net/liigo/article/details/23699459

另外GO是G公司的,虽然目前开源,但会不会哪天也像Oracle一样,穷疯了便开始薅羊毛,Oracle Java JDK已经开始割韭菜了,所以GO智慧产权的风险依然存在,而C/C++已经是宇宙人类的了,世界性的标准化组织在控制管理,风险无穷逼近于零。

#小结

GO在一些点确实有突破,比如让并发编程更容易、运行更高效,比如垃圾回收让程序更安全,比如基于消息(Channel)编程的支持,比如内嵌关联结构,这些都很赞,也很重要。编程语言发展这么多年,任何突破都是艰难和宝贵的。

Goroutine是GO的杀手锏,经过GO改造后的系统有更高并发量和IO吞吐率

GO跟C非常像,这并不奇怪,因为设计师都是C语言大师,C/C++程序员很容易切换到GO,但Java程序员转GO可能要困难一些。

另一个隐患就是在Java占主导的生态中,GO显得比较小众,跟其他中间件的融合也存在潜在风险,引入复杂性甚至混乱。

回到标题的问题,GO是更好的语言吗?GO是理想的编程语言吗?说实话,我不知道,而且我的观点也不重要,这似乎是一个哲学问题。

是否要选择GO作为项目开发语言,我认为不应该被GO声称的优势迷惑,因为你去google任何一门语言,都能收获一堆优点,PR会自然而然的对缺点选择性忽视。

也不能因循守旧,而应该仔细辨别,你度量什么便得到什么。GO是否适合你的项目,GO的新特点和优势对你的项目是否真有必要,是否真有帮助,能给你的项目带来什么好处?比如你写一个单机游戏程序可能GO网络库的简便对你而言为零,所得收益跟你付出新学一门语言的成本相比如何?同时,它的缺陷是什么?你是否全面理解?

有时候,它或许就像一位花枝招展的姑娘,待你抛弃一切去拥抱它的时候,你会发现,它的美好只是存在于你的幻想中,当然也有另一种可能,它真的非常好,很适合你,恭喜你,热情的拥抱它吧。

GO是更好的编程语言吗?

GO有它适应场景,比如适合网络程序、云应用、微服务、高性能分布式、大型多人协同,可能在开发效率上有非常大的提升,清晰度上也有提高,可能是理想的首选。

或许我会尝试用GO开发新项目或者改写老项目,谁知道呢?这取决于权衡折中,取决于领导决断,也取决于我的心情。

#附录

一段GO的示例代码,品味一下GO的STYLE

GO是更好的编程语言吗?
]]>
/9/server/87271.html/feed 0
通俗易懂网络协议(TCP/IP概述) /9/87268.html /9/87268.html#respond Wed, 09 Oct 2019 02:13:16 +0000 /?p=87268 近期工作,跟网络协议相关,这让我有机会更深入学习网络协议,而之前很长一段时间,我对网络协议的理解都停留在比较浅的层面。

比如:TCP是面向连接的、可靠传输,而UDP是非连接的、不可靠传输,TCP建连需要3次握手,会造成delay,UDP更快。

比如:socket编程,服务器socket create、bind、listen、accept、read/write、shutdown/close,客户端socket create、connect、read/write、shutdown/close,再加上epoll/select这几下子。

再比如:我知道网络编程要忽视SIGPIPE信号不然会挂,read返回0代表对端主动关闭,非阻塞的read要放在循环里要考虑返回值,多路复用以及阻塞、非阻塞的区别。

TCP/UDP的区别上,我是这样理解的:从北京到杭州,TCP相当于修了一条高铁线路(建连)再通车发货(传输数据),而UDP相当于寄快递,丢了不管(直接传输数据)。

上面的理解对不对?可以说对,也可以说不对。对于应用程序员来说,有了上面的认识+熟悉socket编程接口,够了吗?不够吗?

大物理学家费曼提出一个高效的费曼学习法,即从问题入手,试着把问题都讲出来,以教代学,一旦你能把问题都讲清楚,便学会了。所以我想尝试一下把TCP/IP讲清楚,借此让自己学明白,顺便帮助一下读者。

虽然《TCP/IP详解卷1》是一本关于互联网协议族很严谨详尽的书,但在我看来,它稍微有点晦涩,可能需要读几遍,才能心领神会。虽然我没有能力把这个问题说的更好,但因为我经历过从稀里糊涂到稍有所悟的过程,这可能是大师不可比的,我将尽量用通俗易懂的语言把TCP/IP相关的知识讲清楚。

TCP/IP是什么

TCP/IP协议族是一组协议的集合,也叫互联网协议族用来实现互联网上主机之间的相互通信。TCP和IP只是其中的2个协议,也是很重要的2个协议,所以用TCP/IP来命名这个互联网协议族,实际上,它还包括其他协议,比如UDP、ICMP、IGMP、ARP/RARP等。

网络分层

大学《计算机网络》教科书上有经典的网络ISO七层模型,但七层划分太细了,稍显繁琐,不容易记住。

互联网协议族TCP/IP按粗粒度的四层划分,两种划分的对照图让彼此关系一目了然。

通俗易懂网络协议(TCP/IP概述)

分层是计算机领域的常用技巧,比如互联网后端的三层架构“接入-逻辑-存储”就是分层思想的典型应用。

分层是为了隔离,通过分层划分职能,拆解问题,层与层之间约定接口,屏蔽实现细节。

TCP/IP自下到上划分为链路层、网络层、传输层、应用层。下层向上层提供能力,上层利用下层的能力提供更高的抽象

1. 链路层,也称网络接口层,包括操作系统的设备驱动程序和网卡,它们一起处理与传输媒介(光纤等)的物理接口细节。

2. 网络层,也就是IP层,负责处理IP datagram在网络中的传输,IP层传输的是IP datagram,借助路由表,把IP datagram从网络的一端传输到另一端,简而言之:IP实现包的路由传输,IP协议和路由器工作在网络层。

3. 传输层,提供端到端之间的通信,包括提供面向连接和高可靠性的TCP,以及无连接不可靠的UDP。貌似TCP更好,但实际不是这样,UDP因为不需要建连开销,所以更快,应用得也很广,比如新一代互联网协议HTTP3就从TCP转向UDP,应根据适应场景选择传输层协议。

4. 应用层,跟应用相关,不同应用解决不同问题,需要不同的应用层协议。

通俗易懂网络协议(TCP/IP概述)

链路层处理数据在媒介上的传输,以及主机与网卡、光纤等打交道的细节。因为与硬件相关,所以需要借助系统的驱动程序,链路层协议就是定义这些细节的,比如怎么把数据从网卡发送到光纤,采用什么格式编码等,它解决的数据在媒介上表示、流动的问题。

光有链路层功能肯定是不够的,网络上有成千上万的机器,主机A与B通信,你不能将数据发到主机C,所以仿照现实,要为主机分配网络地址,通过IP地址去标识网络中的一台主机,发送一个数据包,需要正确路由到目的地,这就好比你从家到公司,要经过哪些路径,需要地图,而路由表就类似这张地图。IP解决的是数据包在网络中的传输路由的问题。

有了网络层的传输路由能力,还不够,因为IP报在传输过程中可能丢包,比如中间经历过的路由器缓冲区满了便会丢包,这样不可靠,如果需要可靠传输的能力,便需要传输层基于IP层,提供更多的能力,TCP解决了可靠性问题。具体而言,如果丢包了,TCP层会负责超时重传,它通过接收确认和重传机制保证了可靠传输。另外,因为IP报都是独立路由的,所以从主机A到主机B,一份数据被拆分成x、y两个IP报先后发送,这2个包可能选择不同的传输路径,这样有可能y包先于x包到达,但我们希望在接收端(主机B)恢复这个数据的信息,但我们无法控制IP报的到达顺序,所以,我们需要在接收端恢复数据,我只需要在x、y包里记录它属于数据块的哪个部分,然后重组这份数据,这正是TCP做的,它会重新组装IP报,从而保证顺序性,递交给应用层。

有时候并不需要保证可靠性和顺序性,这便是UDP能提供的,它只是简单的把数据封装成IP报,然后通过IP层路由发送到目的端。

再往上,便是应用层协议了,比如http,又比如游戏服务器自定义协议,应用层协议通常基于TCP或者UDP做传输。

分层

什么是协议?懒得去翻协议的各种权威定义了,我认为协议就是约定,跟现实生活中协议这个词含义差不多。网络协议就是通信双方共同遵守的约定,更具体一点,就是定义数据在网络上传输的格式、规则和流程。

因为网络是分层模型,不同层有不同层的作用,所以为各层定义各层的规则,各层对应的各层协议。

前面讲了TCP/IP协议族包含很多协议,这些协议分属不同的分层,承担不同的作用。

通俗易懂网络协议(TCP/IP概述)
  1. TCP和UDP是两种主要的传输层协议。
  2. IP是网络层的主要协议,TCP、UDP都需要利用IP协议进行数据传输。
  3. ICMP是互联网控制报文协议,是IP的附属协议,IP层用它来与其他主机或路由器交换错误报文和其他重要信息。比如一个Packet经过某个路由器节点的时候,超过网络对Packet的长度限制,而又不分片,则会给发送端发送一个ICMP包报告错误信息,属于ICMP是用来辅助IP完成数据包传输的。
  4. IGMP是Internet组管理协议,用来把一个包多播到多个主机。
  5. ARP(地址解析协议)和RARP(逆地址解析协议)是用来转换IP层和链路层的地址,IP层使用IP地址,链路层使用Mac地址

应用层和传输层使用端到端(end-to-end)协议,网络层提供的是逐跳(hop-by-hop)协议。

封装

A给B通过网络传送一块数据,可以设想仅仅是传输这块原始数据是不够的,因为网络传输过程中,网络包到了某个路由器,需要转发,而转发必须依赖数据包的一些附加信息,比如目标机器。

发送端在发送数据的时候,将原始数据按照协议格式加上一些控制信息,包装成可在网络上正确传输数据包的过程叫封装。

TCP/IP协议族是层层封装的,从应用层到链路层,每经过一层都要添加一些额外信息(首、尾部)。

通俗易懂网络协议(TCP/IP概述)
  1. 用户数据经过应用程序加上应用程序首部,转给TCP层处理
  2. 经过TCP层加上TCP首部,产生TCP段(segment)
  3. TCP segment经过IP层再加上IP首部,产生IP数据包(datagram)
  4. IP datagram通过链路层,经以太网驱动程序处理后,加上以太网首部+尾部,产生以太网帧(frame),以太网帧的长度在46~1500之间

更准确的说,在IP和链路层传输的数据单元叫分组(Packet),分组既可以是一个IP datagram也可以是IP datagram的一个分片(fragment)。

UDP的封装跟TCP略有不同,主要体现在经过传输层(UDP)之后添加的是8字节UDP首部,产生UDP datagram。

封装过程中,经过TCP/UDP层的时候,会把端口号添加到TCP/UDP首部;经过IP层的时候,会把协议类型(TCP or UDP or ICMP or IGMP)添加到IP首部;经过链路层的时候,会把帧类型(IP or ARP or RARP)添加到以太网首部。这些信息将被用于接收端的处理。

接收端收到数据后,要执行跟发送端相反的解封操作,我们可以把发送端的数据封装比喻成洗澡后一层层穿衣服,而接收端的操作,类似洗澡前一层层脱衣服,把首尾部剥离,获取传递的原始数据。

因为网络上的主机有不同字节序,现在要通过网络传输,便需要约定统一的网络字节序(大端序),采用小端序的主机在网络传输数据的时候要转为大端序。

地址

互联网上每个接口都有一个唯一的网络地址,也叫IP地址,IP地址有IPv4和IPv6两个版本,IPv4是32位4字节的整数,每个字节(8bit)的取值范围是0~255,所以可以把4字节的IPv4用四个点分隔的byte值表示,比如140.252.13.88,每个十进制数值对应32位整数中的每个字节,这种表示法叫点分十进制表示法,很显然,点分十进制法和int32两种表示法之间很容易相互转换。

IPv4地址划分为ABCDE五类,32位地址表示的数值空间有限,难以为互联网上的所有联网设备分配独立的IP地址,所以便存在动态分配、共享、公网+内网地址转化(NAT)等问题,本质上是为了解决IP地址不够用的问题

IPv6使用128bit,2的128次方就非常大了,号称可以为地球上每粒沙子分配一个ip地址。

IP数据报(网络层)用IP地址、而以太网帧(链路层)则是用硬件(48位Mac)地址,ARP和RARP用于IP地址和硬件地址之间做映射(转换)。

端口

TCP/UDP采用16位端口号来识别(区分)应用,比如主机A向主机B发送了一个IP报,主机B的内核收到该IP报之后,应该交给哪个应用程序去处理呢?端口号就是用来干这个的,内核会维护端口号到应用程序之间的对应关系。

比较常用的应用层协议有约定的端口号,也就是知名端口号,而1024~5000之间的端口号是分配给TCP/IP临时用的,而大于5000的另做他用。也就是说,你用TCP方式去连网络服务器,本地为该socket分配的端口号会在1024~5000之间,这取决于操作系统的端口分配策略。

域名系统

域名系统(DNS)提供主机名字和IP地址之间的转换,比如www.baidu.com是一个域名,应用程序可以通过一个标准库函数(gethostbyname)来获得给定名字主机的IP地址,标准库函数(gethostbyaddr)实现逆操作。

ip地址是一串数字,含义不清、也不便于记忆,主机名含义更清晰,www.baidu.com你就很容易记住,这也是为什么存在IP地址还需要主机名的原因。

分用

接收端接收到以太网数据帧(Frame)之后,需要像剥洋葱一样,从协议栈由底向上升,即遵照链路层->网络层->传输层->应用层的顺序,去掉各层协议添加的首尾部,将数据取出,交给最上层应用程序,这个过程叫Demultiplexing,尊从书本的翻译叫分用

通俗易懂网络协议(TCP/IP概述)

回顾前面封装的描述,在传输层、网络层、链路层,分别将端口号存入TCP/IP首部,将协议类型存入IP首部,将帧类型存入以太网帧首部。所以在接收端,将一层层拆掉首部,取出对应信息,然后做分派,丢给不同模块处理,上图就是整个处理过程。

小结

本文讲了地址、域名、端口、TCP/IP分层模型、封装、分用等概念。

你最好能记住TCP/IP链路层->网络层->传输层->应用层的四层划分。

TCP segment、UDP datagram、IP datagram、IP fragment、以太网frame、以及IP层和链路层之间传输的数据单元packet,这些概念你最好分清楚,这样交谈的时候会显得比较专业而不是很土。

数据封装,多看几遍你便能记住了。

TCP封装格式:以太网首部(14)+IP首部(20)+TCP首部(20)+应用数据+以太网尾部(4)

UDP封装格式:以太网首部(14)+IP首部(20)+UDP首部(8)+应用数据+以太网尾部(4)

应用层协议在应用层实现,而传输层、网络层、链路层都是在内核实现,所以想修改或者优化底层协议很难,因为你几乎动不了内核,因为网络上的大量设备OS你没法一并改过来,这就是所谓的网络设备僵化问题,HTTP3用UDP替代TCP,就是想在应用层自己去实现可靠传输等。

每个以太网帧有长度限制(48~1500),网络上每个设备也有对包的长度限制,IP报大了就要分片,分片可能发生在发送端,也有可能发生在中间设备,但应该尽量避免分片,IP报会带有信息让分片后可以重组,MTU的概念可以了解一下。

ICMP和IGMP逻辑上属于网络层,因为他们是IP协议的附属协议,但实际上,ICMP和IGMP报文都被封装为IP datagram传输,所以又可以把他们视为IP层之上的协议。

同样ARP和RARP用于IP地址和硬件MAC地址相互转换,逻辑上属于链路层,但实际上arp和rarp报文跟IP datagram一样,都被封装成以太网Frame传输

接收端收到以太网帧之后,会走分用流程,最终将原始数据交给应用程序。

TCP/IP协议的应用程序经常使用socket编程接口。

有很多跟网络相关的工具,比如ping、ifconfig、netstat、arp、tcpdump、wireshark等。

问题

一年前,我对网络编程这块,脑子里充满疑问。

众所周知,TCP建连三次握手和断连四次握手,但如前所述,任何时候,从主机A都可以任意发一个IP报到主机B,网络主机之间是通过IP层实现路由转发的,两点之间的每个IP报都是独立路由的,既然这样,为什么还要建连?还要浪费时间做A->B、B->A、A->B来回?直接把包发过去不就完了吗?

假设通过AB建立的3个IP报的作用是表示AB之间的网络连通性?哪又有什么作用?因为网络是随时变化的,此刻连通又不代表下一刻连通。建连之后似乎并不存在AB之间的真正连接,只是两端OS层面维护的一个状态(数据对象)?是虚拟连接?

建连到底是什么意思?客户端发送一个IP报到服务器去发起连接?那跟传输数据的普通IP报又有什么区别?

双工是什么意思?为什么socket关闭一半传输之后就不能发送数据了?网络上IP报不是可以任意传输吗?这个限制是哪个地方添加的?

拥塞控制是什么?Nagle是什么?滑动窗口是什么?TCP为什么要保活?

socekt的编程接口和各种概念跟TCP/IP原理有怎样的对应关系?学完TCP/IP原理对理解socket编程有什么帮助?

没有深究TCP/IP原理之前,我其实是有很多问题的,只是做应用程序开发,好像没搞懂那些问题也还可以凑合干,但终究是有点糊里糊涂,感觉不太爽。

本来我想一篇文章讲清楚TCP/IP的主要内容,但是写着写着发现,这样文章会非常长,所以我决定多写几篇,每篇都讲清楚一个主题。

]]>
/9/87268.html/feed 0
Python 3.8 一周后发布,这几个特性值得关注 /6/87265.html /6/87265.html#respond Tue, 08 Oct 2019 06:54:44 +0000 /?p=87265 10月1日,Python 3.8rc1 发布,如果没有意外的话 3.8 将于 10 月 14 日正式发布。新版本的变化有很多,但是我觉得可能最常被用到的,是下面这两个新特性:海象运算符和仅位置参数。

海象运算符 :=

海象运算符是 3.8 版本中最引人瞩目的新特性,因其 :=外观而被称为海象运算符(walrus operator)。引入该运算符的是 PEP 572,而也正是由于 PEP 572 被接受过程中的一些不愉快,导致了 Guido van Rossum 因此辞去了 BDFL 的职位。

有了这个运算之后,我们可以在 if 或 while 语句中使用 :=为变量赋值,其目的也是为了简化多模式匹配和非可迭代对象的循环等问题。

比如说,多模式匹配的写法会从:

m = re.match(p1, line)if m:return m.group(1)else:m = re.match(p2, line)if m:return m.group(2)else:m = re.match(p3, line)...

变成:

if m := re.match(p1, line):return m.group(1)elif m := re.match(p2, line):return m.group(2)elif m := re.match(p3, line):...

而针对非可迭代对象的循环,也可以从:

ent = obj.next_entrywhile ent:... # process entent = obj.next_entry

变成这样:

while ent := obj.next_entry:... # process ent

这可以让程序员更清晰地表达自己的意图。这个功能其实是许多其他语言已经具备的,但是Python中已经缺失近30年。

相较于由它给Python社区带来的变动,这个特性本身带来的变化就不那么明显了。

使用 f-string 调试

Python 3.6 中就加入了 f-string(也被称为格式化字符串),但是在调试输出时的代码写法会显得比较重复:

print(f'foo={foo} bar={bar}')

在 3.8 中,可以改用如下更简洁的写法:

print(f'{foo=} {bar=}')

两种写法的输出是一样的。

此外,还支持使用修饰符来改变输出的类型,比如 !s代表使用str而非repr的输出:

>>> import datetime>>> now = datetime.datetime.now>>> print(f'{now=} {now=!s}')now=datetime.datetime(2019,7,16,16,58,0,680222) now=2019-07-1616:58:00.680222

仅位置参数(position-only)

新引入了一个函数参数语法 /,表示函数的某些参数必须按位置指定,不能用作关键字参数。

下面这个例子中,参数 ab只能是位置参数,而cd可以是位置参数,也可以是关键字参数,ef则要求是关键字参数:

def f(a, b, /, c, d, *, e, f):print(a, b, c, d, e, f)

可以这样调用该函数:

f(10,20,30, d=40, e=50, f=60)

但是不能这样调用:

f(10, b=20, c=30, d=40, e=50, f=60) # b 不可以是关键字参数f(10,20,30,40,50, f=60) # e 必须是关键字参数

该语法的一个用处,是支持纯 Python 函数完整地模拟用 C 编写的函数的行为。例如,内置的 pow函数是不接受关键字参数的:

def pow(x, y, z=None, /):"Emulate the built in pow function"r = x ** yreturn r if z is None else r%z

另外一个用处,是在参数名作用不大的情况下避免使用关键字参数。例如,内置的 len函数的标记是len(obj,/),这样可以避免下面尴尬的调用方式:

len(obj='hello') # obj 关键字降低了可读性

还有一个好处,就是支持以后在不破坏客户端代码的前提下修改参数的名称。例如,在 statistics 模块中,未来可能会调整的参数名 dist,如果像下面这样创建函数的话就可以实现:

def quantiles(dist, /, *, n=4, method='exclusive')...

由于 / 左侧的参数并没有暴露为关键字,意味着我们后续可以在 kwargds中继续使用该关键字:

>>>>>> def f(a, b, /, **kwargs):... print(a, b, kwargs)...>>> f(10,20, a=1, b=2, c=3) # a 和 b 有两种用法1020{'a':1, 'b':2, 'c':3}

这样极大地简化了那些需要接受任意关键字参数的函数的实现。下面是 collections 模块的部分实现,体现了仅位置参数的优势。

class Counter(dict):
def __init__(self, iterable=None, /, **kwds):# Note "iterable" is a possible keyword argument
]]>
/6/87265.html/feed 0
提高 JavaScript 性能的 12 个技巧 /9/web/%e5%89%8d%e7%ab%af%e7%9b%b8%e5%85%b3/87262.html /9/web/%e5%89%8d%e7%ab%af%e7%9b%b8%e5%85%b3/87262.html#respond Tue, 08 Oct 2019 02:47:03 +0000 /?p=87262 在创建 Web 应用程序时应始终考虑性能。为了帮助你开始,本文列举了有效提高应用程序性能的 12 种方法。

性能是创建网页或应用程序时最重要的一个方面。没有人想要应用程序崩溃或者网页无法加载,或者用户的等待时间很长。根据 Kissmetrics,47%的访问者希望网站在不到 2 秒的时间内加载,如果加载过程需要 3 秒以上,则有 40%的访问者会离开网站。

考虑到以上这些数字,你在创建 Web 应用程序时应始终考虑性能。为了帮助你开始,以下提供了有效提高应用程序性能的 12 种方法:

1. 在浏览器中缓存

要这样做有两种选择。第一种是使用 JavaScript Cache API,我们可以安装 service worker 来使用它。第二种是使用 HTTP 协议缓存。

访问某个对象通常要用脚本。通过把重复访问的对象存储在用户定义的变量中,以及在后续对该对象的引用中使用变量,可以立即实现性能的提升。

2. 定义执行的上下文

为了有效地衡量你在程序中加入的任何改进,你必须创建一组定义良好的环境,以便测试代码的性能。

对所有 Javascript 引擎的所有版本进行性能测试和优化实际上是不可行的。但是,在单一的环境中进行测试并非一个好习惯,因为你可能会得到片面的结果。因此,建立多个定义良好的环境并测试代码是否有效非常重要。

3. 删除未使用的 JavaScript

此步骤不仅会缩短传输时间,还会缩短浏览器分析和编译代码所需的时间。为此,你必须考虑以下几点:

  • 如果你检测到一个用户未使用的功能,最好删除所有与之相关的 JavaScript 代码,这样网站的加载速度会更快,用户也会有更好的体验。
  • 还有可能,你错误地加入了一个并不需要的库,或者你有依赖项,这些依赖项提供的功能在所有浏览器中原本就有,那么你无需再增加多余的代码。

4. 避免使用太多内存

你应该始终给内存加一条限制,那就是只有绝对必须的内容才能使用内存,因为你无法知道运行应用程序的设备到底需要多少内存。只要你的代码要求浏览器保留新的内存,浏览器的垃圾收集器就会被执行,并停止 JavaScript 的运行。如果经常发生这种情况,页面将变慢。

5. 推迟不必要的 JS 加载

用户希望页面快速加载,但并非所有函数都需要在页面的初始加载时就可用。如果用户必须执行某个操作才能执行某个函数(例如,通过单击某个元素或更改选项卡),那么你可以将该函数的加载推迟到初始页面加载之后。

通过这种方式,你可以避免加载和编译那些会延迟页面初始显示的 JavaScript 代码。页面完全加载后,我们可以再开始加载这些功能,以便它们在用户开始交互时立即可用。在 RAIL 模型中,Google 建议将此延迟加载以 50 毫秒为单位进行,这样就不会影响用户与页面的交互。

6. 避免内存泄漏

如果内存正在泄漏,则加载的页面将保留越来越多的内存,并最终占用设备的所有可用内存并严重影响性能。你可能见过此类故障(并且可能对此类故障感到懊恼),例如在带有轮播或图像滑动条的页面上。

在 Chrome 开发者工具中,你可以通过在“性能”标签中记录时间线来分析你的网站是否存在内存泄漏。通常,内存泄漏的原因是,你从页面中删除了 DOM,但有一些变量还在引用这些 DOM,因此,垃圾收集器无法消除它们。

7. 适当的使用 Web worker

当你执行耗时很长的代码时,请使用 Web worker。根据 Mozilla 开发人员网络 (MDN) 文档:“Web Worker 可以在与 Web 应用程序的主执行线程分开的后台线程中运行脚本操作。这样做的好处是你可以在一个单独的线程中执行耗时又费力的的处理,同时让主(通常为 UI)线程运行而不被阻塞或减慢。”

Web worker 允许代码执行处理器密集型计算,而不阻塞用户界面线程。Web Worker 允许你生成新线程并将工作委托给这些线程以获得高效的性能。这样,通常会阻碍其他任务且需要长时间运行的任务将被传递给 worker,从而让主线程可以在无阻碍的情况下运行。

8. 适当将 DOM 元素保存在局部变量中

访问 DOM 会很慢。如果要多次读取某元素的内容,最好将其保存在局部变量中。但记住重要的是,如果稍后你会删除 DOM 的值,则应将变量设置为“null”,不然会导致内存泄漏。

9. 优先访问局部变量

JavaScript 首先搜索以查看变量是否存在于本地,然后才在更高级别的作用域内逐步搜索到全局变量为止。将变量保存在本地作用域内能让 JavaScript 更快地访问它们。

局部变量是基于最具体的作用域的,并且可能会穿过多个级别的作用域,因此查找这一动作可能导致出现通用的查询。在一个它前面没有变量声明的局部变量中定义函数作用域时,需要在每个变量之前加上 let 或 const,以便定义当前作用域,防止查找并加速代码执行。

10. 避免使用全局变量

因为脚本引擎在从函数或其他作用域内引用全局变量时需要逐一查看作用域,所以当本地作用域丢失时,该变量将被销毁。如果全局作用域中的变量无法在脚本的生命周期内持续存在,则性能将得到改善。

11. 实施一些优化方案

  • 始终使用计算复杂度最低的算法和最佳的数据结构来解决任务。
  • 重写算法以获得相同的结果和更少的计算。
  • 避免递归调用。
  • 给重复的函数加入变量、计算和调用。
  • 分解和简化数学公式。
  • 使用搜索数组:用它们来获取基于另一个的值,而不是使用 switch/case 语句。
  • 使条件总是更有可能为真,以更好地利用处理器的推测执行。
  • 如果可以,请使用位级运算符替换某些操作,因为这些运算符的处理周期较短。

12. 使用工具检测问题

Lighthouse 是一个很好的网页性能工具,它可以帮助你审核性能、可访问性、最佳实践和 SEO。谷歌 PageSpeed 旨在帮助开发人员了解网站的性能优化和潜在可改进的方面。这些组件旨在识别网站是否符合 Google Web 性能最佳实践,以及将调整过程自动化。

在 Chrome 中,你还可以使用主菜单中的“更多工具”选项来查看每个选项卡使用的内存和 CPU。对于更高级的分析,你可以使用 Firefox 或 Chrome 中的开发人员工具“性能”视图来分析不同的指标,例如:

提高JavaScript性能的12个技巧

devtools 的性能分析允许你在加载页面时模拟 CPU 消耗、网络和其他指标,以便识别和修复问题。

提高JavaScript性能的12个技巧

为了更深入地了解,建议你使用 JavaScript Navigation Timing API,它允许你详细测量代码的每个部分从编程本身中获取的内容。

对于基于 Node.js 构建的应用程序,NodeSource Platform 也是一种非常好、影响低的方式,它可以在非常精细的级别上探索应用程序性能。

全面的 Node.js 指标可帮助你识别内存泄漏源或其他性能问题,并更快地解决这些问题。

最后的说明

在代码的可读性和优化之间保持平衡很重要。代码由计算机解释,但我们需要确保代码将来可以由我们自己或其他人维护,因此它们需要易于理解。

请记住:应始终考虑性能,但不应将性能凌驾于错误检测和功能添加之上。

]]>
/9/web/%e5%89%8d%e7%ab%af%e7%9b%b8%e5%85%b3/87262.html/feed 0
4 种开源云安全工具 /31/112/87259.html /31/112/87259.html#respond Tue, 08 Oct 2019 02:06:31 +0000 /?p=87259 4 种开源云安全工具

查找并排除你存储在 AWS 和 GitHub 中的数据里的漏洞。

— Alison Naylor,anderson Silva(作者)

如果你的日常工作是开发者、系统管理员、全栈工程师或者是网站可靠性工程师(SRE),工作内容包括使用 Git 从 GitHub 上推送、提交和拉取,并部署到亚马逊 Web 服务上(AWS),安全性就是一个需要持续考虑的一个点。幸运的是,开源工具能帮助你的团队避免犯常见错误,这些常见错误会导致你的组织损失数千美元。

本文介绍了四种开源工具,当你在 GitHub 和 AWS 上进行开发时,这些工具能帮助你提升项目的安全性。同样的,本着开源的精神,我会与三位安全专家—— Travis McPeak ,奈飞高级云安全工程师; Rich Monk ,红帽首席高级信息安全分析师;以及 Alison Naylor ,红帽首席信息安全分析师——共同为本文做出贡献。

我们已经按场景对每个工具都做了区分,但是它们并不是相互排斥的。

1、使用 gitrob 发现敏感数据

你需要发现任何出现于你们团队的 Git 仓库中的敏感信息,以便你能将其删除。借助专注于攻击应用程序或者操作系统的工具以使用红/蓝队模型,这样可能会更有意义,在这个模型中,一个信息安全团队会划分为两块,一个是攻击团队(又名红队),以及一个防守团队(又名蓝队)。有一个红队来尝试渗透你的系统和应用要远远好于等待一个攻击者来实际攻击你。你的红队可能会尝试使用 Gitrob ,该工具可以克隆和爬取你的 Git 仓库,以此来寻找凭证和敏感信息。

即使像 Gitrob 这样的工具可以被用来造成破坏,但这里的目的是让你的信息安全团队使用它来发现无意间泄露的属于你的组织的敏感信息(比如 AWS 的密钥对或者是其他被失误提交上去的凭证)。这样,你可以修整你的仓库并清除敏感数据——希望能赶在攻击者发现它们之前。记住不光要修改受影响的文件,还要 删除它们的历史记录 。

2、使用 git-secrets 来避免合并敏感数据

虽然在你的 Git 仓库里发现并移除敏感信息很重要,但在一开始就避免合并这些敏感信息岂不是更好?即使错误地提交了敏感信息,使用 git-secrets 可以避免你陷入公开的困境。这款工具可以帮助你设置钩子,以此来扫描你的提交、提交信息和合并信息,寻找常见的敏感信息模式。注意你选择的模式要匹配你的团队使用的凭证,比如 AWS 访问密钥和秘密密钥。如果发现了一个匹配项,你的提交就会被拒绝,一个潜在的危机就此得到避免。

为你已有的仓库设置 git-secrets 是很简单的,而且你可以使用一个全局设置来保护所有你以后要创建或克隆的仓库。你同样可以在公开你的仓库之前,使用 git-secrets 来扫描它们(包括之前所有的历史版本)。

3、使用 Key Conjurer 创建临时凭证

有一点额外的保险来防止无意间公开了存储的敏感信息,这是很好的事,但我们还可以做得更好,就完全不存储任何凭证。追踪凭证,谁访问了它,存储到了哪里,上次更新是什么时候——太麻烦了。然而,以编程的方式生成的临时凭证就可以避免大量的此类问题,从而巧妙地避开了在 Git 仓库里存储敏感信息这一问题。使用 Key Conjurer ,它就是为解决这一需求而被创建出来的。有关更多 Riot Games 为什么创建 Key Conjurer,以及 Riot Games 如何开发的 Key Conjurer,请阅读 Key Conjurer:我们最低权限的策略 。

4、使用 Repokid 自动化地提供最小权限

任何一个参加过基本安全课程的人都知道,设置最小权限是基于角色的访问控制的最佳实现。难过的是,离开校门,会发现手动运用最低权限策略会变得如此艰难。一个应用的访问需求会随着时间的流逝而变化,开发人员又太忙了没时间去手动削减他们的权限。 Repokid 使用 AWS 提供提供的有关身份和访问管理(IAM)的数据来自动化地调整访问策略。Repokid 甚至可以在 AWS 中为超大型组织提供自动化地最小权限设置。

工具而已,又不是大招

这些工具并不是什么灵丹妙药,它们只是工具!所以,在尝试使用这些工具或其他的控件之前,请和你的组织里一起工作的其他人确保你们已经理解了你的云服务的使用情况和用法模式。

应该严肃对待你的云服务和代码仓库服务,并熟悉最佳实现的做法。下面的文章将帮助你做到这一点。

对于 AWS:

  • 管理 AWS 访问密钥的最佳实现
  • AWS 安全审计指南

对于 GitHub:

  • 介绍一种新方法来让你的代码保持安全
  • GitHub 企业版最佳安全实现

同样重要的一点是,和你的安全团队保持联系;他们应该可以为你团队的成功提供想法、建议和指南。永远记住:安全是每个人的责任,而不仅仅是他们的。

]]>
/31/112/87259.html/feed 0
深度好文:PHP写时拷贝与垃圾回收机制 /9/20/87255.html /9/20/87255.html#respond Sun, 29 Sep 2019 07:18:43 +0000 /?p=87255 写入拷贝(Copy-on-write,简称COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时要求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

PHP中的COW

注意:以下代码基于PHP5.6,PHP7之后引用计数机制有变化。

大家都知道,PHP是由C实现的,可是C是强类型语言,PHP怎么做到弱类型语言。一起来看下,PHP变量在C语言底层中的代码:

typedef struct _zval_struct zval;
typedef unsigned int zend_uint;
typedef unsigned char zend_uchar;

struct _zval_struct {
zvalue_value value; /*注意这里,这个里面存的才是变量的值*/
zend_uint refcount__gc; /*引用计数*/
zend_uchar type; /* 变量当前的数据类型 */
zend_uchar is_ref__gc; /*变量是否引用*/
};
typedef union _zvalue_value {
long lval; /*PHP中整型的值*/
double dval; /*PHP的浮点数值*/
struct {
char *val;
int len;
} str; /*PHP的字符串*/
HashTable *ht; /*数组*/
zend_object_value obj; /*对象*/
} zvalue_value;

PHP的变量,低层是一个结构体zval,里面的zvalue_value结构体实际上是个联合体,这个联合体才是实际存放着PHP的变量值。 Zend引擎为了区别同一个zval地址是否被多个变量共享,引入了ref_countis_ref两个变量进行标识。

运行以下代码,观察变量refcount的变化:

<?php 
$foo = 1;
xdebug_debug_zval('foo');
$bar = $foo;
xdebug_debug_zval('foo');
$bar = 2;
xdebug_debug_zval('foo');
?>
//-----执行结果-----
foo: (refcount=1, is_ref=0)=1
foo: (refcount=2, is_ref=0)=1
foo: (refcount=1, is_ref=0)=1

当$foo被赋值时,$foo变量的值的只由$foo变量指向。当$foo的值被赋给$bar时,PHP并没有将内存复制一份交给$bar,而是把$foo和$bar指向一个地址, 同时引用计数增加1,也就是新的2。随后,我们更改了$bar的值,这时如果直接需该$bar变量指向的内存,则$foo的值也会跟着改变。这不是我们想要的结果。于是,PHP内核将内存复制出来一份,并将其值更新为赋值的:2(这个操作也称为变量分离操作),同时原$foo变量指向的内存只有$foo指向,所以引用计数更新为:refcount=1。

下面让我们看一个查看内存的例子,可以更容易看到COW在内存使用优化方面的明显作用:

<?php 
$j = 1;
var_dump(memory_get_usage());
$tipi = array_fill(0, 100000, 'php-internal');
var_dump(memory_get_usage());
$tipi_copy = $tipi;
var_dump(memory_get_usage());
foreach($tipi_copy as $i){
$j += count($i);
}
var_dump(memory_get_usage());
//-----执行结果-----
$ php t.php
int(630904)
int(10479840)
int(10479944)
int(10480040)

上面的代码比较典型的突出了COW的作用,在数组变量$tipi被赋值给$tipi_copy时,内存的使用并没有立刻增加一半,在循环遍历数$tipi_copy时也没有发生显著变化,在这里$tipi_copy和$tipi变量的数据共同指向同一块内存,而没有复制。

也就是说,即使我们不使用引用,一个变量被赋值后,只要我们不改变变量的值 ,也不会新申请内存用来存放数据。据此我们很容易就可以想到一些COW可以非常有效的控制内存使用的场景:只是使用变量进行计算而很少对其进行修改操作,如函数参数的传递,大数组的复制等等等不需要改变变量值的情形。

引用计数原理

了解了php变量的内部存储结构之后,再了解下php变量赋值相关的原理和早期垃圾回收机制。

PHP5.2中使用的内存回收算法是大名鼎鼎的Reference Counting,这个算法中文翻译叫做“引用计数”,其思想非常直观和简洁:为每个内存对象分配一个计数器,当一个内存对象建立时计数器初始化为1(因此此时总是有一个变量引用此对象),以后每有一个新变量引用此内存对象,则计数器加1,而每当减少一个引用此内存对象的变量则计数器减1,当垃圾回收机制运作的时候,将所有计数器为0的内存对象销毁并回收其占用的内存。

内存泄漏

但是php5.3版本之前的垃圾回收机制存在一个漏洞,即当数组或对象内部子元素引用其父元素,而此时如果发生了删除其父元素的情况,此变量容器并不会被删除,因为其子元素还在指向该变量容器,但是由于所有作用域内都没有指向该变量容器的符号,所以无法被清除,因此会发生内存泄漏,直到该脚本执行结束

如果你已经安装了Xdebug,你能通过调用函数 xdebug_debug_zval()显示”refcount”和”is_ref”的值。

举例:

由于该示例不好输出结果,用图表示,如图:

深度好文:PHP写时拷贝与垃圾回收机制

举例:

unset($a);
xdebug_debug_zval('a');

如图:

深度好文:PHP写时拷贝与垃圾回收机制

根缓冲机制

php5.3版本之后引入根缓冲机制,即php启动时默认设置指定zval数量的根缓冲区(默认是10000),当php发现有存在循环引用的zval时,就会把其投入到根缓冲区,当根缓冲区达到配置文件中的指定数量(默认是10000)后,就会进行垃圾回收,以此解决循环引用导致的内存泄漏问题

为什么内存没有全部收回来

因为php的核心结构Hashtable,在定义的时候不可能一次性分配足够多的内存块,所以初始化的时候只会分配一小块,等不够的时候在进行扩容,而Hashtable只扩容不减少,所以当存入100个变量的时候符号表不够用了就进行一次扩容,当unset()时只是放了为变量值分配的内存,但是为变量名分配的内存还是在符号表中的,符号表并没有缩小,所以没收回来的内存是被符号表占去了。

php并不是只要内存不够就去向OS申请内存,而是先申请一大块内存,然后将其中一部分分给申请者,这样再有逻辑需要申请内存的时候,就不需要再向OS申请内存了,避免了重复申请,只有当一大块内存不够用的时候再去申请。而当释放内存时,php并非把内存还给了OS,而是把内存轨道自己维护的空闲内存列表,以便重复利用。

垃圾回收相关的配置

  • zend.enable_gc,默认值为on,如果想关闭垃圾回收机制,可以设置为off

小知识点

  • unset():unset()只是断开一个变量到一块内存区域的连接,同时将该内存区域的引用计数减1,内存是否回收主要还是看refcount是否到0了。
  • null:将null赋值给一个变量是直接将该变量指向的数据结构置空,同时将其引用计数归0。
  • 脚本执行结束:该脚本中所有内存都会被释放,无论是否有环引用。
]]>
/9/20/87255.html/feed 0