复盘: 项目架构设计与实现

1、为何单体架构中要做出成分布式延迟队列

大家常说的单体架构 只是业务形态,而分布式延迟队列 说的是调度模式与消费机制是否支持多实例、幂等、安全抢占。

即使是单体服务,也可进行多副本进行部署:

go 复制代码
Nginx / SLB
  -> manpao-service 实例 A
  -> manpao-service 实例 B
  -> manpao-service 实例 C

这种属于水平面拓展,虽然依旧不是分布式系统,但是这是确保架构高可用性的必要手段。

2、为什么要采用outbox模式

其实这个点,我一直想要说出来。

不论是在异步MQ的时候,还是做延迟队列的时,我的项目都采用outbox作为基石。

为什么?

他是发信箱模式 ,他确保的是最终一致性。

他解决的是业务数据写入成功消息/事件发送成功 两者之间尽量保持一致。

为了防止数据入库了,但是消息却因为网络原因或MQ挂了,最后导致发送失败。

并且他的拓展性挺高的。

若后期存储的数据量变大 的话,我可以根据不同功能分表

若后期吞吐量变大 的话,我可以采用cdc这种监听日志的组件,去mysql的binlog日志。

若后期要保证最终一致性,我可以采用indox表,作为消费端的消费表。用来做幂等性控制。

3、为何我要做分布式延迟队列

因为做分布式延迟队列,主要是为了解决订单超时问题。并且要确保多实例下任务调度依旧可靠。

并且只放做一个定时器关闭的话,服务重启就没了。

因为属于异步写入,所以可能会出现一种情况,订单处理成功,但是redis没写入成功。

所以我采用了outbox模式。

并且 zset 天然适合这种排序的。具体的负载我放到hash中,可以降低内存开销。

同时我在抢占的时候,不是直接用zrangebyscore,而是采用lua脚本,进行任务抢占、状态修改、可见性时间修改、以及发放 预订令牌 等操作,来避免被重复抢占。

其中,通过修改可见性时间,是增加score时间,暂时对其他实例不可见。

预订令牌,是对每个取出的数据发放的令牌。具有唯一性,是为了保证幂等操作的。只要持有令牌的的才能够被正常消费。

在设计的时候,其实考虑了很多问题

  1. 采用outbox是不得已的,毕竟redis与mysql属于不同系统,除了引入分布式事务,几乎没法回滚。
  2. 既然采用了outbox,就可能会出现重复投递,因为我可能改变本地状态的操作失败。
  3. outbox扫表很慢,是避免不了的,而我采用的是索引优化、分页描扫、后期还可以采用cdc技术。
  4. 我的score设计的是毫秒级的,为了防止时间戳问题,我还专门统一了时区。
  5. 如果后期数据量特别大的话,还可以考虑分片,分几个不同的key,避免全部积累在一处。
  6. 如果可见性时间到期了,然后我又取了一次,这时就以zset+hash中,拿着的新令牌为准。只有合法token才能提交结果。
  7. 我的令牌token,只能防并发,最多只能算是链路幂等,不能说是业务幂等。
  8. 不用MQ的原因,就是因为MQ太重,太笨。不灵活。

4、我的微服务采用的架构:

采用的 Clean Architecture ,其实就是外层依赖 内层的一种思想。

一般是配合DDD架构搭配起来的。

5、图片管理模块设计

我在设计图片管理模块时,首先抽象了存储驱动的接口,对外屏蔽细节,只暴露了Upload/Delete能力。上层可以通过具体的配置参数,来选择驱动。我们平时开发环境走本地,生产环境走oss对象存储。

然后针对大文件上传,我设计了一套服务器中转方式分片上传 流程。

首先建立一个临时目录,并初始化一份manifest.json文件,用来记录分片的元数据。如预计分片个数、过期时间等一些必要内容。这样前端可以在断网后,根据元数据,补充缺失的分片。

最终所有到齐之后,在统一合并起来。

并且为了防止并发严重,我这里用带缓冲的 chan 做了一个全局限流。同时限定上传图片或分片 的数量与同时合并的数量。避免服务器被打爆。

我merge的实际操作, 就是先根据manifest.json中的元数据,找到写入顺序,然后通过io.copy进行写入一个文件。

其实在设计之初,我已经知道我此时做的是不完美的。

  1. 我采用的是服务器中转,他的缺点我非常清楚,回到是磁盘IO增加、并且占用网络带宽。我们这样做是因为数据量比较小,最重要的是我们这个暂时还支持本地。如果后期数据量大的话,我会让前端来做这些事情,我这里只需要给凭证,记录状态即可。
  2. 我的manifest.json 暂时是存到了本地 ,等以后多实例 的时候,我会考虑到存放到数据库 / redis里面,以应对多实例 / 多节点的情况。
  3. 我才用带缓冲的channel作为并发限流,是因为他很轻量。
  4. 我才用的是io.copy这种流式拷贝,为的就是避免分片合并时,一次性直接输入占用大量内存。
  5. 为了保证我的分页没坏,我会变拷贝变计算hash,最终做一个对比,看对于否。
  6. 如果分片上传到一半时断网了,那没关系,因为他的后缀是.temp,所以我不会用它,只能重传。
  7. 我的孤儿文件分为了两种,一种是过期的,另一种是上传到oss了,但是没有入数据库。所以最终会被清扫掉。
  8. 最大的缺点就是占用网络宽带与磁盘io,并且此时是无法做到多实例。

