mysql分片
分片算法主流的两种为范围法和分片法
缓存
CDN缓存原理
Nginx缓存设置
1 | http { |
MySQL主键问题
主键自增
- 不分片无集群的情况下,主键自增是可行的
- 当分片时,主键自增只能采用”范围分片”形式,会产生“尾部热点”效应,无法分担数据库压力,例如数据库2的范围分布在1w-2w数据之间,连续的自增主键数据某一时段内只能存放在数据库2
UUID做主键不可取的原因
UUID是无序的,作为主键会涉及大量索引重排
雪花算法
分布式ID推荐雪花算法,格式如下
注意:时间回拨可能会影响ID唯一性,例如服务器时间手动回调或者多个服务器之间时间同步
MySQL脏读、幻读、不可重复读
- 脏读指读取到其他事务正在处理的未提交数据
- 不可重复读指并发更新时,另一个事务前后查询相同数据时的数据不符合预期
- 幻读指并发新增、删除这种会产生数量变化的操作时,另一个事务前后查询相同数据时的不符合预期
MySQL默认事务级别是Repeatable Read(可重复读),MySQL 5.1以后默认存储引擎就是InnoDB,因此MySQL默认RR也能解决幻读问题
MySQL MVCC(多版本并发控制)机制
一些基础概念
- 快照读就是最普通的Select查询SQL语句
- 当前读指代执行下列语句时进行数据读取的方式(Insert、Update、Delete、Select…for update、Select…lock in share mode)
- ReadView是一个数据结构,包含4个字段
- m_ids:当前活跃的事务编号集合
- min_trx_id:最小活跃事务编号
- max_trx_id:预分配事务编号,当前最大事务编号+1
- creator_trx_id:ReadView创建者的事务编号
RC读已提交模式:在每一次执行快照读时生成ReadView
RR可重复读:仅在第一次执行快照读时生成ReadView,后续快照读复用(有例外)
例外:当两次快照读之间存在当前读,ReadView会重新生成,导致产生幻读
MySQL索引选择
- 搜索严禁左模糊和全模糊
- 并不是所有右模糊都能用到索引,要看索引命中率,高命中率还是会采用全表扫描
- 使用
force index(索引名称)
强制使用索引
MySQL语句对于分页的优化
语句
select * from A order by creat_time limit 500000,10
分析
如果只是给creat_time
字段添加索引是没用的(非主键索引的叶子节点保存了主键的值,在用非主键索引查询时,先会查询出主键的值,然后在主键索引的表中查询),所以对于非主键索引查询应该select creat_time from A order by creat_time limit 500000,10
解决方法
select * from A where creat_time >= (select creat_time from A order by creat_time limit 500000,1) order by creat_time limit 10
存在的问题
如果并发量很大的前提下,例如这一页的数据中第一行creat_time和最后一行creat_time是相同的,那么就会进入死循环,解决方法是数据增加偏移量处理
通过业务上进行处理
当数据量庞大的情况下,根据业务就行处理,没有必要将所有数据全部显示出来(比如几千页后的数据)用户不会关心后面的数据,通过控制数据的总量来实现查询速度加快
布隆过滤器
为了防止恶意缓存穿透攻击,使用布隆过滤器拦截无效请求
- 初始化过滤器,例如将商品表中的所有商品编号通过多次hash的方式让布隆过滤器进行标记
- 过滤器是会出现误判的,降低误判的方式有增加二进制数组位数和增加Hash次数
- 过滤器hash位置任意一个不存在则一定不存在,hash位置都存在则可能存在(存在误判几率,一般设置为1%)
数据删除存在的问题
如果库中数据删除,是无法直接删除布隆过滤器针对该数据存储的位置的,因为同一个位置会被多个数据引用,针对删除有两种办法
1. 异步定时重构布隆过滤器
1. 计数布隆过滤器代替普通布隆过滤器,其原理是每一个位上都额外增加了一个计数器,在插入元素时给对应的 k (k 为哈希函数个数)个 Counter 的值分别加 1,删除元素时给对应的 k 个 Counter 的值分别减 1
CAP定理
- 一致性C:更新操作成功后,所有节点在同一时间的数据完全一致。
- 可用性A:用户访问数据时,系统是否能在正常响应时间返回预期的结果。
- 分区容错性P:分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性或可用性的服务。
三者只能满足其二,例如
- AP表现为订单创建后不等待库存减少直接返回处理结果(为了追求客户体验,电商一般采用这个方法)
- AC表现为不再拆分数据系统,在一个数据库的一个事务中完成操作,也就是单体应用(小应用)
- CP表现为订单创建后一直等待库存减少后才返回结果
如何保证最终一致性
- 重试:例如MQ就是通过重试的方案来尽可能保证数据不丢失
- 数据校对程序:通过定时任务,每隔几分钟去抽取、补发数据
- 人工介入
canal
Canal主要功能是监听mysql的binlog
日志,进行解析后将消息传入队列,订阅队列的应用获取推送
Redis高可用架构
redis主从一致性原理
Redis Sentinel(哨兵模式)
针对redis的侦察选举方式
针对Sentinel自我本身的选举方式
Redis Cluster(集群模式)
集群数据分散分布算法
Redis Cluster
集群采用Hash Slot(哈希槽)分配,Redis集群预分好16384个槽,初始化集群时平均规划给每一台Redis Master
假设目前有三个主节点,那么将会把16384平分为3个区域,通过crc16
算法对key进行计算得到一个数值,再对16384进行取余,得到的值放入对应范围内的区域即可,反之亦然
集群配置文件redis-cluster.conf
创建集群命令
1 | --cluster-replicas 1 代表一个主节点对应一个副节点 |
集群模式和哨兵模式的区别
哨兵模式
每个节点持有全量数据,且数据保持一致,为系统Redis高可用
集群模式
每个节点主数据不同,是数据的子集,利用多台服务器构建集群提高超大规模数据处理能力,同时提供高可用支持
缓存的设计模式
- Cache Aside Pattern: 更新操作时,先写库,再删除缓存(不要更新缓存,并发数据易出错)(并不能数据保证强一致性)
数据出现不一致的场景
延迟双删
图二中数据不一致主要原因是写缓存在删缓存之后,简而言之只要保证保证删缓存一定在写缓存后即可。可添加定时任务或mq解决,时间间隔需要以线程2的逻辑处理时间作为参考
- 如要保证缓存与数据库强一致性,最好使用分布式锁,但那样并发性极低
分布式事务Seata框架
通常分布式事务流程
事务流程分为两个阶段(二阶段提交)
- 事务协调者发起请求,通知各个服务处理本地事务,所有服务处理各自的 逻辑,处理完成后事务不提交
- 事务协调者判断是否全部服务都正常进行,如果正常则再下达指令让各个服务事务提交,如任意一个出现异常,全员回滚
手动事务提交
1 |
|
自动提交事务
1 |
|
Seata分布式事务流程
Seata和通常流程的区别
每个服务逻辑完成之后先提交事务,不用等事务协调者的二次命令
SeataAT模式(默认)事务回滚
- 在所有数据库中添加
UNDO_LOG
表(回滚日志表) - 底层使用
sqlparse
对事务执行的sql进行解析,生成相反的sql插入UNDO_LOG
表中 (事务执行insert,它就生成delete) - 事务协调者下达提交命令,删除
UNDO_LOG
表中该条数据即可。下达回滚命令,执行UNDO_LOG
表中逆向sql,还原数据
Seata避免高并发脏读脏写
因为Seata第一阶段本地事务就提交了,所以可能出现问题是同一时刻其他事务可能会造成脏读脏写
保障接口幂等性方法
- 在原有的代码中添加前置判断,
if(!员工已调薪){进行调薪}
,虽然方便但是容易遗漏 - 构建幂等表
- 应用系统发送的请求头需要强制带上一串能保证一段时间内唯一的字符
- 应用网关通过nginx+lua脚本从redis中查看是否存在携带唯一性字符的请求
- 如果存在,则直接过滤请求返回错误码,如果不存在,则请求下达数据服务,进行一个正常的请求逻辑
并发数据冲突解决
悲观锁
在查询的时候,使用for update
产生行级锁,例如select * from XX where id = 1 for update
,此时只有一个线程可以读取id为1的数据(其他id数据可以获取),其他线程因为拿不到行级锁,只能进行等待(效率低)
乐观锁
数据库中新增一个_version
字段来控制数据版本
1 | select money,_version from acc where id = 1001; |
遇到冲突后的解决方法
- 前端应用提示,请稍后重新尝试
- 附加`Spring-retry`进行方法重试`@Retryable(value={VersionException.class},maxAttempts=3)`
JWT认证方案设计
JWT = 标头Header(加密方式) + 载荷Payload(需要传递的参数) + 签名Sign
- 网关统一校验
- 应用认证
方案一:JWT校验无感知,验签过程无侵入,执行效率低,适用于低并发企业级应用
方案二:控制更加灵活,有一定代码侵入(自定义注解,AOP注入即可),代码可以灵活控制,适用于追求性能互联网应用
JWT续签方案设计
- 不改变令牌续签 (通过添加redis来控制jwt的过期时间)
注意的是为了更加的安全,redis的key不建议使用jwt,而是把环境特征和数据进行md5加密作为key,redis只是用来标记当前环境登陆信息jwt的一个有效时间,并不存储jwt
- 允许改变令牌续签
重复生成jwt问题
认证中心设计一个计时Map数据结构,只记录过去n秒内的原始jwt刷新所生成新jwt数据,几秒内如果发现同样的jwt在再次请求刷新,就返回相同的新jwt数据
Nginx高可用
- 图上左右两个框内的Ng实际是同一个模型,为了方便观察将它区分(上图共用了2台Ng)
- Ng之间的检查心跳使用VIP(虚拟ip)技术
- Ng的轮询依赖于
DNS-Server
Docker中NG负载均衡配置
- 给需要用到的应用实例创建一个统一的默认网段
docker network create default_network
docker run --name app1 --network default_network -p 8080:8080
docker run --name app2 --network default_network -p 8081:8080
docker run --name nginx -v /XX/nginx/nginx.conf:/etc/nginx/nginx.conf --network default_network -p 80:80 -d nginx
Nginx.conf
配置如下
1 | http { |
MQ可靠性投递实现
可能出现的问题以及如何避免
- 异步刷盘,改同步刷盘(即2、3过程)
- 存储介质损坏,建议采用RAID10或分布式存储
- 不要启用自动Ack,应当手动ack处理确保消费者正常消费信息
RabbitMQ六种队列模式
1 | 1. 简单模式 |
RabbitMQ解决消息堆积
Springboot配置死信队列
1 | spring.rabbitmq.listener.simple.default-requeue-rejected=false |
1 |
|
1 |
|
MQ中pull模式和push模式如何取舍
写扩散优化
- 设置上限,比如粉丝好友上限为5000个
- 优化存储策略,采用NoSQL或大数据方案
- 限流策略,X分钟内完成消息发布
读扩散优化
- MQ削峰填谷,超长队列直接拒绝
- 增加轮询间隔,减少请求次数
- 服务端增加缓存,优化查询效率
- 增加验证码,分散时间,减少机器人访问
混合模式
订阅数小于某一个值采用Push模式,大于某一个值采用Pull模式,这个值需要根据服务器压力进行评估
企业应用发布流程思路
列式存储、行式存储区别
- 列式存储:Hbase、cassandra
- 行式存储:mysql、sqlserver
读取数据
新增、修改、删除数据
- 新增:多个列族,并发写磁盘
- 更新:添加一个新的数据(不修改原先数据),并标明版本号,读取时会读取最新版本号的数据,定时删除旧版本号数据
- 删除:添加删除标记(keyType=delete),类似于数据库假删除
千万级订单系统高可用设计
避免丢单要点
- 关键逻辑不要读写分离、缓存查询方式,避免从库同步延时导致订单无法被查询到,从而创建支付单失败(主库已写入,还未同步到从库,缓存同理)
订单补偿不要粗暴地使用消息队列的方式,避免中间件发送消息和接收消息时引发的订单丢失
接收消息处理失败时一定要让消息重试,避免丢失
避免锁表
在数据库事务里同时去更新其他数据源,或发送 MQ 消息等,这不仅不能保证数据一致性,还会把数据库的连接耗尽
千万级订单系统设计方案
- 将下单服务进行了服务拆分,使用单独的接单服务处理接单
- 使用订单引擎和订单管道处理订单业务逻辑
- 改用双写和数据补偿的方式处理缓存
- 使用缓存过期的方式控制数据量
Zookeeper分布式锁实现原理
Zookeeper的数据结构是树形的,节点叫做Znode,Znode共分为有4种类型
- 持久节点
- 持久节点顺序节点
- 临时节点
- 临时顺序节点
持久节点断开连接依旧存在,临时节点则会被删除,所谓顺序节点,就是在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号
Zookeeper分布式锁实现代码
- pom中引入
curator-recipes
,来简化Zookeeper的实现
1 | <dependency> |
- 代码流程
1 | public int outOfWarehouseWithLock() throws Exception { |
大型电商秒杀架构
利用了redis中
decr
命令的原子性来保证库存不会超卖lua脚本的作用是:监听请求,发送
decr 商品编号
命令到redis中,实现商品库存自减,自减后判断库存是否小于0,如果是则直接返回“已无库存”,如果不是,则将订单信息发送给MQ(秒杀瞬时流量过大,需要MQ进行限流),返回订单编号即可(接下来的操作与lua脚本无关)- 通过MQ的限流让订单程序可以正常的运行,最后订单的状态由APP轮询获得
解决高并发热点数据的访问倾斜问题
热点数据特征
短时访问频率超高,数据总量相对较少
方案一 :专门设置热点数据缓存(高成本)
关于热点的选择,一种是将所有大促的商品放入热点缓存内,适合小规模活动,另一种是根据以往的用户行为分析大数据评估,将个别数据选为热点数据
方案二:缓存前置+闪电缓存(低成本)
缺点是会造成短时间的缓存数据不一致,但是节约了成本
日志收集架构
- ELK:es、logstash、kb三款软件进行监管
- TCP推送:在ELK的基础上,应用通过Logback插件
LogstashTcpSocketAppender
插件进行推送 - EFK:es、filebeat(监视指定的位置或者日志文件将其转发到logstash或者es,只能选一个,无法同时向多个output发送) 、kb三款软件进行监管
- kefk:es、filebeat、kb、kafka(用来解决filebeat无法同时向多个output发送的问题)