后台任务漫谈

查看更多 →

API认证授权

本文记录了常见的API认证(Authentication)和授权(Authorization)协议,包括 ACL, RBAC, ABAC,OAuth2.0
查看更多 →

APISIX 插件

编写APISIX的插件的常见姿势以及简单介绍
查看更多 →

Openresty 笔记

Openresty 笔记 简介 Openresty 是一个国人将LuaJIT嵌入Nginx进程进而可以使用Nginx来进行开发高性能的Web框架。 入门的简介可以参考这个文档,OpenResty 不完全指南 注意点 Openresty 由于是在每个 nginx worker 都运行了一个 luajit 所以它仍然是一个单线程模型所以不存在 并行(parall) 问题,但是用于处理请求的是 lua 的 协程coroutine,那么这里可能存在 并发(parallelism) 问题,即如果 协程A 因为某种原因挂起后yield(IO操作/sleep),此时 协程B 将会被运行,同时共享 模块变量 以及 全局变量。openresty 这种非抢占式的协程调度带来了优点,也有缺点,优点就是不存在多个协程同时进行导致的并发读写问题,缺点在于如果某个协程进行一些长时间的调用,比如计算或者系统调用等,那么整个进程都会被卡主,导致吞吐量大大下降。(go 在 1.14 之后实现了基于信号的抢占式协程调度,由 sysmon 线程来执行检测,每 20us 检测周期,如果发现P的running状态超过 10ms or syscall 超过 20us,那么直接发送信号给 M,让其放弃正在执行的 G,开始执行其他的 G) 使用 Openresty 可以调用 c 的代码,底层是利用了 linux 的 dlsym 和 dlopen 利用后者加载链接库,前者加载函数符号。由于 LuaJIT 与 C 进行交互,所以不会识别 c++ 的机制,比如析构函数,这样一来你就没法使用 c++ 推荐的 RAII 机制。你有两个方式来管理内存:1、c way,在使用完后释放他,这是你的责任 2、lua way 可以在 lua侧包装一下返回的类,再使用 ffi.
查看更多 →

分布式事务笔记

分布式事务笔记 分布式事务的常见解决方案: 2PC: perpare + commit 3PC: can_commit + pre_commit + do_commit TCC: try + confirm + cancel Saga: Events/Choreography or Command/Orchestration 本地消息表: local + event 2PC 2PC 由协调器通知各个事务参与者,由 perpare 与 commit 两个阶段组成,前者通知所有参与者预备事务,后者通知参与者真实提交事务。如果有参与者在 prepare 阶段失败,那么会通知所有参与者回滚。 优点: 步骤少,简单 缺点: 协调器单点故障 会阻塞其他参与者的事务 commit 阶段可能出现消息丢失 3PC 3PC 在 2PC 的基础上增加了各个参与者的超时时间,如果参与者在超时是时间内没有相应,那么视为失败。3PC 依然没有解决 commit 消息丢失的问题。 TCC TCC 相当于 3PC 的完善,除了超时机制外,还存在 cancel 步骤,意味着可以在 commit 失败后进行补偿。 Saga Saga 是一种长活事务的设计模式( Long-live transaction ),它基于 1987 年的一篇论文,主要有两种实现:Events/Choreography or Command/Orchestration 可以参考:saga-pattern-implement-business-transactions-using-microservices-part、 saga。
查看更多 →

分布式一致性笔记

查看更多 →

微服务最佳实践

