Redis和MySQL的一致性
缓存设计
-
旁路缓存模式 读:先读redis,没有再读数据库然后写入redis 写:先写数据库,再删除redis , 这种写入方式叫做 Write-Around(不写缓存)
-
读写穿透模式:读穿透+写穿透,抽象出缓存层,业务系统不直接和缓存交互,而是和缓冲层交互,缓存层在和具体的数据库(redis or mysql)交互
读:先读redis,没有再读数据库然后更新redis 写:先更新redis,再更新数据库(通过事务保证原子性)
-
异步写入模式(也称为写后模式 write-behind) 读:先读缓存 写:缓存中数据达到一定量级后,再批量同步到数据库
缓存的加载模式
系统启动时何时加载缓存
- 预刷新:提前预热系统,将数据库中数据加载到缓存
- 懒加载:具体请求时再加载
一致性分析 主要是读/写缓存和数据库这两个操作不具备原子性的问题:在多线程环境下如何保证一致性,以及某个操作出现错误然后影响一致性
- 先写缓存再写数据库 写数据库报错就会出现不一致 多线程下同时写也会出现问题
- 先写库再写缓存 报错回滚数据库,也许能解决不用回滚缓存的问题 但是多线程下同时写会出现问题
- 先删除缓存再删除数据库 考虑多环境下读写:A删除->B读取->B更新缓存->A写数据库==>不一致
- 先写库再删除缓存 多环境下读写(考虑缓存过期):缓存过期->A读取数据库->B写数据库->B删除缓存->A写缓存==>不一致
解决方案
- 失败重试策略:先写库再删除缓存,删除失败就重试删除
- 延迟双删除策略:先删除缓存,写库,隔一段时间(200ms-300ms)再次删除缓存(还是要解决保证删除成功)
- 异步更新/删除策略:解决代码侵入性高,耦合度高 删除缓存->更新数据库->发生删除缓存MQ->消费者删除缓存 可以使用canel监听bin-log实现删除缓存策略,几乎0侵入。
- 更新标识策略:用于表示当前是否在处于更新阶段,处于更新阶段从数据库读取到的数据就不放入缓存,对业务侵入性比较高
分布式ID
分布式组件
为什么需要这样组件?没有这个会怎么样,有这个会怎么样
注册中心
什么是注册中心:服务提供方在注册中心注册自己的服务,服务调用方更具自己的需要从注册中心拉取提供服务的列表,然后调用提供方提供的服务。
为什么需要注册中心?
- 没有注册中心:系统间调用,ip采用硬编码方式,扩展性差,耦合性高
- 有注册中心 降低后合度:子系统迁移,依赖它的系统不需要修改代码 提高维护性 检查节点健康状态:能够很大程度上保证通信顺畅 支持动态伸缩:集群扩展方便
注册中心的优点
- 服务注册
- 服务发现
- 系统解耦
- 灵活:节点动态伸缩,应对突发状况
- 中心化管控:健康探测,自动剔除故障节点
- 扩展性
市面上的解决方案:Zookeeper,Eureka,Consul,Nacos
配置中心
用于统一的管理配置(服务越来越多,配置也会越来越难以管理)
**约定大于配置:**如约定好的项目结构,减少不必要的沟通。
**配置大于编码:**能通过编码实现就不要用硬编码。
配置中心的好处
- 集中管理
- 降低冗余
- 动态刷新
- 版本管理
- 多环境支持
远程调用
服务间的相互调用过程类似,可以抽取成为一个组件
解决方案:OpenFeign,Dubbo
好处
- 无需关心调用底层协议细节:直接调用即可,不管是Http还是其他协议
- 使用起来类似本地调用
- 与注册中心配合:从配置中心感知调用地址
服务网关
网关:为系统提供一个统一的外部API入口,主要功能就是路由,将外部请求路由到具体的节点
网关分类
- 接入层网关
- 服务网关
服务网关功能
- 请求路由:承接外部流量,按照规则转发
- 安全控制:统一鉴权,反爬虫等
- 响应处理:对响应统一处理
- 协议转换:转换外部协议和内部协议
- 负载均衡:
- 流量控制:控制流量,保护节点安全
为什么需要网关?
- 将大多数请求的通用能力进行一个统一实现
服务网关的解决方案:Zuul,GateWay
负载均衡
用于服务间调用或者网关往集群分发请求的节点选择策略
分类
- 服务端负载均衡:统一的负载均衡服务,通过调用这个服务然后再负载均衡到具体的服务,如nginx可以充当这个角色
- 客户端负载均衡:将负载均衡在发起调用的一方实现,与注册中心紧密配合
为什么不用服务端负载均衡?
- 太重了,中心化
- 客户端负载均衡让每个客户端都局部分发能力,提供容错性
客户端负载均衡的缺点
- 会出现请求倾斜
市面上客户端解决方案:Ribbon,LoadBalancer
服务保护
集群的弹性伸缩在一定程度上能够解决高并发带来的问题,但是硬件的资源始终是有限的,一个节点的故障有可能会影响整个集群。
服务雪崩:单个服务的故障蔓延到整个系统
**服务降级:**某个服务不可用时切换到备用服务或者方案
服务保护组件:保证系统在故障不可控环境下稳定运行
- 防止雪崩发生
- 实现降级和限流
- 资源隔离:如不同的接口使用不同的线程池,从而这个接口的并发不影响其他接口的线程资源
- 请求缓存、合并、重试和监控机制
解决方案:Hystrix,sentinel等
分布式事务
一个请求涉及多个库的操作,需要分布式事务进行控制。
解决方案:seata
持续集成/持续部署(CI/CD)
分布式下节点过多,部署麻烦
常见方案:Git+Jenkins+Docker+Kubernets
日志收集
解决分布式下日志分散在各个应用内
解决方案:ELK等
链路追踪
从统一的日志管理中查找对应日志链路从而分析bug问题。
为每个请求分发一个全局ID,输出日志,携带输出ID
系统监控
监控服务器资源,应用资源,中间件监控,业务监控等这些。
微服务设计
微服务等于分布式系统,但是分布式系统不等于微服务
为什么需要要微服务?系统规模大,用户体量大,流量峰值够高,项目成员多,硬件资源够,工期时间足
- 所有成员在一个项目上开发,代码不好管理
- 业务耦合度高
- 吞吐量
设计原则
设计要求:
- 高内聚:每个服务内部功能高度相关,专注单一业务
- 低耦合:服务间依赖最小化,减少变更带来的影响
- 可靠性:某个服务故障时,能保证其他无关服务的运行
- 稳定性:减少事故发生频率
- 可用性:系统提供服务的时间比例
- 可扩展性:能够按需扩展资源应对负载变化,也可以是功能上的扩展不影响其他功能的
- 灵活性:服务能够适应需求变化和技术变化的能力,如替换成新服务
- 可维护性:系统易于理解,修改,升级。
- 独立性:团队独立,数据独立等
- 可管理性:系统监控,配置管理等
设计准则:
- 单一职责
- 隔离性:如有自己独立数据库
- 可伸缩性:能够水平扩展
- 可替换性:替换成新服务,依旧能运行
- 容错性:部分失败不影响全局,需要有备用服务和故障处理
- 开闭原则
- 服务自治性:每个服务都包含自己的业务逻辑和数据
- 可观察性:能够监控
- 可重复部署:能够重复的部署
设计原则
- 完善的基础设施:包含所有的分布式组件,不要等出问题再去考虑
- 拆分粒度:考虑业务,流量,团队,适当拆分
- 服务分层:服务又基础服务和业务服务两大类。分层就是需要抽取服务里的共性操作,抽象成独立的基础服务,基础服务会被大量的其他服务接入,稳定性是首要考虑的问题。
- 无状态设计:要能够弹性伸缩,前提就是无状态服务,如定时调度技术SpringTask会使得服务有状态,伸缩都需要去考虑其带来的问题。Session的使用也是一样。
- 容灾体系:做好容灾方案,如数据备份,主从热备,容灾告警,多机房等。中小型系统也没有必要做到面面俱到,因为容灾资源99%的时间都是闲置状态。
- 约定大于一切,配置大于编码:做好编码,数据库,性能规范等管理。微服务开发最好是接口先行,再考虑语言实现。
- 面向失败设计:尽量去考虑出现失败后的场景,设计好兜底方案。
- 摆脱单体思维:合理运用中间件处理业务,如redis,MQ,ES等去实现业务。
微服务祛魅
微服务并非银弹,其带来了更高的灵活性的同时也带来了复杂性和其他许多挑战。
如果说目前项目的还没有使用微服务的必要,但是又需要考虑到未来用户体量增大时去进行服务拆分的场景,那么尽量先通过Maven的多模块去构建单体项目。
服务过度拆分
每个服务都应该由一个团队负责,过度拆分会提高人力成本,同时也可能会降低沟通的效率,从而降低了开发效率。
盲目跟风微服务
微服务有很多的好处,但不是所有应用所有场景都要用到微服务,盲目使用只会提高成本。
大部分情况用微服务的原因都是:汇报更好看,对外更好吹,提升开发者的竞争力。
更低的性能
服务之间的调用需要通过网络完成,相比于原来的直接本地调用,性能只会更差。这个性能之间的差距往往是数倍的关系。
微服务接口设计
怎么设计接口
- 完善注释和接口文档
- 合理的执行日志:位置,级别,内容都要合理 日志链路跟踪
- 统一的接口风格: RESTful风格 统一的返回结构
- 跨域解决
- 安全性设计 鉴权机制 接口签名机制:发放token 出入数据加密:防止篡改和请求重放,对响应进行加密(防止爬虫) 黑白名单机制:ip级别,用户界别,地域级别
- 资源隔离:如线程池隔离
- 版本兼容问题:一定要考虑
- 支持动态配置:熔断时间动态配置
- 面向失败设计:考虑好所有失败的情况
- 做好接口限流
如何写好接口
- 处理好写入资源:写到哪里?要不要保留
- 请求排队:单次调用接口消耗大量资源,不用进行实时处理,就可以需要进行排队
- 接口防止重复和幂等设计
- 批处理思想:合并读写
- 多线程优化:异步优化或者并发优化
- 并发安全机制:分布式锁这些
接口防重和幂等
重复请求:用户重复提交,非法重复提交,失败重试,重复消息,网络故障
幂等机制:多次调用接口返回结果一致
防重解决方案
- 前端 按钮变成灰色防止重复点 重定向页面
- 后端 **使用唯一key:**获取key,首次提交生成放入redis **防重表:**操作往表插入操作id **状态机:**操作前检查操作状态(如订单状态) **Token方案:**进入表单先获取唯一Token,提交时携带token,后台删除redis中的token,没有检查到token说明重复提交了