分类目录归档:系统

基于数据库实现幂等接口

 

TL;DR

通过唯一编号确定同一请求,没有唯一编号的自行生成。

数据库记录操作状态,数据库事务保证数据一致性。

概述

通过HTTP API进行通信的系统,在支付或者只允许操作一次的相关场景中,对接口的幂等性有严格要求。

接口的幂等性体现在:

请求执行成功所得到的结果与次数无关

如果接口没有实现幂等性,对于转账的应用场景:

A. 正常转账

  1. A账户金额为¥200,B账户金额¥100
  2. A调用API向B转账¥100,接口调用成功
  3. A账户金额为¥100,B账户金额¥200

在这一场景下,整个流程正常,接口无论是否实现幂等性与否都对执行结果没有影响。

B. 转账重试

  1. A账户金额为¥200,B账户金额¥100
  2. A调用API向B转账¥100,接口调用失败
  3. A重试请求,本次成功
  4. A账户金额为¥100,B账户金额¥200

在这一场景,重试成功的情况下,接口无论是否实现幂等性与否都对执行结果没有影响。

C. 转账操作超时后重试

  1. A账户金额为¥200,B账户金额¥100
  2. A调用API向B转账¥100,接口调用超时,A不了解本次转账是否完成,服务端实际转账成功
  3. A重试请求,本次成功
  4. A账户金额为¥0,B账户金额¥300

在实际应用场景中,接口超时的情况并不罕见,接口超时不代表操作失败,可能存在的情况就有操作实际成功然而并没有返回数据。在这样一个场景之下,接口没有实现幂等性造成重复操作,对于系统的可靠性来说是不可容忍的。

D. 重复的转账操作

  1. A账户金额为¥200,B账户金额¥100
  2. A调用API向B转账¥100,由于A的误操作发出了第二次同样的请求
  3. A发出的两次请求均成功
  4. A账户金额为¥0,B账户金额¥300

两次操作同时发出,并且都成功,接口没有实现幂等的情况下,两次转账操作都会成功,但是对于用户A来说,实际上这是同一次的转账意愿。

以上的场景还是在A与B账户均存在于同一个资源(一般为数据库)之上的操作,如果A与B账户处于两个资源,场景还会更加的复杂。

由上述的场景可以看出,实现接口幂等性的两个方向在于:

  • 定义同一次操作
  • 拒绝重复操作

实现

利用数据库实现上述两个需求十分方便。

定义同一次操作

使用数据库实现发号器,为每一次请求生成唯一编号

拒绝重复操作

通过数据库事务以及唯一索引,以请求编号作为依据,保证同一时间内只有一个请求进行操作,经过先查询后操作的方式,已完成操作不执行更改逻辑,保证请求值执行一次。

以MySQL为例,针对需要实现幂等的操作,可以建立如下的数据表:

其中op_no列存在唯一索引。

针对上述转账的场景,设定A与B都处于同一数据库中,可以用如下伪代码表示上述的转账操作:

以上。

《微服务设计》

第1章 微服务

微服务就是一些协同工作的小而自治的服务。

大多数系统强调高内聚,低耦合,即通过抽象或者模块保证代码的内聚性。

一个基本的特征就是微服务是一个独立的实体。无论是在容器还是在进程中存在。

微服务通过API进行解耦合。

微服务的优点集中在:

  • 技术可以异构
  • 更大的弹性(可操作的粒度更大)
  • 易于扩展(精细到功能级别)
  • 简化部署(API接口不变,改动一个模块影响不大)

第2章 演化式架构师

架构师的一个重要职责是,确保团队有着共同的技术愿景,以帮助我们向客户交付他们想要的系统。

架构师要改变那种从一开始就要设计出完美产品的想法,而选择设计出一个合理的框架,在这个框架下可以慢慢的演化出正确的系统,一旦学到了跟过的的还是,可以加以使用。

架构师的职责之一是保证该系统适合开发人员在其之上工作。

架构师专注大方向,在优先的情况下参与到非常细节的具体实现上。

架构师关注服务边界之间的问题,而不应当过多关注边界内部的问题。

一个好服务至少要做到如下三个方面的优势:

