项目一般问什么 #
项目问题因人而异,但是这些问题是共性的,可以思考一下 #
- 你最有成就感,或者最有挑战的项目经过,解决什么样的问题
- 数据量很大还是并发量很高,并发量体现在哪里?QPS是多少?
- 怎么提高可用性的?
- 技术难点体现在哪里?
- 你的项目有没有出现什么重大事故/故障,是怎么解决的,具体是什么原因
- 有没有什么印象深刻的Bug
分布式锁如何实现 #
分布式锁,一般为了达到分布式加锁需要通过投票的机制,依次向所有节点申请加锁,半数以上节点投票通过完成加锁,可以避免单点故障(Redis称为Redlock算法)
- 加锁的动作需要保证原子性,
Redis
通过Lua
脚本来保证 - 谁加的锁谁来释放锁,所以需要标记锁来源
- 预防加锁程序挂掉导致的锁不释放,所以需要设置过期时间
- 加锁成功需要判断获取锁总耗时没有超过锁有效时间,这才判定为加锁成功
注意:假如程序处理速度比锁过期时间要长,是不合理的设计,超时时间的设置就很精细,一般都是远大于处理的时间,如果真的处理时间太长应该判定失败并告警
见 redis
如何实现一个分布式id生成器 #
首先要知道自增主键出现的问题
- 在高并发的情况下加入事务执行失败回滚,会跳过当前插入
ID
,使ID
不连续 - 所有数据库中的自增字段或者自增序列都要记录日志,会产生磁盘IO,会成为性能瓶颈
- 假如数据库使用的是Range分片,自增
ID
可能会集中写入集群中的一个节点,出现数据访问热地世,性能退化成单机写入
解决方案
- 随机主键
UUID
方案(32 个的 16 进制数字,16^32 = 2^128 就是128位),虽然可以保证每次随机都不一样,但缺点是键值长度过长,存储和计算的代价增加,uuid只能保证不重复,但数据页可能会分裂,影响查询性能 - 号段模式,每个业务批量获取数据库中的号段,比如一次获取1000个,然后内存生成1000个自增ID,使用完再获取1000个;只需要插入一条记录,步长设置为1000,注意使用乐观锁(维护版本号),记录字段有业务类型、当前最大可用id、号段步长,version号;缺点服务重启时重新申请号段,不够随机有被猜到的风险
- 在
TiDB
里提供了一种AutoRandom
的算法,生成64位整型随机ID
,1bit
符号位、5bit
事务开始时间,58bit
自增序列号,还是有可能出现尾部热点 - 雪花算法
Snowflake
,时间戳精确到毫秒,10位长度机器码最大规模1024个节点(2^10), 12位序列代表1毫秒能产生的id数量最多4096个。所以TPS
可以达到419
万左右(2^22*1000), 每秒那么多大多系统都够了
注意雪花算法,对时间的要求比较高,如果时间不同步,时钟回拨时 ID
有可能出现重复
引用: 分布式数据库30讲
如何优化雪花算法的问题 #
雪花算法的问题主要在于时间回拨出现id
重复、机器id有上限
时钟回拨就是本机时间略快,完成时间服务器的校准(NTP或者闰秒回拨)以后,会出现时间倒退,导致生成ID重复
时钟回拨解决办法:
- 继续在当前ID序列号最大基础上增加,方案来自 snowflake算法的时钟回拨问题如何解决
- 如果时间偏差比较小,
<=5ms
可以等待2倍时间,牺牲很短时间的可用性,方案来自 SnowFlakeID原理和改进优化 - 时间回拨跨度太大时告警,并摘除本身节点,只会影响一个节点
- 也可以考虑直接关闭时间同步
机器id有上限的解决办法(雪花算法优化)
- 百度(uid-generator)的解决办法是可以自定义各部分的位数,工作机器
ID
需要数据库中创建一个表,插入机器相关信息(host
和port
),再根据表的自增ID
作为workID
,重启服务就另申请workID
- 美团使用
Leaf
算法,可以基于号段模式或雪花算法,对号段模式优化 双buffer方案,提前加载下一号段;雪花算法借助zookeeper
的持久顺序节点的特性配置workID
(我想上容器的话直接使用hostname或者使用k8s中的sts也不错)
注意雪花算法实际上是趋势递增,而不是绝对递增,这是为了保证性能
如何实现秒杀系统 #
漏斗的思路,是架构上设计,客户端,网关,后台服务,层层限流,保证业务处理不被流量洪峰打挂了
客户端侧降低服务端压力:
- 动静分离,静态资源放到cdn(某些服务为了更新及时不能放cdn)、前端文件
webpack
打包减少请求量 - 减少后端请求数量,只保留抢按钮的请求
- 时间使用客户端时间,不到时候无法点击
- 增加互动游戏再降低并发请求量
- 秒杀活动一旦发起,不允许修改详情等信息
- 保证web安全,防止xss与重放(随机数、时间戳、序列号)、CSRF等攻击方式
部署架构:
- 后端服务部署多个可用区,防止单可用区故障导致整体不可用
- 需要配置安全策略:防火墙、防DDOS、API网关、WAF;接入风控挡掉不合法请求
- 使用负载均衡SLB,根据不同节点负载情况分发流量
- 硬件上使用SSD
后端防护:
- 防止超卖,推动库存确认流程到支付阶段
- 库存信息放到内存中(redis)
- 使用另外的数据库集群
过载保护(有损保护):
- 服务降级:秒杀期间关闭某些服务,比如淘宝关闭退款流程,微信抢红包延迟到账
- 熔断:接入监控系统,根据系统节点的承载能力和服务质量有关,比如 CPU 的使用率超过 90%,请求错误率超过 5%,请求延迟超过 500ms, 它们中的任意一个满足条件就会出现熔断,主动拒绝请求;
- 限流:速度过快时加入验证码流程,接入API网关可以进行流量控制,请求过滤和控制,并过滤的请求,前端根据错误码返回友好的页面(已抢完之类)常见限流算法:漏桶>令牌桶>滑动窗口>计数器
容灾 #
假如某个节点无法拉起,或者量级大被打挂了
追踪链的traceid是怎么生成的 #
traceID 一般由请求经过的第一个服务器生成,参考 服务器 IP + 生成 ID 的时间 + 自增序列
,它的作用是把各个服务器上的调用日志串联起来
- 前 8 位 0ad1348f 为生成 TraceId 的服务器 IP,这是一个十六进制的数字,每两位代表 IP 中的一段,把这个数字按每两位转成十进制即可得到常见的 IP 10.209.52.143,可以根据此规律来寻找请求经过的第一个服务器。
- 后 13 位 1403169275002 是生成 TraceId 的时间。
- 最后四位 1003 是一个自增序列,范围是 1000 到 9000,到达 9000 后回到 1000 再重新开始自增。