架构设计常画哪些图
在架构设计时,经常需要画图。在大多数时候,我们工作的团队并没有对架构设计中的图做规定,只要能把架构表达清楚即可,对于比较简单的软件,甚至不需要画图,只使用文字就可以描述清楚。这种因人而异的画图方式,比较灵活,并且效率高,用较少的图就能表达较多的信息,一张图中既能表达静态信息,又能表达动态信息,但是不利于跨团队之间的合作(很多时候也不需要跨团队合作,当然也没有问题)。当需要跨团队合作,或者多个人协同开发的时候,统一画图标准,往往就比较重要。
uml定义了一些图的类型,包括类图、组件图、时序图、流程图、状态机图、用例图等。不同类型的图适用的场景是不一样的,类图、组件图用于表达软件的静态架构;时序图用于表达不同软件组件的交互流程;流程图适合表达一个组件的流程(聚焦于一个组件);状态机图用于描述具有多个状态的对象,描述对象锁具备的状态以及状态之间的转换关系,转换条件;用例图用于表示软件需求,即站在用户的角度来看软件,软件所具备的功能。
在架构设计时,往往需要用不同的图来说明软件的架构,多种类型的图既有自己独特的部分,也有相互重叠的部分,从不同角度来共同描述软件的功能。
架构设计的前置条件是软件需求,软件需求往往包括功能需求,性能需求,接口需求,这些都是站在用户的角度来提出的需求。功能需求,满足用户需要的功能,这是最基础的;性能需求,比如软件占用的内存、cpu不能大于多少,完成一个功能的时间不能呢个大于多少;接口需求,如果用户对于接口有要求,那么软件实现之后,提供给用户的接口,就要按照用户的需求来实现。
架构设计的后置工作是详细设计或者代码开发,这就要求架构设计要足够的清除,让代码开发人员能够按照架构设计进行代码开发。
2静态图
2.1组件图
元素图用于表示组成软件的组件有哪些。比如,对于一辆汽车,有4大部分组成:发动机、底盘、车身、电气设备。这4个系统可以看做是汽车的一级组成系统,每个一级系统又会包括多个二级系统,二级系统又会包括三级系统,等等。这些都是汽车的静态组成部分。再比如,linux操作系统,由3个基础的子系统组成:文件系统,内存管理,进程管理。另外设备驱动,网络也是操作系统的重要组成部分。
以linux的网络子系统为例,其元素图如下,下图进行了简化,同时又能说明元素图的含义:
架构设计的前置条件是软件需求,软件需求的主要内容就是功能需求,也就是软件要实现什么样的功能。作为软件开发工程师,首先要将功能进行拆分,当然对于很简单的功能,几行或者几百行代码就能实现,这个时候功能比较单一,也许不需要对功能进行拆分;大部分时候,我们开发的软件都是比较复杂的,代码可以到几千行甚至几万行,这个时候就需要对功能进行拆分。
如上图所示,linux网络子系统,包括6个模块:
(1)epoll作为多路复用技术,可以监听socket的事件,比如socket收到了数据,socket发生了错误。
(2)socket作为提供给用户的网络抽象,可以代表一个tcp连接。socket和epoll均是用户可以使用的。
(3)tcp,传输层协议,面相连接的可靠的字节流。
(4)ip,网络层协议,实现分片和路由。
(5)network device,linux内核为网卡驱动提供的一套框架,屏蔽掉不同网卡驱动的区别,实现统一的接口。
(6)网卡驱动。
2.2接口图
接口图,用来说明个功能模块之间的接口。各个模块,功能实现软件的功能,模块之间往往需要相互依赖。所在,在架构设计时,需要定义好模块之间的接口。定义好各个模块的接口之后,在代码开发阶段,可以分工合作,每个人负责其中的一个模块。
2.2.1epoll和socket之间的接口
(1)socket提供接口sock_poll,epoll调用该接口,确定socket中发生了什么事件,是有数据可读还是发生了错误,还是发送缓冲区空闲了
(2)epoll提供接口ep_poll_callback,确切来说,是epll向socket注册了这个函数,当socket中有事件的时候,socket会调用到这个函数
2.2.2socket与tcp之间的接口
(1)tcp提供函数tcp_poll,tcp_recvmsg,tcp_sendmsg,socket分别用来获取事件、接收数据、发送数据。
(2)socket提供接口sock_def_readable、sock_def_write_space、socket_def_error_report,tcp分别用来向socket通知可读事件、写缓冲区空闲、报告错误。
2.2.3tcp与ip之间的接口
(1)接收数据时,ip层收到报文之后,通过函数tcp_v4_rcv将报文传递给tcp来处理
(2)发送数据时,tcp通过函数ip_queue_xmit,将报文发送到ip层处理
2.2.4ip与network device之间的接口
(1)发送报文时,ip调用函数__dev_queue_xmit发送报文
(2)接收报文时,network device调用ip_rcv将报文传递给ip层进行处理
2.2.5network device和nic driver之间的接口
network device把一些通用的从网卡驱动中抽象出来,屏蔽掉不同网卡驱动的区别,统一了接口。
就是在ip层和网卡驱动层中间的一层。
(1)接收报文时,网卡驱动调用__napi_schedule,触发软中断,在软中断中处理接收到的报文
(2)发送报文时,net_device_ops.ndo_start_xmi挂的函数发送报文
2.3类图
类图能更详细的描述雷和类之间的关系,每个类提供了哪些接口。
uml类关系(实现、继承,聚合、组合,依赖、关联)_uml类关系 ea-CSDN博客
3动态图
时序图和流程图是最常用的两种动态图。时序图常用来描述模块和模块之间的交互关系。流程图常用来描述模块内部的流程。
3.1时序图
上图是最简单的接收报文的时序图。
3.2流程图
3.2.1函数tcp_sendmsg流程图
(1)在tcp_sendmsg函数中,首项要计算size_goal,根据gso参数和mss计算size_goal。size_goal的意思发送的判断条件,如果这次可以发送的数据已经不小于size_goal了,那么可以立即发送数据。
(2)将数据拷贝到skb中,skb是贯穿整个网络协议栈的,管理报文的数据结构。
(3)如果用户数据均已拷贝到skb中,则调用tcp_push来发送数据。
tcp_push函数中,并不是立即发送数据,要进行autocork判断,自动阻塞。以下四个条件同时满足,则会自动阻塞:
①当skb中数据长度小于size_goal
②并且tcp_autocorking标志为true
③重传队列不为空,说明当有数据正在网络中传输,并且ack没有返回,可能很快就会有ack返回了,ack返回的时候也会尝试发送数据。等待这很短的事件,可能可以将用户的数据进行合并,进行减少协议栈的调用次数。
④当前发送队列中还有数据,在等待发送,之前的数据还在等待,那么现在这个着急向下进行照样也是等待,还不如在最开始的时候进行等待。
static bool tcp_should_autocork(struct sock *sk, struct sk_buff *skb,int size_goal)
{return skb->len < size_goal &&READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_autocorking) &&!tcp_rtx_queue_empty(sk) &&refcount_read(&sk->sk_wmem_alloc) > skb->truesize;
}
(3)如果拷贝的数据长度不小于size_goal,并且没有设置MSG_OOB标志
判断force push标志,write_seq是拷贝到skb中的数据,但是还没有发送,pushed_seq是已经发送的数据,当两者差值大于最大窗口的一半的时候,则立即发送数据。
static inline bool forced_push(const struct tcp_sock *tp)
{return after(tp->write_seq, tp->pushed_seq + (tp->max_window >> 1));
}
当前skb正好是要处理的下一个skb,立即发送。