大家好,今天小编来为大家解答以下的问题,关于侵入式服务与非侵入式程序结构-侵入式和非侵入式,这个很多人还不知道,现在让我们一起来看看吧!
[[321760]]
假设网络通信框架具有一定的结构,根据通信数据是否流入和流出网络框架,我们将服务器结构分为侵入式和非侵入式两种。
非侵入式结构非侵入式比较简单一点,我们先讨论一下。所谓非侵入式是指一个服务中的所有通信或业务数据都在网络通信框架内流动,也就是说没有任何外部数据源注入或流出网络通信框架。例如,对于IM业务服务器来说,通常无论是单聊消息还是群聊消息,其核心业务本身的数据流都在网络通信框架内流动。在私聊过程中,用户A向用户B发送消息,实际上消息流是从用户A的连接对象传递到用户B的连接对象,然后通过B的连接对象的send方法发送出去的。群聊也是同样的道理。来自一个用户的连接会同时发送到多个其他用户的连接对象。无论哪种情况,这些连接对象都是网络通信框架的内部结构。
非侵入式服务结构侵入式结构如果外部消息流入或流出网络通信模块,则相当于外部消息“侵入”网络通信结构。我们将这种服务器结构称为侵入式服务结构。
除了网络通信组件之外,入侵服务器的其他组件的结构设计可以是多种多样的。我们来看一下两种常见的结构:
结构一:业务线程(或数据源线程)处理数据后交给网络通信组件发送。
结构2:网络解包后,需要将任务交给专门的业务线程处理。处理完毕后,需要通过网络通信组件再次发送出去。
结构一实际上是结构二的后半部分,所以我们重点关注结构二。
我们在一线程一循环的思想下介绍了各个网络线程的基本结构:
while(!m_bQuitFlag){epoll_or_select_func();handle_io_events();handle_other_things();} 当handle_io_events收集网络数据并解包时,解包后得到的任务处理逻辑比较耗时。这时候,我们就需要把这些任务交给专门的业务线程来处理。业务线程可以是一组工作的消费者线程。我们可以将这些任务放在一个队列中,这样网络组件线程(网络线程)是生产者,业务工作线程是消费者,我们可以使用互斥体、临界区(Windows)或条件变量等技术来协调生产者和消费者。这意味着涉及公共排队系统。这是一种常用的实现,其中数据从网络组件流向其他组件(业务组件)。
接下来,如果业务组件处理后需要再次通过网络传输处理后的数据,我们如何将处理后的数据从业务组件传输到网络组件呢?这里一般有两种方法。
方法一直接通过业务对应的socket fd、sessionID等某些指标找到这些数据对应的网络组件中的session,直接发送数据。
例如,某个数据需要经过处理后发送给所有用户。演示代码如下:
:互斥体
voidWebSocketSessionManager:pushInstrumentAndIndexIncrementData(conststd:stringdataToPush){std:lock_guardscoped_lock(m_mutexForSession);for(autosession:m_mapSessions){session.second-pushInstrumentAndIndexIncrementData (dataToPush);}}代码中,dataToPush是需要发送给所有用户的数据,所以记录的所有session对象变量网络组件并将它们一一发送(Session对象记录在m_mapSessions中)。
再比如,如果将处理后的数据发送给用户,则演示代码如下:
:互斥体
boolWebSocketSessionManager:pushOtherTypeDataToSingle(conststd:stringaccountID,conststd:stringtype,conststd:stringcontent,int64_toffset){boolfound=false;std:lock_guardscoped_lock(m _mutexForSession) ;//每次都需要遍历TODO:太慢,优化for(autosession:m_mapSessions){if(session.second-isAccountIDMatched(accountID) ) {session.second-pushOtherTypeData(type,content,offset);found=true;}}if(!found){LOGW('useraccountId=%sisnotfoundinsessions,type:%s,data:%s',accountID.c_str(), type .c_str(),content.c_str());returnfalse;}returntrue;}以上代码逻辑根据数据中的accountID定位到具体的session,然后将数据发送给session对应的用户。
该方法是业务组件处理数据后将数据传输到网络线程的常用方法之一。然而,该方法具有以下两个缺点。
缺点一从调用关系来看,业务线程实际上是调用网络线程相关的接口函数来发送数据。也就是说,本质上是业务组件直接发起的网络发送数据操作。如果按照功能来划分,发送数据应该是数据网络线程的功能,而业务线程不应该发送数据,所以这一般是不合理的。由于Session对象属于网络线程(网络线程管理这些Session的生命周期),而这里的业务线程直接操作Session对象,所以在上面的演示代码中,使用了mutex(成员变量m_mutexForSession)相应的发送功能。使用m_mapSessions 保护会话记录集。
该方法的示意图如下:
虽然这个方法不太合理,但却是很多服务项目的做法。当业务组件调用这些发送方法时,这些会话通过互斥体被锁定。但这里存在一个效率问题。我们以上面向所有用户发送数据的例子为例:
:互斥体
1voidWebSocketSessionManager:pushInstrumentAndIndexIncrementData(conststd:stringdataToPush)2{3std:lock_guardscoped_lock(m_mutexForSession);4for(autosession:m_mapSessions)5{6session.second-pushIn strumentAndIndexIncrementData(dataToP) ush);7}8} 这段代码实际上调用了每个会话对象的pushInstrumentAndIndexIncrementData方法(代码第6行)。如果session对象的pushInstrumentAndIndexIncrementData方法耗时较长(时间长是相对的,在实际开发中,我们应该避免该函数耗时过长),因为记录session对象m_mapSessions正在被业务模块(业务线程)使用这次。所以如果网络线程想要修改m_mapSessions对象,必须等到业务线程调用WebSocketSessionManager:pushInstrumentAndIndexIncrementData函数,这可能会影响网络线程的执行效率。因此,有的开发者会这样设计:
voidWebSocketSessionManager:pushInstrumentAndIndexIncrementData(conststd:stringdataToPush){std3336 0:mapmapLocalSessions;{std:lock_guardscoped_lock(m_mutexForSession);//将session对象指针从m_mapSessions复制到mapLo calSessionsmapLocalSessions=m_mapSessions;}//这里使用mapLocalSessions,以便网络线程可以继续操作m_mapSessionsfor(autosession:mapLocalSessions){session.second-pushInstrumentAndIndexIncrementData( dataToPush);}} 上面的代码使用了一个临时变量mapLocalSessions来复制一个记录在的session指针原始的m_mapSessions。这种情况下,m_mutexForSession锁的粒度大大降低,业务线程尽快释放m_mapSessions,网络线程m_mapSessions也能很快使用。
然而,这个看似不错的设计却存在严重的问题。 m_mapSessions和mapLocalSessions中记录的很多会话指针都指向一个对象。如果此时有连接断开,网络线程就会销毁m_mapSessions中的记录。 session对象,这样业务线程就可以继续用这个session对象的指针进行操作(发送数据)。此时这个指针已经是野指针了,这会导致我们的程序崩溃。有读者会说mapLocalSessions中记录的session对象不应该使用裸指针。可以使用智能指针,但是智能指针不保存会话的生命周期,如下形式:
std:mapmapLocalSessions;虽然这样可以解决绝对使用session对象时及时发现session对象是否有效的问题,但是如果在使用session的过程中,比如进入session.second-pushInstrumentAndIndexIncrementData函数后,session被回收了网络线程中,此访问会话的任何成员变量都会导致程序访问到非法指针,从而导致程序崩溃。
因此,在使用该方法时,一定不要使用该技术来降低锁的粒度。这是不正确的。为了保证性能,session.second-pushInstrumentAndIndexIncrementData函数的实现必须尽快执行。
缺点二第二个缺点是方法一在以下场景下的致命问题:假设你的服务有以下两个信息流。信息流一:业务组件产生的数据需要发送。网络线程本身与客户端或者下游服务交互后,还必须发送生成的数据。如果将这两类数据发送到同一个连接,那么这两类数据是有一定的顺序的。现在情况很糟糕。因为在这种设计中,你的业务线程会间接使用某个session来发送数据,而你的网络线程会直接使用某个session来发送数据。这相当于多个线程同时调用send函数在同一个socket上发送。这种情况下,每个数据包可能不会出错,但多个数据包之间的顺序就会出错。
我在做我们交易系统的行情推送服务时曾经遇到过这样的问题。业务模块会从某个kafka topic中取出增量数据,然后侵入网络组件发送给用户,但是用户自己将订阅命令发送给网络模块,网络模块会查询并推送一条完整的数据通过内部http 服务向用户提供的数据量。当用户收到增量数据时,会在全量数据的基础上进行全量数据的添加、删除或修改。也就是说,如果用户没有收到全量数据,则收到的增量数据将被丢弃。然而,被丢弃的增量数据可能是有效的,因为它被丢弃,导致用户在接收到全量数据后,使用新的增量数据对全量数据进行变换。此时,转换后的增量数据就不再正确了。
这就是方法一不适用的场景,即入侵网络组件的其他组件产生的数据有多个来源,并且多个来源有顺序要求。另一方面,如果入侵网络组件产生的数据源只有一个数据源或者有多个数据源但数据源之间的数据不存在顺序依赖,这种设计也是可以的。
不适用的场景示意图
那么如果有多个数据源,但是数据源之间的数据有顺序依赖关系,有没有办法继续使用方法一呢?是的,您可以处理来自多个数据源的数据并将其交给专用数据源。然后排序模块统一调用网络模块的数据发送模块。需要说明的是,网络组件内部产生的需要发送的数据也必须交给排序组件。示意图如下:
可以以此场景为例。我们在做交易系统的行情推送服务时,推送的数据来自多个来源,有的是来自kafka模块,有的是来自管理后端接口,有的是来自网络通信模块。这三类数据在内部生成,最终都需要按照一定的顺序发送给用户。我是按照上面示意图所示的结构来设计的。排序组件使用队列。不同数据源的数据按照一定的顺序进入队列。排序模块从队列中一一取出排序后的数据,调用网络通信模块的数据发送模块发送。
方法二第一种方法是直接调用业务组件中的网络组件方法,有点越界了。第二种方法是将业务组件需要发送的数据交给网络组件本身。常见的实现方法是将对应的数据添加到该数据所属连接的网络线程中。再看一下这个结构:
while(!m_bQuitFlag){epoll_or_select_func();handle_io_events();handle_other_things();} 可以使用另一个队列,业务组件会将队列交给这个队列,然后通知对应网络组件中的线程它需要来执行任务。这个逻辑之前已经介绍过,即利用唤醒机制执行handle_other_things函数。
这是一个实现,业务组件调用
voidEventLoop:runInLoop(constFunctorcb){if(isInLoopThread()){cb();}else{queueInLoop(cb);}} 其中cb是需要执行的任务。由于业务线程和网络线程不是同一个线程,所以会执行queueInLoop函数。queueInLoop的实现如下,从而将任务放入pendingFunctors_容器中,然后调用唤醒函数wakeup()。
:互斥体
voidEventLoop:queueInLoop(constFunctorcb){{std:unique_locklock(mutex_);pendingFunctors_.push_back(cb);}if(!isInLoopThread()||doingOtherTasks_){wakeup();}}被唤醒的线程执行handle_other_things()函数,该函数从pendingFun开始演员_取出任务并执行。
:互斥体
voidEventLoop:handle_other_things(){std:vectorfunctors;doingOtherTasks_=true;{std:unique_locklock(mutex_);functors.swap(pendingFunctors_);}for(size_ti=0;ifunctors.size();++i){functors[i] ();}做其他任务_=false;}通过这个过程,网络组件本身发送业务组件交给它的数据。
希望读者能够深入理解侵入式和非侵入式服务结构的特点和细节,以及侵入式结构中网络组件和业务组件之间交换数据的两种方法,并结合实际设计出高质量的服务框架。商业。
OK,本文到此结束,希望对大家有所帮助。
本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.iotsj.com//kuaixun/7158.html
用户评论
这篇文章好像在讨论软件开发中两种不同的设计方式?
有16位网友表示赞同!
我一直不太懂“侵入式”是什么意思,这篇文章能解释一下吗?
有14位网友表示赞同!
我觉得“非侵入式”听起来比较好,不会对系统造成太大的影响吧?
有15位网友表示赞同!
我平时用的编程语言中好像没有提到过这些概念。
有20位网友表示赞同!
看来学习新的技术总是需要不断的挑战和思考啊!
有11位网友表示赞同!
这种文章标题让人很难懂啊,是不是可以简明易懂一点呢?
有11位网友表示赞同!
感觉这两者之间可能会有比较大的区别吧,希望能详细分析一下。
有17位网友表示赞同!
最近在学习软件设计,刚好碰到这篇文章,很有兴趣想要深入了解一下
有14位网友表示赞同!
看标题就想问问作者他们是如何区分侵入式和非侵入式的?
有14位网友表示赞同!
这篇文章应该能帮我更好地理解不同技术架构的优缺点吧。
有16位网友表示赞同!
学习新知识总是很好的一件事,希望这篇文章能给我带来启发!
有19位网友表示赞同!
我以前从没想过软件设计会有这么细致的区别,真是开眼界了
有6位网友表示赞同!
对软件开发的两种设计模式感兴趣,希望能读到更详细的讲解。
有5位网友表示赞同!
这种标题听起来像是专业术语,希望文章能解释清楚概念。
有5位网友表示赞同!
感觉这篇文章应该很实用,可以帮助我在学习编程时更好地选择合适的方案。
有6位网友表示赞同!
我之前接触过一些软件架构设计,或许这篇文章会让我有新的理解
有15位网友表示赞同!
软件的结构确实很重要,不同的结构会影响程序的性能和维护性吧。
有8位网友表示赞同!
看标题感觉好像是一篇比较学术性质的文章,希望能读得懂!
有17位网友表示赞同!
对侵入式和非侵入式的区别有点好奇,希望文章能解释清楚!
有19位网友表示赞同!