前言
除了保证项目如期上线,如何保证项目上线后的运行速度,如何提高容灾能力,减少bug同样是我们需要考虑的问题。
我们从以下几个方面来探究思考,抛砖引玉,看看大家是否有其他维度来提高项目,欢迎在评论区留言。
1.语言选择方面
开发效率和运行效率的平衡点
- 我入行时做的安卓开发,使用Java语言,觉得入门门槛比较高:异常处理,IO,集合,JDBC等等,除了基础概念不好理解之外,代码量也比较大。
- 使用Java开发了2年安卓之后,开始使用PHP做接口开发,觉得PHP开发真的比Java开发代码量少太多了,而且PHP的数组实在是太简单了。那时候满脑子想的就是“PHP是最好的语言”
- 去年接触了Go,发现Go对并发处理实在是太友好了,而且代码量像PHP一样少,甚至更少。而且Go打包编译时自动格式化代码,比PHP更规范。Go性能比肩Java,对开发者的友好程序甚至略好于PHP(我个人的体会)
- 所以,对开发语言的选择对项目至关重要,我们要结合自己的业务场景,选择合适的语言进行开发。我们目前采用的是PHP+Go,内部服务采用RPC的方式来通讯。
2.框架选择方面(开发速度、项目性能)
我们来探究一下相较于其他框架,为什么swoole的速度更快?swoole是如何做到的?
- swoole是PHP的一个extension扩展,但又不仅是一个扩展,因为普通的扩展只是提供了库函数。而swoole扩展在运行后是可以接管PHP的控制权,进入事件循环的;
- swoole使用纯C语言编写,定位就是一个网络通信和异步IO的基础库,不依赖其他三方,定位纯粹;
- swoole底层内置了异步非阻塞、多线程的网络IO服务器,当IO事件发生后,swoole会自动回调相应的php函数
- 总结:异步处理,提高对IO密集型场景并发处理
- swoole框架相比于fpm等,主要节省了PHP框架和全局对象每次请求创建销毁带来的性能消耗:与http请求无关的全局对象只需要构造一次,并且swoole还支持对某些对象和数据做缓存控制
- 总结:swoole常驻内存,避免重复创建销毁,重复加载带来的性能消耗
- 之前一个浅薄的认识:认为swoole之所以比laravel这类框架性能高,是因为laravel做了大量封装,引入中间件,每次请求都需要额外走一些非必要的流程。认为“程序开发效率和程序运行效率肯定是对立面”这种理论,有点浅薄了,还是要深入理解原理呀。
3.数据库存储引擎方面(性能)
我们通过对MySQL的MyISAM和InnoDB存储引擎做对比,来分析数据库对项目性能的影响
- 首先介绍一下MySQL区别其他数据库的一个重点特点:插件式的表存储引擎;注意,MySQL的存储引擎是基于表的,不是基于数据库的
- 下面根据特性对比分析2个存储引擎
- 事务处理:
- MyISAM不支持事务处理
- InnoDB支持事务处理,支持4个事务隔离级别,支持多版本读
- 所以对事物有要求的业务场景需要使用InnoDB
- 表级锁定
- MyISAM锁定机制是表级索引,表级索引实现的成本很小但是大大降低了并发性能
- InnoDB支持行级锁定,通过索引实现;InnoDB很好的解决了并发问题,可以说InnoDB就是为处理大数据量高并发设计的
- InnoDB不仅支持行锁,也支持外键
- 读写互相阻塞
- MyISAM不仅会在写入的时候阻塞读取,也会在读取的时候阻塞写入;但是读本身不会阻塞另外的读
- InnoDB支持非锁定读(默认读操作不会产生锁)
- 缓存
- MyISAM只会缓存索引,MyISAM可以通过key_buffer缓存以大大提高访问性能较少磁盘IO,但是缓存原理是只缓存索引,不缓存数据
- InnoDB既能缓存索引,也能缓存数据,具有非常高效的缓存特性
- InnoDB的高阶新特性
- InnoDB可以通过多版本控制(MVCC)来获得高并发
- 实现了4种隔离级别,默认为Repeatable read级别
- 支持通过next-key Locking策略避免幻读(phantom)现象的产生
- 插入缓存(insert buffer)
- 二次写(double write)
- 自适应哈希索引(adaptive hash index,AHI)
- 预读(read ahead)
- 试用场景
- MyISAM
- 不需要支持事务处理
- 并发较低
- 数据修改较少
- 以读为主
- InnoDB
- 需要支持事务处理的场景
- 大数据量、高并发的场景
- 对数据一致性要求高的场景
- 注意:可以利用InnoDB较好的缓存能力提高内存利用率,减少磁盘IO
4.算法方面(性能)
我们通过深入了解算法相关的概念来理解如何衡量自己设计的算法是否高效,如何优化。
首先说明算法是什么?
- 算法(Algorithm)是指用来操作数据,解决程序问题的一组方法。
时间复杂度&空间复杂度
对于同一个问题,使用不同的算法,在执行过程中消耗的资源和时间是有很大区别的,我们引入了“时间复杂度”和“空间复杂度”来衡量不同算法之间的优劣
时间复杂度
时间维度:执行当前算法所消耗的时间,通常用【时间复杂度】来描述
大O符号表示法:考虑到在不通配置的机器上运行或者数据规模的区别,我们不能简单的通过算法具体的运行时间来描述“时间复杂度”,我们引入了【大O符号表示法】
在大O符号表示法中,时间复杂度的公式是:T(n)=O(f(n)),其中f(n)表示每行代码执行次数之和,而O表示正相关关系,这个公式的全称是:算法的渐进时间复杂度。
大O符号不是真实代表算法执行时间的,它是用来表示代码执行时间的增长变化趋势的。
常见的时间复杂度量级:(时间复杂度越来越大,执行效率越来越低)
- 常数阶O(1)
- 对数阶O(logN)
- 线性阶O(n)
- 线性对数阶O(nlogN)
- 平方阶O(n²)
- 立方阶O(n³)
- K次方阶O(n^k)
- 指数阶O(2^n)
空间复杂度
空间维度:执行当前算法需要占用的内存空间,通常使用【空间复杂度】来描述
既然时间复杂度不是用来计算程序具体耗时的,空间复杂度也不是用来计算程序实际占用空间的。
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映一个趋势,我们用S(n)来定义
常见的空间复杂度量级:(空间复杂度越来越大,执行效率越来越低)
- 常数阶O(1)
- 线性阶O(n)
- 平方阶O(n²)
5.函数设计方面(性能、稳定性)
我们以递归和迭代的区别是什么来抛转引玉,请大家思考如何结合自己的业务场景设计合适的函数
- 递归的基本概念就是调用自身,直接或者间接的调用自己,通常把一个大型问题转化为一个和原问题相似的、规模较小的问题来解决。可以简单理解为A调用A
- 迭代的基本概念是利用变量的原值推算出变量的新值,可以简单的理解为A调用B
- 各自优点:
- 递归的优点是可以把大问题转成小问题,可以精简代码,可读性号
- 迭代的优点是执行效率高,空间复杂度低(因为迭代的时间只和循环次数呈一个线性关系,没有额外的空间花销)
- 各自缺点:
- 递归浪费空间,递归太深会造成堆栈溢出
- 迭代代码比递归代码复杂,不够简洁,可读性差
- 应用场景分别是什么?
- 递归中一定有迭代的概念,但是迭代中不一定有递归,大部分都是可以相互转换的
- 理论上能用迭代的不用递归,因为递归函数浪费内存空间,可能造成堆栈溢出
- 实际项目中还要考虑代码的可读性,不止是方便别人,也方便自己,我们使用递归时,可以根据业务场景设置一个递归层级的最大值。
6.网络请求方面(安全性)
我们通过对比http与https的区别,探讨一下如何保证数据传输的安全性
- https协议需要到CA申请证书,一般免费证书较少,因而需要一定费用。
- http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl/tls加密传输协议。
- http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
- http的连接很简单,是无状态的;
- HTTPS协议是由SSL/TLS+HTTP协议构建的可进行==加密传输、身份认证==的网络协议,比http协议安全。
7.会话方面(安全性)
我们通过探讨session和cookie的最佳实践,来探讨一下会话方面的安全性
- session数据存储在服务器,cookie数据存储在客户端浏览器上
- cookie不是很安全,我们可以查询伪造存储再客户端的cookie进行欺骗请求,考虑到安全应该使用session
- session会在一定时间内保存在服务器,当访问量增多时,会比较占用服务器性能,考虑到性能时可以使用cookie
- 单个cookie保存的数据不能超过4kb,很多浏览器会限制一个站点最多保存的cookie数
- cookie具有不可跨域名性
- 会话cookie和持久cookie:
- 会话cookie即不设置过期时间,会随着浏览器关闭就消失的cookie,一般存储在内存中;
- 持久cookie即设置了过期时间,即使关闭了浏览器也不会消失的cookie,一般存在硬盘中;再次打开浏览器仍然有效,直到达到过期时间。
- session共享:
- 对于多网站单服务器(同一父域名不同子域名)如何解决不同网站之间的SessionId共享问题?由于域名不同(a.test.com,b.test.com),而sessionId又分别存储再不同的cookie中,我们的思路就是改变cookie的存储范围到父域名,达到共享cookie的目的,从而实现SessionId的共享。
- 由此带来的弊端是子站之间的cookie信息也被共享了
- 比较好的实践是:把登录信息等敏感数据保存到session中,其他非敏感数据保存在cookie中