一个典型的服务器结构
主要由三部分组成
网络I/O+服务器高性能编程技术+数据库
一:网络I/O
网络I/O方面,linux下面使用
epoll,windows上面有IOCP,其他平台还有kqueue,dev/poll等机制。
二:服务器及数据库的负载均衡
1.数据库
数据库可能会有以下几点需要解决:
1.超出数据库连接数
假设数据库并发连接数10个,应用服务器这边有1000个并发访问请求,将会有990个失败。
解决方法:
队列 + 连接池
即加一个中间层DAL(数据库访问层),DAL和应用服务器即可以部署在同一台服务器,
也可以部署在不同台服务器,它们使用TCP通信。最好使用DAL队列服务+连接池的方式。
2.超出时限
数据库并发连接数10个,数据库1秒之内最多能处理1000个请求,应用服务器这边有10000个并发请求,会出现0~10秒的等待
解决方法:
使用缓存
数据库上的业务逻辑处理应该简单,主要业务逻辑的处理放在应用服务器上。不过这也很有限的降低
数据库的压力。更好的方法是缓存。但是一旦应用了缓存,就会出现缓存的更新,缓存的同步问题。
1.缓存具有一定的时效timeout,如果缓存失效,有两种方法:
(1)对于查询上的缓存,就需要重新去数据库里面查询,查询到数据后更新缓存,进行缓存同步。
将一些热点数据存至缓存,这种方法实时性比较差。
(2)一旦数据库中数据更新,立即通知前端的缓存更新,实时性比较高,但实现有一定难度。
2.缓存换页
内存不够,将不活跃的数据换出缓存。可以使用FIFO,LRU(least recently used)即最近最少使用、
LFU(least frequently used)最近最不频繁使用。
这些技术nosql中都有这种功能。比如redis,memcached都可以作为分布式缓存。
同样,由于
可伸缩性,缓存也可以不跟服务器放在同一台机器上。如果放在同一台机器上,它就只是
一个局部的缓存,只能被该机器访问。如果是部署在独立的机器上,并且使用了分布式缓存机制,它就是全局缓存,
可以被各个服务器访问。
那么如果前端还有大并发请求呢?
我们应该做数据库
读写分离。
对于大部分数据库而言,数据库的读操作多于写操作。我们可以对数据库进行负载均衡。
现在主流的数据库都有replication机制。
查询到读库,写到写库,对于写数据需要更新到读库,这就是主从分布replication机制。
那么数据库负载均衡了,现在压力又到应用服务器上了。
二.应用服务器
应用服务器的负载均衡
方案一:
应用服务器被动接受任务方案
增加一个任务服务器来实现,任务服务器可以监视应用服务器的负载,到底是CPU高还是I/O高,还是并发
高,或者是内存换页高。
应用服务器可以暴露一个http接口给任务服务器,任务服务器选取负载最低的服务器分配任务。
方案二:
应用服务器主动请求任务方案
当应用服务器任务执行完毕后,主动去任务服务器请求任务。
那么任务服务器如果故障了怎么办呢?
任务服务器需要有两台,甚至多台!任务服务器应该具有
fail over(故障转移)机制。
当其中一台任务服务器故障或者失效了,另一台应该可以立刻投入使用,它们两者之间通过心跳来实现,
这就是
服务器的高可用(HA机制high abalitity)。
但是对于大型的应用来说,数据库的量是非常庞大的。
这时候应该对数据库进行数据分区(分库,分表)
分库,即垂直分区,是指数据库可以按照一定的逻辑,把表分散到不同的数据库。比如一个数据库有用户
相关的表,有业务相关的表,有基础信息相关的表,就可以分成三个库。
但更加常用的是水平分区,将一个数据库水平切割为若干个数据库,每个数据库都有这些表,用户表,业务表,信息表。
这种方式需要改写DAL的代码。
三:高性能编程技术
服务器性能四大杀手:
数据拷贝:应该尽可能减少数据拷贝,内存拷贝,同样得利用缓存机制解决。
环境切换:(理性创建线程)线程切换,该不该用多线程,单线程好还是多线程好?
答:单核服务器(状态机编程效率是最高的),单核服务器是不能够并行处理任务的。如果使用多线程,
会增加线程间的切换开销。如果服务器是多核的,多线程能够充分发挥多核并行的优势,但是多核
服务器使用多线程,也应该尽可能避免线程之间的环境切换。
内存分配:使用内存池技术解决
锁竞争:锁竞争会降低程序性能,应该尽可能通过逻辑来避免锁的使用。