+ 监控

+ 接口

+ 架构安全性

COBIT(Control Objectives for Information and Related Technology)给出的的治理的定义是:

治理通过评估干系人的需求、当前情况及下一步的可能性来确保企业目标的达成,

通过排优先级和做决策来设定方向。对于已经达成一致的方向和目标进行监督。

第3章 如何建模服务

服务要高内聚,低耦合,划分微服务的一个方式可以是找到各自功能的限界上下文(Bounded Context)。

第4章 集成

集成的几个原则:

  • 技术的选择需要有收益
  • 避免破坏性修改,对服务的修改不影响已有消费方
  • 保证API的技术无关性,应该是需求驱动实现,而不是实现驱动需求
  • 消费方易于使用,尽量不引入其他会引起耦合的使用方式(如client)
  • 隐藏内部实现细节

共享数据库会带来的问题:

+ 所有使用者需要了解schema之间的细节

+ 消费方被绑定了技术

基于请求/响应模式的同步请求易于实现,而基于事件的异步请求模式则能应对长时操作以及降低耦合度。

编排(Orchestration)与协同(Choreography)的区别在于是否有有中心。编排会通过中心驱动流程。编排优点是状态和流程明确,问题在于中心负担过重,导致其他协作方过于单薄;协同的优点在于解耦合,问题在于需要额外的监控流程。异步是便于实现协同模式的通讯方式。

对于基于事件的异步协作方式,需要关注的地方在于事件的发布机制和接收机制。发布机制中需要注意的有:

+ 消息中间件要尽量简单,不要混杂业务逻辑

服务即状态机。

权衡DRY与微服务过程耦合的冲突,原则上是微服务内部DRY,跨服务可以适当违反DRY。

服务客户端的开发的要点是:

+ DRY

+ 处理与服务本身职责没有关系,但是又影响服务大规模运行部署的一些基础功能的部分(服务发现,故障模式,日志),只包含处理底层传输协议的代码

+ 由客户端决定升级时机

RPC与REST相比,客户端和服务端的部署无法分离。

第5章 分解单块系统

“接缝”的定义是,系统中可以抽取相对独立的一部分代码,这部分代码进行修改不会影响系统其他部分,是划分服务边界的依据。

分块的一些实例:

+ 外键约束通过业务逻辑实现

+ 对于共享的数据,通过单一服务单元进行操作

+ 共享的数据库表,拆分字段单元

+ 报表的导出,要么通过异步逻辑(提交请求异步导出),要么用一些软件(如列数据库),或者独立程序定期生成报表数据到其他数据库(类似InfluxDB中INTO的表现)

第6章 部署

每个微服务都建议有自己的CI流程。

如果可能,应该将每个服务都放到单独的主机或者容器之中,

部署的关键在于各个步骤的自动化。

第7章 测试

测试时主要关注对场景的测试,而非面面俱到。

想要频繁的发布版本,需要尽可能频繁的发布小范围的改变。

蓝/绿部署和金丝雀发布的区别在于,蓝绿是切全部流量,金丝雀发布是引导部分流量。并且Canary版本验证的内容会包括功能与非功能的,两个版本共存时间更长。Canary版本的好处在于可以对效果做更多的干预,通过实际运行效果评估开发的效果。

微服务中MTTR表现良好胜于MTBF。

第8章 监控

Web提供给监控系统的指标数据,最低要求就是提供响应时间错误率

为了便于监控系统追踪请求,可以使用全局ID的方式,贯穿整个请求的流程。

对于系统来说,对于数据的聚合,可以:

+ 聚合CPU一类的的主机层级的指标及应用程序程标(帮助找到程序性能瓶颈)

+ 要能回溯存储数据(Nagios的瓶颈,存储时间太短,可以加入第三方的存储组件)

第9章 安全

交给单点登录网关的应该是粗粒度的身份认证,而系统级别/业务级别的认证控制,应该在微服务内部实现。

权衡服务间的信任问题,可以根据操作的敏感程度,从低到高选择边界内隐式信任验证调用方身份(验证access_token?),要求调用方提供原始主体凭证(如支付宝支付密码?)。