6、我是如何搭建消息通知链路的

其实这个主要就是做范围的群公告用 ,比如给所有的学校管理员发消息,或者说给某个学校内的所有人发消息,并且要知道他们的已读未读的数量。

我首先采用的就是读扩散,只记录一条公告,与发送范围,而不是直接给每个人都记录一条信息,然后通过websocket给所有人发布一个通知信号,前端收到信号后,主动通过http,去拉取或者刷新可见信息。

此时在单独另存一个表,用来存放谁读了、谁删除了。

如果没有任何记录,就代表没有浏览过。

这样设计,可以极大程度上减少存储成本。

我当时为了这样设计,也思考了许久。

  1. 为啥采用读扩散,而不采用写扩散,是因为读扩散只需要O(1)时间写入,只有读取的时候麻烦。
  2. 我的范围是通过学校、角色、城市这些范围去判断过滤的。
  3. 如果后期我的表格非常大 的情况下,我会采用冷热分离的模式,把最近30天的放在一个表中,剩下其余的放在其他表中。
  4. 我现在没有支持已撤回这一点,但是如果后期非要支持已撤回的话,我会新增一个状态字段,以后过滤一下就行。
  5. 我现在不支持像钉钉那样的精确读,读扩散非常适合的是群公告一类的。
  6. 如果后期的写入压力非常大,也可以采用分库分表等优化方向。

7、我是如何设计多租户体系的

首先就是在jwt内部携带用户id,当作用户的什么凭证。

短token经常传输、被应用,所以把他设计的生命周期很短。

而长token,被存到前端本地,比较难被获取,所以生命周期长,长用来刷新短token。

此外

我的组织、角色、用户、以及api、路由组、菜单等各种关系我是存在数据库中的。

casbin是授权判断引擎 ,是存到内存中的,用来频繁判断权限会很快,且性能好。

我会在项目开启时,把本地数据库内的所有权限功能同步到casbin中。

若有用户权限发生改变,我则会发布事务去通知所有的实例,去更新。

我说了三级授权,他其实是:

在不同组织上下文隔离 的情况下,对属于该组织的角色,进行前端组件/后端接口/资源点的授权。

其中,当给某个用户分配新角色,会触发casbin_rule进行增量添加。然后需要把这件事情通知给所有的权限进行重载。因为他只影响了用户一人。所以可以采用增量。

但若是时给角色新增权限,则需要casbin_rule规则进行重建。然后其他实例一起重载。因为这个影响了所有人。

用户进行访问的时候,会优先走redis获取他此时组织与角色,只有过期的时候,才会打到mysql上。

8、我是如何实现分布式锁的

我的分布式锁,是通过:

set lock:xxx NX EX ttl

这样实现的,nx就是只有key不存在才能写入,互斥用的,EX是过期时间。

同时我才用的lua脚本进行原子操作,并且专门设计一个看门狗进行续期。

9、我是如何设计学生认证模块的?

我首先设计一套ocr接口,用户屏蔽内部细节,上层调用我的接口就行。

然后我设置了一套工厂模式,优先选用腾讯云的ocr,当其出错了、额度用完、或者置信度太低,则会降级到阿里云的ocr。

此外我还基于redis设计了一套熔断器

最初是关闭状态,当连续失败数量达到阈值,或者错误率到达50%后,会进行熔断。

不再打到主ocr上,会将请求放到备用ocr上。

等到30秒后,才会默默的允许放出一点探针进行试探。

相关推荐
啊吧怪不啊吧4 天前
C++之基于正倒排索引的Boost搜索引擎项目日志+server代码及详解
c++·搜索引擎·项目
琪露诺大湿9 天前
网页聊天系统——测试报告
java·软件测试·功能测试·websocket·html·项目·测试报告
深念Y9 天前
从0到1:推拿头疗店ERP系统的需求分析与架构设计全复盘
物联网·需求分析·跨平台·saas·数字化·项目·erp
琪露诺大湿9 天前
VeloQueue-测试报告
java·开发语言·消息队列·单元测试·项目·测试报告
YYYing.13 天前
【C++项目之高并发内存池 (一)】项目介绍与定长内存池的构建
项目·c/c++·内存池·池化技术
A923A1 个月前
【小兔鲜电商前台 | 项目笔记】第八天
前端·vue.js·笔记·项目·小兔鲜
Linux猿1 个月前
基于单片机浴室窗帘控制系统 | 附源码
单片机·嵌入式硬件·毕业设计·源码·课程设计·项目·基于单片机于是窗帘控制系统
Linux猿1 个月前
基于单片机的智能路灯控制系统设计 | 附源码
单片机·嵌入式硬件·课程设计·项目·系统设计·基于单片机的智能路灯控制系统
A923A1 个月前
【小兔鲜电商前台 | 项目笔记】第二天
前端·vue.js·笔记·项目·小兔鲜