需要对一个sample进行评分,记录下评分表,自己做项目的时候也注意下。
项目的组织结构
- 模块化
- 项目的不同关注点应该被存放在不同的模块中。
- 基于业务,划分模块可能有
User
,Product
- 基于职责的技术分层,可能有
Entry points
,Manager
,DataAccess
- 基于业务,划分模块可能有
TODO项目结构
- 当项目已经模块化了,记住你引入的每个模块所代表的抽象。每个模块的职责和行为需要都很清晰才行。
- 把每个模块的可配置常量,
magic numbers
和magic string
都存放在配置文件中。 - 部署的脚本,nginx的配置文件,以及数据库初始化脚本应该包含在项目中
- 不要让一个函数或者文件过于复杂。如果有这种情况,需要适当重构。
Git
- 不要提交不必要的文件。可以使用
.gitignore
排除。 - 需要有意义的
commit message
- 对git的使用和工作流程比较清晰。如
cherry pick
,merge conflicts
,stash
等等。
日志
- 日志是产品的一部分。对于开发阶段以及未来的维护是很有必要的,有助于调试和定位问题。
- 日志需要区分不同的等级,比如
debug
,info
,warning
,error
等。在不同场景下,你可能只需要关注不同级别的日志。 - 不同级别的日志,应该被存放在不同的文件中。
- 更方便查找
- 更方便存储的管理,比如debug日志可能只保存1天,但是error日志可能会保存一周以上。
- 在生产环境中禁用
debug
级别 - 使用
logger
,而不是print
。- 因为日志库通常都有对日志的记录做专门的优化,比如cpu的non-block使用,线程安全等。
Print
在高并发的生产环境中可能会有不可预知的问题。 - Log Handler通产还有附加的格式可以直接使用。
- 因为日志库通常都有对日志的记录做专门的优化,比如cpu的non-block使用,线程安全等。
- 注意日志文件的大小。在生产环境中,通常会要求减少日志的长度。
- 减少不必要的日志
- 如果某些信息已经被你的上游记录了,可以使用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
- 只有
GET
和POST
方法可用
- 所有参数都放在url query string 或者POST body,如
- Restful
- 对象的id可以放在url中,比如
/object/:id/action
- 使用不同的http verbs来表示不同的行为ie
- 对象的id可以放在url中,比如
- RPC
- 对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语言中,模块需要定义接口,以便单元测试
Be First to Comment