批量数据处理
每次读取数据时,批量读取,然后在应用中进行分组、分发,例如:
订单列表页涉及到用户、订单、商品等表,批量把当前页的订单、用户、商品一次性读取回来,然后在应用中进行数据的组装,以减少sql请求量,降低数据库压力,加快操作响应速度。
线程池的使用
建议尽量避免使用
1 | @Async |
注解,因为这个是共享了Spring提供的线程池,大量使用的时候,容易造成线程池溢出,建议使用自定义的线程池,并且估算好以下参数:
- 核心线程数、最大线程数
- 存活时长
- 任务队列及长度
- 自定义ThreadFactory,指定线程名称
- 根据业务指定拒绝策略
HashMap不要在多线程环境下使用
应用默认都是多线程环境的,所以在定义HashMap对象的时候一定要清楚的知道当前有没有多线程的问题
控制数据量大小
每次操作数据的时候,一定要清楚的知道当前操作的数据可能的量,并清楚的知道最大的量是多少、未来的增长数据和空间,以防止引起内存不足,我们的系统的内存大部分是4-8个G;
除非能确定数据量有限且不增长,否则都应该分批操作,保证每批数据的有限性,以防随着数据量大后造成系统风险。
对于需要较长时间的业务,比如导入数据、调用第三方接口之类,最好做成任务类型
数据和操作的安全性、可追溯性
- App端:不允许通过前端传入用户ID来操作对应用户的数据;对用户的私有数据操作,一定要检查当前数据是否属于当前用户,包括但不限于:地址、订单、关注、购物车、评论、个人信息、账户余额等
- Backend端:后台对用户的操作有权限校验,当前是基于URL做的,所以要确保当前操作的数据一定要在某个url之下,比如菜单url是/ic/item,那么点开这个菜单以后展现的界面里所有的操作对应的url,都要在/ic/item这个url之后(之下),比如/ic/item/to-add,/ic/item/do-add/_ajax
- 上传到OSS的内容,如果是公司私有的,一定不能使用公有读权限上传,以防造成信息泄露。
- 关键配置不要直接写在配置文件里,当前我们是写的启动脚本里(配置中心还没有做)
- 关键数据的操作和修改,一定要有对应的操作日志的记录,以确保可追溯
定时任务
- 如果是定时执行的,约定只在整点或半点的时候执行,以便大家在发布系统的时候能知道在哪些时间点避免发布;
- 如果要是对过去一段时间的数据进行定时处理,请确保处理的数据的时间范围大两个(或更长时间)或以上的定时执行时间差内,以便让当前处理的数据能覆盖到上一次定时执行的数据范围,以便上次未执行或执行失败时,能做重试处理
- App端不做任务定时任务,以确保APP端系统的稳定
- 根据自身业务情况,可以考虑放到redis队列里去,然后由多台机子一起跑这个队列里的数据,这样的话即使服务器频繁重启也不会中断任务。
配置的内外网之分
为了加速操作的执行速度,在能使用内网的时候,一定使用内网连接,例如:Redis/Mysql/OSS等,请大家在添加或修改线上或测试环境配置的时候,一定要了解当前是否可以使用内网。
外部服务调用
如果有对外部服务的调用,一定要想好如果服务不可用、服务响应时间过长、或者服务响应结果有错,会不会对系统造成压力或影响;尤其是当该调用在事务方法中时,有可能造成事务时间过长,数据库连接被占满,系统不可用。
事务处理
- 要清楚的理解当前操作是否需要事务,能避免事务操作的就不要使用事务
- 如果是使用事务,那就需要清楚的知道什么时候可以返回结果,什么时候应该抛出异常,以使事务生效
- 事务中尽量避免长时操作
延迟或异步给出不必要的数据
对于非必要的数据,可以延迟或异步给出,以加快操作的响应速度。例如之前后台的订单详情页,除了加速订单相关数据外,还会加载订单的物流信息,造成整个订单详情页响应时间超长,后来优化了一下,延迟加载物流信息,整个响应速度就极大的提升了;又比如前端很多页面都有优有推荐数据,为了显示页面,除了要读取必要的数据,还要等待推荐数据的返回才能显示页面,就造成了整个页面的响应过慢。
异步处理,加快响应速度
- 对于有多个长时操作,但是互相之间没有依赖的,可以让每个操作异步处理,然后在最后再等待所有的操作结果返回,汇总结果,再输出,以加快操作的响应速度。
- 除此之外,尽量使用消息系统
幂等性
自己写的方法、接口,特别是比较重要的业务,一定要处理好幂等性,不管是消息的消费还是对外暴露的API等等。
缓存的使用
- 对于耗时的操作,能使用缓存的尽量使用缓存
- 对于变动不频繁但是使用频繁的,可以使用缓存
- 对于不会变的数据,可以使用本地缓存,但是要注意缓存数据量的大小,以免造成内存压力
数据库索引
写sql查数据的时候,除了要考虑数据量外,要检查是否有对应的索引使用,防止查询过慢。
一般一个表的索引不要超过6个,索引应建立在离散度比较高且在查询条件中出现频度较高的字段上,索引尽量在建表的时候考虑好,后期数据量大了再加索引的话会比较困难。
其他
- 使用集合时,估算出可能需要的容量,并以此指定集合的容量,以避免频繁扩容
- 字符串拼接时,建议使用StringBuilder(Java8中会自动使用)
- 数据库中存储时间,建议用时间戳(10位,到秒),鉴于历史问题,如果旧表使用的是dateTime,那么后续旧表加时间字段时也用dateTime类型。
- 读取数据库时间以及和前端交互时,注意时区的问题
- 数据库中存储金额,都使用分,计算过程涉及取舍时不能精确到分,总和会出现差距
- 调用方法都检查返回结果;提供方法时尽量避免null结果返回,区分集合和普通对象,处理好返回值为 null 的情况
- 方法和变量命名是直观且和它所对应的业务意义一至
- 对于业务逻辑,要写好注释,描述清楚代码所对应的业务逻辑,且注释和代码逻辑要真的一致,对于 map 一定要注释好 key 和 value 分别对应什么
- 关键的业务要记录好日志,谁,什么时候,做了什么事情,数据变化情况
- logger.error 注意多个参数,可能丢失堆栈
- 异常处理:只捕获自己可以处理的异常,不能处理的不要捕获
- java7 开始,jdk排序算法已改变,需要满足:自反性,传递性,对称性
- 集合类判断是否为空,使用 CollectionUtil 不要使用 size() > 0
定期review
定期的去APM系统中或Kibana中检查操作的响应速度和耗时处