最近写得的一个业务端模块有比较高的性能要求,而且对数据的实时性也有要求,后端只提供了一个获取单个状态的接口,没有批量接口,那只能并发去取了。
1.线程数控制
主要是为了控制资源消耗,CPU资源和内存资源,极限情况下也不能把服务器资源耗光,保证服务器上其他模块的正常运行。
常用手段:C语言中,使用线程池设定同时运行的线程个数,使用cgroup限制使用的cpu核数、内存消耗limit,go语言中使用runtime.GOMAXPROCS(MAX_PROC),限制程序消耗的cpu核数,极限情况下也只是耗光指定的几个cpu。
另外可以使用协程,linux中一个进程默认最多能起大约1024个线程(线程池大小一般是个位数,太多了的话,频繁的进行上下文切换也会消耗不少资源,具体情况可以查看profiling信息,需要注意的是profiling记录的是cpu时间,时间片切走了是不会算入profiling样本数的),但是协程数却没有限制,启动上百万个都没问题。
多线程间通信主要有两种途径:1.内存共享,2.通道。
内存共享可能会涉及到数据同步问题,关键资源需要加锁。
通道通信的话,需要注意通道的阻塞和非阻塞。
以上主要是针对c和go这一类的控制力较强、较低层的语言,在PHP中,由于PHP本身设计定位的原因,本身不支持多进程/多线程,要实现并发需要靠扩展或者模拟,PHP中实现并发执行的技术主要有PCNTL和popen,通过pcntl fork一个子进程或者popen开启一个文件句柄,都能比较方便的实现并发效果,ps:对于高性能并且资源有限的情况下php中的并发表现并不理想,子进程和文件句柄都比较重,系统能开启的个数也有限制。
2.数据库连接数控制
一般而言单个server数据库连接数是非常有限的,每个协程中建立一个数据库连接显然是不行的,数据库连接池中连接数必须加以限制,需要用时去随机取出一个空闲连接,处理完了及时归还,使用连接是可以先ping一下,确认连接是否存活。
3.超时
并发执行时,超时问题是不可避免的,特别是涉及网络访问时,需要访问一个远程端口,延时是不可回避的,必须控制好超时,另外整个并发处理的总耗时也必须设定一个超时时间。
4.重试
线上服务,环境比较复杂,而言很多服务是由集群提供的,偶尔会遇到单台server失败的情况,失败重试功能也是必不可少的,一台失败了,换成另一台server或者等待一段时间再重试。
5.并发优化的前提
如果服务本身是CPU消耗型的,并发优化可能就没什么效果,如果是IO消耗型的,并发优化的效果就比较明显。这一点在优化先需要格外注意,在服务部署时也需要注意,特别是混部的情况下。
ps,贴一下并发优化的效果:
(每次需要从远程端口取一个项的状态,网络延迟1-2ms,再查一次数据库中的状态)
php中并发取1000个项的状态需要大约6s
使用go写了一个基于thrift的并发中间层,取10000个项的状态需要大约1s,网络延时为30ms时, 取10000个 项的状态需要大约6s