不要自己实现加密解密!不要发明自己的安全协议!要用行业的通用方式。

Datensparsamkeit:只存储完成业务运营或者满足当地法律所需的信息。没有存储,就没有丢失。

第10章 康威定律和系统设计

康威定律:任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。

团队结构影响开发成果(反康威定律也是有可能的)。

第11章 规模化微服务

设计微服务时的态度,就是假设一切都会失败。

权衡实现的复杂度,可以通过可接受的损失程度确定。

考虑跨功能服务发生故障时的场景,能明确定位和错误处理的实现。

级联的架构,必须要有保护机制,防止全站不可用。

主动制造故障驱动系统强壮性的增强(因人而异,无需极端追求)。

尽力追求操作幂等。

作业与执行逻辑分离,即通过worker实际执行任务,虽然可能中断或者执行缓慢,但是不至于造成任务丢失。

CAP中只能存在AP系统和CP系统。AP系统要实现最终一致,而CP系统通过拒绝服务保证C。CA系统因为牺牲了分区容忍性,根本不能跨网络运行,在分布式系统中,这完全与分布式不符合。

服务发现是规模化的一个重要组成部分。常规方案有:

+ DNS,通过名字引导流量到服务,缺点在于灵活性以及时效性不足

+ 动态服务注册,通过ZK等软件

第12章 总结

一切去中心化是微服务一个重要原则。

不了解系统承载的业务和特点之前,不要微服务化!不能自动化,不要微服务化!

> 变化是无法避免的,所以,拥抱它吧!

作者推荐书单:

其他关键词

  • CQRS(Command-Query Responsibility Segregation)

简要了解 MySql 5.5/5.6/5.7/8 出现的新特性

 

概述

中秋假期的前夕的9月12日,MySQL 8.0.0 放出了 Development Milestone Release。是开源数据库的一大新闻。

要知道MySQL的上一个版本号仅仅是 5.7。在MySQL归属于Oracle公司之后,版本号的飞速提升也开始了,按此趋势,预计不日将会赶超Oracle自家商业数据库产品……

发布之际,简要的了解和比较一下 MySQL 5.5/5.6/5.7/8 之间的区别和特性。

如有错误,烦请指出。

MySQL的开发周期

在比较之前,首先提一下MySQL的开发周期.

MySQL一个大版本的开发,大致经历如下几个阶段:

  • Feature Development
  • Feature Testing
  • Performance Testing
  • Lab Releases
  • Development Milestone Releases
  • General Availability Release(GA)

Feature Development

也即是所谓的特性开发阶段。这个阶段是常规的功能点确定,代码开发,完成CR以及QA等常规的开发流程。

在一系列的测试以及bugfix之后,当QA “signs off” 即测试通过之后,会合并到TRUNK之中。

Feature Testing

即特性测试阶段。

特性测试的阶段,从质量保证的角度出发,MySQL的测试工作将会有以下的关注点:

  • 完成新特性的基本测试工作
  • 未出现性能衰减/特性的倒退
  • 至少达到80%的代码覆盖率

一个新特性达到要能被合并到TRUNK之中,需要满足如下条件:

  • 没有任何已知的错误,包括那些微小的错误
  • 没有任何已知的性能衰减/特性的倒退
  • 代码覆盖率达到预期
  • 自动化测试集中需要有这一新特性的回归测试用例

Performance Testing

作为应用的基础组件,性能是绝大多数开发人员都在关注的问题。在特性测试完成之后,将要进行的是性能测试。

性能测试主要关注两个指标:吞吐量响应时间

针对于吞吐量的测试,会有如下的特点:

  • 并发测试范围从81024个连接
  • 使用诸如sysbench这类开源软件,进行简单OLTP(基本的事务处理)的测试,每个测试时间在5-10min范围内,随着数据集以及系统配置而定
  • 针对特定场景也会有相应的测试用例
  • 测试数据会被存储到数据库中,以便比较或者确定测试的基准值

而对于响应时间的测试,则会有如下的特点:

  • 在单个被测试线程上完成测试
  • 准备两方面测试用例,分别考验计算复杂型场景和IO密集型场景

