Go秒杀服务端优化
架构调整
旧架构
新架构
预备知识
cookie,session
-
cookie和session的区别
-
cookie和session的联系
-
chrome抓包查看 // TODO
-
普通登录场景下cookie和session的配合使用流程
对称加密和非对称加密
参考附录1,2
- 对称加密
- 非对称加密,如: AES, RSA
- AES的使用 // 参考aes.NewCipher(key)函数注释
16,24,32位字符串的话,分别对应AES-128,AES-192,AES-256 加密方法
base64 // 附录3
Q: base64最开始的功能
A:
- 其实BASE64编码的初衷是为了满足电子邮件中不能直接使用非ASCII码字符的规定。
- 但是也有其他重要意义:所有的二进制文件,都可以因此转化为可打印的文本编码,使用文本软件进行编辑,并且对于数据流来说是一种简单的加密。
Q: 为什么要使用base64编码,有哪些情景需求?
A: 有2个场景不能传输二进制
- 电子邮件的历史原因
- 纯文本协议,不能传送二进制,可以借助base64编码后传输。因为传输一个纯文本协议,二进制中可能会出现被当做控制字符处理的部分。
服务端优化点
第10章 实现cookie验证(单节点)
- 改进点
突破session的限制(用cookie替代session实现验证) - 思路
去掉session,在cookie中存入处理过的用户关键信息,处理方法:
step1. 公钥加密: 把uid使用私钥加密。// 私钥16位,加密结果128位。
step2. base64编码: // 因为http的cookie中不能存放二进制数据,只能存放ascll字符。 - 相关代码
1. user_controller.go
登录接口的后端实现。本来登录成功后往只cookie写入了uid字段,为了防止uid被篡改,往cookie中写入了一个验证uid是否被篡改的sign字段。
2. auth.go
验证用户是否登录的中间件。验证uid是否存在。代码不变。
第11章 服务端性能优化之分布式验证实现
-
分布式架构
从原理部署在一个机器的一个进程中,改为部署到多个机器的不同进程中。
拆分后的构成: 权限验证(水平扩容)+数量控制(水平扩容)+web服务器+消息队列+mysql
-
权限验证: 使用拦截器实现
- 拦截器(传递函数) // 装饰器模式的go实现 // 附录4
功能: 验证cookie中的用户信息是否可信(没有被篡改)。
思路: 通过验证cookie中的uid和sign解密结果是否匹配来验证是否被篡改。
代码: filter.go(mian包) + validate.go单独部署
.验证代码从代码中提取处理单独部署
扩展: OAuth协议
-
Q: 我们这里为什么需要一致性hash算法
A: 一致性hash算法的主要应用场景是 分布式存储,分布式缓存, 负载均衡。我们这里属于分布式缓存这类应用,注意我们并没有用redis,而是用的内存缓存,每个机器存放不同的用户信息缓存。
数据存储需求: 每台机器(进程)存放uid及其请求时间。比如uid=1,2,3缓存在机器1,uid=4,5,6缓存在机器2,那么uid=1请求机器2时就会去机器1的缓存上去拿数据(内网) -
一致性Hash算法 //附录5
Q: 一致性Hash算法主要作用
A: 一致性哈希是为了解决节点可扩展的问题。
- 普通的hash算法,在节点增删时会产生缓存雪崩(同一时间大量缓存同时失效)。
- 一致性hash算法把这种缓存失效的情况,缩小为增删节点与相邻旧节点之间的区域。
- 一致性Hash算法代码实现 / /TODO
关键点:
- IP=Local IP,则读取本机缓存。否则IP != Local IP, 则本机充当代理去另一台机器获取数据。(内网传输)
- 关键结构体
//创建结构体保存一致性hash信息
type Consistent struct {
//hash环,key为哈希值,值存放节点的信息 // TODO k: 请求key或节点的hash值 v: 真实节点或虚拟节点信息(如果有2个真实节点,每个节点20个虚拟节点,那么一共有20个v需要存储,环上其他位置并不需要存放数据)
circle map[uint32]string
//已经排序的节点hash切片 // TODO 排好序的虚拟节点hash值,40个元素,对应2个v。
sortedHashes units
//虚拟节点个数,用来增加hash的平衡性
VirtualNode int
//map 读写锁
sync.RWMutex
}
- validate.go中应用一致性hash算法
//设置集群地址,最好内网IP
var hostArray = []string{"192.168.0.1", "192.168.0.2"}
var localHost = "192.168.0.1"
// 创建Consistent结构体
hashConsistent = common.NewConsistent()
// 添加服务器节点
for _, v := range hostArray {
hashConsistent.Add(v)
}
// 作为缓存的结构体
type AccessControl struct {
//用来存放用户想要存放的信息
sourcesArray map[int]interface{}
sync.RWMutex
}
- 使用了一致性hash算法后的使用流程
- 阿里云SLB来负责负载均衡到不同的云服务器ECS上
是随机选择一个ECS来处理请求的。
第12章 服务端性能优化解决超卖&引入消息队列
12-1 突破Redis瓶颈限制 (07:01)
-
假设使用redis保存商品数量
-
redis瓶颈原理
redis单机qps 8w。
redis集群qps 整体千万, 但是对单个商品而言也是8w。
-
突破方法
不用redis保存商品数量,而是直接写在代码,这样只访问go接口,不访问redis和数据库。
测试结果:24w的qps。 -
wrk
wrk参数和输出结果 -
修改
fronted/web/controllers/product_controller.GetOrder方法
原有方法对数据访问压力很大,每调用一次创建一个订单就要访问几次数据库。
新方法: -
rabbitmq的消费模式配置
- 消息者流量控制: 每次只发送一个消息给消费者。配置项:
channel.Qos
- 确认模式: channel.Consume(autoAck:false)
-
rabbimq相关代码实现
product_controller.go: 在下单接口GetOrder()中生产消息(消息结构体有2个字段: userId+productId)
consumer.go :消费者。流量控制(channel.Qos设置1次取1个消息),手动确认后删除消息 (消费成功,调用msg.Ack(false)使得rabbitmq删除该消息 // TODO 消费失败应该怎么处理) -
当前代码存在的问题 // TOOD
没有事务(商品数量减1+生成订单)
问题
Q: 什么叫横向扩展 Q: 如何设计可横向扩展的模块 Q: 可以横向扩展的模块又哪些
其他
Q: getOne: 数量控制多实例部署, 同一个商品的数量怎么共享?把整个商品的商量平均分给每台服务器吗?
Q: 44 | 记一次双十一抢购性能瓶颈调优(讲得很完整的秒杀例子
) && 一个横向扩容导致更严重问题的例子
Q: 思考题假设你现在负责一个电商系统,马上就有新品上线了,还要有抢购活动,那么你会将哪些功能做微基准性能测试,哪些功能做宏基准性能测试呢?
兜底策略(限流,实现智能化横向扩容,提前扩容)
Q: 05 | 系统设计目标(三):如何让系统易于扩展?
高并发系统设计40问
参考
- 对称加密、非对称加密、RSA(总结)
- 对称加密和非对称加密结合使用的例子:https
- 为什么要使用Base64及其编码原理和实现
- 装饰器本质上允许您包装现有功能并在开始或结尾处添加您自己的自定义功能
扩展: 装饰器绝对不是处理 REST API 保护的正确方法,我建议您使用 JWT 或 OAuth2 来实现这一目标!OAuth2.0协议
扩展: 明白了,原来Go web框架中的中间件都是这样实现的 - 面试必备:什么是一致性Hash算法?
扩展: Redis与Mysql双写一致性方案解析