系统设计层面
攒包/定时地进行批量处理。提高单包承载率,减少网络io、减少系统调用次数。
数据处理(cpu消耗型代码)、io独立线程处理。
多地部署,就近接入。分set,避免跨城、跨地区流量访问。
消息队列尽量无锁化、一写一读等。
尽量减少无用的代理层,去中间商。
灰度机制。
内核级优化(操作系统多队列网卡特性等)。
数据自动报表化,多维度展现。
CDN静态加速、css和js合并压缩(在HTTP2中不使用,因为其中一个文件变更,导致缓存失效,不值得)
DNS调度,就近接入(测速)
proxy 预建连、多连接、Quic等加速
tcp长链接(Keep-Alive),请求复用连接(但是http1.1中,协议是文本,“发送方”需按序发送,后面的请求必须等前面的请求完成)
http2(头部压缩、二进制编码、免发重复数据、多路复用(通过引入二进制数据帧和流,使得“发送方”可以乱序发送不用等待,服务端可以恢复顺序))
底层网络收发包使用多路复用如epoll、select等(netty框架)
使用proxy、worker、请求队列方式搭建服务,增加服务器抗网络抖动能力
上流使用反向代理服务器,提供负载均衡、CND缓存能力
限流(qps、流量),对超过服务器承载量的请求及时作丢包处理,避免滚雪球,如令牌桶算法和漏桶算法(令牌通强调输入速率,从入口处限制请求;而漏桶算法强调输出,在出口处限制请求)。
Guava RateLimiter
C语音层面
用register修饰高频访问的变量。register修饰的变量将尽可能放在CPU内部寄存器中,减少了访问内存的时间。
inline修饰高频调用的函数
内存对齐访问
操作char型数据转换成操作long型数据,因为32位系统简单操作一次32位数据的时间几乎和操作一次8位数据的时间相同
for (j=0;j<8;j++)
src_buf[j]^=iv_crypt[j];
需要存取内存8*2次,每次只操作1字节,它优化之后的语句:
*(uint64_t *)src_buf ^= *(uint64_t *)iv_crypt;
只需存取内存2*2次,每次可以操作4个字节。
优化之前的语句:
while(nInBufLen) {
if (src_i<8) {
src_buf[src_i++]=*(pInBuf++);
nInBufLen--;
}
...
}
每个字节需要循环一次,优化之后,每8个字节循环一次。优化之后的语句如下:
while(nInBufLen) {
if (src_i == 0 && nInBufLen >= 8) {
*(uint64_t *)src_buf = *(uint64_t *)pInBuf;
pInBuf += 8;
nInBufLen -= 8;
src_i = 8;
...
}
...
}
中断优化
1.避免没有必要的缺页中断
用new或者malloc申请内存时,系统并不会立刻分配相应内存,而是在实际使用时才这片内存时才分配。所以为了避免没有必要的缺页中断,可以在申请内存后,立刻初始化一遍内存,避免进程处理业务时产生终端。
使用mlock锁定内存,避免被交换出去。
尽量减少swap或把swap关掉。
http://blog.csdn.net/b2222505/article/details/60324150
http://blog.csdn.net/b2222505/article/details/54144353
2.减少页表映射
如果物理内存很大,映射表的条目将会非常多,会产生较多TLB Miss,内存访问过程很耗时。例如程序需要访问4MB的内存,那么就需要1024个页面,TLB需要1024个表项,同时需要1024个页表项,系统至少需要产生1024次TLB中断,才可以把4MB内存映射到物理内存。
通常情况下对于大块内存的频繁访问,可以采用大页表减少页表条目,提高内存查询速度
cat /proc/meminfo|grep "HugePages"可以查看大页表的状态
减少了内存页表项的缓存压力和CPU cache缓存内存地质映射的压力,提高了寻址能力和内存管理效率。大页内存还有其他一些使用时需要注意的地方:
大页内存不能交换(SWAP).
使用不当时可能造成更大的内存泄漏。
3.避免伪共享
多核CPU,每个核都拥有独立的L1/L2的缓存,而缓存按照最小单位缓存行进行和内存交互,缓存行一般为64字节。
当多线程修改互相独立的变量(一般出现在全局变量或者一些动态申请的内存)时,如果这些变量共享同一个缓存行,那么当某个核修改缓存行某个变量值时,需要锁定缓存行,同时其他核已经加载该缓存行的cache也跟着失效,需要重新从内存读取。
4、内存池,减少频繁申请、释放内存
编写cpu缓存友好的代码
空间局部性原理
分配连续紧凑的内存,且最好不要超过catch line的大小
时间局部性原理
尽量重复使用同一个变量,使其保持在寄存器中
因为cpu的MESI的存在,要尽量避免伪共享
将共享在多线程间的数据进行隔离
1、增大数组元素的间隔使得不同线程存取的元素位于不同的cache line上
2、在每个线程中创建全局数组各个元素的本地拷贝,然后结束后再写回全局数组
考虑清楚哪些变量是不变的,哪些是经常变化的,哪些变化是完全相互独立的,哪些属性一起变化
将该对象属性分组,将一起变化的放在一组,与其他属性无关的属性放到一组,将不变的属性放到一组
1、Padding 方式
2、Contended注解方式
```java
@sun.misc.Contended
@SuppressWarnings("restriction")
public class ContendedData {
int value;
long modifyTime;
boolean flag;
long createTime;
char key;
}
```
使用Disruptor
一个线程内通信框架,用于线程里共享数据。它确保任何数据只由一个线程拥有以进行写访问,从而消除写争用的设计
数组比链表、树更具有缓存友好性
http://geek.csdn.net/news/detail/114619
参考资料
https://segmentfault.com/a/1190000011172823
https://segmentfault.com/q/1010000005167289