Lab Releases

实验室发布是在用户对某一特性有着强烈的兴趣时,发布的快照版本,通常还没有合并到TRUNK之中。

实验室发布版本存在的特性,并不会保证在正式版等版本中存在。如果想要尝鲜,可以访问labs.mysql.com

Development Milestone Releases

上述步骤结束之后,到了DMR阶段,也就是本次 8.0 版本所处的阶段。

DMR将会是一个重复迭代的阶段,下一个DMR版本将会包含上一个DMR版本的新特性或者功能修复。并且,DMR了的MySQL将会支持所有的平台,DMR状态下的MySQL的代码质量是达到了可供发布的水准的。

DMR看起来像是游戏的公测版本,每3-6个月会发布一个版本,每个DMR版本的MySQL会有一个特性需求收集截止时间点,新增的特性会历经开发、测试的历程,合并到TRUNK之中,变为下一个DMR。

总之,DMR的目的就是能够时常发布,让用户和客户能够反馈需求,体验新版本。

General Availability Release(GA)

终于,在n个DMR之后,MySQL表现稳定,需求“无可”增加,客户满意,质量过关。

基于最后一个DMR版本,会发布GA版本,即我们可以“放心”使用在生产环境中的MySQL。

每个GA版本的发布周期间隔在18-24个月。

版本比较

对于这些MySQL版本,如果想要了解之间的差异,一个可行的办法是阅读GA版本的Release Notes。

MySQL 的近几个版本发行时间间隔并不太大:

版本 GA 版本发布时间
5.5.8 2010-12-03
5.6.10 2013-02-05
5.7.9 2015-10-21

大约2年会有一个较大更新的版本会发出。

新特性

对于使用者而言,新特性应该是关注的第一焦点。下面会针对版本列出一些个人认为有特点的新特性。

5.5

InnoDB 作为默认存储引擎

InnoDB 因为支持事务、行级别锁而广为人知,并广泛应用。但是在之前的版本中,InnoDB并不是默认的存储引擎。在5.5中,InnoDB成为了默认的存储引擎。

半同步复制

半同步复制(Semisynchronous Replication)在MySQL 5.5中被支持(以插件形式实现)。

默认的MySQL通过异步模式进行复制,主库写入binlog之后,从库不一定能够被读取并处理,因为写入成功只是说明在主库上成功。主从不同步带来的问题相当之多,提升了开发难度。

而半同步复制则是主库需要有至少一个半同步从库,当一次写入操作进行之后,至少在主库和至少一个半同步从库上都完成了写入之后,用户才会收到已成功的信息。

半同步复制在这一程度上提高了数据的安全性。

5.6

MySQL 5.6 的主要变化在性能优化方面。有一些小的新特性也值得关注。

表中可以设置多个Timestamp属性

MySQL 5.5 中,如果设定多个Timestamp的属性为 ON UPDATE CURRENT_TIMESTAMP 时,这样的操作是不能完成的,这样的需求,通常要在业务代码中完成。

而到了 MySQL 5.6 中,这样的操作可以直接通过设定字段的属性即可完成。

InnoDB 支持全文索引

全文索引 MyISAM 存储引擎之前相对于InnoDB的一个“优势”特性,在MySQL 5.6中不复存在。

针对字符串型的字段(CHARVARCHAR或者TEXT),可以选择在创建表时增加这个类型的索引。也可以后续添加。

InnoDB的全文索引也使用的是倒排索引的设计,分词完成的词汇将会存储在独立的索引表之中。当包含全文索引的字段插入之后,会进行分词,同时先将分词结果进入内存缓存,之后再刷入索引表中,避免一次写入带来的大量附加的小规模的更改操作。

多线程复制

在MySQL 5.6中,会针对每一个数据库开启一个独立的复制线程,如果数据库压力平均的话,对于主从同步延迟会有一定的改善。但是如果数据操作都在一个数据库上,就不会有太多显著的效果了。

加入全局事务ID(GTID)

在MySQL 5.6前,如果从库宕机,重启之后需要进行同步,需要知道binlog文件名已经位置。

