Skip to content

项目评分表

需要对一个sample进行评分,记录下评分表,自己做项目的时候也注意下。

项目的组织结构

  • 模块化
  • 项目的不同关注点应该被存放在不同的模块中。
    • 基于业务,划分模块可能有User,Product
    • 基于职责的技术分层,可能有 Entry points,Manager,DataAccess

TODO项目结构

  • 当项目已经模块化了,记住你引入的每个模块所代表的抽象。每个模块的职责和行为需要都很清晰才行。
  • 把每个模块的可配置常量,magic numbersmagic string 都存放在配置文件中。
  • 部署的脚本,nginx的配置文件,以及数据库初始化脚本应该包含在项目中
  • 不要让一个函数或者文件过于复杂。如果有这种情况,需要适当重构。

Git

  • 不要提交不必要的文件。可以使用.gitignore排除。
  • 需要有意义的commit message
  • 对git的使用和工作流程比较清晰。如cherry pickmerge conflictsstash等等。

日志

  • 日志是产品的一部分。对于开发阶段以及未来的维护是很有必要的,有助于调试和定位问题。
  • 日志需要区分不同的等级,比如debuginfowarning,error等。在不同场景下,你可能只需要关注不同级别的日志。
  • 不同级别的日志,应该被存放在不同的文件中。
    • 更方便查找
    • 更方便存储的管理,比如debug日志可能只保存1天,但是error日志可能会保存一周以上。
  • 在生产环境中禁用debug级别
  • 使用logger,而不是print
    • 因为日志库通常都有对日志的记录做专门的优化,比如cpu的non-block使用,线程安全等。Print在高并发的生产环境中可能会有不可预知的问题。
    • Log Handler通产还有附加的格式可以直接使用。
  • 注意日志文件的大小。在生产环境中,通常会要求减少日志的长度。
    • 减少不必要的日志
    • 如果某些信息已经被你的上游记录了,可以使用request id来从上游服务获取相关日志。
  • 尽量避免使用空格
    • 节省空间
    • 让你的日志查找指定信息的时候变得更困难。

数据库

  • 数据库设计规范
    • 统一的命名
    • 如果不需要负数的话,INT需要指定为UNSIGNED,来增加可使用的数值范围。
    • 时间数值需要存储为UNSIGNED INT,来减少时区带来的复杂度
    • 如果要支持多语言,那么字符集需要使用utf8mb4_unicode_ci
  • 避免使用数据库的约束,如果你可以在应用中进行处理的话,可以根据应用逻辑灵活地在应用层进行性能优化。
  • 主键需要趋势递增
    • 存储在磁盘的数据通常根据主键进行排序。如果插入的时候没有排序,那么磁盘的io会变得很高。
  • 理解索引
    • 数据结构、存储方式、为啥会更快、成本是什么、不同引擎的区别
    • 覆盖索引、复合索引、怎么减少索引树的高度、如果主键的值太长了会有什么影响

缓存

  • 在生产环境中,有必要使用缓存来减少网络耗时和高成本的操作。
  • 要缓存多久?你需要考虑数据一致性问题。

Nginx

  • 是否启用nginx log?什么样的格式对你调试更有帮助
  • 你怎么提供静态文件的服务?为什么把静态文件放在nginx比放在应用服务器更好点。
  • nginx不同配置对性能的影响
  • nginx应该尽量简单,不要把业务逻辑放在nginx

API Design

  • 输入输出的格式需要尽量保持一致
  • API的url需要简洁,每个segment需要都有意义。有两种不同的api风格,只要在项目中保存一致就好了。
    • RPC
      • 所有参数都放在url query string 或者POST body,如/object/action?id=xxx
      • 只有GETPOST方法可用
    • Restful
      • 对象的id可以放在url中,比如/object/:id/action
      • 使用不同的http verbs来表示不同的行为ie
  • 对API来说,所有API的输出都需要是一个json对象。
    • JSON比string更灵活
    • 不要在最外层使用json array,这样的数据结构后续就无法扩展了
  • 最好每个endpoint都有对应的schema
    • 对请求来说,可以使用json schema或者表格,那么我们不看代码也可以知道输入的内容
    • 对响应来说,提供json schema,我们可以对返回的数据进行过滤。
    • 如果有必要返回错误信息给前端,最好定义一个标准的错误格式,比如{'error':'you error info'}
    • 定义你自己的错误码,而不单依赖http标准错误码
      • 可以区分系统问题和业务问题
      • 业务问题需要保留在业务层,不该影响其他系统。比如密码错误登录失败,那么其他人还是可以正常登录的,但是如果数据库服务出问题了,那么该服务可能需要被隔离,所有人都不能使用该服务。
    • 使用一个装饰其或公共函数来抽象api的行为,可能更简洁点
  • API应该是幂等的。需要考虑怎么处理异常请求。

