Http 协议回顾
Http 协议是什么?
为什么需要协议?
Post 请求的流程?
协议的组成
- 常见的请求方法:Get、Delete、Put、Post、Head 等
- 请求行/状态行:方法名、URL、协议版本/协议版本、状态码、状态码描述
- 请求头/响应头:协议约定的业务
- 请求体/响应体
常见状态码
- 1xx:信息类
- 2xx:成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务端错误
请求流程
不足之处
HTTP 也在不断更新迭代,不断修复不足,现在 Http3 使用的更多就是基于 QUIC 的 UDP 协议。
HTTP 框架的设计与实现
分层设计
主要目标是为了实现高内聚、易复用以及高可拓展性
下面这个是 Hertz 的分层设计与实现:
这里提交一个设计的原则:盖尔定律
应用层设计
中间件设计
中间件需求
DDD 领域驱动架构模型,也是洋葱模型
路由设计
协议层设计
网络层设计
性能修炼之道
针对原生的网络库进行了一定的优化,从而提升框架的性能。
采用零拷贝的概念,绑定缓冲区
存在全部的请求头 Header,拷贝出完整的 body
采用内存预分配技术进行优化
不同网络库的优势
- go net:流式友好、小包性能高
- netpoll:中大包性能比较好,延迟比较低
针对协议的优化
请求头优化
过滤掉没有用的字段,解析有用的字段
请求头规范化
热点资源池化
企业实践
字节的 HTTP 框架 Hertz 1w+服务、3kW+ qpS
- 追求性能
- 追求容易使用、容易上手
- 搭建内部生态、打通内部
- 文档健全
- 社区活跃
总结
以下是对这五个问题的详细解答:
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)
这种方式在路径数量较少且固定的情况下,查找速度非常快。