百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

给DPVS加上SESSION同步功能

mhr18 2024-12-14 11:14 32 浏览 0 评论

给DPVS加上SESSION同步功能

前言

DPVS是一款爱奇艺开源的基于DPDK的优秀软件(https://github.com/iqiyi/dpvs)。利用DPDK工作在用户空间的特性,相比于内核空间的LVS,我们可以使用用户空间的一系列工具/中间件等完成很多在内核空间很难完成的功能。

Just for fun

虽然笔者日常工作中是搞Java中间件开发的,但一直都对底层技术尤其是在网络层面抱有很大的激情与好奇心。偶然接触到DPDK这个用户态数据平面开发套件,看了其官方文档和源码后,不禁技痒难耐,于是就尝试在DPVS上增加一个Session同步功能。虽然和工作关系不大,但搞技术的乐趣不就在于不停的折腾么,Just For Fun!当然,由于精力原因,只是写出了原型并测试成功,距离生产环境还有很大的距离,毕竟不靠这个吃饭^_^。

DPVS

DPVS事实上就是一个负载均衡软件,源于LVS,我们常说的Virtual IP(VIP)就可以使用DPVS来支持,如下图所示:


这次笔者就是在DPVS在FullNAT模式下对于主从模式增加了Session同步功能。如下图所示:

没有SESSION同步功能会如何

由于DPVS的数据转发是通过内部的session表来分发数据包的,如果没有Session同步功能,那么对应的数据库由于找不到对应的Session进而被丢弃。如果Client端是通过tcp进行连接的话:


那么将会在配置的tcp重传超时之后报错。

TCP Client RealServer

如果SESSION同步会如何


如果Session同步后,由于新晋升的DPVS2 Master依旧能够知道将这个Packet发送到后面哪台RealServer,如果是采用TCP连接的话,在一次重传之后,依旧能够保证连接的稳定。

SESSION同步方法

笔者这次尝试的是主从模式下FullNat的Session同步,事实上只需要将FullNat下的两张Session表(Session_IN和Session_OUT)从Master同步到Slave即可。

如果工作在内核态的LVS如何同步

由于LVS这一类的软件工作在内核态,那么就需要使用比较复杂且难于调试的问题进行主从之间的通信,如下图所示:


内核态的调试由于比起用户态来说相对复杂,而且没什么好用的中间件,笔者就没有做这方面的尝试。

在用户态笔者采用Redis Pub/Sub同步

而在用户态,可用的工具就太多了,于是笔者就选择了使用Redis的订阅/发布(Pub/Sub)功能将Session表信息从Master同步到Slave,如下图所示:


由于FullNat采用五元组,所以笔者在Redis中Pub的Key为:

session_key_(af协议簇)
             _(proto协议)
             _(client源地址)
             _(client端口号)
             _(vip地址)
             _(vip端口号)
             _(localIP)
             _(localPort)
             _(RealServer目的地址)
             _(RS目的端口号)
             _(当前session所在CPUID)

SESSION同步工作线程

首先,笔者在DPVS启动的main函数除了DPVS的线程之外用pthread新建了两个线程,用于reids的Send(Pub)和Receive(Sub)。

线程间通信

发布信息到Redis

DPDK线程与Send/Recv线程间,同时ring_buffer进行通信。所以一开始创建的时候,就给每个DPDK线程创建了一个rte_ring(session_rings)。当每有新建连接动作时候,DPDK线程就会将新建连接的动作封装成一个消息扔到里面,然后由SendPub线程去消费。如下图所示:


由于ring_buffer是有限的,可能出现消息丢失的现象。
新建连接的DPVS运行栈为:

__dp_vs_in
    |->conn_sched
        |->tcp_conn_sched (tcp协议)
            /* only TCP-SYN without other flag can be scheduled */
            /* 即只有TCP-SYN包才会走新建连接的逻辑 */
            |->dp_vs_schedule
                |->dp_vs_snat_schedule (FullNAT模式)

在最终的dp_vs_snat_schedule代码中,加入一段代码:

static struct dp_vs_conn *dp_vs_snat_schedule(......)
{
    conn = dp_vs_conn_new(mbuf,iph,?m,dest,0);
    ......
    // 加入把conn信息放入session_buffer的逻辑
    session_info_enqueue(conn);
    return conn;
}

放入逻辑,其实就是将conn的信息组装成一个sesion_msg结构体,然后将之前session_key的9个信息从conn中提取:

void session_info_enqueue(struct dp_vs_conn* conn){
    ......
    int cid = rte_lcore_id();
    struct session_msg* msg;
    if(rte_mempool_get(message_pool,(void**)&msg) < 0){
        ......
        return;
    }
    copy_conn_to_msg(conn,msg);
    if(rte_ring_enqueue(session_rings[cid],msg) != 0){
        ...
        rete_mempool_put(message_pool,msg);
        return;
    }
}

从Redis订阅消息

同样的,有一个Recv(Sub)线程从Redis订阅信息,然后Recv(Sub)线程和DPDK间的线程也用ring_buffer来同步,不过另用了一个session_subscribe_buffer。


如图中所示,从Redis订阅到信息之后,将消息重新塞到session_subscribe_buffer(每个线程都有)里面。然后利用DPVS的job回调方法在每个线程中处理subscribe消息并通过此消息重建session表:

lcore_job_recv_fwd
    |->lcore_process_session_subscribe_ring

void lcore_process_session_subscribe_ring(...){
    struct rte_ring* ring = session_subscribe_rings[cid];
    ...
    struct session_msg* msg;
    if(rte_ring_dequeue(ring,(void**)&msg) < 0){
        return;
    }
    new_dpvs_conn(msg);
    rte_mempool_put(message_pool,msg);
}

笔者在new_dpvs_conn里面做了FullNAT的两张session表同步操作。

void dp_vs_conn_new_from_session(struct session_msg* msg){
    ......
    /*init inbound conn tuple hash*/
    // SESSION IN 表项构建
    t->af = msg->af;
    t->proto = msg->proto;
    ......
    /*init outbound conn tuple hash*/
    // SESSION OUT 表项构建
    new->af = msg->af;
    new->proto = msg->proto;
    ......
    // 绑定dest
    err = dp_vs_conn_bind_dest(new,dest);
    ......
    // 绑定hash表
    dp_vs_conn_hash(new);
}

MQ消费重放

用Redis做Pub/Sub只是笔者为了保持编码简单而做的选择。如果正式用在产线,笔者觉得还是要把这种Session发到Kafka这种queue里面,那么就可以将Session的变化落到本地。这样,在主备都宕机的情况下,可以通过消费Kafka中已有的消息重建Session表。

遇到的小坑

在笔者进行测试的时候,遇到的一个问题时,在Session同步之后,虽然Session表项同步无误,但始终tcp连接被断开,在加了各种Print判断和TCP dump了一堆之后。才发现,DPVS本身会对TCP的sequence进行重写以增加toa字段,所以导致TCP sequence对不上,进而连接被断开。为了简单起见,笔者注掉了这段代码,然后终于成功了!

static int tcp_fnat_in_handle(...)
{
    struct tcphdr *th;
    ......
    // tcp_in_add_toa(conn,mbuf,th);
    // tcp_in_adjust_seq(conn,th);
    th->source = conn->lport;
    th->dest = conn->dport;

    return tcp_send_csum(af,iphdrlen,th,conn,mbuf);
}

不足之处

当前笔者只做了Session新建动作的同步,Session删除等其它动作还需要慢慢斟酌。
另外,由于时间精力所限,笔者对DPVS的编码只相当于做了一次简单的原型验证,还远远达不到产线高可用的要求。
不过,当测试成功,Master宕机后另一台Slave立马接上后,长连接(用的MySQL Client做测试)保持不断,查询数据依旧丝滑,仿佛什么都没发生过的时候(如果没有这个功能,只能坐等25s左右的卡主超时了,tcp_retries2=5),就感觉非常的有成就感!

总结

笔者爱折腾、喜欢做有挑战的事。笔者在玩只狼的时候,在挑战蝴蝶夫人70多次败北终于成功后,那种喜悦难以言喻。这次玩DPVS也一样,在Debug了大半天之后,终于成功的感觉和只狼如出一辙,这也是我乐此不疲的原因,Just For Fun!
关注笔者公众号《解Bug之路》,获取更多干货文章。

相关推荐

MYSQL数据同步(mysql数据同步方式)

java开发工程师在实际的开发经常会需要实现两台不同机器上的MySQL数据库的数据同步,要解决这个问题不难,无非就是mysql数据库的数据同步问题。但要看你是一次性的数据同步需求,还是定时数据同步,亦...

SpringBoot+Redis实现点赞收藏功能+定时同步数据库

由于点赞收藏都是高频率的操作,如果因此频繁地写入数据库会造成数据库压力比较大,因此采用redis来统计点赞收藏浏览量,之后定时一次性写入数据库中,缓解数据库地压力。一.大体思路设计redis中的储存结...

双11订单洪峰:Codis代理层如何扛住Redis集群搞不定的120万QPS?

双11订单洪峰下的技术挑战每年的双11购物节,都是对电商平台技术架构的极限考验。当零点钟声敲响,海量用户瞬间涌入,订单量呈指数级增长,系统需要承受每秒数十万甚至上百万次的请求。作为电商系统的核心组件之...

基于spring boot + MybatisPlus 商城管理系统的Java开源商城系统

前言Mall4j项目致力于为中小企业打造一个完整、易于维护的开源的电商系统,采用现阶段流行技术实现。后台管理系统包含商品管理、订单管理、运费模板、规格管理、会员管理、运营管理、内容管理、统计报表、权限...

商品券后价产品设计方案(显示券后价)

如何设计一套高效、准确且稳定的券后价计算系统,是电商产品设计中的关键挑战之一。本文详细介绍了商品券后价的产品设计方案,从背景目标、功能设计、系统实现逻辑到异常处理机制等多个方面进行了全面阐述。一、背景...

外观(门面)模式-Java实现(java 门面模式)

定义外观模式(FacadePattern),也叫门面模式,原始定义是:为了子系统中的一组接口提供统一的接口。定义一个更高级别的接口,使子系统更易于使用。大大降低应用程序的复杂度,提高了程序的可维护性...

Mall - 用 SpringBoot 实现一个电商系统

目前最为主流的Web开发技术,包括SpringBoot、MyBatis、MongoDB、Kibina、Docker、Vue等,都是开发者十分需要掌握的技术。有没有一个全面而又实际的项目,能把这...

腾讯云国际站:哪些工具能实现可视化运维?

本文由【云老大】TG@yunlaoda360撰写开源工具Grafana:开源的可视化平台,可与Prometheus、Elasticsearch、MySQL等多种数据源集成,将复杂监控数据转化...

系统稳定性保障全流程实战:事前、事中、事后 Java 代码详解

在互联网架构中,系统稳定性是生命线。本文基于“事前预防、事中管控、事后复盘”三阶段模型,结合Java实战代码,深度解析如何构建高可用系统,让你的服务稳如磐石!一、事前:未雨绸缪,筑牢防线1.发...

Java面试题:拆分微服务应该注意哪些地方方,如何拆分?

在拆分微服务时,需要综合考虑业务、技术和组织等多方面因素,以下是关键注意事项及拆分策略的详细说明:一、拆分注意事项1.业务边界清晰化单一职责原则:每个服务应专注于单一业务能力,例如订单服务仅处理订单...

软件性能调优全攻略:从瓶颈定位到工具应用

性能调优是软件测试中的重要环节,旨在提高系统的响应时间、吞吐量、并发能力、资源利用率,并降低系统崩溃或卡顿的风险。通常,性能调优涉及发现性能瓶颈、分析问题根因、优化代码和系统配置等步骤,调优之前需要先...

Docker Compose实战,多容器协同编排的利器,让开发部署更高效!

开篇导读你是否有过这样的经历?启动一个项目,数据库、Redis、Web服务得一个个敲dockerrun?想让别人复现你的开发环境,却得发一堆复杂的启动命令?明明都是容器,为什么不能“一键启动”所...

如何设计Agent的记忆系统(agent记忆方法)

最近看了一张画Agent记忆分类的图我觉得分类分的还可以,但是太浅了,于是就着它的逻辑,仔细得写了一下在不同的记忆层,该如何设计和选型先从流程,作用,实力和持续时间的这4个维度来解释一下这几种记忆:1...

不了解业务和技术术语怎么做好产品和项目?

基础技术术语术语分类解释API开发技术应用程序接口,不同系统间数据交互的协议(如支付接口、地图接口)。SDK开发工具软件开发工具包,包含API、文档和示例代码,帮助快速接入服务。RESTfulAPI...

Docker 架构详解与核心概念实战图解:一文读懂容器的前世今生

不懂Docker架构,你只是“用容器的人”;理解了它的底层逻辑,才能成为真正的高手!在学习Docker之前,很多同学可能会陷入一个误区:“反正我用dockerrun就能跑起服务,架构这种...

取消回复欢迎 发表评论: