judge模块的框架

完成了网页渲染的功能之后,就需要判断用户提交的代码是否是正确的,当用户点击提交之后,就会交给路由模块的/judge模块,然后这个路由模块就需要去调用jude模块了,也就是需要一个新的jude模块,这个功能也有control模块整合之后提供给上层。路由模块提供的判题路由服务会给juge模块提供一个字符串,这个json串中有题目的id,然后是用户的输入("因为我没有做相关的工作所以没有作用"),最后是用户的代码。这样的一个json串。

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡

得到了用户提交的json串之后,将json串进行发序列化之后,就需要使用Judge去调用后台的compile_run服务器提供的编译允许服务了。但是对于用于提交的代码,只有函数,没有测试用例是无法运行的所以这里还需要对用户提交的代码进行拼接之后再提交给后端编译服务器去运行。

由此就知道了这个Judge模块需要做的工作:

负载均衡式在线oj项目开发文档2(个人项目)_离线_02

对于这5个工作除了第三个,其它的在这个函数中都能够完成了。为了完成第三个工作,所以就需要完成负载均衡模块了。

在这个模块中也需要进行很多的差错处理。下面就来完成这个模块

负载均衡模块的编写

对于这个模块我首先需要创建一个新的文件夹和新的文件:

负载均衡式在线oj项目开发文档2(个人项目)_服务器_03

在这个文件中写入的就是现在存活的后端编译服务所在的ip地址和端口号,写文件的样式如下:

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_04

上图就是存在三台提供编译运行服务的后端服务器。以及自己对应的ip地址和端口号。

现在服务器的配置文件就已经有了。现在就需要在oj_control中设计负载均衡,首先既然是要负载均衡的选择服务器,所谓的选择也就是管理,如何管理呢?先描述再组织。所以需要先将后端的编译服务器使用一个结构体进行描述,然使用一个容器将这些结构体对象储存起来,这就是大体的设计逻辑了。下面是详细的代码:

负载均衡式在线oj项目开发文档2(个人项目)_离线_05

但是因为某一个后端服务器可能会同时收到多个负载请求,让load不断++,并且这些请求是并行的,为了防止load出现错误,需要使用一个锁。这里使用c++中的锁。但是c++的锁无法进行拷贝,所以下面使用的是c++锁的指针。便于之后将描述服务器的对象放到容器中进行管理。

负载均衡式在线oj项目开发文档2(个人项目)_离线_06

有了元素之后再写构造函数。

负载均衡式在线oj项目开发文档2(个人项目)_服务器_07

有了描述服务器的类,下面就是通过容器实现负载均衡了。

这个模块也就是LoadBlance,这里我选择的容器是vector.

负载均衡式在线oj项目开发文档2(个人项目)_服务器_08

那么如果这些机器不在线或者在线又如何表示呢?难道真的就是离线从这个列表中删除吗?这样有些麻烦了,这里再使用两个vector一个用于维护现在在线的机器,一台用于维护离线的机器。

负载均衡式在线oj项目开发文档2(个人项目)_服务器_09

也就是说如果将服务器的Machine填到list表中后,除非后来再增加新的主机否则这个vector就一直不在变了,如果出现了主机离线就直接使用下面的vector进行处理即可。

然后就是构造和析构函数了。添加完成之后,因为所有服务器的信息都在conf文件中,所以需要一个函数,用于读取conf文件中的内容,将现在在线的编译服务器形成对象放到vector中。

负载均衡式在线oj项目开发文档2(个人项目)_服务器_10

再创建一个全局变量:

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_11

最后再提供一个函数,用于在在线列表中选择负载最小的一台机器。

负载均衡式在线oj项目开发文档2(个人项目)_服务器_12

然后还要提供一个上线服务器的函数,以及一个下线服务器的函数

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_13

也就是对在线数组/下线数组的增删功能。

下面就来实现这些函数

首先是读取conf文件的函数也就是loadconf函数。

要读取文件自然需要先将文件打开了,

负载均衡式在线oj项目开发文档2(个人项目)_离线_14

加载配置文件失败了,那么我的这个服务器也就只能提供题目和编写代码的功能了,judge功能就直接没有了,所以直接进行返回即可。

然后再配置文件中的文件信息的格式如下:

负载均衡式在线oj项目开发文档2(个人项目)_离线_15

一行字符串中间使用:进行分隔,为了能够分别得到两个对我有用的信息,所以需要对字符串进行处理。这就需要使用之前写好的工具类了。

完成拆分之后,形成一个Machine对象,然后将值填入申请好一个锁,最后放到vector中。然后再将onliev的值进行更新。读取文件完毕之后,关闭文件。

下面是代码:

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_16

负载均衡式在线oj项目开发文档2(个人项目)_离线_17

对于这个函数详细的细节请看上面的注释。

到这里所有主机的配置信息就全部都有了,并且每一个在线主机的id也都有了。然后将这个函数放到Load类的构造中,让其一创建对象就会完成读取文件的工作。

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_18

现在已经完成了读取配置文件的工作,下面就是完成负载均衡选择了,也就是smartchoice函数。

这个函数需要完成两个功能,第一个:使用选择好的主机(更新主机的负载),第二个:部分主机可能需要离线(当然这个离线和储存machine对象的vector是没有关系了,也就是要将某个主机的id从online移动到offline中)。

因为这两个功能,所以这个函数需要两个参数

负载均衡式在线oj项目开发文档2(个人项目)_离线_19

注意这里的machine* m这个参数是存在问题的,之后会修改。

现在思考一个问题,未来可能会有多台客户端提交代码。也就是LoadBance服务可能会为多个执行流提供服务,为了保证这个服务的安全所以LoadBlance也需要加锁。

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_20

这样就能保证LoadBance访问的安全了。

现在LoadBance有了锁,所以smart服务就需要先去申请锁了。

负载均衡式在线oj项目开发文档2(个人项目)_服务器_21

这里我选择的是通过轮询的方式找到负载最低的主机。

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_22

上面的代码考虑了在在线主机为0的时候,表示已经无法提供服务直接返回false。然后就是在下面轮询寻找负载最低负载的代码也是存在问题的,因为一个执行流在轮询找最低负载的代码时,其它的执行流可能会让这个主机的负载进行增加或者减减。所以这里我在Machine中增加一个函数,用于安全的返回负载。

负载均衡式在线oj项目开发文档2(个人项目)_离线_23

将这个函数放到上面的负载均衡代码中。

然后选择到了一个主机,就需要对对应的主机进行负载的更新了,所以依旧是要在machine中增加一个安全的增加负载的函数。同时完成任务的服务器也需要进行负载的减减。并且这两个函数也是会有多个执行流去访问的所以依旧需要使用锁去进行保护。

下面去完成这两个函数:

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_24

但是获取Load那个函数其实没有太大的意义只是为了统一代码。

现在这个模块就已经基本完成了,后面的代码暂时不做处理。先去完成judge模块。

现在主机有了,负载均衡也有了。就需要进行judge了。此时就需要将负载均衡模块放到ctrl中了

udge模块的编写

Judge模块后序是被server.cc模块调用起来的。

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_25

并且这个模块还会将题目的编号也传递过来。

这里我就修改一下judge函数的参数让其能够接收这个number。

既然题目的编号已经有了,那么传入的这个in_json的情况也就可以将题目编号删除了。

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_26

那么现在题目的编号已经有了第0步(上图少了一步)通过题目的编号获取题目。通过model模块这是可以做到的。并且这个返回值也不需要进行判断,因为这个judge模块是服务器返回给用户的,一定是存在的所以不需要进行判断。

负载均衡式在线oj项目开发文档2(个人项目)_服务器_27

然后用户提交的代码信息就在in_json这个json串中,下面要做的自然就是对这个json串进行反序列化,读取用户提交的信息了。

负载均衡式在线oj项目开发文档2(个人项目)_离线_28

但是还有一个最重要的步骤就是要将用户提交的代码和测试代码进行合并形成一个新的代码。(之后还需要去编译模块设置一下g++,让其设置一个环境变量,很简单)

负载均衡式在线oj项目开发文档2(个人项目)_服务器_29

下面就是要形成一个新的json串了,这个json串用于提供给编译

服务。

负载均衡式在线oj项目开发文档2(个人项目)_离线_30

此时进行后端请求的json串就有了。

之后就是选择负载最低的主机了。这个选择也是有策略的,你要请求几次呢?如果请求失败了呢?

首先关于选择策略,我的规则是一直选择,直到存在主机可用,否则所有主机都已经挂了。

下面就是选择主机的代码:

负载均衡式在线oj项目开发文档2(个人项目)_服务器_31

  这也是为什么在smartchoice函数那里我选择的是将二级指针作为我的参数,就是因为我打算在这里以指针的方式获取服务器的信息,所以在smartchoice中就只能使用二级指针的方式去储存machine的地址。

  下面就需要将编译允许模块发起请求了

负载均衡式在线oj项目开发文档2(个人项目)_离线_32

  如何发起请求呢?在httplib这个第三库中是提供了对应的方法的:

  例如下面就是以post方式进行网络请求第一个参数你要请求的资源路径,第二个就是你要传递给对方的参数,第三个就是参数类型

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_33

  例如下面就是一个示例代码:

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_34

  只用将方法替换为post即可,函数如下:

负载均衡式在线oj项目开发文档2(个人项目)_服务器_35

  这里能够使用res去判断因为这个函数的返回值是一个

负载均衡式在线oj项目开发文档2(个人项目)_服务器_36

  Result,而这个Result:

负载均衡式在线oj项目开发文档2(个人项目)_服务器_37

  内部就是一个智能指针,既然是指针自然可以使用这种方法了,指针为nullptr,就肯定是失败的。

如果请求失败了,就打印一条日志:并且对这台服务器进行离线处理。

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_38

如果请求成功了,运行的结果就在compile服务发送过来的json串中,而这个串的内容就在res这个返回结构的body中。

负载均衡式在线oj项目开发文档2(个人项目)_服务器_39

之后如果请求成功那么自然是要让负载降低的。而如果这个服务器请求失败了那么负载也是需要降低的。选择如果成功了那么负载是要进行增加的。

负载均衡式在线oj项目开发文档2(个人项目)_离线_40

但是其实当选择失败的时候,对负载的降低是没有必要的,因为在离线服务器的时候会直接将负载减少为0。

但是上面的代码还是存在问题的,首先就是即使是从编译服务端获得了响应,如果这个请求的状态码不是200,那么这个响应就是无效的。但是这里对于其它的状态码我这里不管,只处理状态码为200的响应。从这个响应的结构体中就可以看到状态码这个字段:

负载均衡式在线oj项目开发文档2(个人项目)_服务器_41

如果状态码为其它的就让这个主机的负载降低,然后去选择其它的主机。

代码如下:

负载均衡式在线oj项目开发文档2(个人项目)_负载均衡_42

如果连响应都没有说明这个主机已经挂了就需要去离线这一台主机,而我是知道这台主机的id值的,这个id就是离线主机的依据。

下面就是离线主机的函数编写。需要主机在一个执行流正在离线某一台主机的时候,如果不进行加锁,就可能导致另外一个执行流选择到这台离线的主机,所以这里需要进行加锁。

以下就是一个离线的功能了

负载均衡式在线oj项目开发文档2(个人项目)_服务器_43

对于让主机上线这个功能之后再说。

这里为了便于之后我的调试我增加了一个打印当前的在线主机/离线主机列表的函数。然后在请求主机失败的时候离线之后打印一下这个信息查看是否离线成功。

负载均衡式在线oj项目开发文档2(个人项目)_离线_44

在编译运行服务成功的地方使用日志打印一下信