QUORUM:祖鲁兰大学的预付费互联网
祖鲁兰大学是一所“历史弱势”大学校园,位于南非东北部沿海地区,约有 6,000 名学生。我们的弱势历史在后种族隔离时代的南非仍在延续,我们的学生大多是黑人,来自经济条件不利的家庭。因此,我们的运营预算受到严重限制,“以更少的资源做更多的事情”的日常挑战依然存在。
在这种环境下,Linux 是我们互联网服务平台的显而易见的选择,我们几乎将其用于所有方面:电子邮件和 WWW 服务器、DNS、DHCP、HTTP 代理、防火墙和拨号访问。我们的管理层喜欢 Linux “免费如啤酒” 的特性。然而,我们使用 Linux 的主要原因更令人信服——使用 Linux 是交付我们的互联网服务最有趣的方式。
在 2000 年初,我们最大的财务挑战是为所有教职员工和学生提供互联网访问。南非的互联网带宽成本远高于美国。我们的 128Kbps 访问线路每月花费约 5,000 美元。这条线路在白天已经被我们的 400 名教职员工用户饱和使用,而我们仍然需要为我们使用频繁的学生实验室中的 350 个工作站提供互联网访问。
我们需要更多的带宽,但我们的预算无法承担增加一倍或两倍的访问带宽。在没有监控或控制互联网使用的情况下,管理层对承诺在互联网访问上大幅增加支出感到不安。他们对支付巨额月度账单持怀疑态度,因为他们不知道谁在使用互联网或用于什么目的。
我们的问题实际上是如何在预算限制内为 WWW 浏览提供可接受的服务质量 (QoS)。在不受监管的互联网访问下,访问线路的拥塞是限制需求的唯一因素,这通常会导致最差的 QoS,而核心的铁杆下载者可以忍受这种 QoS。
我们需要为互联网使用提供某种类型的“成本”,以便 регулировать 需求。这个系统将让我们的用户了解大学提供互联网访问的实际成本。对于学生来说,这项成本应该是预付的。南非弱势大学的坏学费债务是一个严重的问题,我们不想将互联网访问账单添加到债务催收问题中。我们需要一个 配额 系统来管理互联网使用。
自 2001 年高等教育新网络推出以来,我们的带宽限制有所缓解。我们将互联网访问升级到 768Kbps(国际流量为 384Kbps CIR),同时将每月成本保持在大致相同的水平。但是,我们仍然相信我们的配额系统对于维护用户合理的 QoS 仍然是必要的。
我们使用 NLANR 流行的 Squid HTTP 缓存代理软件为我们的用户提供 WWW 访问。我们的防火墙确保教职员工和学生必须使用我们的代理服务器才能访问 WWW,并且我们将代理配置为需要登录名和密码才能进行 WWW 访问。通过这种配置,Squid 生成的 access.log 文件完整记录了所有用户的 WWW 活动。使用 access.log 中的信息,统计给定用户的 WWW 使用情况非常简单。
Squid 可以使用外部 URL 重定向器程序。当配置了 URL 重定向器时,每个发送到代理的 URL 请求都会发送到 URL 重定向器程序的标准输入,该程序通过告知代理获取请求的 URL 或将请求重定向到不同的 URL 来响应。内容过滤使用此功能,例如 SquidGuard 等程序,其中 URL 重定向器根据不良 URL 模式数据库检查每个 URL 请求。对不良 URL 的任何请求都会被重定向到一个页面,告知用户她可能无法查看请求的 URL。
我们意识到 URL 重定向器也可以用于实施使用配额。URL 重定向器检查用户的配额,如果已超出配额,它会将她重定向到一个页面,告知她信用已用完。虽然原则上很简单,但这必须快速发生,否则会影响代理服务器的整体性能。在我们的实现中,当 URL 请求被允许时,URL 重定向器通常在两毫秒内响应。
QUORUM(基于配额的资源使用管理器)是一个服务器,它提供了一个简单的消息 API,具有两种基本消息类型:计数(为特定帐户/会话的项目计费)和查询(询问服务器给定帐户/会话是否仍有信用)。
我们使用 QUORUM 服务器来管理代理检索的所有 URL 的计数以及用户帐户的当前信用余额,并跟踪用户会话,这对应于用户不间断的 WWW 使用时段。服务器保留所有活动帐户和会话计数以及信用余额的缓存。这些缓存中更新的项目会定期写入数据库。服务器具有 WWW 界面,用于显示帐户余额、用户会话和其他管理和调试信息。
服务器的早期版本是用 C 语言编写的,使用各种 CGI 程序来提供 WWW 界面。但是,当我们发现 Java servlet 时,就放弃了这种方法。QUORUM 服务器是作为 Java servlet 和 JSP 的集合编写的,使用 JDBC 进行后端数据库连接。我们目前在 Caucho 的 Resin Servlet/JSP 容器下运行它。我们使用 MySQL 作为后端数据库,使用 Resin 的内置 JDBC 类进行 MySQL 访问和数据库连接池。
Java servlet 是实现像 QUORUM 这样的应用程序的自然选择。服务器包含几个持久共享的数据结构,用于用户会话、每个会话的计数以及帐户计数和信用余额的缓存。由于 servlet 只是 Java 类,它们是实现 WWW 服务器的容器应用程序的一部分,因此可以简单地将这些持久对象创建为服务器的一部分,以及后台线程,这些线程检查空闲会话并定期将更改的数据刷新到会计数据库。
当用户请求 URL 时,她的浏览器将 URL 请求发送到 Squid 代理。然后 Squid 将此 URL 请求信息发送到 URL 重定向器。URL 重定向器向 QUORUM 服务器发送 querySsn 消息,询问用户是否具有当前的 QUORUM 会话以及用户的帐户是否仍有信用。如果一切正常,则 URL 重定向器用空行回复 Squid,告知 Squid 获取请求的 URL。
如果用户没有当前会话,或者 querySsn 响应表明用户的帐户没有信用,则浏览器将被重定向到 QUORUM 服务器中的 JSP ssnInfo.jsp,该 JSP 显示用户当前的帐户使用情况和可用信用额度,并要求她通过单击指向另一个 JSP beginSsn.jsp 的链接来启动 QUORUM 会话。如果用户的帐户有信用,则此 JSP 会为用户创建 QUORUM 会话。
URL 重定向器实际上将重定向发送到 servlet,该 servlet 将另一个重定向发送到 ssnInfo.jsp。URL 重定向器在对 servlet 的请求中传递用户名和用户的 IP 地址作为参数
/QUORUM/servlet/Redirect?ssn_id=uname@ipaddr
servlet 将 ssn_id 保存在与客户端浏览器关联的 HTTP 会话中。这使所有其他 JSP 都可以使用 HTTP 会话变量来检索用户的帐户和会话信息。
使用情况核算取决于 Squid 的 access.log 文件中的信息。在 Squid 获取 URL 后,它会在 access.log 文件中附加一行,其中包含获取的 URL、对象的大小(以字节为单位)、请求者的用户 ID 和发出请求的工作站的 IP 地址。Python 脚本 squidTallies.py 读取 access.log 的尾部,并向 QUORUM 服务器生成 tallySsnItem 请求消息。QUORUM 服务器处理这些计数消息,并保持用户帐户和会话的运行使用计数,以及所有活动用户帐户的信用余额。
帐户余额、使用情况计数和用户会话日志都写入 MySQL 数据库。QUORUM 服务器中的后台线程定期遍历会话计数、帐户计数和信用余额的缓存,并将任何更改的数据刷新到数据库。这使服务器能够在数据库中保持合理最新的信息,而不会因每个获取的 URL 的多次更新而使数据库不堪重负。
QUORUM 请求消息是简单的文本消息,以换行符结尾。例如
ref000001 querySsn ssn_id=soren@1.2.3.4 ccode=11000
是一个请求消息,询问服务器 IP 地址为 1.2.3.4 的用户 soren 是否具有当前的 QUORUM 会话,以及用户对于“成本代码” 11000 是否仍有信用。所有请求消息的第一个字段是用户引用,该引用在响应消息中回显给客户端,这允许多线程客户端将响应与请求消息匹配。
作为 J2EE 应用程序,您可能会期望更时髦的消息传输方式,例如基于 HTTP 的 SOAP。我们考虑将请求编码为对 servlet 的单个 HTTP GET 请求,这些 servlet 将实现各种 QUORUM API 消息。这本可以更干净地与 servlet 框架相匹配,但我们的测试表明 HTTP 开销对于我们的应用程序来说太大了。我们学生实验室的峰值需求为每秒 20-30 个 URL。由于每个 URL 请求都将对应于两个 QUORUM 请求消息(一个 querySsn 请求和一个 tallySsnItem 请求),因此服务器需要舒适地处理每秒 60 个请求。我们使用单个 QUORUM 服务器来处理我们的教职员工和学生代理,因此我们需要至少处理每秒 100-150 个请求。即使通过单个持久 HTTP 连接进行背靠背请求,我们也无法使用每个 QUORUM 请求消息的单独 HTTP 请求来实现这些速率。
相反,单个 servlet PostMsg 处理来自客户端的所有 QUORUM 请求消息。客户端应用程序发出 HTTP POST 请求,然后在 POST 请求的 TCP 连接上发送 QUORUM 请求消息,就像它在请求中上传文件或其他数据一样。PostMsg servlet 使用输入流的 readLine() 方法从 ServletInputStream 中逐行读取请求消息。它处理请求消息并将响应发送回 ServletOutputStream 上的客户端。发送响应后,它会在 ServletHTTPResponse 对象上调用 flushBuffer() 方法,以确保响应已发送到客户端。使用此技术,单个 QUORUM 服务器可以在单个客户端连接上处理每秒数千个请求。
此技术存在两个小问题。首先,servlet 容器将关闭任何空闲时间超过设定时间(通常为 30 秒左右)的 HTTP 连接。这意味着当客户端一段时间不发送任何请求时,客户端将被断开连接。然后客户端将必须重新连接并发送另一个 HTTP POST 请求才能发送更多 QUORUM 请求消息。我们编写了一个 Python 类,该类提供了一个 UNIX 管道,用于发送请求和读取响应。该类按需建立 HTTP 连接并发送 HTTP POST 请求。按需连接技术的优势在于消息传输非常可靠——空闲连接不会挂起,并且不会出现客户端卡住的问题,认为他们具有服务器认为已关闭的连接。
另一个问题是 HTTP/1.1 要求 POST 请求在请求和响应中都具有 Content-length 标头。虽然我们发现 Tomcat 忽略了此限制,但 Resin 没有,并且当客户端或服务器发送的字节数超过 Content-length 值时,它会关闭连接。我们的解决方法是在 Content-length 标头中设置一个非常大的值,并安排客户端在发送的字节数远小于该值之前关闭连接。
URL 重定向器的性能对于代理至关重要,因为每个 URL 请求都会延迟到 URL 重定向器响应为止。QUORUM 的 URL 重定向器保留了所有最近会话的 querySsn 回复缓存。这使得重定向器对于 URL 请求非常快,一旦会话建立并且信用良好,因为 URL 重定向器根据缓存中最新的回复消息进行响应。在这种情况下,响应时间通常为两毫秒或更短。即使存在缓存的回复,重定向器始终会向 QUORUM 服务器发送 querySsn 请求。回复消息用于更新回复缓存,因此缓存始终包含有关用户会话状态和信用余额的最新信息。
当重定向器没有缓存的回复消息,或者缓存的回复为负(用户信用已用完)时,重定向器会发送 querySsn 请求并阻止对代理服务器的回复,直到收到来自 QUORUM 服务器的响应。在这种情况下,重定向器可能需要 20 到 50 毫秒才能响应。
可以将 Squid 配置为启动多个 URL 重定向器副本,以便可以并行处理 URL 重定向请求。QUORUM URL 重定向器是多线程的,因此 Squid 可以发出多个并发 URL 请求。Squid 实际上与多个小型存根程序副本通信,这些副本通过 unix 域套接字连接将 URL 重定向器请求转发到 URL 重定向器进程。这允许 URL 重定向器为所有请求共享相同的 querySsn 回复缓存。
用户帐户的轻松管理对于该系统的成功采用至关重要。在我们的 QUORUM 部署中,学生必须具有 WWW 访问的使用配额。目前 QUORUM 服务器统计教职员工的使用情况,但教职员工尚不受配额限制。(然而,使用 QUORUM 跟踪教职员工的使用情况对于打击学生使用被盗教职员工帐户的行为非常宝贵。)
对于与特定课程相关的互联网使用,我们使用我们的学生注册信息数据库,为课程中的所有学生记入指定数量的使用信用额度。我们在每个学期开始时为所有讲师已请求互联网访问的课程执行此操作。
过去,我们发现不断有学生迟到注册、由于某些其他原因未在我们的数据库中注册或只是需要额外使用配额的情况。我们现在为某些部门提供额外的酌情帐户。指定的讲师可以将资金从他们的部门帐户转移到个人学生帐户。这减轻了我们部门的行政负担,并赋予部门管理学生帐户的某些酌处权。
对于需要超出其已发行配额的额外使用量的学生,我们将引入预付费凭证系统。此系统有意让人联想到预付费手机的凭证系统,该系统在我们的学生中非常受欢迎。学生将能够从大学书店购买一张凭证,该凭证具有秘密访问号码。可以通过在 WWW 表单中键入此号码并提交来兑换凭证。将输入的号码的 MD5 哈希值与存储在数据库表中的哈希值进行比较,如果哈希值与其中一个匹配,则学生的帐户将记入凭证上显示的金额。我们已与我们新的校园销售点 (PoS) 系统的供应商进行了初步讨论,以了解我们是否可以使用类似于当前用于在收银点销售预付费手机凭证的系统来销售我们的互联网配额凭证。
凭证系统是我们为用户提供灵活的互联网服务、补贴学术目的的互联网使用并收回其他使用成本的目标的重要组成部分。我们目前正在测试凭证系统。学生们有压力要求尽快引入凭证系统,但我们对系统的推出非常谨慎,因为这将是学生直接付费的服务。重要的是,该系统已完善并被我们的学生用户充分理解,以便他们可以完全放心地为他们所支付的费用付费。
从一开始,QUORUM 的设计就超越了 WWW 使用情况核算和配额管理。任何生成使用情况信息日志文件的服务都可以通过脚本进行解析,该脚本向 QUORUM 服务器发送计数请求消息。在不久的将来,我们将添加电子邮件使用情况核算,以便将带有大型附件的邮件消息计入发送者的使用配额。在我们站点,无端的电子邮件使用(将很酷的 AVI 发送给您的所有朋友)一直是一个反复出现的问题。将来,我们的用户将不得不决定,现在发送大型电子邮件附件可能意味着以后必须为 WWW 访问付费。
QUORUM 中的使用情况核算被组织成成本代码。目前,WWW 使用情况分为国际和国内流量,它们的收费标准不同,以及 Squid 缓存命中,这些命中不收费。收费的细分是分层的,其中互联网使用可以分为 WWW 和电子邮件访问,每种访问又分为更多类别。最终,我们计划为网络打印、拨号访问和通过我们的防火墙的直接 TCP/IP 流量添加额外的使用成本代码。成本代码的分层结构意味着我们可以对所有服务使用一个配额,或者特定的配额信用额度仅适用于成本代码层次结构的部分。例如,我们可以决定,课程中发给学生的配额信用额度仅适用于互联网访问,但预付费凭证信用额度也可以用于网络打印。
我们正在努力更有效地利用我们的互联网带宽,方法是鼓励我们的用户这样做。我们的收费结构使那些从南非境内的镜像站点而不是海外站点下载大型文件的用户受益。对于国内目的地,购买有保证的带宽比海外目的地便宜。同样,我们提供下班后折扣,以鼓励用户在晚上互联网访问不那么拥挤时下载大型文件。
另一个有趣的想法是将消息传递/公告系统集成到 QUORUM 中,这是一种内联网即时消息传递。我们目前可以看到哪些用户正在浏览 WWW。可以扩展 QUORUM,以便可以将消息发送给用户或用户组。当收件人之一下次请求 URL 时,QUORUM 服务器可以将用户重定向到一个页面,该页面显示消息/公告。
Soren Aalto (soren@pan.uzulu.ac.za) 在祖鲁兰大学网络服务部门工作,他在系统管理、WWW 应用程序开发和将其他人转变为 Linux 的乐趣之间分配时间。他的妻子在大学任教,他们有两个孩子、三只猫、一条狗和数量不定的热带鱼。