消息队列
为什么使用消息队列?
解耦、异步、削峰
消除队列有什么缺点?
- 系统可用性降低,系统引入的外部依赖越多,越容易挂掉
- 提高系统复杂度,要考虑消息幂等性与处理消息丢失问题
- 一致性问题,部分子系统处理消息失败了
kafka、ActiveMQ、RabbitMQ、RocketMQ优缺点?
- RabbitMQ延迟最低
- RocketMQ能支持大量topic,而Kafka的topic从几十到几百时,吞吐量会大幅下降
- 可用性:Kafka>RabbitMQ>ActiveMQ=RocketMQ
- 可靠性上ActiveMQ有较低的概率丢失数据
- 建议使用RabbitMQ,它的社区活跃度高。大数据领域的实时计算、日志采集等建议Kafka
RabbitMQ
它是基于主从(非分布式)做高可用性的。它有三种模式:单机模式、普通集群模式、镜像集群模式
普通集群模式无高可用性,你创建的queue,只会放在一个rabbitmq实例上,但是每个实例都同步queue的元数据,通过元数据,可以找到queue所在实例。当你消费时,如果连接到另外一个实例,那么那个实例会从queue所在实例上的拉取数据过来。这方案主要是提高吞吐量,就是让集群中多个节点来服务某个queue的读写操作
镜像集群模式(高可用性),每个rabbitmq节点都有某个queue的一个完整镜像。但它带来两个缺点:
- 性能开销大,消息需要要同步到所有机器上
- 没有办法线程扩展你的queue,因为一个queue的全部消息都放在一台机器上
Kafka
它是天然的分布式消息队列,就是一个topic数据,分散放在多个机器上,每个机器存放部分数据。
写数据时,生产者写leader,然后leader将数据落地本地磁盘,其它follower自己主动从leader来pull数据,一旦所有follower同步好数据,发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。
消费时,只会从leader去读,但是只有当一个消息已经被所有follower都同步成功返回ack时,这个消息才会被消费者读取。
如何保证MQ的幂等性?
产生原因,如kafka为每条数据分配一个offset,代表数据的序号,如果消费者消费了offset=153的这条数据,准备提交给broker,此时消费者重启了。消息过的数据的offset并没有提交,kafka就不知道你已经消费了,下次仍会投递这条消息。
解决方案,消息加上一个全局唯一的id,保存到redis中,下次去redis中查询是否消费过。
如何保证消息的可靠性传输,如何处理消息丢失问题?
- 对于rabbitmq:
- 生产者开启confim机制
- 中间件rabbitmq开启持久化机制,设置持久化有两个步骤,一是创建queue时设置为持久化,保证rabbitmq持久化queue的元数据,二是发送消息时将消息的deliveryMode设置为2,就是将消息设置为持久化。
- 消费者使用手动ack机制
- 对于kafka,存在kafka的leader机器宕机了,将follower切换为leader之后,出现数据丢失了,原因是follower刚好还有此数据没有同步完成。一般要求起码设置如下4个参数,保证kafka broker端可以保证在leader所在broker发生故障进行切换时,数据不会丢失:
- topic设置repliaction.factor值大于1,要求每个partition必须至少有2个副本
- 在kafka服务端设置min.insync.replicas大于1,要求一个leader至少感知到有至少一个follower还跟自己保持联系
- producer端设置acks=all,写入所有replica之后,才能认为是写成功了
- producer端设置retries=MAX,要求一旦写入失败,就无限重试
如何保证MQ消息的顺序性?
kafka在生产者写的时候可指定一个key,如订单id作为key,那么一定会被发到同一个partition中去,这个partition中数据一定有顺序的。一个partition需要由同一个消费者线程消费保证消费顺序不被打乱
rabbitmq可用consumer的内存队列做排队,然后分发给不同的worker来处理
如何解决消息队列的延时以及过期失效问题?
写一个临时的分发数据的程序,将消息分发到更多的queue上,然后用部署更多的consumer消费所有queue上的消息。即将queue和consumer都扩大了。如果消息在积压过程中失效了,就只能在深夜使用低峰期时将丢失的数据查出来,重新灌入到mq中去。如果mq导致磁盘满了,无法做其它操作,就只能消费一个丢弃一个,然后到了晚上再补数据。
搜索引擎
es概念 VS db概念
| es | db |
|---|---|
| index | 数据库 |
| type | 表 |
| document | 行记录 |
| field | 字段 |
elasticsearch
搜索流程
- client发送请求到任意一个node,这node成为协调节点
- 协调节点将搜索请求转发所有的shard对应的primary shard或replica shard
- 每个shard将自己搜索结果的doc id返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果
- 最后由协调节点根据doc id,去各个节点拉取实际的document数据,返回给client
写数据
- 客户端选择一个node发送请求,这个node成为协调节点
- 协调节点对document进行路由,请请求转发对应的node(primary shard)
- 实际的node上的primary shard处理请求,然后将数据同步到replic node
- 协调节点发现primary和所有replica node都搞定之后,就返回响应结果给客户端
es原理图
- es是准实时的,数据写入1s后可搜索到
- es每隔5s写入日志文件,可能丢失5s数据
es在十亿级别的数据如何提高查询效率
- es的jvm不要分配太大,留足够的内存给filesystem cache,如果查询数据都在filesystem cache性能就能提高很多(如每次查询5~10s,放在filesystem cache就是50ms)。
- 只存要检索的数据到es中,使数据少,都能放到filesystem cache中。其它展示数据可以放到hbase中。
- 数据预热,做一个专门的缓存预热子系统,对热数据每隔一段时间访问一下,让数据进入filesystem cache中。这样别人访问,性能一定会好很多
- 冷热分离,将冷数据放到另一个索引中。这样可确保热数据在被预热之后,尽量都让他们在filesystem os cache里,不被冷数据刷掉
- 尽量不要让es做复杂的关联查询,尽量在document设计时,写入的时候就能完成
- es的分页越翻到后面越慢。解决方案:
- 不允许深度分页
- 使用scroll api或search_after,缺点是不能随意跳到任何一页
Redis
redis过期策略:定期删除+惰性删除
key过期不是立刻删除的,如果此时大量过期key堆积在内存里,导致redis内存耗尽,则走内存淘汰机制
redis内存淘汰默认是最近最少使用算法
缓存雪崩解决方案
产生原因redis挂了或热点数据大量同时过期,如果是后者需要通过让热点数据在不同时间段失效来解决。
事前:redis cluster保证高可用
事中:本地ehcache缓存+hystrix限流和降级,避免mysql被打死
事后:redis持久化,一旦重启能从磁盘上加载数据
缓存穿透
缓存穿透容易被人利用恶意攻击,黑客针对不存在的数据发起大量请求,导致请求都打在了mysql中,可写一个空值到缓存然后设置一个过期时间,可以让下次同一个key查询请求直接走缓存
缓存击穿
某个key访问非常频繁,当key失效时,大量请求走mysql。可基于redis或zookeeper实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求能直接走缓存。
MySQL
如何解决主从同步延时与数据丢失问题
如果从库还没有把主库数据同步过来,主库宕机了就会产生数据丢失,可通过半同步复制解决,即让主库接收到至少一个从库的ack之后才会认为写操作成功
主从数据延时问题,解决办法有:
- 并行复制,从库开启多个线程并行读取relay log中不同库的日志,然后并行重放不同库的日志,这是库级别的并行
- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计
- 写代码的同学,要注意,插入数据时立马查询可能查不到,如果写代码逻辑是先插入一条数据,再把它查出来,然后更新这条数据。可能会因无法立即查询出来而更新失败
- 如果确实要插入数据要立马能查询到,可对这个查询设置直连主库。不过,此时读写分离就失去意义了
高并发
如何设计一个高并发系统
- 系统拆分减少压力
- 缓存提高读请求并发量
- MQ削峰限流提高请求并发量
- 分表分库提高sql性能
- 读写分离提高减缓数据库压力
- 天然分布式的es支撑数据库的部分普通查询