阿里开发规范--笔记(二)

(二) 日志规约

  1. 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架
    SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class);

  1. 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:
    appName_logType_logName.log。logType:日志类型,推荐分类有 stats/desc/monitor/visit
    等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么
    类型,什么目的,也有利于归类查找。
    正例:mppserver 应用中单独监控时区转换异常,如:
    mppserver_monitor_timeZoneConvert.log
    说明:推荐对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于
    通过日志对系统进行及时监控。
  2. 【强制】对 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);
  1. 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
    正例:
  2. 【阿里开发规范--笔记(二)】【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上
    抛。
    正例:logger.error(各类参数或者对象 toString + “_” + e.getMessage(), e);
  3. 【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适
    从。注意日志输出的级别,error 级别只记录系统逻辑出错、异常、或者重要的错误信息。如
    非必要,请不要在此场景打出 error 级别,避免频繁报警。
  4. 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使
    用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘
    撑爆,并记得及时删除这些观察日志。
    说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。纪录日志时请
    思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
三、MYSQL 规约
(一) 建表规约
  1. 【强制】表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint
    ( 1 表示是,0 表示否),此规则同样适用于 odps 建表。
    说明:任何字段如果为非负数,必须是 unsigned。
  2. 【强制】禁用保留字,如 desc、range、match、delayed 等,参考官方保留字。
  3. 【推荐】字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:
    1)不是频繁修改的字段。
    2)不是 varchar 超长字段,更不能是 text 字段。
    正例:各业务线经常冗余存储商品名称,避免查询时需要调用 IC 服务获取。
  4. 【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
    说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
    反例:某业务三年总数据量才 2 万行,却分成 1024 张表,问:你为什么这么设计?答:分 1024
    张表,不是标配吗?
(二) 索引规约
  1. 【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
    说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明
    显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,
    必然有脏数据产生。
  2. 【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,
    保证被关联的字段需要有索引。
    说明:即使双表 join 也要注意表索引、SQL 性能。
  3. 【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据
    实际文本区分度决定索引长度。
    说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分
    度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度来
    确定。
  4. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
    说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索

  5. 【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索
    引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
    正例:where a=? and b=? order by c; 索引:a_b_c
    反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b
    无法排序。
  6. 【推荐】利用延迟关联或者子查询优化超多分页场景。
    说明: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
  7. 【推荐】 SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts
    最好。
    说明:
    1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
    2)ref 指的是使用普通的索引。(normal index)
    3)range 对索引进范围检索。
    反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级别
    比较 range 还低,与全表扫描是小巫见大巫。
  8. 【推荐】建组合索引的时候,区分度最高的在最左边。
    正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。
    阿里巴巴 JAVA 开发手册
    24 / 32
    说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>?
    and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
(三) SQL 规约
  1. 【强制】不要使用 count(列名)或 count(常量)来替代 count(),count()就是 SQL92 定义的
    标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
    说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
(四) ORM 规约
  1. 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
    说明:1)增加查询分析器解析成本。2)增减字段容易与 resultMap 配置不一致。
  2. 【强制】POJO 类的 boolean 属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中
    进行字段与属性之间的映射。
    说明:参见定义 POJO 类以及数据库字段定义规定,在 sql.xml 增加映射,是必须的
  3. 【强制】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);
  4. 分层
    ? 开放接口层:可直接封装 Service 接口暴露成 RPC 接口;通过 Web 封装成 http 接口;网关控
    制层等。
    ? 终端显示层:各个端的模板渲染并执行显示层。当前主要是 velocity 渲染,JS 渲染,JSP 渲
    染,移动端展示层等。
    ? Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
    ? Service 层:相对具体的业务逻辑服务层。
    ? Manager 层:通用业务处理层,它有如下特征:
    1) 对第三方平台封装的层,预处理返回结果及转化异常信息;
    2) 对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;
    3) 与 DAO 层交互,对 DAO 的业务通用能力的封装。
    ? DAO 层:数据访问层,与底层 Mysql、Oracle、Hbase、OB 进行数据交互。
    ? 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。
  5. 【参考】(分层异常处理规约)在 DAO 层,产生的异常类型有很多,无法用细粒度异常进行
    catch,使用 catch(Exception e)方式,并 throw new DaoException(e),不需要打印日志,
    因为日志在 Manager/Service 层一定需要捕获并打到日志文件中去,如果同台服务器再打日志,
    浪费性能和存储。在 Service 层出现异常时,必须记录日志信息到磁盘,尽可能带上参数信息,
    相当于保护案发现场。如果 Manager 层与 Service 同机部署,日志方式与 DAO 层处理一致,如
    果是单独部署,则采用与 Service 一致的处理方式。Web 层绝不应该继续往上抛异常,因为已
    经处于顶层,无继续处理异常的方式,如果意识到这个异常将导致页面无法正常渲染,那么就
    应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。开放接口层要将异常处理成错
    误码和错误信息方式返回。
  6. 【参考】分层领域模型规约:
    ? DO(Data Object):与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
    ? DTO(Data Transfer Object):数据传输对象,Service 和 Manager 向外传输的对象。
    ? BO(Business Object):业务对象。可以由 Service 层输出的封装业务逻辑的对象。
    ? QUERY:数据查询对象,各层接收上层的查询请求。注:超过 2 个参数的查询封装,禁止使
    用 Map 类来传输。
    ? VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
四、其他规约
(一)二方库规约
  1. 【强制】定义 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:详细规定参考下方。
  2. 【强制】二方库版本号命名方式:主版本号.次版本号.修订号
    1) 主版本号:当做了不兼容的 API 修改,或者增加了能改变产品方向的新功能。
    2) 次版本号:当做了向下兼容的功能性新增(新增类、接口等)。
    3) 修订号:修复 bug,没有修改方法签名的功能加强,保持 API 兼容性。
  3. 【推荐】工具类二方库已经提供的,尽量不要在本应用中编程实现。
    ? 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 的特性是不
    支持的,例如:泛型。
(二)服务器规约
  1. 【推荐】高并发服务器建议调小 TCP 协议的 time_wait 超时时间。
    说明:操作系统默认 240 秒后,才会关闭处于 time_wait 状态的连接,在高并发访问下,服务
    器端会因为处于 time_wait 的连接数太多,可能无法建立新的连接,所以需要在服务器上调小
    此等待值。
    正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒):
    net.ipv4.tcp_fin_timeout = 30
  2. 【推荐】调大服务器所支持的最大文件句柄数(File Descriptor,简写为 fd)。
    说明:主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对应
    于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很容易
    因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。 建议将 linux
    服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。
  3. 【推荐】给 JVM 设置-XX:+HeapDumpOnOutOfMemoryError 参数,让 JVM 碰到 OOM 场景时输出 dump
    信息。
    说明:OOM 的发生是有概率的,甚至有规律地相隔数月才出现一例,出现时的现场信息对查错
    非常有价值。
  4. 【参考】服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL Broker 生成,
    否则因线上采用 HTTPS 协议而导致浏览器提示“不安全”。此外,还会带来 URL 维护不一致的
    问题。
(三)参数规约
  1. 表单、AJAX 提交必须执行 CSRF 安全过滤。
  2. URL 外部重定向传入的目标地址必须执行白名单过滤。
  3. 用户请求传入的任何参数必须做有效性验证。
    说明:忽略参数校验可能导致:
    ? page size 过大导致内存溢出
    ? 恶意 order by 导致数据库慢查询
    ? 正则输入源串拒绝服务 ReDOS
    ? 任意重定向
    ? SQL 注入
    ? Shell 注入
    ? 反序列化注入

    推荐阅读