高并发
- 高并发读
- 高并发写
- 高并发读写
总结:想要高并发,首先要高性能,性能工程对于性能优化是有一些原则方法论的
单个数据库每秒两三千并发就基本扛不住了
系统拆分
将单体系统拆分成多个系统或者多个服务,每个子系统使用自己的数据库,提高并发度
大部分的系统都是需要经过多轮拆分的,第一轮拆分将系统粒度划的小一点,可能随着业务的发展,单个系统会变得更复杂,所以需要进一步的拆分
拆分的维度:
- 业务维度
- 功能维度
- 资源维度 资源使用多与使用少的场景划分
局部并发原则
将同步调用的外部服务拆分为多个无关异步调用,来提升性能
高性能流程
容量规划
使用压测监控预测等确定要支撑多少性能指标 确定哪些性能指标的基准水位线
负载测试
测试系统在正常运行压力下的各项指标,这个流程是一个长期的流程以保证系统性能可预测,出现问题也能发现问题在哪
压力测试
要压到发现系统失效点,远高于正常的负载 只对关键、瓶颈业务进行测试 好钢用在刀刃上
失效点关注于响应时间、内存、失败率等异常指标
APM监控
- 基础监控:通过时序数据等指标发现异常以告警
- 追踪监控:通过链路追踪以发现出错的原因
- 业务监控
弹性扩缩容
根据监控,使用策略,管理资源
高并发读
策略:加缓存
大部分高并发的场景,都是写多读少,使用缓存,可以有效抗住高并发
对于缓存需要考虑 雪崩 击穿 穿透 等缓存问题
- 本地缓存或以Redis为代表的集中式缓存
- 数据库主从复制分担主库压力
- CDN 静态加速
策略:并发读
- 异步RPC 要求各个调用之间是独立的
- 既然数据库每秒能撑住的请求是有限的,那么就可以使用MQ,大量的请求灌入MQ,利用MQ的削峰,让下游系统慢慢消费
- 冗余请求 通过每次调用多个服务器 哪个返回的快就使用的哪个
- 分库分表 让每个表的数据少一点,提高SQL的执行速度
策略:重写轻读
某些事件产生的数据提前聚合好 等需要的时候直接读取即可 而非在需要的时候实时计算
总结:读写分离
- 数据库读写分离
大部分对数据库的请求都是读多写少,所以读写分离,分配多一些机器给读请求,能有效提高性能
问题
- 读写分离带来的问题及解决方案
- 复制延迟
- 针对特定业务 读写都在主机上 非核心业务使用从机
- 读从机,发现从机上的数据是旧的 则二次读取主机
- 分配机制 如何让客户端将读写操作区分开来,然后访问不同的数据库服务器
- 客户端分离 TDDL shardingSphere 动态数据源也可以实现
- 数据库中间件 [MyCat](/中间件/数据库/MyCat.html), MySQL Router,
高并发写
策略:数据分片
- 数据库[分库分表](/中间件/数据库/mysql/数据库优化.html#分库分表)
- JDK的ConcurrentHashMap 通过分段(之前的版本) 来降低竞争
- Kafka的分区 不同的分区可以并发地读写
- ES分布式索引
策略:任务分片
- 指令流水线
- mapreduce
策略:异步化
减少等待、Y轴扩展、削峰填谷
- 系统层面:异步网络模型
- JDK层面 NIO等
- 接口层面 线程池 异步调用 Future等
- 业务层面 RPC方式
策略:批量
- 批量写入数据
- 将小操作合并成一整个大操作
妥协
资源是有限的 并发无上限
- 排队系统
- 基于阻塞方式
- 基于令牌方式(令牌桶)
- 熔断降级
负载均衡
调度后方的多台机器,以统一的接口对外提供服务,承担此职责的技术组件被称为“负载均衡”。
- 四层负载均衡优势是性能高,七层负载均衡的优势功能强
- 做多级混合负载均衡,通常应是低层的负载均衡在前,高层的负载均衡在后
“四层”的意思是说这些工作模式的共同特点是都维持着同一个TCP连接,而不是说它就只工作在第四层
链路负载均衡
通过DNS解析来完成
实时性不强 一旦后端服务器宕机 这种负载方式无法及时发现
硬件负载均衡
- F5 A10 等能支持达到百万量级的并发 缺点就是贵
操作系统负载均衡
利用系统级别的中断以及多队列网卡等来充分利用资源 达到负载均衡的功能
客户端负载均衡
- 传统微服务
代理负载均衡
- 服务网格
集群负载均衡
网络级别的负载均衡
- 数据链路层负载均衡直接修改请求的数据帧中的MAC目标地址,让用户原本是发送给负载均衡器的请求的数据帧,被二层交换机根据新的MAC目标地址转发到服务器集群中对应的服务器二层负载均衡器直接改写目标MAC地址的工作原理决定了它与真实的服务器的通讯必须是二层可达的 效率很高,但必须是二层可达,这就导致了它适合做第一级负载设备 ![屏幕截图 2020-11-15 124626](/assets/屏幕截图%202020-11-15%20124626.png)
- 网络层负载均衡
- IP隧道模式:对IP包进行封装,封装成一个新包发送到后端服务器 这种方式需要后端服务器持有一个VIP 这样才能直接响应请求,不够透明 ![屏幕截图 2020-11-15 125319](/assets/屏幕截图%202020-11-15%20125319.png)
- NAT模式:直接由均衡器对IP头进行修改 所有数据报都要经过均衡器 这个均衡器很容易称为瓶颈 ![屏幕截图 2020-11-15 130127](/assets/屏幕截图%202020-11-15%20130127.png)
- 更彻底的NAT模式:均衡器在转发时,不仅修改目标IP地址,连源IP地址也一起改了,源地址就改成均衡器自己的IP,称作Source NAT(SNAT) 在后端服务器的视角看来,所有的流量都来自于负载均衡器
- 应用层负载均衡 该层的优势就在于比链路层 网络层可以得到更多的信息 从而做出更加智能的决策
负载均衡算法
任务平分类
- 轮循均衡
- 权重轮循均衡
- 随机均衡
- 权重随机均衡
压力均衡类
- 响应速度均衡
- 最少连接数均衡
hash类
- 一致性哈希均衡
- ip哈希
- id哈希
均衡器实现
- 硬件负载均衡 F5 A10
- 软件负载均衡
- 构建在内核的:LVS
- 应用程序的形式:Nginx、HAProxy、KeepAlived
- 构建在内核的:LVS
LVS
- D-NAT模式
- DR模式
将RS的VIP配置在内核中
- TUN模式
调度算法:
- 静态
- 轮询
- 加权轮询
- ...
- 动态
- 最少连接
- 加权最少连接
- ...
流程:
node01: ifconfig eth0:8 192.168.150.100/24node02~node03: 1)修改内核: echo 1 > /proc/sys/net/ipv4/conf/eth0/arp_ignore echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore echo 2 > /proc/sys/net/ipv4/conf/eth0/arp_announce echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce 2)设置隐藏的vip: ifconfig lo:3 192.168.150.100 netmask 255.255.255.255 RS中的服务:node02~node03: yum install httpd -y service httpd start vi /var/www/html/index.html from 192.168.150.1xLVS服务配置node01: yum install ipvsadm ipvsadm -A -t 192.168.150.100:80 -s rr ipvsadm -a -t 192.168.150.100:80 -r 192.168.150.12 -g -w 1 ipvsadm -a -t 192.168.150.100:80 -r 192.168.150.13 -g -w 1 ipvsadm -ln验证: 浏览器访问 192.168.150.100 看到负载 疯狂F5 node01: netstat -natp 结论看不到socket连接 node02~node03: netstat -natp 结论看到很多的socket连接 node01: ipvsadm -lnc 查看偷窥记录本 TCP 00:57 FIN_WAIT 192.168.150.1:51587 192.168.150.100:80 192.168.150.12:80 FIN_WAIT: 连接过,偷窥了所有的包 SYN_RECV: 基本上lvs都记录了,证明lvs没事,一定是后边网络层出问题
问题:
- LVS可能会发生单点故障
- 主备
- RS挂的话,部分请求会失败
keepalived
作为一个通用工具,解决高可用问题
配置
vrrp_instance VI_1 { state MASTER // 备服务器BACKUP interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 172.17.0.100/16 dev eth0 label eth0:3 }}virtual_server 172.17.0.100 80 { delay_loop 6 lb_algo rr lb_kind DR nat_mask 255.255.255.0 persistence_timeout 0 protocol TCP real_server 172.17.0.4 80 { weight 1 HTTP_GET { url { path / status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } } real_server 172.17.0.6 80 { weight 1 HTTP_GET { url { path / status_code 200 } connect_timeout 3 nb_get_retry 3 delay_before_retry 3 } }}