微服务最佳实践 本文记录了工作当中积累的一些关于微服务最佳实践的想法 一定要做到的点 每个请求一定要有 request-id,一般由调用方生成,如果你是在推动一个遗留项目,那么可以在网关或者AOP层去生成它,并且在日志会和调用链路中携带上这个ID,这样才能够在繁杂的日志中找到特定的请求信息。建议由 uuid 或者 雪花算法 生成。 请求日志和错误日志分离,这么是因为这两者具有完全不同的关注点,且前者格式相对固定。分离他们可以在清洗日志时使用不同的格式。 当请求发生错误时(无论是预期内的还是预期外)都应该体现在日志当中 如果你们的系统引入了调用链追踪,那么可以使用 request-id 作为 trace-id。 对于每一个资源的操作都应有反馈,比如删除不存在的资源或者创建同名的资源,我们都应该返回特定的错误码,通常可以使用三类: NotFoundError, ConflictError, InternalError,ValidationError,同时错误码应该有所区分——预期和非预期的(比如使用code的范围去区分是一个选择),以便于落地告警 网关和微服务打印日志建议分开侧重点,网关应该打印协议层的细节,比如 http header, http code, cookie等,而微服务应该打印 应用Code, 请求参数,响应参数。这样可以在网关统计到后端微服务有多少是因为转发时的网络问题而导致访问失败,多少又是因为应用服务内的问题而失败。 系统和服务都应该有明确的边界,服务边界很容易理解,就是一个独立的部署单元,系统的边界是指对所有系统外的服务的交互都应该收口,这样才能更好的进行流量治理。理想情况下我们可以通过网络策略来形成物理边界,比如k8s的namespace。但是在真实的情况中可能你要面对很多遗留服务,它们已经通过IP地址或者服务发现互相访问了,直接去改造它们成本非常大,那么你可以在逻辑上形成边界,比如提炼一个新的概念对象,将现存的服务都分门别类,然后再请求上就可以区分出流量到底来自外部还是内部。不过值得注意的是,物理边界虽然理想,但是在做异地多活时可能会面临一些挑战,比如你使用namespace来作为网络边界,那么你需要把不同地区和机房的机器都包含到一个集群中才能满足不同程度的容灾需求。 谨慎对待 Error 和 Warning 级别的日志,它们可以用于反馈当前服务的健康度,可以对其进行监控和告警,同时为了控制好整个程序中的错误级别,建议不要在代码中hardcode打印日志,统一返回到上层去区分处理。 乐观锁可以使用最后更新时间或者http标准下的ETag 请求时一定要注意超时时间,尤其是调用链路比较长时,要保证 client 和 server 的 context 声明周期一致,通常来说,client 不应该设置自己的超时时间,统一由服务端控制。但是如果基础设施不稳定的,话,这种方式可能会导致较长的无效等待,为了达到更高的可用性,全链路超时是必须的,超时不由服务端决定,而是客户端决定。 在没有使用 ThreadLocal 技术的项目中。无论在开发or基础库都应该注意日志打印时预留context参数,便于日志染色 枚举的转义关系应尽量交给客户端来渲染,客户端可以从枚举的维护方自行下载缓存到本地做映射,这里有几个考虑 暴露枚举值的转义表之后,各个接入方都可以自行转义,包括前端。如果你期望将转义关系隐藏在后端,只有当你的枚举值不被其他服务所关心时才能这样做,且没有带来太多的价值。 每个消费房对枚举转义形式可能期望不同,比如一个数组的表现形式,树当中一个节点的表现形式等,都可能不同 如果枚举的定义并不是你的服务,且业务逻辑并没有依赖这部分数据,这时仅为了满足某个消费方(通常来说是前端orApp)去依赖其他业务接口来完成转义,其实是引入了更多风险,并且没有必要,因为需要这个转义的消费方多数时候其实已经获得过转义表 对于预期外的行为,服务必须要有响应,比如: 获取、更新、删除一个不存在的记录 写入冲突 项目结构(project layout) 这真是一个非常常见的问题了,我觉得这个问题需要结合场景来看待: 编程语言:每个语言都有自己比较常见的项目结构,比如 golang,在各自的标准下的结构才是我们需要考量如何区分的。 架构风格:由于解决的问题不同,你的软件可能会采用不同的架构风格,比如 数据流式(批处理,管道),请求/响应式(N层,面向对象),独立组件式(交互进程,事件驱动),数据中心式(数据库,黑板系统)可以参考 Architectural-Styles-Patterns。使用不同架构风格时,你的项目结构也会调整。 总的来看项目结构很难以一个万金油的模版去一统天下,都需要结合各自语言的特性以及不同架构风格来解决,这里提供一些常见选择:
查看更多 →

DDD实践心得

查看更多 →

接口安全漫谈

介绍常见的网络攻击手段:重放攻击,数据劫持以及篡改以及加解密等
查看更多 →