在MySQL 5.6中,加入了GTID(global transaction identifier)。GTID由source_id和transaction_id构成,source_id标识主库,transaction_id标识在数据库上进行的事务,格式即GTID = source_id:transaction_id

在加入GTID之后,重启从库之后,不需要重新进行位置的指向,只需要连接到主库即可,剩下的步骤将会是自动的。

5.7

InnoDB

InnoDB地位进一步增强,这一次系统表已然变成了基于InnoDB存储引擎的表。并且也不能禁用InnoDB存储引擎了。

增强的多线程复制

在5.6中添加的多线程复制的增强版,针对每个数据库可以增加线程数进行同步,对5.7.9版本,在实际使用中,在机械盘的服务器上,原有业务高峰时主从同步延迟在10-30分钟左右,使用5.7.9之后基本实现了数据上的同步。

多源复制

即将多个主库的数据归并到一个从库的实例上。

之前的MySQL,每个从库都只能拥有一个主库,如今MySQL提供了官方的解决方案,用于将多个主库的数据同步到一个从库之上。

多源复制有一个关键概念,即频道(channel)。频道指代一个主从库之间用于同步binlog的连接,通过新增的FOR CHANNEL子句,指定一个非空的频道名称,按照先前版本的连接主库的方法,即可实现多源复制功能。

需要注意的是,当多个主库均写入同一张表时,是要自行处理主键冲突。

JSON数据类型操作

PostgreSQL 9.3开始,PostgreSQL中JSON成为了内置的数据类型。

作为被广泛使用的数据组织格式,之前版本的只能讲JSON格式数据按照字符串形式进行存储。

到了5.7之后,JSON支持也被加入。

JSON中的字符串在MySQL中会被转化成utf8mb4的字符集,给携带诸如emoji字符的数据的存储带来了方便。

对于JSON数据的结构特性,MySQL中对JSON的查询需要借助path expression以及JSON_EXTRACT方法进行查询。path expression的简要要点如下:

  • $符号开头
  • .符号紧接着的是对象中的key
  • [n]中表示的是数组中的第n个元素,n>=0
  • .[*]表示一个key下的所有对象
  • [*]表示一个key下所有的数组
  • exp_a**exp_b则表示path中带有exp_aexp_b的值
  • key如果包含特殊字符,需要通过双引号包裹起来

更多操作参见手册

innodb_buffer_pool_size参数动态修改

在之前的版本中,innodb_buffer_pool_size调整之后,需要重启数据库实例,这个对于线上业务几乎是不可接受的。硬件性能强悍的服务器,调整这一参数之后,MySQL的表现会有较为客观的提升。

到了MySQL 5.7,这一参数终于可以在线调整了。

初始化工具

在之前的版本中,初始化系统表一般都会使用mysql_install_db脚本,到MySQL 5.7之后建议使用mysqld --initialize完成实例初始化。

在通过mysqld --initialize进行初始化时,需要加上--initial-insecure才能实现空密码登录,否则会将初始化的默认密码写入到错误文件中。

初始化完成之后,还需要使用MySQL 5.7版本的客户端登录,并且修改默认密码。

8.0

作为版本号突飞猛进的一个版本,在MySQL 8.0中新增了如下的特性:

用户角色

8.0中将会增强账号管理的功能,提供角色这一概念,即能组合权限,批量授权给某一用户。

增强的InnoDB

  • 自增id会写入到redo log中,这一改动使得数据库重启之后的自增值能恢复到重启前的状态
  • 增加了死锁检测开关innodb_deadlock_detect,可以在高并发系统中动态调整这一特性,提升性能

增强的JSON操作

  • 增加了->>操作符,使用这一操作符等同于对JSON_EXTRACT的结果进行JSON_UNQUOTE操作,简化了SQL语句
  • 增加了按JSON数据组织返回数据操作的两个方法:JSON_ARRAYAGGJSON_OBJECTAGGJSON_ARRAYAGG将某列的值按照一个JSON数据返回,而JSON_OBJECTAGG将列A作为键,列B作为值,返回一个JSON对象格式的数据

后续将会继续更新本文。

相关

[1]: What’s New in MySQL 5.7? (So Far)