巧用Hash,避免使用锁

业务需求


如下图,业务A 往队列写消息,业务B 负责消费产生的消息,每条消息都会有一个用户id,更新存储A、B中与用户相关的信息,更新操作:

  1. 根据用户id从存储A获取信息valueA
  2. 根据valueA更新存储B中的信息
  3. 更新用户在存储A中的信息为valueB

由于对存储A、B的更新操作无法保证事务性,所以当业务B开了多个消费者的时候,就可能并发地更新同一个用户信息,从而导致用户的信息出现错乱。

问题解决


首先想到的解决方案是通过锁来实现,比如使用redis,以用户id作为key,然后执行set userid '' EX 2 NX',成功执行了set命令的消费者获得锁,当更新完用户信息后,可以执行delete userid来将锁释放掉。

然而,使用锁之后,发现消费者的吞吐量下降了很多,于是就想有没办法不用锁。

认识问题本质

问题产生的根本原因在于多个消费者并行地更新用户的信息,加锁是为了让每个用户信息的更新操作能够被串行执行。

所以只要能够使得对同一个用户的更新操作串行地执行,问题就解决了。在不加锁的情况下,可以想办法使得同一个用户的消息都入到同一个队列,从而只被一个消费者消费。

Hash

那么,问题就变成如何将同一个用户的消息入到同一个队列里了,因为每个用户消息里都会有一个用户id,所以可以利用这个用户id的hash值,然后取模,使得同一个用户的消息映射到同一个队列上,从而避免使用锁。


2017-09-08

基于postman/newman实现的HTTP接口监控

本文主要分两部分,第一部分是介绍postman的一些使用tips,第二部分是简单介绍下自己基于postman/newman写的一个HTTP接口监控的程序~

postman


postman作为一个http请求模拟工具相信很多人都已经用到了。postman有pc app,也有基于chrome 的app,在使用体验上,pc app会更好(比如打开太多tab时可以方便地关掉,跑collection时展示效果更好)。

目前,有一点chrome app更好用的就是,它可以利用附带的一个chrome扩展Postman Interceptor,方便地捕获chrome的请求。而pc app需要通过它的proxy功能来捕获请求。


Read more...

2017-07-20

HTTPS 性能优化学习

最近在学习https性能优化,虽然网上已经有许多的关于https性能优化的文章了,但还是想写下这篇文章,作为学习总结=^_^=,文中对于一些概念性或实现细节上的东西并不会展开,但会给出相应的引用,有些图片也来自网上资源。

章节规划:

  • 认识SSL/TLS
  • 算法选择
  • 会话恢复
  • OCSP stapling
  • TLS 缓冲区优化
  • TLS false start
  • 其他优化

认识SSL/TLS


SSL和TLS都是用于保障端到端之间连接的安全性。SSL最初是由Netscape开发的,后来为了使得该安全协议更加开放和自由,更名为TLS,并被标准化到RFC中,现在主流的是TLS 1.2版本。

从上图,可以看出SSL/TLS是介于应用层和传输层之间,并且分为握手层(Handshake Layer)和记录层(Record Layer)。

  • 握手层:端与端之间协商密码套件、连接状态。
  • 记录层:对数据的封装,数据交给传输层之前,会经过分片-压缩-认证-加密

从TLS 1.2 RFC可以了解更多:https://www.ietf.org/rfc/rfc5246.txt


Read more...

2017-05-13

关于消息队列的思考

消息队列是服务架构中常见的组件,可用于服务间解耦、事件广播、任务异步/延迟处理等,本文对于消息队列的实现如何满足几种消费语义进行了阐述。

消息队列组成

  • 生产者(Producer):负责产生消息
  • 消息代理(Message Broker):负责存储/转发消息(转发分为推和拉两种,拉是指Consumer主动从Message Broker获取消息,推是指Message Broker主动将Consumer感兴趣的消息推送给Consumer)
  • 消费者(Consumer):负责消费消息


Read more...

2017-03-22 消息队列

Golang singleflight 用武之地

缓存更新问题


当缓存失效时,需要去数据存储层获取数据,然后存储到缓存中。

通常缓存更新方案:

  1. 业务代码中,根据key从缓存拿不到数据,访问存储层获取数据后更新缓存
  2. 由专门的定时脚本在缓存失效前对其进行更新
  3. 通过分布式锁,实现只有一个请求负责缓存更新,其他请求等待:一种基于哨兵的缓存访问策略

服务中某个接口请求量暴增问题


比如某个帖子突然很火,帖子下有非常多的跟帖回复,负责提供帖子内容、回帖内容的接口,对于该帖子的请求量就会非常多。

如果每个请求都落到下游服务,通常会导致下游服务瞬时负载升高。如果使用缓存,如何判断当前接口请求的内容需要缓存下来?缓存的过期、更新问题?


Read more...

2017-03-07

golang并发编程的理解

golang通过语言层面的特性goroutine和channel提供了优雅的并发编程方式,Share Memory By Communicating一文中有这么句“名言”:

Do not communicate by sharing memory; instead, share memory by communicating.

单纯看这句话其实不好理解,但结合文中的例子算是基本明白其中的含义了。

文中以编写并发拉取给定的URL资源程序为例子,展示了传统多线程并发编程模型和golang并发编程模型的不同。


Read more...

2017-02-11 GoLang并发

MySQL主从复制延迟的监控

MySQL的同步模式有三种,从官方文档可知Replication

  • With asynchronous replication, the master writes events to its binary log and slaves request them when they are ready. There is no guarantee that any event will ever reach any slave.
  • With fully synchronous replication, when a master commits a transaction, all slaves also will have committed the transaction before the master returns to the session that performed the transaction. The drawback of this is that there might be a lot of delay to complete a transaction.
  • Semisynchronous replication falls between asynchronous and fully synchronous replication. The master waits only until at least one slave has received and logged the events. It does not wait for all slaves to acknowledge receipt, and it requires only receipt, not that the events have been fully executed and committed on the slave side.

在asynchronous和Semisynchronous模式下,主库与从库之间必然存在一定延迟,当延迟大的话,从库的查询就可能查询到旧的数据,或者查询不到数据(比如主库插入的数据尚未同步到从库)。


Read more...

2016-11-06 MySQL复制