【从零构建分布式在线评测系统】三、服务器端:评测机管理器和评测机类

📅 2026/7/8 9:10:38
【从零构建分布式在线评测系统】三、服务器端:评测机管理器和评测机类
在服务器端有很多事情要做1、管理Judge0服务器2、轮询数据库获取待评测的用户提交1次用户提交对应多个评测点每个评测点都需要下发给Judge0进行评测3、接收Judge0服务器的回调根据Token来查询当前测试点是哪个题目的将结果加入到该题目中。当一个题目的测试点全部返回计算分数写入数据库。这中间有一些生产者消费者模型也涉及到很多OJ和Judge0常量的转换、数据库格式等基本上所有代码都要考虑异步和并发。本文主要展示上一篇中服务器管理器的具体实现以及Judge0Server是如何完成各项功能的。一、服务器管理的完整代码Imports System.Collections.Concurrent Imports System.Net Imports System.Text Imports System.Text.Json Imports System.Threading Public Class Judge0ServerManager Structure CallBackPort Public port As Integer Public used As Boolean Sub New(p As Integer) port p used False End Sub End Structure Private Shared HttpListener As HttpListener Private Shared CallbackPortList As ConcurrentDictionary(Of Integer, CallBackPort) Private Shared Judge0ServerGroup0List As ConcurrentDictionary(Of String, Judge0Server) Private Shared Judge0ServerGroup1List As ConcurrentDictionary(Of String, Judge0Server) Friend Shared Async Sub Work() 开始监听服务器心跳 BeginHttpListerner() Dim lastCleanupTime As DateTime DateTime.UtcNow While True 每10秒清理一次超时token If (DateTime.UtcNow - lastCleanupTime).TotalSeconds 10 Then CleanupAllServersExpiredTokens() lastCleanupTime DateTime.UtcNow End If 检查待评测队列内个数 Dim qCountSmall As Integer Judge0TestQueue.Count(0) If qCountSmall 0 AndAlso Judge0ServerGroup0List.Count 0 Then 更新每个服务器工作队列大小为每个服务器分发任务。若某个服务器无法工作移除它。 Dim sum As Integer qCountSmall For Each kv In Judge0ServerGroup0List sum kv.Value.Judge0QueueCount 加上每个服务器现有负载 Next 使服务器达到平均个数不是强制的服务器会自行决定是否摘取如果队列为空服务器不会摘取 Dim a As Integer (sum Judge0ServerGroup0List.Count - 1) / Judge0ServerGroup0List.Count For Each v In Judge0ServerGroup0List.Values StartServerProcessing(v, a) Next End If Dim qCountBig As Integer Judge0TestQueue.Count(1) If qCountBig 0 AndAlso Judge0ServerGroup1List.Count 0 Then Dim sum As Integer qCountBig For Each kv In Judge0ServerGroup1List sum kv.Value.Judge0QueueCount 加上每个服务器现有负载 Next 使服务器达到平均个数不是强制的服务器会自行决定是否摘取如果队列为空服务器不会摘取 Dim a As Integer (sum Judge0ServerGroup1List.Count - 1) / Judge0ServerGroup1List.Count For Each v In Judge0ServerGroup1List.Values StartServerProcessing(v, a) Next End If Await Task.Delay(200) 每200毫秒检测一次 End While End Sub Private Shared Sub CleanupAllServersExpiredTokens() Try For Each server In Judge0ServerGroup0List.Values server.RemoveExpiredTokens() server.ValidateAndFixQueueCount() 清理后校验计数 Next For Each server In Judge0ServerGroup1List.Values server.RemoveExpiredTokens() server.ValidateAndFixQueueCount() 清理后校验计数 Next Catch ex As Exception Console.WriteLine($[{DateTime.Now}] $CleanupAllServersExpiredTokens异常: {ex.Message}) End Try End Sub Private Shared Sub StartServerProcessing(server As Judge0Server, targetQueueSize As Integer) Task.Run(Async Function() Dim workCount targetQueueSize - server.Judge0QueueCount Try Dim success As Boolean Await server.Work(workCount) If Not success Then Console.WriteLine(server.Work(workCount) 出现错误) End If Catch ex As Exception Console.WriteLine(RemoveServer(server) 出现错误) End Try End Function) End Sub Private Shared Sub RemoveServer(ip As String, group As Integer) Try Dim removedServer As Judge0Server Nothing Dim success As Boolean False If group 0 Then success Judge0ServerGroup0List.TryRemove(ip, removedServer) Else success Judge0ServerGroup1List.TryRemove(ip, removedServer) End If If success AndAlso removedServer IsNot Nothing Then Console.WriteLine($[{DateTime.Now}] $移除服务器(Group{group}): {ip}) 回滚该服务器上所有待处理的提交 RollbackPendingSubmissions(removedServer) End If Console.WriteLine($[{DateTime.Now}] $当前服务器数量 - Group0: {Judge0ServerGroup0List.Count}, Group1: {Judge0ServerGroup1List.Count}) Catch ex As Exception Console.WriteLine($[{DateTime.Now}] $RemoveServer异常: {ex.Message}) End Try End Sub Private Shared Sub RollbackPendingSubmissions(server As Judge0Server) Try server.RollbackAllPendingSubmissions() Catch ex As Exception Console.WriteLine($[{DateTime.Now}] $RollbackPendingSubmissions异常: {ex.Message}) End Try End Sub Shared Function BeginHttpListerner() As Boolean CallbackPortList New ConcurrentDictionary(Of Integer, CallBackPort) For i As Integer OJConfig.ListerPortBegin To OJConfig.ListerPortEnd CallbackPortList.TryAdd(i, New CallBackPort(i)) Next Judge0ServerGroup0List New ConcurrentDictionary(Of String, Judge0Server) Judge0ServerGroup1List New ConcurrentDictionary(Of String, Judge0Server) Try HttpListener New HttpListener HttpListener.Prefixes.Add($http://*:{OJConfig.ListerHearbeatPort}/) HttpListener.Start() 改为异步处理 BeginGetContext() Task.Run(AddressOf HealthCheck) Return True Catch ex As Exception Console.WriteLine($[{DateTime.Now.ToString}] HttpCallBack Err: ex.ToString) Return False End Try End Function Private Shared Sub BeginGetContext() 异步开始接收请求 HttpListener.BeginGetContext(AddressOf HandleRequest, HttpListener) End Sub Private Shared Sub HandleRequest(ar As IAsyncResult) Dim context As HttpListenerContext Nothing Try Dim listener As HttpListener CType(ar.AsyncState, HttpListener) context listener.EndGetContext(ar) 立即开始监听下一个请求 BeginGetContext() 获取客户端 IP 地址 Dim clientIP As String context.Request.RemoteEndPoint.Address.ToString 读取请求数据并复制到内存中这样可以在异步处理中使用 Dim requestBody As String Nothing Using reader As New IO.StreamReader(context.Request.InputStream, context.Request.ContentEncoding) requestBody reader.ReadToEnd() End Using Console.WriteLine($[{datetime.now.tostring}] $接收到一个回调。) 使用线程池处理请求但不再传递context对象 ThreadPool.QueueUserWorkItem(Sub(state) Try 处理请求数据 Dim resultResponseApi JsonSerializer.Deserialize(Of HeartbeatMessage)(requestBody) 处理结果并加入队列 ProcessResult(resultResponseApi, clientIP) Catch ex As Exception If String.IsNullOrEmpty(requestBody) Then Console.WriteLine($[{DateTime.Now.ToString}] {clientIP} 扫描了本地端口 {OJConfig.ListerHearbeatPort}) Else Console.WriteLine($[{DateTime.Now.ToString}] clientIP Judge0ServerManager Request processing error: ex.ToString vbCrLf 原始内容 requestBody) End If End Try End Sub) 立即响应客户端 Dim responseBuffer As Byte() Encoding.UTF8.GetBytes(Callback received successfully.) context.Response.ContentLength64 responseBuffer.Length context.Response.StatusCode 200 context.Response.OutputStream.Write(responseBuffer, 0, responseBuffer.Length) context.Response.Close() Catch ex As Exception Console.WriteLine($[{DateTime.Now.ToString}] Judge0serverManager HandleRequest发生错误 ex.ToString) If context IsNot Nothing AndAlso context.Response IsNot Nothing Then context.Response.StatusCode 500 context.Response.Close() End If End Try End Sub Private Shared Sub ProcessResult(resultResponseApi As HeartbeatMessage, clientIP As String) If Judge0ServerGroup0List.ContainsKey(clientIP) OrElse Judge0ServerGroup1List.ContainsKey(clientIP) Then Else If resultResponseApi.Group 0 Then Console.WriteLine(${Now.ToString} 收到{clientIP}的心跳消息可用内存(MB){resultResponseApi.AvailableMemoryOfMB}内存保护阈值(MB){resultResponseApi.AvailableMemoryThresholdOfMB}。共有服务器{Judge0ServerGroup0List.Count 1}/{ Judge0ServerGroup1List.Count}) Else Console.WriteLine(${Now.ToString} 收到{clientIP}的心跳消息可用内存(MB){resultResponseApi.AvailableMemoryOfMB}, 内存保护阈值(MB){resultResponseApi.AvailableMemoryThresholdOfMB}。共有服务器{Judge0ServerGroup0List.Count }/{ Judge0ServerGroup1List.Count 1}) End If End If 检查是否是服务器重启之前存在现在重新连接 Dim isRestart As Boolean False If Judge0ServerGroup0List.ContainsKey(clientIP) Then Dim existingServer Judge0ServerGroup0List(clientIP) If (DateTime.UtcNow - existingServer.Judge0ServerlastHeartbeat).TotalSeconds 30 Then isRestart True Console.WriteLine($[{DateTime.Now}] $检测到服务器重启(Group0): {clientIP}) RemoveServer(clientIP, 0) End If ElseIf Judge0ServerGroup1List.ContainsKey(clientIP) Then Dim existingServer Judge0ServerGroup1List(clientIP) If (DateTime.UtcNow - existingServer.Judge0ServerlastHeartbeat).TotalSeconds 30 Then isRestart True Console.WriteLine($[{DateTime.Now}] $检测到服务器重启(Group1): {clientIP}) RemoveServer(clientIP, 1) End If End If Dim CallBackURL As String If isPrivateNetwork(clientIP) Then CallBackURL OJConfig.OJPrivateNetWorkURL Else CallBackURL OJConfig.OJPublicNetWorkURL End If If resultResponseApi.Group 0 Then If Judge0ServerGroup1List.ContainsKey(clientIP) Then RemoveServer(clientIP, 1) Dim Server As Judge0Server Judge0ServerGroup0List.GetOrAdd( clientIP, Function(key) Dim NextCallBackPort As Integer FindMinUnusedPort() SetPortUsedStatus(NextCallBackPort, True) Return New Judge0Server(clientIP, resultResponseApi.Port, NextCallBackPort, CallBackURL, resultResponseApi.QueueMaxSize, resultResponseApi.RequestCountSize, resultResponseApi.AvailableMemoryThresholdOfMB, resultResponseApi.Group) End Function) 更新心跳时间 Server.Judge0ServerlastHeartbeat DateTime.UtcNow 更新可用内存大小 Server.Judge0ServerAvailableMemoryOfMB resultResponseApi.AvailableMemoryOfMB Else If Judge0ServerGroup0List.ContainsKey(clientIP) Then RemoveServer(clientIP, 0) Dim Server As Judge0Server Judge0ServerGroup1List.GetOrAdd( clientIP, Function(key) Dim NextCallBackPort As Integer FindMinUnusedPort() SetPortUsedStatus(NextCallBackPort, True) Return New Judge0Server(clientIP, resultResponseApi.Port, NextCallBackPort, CallBackURL, resultResponseApi.QueueMaxSize, resultResponseApi.RequestCountSize, resultResponseApi.AvailableMemoryThresholdOfMB, resultResponseApi.Group) End Function) 更新心跳时间 Server.Judge0ServerlastHeartbeat DateTime.UtcNow 更新可用内存大小 Server.Judge0ServerAvailableMemoryOfMB resultResponseApi.AvailableMemoryOfMB End If If isRestart Then Console.WriteLine($[{DateTime.Now}] $服务器{clientIP}重启完成重新注册) End If End Sub Private Shared Sub LogServerStatus() Console.WriteLine($[{DateTime.Now}] $服务器状态 - Group0: {Judge0ServerGroup0List.Count}台, Group1: {Judge0ServerGroup1List.Count}台) For Each server In Judge0ServerGroup0List.Values Console.WriteLine($[{DateTime.Now}] $ Group0 - {server.Judge0ServerIP}: 队列{server.Judge0QueueCount}, 最后心跳{(DateTime.UtcNow - server.Judge0ServerlastHeartbeat).TotalSeconds}秒前) Next For Each server In Judge0ServerGroup1List.Values Console.WriteLine($[{DateTime.Now}] $ Group1 - {server.Judge0ServerIP}: 队列{server.Judge0QueueCount}, 最后心跳{(DateTime.UtcNow - server.Judge0ServerlastHeartbeat).TotalSeconds}秒前) Next End Sub Private Shared Function isPrivateNetwork(ip As String) As Boolean Try Dim address As IPAddress IPAddress.Parse(ip) 如果是IPv6地址可以根据需要处理或直接返回False If address.AddressFamily Sockets.AddressFamily.InterNetworkV6 Then 对于IPv6您可以在这里添加私有地址判断逻辑 这里先简单返回False Return False End If 获取IPv4地址的字节数组 Dim bytes As Byte() address.GetAddressBytes() 检查私有地址范围 10.0.0.0/8: 10.0.0.0 - 10.255.255.255 If bytes(0) 10 Then Return True 172.16.0.0/12: 172.16.0.0 - 172.31.255.255 If bytes(0) 172 AndAlso bytes(1) 16 AndAlso bytes(1) 31 Then Return True 192.168.0.0/16: 192.168.0.0 - 192.168.255.255 If bytes(0) 192 AndAlso bytes(1) 168 Then Return True 127.0.0.0/8: 环回地址 If bytes(0) 127 Then Return True 169.254.0.0/16: 链路本地地址 If bytes(0) 169 AndAlso bytes(1) 254 Then Return True Return False Catch ex As Exception Console.WriteLine($[{DateTime.Now}] IP地址解析错误: {ip} - {ex.Message}) Return False End Try End Function Private Shared Function FindMinUnusedPort() As Integer If CallbackPortList Is Nothing OrElse CallbackPortList.IsEmpty Then Return -1 End If Dim minUnusedPort CallbackPortList.Values. Where(Function(portInfo) Not portInfo.used AndAlso CallbackPortList.ContainsKey(portInfo.port)). OrderBy(Function(portInfo) portInfo.port). FirstOrDefault() If minUnusedPort.port 0 Then Return minUnusedPort.port Else Return -1 End If End Function summary 设置指定端口的used标志 /summary param nameport端口号/param param nameisUsed使用状态/param returnsTrue表示设置成功False表示端口不存在/returns Private Shared Function SetPortUsedStatus(port As Integer, isUsed As Boolean) As Boolean If CallbackPortList Is Nothing OrElse Not CallbackPortList.ContainsKey(port) Then Return False End If Dim existingPort As CallBackPort Dim success CallbackPortList.TryGetValue(port, existingPort) If success Then 创建更新后的端口信息 Dim updatedPort existingPort updatedPort.used isUsed 更新字典中的值 CallbackPortList.AddOrUpdate(port, updatedPort, Function(key, oldValue) updatedPort) Return True End If Return False End Function Private Shared Async Sub HealthCheck() While True Try Await Task.Delay(45000) 45秒检查一次 Dim deadServers As New List(Of (String, Integer)) 检查所有服务器 For Each server In Judge0ServerGroup0List If (DateTime.UtcNow - server.Value.Judge0ServerlastHeartbeat).TotalSeconds 120 Then 2分钟无心跳 deadServers.Add((server.Key, 0)) End If Next For Each server In Judge0ServerGroup1List If (DateTime.UtcNow - server.Value.Judge0ServerlastHeartbeat).TotalSeconds 120 Then deadServers.Add((server.Key, 1)) End If Next 处理死亡的服务器 For Each deadServer In deadServers Console.WriteLine($[{DateTime.Now}] $检测到死亡服务器(Group{deadServer.Item2}): {deadServer.Item1}最后心跳: {(DateTime.UtcNow - Judge0ServerGroup0List(deadServer.Item1).Judge0ServerlastHeartbeat).TotalSeconds:F1}秒前) RemoveServer(deadServer.Item1, deadServer.Item2) Next Catch ex As Exception Console.WriteLine($[{DateTime.Now}] $HealthCheck异常: {ex.Message}) End Try End While End Sub End Class Serializable Public Class HeartbeatMessage Public Property Port As Integer Judge0服务器端口 Public Property QueueMaxSize As Integer 队列最大大小 Public Property RequestCountSize As Integer 同时最大投递数 Public Property Group As Integer 0为低性能组仅分配小任务1为高性能组可分配任何任务 Public Property AvailableMemoryOfMB As Double 可用内存大小MB Public Property AvailableMemoryThresholdOfMB As Double 可用内存保护阈值可用内存低于该值后应不再向服务器投递评测 End Class二、JudgeServer类Judge0Server 类是分布式评测系统中的工作节点代理它封装了与单个 Judge0 服务器实例的所有交互逻辑。可以把它理解为每个 Judge0 节点的管家负责管理任务提交、状态监控、结果回调等全生命周期操作。1. 任务提交与管理向 Judge0 服务器提交评测任务管理任务的 Token唯一标识跟踪任务超时和状态2. 负载控制查询服务器实时状态队列长度、工作线程等计算可用任务槽位控制并发请求数3. 结果回调处理监听 Judge0 的回调请求解析评测结果支持超长响应简化将结果放入全局结果队列4. 故障恢复清理超时的 Token服务器故障时回滚所有待处理任务队列计数校验和修复其核心工作流程我们就核心的任务提交和Token管理进行说明Private Class TokenInfo Public Property SubmitInfo As SubmitInfo 原始提交信息 Public Property CreatedTime As DateTime 创建时间 Public Property LastCallbackTime As DateTime 最后回调时间 Public Property TimeoutMinutes As Integer 超时时间动态计算 Public ReadOnly Property IsExpired As Boolean 是否已超时 End ClassPrivate Async Function SendRequest(SubmitInfo As SubmitInfo) As Task(Of Integer) 1. 构建请求体包含代码、参数、回调地址等 2. 发送到 Judge0 /submissions API 3. 解析返回的 Token 4. 记录 TokenInfo包含超时时间 5. 增加队列计数 End Function其完整代码Private Async Function SendRequest(SubmitInfo As Judge0TestQueue.SubmitInfo) As Task(Of Integer) 定义请求的内容 Dim jsonContent As New With { .source_code SubmitInfo.UserSource, .language_id SubmitInfo.LanguageID, .stdin SubmitInfo.Input, .expected_output SubmitInfo.Output, .compiler_options SubmitInfo.CompileOptions, .cpu_time_limit SubmitInfo.TimeLimit, 限制程序运行时间 .cpu_extra_time 3.0F, 额外的运行时间 .wall_time_limit .cpu_extra_time 3.0F, 包括编译等的额外时间 .memory_limit SubmitInfo.MemoryLimit, .callback_url ${Judge0CallBackURL}:{Judge0ServerCallBackPort}, .enable_network False } Dim content As New StringContent(JsonSerializer.Serialize(jsonContent), Encoding.UTF8, application/json) Try If (DateTime.UtcNow - Judge0ServerlastHeartbeat).TotalSeconds 30 Then Console.WriteLine($[{DateTime.Now}] ${Judge0ServerIP}服务器心跳超时跳过提交) Return -5001 End If 发送 POST 请求 Dim response As HttpResponseMessage Await httpClient.PostAsync(Judge0ServerSubmissionURL, content) If response.IsSuccessStatusCode Then Dim responseBody As String Await response.Content.ReadAsStringAsync() 提取 token Dim jsonResponse As JsonDocument Try jsonResponse JsonDocument.Parse(responseBody) Catch ex As Exception Console.WriteLine($[{DateTime.Now}] ${Judge0ServerIP}解析responseBody{responseBody}出错{ex}) Return -3001 End Try Dim token As String String.Empty Try token jsonResponse.RootElement.GetProperty(token).GetString() Catch ex As Exception Console.WriteLine($[{DateTime.Now}] ${Judge0ServerIP}获取token失败{responseBody}) Return -3002 End Try If String.IsNullOrEmpty(token) OrElse token null Then Console.WriteLine($[{DateTime.Now}] ${Judge0ServerIP}获取token失败{responseBody}) Return -3003 End If Dim tokenInfo As New TokenInfo With { .SubmitInfo SubmitInfo, .CreatedTime DateTime.UtcNow, .LastCallbackTime DateTime.MinValue, .TimeoutMinutes CalculateTimeoutMinutes(SubmitInfo) } 记录Token信息 If TokenInfoDict.TryAdd(token, tokenInfo) Then 只有Token记录成功后才增加队列计数 Interlocked.Increment(Judge0QueueCount) Console.WriteLine($[{DateTime.Now}] ${Judge0ServerIP}成功提交评测Token: {token}SolutionID: {SubmitInfo.SolutionID}超时: {tokenInfo.TimeoutMinutes}分钟当前队列数: {Judge0QueueCount}) Return SubmitInfo.SolutionID Else Judge0TestQueue.EnQueue(SubmitInfo) Console.WriteLine($[{DateTime.Now}] ${Judge0ServerIP}Token记录失败: {token}已重新入队) Return -4001 End If Else Console.WriteLine($[{DateTime.Now}] $向{Judge0ServerURL}请求失败状态码: {response.StatusCode}) 这个错误是因为参数超过了Judge0设置的容许值包括但不限于 cpu_time_limit 的最大允许值 memory_limit 的最大允许值 language_id 是否在支持的范围内 callback_url 格式正确 最可能的就是Judge0服务器剩余内存不足以运行当前提交的测试所以被驳回。 Return -2001 End If Catch ex As Exception Console.WriteLine($[{DateTime.Now}] $给地址{Judge0ServerSubmissionURL}发送请求时发生错误{ex.Message}) Return -1001 End Try End Function在得到Judge0服务器的评价结果之后通过配置Judge0服务器使用回调我们需要马上按格式解析其返回的内容上一篇我们提到有些代码在死循环中进行输出这会对Judge0造成压力的同时给我们解析返回值、数据库造成较大影响。一般来说一个OJ代码的输出不会太大的我们需要进行有效的限制。Private HttpListener As HttpListener Private Function Init_HttpListerner() As Boolean Try HttpListener New HttpListener HttpListener.Prefixes.Add($http://*:{Judge0ServerCallBackPort}/) Console.WriteLine(${Judge0ServerIP}开始监听{$http://*:{Judge0ServerCallBackPort}/}的回调。) HttpListener.Start() 改为异步处理 BeginGetContext() Catch ex As Exception Console.WriteLine($[{DateTime.Now.ToString}] ${Judge0ServerIP}HttpCallBack Err: ex.ToString) Return False End Try Return True End Function Private Sub BeginGetContext() 异步开始接收请求 HttpListener.BeginGetContext(AddressOf HandleRequest, HttpListener) End Sub Private Sub HandleRequest(ar As IAsyncResult) Dim context As HttpListenerContext Nothing Try Dim listener As HttpListener CType(ar.AsyncState, HttpListener) context listener.EndGetContext(ar) BeginGetContext() 定义常量 Const MAX_REQUEST_SIZE As Long 10 * 1024 * 1024 10MB Const MAX_FIELD_SIZE As Integer 4096 字段最大长度 4KB Const EXCEED_MSG As String 超出长度限制 Dim resultResponseApi As Judge0ResultQueue.ApiResponse 检查请求大小 If context.Request.ContentLength64 MAX_REQUEST_SIZE Then Console.WriteLine($[{DateTime.Now.ToString}] {Judge0ServerIP} 警告: 请求超过10MB ({context.Request.ContentLength64} 字节)将解析并简化响应) 解析超长请求并简化各个字段 resultResponseApi ParseAndSimplifyResponse(context.Request.InputStream, MAX_FIELD_SIZE) If resultResponseApi Is Nothing Then 如果解析失败创建最基本的简化响应 resultResponseApi New Judge0ResultQueue.ApiResponse With { .token unknown, .stdout EXCEED_MSG, .stderr EXCEED_MSG, .compile_output EXCEED_MSG, .message EXCEED_MSG, .time 0, .memory 0, .status New Judge0ResultQueue.StatusStruct With { .id Judge0ResultState.Unknow, .description Failed to parse response (10MB) } } End If Else 正常大小请求完整读取 Dim requestBody As String Nothing Using reader As New IO.StreamReader(context.Request.InputStream, context.Request.ContentEncoding) requestBody reader.ReadToEnd() End Using If String.IsNullOrEmpty(requestBody) Then Console.WriteLine($[{DateTime.Now.ToString}] {Judge0ServerIP} Judge0Server.HandleRequest()发生错误:requestBody为空。) resultResponseApi Nothing Else 正常反序列化 resultResponseApi JsonSerializer.Deserialize(Of Judge0ResultQueue.ApiResponse)(requestBody) End If End If 处理结果 If resultResponseApi IsNot Nothing Then ThreadPool.QueueUserWorkItem(Sub(state) Try ProcessResult(resultResponseApi) Catch ex As Exception Console.WriteLine($[{DateTime.Now.ToString}] {Judge0ServerIP} Judge0Server.HandleRequest()发生错误: vbCrLf ex.ToString) End Try End Sub) End If 立即响应客户端 Dim responseBuffer As Byte() Encoding.UTF8.GetBytes(Callback received successfully.) context.Response.ContentLength64 responseBuffer.Length context.Response.StatusCode 200 context.Response.OutputStream.Write(responseBuffer, 0, responseBuffer.Length) context.Response.Close() Catch ex As Exception Console.WriteLine($[{DateTime.Now.ToString}] {Judge0ServerIP}HandleRequest发生错误 ex.ToString) If context IsNot Nothing AndAlso context.Response IsNot Nothing Then Try context.Response.StatusCode 500 context.Response.Close() Catch End Try End If End Try End Sub至此整个工作流程就非常清晰了首先轮询数据库当有评测任务时进行一些预处理我使用了本地编译它可以有效的过滤掉那些无法编译的代码给Judge0减轻负担实际上也可以单独测试测试点1如果它产生溢出之类的错误后续也没有必要评测。然后就可以把每个测试点放入“待评测队列”服务器管理器会提示检查任务并且自动分配当得到一个评测结果之后立即放入到返回结果队列而后从结果队列中摘取再分配给每次代码提交当代码提交记录的token都返回或者返回足够个数即可认定评测完成。这样做的好处是能够立即响应网络请求每次提交的若干测试点多数时候被分配给多个服务器——充分利用每个服务器那么用户感知评测就比较快但也有一些问题这个过程中记录了很多中间结果在大量评测同时进行时例如重测某些题目或比赛OJ服务器本身压力较大需要足够的内存。