HTTP 修炼之道 | 豆包MarsCode AI刷题

99 阅读6分钟

Http 协议回顾

Http 协议是什么?

image.png

为什么需要协议?

image.png

Post 请求的流程?

协议的组成

  • 常见的请求方法:Get、Delete、Put、Post、Head 等
  • 请求行/状态行:方法名、URL、协议版本/协议版本、状态码、状态码描述
  • 请求头/响应头:协议约定的业务
  • 请求体/响应体

常见状态码

  • 1xx:信息类
  • 2xx:成功
  • 3xx:重定向
  • 4xx:客户端错误
  • 5xx:服务端错误

image.png

请求流程

image.png

不足之处

HTTP 也在不断更新迭代,不断修复不足,现在 Http3 使用的更多就是基于 QUIC 的 UDP 协议。

image.png

HTTP 框架的设计与实现

分层设计

image.png

主要目标是为了实现高内聚、易复用以及高可拓展性

image.png

下面这个是 Hertz 的分层设计与实现:

image.png

这里提交一个设计的原则:盖尔定律

image.png

应用层设计

image.png

中间件设计

image.png

中间件需求

image.png

DDD 领域驱动架构模型,也是洋葱模型

image.png

路由设计

image.png

image.png

协议层设计

image.png

网络层设计

image.png

image.png

性能修炼之道

针对原生的网络库进行了一定的优化,从而提升框架的性能。

image.png

采用零拷贝的概念,绑定缓冲区

image.png

存在全部的请求头 Header,拷贝出完整的 body

image.png

采用内存预分配技术进行优化

image.png

不同网络库的优势

  • go net:流式友好、小包性能高
  • netpoll:中大包性能比较好,延迟比较低

针对协议的优化

image.png

请求头优化

image.png

过滤掉没有用的字段,解析有用的字段

image.png

请求头规范化

image.png

热点资源池化

image.png

image.png

image.png

企业实践

字节的 HTTP 框架 Hertz 1w+服务、3kW+ qpS

  • 追求性能
  • 追求容易使用、容易上手
  • 搭建内部生态、打通内部
  • 文档健全
  • 社区活跃

image.png

总结

image.png

以下是对这五个问题的详细解答:

1. 为什么HTTP框架要分层设计?

分层设计有哪些优势与劣势。

原因
  • 模块化和职责分离:HTTP框架处理的功能众多,包括请求处理、响应生成、资源管理、安全验证等。分层设计可以将这些功能模块分开,每个层专注于特定的任务,便于开发、维护和扩展。
    • 可复用性:分层后,每一层可以在不同的应用场景或项目中被复用。例如,底层的数据处理层可以在多个不同的上层业务逻辑应用中使用。
优势
  • 易维护:当出现问题时,可以快速定位到特定的层进行排查和修复,而不需要在整个代码库中搜索。
  • 可扩展性:可以方便地在某一层添加新功能或修改现有功能,而不会对其他层造成太大影响。
  • 灵活性:不同的层可以独立替换或升级,例如更换底层的数据库系统,而不影响上层的业务逻辑。 #### 劣势 - 性能开销:多层之间的调用和数据传递可能会带来一定的性能损耗,尤其是在对性能要求极高的场景下。
  • 复杂度增加:分层设计本身需要良好的架构设计,如果设计不当,可能会导致层与层之间的关系混乱,反而增加了系统的复杂性。

2. 现有开源社区HTTP框架有哪些优势与不足。

优势
  • 成熟稳定:许多开源HTTP框架(如Spring Boot、Django等)经过了大量项目的实践检验,在稳定性方面有保障。
  • 社区支持:拥有庞大的开源社区,能够得到及时的技术支持,并且有大量的文档、教程和示例代码可供参考。
  • 功能丰富:通常集成了诸如路由、缓存、数据库连接、安全验证等多种功能,减少了开发者的工作量。 #### 不足
  • 定制性限制:对于某些特殊的业务需求,开源框架可能无法很好地满足,需要进行大量的定制化工作,甚至可能需要修改框架的核心代码。
  • 性能瓶颈:在高并发场景下,一些开源框架可能会暴露出性能问题,需要进行性能优化或者寻找更适合的框架。
  • 版本兼容性:随着框架版本的更新,可能会出现与现有项目不兼容的情况,需要花费时间和精力进行升级和适配。

3. 中间件还有没有其他实现方式?可以用伪代码说明。 中间件有多种实现方式,以下是一种基于函数式编程的中间件实现方式(伪代码示例):


# 定义中间件函数类型

Middleware = Callable[[Callable[[], Any]], Callable[[], Any]]

def middleware1(next_func):
    def wrapper():
        print("Middleware 1: Before")
        result = next_func()
        print("Middleware 1: After")
        return result
    return wrapper

def middleware2(next_func):
    def wrapper():
        print("Middleware 2: Before")
        result = next_func()
        print("Middleware 2: After")
        return result
    return wrapper

# 定义目标函数
def target_function():
    print("Target function executed")
    return "Result"

# 应用中间件
wrapped_function = middleware2(middleware1(target_function))
wrapped_function()

在这个示例中,中间件通过函数嵌套的方式实现,每个中间件可以在目标函数执行前后添加自定义的逻辑。

4. 完成基于前缀路由树的注册与查找功能?可以用伪代码说明。 以下是基于前缀路由树(Trie树)的路由注册与查找的伪代码:


class TrieNode:
    def __init__(self):
        self.children = {}
        self.handler = None

class Router:
    def __init__(self):
        self.root = TrieNode()

    def register(self, route, handler):
        current = self.root
        for char in route:
            if char not in current.children:
                current.children[char] = TrieNode()
            current = current.children[char]
        current.handler = handler

    def lookup(self, route):
        current = self.root
        for char in route:
            if char not in current.children:
                return None
            current = current.children[char]
        return current.handler

在上述伪代码中: - TrieNode 表示路由树的节点,包含子节点字典和对应的处理函数。 - Router 类用于管理路由树,包括注册路由(register 方法)和查找路由(lookup 方法)。

5. 路由还有没有其他的实现方式?

基于正则表达式的路由
  • 原理:使用正则表达式来匹配请求的URL路径,根据匹配结果找到对应的处理函数。
  • 示例:在Python的Flask框架中,可以使用 @app.route 装饰器并传入正则表达式来定义路由,例如 @app.route(r'/user/<regex("[a - z]+"):username>') 来匹配以 /user/ 开头,后面跟着一个或多个小写字母的路径。
基于哈希表的路由
  • 原理:将请求的路径作为哈希表的键,对应的处理函数作为值。当请求到来时,通过计算路径的哈希值来快速查找处理函数。
  • 示例
route_table = {}

def register_route(path, handler):
    route_table[path] = handler

def lookup_route(path):
    return route_table.get(path)

这种方式在路径数量较少且固定的情况下,查找速度非常快。