开发一个软件系统,想要性能好,需要好的技术架构、以及代码要写得好。以下是我的一些工作经验。
一、数据库方面
1 索引 [重点]
请注意,一定要重视,一定要知道索引的重要性。否则,上线后系统肯定卡,要么炸。
什么时候建索引?建什么样的索引?
可以在产品需求确认后、编码前、统一建表时把索引设定好确认好,写代码时围绕着这些索引去写查询条件。后期根据实际情况还会有些调整。 也可以在项目代码全部写完后,再统一定索引。根据代码的查询条件以及慢查询,做调整。
理论上,一个软件系统,一定要有主键索引、肯定会有单索引、肯定会有联合索引。常用查询,必须建索引。举个例子,一个订单表,可能就需要 商户id+用户id+创建时间 作为联合索引:alter table order add index u_m(`merchant_id`,`uid`,`create_time`) USING BTREE;
如果把索引弄得好,一张表5000万行数据,没什么压力。比如订单表、账变表、积分流水表。但一定要慎重、小心、认真,因为以后处理问题处理数据的时候会非常耗时,容易搞崩。
注意事项:索引越少越好,千万别说唉呀我每个字段都建了索引,软件怎么还这么慢呢?
关于索引的原理,可以看我另一篇文章:一文掌握MySQL的索引 -- 数据结构(认真排版、简洁易懂)
关于SQL的性能分析,可以看我这篇文章:如何分析MySQL语句的性能(MySQL Explain详解)
2 数据归档
数据量大的表,可以做归档,比如只提供3个月的数据查询。比如移动电话账单,只能查近6个月。
3 写代码查数据时,别用 select * ,请指定要查哪些字段。
原因是:(1) 少查一个字段,内存的节约、速度的提升,都会好不少。(2) select … 指定全部字段,也比select * 的性能要好。(3) 可能会使用覆盖索引,性能极速提升。
4 如果能不用 select count() 就别用。这个性能很差。
前台的接口,提供给用户翻页,用户下拉翻一页,不需要知道总页数。
就算是普通的页码分页组件,也应该问一声,是否可以让用户一直点下一页,点到没有了就是最后一页。
5 慎用 limit
比如 limit 100000000,10 这样系统会直接卡死,性能非常差。有时可以不用 limit, 换一种写法,用主键判断再配合 limit 使用,性能提升非常多,如:where id > 9000000 limit 100
使用场景,这里说两个。
(1) 比如有一个数据中心系统,提供接口给别的系统采集数据,别接受页码、第几页等参数去数据库分页,可以接受一个“last_id”作为参数,然后where id > $last_id limit 100;
(2) 分页
6 默认一个时间范围的查询条件
去数据库查询时,尽量带上默认时间范围。比如后台订单报表,一打开界面,如果默认展示的数据时间范围是全部,这性能会很差,而且特别影响系统的整体性能,影响终端用户的使用体验。
7 善用事务
如果你不用事务,数据很难保证一致性,也很难保证不出现脏数据错误数据。但注意别什么都往事务里塞,容易死锁。
8 读写分离
访问量一多,必须考虑读写分离,一主多从。数据库的压力就会降下来。但一定要注意,更新数据后又查询时,如果时时性要求高,自己注意这是否会查主库,如果不会,请自己指定去主库查询。比如采集到商品数据后,丢入队列进行一些额外处理,队列里判断数据是否存在,如果不存在就返回false,这就会有问题。
一般的ORM的底层有自动处理好,同一事务里,会自动连接主库。
Thinkphp的框架还有个配置 read_master ,写入操作之后的读数据库,会自动去主库读取。这是个很好的功能。
9 分库分表
如果短时间之内会有大量数据,或者不能做数据归档。可以使用分表,甚至分库。
注:我经历过分表。没经历过分库。
10 多注意看日志
平常没事儿干的时候多看看服务器的慢查询,以及错误日志。这可以让你在问题暴露之前发现问题,处理问题。
二、缓存方面
1 正常一个企业的软件系统,都需要上缓存。请重视。否则,你的系统上线后,不是卡就是炸。
2 单机部署,linux系统一般可能只有1000来个连接,需要配置内核网络参数。单节点的吞吐量(QPS)在5W左右,如果机器性能好可以达到八九万。
3 如果规模非常大,就用分布式部署。比如Redis,可以考虑哨兵模式、集群模式,建议选用集群模式,比如三主三从。如果有兴趣了解三主三从,可以看我这篇文章:docker容器化搭建 redis7.0.4 三主三从集群
4 缓存key的长度,尽量减短。
5 缓存的内容,尽量减短,并使用压缩。别什么都往里面丢。
6 必须设置过期时间。否则内存会有爆的一天。不能过期不能随时清除的缓存,请在后端代码里自行维护。
7 避免在同一时间大量key过期。你可以根据业务情况,随机过期时间。
8 注意避免缓存穿透。数据库无值时,别一直查库。请先判断是否存在此key
9 注意热点KEY的问题。如何发现热点KEY,提供几个方案:代码访问时封装一个统一入口、用redis提供的命令。解决方案,提供几个方案:人工拆分、主从节点key、二级缓存。
三 、 代码方面
1 尽量保证每个接口在本地测试时的耗时在100ms以内。
2 及时性要求不高的任务,特别是耗时任务,请用队列。比如前面说的记录日志,比如下单成功后给用户短信通知、计划报表等。
3 减少http接口的数量。这样可以减少你的各种连接数。
4 不要在循环里查数据库。这性能特别差。
5 不要在循环里查缓存。这性能也挺差。
6 一些复杂数据,能在代码里计算就在代码里计算。从数据库取出简单数据,然后用代码进行计算,别直接用sql语句去数据库里处理。
7 严格记录日志,用于日后排查问题,并且免于日后扯皮。
8 该用锁的地方要用锁。比如定时将任务丢入队列,如果队列里其中一个任务卡住了,这个时候会依然持续不断的丢入新的任务到队列里,这样会把队列撑爆。
10 调试性能的时候,调试bug的时候,多看开发手册,多看框架底层代码。比如数据量大的时候、索引、读写分离,以及一些特殊的BUG和性能需求。
我以前遇到一个问题,用的Thinkphp5.1,command命令行里自动在主库update了数据,然后丢到队列进行其它业务处理,队列里自动去从库查询数据,因为主从同步有延迟,这时查不到数据。现在解决方案是要让所有队列(或队列里某个任务)去主库读取数据,怎么实现呢?这里不贴代码了,有兴趣的可以自己研究下看看。
11 前端资源,能使用CDN的就使用CDN
12 图片,不要直接存到当前项目,请使用单独的图片服务器,比如公司自己部署的图片服务器,或者第三方存储商。
13 当我们直接new一个对象的时候,请考虑它的性能,系统访问量有点大的时候,内存的不断创建和回收会影响性能。可以考虑使用单例模式。也可以使用容器,比如thinkphp laravel提供的容器,这是个注册树模式,它给我们提供了很好的代码架构,对系统性能也会有很大提升。
四、Nginx
1 访问量一多,必须搭建服务器的负载均衡。可以自己配置多台服务器。用Nginx的反向代理,也可以使用阿里云等云服务商提供的负载均衡服务。代码服务器的压力就可以降下来。
2 单节点的QPS能达到10万左右。
五、websocket
1 频繁请求的接口,请使用websocket进行推送。
2 请不要使用前端ajax轮询,请用websocket推送
3 单节点一般能对付大部分场景,我用的workerman,QPS在二三十万,甚至更多。
五、升级到HTTP 2.0
主要有这几个方面的提升:
多路复用。做到同一个连接并发处理多个请求,从头到尾只用一个TCP连接,解决了TCP慢启动的问题;
头部数据压缩。一般我们一个页面,会有很多很多请求,每个请求都带一堆请求头数据,很影响性能和带宽。2.0解决了这个问题。
服务器推送。我们访问一个页面时,这个页面通常还需要一堆额外的请求,浏览器会主动把这些资源推给浏览器,大大提升了性能。
我们部署一个网站,默认的HTTP协议版本是HTTP1.1,并且大部分网站使用的也是1.1版本。升级到2.0版,可以大大提升性能。具体情况,自行百度搜索HTTP每个版本的区别、它是如何提升网络速度的、如何升级到HTTP2.0。
六、前端效果(交互欺骗)
1 当一个页面信息元素比较多时,你可以先加载主要数据,后加载次要数据。给用户的感觉是,哇,一点就打开了,真快。其实有一些他没那么快注意到的数据,还没展示出来。
2 有些复杂的交互,可以拆分成两步甚至多步。