异常就是程序的运行过程中所发生的不正常的时间。它会终端正在运行的程序。
提示:
在需求分析阶段,如果与系统交互的User超过一类并且相关的User Case超过5个, 使用用例图
来表达更加清晰的结构化需求。
如果系统中模型类超过5个
,并且存在复杂的依赖关系,使用类图
来表达并且明确类之间的关系。 说明:类图像建筑领域的施工图,如果搭平房,可能不需要,但如果建造蚂蚁 Z空间大楼,肯定需要详细 的施工图。
如果系统中某个功能的调用链路上的涉及对象超过3个
,使用时序图
来表达并且明确各调用环节的输入与输出。
说明:时序图反映了一系列对象间的交互与协作关系,清晰立体地反映系统的调用纵深链路。
如果某个业务对象的状态超过3个
,使用状态图
来表达并且明确状态变化的各个触发条件。
说明:状态图的核心是对象状态,首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,再明确触发状态转换的条件是什么。
正例:淘宝订单状态有已下单、待付款、已付款、待发货、已发货、已收货等。比如已下单与已收货这两 种状态之间是不可能有直接转换关系的。
如果系统中超过2个对象
之间存在协作关系,并且需要表示复杂的处理流程,使用活动图
来表示。 说明:活动图是流程图的扩展,增加了能够体现协作关系的对象泳道,支持表示并发等。
提示:
【推荐】
图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于 Web层,也可以直接依赖于 Service层,依此类推。
Dubbo
与springcloud
框架下的工程结构是不一样的。【参考】分层异常处理规约
在 DAO 层,产生的异常类型有很多,无法用细粒度的异常进行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因为日志在 Manager/Service层一定需要捕获并打印到日志文件中去,如果同台服务器再打日志, 浪费性能和存储。在 Service层出现异常时,必须记录出错日志到磁盘,尽可能带上参数信息, 相当于保护案发现场。Manager层与 Service同机部署,日志方式与 DAO层处理一致,如果是 单独部署,则采用与 Service一致的处理方式。Web层绝不应该继续往上抛异常,因为已经处 于顶层,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面, 尽量加上友好的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。
个人理解
:这个地方可以做为强制规范,因为日志的规范打印,对于线上问题的处理能起到很大作用。
这段文字大概是这个意思:
Web层可以通过切面需要统一做异常处理,不允许往外抛异常。
Dao层因为异常种类太多,直接向外抛异常即可。
Service层/Manager层:service层需要打印日志。manager层是否打印日志,与部署关系有关。在大厂中,rpc服务一般是单独部署的,这个时候service与manger层是分在两个不同的进程中,此时,manager需要捕获异常并打印日志,很多开发工程师为了偷懒,在rpc上做切面全部切出日志。如果manager与service是在同一个进程中,则向Dao一样抛出异常即可。
【参考】分层领域模型规约
数据库表结构
一一对应,通过 DAO层向上传输数据源对象。2 个参数
的查询封装,禁止使用 Map 类
来传输。个人建议
:其实这个命名是可以。但是很多时候使用BeanCopyUtils类来拷贝对象,在大对象
。其他公司
发布的jar包。1.【强制】定义 GAV遵从以下规则:
1) GroupID 格式:com.{公司/BU }.业务线 [.子业务线],最多4 级
。
说明:{
公司/BU}
例如:alibaba/taobao/tmall/aliexpress 等BU 一级;子业务线可选。
正例:com.taobao.jstorm 或 com.alibaba.dubbo.register
2) ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库
去查证一下。
正例:dubbo-client / fastjson-api / jstorm-tool
3) Version:详细规定参考下方
2.【强制】二方库版本号命名方式 主版本号.次版本号.修订号
1)主版本号:产品方向改变,或者大规模 API不兼容,或者架构不兼容升级。
2)次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API不兼容修改。
3) 修订号:保持完全兼容性,修复BUG、新增次要功能特性等。
说明:注意起始版本号必须为:1.0.0,而不是0.0.1。
反例:仓库内某二方库版本号从 1.0.0.0 开始,一直默默“升级”成 1.0.0.64,完全失去版本的语义信息。
3.【强制】
线上应用不要依赖 SNAPSHOT版本(安全包除外);
正式发布的类库必须先去中央仓库进行查证,使RELEASE版本号有延续性,且版本号不允许覆盖升级。
说明:不依赖SNAPSHOT 版本是保证应用发布的幂等性
。另外,也可以加快编译时的打包构建。
个人理解:尤其在多个并行分支的情况下,不要在生产依赖于SNAPSHOT版本。某的其实在这块的管理是没有太多关注的。
4.【强制】
二方库的新增或升级,保持除功能点之外的其它jar包仲裁结果不变。如果有改变, 必须明确评估和验证。
说明:在升级时,进行 ·dependency:resolve· 前后信息比对,如果仲裁结果完全不一致,那么通过 ·dependency:tree· 命令,找出差异点,进行排除 jar 包。
5.【强制】
二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO对象。
就是说不要在Java类中包含枚举类,而应该单独定义枚举类。并且在rpc调用过程中返回值不应该是枚举类型。
6.【强制】
依赖于一个二方库群时,必须定义一个统一的版本变量
,避免版本号不一致。 说明:依赖springframework-core,-context,-beans,它们都是同一个版本,可以定义一个变量来保存版本:${spring.version}
,定义依赖的时候,引用该版本。
7.【强制】
禁止在子项目的 pom依赖中出现相同的 GroupId,相同的 ArtifactId
,但是不同的 Version
。
说明:在本地调试时会使用各子项目指定的版本号,但是合并成一个 war,只能有一个版本号出现在最后的 lib 目录中。曾经出现过线下调试是正确的,发布到线上却出故障的先例。
8.【推荐】
底层基础技术框架、核心数据管理平台、或近硬件端系统谨慎引入第三方实现
。
9.【推荐】
所有 pom文件中的依赖声明
语放在
语句块中;所有版本仲裁放在
语句块中。
说明:
里只是声明版本,并不实现引入,因此子项目需要显式的声明依赖, version 和scope 都读取自父 pom。而所有声明在主 pom的里的依赖都会自动引入,并默认被所有的子项目继承。
10.【推荐】
二方库不要有配置项,最低限度不要再增加配置项。
11.【推荐】
不要使用不稳定的工具包或者Utils类。
说明:不稳定指的是提供方无法做到向下兼容,在编译阶段正常,但在运行时产生异常,因此,尽量使用 业界稳定的二方工具包。
12.【参考】
为避免应用二方库的依赖冲突问题,二方库发布者应当遵循以下原则:
1)精简可控原则。移除一切不必要的 API和依赖,只包含 Service API、必要的领域模型对象、Utils类、 常量、枚举等。如果依赖其它二方库,尽量是 provided 引入,让二方库使用者去依赖具体版本号;无log 具体实现,只依赖日志框架。
2)稳定可追溯原则。每个版本的变化应该被记录,二方库由谁维护,源码在哪里,都需要能方便查到。除 非用户主动升级版本,否则公共二方库的行为不应该发生变化。
二方库发布原则
time_wait
超时时间。正例:在linux服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):
net.ipv4.tcp_fin_timeout = 3
2.【推荐】
调大服务器所支持的最大文件句柄数(File Descriptor
,简写为 fd)。 说明:主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对应于一个 fd。 主流的linux服务器默认所支持最大fd数量为1024,当并发连接数很大时很容易因为fd不足而出现“open too many files”错误,导致新的连接无法建立。建议将 linux服务器所支持的最大句柄数调高数倍(与服 务器的内存数量相关)。
3.【推荐】
给JVM环境参数设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM 场景时输出dump信息。
说明:OOM 的发生是有概率的,甚至相隔数月才出现一例,出错时的堆内信息对解决问题非常有帮助。
个人理解:这点非常重要。之前一个线上OOM问题,并没有打印堆栈信息,对于定位问题很不利。
4.【推荐】
在线上生产环境,JVM的 Xms和 Xmx设置一样大小的内存容量,避免在 GC 后调整 堆大小带来的压力。
5.【参考】
服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL Broker生成,否则因线上采用HTTPS协议而导致浏览器提示“不安全“。此外,还会带来 URL维护不一致的 问题。
1.【强制】
存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档。
说明:有缺陷的底层数据结构容易导致系统风险上升,可扩展性下降,重构成本也会因历史数据迁移和系 统平滑过渡而陡然增加,所以,存储方案和数据结构需要认真地进行设计和评审,生产环境提交执行后, 需要进行 double check。
正例:评审内容包括存储介质选型
、表结构设计
能否满足技术方案、存取性能
和存储空间
能否满足业务发展、表或字段之间的辩证关系、字段名称、字段类型、索引等;数据结构变更(如在原有表中新增字段) 也需要进行评审通过后上线。
2.【强制】
在需求分析阶段,如果与系统交互的User超过一类并且相关的User Case超过5个, 使用用例图
来表达更加清晰的结构化需求。
3.【强制】
如果某个业务对象的状态超过3个
,使用状态图
来表达并且明确状态变化的各个触发条件。
说明:状态图的核心是对象状态,首先明确对象有多少种状态,然后明确两两状态之间是否存在直接转换关系,再明确触发状态转换的条件是什么。
正例:淘宝订单状态有已下单、待付款、已付款、待发货、已发货、已收货等。比如已下单与已收货这两 种状态之间是不可能有直接转换关系的。
4.【强制】
如果系统中某个功能的调用链路上的涉及对象超过3个
,使用时序图
来表达并且明确各调用环节的输入与输出。
说明:时序图反映了一系列对象间的交互与协作关系,清晰立体地反映系统的调用纵深链路。
5.【强制】
如果系统中模型类超过5个
,并且存在复杂的依赖关系,使用类图
来表达并且明确类之间的关系。 说明:类图像建筑领域的施工图,如果搭平房,可能不需要,但如果建造蚂蚁 Z空间大楼,肯定需要详细 的施工图。
个人理解:这块其实我自己很多时候都没有做到。很多时候只做了概要设计就开始开发了。对重要或核心的流程才用了思维导图做了。这块很多公司应该都没有做类图的设计吧。对于类图的编写,可以参考rocketMQ的文档。
6.【强制】
如果系统中超过2个对象
之间存在协作关系,并且需要表示复杂的处理流程,使用活动图
来表示。 说明:活动图是流程图的扩展,增加了能够体现协作关系的对象泳道,支持表示并发等。
7.【推荐】
系统架构设计时明确以下目标:
很多时候后续的设计与演化是与需求相关的,技术人员或架构师必须知道以后产品的迭代方向,所以,要求需求的提供者能尽早对产品的发展趋势做规范。
8.【推荐】
需求分析与系统设计在考虑主干功能的同时,需要充分评估异常流程与业务边界。 反例:用户在淘宝付款过程中,银行扣款成功,发送给用户扣款成功短信,但是支付宝入款时由于断网演 练产生异常,淘宝订单页面依然显示未付款,导致用户投诉。
个人理解:设计时需要对重点主干流程做分析,这块是不能出现偏差的。对这块的设计过程中,需要重点考虑异常流程及业务边界。
异常流程包括:分布式事务退回方案,交易的幂等与状态,流程的合理及黑客攻击性。比如图形验证码的业务流程。该业务流程的边界和发展趋势。
9.【推荐】
类在设计与实现时要符合单一原则。详见第三部分
说明:单一原则最易理解却是最难实现的一条规则,随着系统演进,很多时候,忘记了类设计的初衷。
10.【推荐】
谨慎使用继承
的方式来进行扩展,优先使用聚合/组合
的方式来实现。
说明:不得已使用继承的话,必须符合里氏代换原则,此原则说父类能够出现的地方子类一定能够出现, 比如,“把钱交出来”,钱的子类美元、欧元、人民币等都可以出现。
11.【推荐】
系统设计阶段,根据依赖倒置原则,尽量依赖抽象类与接口
,有利于扩展与维护。 说明:低层次模块依赖于高层次模块的抽象,方便系统间的解耦。
因为类的设计是详细设计的范畴,所以在详细设计中,可以定义抽象类和接口。
12.【推荐】
系统设计阶段,注意对扩展开放,对修改闭合
。
说明:极端情况下,交付的代码是不可修改的,同一业务域内的需求变化,通过模块或类的扩展来实现。
应该尽量向这种极端情况看齐。
13.【推荐】
系统设计阶段,共性业务
或公共行为抽取出来公共模块、公共配置、公共类、公共方法等,在系统中不出现重复代码的情况。
说明:随着代码的重复次数不断增加,维护成本指数级上升。
14.【推荐】
避免如下误解:敏捷开发 = 讲故事 + 编码 + 发布
。
说明:敏捷开发是快速交付迭代可用的系统,省略多余的设计方案,摒弃传统的审批流程,但核心关键点上 的必要设计和文档沉淀是需要的。
反例:某团队为了业务快速发展,敏捷成了产品经理催进度的借口,系统中均是勉强能运行但像面条一样的代码,可维护性和可扩展性极差,一年之后,不得不进行大规模重构,得不偿失。
一个系统应该要考虑满足未来三年的业务,不然频繁进行重构,导致系统稳定性极差。
15.【参考】
设计文档的作用是明确需求、理顺逻辑、后期维护,次要目的用于指导编码。
说明:避免为了设计而设计,系统设计文档有助于后期的系统维护和重构,所以设计结果需要进行分类归档保存。
个人建议,概要设计是必须的,如果只有概要设计,那建议由架构师编写主要的接口及评估核心流程的技术方案。如果有详细设计,则需要补充类图等。不要为了设计而设计。设计的重点是明确需求,理顺逻辑,后期维护。
16.【参考】
可扩展性的本质是找到系统的变化点,并隔离变化点
。
说明:世间众多设计模式其实就是一种设计模式即隔离变化点的模式。
正例:极致扩展性的标志,就是需求的新增,不会在原有代码交付物上进行任何形式的修改。
17.【参考】
设计的本质就是识别和表达系统难点
。
说明:识别和表达完全是两回事,很多人错误地认为识别到系统难点在哪里,表达只是自然而然的事情, 但是大家在设计评审中经常出现语焉不详,甚至是词不达意的情况。准确地表达系统难点需要具备如下能力: 表达规则和表达工具的熟练性。抽象思维和总结能力的局限性。基础知识体系的完备性。深入浅出的 生动表达力。
18.【参考】
代码即文档的观点是错误的,清晰的代码只是文档的某个片断,而不是全部。
说明:代码的深度调用,模块层面上的依赖关系网,业务场景逻辑,非功能性需求等问题是需要相应的文 档来完整地呈现的。
尤其是复杂业务的深层调用,可以考虑用类图来呈现。
19.【参考】
在做无障碍产品设计时,需要考虑到:
单一职责原则:要求类只负责一件事情。
它规定一个类应该只有一个发生变化的原因。将不同的职责封装到不同的类或模块中。
开闭原则:要求类不作修改而能够扩展功能
,体现了类的封装与继承。
开闭原则指的是开放封闭原则,即对扩展开放,对修改封闭
。
所谓修改封闭,就是之前设计好的类,不要去修改。比如删除掉一个成员函数、改变成员函数的形参列表或更改数据成员类型等。实现对修改封闭,关键在于抽象化。对一个事物抽象化,实质上是对一个事物进行概括、归纳、总结,将其本质特征抽象地用一个类来表示,这样类才会相对稳定,无需更改。
所谓扩展开放,就是在不改变已存在的类的前提下可以添加很多功能。一般是通过继承和多态来实现,如此一来,可以保持父类的原样,只需在子类中添加些所需的新功能。
接口隔离原则:让客户只关心他们所需的接口。单一职责原则与接口分离原都体现了内聚
的思想;
使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口应该是内聚的,应该避免“胖”接口。一个类对另一个类的依赖应该建立在最小的接口上,而不要强迫依赖不同的方法,这是一种接口污染。
在某司系统中,某个服务的所有方法都是通过该接口的invoke方法来实现的。这么个胖子,怎么一个了得。
里氏替换原则:要求派生类要能够替换基类
,是对类继承的规范。
子类可以替换父类并出现在父类能够出现的任何地方。
依赖倒置原则:
其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块
,二者都依赖于抽象
;抽象不依赖于具体
,具体依赖于抽象
。依赖倒置原则是对传统过程性设计方法的“倒转”,是高层次模块复用及其可维护性的有效规范。
这个就是面向对象开发中的抽象。
管理项目中的依赖关系
对项目进行构建,完整的项目构建阶段
规范目录结构
支持多种插件
早期:使用javac编译,根据环境读取资源文件,手动打包,通常需要编写一些脚本
ant:传统构建工具,不能管理依赖
maven:主流构建工具,约定大于配置
Gradle:新兴构建工具,java还是用的maven多
按照DependencyManager版本声明进行仲裁
如无仲裁声明,则按照依赖最短路径确定版本
若相同路径,则按照第一声明优先原则
<dependency>
<groupId>org.xxxgroupId>
<artifactId>xxxxartifactId>
<exclusions>
<exclusion>
<groupId>组织名groupId>
<artifactId>模块名称artifactId>
exclusion>
exclusions>
dependency>
为什么需要第 3 次握手?
如何清晰回答好TCP的三次握手
调小 TCP 协议的 time_wait 超时时
操作系统默认 240 秒后,在高并发访问下,可能无法建立新的连
接
调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)
Linux系统一个连接对应一个文件描述符
Linux系统默认所支持最大fd数量为1024
连接数很大时很容易无法建立连接
JVM 环境参数设置- XX:+HeapDumpOnOutOfMemoryError 参数
JVM 碰到 OOM场景时输出 dump 信息
JVM 的 Xms 和 Xmx 设置一样大小的内存容量
避免在 GC 后调整堆大小带来的压力