I18N

  • i18n(internationalisation) 不仅仅是将文本转为不同的语言,还有其他问题,如货币、时间格式、夏令时、甚至展示文本从左到右还是从右到左。
  • 如果可能的话,尽量在客户端处理i18n

Python代码的风格

  • Python编码风格TODO
  • 尽量避免使用from xxx import *
  • 设计失败的处理,让系统变得健壮
    • dict获取值,可以使用dict.get()
    • 检查输入的数据或者上游的响应信息是否合法
    • 依赖的服务如果出问题或者超时了,怎么办?
  • 推进爱你使用list-comprehensions,来替代filter或者map来获取更好的性能和可读性。

构建和部署

  • 是否有构建脚本来从源码生成二进制文件
  • 理解docker image的工作方式
    • 避免生成太多的image layer
  • 如果你的服务因为异常的原因退出了,怎么自动恢复?

Session

  • 有许多方式来实现session(client side or server side session)
    • 不论使用哪一种方式,你需要理解它是怎么实现的。
  • Session是很关键的,最好自己实现,那么你就会有更多的掌控能力
  • 一个简单且安全的实现session的方式,是在cookie中保存session id,并在服务器端保存session数据。

安全性

  • 检查所有的输入
    • 不要信任任何用户输入或者其他服务器的输入。
    • 你需要保证你的服务不会因为任何输入而产生未知异常或者被hack。
  • HTTPS
    • 整站使用https
    • 加密所有的传输package,确保用户密码和session id这样的敏感信息不会被获取
  • 授权
    • 决定谁是你的服务的消费者。需要提供机制来认证请求的来源。
    • 对于内部服务,可以通过api key+ api secret对来认证。
  • 认证
    • 即使你使用https,你也不该直接发送明文的密码给服务器。因为这些敏感信息可能会被打印在nginx 或者应用日志上,或者在内部服务非安全传输的时候被泄漏。
    • 一个安全的认证流程的例子
      • TODO
    • 你应该持久化保存hash后的密码和salt
    • 在处理请求之前,检查session
  • XSS
    • 确保在渲染html使用使用html-encoded,避免将数据作为脚本来执行。
    • 对于客户端渲染的页面。需要注意各个前端库的安全渲染方法。
    • 不要在数据库中保存html编码。
  • CSRF
    • 注意samesite的资源控制,确保所有调用的外部资源都是安全的。
    • 浏览器的CSP策略
  • 目录遍历攻击(Directory Traversal Attack)
    • 不要直接暴露服务器的目录和文件名
    • 确保用户不能访问指定静态文件之外的服务器目录
  • SQL注入
    • 注意sql查询语句是否经过转义处理( prepared statement)

性能

  • 分析
    • 在调优之前需要先进行性能分析找到对应的瓶颈
    • 系统性能的四要素
      • cpu
      • 内存
      • 磁盘io
      • 网络io
    • 你需要一些linux命令来检查负载
    • 当你获取所有要素的利用率的时候,最高的那个可能就是瓶颈所在
    • 你至少要在测试报告中记录rps和cpu利用率
  • 检查点
    • 配置: 理解你的nginx,gunicorn, db和cache的配置,以便调节性能
    • 连接池
    • 缓存
    • 超时管理
    • Session
    • 应用服务器的静态和动态内容分离
    • 数据库设计:合适的索引和数据类型

测试

  • 在真实的项目中,如果你有清晰的模块化和抽象的定义,你需要对所有功能进行单元测试
  • 通常,对重要的功能 需要添加单元测试,来保证内在逻辑的正确性
  • 在go语言中,模块需要定义接口,以便单元测试

Published in系统设计

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *