面试的时候问的很多问题是关于计算机网络的,而网络中的负载均衡问题又是重中之中,笔者曾经面试大厂的时候就被面试官虐的体无完肤,所以提前掌握了负载均衡问题,并且在此基础上掌握DPVS项目就能跟面试官拼一拼 : )
DPVS是爱奇艺开源的用户态高性能负载均衡项目,将传统的内核 LVS基于 DPDK改造而成。
CDN中的技术
CDN本质上是分布式缓存服务器,其中很重要的就是负载均衡。说起负载均衡,准备过后端面试的同学肯定背的滚瓜烂熟了:四层负载均衡LVS,七层负载均衡nginx。没记住的没关系,赶紧记笔记。LVS一个阿里的传奇人物写的,被纳入了linux源码。
why CDN
为什么提CDN呢?原因就是CDN将负载均衡用到了极致。简单介绍一下吧。首先我们用浏览器输入一个网址,浏览器会拿着这个网址的域名向DNS服务器做DNS解析,DNS解析的结果不一定是个IP地址,也可以是CNAME即另一个域名,然后浏览器继续DNS解析,直到得到A记录即IP地址。CDN厂商就能在此做文章了。举个栗子,小白访问了http://www.baidu.com,浏览器第一次DNS得到的是http://www.baidu.com.cdn.com,这个是百度公司在他的域名服务器上配置的,百度买了http://cdn.com公司的服务,浏览器第二次请求http://www.baidu.com.cdn.com,这个域名解析服务交给了CDN厂商http://cdn.com公司的服务器,这个服务器根据小白的源IP地址,确定了小白的地理位置在北京市海淀区,用的是电信宽带,根据算法取一个离小白最近的机房的IP返回给了浏览器作为http://www.baidu.com的最终目的IP,浏览器用这个IP发送http请求,发到了CDN厂商的服务器上,服务器不是一台设备,是一个大机房,公用一个IP,到底那个机器提供服务就取决于负载均衡算法。负载均衡主要有两层,首先是传输层的负载均衡,用了LVS;其次是应用层的负载均衡,用了nginx。决定出给小白服务的机器,请求也就到了那个机器上,这个机器可以理解成一个缓存硬盘,发现有http://www.baidu.com,就直接回给小白,不让就代替小白向百度请求http://www.baidu.com,保存下来,并且返回给小白。这就是CDN的大致流程。
LVS——CDN的宝剑
LVS的中文译文是Linux虚拟服务器,是中国前阿里程序员写的,内核代码里能找到,可见他的牛逼之处。同iptables一样,是基于内核的netfilter框架实现的,不得不佩服内核结构化设计如此巧妙。篇幅有限,但是非常重要,请读者自行使用搜索引擎学习。
基于DPDK的用户态LVS——DPVS
DPVS的优势
为了达到高性能,使用了多种不同技术
- 绕过内核(在用户空间实现)
- 每个cpu的关键数据无共享(无锁)
- RX控制和CPU亲合性绑定(避免上下文切换)
- 批处理TX / RX
- 零拷贝技术(避免数据包拷贝和系统调用)
- 轮询替换中断
- 高性能ICP的无锁信息
- 其他技术由DPDK加强
今天不想写了,先写点安全相关的
强大的DDoS高防功能——synproxy
- 功能初始化 dp_vs_synproxy_init()
int dp_vs_synproxy_init(void)
{
//初始化随机数g_net_secret
//初始化一个每分钟增1的随机数g_minute_count
//申请ack_mbufpool
}
- 收到客户端的syn包处理 dp_vs_synproxy_syn_rcv()
int dp_vs_synproxy_syn_rcv(int af, struct rte_mbuf *mbuf,
const struct dp_vs_iphdr *iph, int *verdict)
{
/* 查询是否有服务
1. 没有服务接收包
2. 有服务没有realserver丢弃包
3. 源ip在服务的黑名单丢弃包
/* 核心函数
1. 设置tcp options syn_proxy_parse_set_opts()
2. 生成cookie syn_proxy_cookie_v4_init_sequence()
3. 设置seq和ack
4. 交换ip和port
5. 计算校验和
syn_proxy_reuse_mbuf();
/* 交换 mac */
/* 发包 */
netif_xmit()
}
- 收到客户端的ack包处理 dp_vs_synproxy_ack_rcv()
int dp_vs_synproxy_ack_rcv(int af, struct rte_mbuf *mbuf,
struct tcphdr *th, struct dp_vs_proto *pp,
struct dp_vs_conn **cpp,
const struct dp_vs_iphdr *iph, int *verdict)
{
/* 查询是否有服务
1. 没有服务接收包
2. 检查是否带数据
3. 校验cookie
/* 查询负载出的realserver
dp_vs_schedule()
}
- 收到realserver的synack包处理dp_vs_synproxy_synack_rcv()
学习地址:Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家(免费订阅,永久学习)
【文章福利】需要更多DPDK/SPDK学习资料加群793599096(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,大厂面试题 等)可以自行添加学习交流群点击这里噢~
发现采用DR模式会死锁在dp_vs_synproxy_synack_rcv(),因为单向代理收不到realserver的synack包。后来询问开源项目组成员,得到的回复是syncookie只能用在fullnat模式中。感觉这里可以改进!
现在来从头写吧
main.c 是世界的本源
- parse_app_args() dpvs自定义的参数解析函数在dpdk eal解析参数之前先解析dpvs的自定义参数
- dpvs_running() 从/var/run/标准守护进程文件中找到dpvs的pid,检查是否有dpvs正在运行
- dpvs_state_set(DPVS_STATE_INIT) 设置DPVS状态
- 获取时间戳,初始化随机树种子
- set_allthreadaffinity() 设置主线程跑到所有核上,这个操作是啥神奇操作?主线程在rte_eal_init()会绑到一个核上
- rte_eal_init() DPDK的初始化函数,所有DPDK程序都有这个,懂的都懂
- rte_timer_subsystem_init() 初始化dpdk计时器子系统
- rte_pdump_init() 初始化dpdk抓包
- dpvs_scheduler_init() 初始化dpvs工作队列
- global_data_init() 初始化dpvs几个全局变量
- cfgfile_init() 初始化信号处理函数和从文件中初始化一些字段
- netif_virtual_devices_add() 创建bonding网口
- dpvs_timer_init() 初始化时间轮 dpvs学习笔记: 6 定时器实现及连接老化超时