(二) 日志规约
- 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架
SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
- 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:
appName_logType_logName.log。logType:日志类型,推荐分类有 stats/desc/monitor/visit
等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么
类型,什么目的,也有利于归类查找。
正例:mppserver 应用中单独监控时区转换异常,如:
mppserver_monitor_timeZoneConvert.log
说明:推荐对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于
通过日志对系统进行及时监控。
- 【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方
式。
说明:logger.debug(“Processing trade with id: ” + id + ” symbol: ” + symbol); 如果
日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会
执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例:(条件)
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
}
正例:(占位符)
logger.debug(“Processing trade with id: {} and symbol : {} “, id, symbol);
- 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例:
- 【阿里开发规范--笔记(二)】【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上
抛。
正例:logger.error(各类参数或者对象 toString + “_” + e.getMessage(), e);
- 【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适
从。注意日志输出的级别,error 级别只记录系统逻辑出错、异常、或者重要的错误信息。如
非必要,请不要在此场景打出 error 级别,避免频繁报警。
- 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使
用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘
撑爆,并记得及时删除这些观察日志。
说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。纪录日志时请
思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
(一) 建表规约
- 【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint
( 1 表示是,0 表示否),此规则同样适用于 odps 建表。
说明:任何字段如果为非负数,必须是 unsigned。
- 【强制】禁用保留字,如 desc、range、match、delayed 等,参考官方保留字。
- 【推荐】字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:
1)不是频繁修改的字段。
2)不是 varchar 超长字段,更不能是 text 字段。
正例:各业务线经常冗余存储商品名称,避免查询时需要调用 IC 服务获取。
- 【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
反例:某业务三年总数据量才 2 万行,却分成 1024 张表,问:你为什么这么设计?答:分 1024
张表,不是标配吗?
- 【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明
显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,
必然有脏数据产生。 - 【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,
保证被关联的字段需要有索引。
说明:即使双表 join 也要注意表索引、SQL 性能。
- 【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据
实际文本区分度决定索引长度。
说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分
度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来
确定。
- 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索
引
- 【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索
引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b
无法排序。
- 【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N
行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特
定阈值的页数进行 SQL 改写。
正例:先快速定位需要获取的 id 段,然后再关联:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
- 【推荐】 SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts
最好。
说明:
1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2)ref 指的是使用普通的索引。(normal index)
3)range 对索引进范围检索。
反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别
比较 range 还低,与全表扫描是小巫见大巫。
- 【推荐】建组合索引的时候,区分度最高的在最左边。
正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。
阿里巴巴 JAVA 开发手册
24 / 32
说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>?
and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
- 【强制】不要使用 count(列名)或 count(常量)来替代 count(),count()就是 SQL92 定义的
标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
- 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。 - 【强制】POJO 类的 boolean 属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中
进行字段与属性之间的映射。
说明:参见定义 POJO 类以及数据库字段定义规定,在 sql.xml 增加映射,是必须的
- 【强制】iBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使
用。
说明:其实现方式是在数据库取到 statementName 对应的 SQL 语句的所有记录,再通过 subList
取 start,size 的子集合,线上因为这个原因曾经出现过 OOM。
正例:在 sqlmap.xml 中引入 #start#, #size#
map.put(“start”, start);
map.put(“size”, size);
- 分层
? 开放接口层:可直接封装 Service 接口暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控
制层等。
? 终端显示层:各个端的模板渲染并执行显示层。当前主要是 velocity 渲染,JS 渲染,JSP 渲
染,移动端展示层等。
? Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
? Service 层:相对具体的业务逻辑服务层。
? Manager 层:通用业务处理层,它有如下特征:
1) 对第三方平台封装的层,预处理返回结果及转化异常信息;
2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;
3) 与 DAO 层交互,对 DAO 的业务通用能力的封装。
? DAO 层:数据访问层,与底层 Mysql、Oracle、Hbase、OB 进行数据交互。
? 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。
- 【参考】(分层异常处理规约)在 DAO 层,产生的异常类型有很多,无法用细粒度异常进行
catch,使用 catch(Exception e)方式,并 throw new DaoException(e),不需要打印日志,
因为日志在 Manager/Service 层一定需要捕获并打到日志文件中去,如果同台服务器再打日志,
浪费性能和存储。在 Service 层出现异常时,必须记录日志信息到磁盘,尽可能带上参数信息,
相当于保护案发现场。如果 Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如
果是单独部署,则采用与 Service 一致的处理方式。Web 层绝不应该继续往上抛异常,因为已
经处于顶层,无继续处理异常的方式,如果意识到这个异常将导致页面无法正常渲染,那么就
应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。开放接口层要将异常处理成错
误码和错误信息方式返回。 - 【参考】分层领域模型规约:
? DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
? DTO(Data Transfer Object):数据传输对象,Service 和 Manager 向外传输的对象。
? BO(Business Object):业务对象。可以由 Service 层输出的封装业务逻辑的对象。
? QUERY:数据查询对象,各层接收上层的查询请求。注:超过 2 个参数的查询封装,禁止使
用 Map 类来传输。
? VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
(一)二方库规约
- 【强制】定义 GAV 遵从以下规则:
1) GroupID 格式:com.{公司/BU }.业务线.[子业务线],最多 4 级。
说明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一级;子业务线可选。
正例:com.taobao.tddl 或 com.alibaba.sourcing.multilang
2) ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到仓库中心去查证一下。
正例:tc-client / uic-api / tair-tool
3) Version:详细规定参考下方。 - 【强制】二方库版本号命名方式:主版本号.次版本号.修订号
1) 主版本号:当做了不兼容的 API 修改,或者增加了能改变产品方向的新功能。
2) 次版本号:当做了向下兼容的功能性新增(新增类、接口等)。
3) 修订号:修复 bug,没有修改方法签名的功能加强,保持 API 兼容性。 - 【推荐】工具类二方库已经提供的,尽量不要在本应用中编程实现。
? json 操作: fastjson
? md5 操作:commons-codec
? 工具集合:Guava 包
? 数组操作:ArrayUtils(org.apache.commons.lang3.ArrayUtils)
? 集合操作:CollectionUtils(org.apache.commons.collections4.CollectionUtils)
? 除上面以外还有 NumberUtils、DateFormatUtils、DateUtils 等优先使用
org.apache.commons.lang3 这个包下的,不要使用 org.apache.commons.lang 包下面
的。原因是 commons.lang 这个包是从 JDK1.2 开始支持的所以很多 1.5/1.6 的特性是不
支持的,例如:泛型。
- 【推荐】高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务
器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上调小
此等待值。
正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):
net.ipv4.tcp_fin_timeout = 30 - 【推荐】调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。
说明:主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对应
于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很容易
因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。 建议将 linux
服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。 - 【推荐】给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump
信息。
说明:OOM 的发生是有概率的,甚至有规律地相隔数月才出现一例,出现时的现场信息对查错
非常有价值。 - 【参考】服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL Broker 生成,
否则因线上采用 HTTPS 协议而导致浏览器提示“不安全”。此外,还会带来 URL 维护不一致的
问题。
- 表单、AJAX 提交必须执行 CSRF 安全过滤。
- URL 外部重定向传入的目标地址必须执行白名单过滤。
- 用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
? page size 过大导致内存溢出
? 恶意 order by 导致数据库慢查询
? 正则输入源串拒绝服务 ReDOS
? 任意重定向
? SQL 注入
? Shell 注入
? 反序列化注入
推荐阅读
- 松翰单片机--SN8F5702学习笔记(六)TIMER2
- 松翰单片机--SN8F5702学习笔记(五)PWM
- 最优化方法|二次规划基础(二次型、正定矩阵、海塞矩阵)
- "按位取反加一"的新理解——在FFT频分析后如何获得其频率分量