mirror of
https://github.com/doocs/advanced-java.git
synced 2025-04-17 14:00:39 +08:00
feat: add markdown formatter
This commit is contained in:
parent
c4cad8a09e
commit
d1e7119c84
README.mdsummary.md
docs
big-data
README.mdcount-different-phone-numbers.mdfind-a-number-if-exists.mdfind-common-urls.mdfind-hotest-query-string.mdfind-mid-value-in-500-millions.mdfind-no-repeat-number.mdfind-rank-top-500-numbers.mdfind-top-1-ip.mdfind-top-100-words.mdsort-the-query-strings-by-counts.md
distributed-system
README.mddistributed-lock-redis-vs-zookeeper.mddistributed-session.mddistributed-system-idempotency.mddistributed-system-interview.mddistributed-system-request-sequence.mddistributed-transaction.mddubbo-load-balancing.mddubbo-operating-principle.mddubbo-rpc-design.mddubbo-serialization-protocol.mddubbo-service-management.mddubbo-spi.mdwhy-dubbo.mdzookeeper-application-scenarios.md
extra-page
high-concurrency
README.mddatabase-shard-dynamic-expand.mddatabase-shard-global-id-generate.mddatabase-shard-method.mddatabase-shard.mdes-architecture.mdes-introduction.mdes-optimizing-query-performance.mdes-production-cluster.mdes-write-query-search.mdhigh-concurrency-design.mdhow-to-ensure-high-availability-of-message-queues.mdhow-to-ensure-high-concurrency-and-high-availability-of-redis.mdhow-to-ensure-that-messages-are-not-repeatedly-consumed.mdhow-to-ensure-the-order-of-messages.mdhow-to-ensure-the-reliable-transmission-of-messages.mdhuifer-how-to-limit-current.mdmq-design.mdmq-interview.mdmq-time-delay-and-expired-failure.mdmysql-read-write-separation.mdredis-caching-avalanche-and-caching-penetration.mdredis-cas.mdredis-cluster.mdredis-consistence.mdredis-data-types.mdredis-expiration-policies-and-lru.mdredis-master-slave.mdredis-persistence.mdredis-production-environment.mdredis-sentinel.mdredis-single-thread-model.mdwhy-cache.mdwhy-mq.md
micro-services
README.mdchoose-microservice-deployment-strategy.mdevent-driven-data-management-for-microservices.mdhow-eureka-enable-service-discovery-and-service-registration.mdhuifer-micro-service-governance.mdhuifer-micro-services-technology-stack.mdhuifer-what's-microservice-how-to-communicate.mdmicroservices-introduction.mdmigrating-from-a-monolithic-architecture-to-a-microservices-architecture.md
202
README.md
202
README.md
@ -19,149 +19,174 @@
|
||||
|
||||
[另外,我还将在这里更新内容,感兴趣的朋友可以进来看看](/docs/extra-page/subscriptions-for-doocs.md),深入源码学习请关注[“源码猎人”项目](https://github.com/doocs/source-code-hunter),LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》笔面试高频题题解,尽在 [leetcode 项目](https://github.com/doocs/leetcode)!
|
||||
|
||||
- Netlify: https://adjava.netlify.app
|
||||
- Gitee Pages: https://doocs.gitee.io/advanced-java
|
||||
- GitHub Pages: https://doocs.github.io/advanced-java
|
||||
* Netlify: https://adjava.netlify.app
|
||||
* Gitee Pages: https://doocs.gitee.io/advanced-java
|
||||
* GitHub Pages: https://doocs.github.io/advanced-java
|
||||
|
||||
## 高并发架构
|
||||
|
||||
### [消息队列](./docs/high-concurrency/mq-interview.md)
|
||||
- [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](./docs/high-concurrency/why-mq.md)
|
||||
- [如何保证消息队列的高可用?](./docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
|
||||
- [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](./docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
|
||||
- [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](./docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)
|
||||
- [如何保证消息的顺序性?](./docs/high-concurrency/how-to-ensure-the-order-of-messages.md)
|
||||
- [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](./docs/high-concurrency/mq-time-delay-and-expired-failure.md)
|
||||
- [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](./docs/high-concurrency/mq-design.md)
|
||||
|
||||
* [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](./docs/high-concurrency/why-mq.md)
|
||||
* [如何保证消息队列的高可用?](./docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
|
||||
* [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](./docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
|
||||
* [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](./docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)
|
||||
* [如何保证消息的顺序性?](./docs/high-concurrency/how-to-ensure-the-order-of-messages.md)
|
||||
* [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](./docs/high-concurrency/mq-time-delay-and-expired-failure.md)
|
||||
* [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](./docs/high-concurrency/mq-design.md)
|
||||
|
||||
### [搜索引擎](./docs/high-concurrency/es-introduction.md)
|
||||
- [es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)?](./docs/high-concurrency/es-architecture.md)
|
||||
- [es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗?](./docs/high-concurrency/es-write-query-search.md)
|
||||
- [es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](./docs/high-concurrency/es-optimizing-query-performance.md)
|
||||
- [es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](./docs/high-concurrency/es-production-cluster.md)
|
||||
|
||||
* [es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)?](./docs/high-concurrency/es-architecture.md)
|
||||
* [es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗?](./docs/high-concurrency/es-write-query-search.md)
|
||||
* [es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](./docs/high-concurrency/es-optimizing-query-performance.md)
|
||||
* [es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](./docs/high-concurrency/es-production-cluster.md)
|
||||
|
||||
### 缓存
|
||||
- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](./docs/high-concurrency/why-cache.md)
|
||||
- [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](./docs/high-concurrency/redis-single-thread-model.md)
|
||||
- [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](./docs/high-concurrency/redis-data-types.md)
|
||||
- [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](./docs/high-concurrency/redis-expiration-policies-and-lru.md)
|
||||
- [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](./docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
|
||||
- [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](./docs/high-concurrency/redis-persistence.md)
|
||||
- [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](./docs/high-concurrency/redis-cluster.md)
|
||||
- [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](./docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
- [如何保证缓存与数据库的双写一致性?](./docs/high-concurrency/redis-consistence.md)
|
||||
- [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](./docs/high-concurrency/redis-cas.md)
|
||||
- [生产环境中的 Redis 是怎么部署的?](./docs/high-concurrency/redis-production-environment.md)
|
||||
|
||||
* [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](./docs/high-concurrency/why-cache.md)
|
||||
* [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](./docs/high-concurrency/redis-single-thread-model.md)
|
||||
* [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](./docs/high-concurrency/redis-data-types.md)
|
||||
* [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](./docs/high-concurrency/redis-expiration-policies-and-lru.md)
|
||||
* [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](./docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
|
||||
* [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](./docs/high-concurrency/redis-persistence.md)
|
||||
* [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](./docs/high-concurrency/redis-cluster.md)
|
||||
* [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](./docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
* [如何保证缓存与数据库的双写一致性?](./docs/high-concurrency/redis-consistence.md)
|
||||
* [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](./docs/high-concurrency/redis-cas.md)
|
||||
* [生产环境中的 Redis 是怎么部署的?](./docs/high-concurrency/redis-production-environment.md)
|
||||
|
||||
### 分库分表
|
||||
- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](./docs/high-concurrency/database-shard.md)
|
||||
- [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](./docs/high-concurrency/database-shard-method.md)
|
||||
- [如何设计可以动态扩容缩容的分库分表方案?](./docs/high-concurrency/database-shard-dynamic-expand.md)
|
||||
- [分库分表之后,id 主键如何处理?](./docs/high-concurrency/database-shard-global-id-generate.md)
|
||||
|
||||
* [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](./docs/high-concurrency/database-shard.md)
|
||||
* [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](./docs/high-concurrency/database-shard-method.md)
|
||||
* [如何设计可以动态扩容缩容的分库分表方案?](./docs/high-concurrency/database-shard-dynamic-expand.md)
|
||||
* [分库分表之后,id 主键如何处理?](./docs/high-concurrency/database-shard-global-id-generate.md)
|
||||
|
||||
### 读写分离
|
||||
- [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](./docs/high-concurrency/mysql-read-write-separation.md)
|
||||
|
||||
* [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](./docs/high-concurrency/mysql-read-write-separation.md)
|
||||
|
||||
### 高并发系统
|
||||
- [如何设计一个高并发系统?](./docs/high-concurrency/high-concurrency-design.md)
|
||||
|
||||
* [如何设计一个高并发系统?](./docs/high-concurrency/high-concurrency-design.md)
|
||||
|
||||
## 分布式系统
|
||||
|
||||
### [面试连环炮](./docs/distributed-system/distributed-system-interview.md)
|
||||
|
||||
### 系统拆分
|
||||
- [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](./docs/distributed-system/why-dubbo.md)
|
||||
|
||||
* [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](./docs/distributed-system/why-dubbo.md)
|
||||
|
||||
### 分布式服务框架
|
||||
- [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](./docs/distributed-system/dubbo-operating-principle.md)
|
||||
- [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](./docs/distributed-system/dubbo-serialization-protocol.md)
|
||||
- [Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](./docs/distributed-system/dubbo-load-balancing.md)
|
||||
- [Dubbo 的 spi 思想是什么?](./docs/distributed-system/dubbo-spi.md)
|
||||
- [如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](./docs/distributed-system/dubbo-service-management.md)
|
||||
- [分布式服务接口的幂等性如何设计(比如不能重复扣款)?](./docs/distributed-system/distributed-system-idempotency.md)
|
||||
- [分布式服务接口请求的顺序性如何保证?](./docs/distributed-system/distributed-system-request-sequence.md)
|
||||
- [如何自己设计一个类似 Dubbo 的 RPC 框架?](./docs/distributed-system/dubbo-rpc-design.md)
|
||||
|
||||
* [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](./docs/distributed-system/dubbo-operating-principle.md)
|
||||
* [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](./docs/distributed-system/dubbo-serialization-protocol.md)
|
||||
* [Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](./docs/distributed-system/dubbo-load-balancing.md)
|
||||
* [Dubbo 的 spi 思想是什么?](./docs/distributed-system/dubbo-spi.md)
|
||||
* [如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](./docs/distributed-system/dubbo-service-management.md)
|
||||
* [分布式服务接口的幂等性如何设计(比如不能重复扣款)?](./docs/distributed-system/distributed-system-idempotency.md)
|
||||
* [分布式服务接口请求的顺序性如何保证?](./docs/distributed-system/distributed-system-request-sequence.md)
|
||||
* [如何自己设计一个类似 Dubbo 的 RPC 框架?](./docs/distributed-system/dubbo-rpc-design.md)
|
||||
|
||||
### 分布式锁
|
||||
- [Zookeeper 都有哪些应用场景?](./docs/distributed-system/zookeeper-application-scenarios.md)
|
||||
- [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](./docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
|
||||
|
||||
* [Zookeeper 都有哪些应用场景?](./docs/distributed-system/zookeeper-application-scenarios.md)
|
||||
* [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](./docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
|
||||
|
||||
### 分布式事务
|
||||
- [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](./docs/distributed-system/distributed-transaction.md)
|
||||
|
||||
* [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](./docs/distributed-system/distributed-transaction.md)
|
||||
|
||||
### 分布式会话
|
||||
- [集群部署时的分布式 Session 如何实现?](./docs/distributed-system/distributed-session.md)
|
||||
|
||||
* [集群部署时的分布式 Session 如何实现?](./docs/distributed-system/distributed-session.md)
|
||||
|
||||
## 高可用架构
|
||||
- [Hystrix 介绍](./docs/high-availability/hystrix-introduction.md)
|
||||
- [电商网站详情页系统架构](./docs/high-availability/e-commerce-website-detail-page-architecture.md)
|
||||
- [Hystrix 线程池技术实现资源隔离](./docs/high-availability/hystrix-thread-pool-isolation.md)
|
||||
- [Hystrix 信号量机制实现资源隔离](./docs/high-availability/hystrix-semphore-isolation.md)
|
||||
- [Hystrix 隔离策略细粒度控制](./docs/high-availability/hystrix-execution-isolation.md)
|
||||
- [深入 Hystrix 执行时内部原理](./docs/high-availability/hystrix-process.md)
|
||||
- [基于 request cache 请求缓存技术优化批量商品数据查询接口](./docs/high-availability/hystrix-request-cache.md)
|
||||
- [基于本地缓存的 fallback 降级机制](./docs/high-availability/hystrix-fallback.md)
|
||||
- [深入 Hystrix 断路器执行原理](./docs/high-availability/hystrix-circuit-breaker.md)
|
||||
- [深入 Hystrix 线程池隔离与接口限流](./docs/high-availability/hystrix-thread-pool-current-limiting.md)
|
||||
- [基于 timeout 机制为服务接口调用超时提供安全保护](./docs/high-availability/hystrix-timeout.md)
|
||||
|
||||
* [Hystrix 介绍](./docs/high-availability/hystrix-introduction.md)
|
||||
* [电商网站详情页系统架构](./docs/high-availability/e-commerce-website-detail-page-architecture.md)
|
||||
* [Hystrix 线程池技术实现资源隔离](./docs/high-availability/hystrix-thread-pool-isolation.md)
|
||||
* [Hystrix 信号量机制实现资源隔离](./docs/high-availability/hystrix-semphore-isolation.md)
|
||||
* [Hystrix 隔离策略细粒度控制](./docs/high-availability/hystrix-execution-isolation.md)
|
||||
* [深入 Hystrix 执行时内部原理](./docs/high-availability/hystrix-process.md)
|
||||
* [基于 request cache 请求缓存技术优化批量商品数据查询接口](./docs/high-availability/hystrix-request-cache.md)
|
||||
* [基于本地缓存的 fallback 降级机制](./docs/high-availability/hystrix-fallback.md)
|
||||
* [深入 Hystrix 断路器执行原理](./docs/high-availability/hystrix-circuit-breaker.md)
|
||||
* [深入 Hystrix 线程池隔离与接口限流](./docs/high-availability/hystrix-thread-pool-current-limiting.md)
|
||||
* [基于 timeout 机制为服务接口调用超时提供安全保护](./docs/high-availability/hystrix-timeout.md)
|
||||
|
||||
### 高可用系统
|
||||
- 如何设计一个高可用系统?
|
||||
|
||||
* 如何设计一个高可用系统?
|
||||
|
||||
### 限流
|
||||
- [如何限流?在工作中是怎么做的?说一下具体的实现?](/docs/high-concurrency/huifer-how-to-limit-current.md)
|
||||
|
||||
* [如何限流?在工作中是怎么做的?说一下具体的实现?](/docs/high-concurrency/huifer-how-to-limit-current.md)
|
||||
|
||||
### 熔断
|
||||
- 如何进行熔断?
|
||||
- 熔断框架都有哪些?具体实现原理知道吗?
|
||||
- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](./docs/high-availability/sentinel-vs-hystrix.md)
|
||||
|
||||
* 如何进行熔断?
|
||||
* 熔断框架都有哪些?具体实现原理知道吗?
|
||||
* [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](./docs/high-availability/sentinel-vs-hystrix.md)
|
||||
|
||||
### 降级
|
||||
- 如何进行降级?
|
||||
|
||||
* 如何进行降级?
|
||||
|
||||
## 微服务架构
|
||||
- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
|
||||
- [关于微服务架构的描述](./docs/micro-services/microservices-introduction.md)
|
||||
- [从单体式架构迁移到微服务架构](./docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md)
|
||||
- [微服务的事件驱动数据管理](./docs/micro-services/event-driven-data-management-for-microservices.md)
|
||||
- [选择微服务部署策略](./docs/micro-services/choose-microservice-deployment-strategy.md)
|
||||
|
||||
* [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
|
||||
* [关于微服务架构的描述](./docs/micro-services/microservices-introduction.md)
|
||||
* [从单体式架构迁移到微服务架构](./docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md)
|
||||
* [微服务的事件驱动数据管理](./docs/micro-services/event-driven-data-management-for-microservices.md)
|
||||
* [选择微服务部署策略](./docs/micro-services/choose-microservice-deployment-strategy.md)
|
||||
|
||||
### Spring Cloud 微服务架构
|
||||
- [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/huifer-what's-microservice-how-to-communicate.md)
|
||||
- Spring Cloud 和 Dubbo 有哪些区别?
|
||||
- Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
|
||||
- 什么是服务熔断?什么是服务降级?
|
||||
- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
|
||||
- [你所知道的微服务技术栈都有哪些?](/docs/micro-services/huifer-micro-services-technology-stack.md)
|
||||
- [微服务治理策略](/docs/micro-services/huifer-micro-service-governance.md)
|
||||
- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
|
||||
- [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md)
|
||||
- ......
|
||||
|
||||
* [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/huifer-what's-microservice-how-to-communicate.md)
|
||||
* Spring Cloud 和 Dubbo 有哪些区别?
|
||||
* Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
|
||||
* 什么是服务熔断?什么是服务降级?
|
||||
* 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
|
||||
* [你所知道的微服务技术栈都有哪些?](/docs/micro-services/huifer-micro-services-technology-stack.md)
|
||||
* [微服务治理策略](/docs/micro-services/huifer-micro-service-governance.md)
|
||||
* Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
|
||||
* [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md)
|
||||
* ......
|
||||
|
||||
## 海量数据处理
|
||||
- [如何从大量的 URL 中找出相同的 URL?](./docs/big-data/find-common-urls.md)
|
||||
- [如何从大量数据中找出高频词?](./docs/big-data/find-top-100-words.md)
|
||||
- [如何找出某一天访问百度网站最多的 IP?](./docs/big-data/find-top-1-ip.md)
|
||||
- [如何在大量的数据中找出不重复的整数?](./docs/big-data/find-no-repeat-number.md)
|
||||
- [如何在大量的数据中判断一个数是否存在?](./docs/big-data/find-a-number-if-exists.md)
|
||||
- [如何查询最热门的查询串?](./docs/big-data/find-hotest-query-string.md)
|
||||
- [如何统计不同电话号码的个数?](./docs/big-data/count-different-phone-numbers.md)
|
||||
- [如何从 5 亿个数中找出中位数?](./docs/big-data/find-mid-value-in-500-millions.md)
|
||||
- [如何按照 query 的频度排序?](./docs/big-data/sort-the-query-strings-by-counts.md)
|
||||
- [如何找出排名前 500 的数?](./docs/big-data/find-rank-top-500-numbers.md)
|
||||
|
||||
* [如何从大量的 URL 中找出相同的 URL?](./docs/big-data/find-common-urls.md)
|
||||
* [如何从大量数据中找出高频词?](./docs/big-data/find-top-100-words.md)
|
||||
* [如何找出某一天访问百度网站最多的 IP?](./docs/big-data/find-top-1-ip.md)
|
||||
* [如何在大量的数据中找出不重复的整数?](./docs/big-data/find-no-repeat-number.md)
|
||||
* [如何在大量的数据中判断一个数是否存在?](./docs/big-data/find-a-number-if-exists.md)
|
||||
* [如何查询最热门的查询串?](./docs/big-data/find-hotest-query-string.md)
|
||||
* [如何统计不同电话号码的个数?](./docs/big-data/count-different-phone-numbers.md)
|
||||
* [如何从 5 亿个数中找出中位数?](./docs/big-data/find-mid-value-in-500-millions.md)
|
||||
* [如何按照 query 的频度排序?](./docs/big-data/sort-the-query-strings-by-counts.md)
|
||||
* [如何找出排名前 500 的数?](./docs/big-data/find-rank-top-500-numbers.md)
|
||||
|
||||
---
|
||||
|
||||
## 贡献者
|
||||
|
||||
感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
<!-- ALL-CONTRIBUTORS-LIST: START - Do not remove or modify this section -->
|
||||
|
||||
<a href="https://opencollective.com/doocs/contributors.svg?width=890&button=true"><img src="https://opencollective.com/doocs/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
<!-- ALL-CONTRIBUTORS-LIST: END -->
|
||||
|
||||
## 公众号
|
||||
|
||||
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
<td align="center" style="width: 200px;">
|
||||
<a href="https://github.com/doocs">
|
||||
@ -176,4 +201,5 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
|
||||
</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
@ -1,18 +1,20 @@
|
||||
# 海量数据处理
|
||||
- [如何从大量的 URL 中找出相同的 URL?](/docs/big-data/find-common-urls.md)
|
||||
- [如何从大量数据中找出高频词?](/docs/big-data/find-top-100-words.md)
|
||||
- [如何找出某一天访问百度网站最多的 IP?](/docs/big-data/find-top-1-ip.md)
|
||||
- [如何在大量的数据中找出不重复的整数?](/docs/big-data/find-no-repeat-number.md)
|
||||
- [如何在大量的数据中判断一个数是否存在?](/docs/big-data/find-a-number-if-exists.md)
|
||||
- [如何查询最热门的查询串?](/docs/big-data/find-hotest-query-string.md)
|
||||
- [如何统计不同电话号码的个数?](/docs/big-data/count-different-phone-numbers.md)
|
||||
- [如何从 5 亿个数中找出中位数?](/docs/big-data/find-mid-value-in-500-millions.md)
|
||||
- [如何按照 query 的频度排序?](/docs/big-data/sort-the-query-strings-by-counts.md)
|
||||
- [如何找出排名前 500 的数?](/docs/big-data/find-rank-top-500-numbers.md)
|
||||
|
||||
* [如何从大量的 URL 中找出相同的 URL?](/docs/big-data/find-common-urls.md)
|
||||
* [如何从大量数据中找出高频词?](/docs/big-data/find-top-100-words.md)
|
||||
* [如何找出某一天访问百度网站最多的 IP?](/docs/big-data/find-top-1-ip.md)
|
||||
* [如何在大量的数据中找出不重复的整数?](/docs/big-data/find-no-repeat-number.md)
|
||||
* [如何在大量的数据中判断一个数是否存在?](/docs/big-data/find-a-number-if-exists.md)
|
||||
* [如何查询最热门的查询串?](/docs/big-data/find-hotest-query-string.md)
|
||||
* [如何统计不同电话号码的个数?](/docs/big-data/count-different-phone-numbers.md)
|
||||
* [如何从 5 亿个数中找出中位数?](/docs/big-data/find-mid-value-in-500-millions.md)
|
||||
* [如何按照 query 的频度排序?](/docs/big-data/sort-the-query-strings-by-counts.md)
|
||||
* [如何找出排名前 500 的数?](/docs/big-data/find-rank-top-500-numbers.md)
|
||||
|
||||
---
|
||||
|
||||
## 公众号
|
||||
|
||||
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
<table>
|
||||
@ -30,4 +32,4 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
|
||||
</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
@ -1,9 +1,11 @@
|
||||
## 如何统计不同电话号码的个数?
|
||||
|
||||
### 题目描述
|
||||
|
||||
已知某个文件内包含一些电话号码,每个号码为 8 位数字,统计不同号码的个数。
|
||||
|
||||
### 解答思路
|
||||
|
||||
这道题本质还是求解**数据重复**的问题,对于这类问题,一般首先考虑位图法。
|
||||
|
||||
对于本题,8 位电话号码可以表示的号码个数为 10<sup>8</sup> 个,即 1 亿个。我们每个号码用一个 bit 来表示,则总共需要 1 亿个 bit,内存占用约 100M。
|
||||
@ -13,4 +15,5 @@
|
||||
申请一个位图数组,长度为 1 亿,初始化为 0。然后遍历所有电话号码,把号码对应的位图中的位置置为 1。遍历完成后,如果 bit 为 1,则表示这个电话号码在文件中存在,否则不存在。bit 值为 1 的数量即为 不同电话号码的个数。
|
||||
|
||||
### 方法总结
|
||||
求解数据重复问题,记得考虑位图法。
|
||||
|
||||
求解数据重复问题,记得考虑位图法。
|
||||
|
@ -1,6 +1,7 @@
|
||||
## 如何在大量的数据中判断一个数是否存在?
|
||||
|
||||
### 题目描述
|
||||
|
||||
给定 40 亿个不重复的没排过序的 unsigned int 型整数,然后再给定一个数,如何快速判断这个数是否在这 40 亿个整数当中?
|
||||
|
||||
### 解答思路
|
||||
@ -9,9 +10,11 @@
|
||||
依然可以用分治法解决,方法与前面类似,就不再次赘述了。
|
||||
|
||||
#### 方法二:位图法
|
||||
40 亿个不重复整数,我们用 40 亿个 bit 来表示,初始位均为 0,那么总共需要内存:4,000,000,000b≈512M。
|
||||
|
||||
40 亿个不重复整数,我们用 40 亿个 bit 来表示,初始位均为 0,那么总共需要内存:4, 000, 000, 000b≈512M。
|
||||
|
||||
我们读取这 40 亿个整数,将对应的 bit 设置为 1。接着读取要查询的数,查看相应位是否为 1,如果为 1 表示存在,如果为 0 表示不存在。
|
||||
|
||||
### 方法总结
|
||||
**判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。
|
||||
|
||||
**判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。
|
||||
|
@ -1,21 +1,24 @@
|
||||
## 如何从大量的 URL 中找出相同的 URL?
|
||||
|
||||
### 题目描述
|
||||
|
||||
给定 a、b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G。请找出 a、b 两个文件共同的 URL。
|
||||
|
||||
### 解答思路
|
||||
|
||||
每个 URL 占 64B,那么 50 亿个 URL占用的空间大小约为 320GB。
|
||||
|
||||
> 5,000,000,000 * 64B ≈ 5GB * 64 = 320GB
|
||||
> 5, 000, 000, 000 * 64B ≈ 5GB * 64 = 320GB
|
||||
|
||||
由于内存大小只有 4G,因此,我们不可能一次性把所有 URL 加载到内存中处理。对于这种类型的题目,一般采用**分治策略**,即:把一个文件中的 URL 按照某个特征划分为多个小文件,使得每个小文件大小不超过 4G,这样就可以把这个小文件读到内存中进行处理了。
|
||||
|
||||
**思路如下**:
|
||||
|
||||
首先遍历文件 a,对遍历到的 URL 求 `hash(URL) % 1000`,根据计算结果把遍历到的 URL 存储到 a<sub>0</sub>, a<sub>1</sub>, a<sub>2</sub>, ..., a<sub>999</sub>,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b<sub>0</sub>, b<sub>1</sub>, b<sub>2</sub>, ..., b<sub>999</sub> 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a<sub>0</sub> 对应 b<sub>0</sub>, ..., a<sub>999</sub> 对应 b<sub>999</sub>,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。
|
||||
首先遍历文件 a,对遍历到的 URL 求 `hash(URL) % 1000` ,根据计算结果把遍历到的 URL 存储到 a<sub>0</sub>, a<sub>1</sub>, a<sub>2</sub>, ..., a<sub>999</sub>,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b<sub>0</sub>, b<sub>1</sub>, b<sub>2</sub>, ..., b<sub>999</sub> 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a<sub>0</sub> 对应 b<sub>0</sub>, ..., a<sub>999</sub> 对应 b<sub>999</sub>,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。
|
||||
|
||||
接着遍历 a<sub>i</sub>( `i∈[0,999]`),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
|
||||
接着遍历 a<sub>i</sub>( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
|
||||
|
||||
### 方法总结
|
||||
|
||||
1. 分而治之,进行哈希取余;
|
||||
2. 对每个子文件进行 HashSet 统计。
|
||||
2. 对每个子文件进行 HashSet 统计。
|
||||
|
@ -1,14 +1,17 @@
|
||||
## 如何查询最热门的查询串?
|
||||
|
||||
### 题目描述
|
||||
|
||||
搜索引擎会通过日志文件把用户每次检索使用的所有查询串都记录下来,每个查询串的长度不超过 255 字节。
|
||||
|
||||
假设目前有 1000w 个记录(这些查询串的重复度比较高,虽然总数是 1000w,但如果除去重复后,则不超过 300w 个)。请统计最热门的 10 个查询串,要求使用的内存不能超过 1G。(一个查询串的重复度越高,说明查询它的用户越多,也就越热门。)
|
||||
|
||||
### 解答思路
|
||||
|
||||
每个查询串最长为 255B,1000w 个串需要占用 约 2.55G 内存,因此,我们无法将所有字符串全部读入到内存中处理。
|
||||
|
||||
#### 方法一:分治法
|
||||
|
||||
分治法依然是一个非常实用的方法。
|
||||
|
||||
划分为多个小文件,保证单个小文件中的字符串能被直接加载到内存中处理,然后求出每个文件中出现次数最多的 10 个字符串;最后通过一个小顶堆统计出所有文件中出现最多的 10 个字符串。
|
||||
@ -16,17 +19,19 @@
|
||||
方法可行,但不是最好,下面介绍其他方法。
|
||||
|
||||
#### 方法二:HashMap 法
|
||||
|
||||
虽然字符串总数比较多,但去重后不超过 300w,因此,可以考虑把所有字符串及出现次数保存在一个 HashMap 中,所占用的空间为 300w*(255+4)≈777M(其中,4表示整数占用的4个字节)。由此可见,1G 的内存空间完全够用。
|
||||
|
||||
**思路如下**:
|
||||
|
||||
首先,遍历字符串,若不在 map 中,直接存入 map,value 记为 1;若在 map 中,则把对应的 value 加 1,这一步时间复杂度 `O(N)`。
|
||||
首先,遍历字符串,若不在 map 中,直接存入 map,value 记为 1;若在 map 中,则把对应的 value 加 1,这一步时间复杂度 `O(N)` 。
|
||||
|
||||
接着遍历 map,构建一个 10 个元素的小顶堆,若遍历到的字符串的出现次数大于堆顶字符串的出现次数,则进行替换,并将堆调整为小顶堆。
|
||||
|
||||
遍历结束后,堆中 10 个字符串就是出现次数最多的字符串。这一步时间复杂度 `O(Nlog10)`。
|
||||
遍历结束后,堆中 10 个字符串就是出现次数最多的字符串。这一步时间复杂度 `O(Nlog10)` 。
|
||||
|
||||
#### 方法三:前缀树法
|
||||
|
||||
方法二使用了 HashMap 来统计次数,当这些字符串有大量相同前缀时,可以考虑使用前缀树来统计字符串出现的次数,树的结点保存字符串出现次数,0 表示没有出现。
|
||||
|
||||
**思路如下**:
|
||||
@ -36,4 +41,5 @@
|
||||
最后依然使用小顶堆来对字符串的出现次数进行排序。
|
||||
|
||||
### 方法总结
|
||||
|
||||
前缀树经常被用来统计字符串的出现次数。它的另外一个大的用途是字符串查找,判断是否有重复的字符串等。
|
||||
|
@ -1,17 +1,20 @@
|
||||
## 如何从 5 亿个数中找出中位数?
|
||||
|
||||
### 题目描述
|
||||
|
||||
从 5 亿个数中找出中位数。数据排序后,位置在最中间的数就是中位数。当样本数为奇数时,中位数为 第 `(N+1)/2` 个数;当样本数为偶数时,中位数为 第 `N/2` 个数与第 `1+N/2` 个数的均值。
|
||||
|
||||
### 解答思路
|
||||
如果这道题没有内存大小限制,则可以把所有数读到内存中排序后找出中位数。但是最好的排序算法的时间复杂度都为 `O(NlogN)`。这里使用其他方法。
|
||||
|
||||
如果这道题没有内存大小限制,则可以把所有数读到内存中排序后找出中位数。但是最好的排序算法的时间复杂度都为 `O(NlogN)` 。这里使用其他方法。
|
||||
|
||||
#### 方法一:双堆法
|
||||
|
||||
维护两个堆,一个大顶堆,一个小顶堆。大顶堆中最大的数**小于等于**小顶堆中最小的数;保证这两个堆中的元素个数的差不超过 1。
|
||||
|
||||
若数据总数为**偶数**,当这两个堆建好之后,**中位数就是这两个堆顶元素的平均值**。当数据总数为**奇数**时,根据两个堆的大小,**中位数一定在数据多的堆的堆顶**。
|
||||
|
||||
```java
|
||||
``` java
|
||||
class MedianFinder {
|
||||
|
||||
private PriorityQueue<Integer> maxHeap;
|
||||
@ -55,6 +58,7 @@ class MedianFinder {
|
||||
以上这种方法,需要把所有数据都加载到内存中。当数据量很大时,就不能这样了,因此,这种方法**适用于数据量较小的情况**。5 亿个数,每个数字占用 4B,总共需要 2G 内存。如果可用内存不足 2G,就不能使用这种方法了,下面介绍另一种方法。
|
||||
|
||||
#### 方法二:分治法
|
||||
|
||||
分治法的思想是把一个大的问题逐渐转换为规模较小的问题来求解。
|
||||
|
||||
对于这道题,顺序读取这 5 亿个数字,对于读取到的数字 num,如果它对应的二进制中最高位为 1,则把这个数字写到 f1 中,否则写入 f0 中。通过这一步,可以把这 5 亿个数划分为两部分,而且 f0 中的数都大于 f1 中的数(最高位是符号位)。
|
||||
@ -68,4 +72,5 @@ class MedianFinder {
|
||||
> **注意**,当数据总数为偶数,如果划分后两个文件中的数据有相同个数,那么中位数就是数据较小的文件中的最大值与数据较大的文件中的最小值的平均值。
|
||||
|
||||
### 方法总结
|
||||
|
||||
分治法,真香!
|
||||
|
@ -1,38 +1,41 @@
|
||||
## 如何在大量的数据中找出不重复的整数?
|
||||
|
||||
### 题目描述
|
||||
|
||||
在 2.5 亿个整数中找出不重复的整数。注意:内存不足以容纳这 2.5 亿个整数。
|
||||
|
||||
### 解答思路
|
||||
|
||||
#### 方法一:分治法
|
||||
与前面的题目方法类似,先将 2.5 亿个数划分到多个小文件,用 HashSet/HashMap 找出每个小文件中不重复的整数,再合并每个子结果,即为最终结果。
|
||||
|
||||
#### 方法二:位图法
|
||||
|
||||
**位图**,就是用一个或多个 bit 来标记某个元素对应的值,而键就是该元素。采用位作为单位来存储数据,可以大大节省存储空间。
|
||||
|
||||
位图通过使用位数组来表示某些元素是否存在。它可以用于快速查找,判重,排序等。不是很清楚?我先举个小例子。
|
||||
|
||||
假设我们要对 `[0,7]` 中的 5 个元素 (6, 4, 2, 1, 5) 进行排序,可以采用位图法。0~7 范围总共有 8 个数,只需要 8bit,即 1 个字节。首先将每个位都置 0:
|
||||
|
||||
```
|
||||
```
|
||||
0 0 0 0 0 0 0 0
|
||||
```
|
||||
|
||||
然后遍历 5 个元素,首先遇到 6,那么将下标为 6 的位的 0 置为 1;接着遇到 4,把下标为 4 的位 的 0 置为 1:
|
||||
|
||||
```
|
||||
```
|
||||
0 0 0 0 1 0 1 0
|
||||
```
|
||||
|
||||
依次遍历,结束后,位数组是这样的:
|
||||
|
||||
```
|
||||
```
|
||||
0 1 1 0 1 1 1 0
|
||||
```
|
||||
|
||||
每个为 1 的位,它的下标都表示了一个数:
|
||||
|
||||
```
|
||||
```
|
||||
for i in range(8):
|
||||
if bits[i] == 1:
|
||||
print(i)
|
||||
@ -44,13 +47,14 @@ for i in range(8):
|
||||
|
||||
**那么对于这道题**,我们用 2 个 bit 来表示各个数字的状态:
|
||||
|
||||
- 00 表示这个数字没出现过;
|
||||
- 01 表示这个数字出现过一次(即为题目所找的不重复整数);
|
||||
- 10 表示这个数字出现了多次。
|
||||
* 00 表示这个数字没出现过;
|
||||
* 01 表示这个数字出现过一次(即为题目所找的不重复整数);
|
||||
* 10 表示这个数字出现了多次。
|
||||
|
||||
那么这 2<sup>32</sup> 个整数,总共所需内存为 2<sup>32</sup>*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作:
|
||||
|
||||
遍历 2.5 亿个整数,查看位图中对应的位,如果是 00,则变为 01,如果是 01 则变为 10,如果是 10 则保持不变。遍历结束后,查看位图,把对应位是 01 的整数输出即可。
|
||||
|
||||
### 方法总结
|
||||
**判断数字是否重复的问题**,位图法是一种非常高效的方法。
|
||||
|
||||
**判断数字是否重复的问题**,位图法是一种非常高效的方法。
|
||||
|
@ -1,9 +1,11 @@
|
||||
## 如何找出排名前 500 的数?
|
||||
|
||||
### 题目描述
|
||||
|
||||
有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20*500 个数中找出前 500 的数?
|
||||
|
||||
### 解答思路
|
||||
|
||||
对于 TopK 问题,最常用的方法是使用堆排序。对本题而言,假设数组降序排列,可以采用以下方法:
|
||||
|
||||
首先建立大顶堆,堆的大小为数组的个数,即为 20,把每个数组最大的值存到堆中。
|
||||
@ -14,7 +16,7 @@
|
||||
|
||||
> 为了在堆中取出一个数据后,能知道它是从哪个数组中取出的,从而可以从这个数组中取下一个值,可以把数组的指针存放到堆中,对这个指针提供比较大小的方法。
|
||||
|
||||
```java
|
||||
``` java
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Arrays;
|
||||
@ -57,7 +59,6 @@ public class DataWithSource implements Comparable<DataWithSource> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Test {
|
||||
public static int[] getTop(int[][] data) {
|
||||
int rowSize = data.length;
|
||||
@ -104,4 +105,5 @@ class Test {
|
||||
```
|
||||
|
||||
### 方法总结
|
||||
求 TopK,不妨考虑一下堆排序?
|
||||
|
||||
求 TopK,不妨考虑一下堆排序?
|
||||
|
@ -1,14 +1,17 @@
|
||||
## 如何找出某一天访问百度网站最多的 IP?
|
||||
|
||||
### 题目描述
|
||||
|
||||
现有海量日志数据保存在一个超大文件中,该文件无法直接读入内存,要求从中提取某天访问百度次数最多的那个 IP。
|
||||
|
||||
### 解答思路
|
||||
|
||||
这道题只关心某一天访问百度最多的 IP,因此,可以首先对文件进行一次遍历,把这一天访问百度 IP 的相关信息记录到一个单独的大文件中。接下来采用的方法与上一题一样,大致就是先对 IP 进行哈希映射,接着使用 HashMap 统计重复 IP 的次数,最后计算出重复次数最多的 IP。
|
||||
|
||||
> 注:这里只需要找出出现次数最多的 IP,可以不必使用堆,直接用一个变量 max 即可。
|
||||
|
||||
### 方法总结
|
||||
|
||||
1. 分而治之,进行哈希取余;
|
||||
2. 使用 HashMap 统计频数;
|
||||
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**。
|
||||
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**。
|
||||
|
@ -1,20 +1,23 @@
|
||||
## 如何从大量数据中找出高频词?
|
||||
|
||||
### 题目描述
|
||||
|
||||
有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。
|
||||
|
||||
### 解答思路
|
||||
|
||||
由于内存限制,我们依然无法直接将大文件的所有词一次读到内存中。因此,同样可以采用**分治策略**,把一个大文件分解成多个小文件,保证每个文件的大小小于 1MB,进而直接将单个小文件读取到内存中进行处理。
|
||||
|
||||
**思路如下**:
|
||||
|
||||
首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000`,将结果为 i 的词存放到文件 a<sub>i</sub> 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。
|
||||
首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000` ,将结果为 i 的词存放到文件 a<sub>i</sub> 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。
|
||||
|
||||
接着统计每个小文件中出现频数最高的 100 个词。最简单的方式是使用 HashMap 来实现。其中 key 为词,value 为该词出现的频率。具体方法是:对于遍历到的词 x,如果在 map 中不存在,则执行 `map.put(x, 1)`;若存在,则执行 `map.put(x, map.get(x)+1)`,将该词频数加 1。
|
||||
接着统计每个小文件中出现频数最高的 100 个词。最简单的方式是使用 HashMap 来实现。其中 key 为词,value 为该词出现的频率。具体方法是:对于遍历到的词 x,如果在 map 中不存在,则执行 `map.put(x, 1)` ;若存在,则执行 `map.put(x, map.get(x)+1)` ,将该词频数加 1。
|
||||
|
||||
上面我们统计了每个小文件单词出现的频数。接下来,我们可以通过维护一个**小顶堆**来找出所有词中出现频数最高的 100 个。具体方法是:依次遍历每个小文件,构建一个**小顶堆**,堆大小为 100。如果遍历到的词的出现次数大于堆顶词的出现次数,则用新词替换堆顶的词,然后重新调整为**小顶堆**,遍历结束后,小顶堆上的词就是出现频数最高的 100 个词。
|
||||
|
||||
### 方法总结
|
||||
|
||||
1. 分而治之,进行哈希取余;
|
||||
2. 使用 HashMap 统计频数;
|
||||
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**。
|
||||
3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**。
|
||||
|
@ -1,19 +1,24 @@
|
||||
## 如何按照 query 的频度排序?
|
||||
|
||||
### 题目描述
|
||||
|
||||
有 10 个文件,每个文件大小为 1G,每个文件的每一行存放的都是用户的 query,每个文件的 query 都可能重复。要求按照 query 的频度排序。
|
||||
|
||||
### 解答思路
|
||||
|
||||
如果 query 的重复度比较大,可以考虑一次性把所有 query 读入内存中处理;如果 query 的重复率不高,那么可用内存不足以容纳所有的 query,这时候就需要采用分治法或其他的方法来解决。
|
||||
|
||||
#### 方法一:HashMap 法
|
||||
|
||||
如果 query 重复率高,说明不同 query 总数比较小,可以考虑把所有的 query 都加载到内存中的 HashMap 中。接着就可以按照 query 出现的次数进行排序。
|
||||
|
||||
#### 方法二:分治法
|
||||
|
||||
分治法需要根据数据量大小以及可用内存的大小来确定问题划分的规模。对于这道题,可以顺序遍历 10 个文件中的 query,通过 Hash 函数 `hash(query) % 10` 把这些 query 划分到 10 个小文件中。之后对每个小文件使用 HashMap 统计 query 出现次数,根据次数排序并写入到零外一个单独文件中。
|
||||
|
||||
接着对所有文件按照 query 的次数进行排序,这里可以使用归并排序(由于无法把所有 query 都读入内存,因此需要使用外排序)。
|
||||
|
||||
### 方法总结
|
||||
- 内存若够,直接读入进行排序;
|
||||
- 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。
|
||||
|
||||
* 内存若够,直接读入进行排序;
|
||||
* 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。
|
||||
|
@ -1,31 +1,39 @@
|
||||
# 分布式系统
|
||||
|
||||
## [面试连环炮](/docs/distributed-system/distributed-system-interview.md)
|
||||
|
||||
## 系统拆分
|
||||
- [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md)
|
||||
|
||||
* [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md)
|
||||
|
||||
## 分布式服务框架
|
||||
- [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](/docs/distributed-system/dubbo-operating-principle.md)
|
||||
- [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](/docs/distributed-system/dubbo-serialization-protocol.md)
|
||||
- [Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](/docs/distributed-system/dubbo-load-balancing.md)
|
||||
- [Dubbo 的 spi 思想是什么?](/docs/distributed-system/dubbo-spi.md)
|
||||
- [如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](/docs/distributed-system/dubbo-service-management.md)
|
||||
- [分布式服务接口的幂等性如何设计(比如不能重复扣款)?](/docs/distributed-system/distributed-system-idempotency.md)
|
||||
- [分布式服务接口请求的顺序性如何保证?](/docs/distributed-system/distributed-system-request-sequence.md)
|
||||
- [如何自己设计一个类似 Dubbo 的 RPC 框架?](/docs/distributed-system/dubbo-rpc-design.md)
|
||||
|
||||
* [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](/docs/distributed-system/dubbo-operating-principle.md)
|
||||
* [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](/docs/distributed-system/dubbo-serialization-protocol.md)
|
||||
* [Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](/docs/distributed-system/dubbo-load-balancing.md)
|
||||
* [Dubbo 的 spi 思想是什么?](/docs/distributed-system/dubbo-spi.md)
|
||||
* [如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](/docs/distributed-system/dubbo-service-management.md)
|
||||
* [分布式服务接口的幂等性如何设计(比如不能重复扣款)?](/docs/distributed-system/distributed-system-idempotency.md)
|
||||
* [分布式服务接口请求的顺序性如何保证?](/docs/distributed-system/distributed-system-request-sequence.md)
|
||||
* [如何自己设计一个类似 Dubbo 的 RPC 框架?](/docs/distributed-system/dubbo-rpc-design.md)
|
||||
|
||||
## 分布式锁
|
||||
- [Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md)
|
||||
- [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
|
||||
|
||||
* [Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md)
|
||||
* [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
|
||||
|
||||
## 分布式事务
|
||||
- [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md)
|
||||
|
||||
* [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md)
|
||||
|
||||
## 分布式会话
|
||||
- [集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.md)
|
||||
|
||||
* [集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.md)
|
||||
|
||||
---
|
||||
|
||||
## 公众号
|
||||
|
||||
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
<table>
|
||||
@ -43,4 +51,4 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
|
||||
</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
@ -2,36 +2,37 @@
|
||||
一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实一般问问题,都是这么问的,先问问你 zk,然后其实是要过渡到 zk 相关的一些问题里去,比如分布式锁。因为在分布式系统开发中,分布式锁的使用场景还是很常见的。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### redis 分布式锁
|
||||
|
||||
官方叫做 `RedLock` 算法,是 redis 官方支持的分布式锁算法。
|
||||
|
||||
这个分布式锁有 3 个重要的考量点:
|
||||
|
||||
- 互斥(只能有一个客户端获取锁)
|
||||
- 不能死锁
|
||||
- 容错(只要大部分 redis 节点创建了这把锁就可以)
|
||||
* 互斥(只能有一个客户端获取锁)
|
||||
* 不能死锁
|
||||
* 容错(只要大部分 redis 节点创建了这把锁就可以)
|
||||
|
||||
#### redis 最普通的分布式锁
|
||||
|
||||
第一个最普通的实现方式,就是在 redis 里使用 `setnx` 命令创建一个 key,这样就算加锁。
|
||||
|
||||
|
||||
```r
|
||||
``` r
|
||||
SET resource_name my_random_value NX PX 30000
|
||||
```
|
||||
|
||||
执行这个命令就 ok。
|
||||
|
||||
- `NX`:表示只有 `key` 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 `nil`)
|
||||
- `PX 30000`:意思是 30s 后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。
|
||||
* `NX` :表示只有 `key` 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 `nil` )
|
||||
* `PX 30000` :意思是 30s 后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。
|
||||
|
||||
释放锁就是删除 key ,但是一般可以用 `lua` 脚本删除,判断 value 一样才删除:
|
||||
|
||||
```lua
|
||||
``` lua
|
||||
-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。
|
||||
if redis.call("get",KEYS[1]) == ARGV[1] then
|
||||
return redis.call("del",KEYS[1])
|
||||
@ -45,11 +46,12 @@ end
|
||||
但是这样是肯定不行的。因为如果是普通的 redis 单实例,那就是单点故障。或者是 redis 普通主从,那 redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。
|
||||
|
||||
#### RedLock 算法
|
||||
|
||||
这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把锁:
|
||||
|
||||
1. 获取当前时间戳,单位是毫秒;
|
||||
2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒;
|
||||
3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1`;
|
||||
3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1` ;
|
||||
4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了;
|
||||
5. 要是锁建立失败了,那么就依次之前建立过的锁删除;
|
||||
6. 只要别人建立了一把分布式锁,你就得**不断轮询去尝试获取锁**。
|
||||
@ -62,7 +64,7 @@ end
|
||||
|
||||
zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能**注册个监听器**监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。
|
||||
|
||||
```java
|
||||
``` java
|
||||
/**
|
||||
* ZooKeeperSession
|
||||
*/
|
||||
@ -191,12 +193,11 @@ public class ZooKeeperSession {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
也可以采用另一种方式,创建临时顺序节点:
|
||||
|
||||
如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 zookeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。
|
||||
|
||||
```java
|
||||
``` java
|
||||
public class ZooKeeperDistributedLock implements Watcher {
|
||||
|
||||
private ZooKeeper zk;
|
||||
@ -325,8 +326,8 @@ public class ZooKeeperDistributedLock implements Watcher {
|
||||
|
||||
### redis 分布式锁和 zk 分布式锁的对比
|
||||
|
||||
- redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。
|
||||
- zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。
|
||||
* redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。
|
||||
* zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。
|
||||
|
||||
另外一点就是,如果是 redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。
|
||||
|
||||
|
@ -2,12 +2,14 @@
|
||||
集群部署时的分布式 session 如何实现?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
面试官问了你一堆 dubbo 是怎么玩儿的,你会玩儿 dubbo 就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最大的问题就是**分布式事务**、**接口幂等性**、**分布式锁**,还有最后一个就是**分布式 session**。
|
||||
|
||||
当然了,分布式系统中的问题何止这么一点,非常之多,复杂度很高,这里只是说一下常见的几个问题,也是面试的时候常问的几个。
|
||||
|
||||
## 面试题剖析
|
||||
session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 `jsessionid cookie`,就根据这个东西,在服务端可以维护一个对应的 session 域,里面可以放点数据。
|
||||
|
||||
session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 `jsessionid cookie` ,就根据这个东西,在服务端可以维护一个对应的 session 域,里面可以放点数据。
|
||||
|
||||
一般的话只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。
|
||||
|
||||
@ -18,14 +20,16 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存
|
||||
其实方法很多,但是常见常用的是以下几种:
|
||||
|
||||
### 完全不用 session
|
||||
|
||||
使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。
|
||||
|
||||
### tomcat + redis
|
||||
|
||||
这个其实还挺方便的,就是使用 session 的代码,跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 tomcat 都将 session 数据存储到 redis 即可。
|
||||
|
||||
在 tomcat 的配置文件中配置:
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
|
||||
|
||||
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
|
||||
@ -48,6 +52,7 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存
|
||||
还可以用上面这种方式基于 redis 哨兵支持的 redis 高可用集群来保存 session 数据,都是 ok 的。
|
||||
|
||||
### spring session + redis
|
||||
|
||||
上面所说的第二种方式会与 tomcat 容器重耦合,如果我要将 web 容器迁移成 jetty,难道还要重新把 jetty 都配置一遍?
|
||||
|
||||
因为上面那种 tomcat + redis 的方式好用,但是会**严重依赖于web容器**,不好将代码移植到其他 web 容器上去,尤其是你要是换了技术栈咋整?比如换成了 spring cloud 或者是 spring boot 之类的呢?
|
||||
@ -55,7 +60,8 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存
|
||||
所以现在比较好的还是基于 Java 一站式解决方案,也就是 spring。人家 spring 基本上承包了大部分我们需要使用的框架,spirng cloud 做微服务,spring boot 做脚手架,所以用 sping session 是一个很好的选择。
|
||||
|
||||
在 pom.xml 中配置:
|
||||
```xml
|
||||
|
||||
``` xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-redis</artifactId>
|
||||
@ -69,7 +75,8 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存
|
||||
```
|
||||
|
||||
在 spring 配置文件中配置:
|
||||
```xml
|
||||
|
||||
``` xml
|
||||
<bean id="redisHttpSessionConfiguration"
|
||||
class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
|
||||
<property name="maxInactiveIntervalInSeconds" value="600"/>
|
||||
@ -92,7 +99,8 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存
|
||||
```
|
||||
|
||||
在 web.xml 中配置:
|
||||
```xml
|
||||
|
||||
``` xml
|
||||
<filter>
|
||||
<filter-name>springSessionRepositoryFilter</filter-name>
|
||||
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
|
||||
@ -104,7 +112,8 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存
|
||||
```
|
||||
|
||||
示例代码:
|
||||
```java
|
||||
|
||||
``` java
|
||||
@RestController
|
||||
@RequestMapping("/test")
|
||||
public class TestController {
|
||||
@ -123,7 +132,6 @@ public class TestController {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
上面的代码就是 ok 的,给 sping session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了。
|
||||
|
||||
实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。
|
||||
实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。
|
||||
|
@ -2,6 +2,7 @@
|
||||
分布式服务接口的幂等性如何设计(比如不能重复扣款)?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
从这个问题开始,面试官就已经进入了**实际的生产问题**的面试了。
|
||||
|
||||
一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢?
|
||||
@ -13,15 +14,17 @@
|
||||
所以你肯定得知道这事儿,否则你做出来的分布式系统恐怕容易埋坑。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
这个不是技术问题,这个没有通用的一个方法,这个应该**结合业务**来保证幂等性。
|
||||
|
||||
所谓**幂等性**,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款、不能多插入一条数据、不能将统计值多加了 1。这就是幂等性。
|
||||
|
||||
其实保证幂等性主要是三点:
|
||||
- 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。
|
||||
- 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。
|
||||
- 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
|
||||
|
||||
* 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。
|
||||
* 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。
|
||||
* 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。
|
||||
|
||||
实际运作过程中,你要结合自己的业务来,比如说利用 redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。
|
||||
|
||||
要求是支付一个订单,必须插入一条支付流水,order_id 建一个唯一键 `unique key`。你在支付一个订单之前,先插入一条支付流水,order_id 就已经进去了。你就可以写一个标识到 redis 里面去,`set order_id payed`,下一次重复请求过来了,先查 redis 的 order_id 对应的 value,如果是 `payed` 就说明已经支付过了,你就别重复支付了。
|
||||
要求是支付一个订单,必须插入一条支付流水,order_id 建一个唯一键 `unique key` 。你在支付一个订单之前,先插入一条支付流水,order_id 就已经进去了。你就可以写一个标识到 redis 里面去, `set order_id payed` ,下一次重复请求过来了,先查 redis 的 order_id 对应的 value,如果是 `payed` 就说明已经支付过了,你就别重复支付了。
|
||||
|
@ -8,23 +8,30 @@
|
||||
> 近几年开始兴起和流行 Spring Cloud,刚流行,还没开始普及,目前普及的是 dubbo,因此这里也主要讲 dubbo。
|
||||
|
||||
面试官可能会问你以下问题。
|
||||
|
||||
### 为什么要进行系统拆分?
|
||||
- 为什么要进行系统拆分?如何进行系统拆分?拆分后不用dubbo可以吗?dubbo和thrift有什么区别呢?
|
||||
|
||||
* 为什么要进行系统拆分?如何进行系统拆分?拆分后不用dubbo可以吗?dubbo和thrift有什么区别呢?
|
||||
|
||||
### 分布式服务框架
|
||||
- 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?
|
||||
- dubbo 支持哪些序列化协议?说一下 hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
|
||||
- dubbo 负载均衡策略和高可用策略都有哪些?动态代理策略呢?
|
||||
- dubbo 的 spi 思想是什么?
|
||||
- 如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
|
||||
- 分布式服务接口的幂等性如何设计(比如不能重复扣款)?
|
||||
- 分布式服务接口请求的顺序性如何保证?
|
||||
- 如何自己设计一个类似 dubbo 的 rpc 框架?
|
||||
|
||||
* 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?
|
||||
* dubbo 支持哪些序列化协议?说一下 hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
|
||||
* dubbo 负载均衡策略和高可用策略都有哪些?动态代理策略呢?
|
||||
* dubbo 的 spi 思想是什么?
|
||||
* 如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
|
||||
* 分布式服务接口的幂等性如何设计(比如不能重复扣款)?
|
||||
* 分布式服务接口请求的顺序性如何保证?
|
||||
* 如何自己设计一个类似 dubbo 的 rpc 框架?
|
||||
|
||||
### 分布式锁
|
||||
- 使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
|
||||
|
||||
* 使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
|
||||
|
||||
### 分布式事务
|
||||
- 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?
|
||||
|
||||
* 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?
|
||||
|
||||
### 分布式会话
|
||||
- 集群部署时的分布式 session 如何实现?
|
||||
|
||||
* 集群部署时的分布式 session 如何实现?
|
||||
|
@ -2,6 +2,7 @@
|
||||
分布式服务接口请求的顺序性如何保证?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实分布式系统接口的调用顺序,也是个问题,一般来说是不用保证顺序的。但是**有时候**可能确实是需要**严格的顺序**保证。给大家举个例子,你服务 A 调用服务 B,先插入再删除。好,结果俩请求过去了,落在不同机器上,可能插入请求因为某些原因执行慢了一些,导致删除请求先执行了,此时因为没数据所以啥效果也没有;结果这个时候插入请求过来了,好,数据插入进去了,那就尴尬了。
|
||||
|
||||
本来应该是 “先插入 -> 再删除”,这条数据应该没了,结果现在 “先删除 -> 再插入”,数据还存在,最后你死都想不明白是怎么回事。
|
||||
@ -9,12 +10,13 @@
|
||||
所以这都是分布式系统一些很常见的问题。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
首先,一般来说,个人建议是,你们从业务逻辑上设计的这个系统最好是不需要这种顺序性的保证,因为一旦引入顺序性保障,比如使用**分布式锁**,会**导致系统复杂度上升**,而且会带来**效率低下**,热点数据压力过大等问题。
|
||||
|
||||
下面我给个我们用过的方案吧,简单来说,首先你得用 dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上,因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去,强制排队,这样来确保他们的顺序性。
|
||||
|
||||

|
||||
|
||||
但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成**热点**怎么办?解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已,所以,还是建议什么呢?
|
||||
但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成**热点**怎么办?解决这些问题又要开启后续一连串的复杂技术方案...... 曾经这类问题弄的我们头疼不已,所以,还是建议什么呢?
|
||||
|
||||
最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是其它什么,避免这种问题的产生。
|
||||
最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是其它什么,避免这种问题的产生。
|
||||
|
@ -2,20 +2,23 @@
|
||||
分布式事务了解吗?你们是如何解决分布式事务问题的?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
只要聊到你做了分布式系统,必问分布式事务,你对分布式事务一无所知的话,确实会很坑,你起码得知道有哪些方案,一般怎么来做,每个方案的优缺点是什么。
|
||||
|
||||
现在面试,分布式系统成了标配,而分布式系统带来的**分布式事务**也成了标配了。因为你做系统肯定要用事务吧,如果是分布式系统,肯定要用分布式事务吧。先不说你搞过没有,起码你得明白有哪几种方案,每种方案可能有啥坑?比如 TCC 方案的网络问题、XA 方案的一致性问题。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
分布式事务的实现主要有以下 5 种方案:
|
||||
|
||||
- XA 方案
|
||||
- TCC 方案
|
||||
- 本地消息表
|
||||
- 可靠消息最终一致性方案
|
||||
- 最大努力通知方案
|
||||
* XA 方案
|
||||
* TCC 方案
|
||||
* 本地消息表
|
||||
* 可靠消息最终一致性方案
|
||||
* 最大努力通知方案
|
||||
|
||||
### 两阶段提交方案/XA方案
|
||||
|
||||
所谓的 XA 方案,即:两阶段提交,有一个**事务管理器**的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
|
||||
|
||||
这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 `Spring + JTA` 就可以搞定,自己随便搜个 demo 看看就知道了。
|
||||
@ -29,11 +32,12 @@
|
||||

|
||||
|
||||
### TCC 方案
|
||||
TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||
|
||||
- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。
|
||||
- Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。
|
||||
- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
|
||||
TCC 的全称是: `Try` 、 `Confirm` 、 `Cancel` 。
|
||||
|
||||
* Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。
|
||||
* Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。
|
||||
* Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
|
||||
|
||||
这种方案说实话几乎很少人使用,我们用的也比较少,但是也有使用的场景。因为这个**事务回滚**实际上是**严重依赖于你自己写代码来回滚和补偿**了,会造成补偿代码巨大,非常之恶心。
|
||||
|
||||
@ -46,6 +50,7 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||

|
||||
|
||||
### 本地消息表
|
||||
|
||||
本地消息表其实是国外的 ebay 搞出来的这么一套思想。
|
||||
|
||||
这个大概意思是这样的:
|
||||
@ -62,6 +67,7 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||

|
||||
|
||||
### 可靠消息最终一致性方案
|
||||
|
||||
这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。
|
||||
|
||||
大概的意思就是:
|
||||
@ -76,6 +82,7 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||

|
||||
|
||||
### 最大努力通知方案
|
||||
|
||||
这个方案的大致意思就是:
|
||||
|
||||
1. 系统 A 本地事务执行完之后,发送个消息到 MQ;
|
||||
@ -83,10 +90,11 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||
3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。
|
||||
|
||||
### 你们公司是如何处理分布式事务的?
|
||||
|
||||
如果你真的被问到,可以这么说,我们某某特别严格的场景,用的是 TCC 来保证强一致性;然后其他的一些场景基于阿里的 RocketMQ 来实现分布式事务。
|
||||
|
||||
你找一个严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案。
|
||||
|
||||
友情提示一下,RocketMQ 3.2.6 之前的版本,是可以按照上面的思路来的,但是之后接口做了一些改变,我这里不再赘述了。
|
||||
|
||||
当然如果你愿意,你可以参考可靠消息最终一致性方案来自己实现一套分布式事务,比如基于 RocketMQ 来玩儿。
|
||||
当然如果你愿意,你可以参考可靠消息最终一致性方案来自己实现一套分布式事务,比如基于 RocketMQ 来玩儿。
|
||||
|
@ -2,20 +2,26 @@
|
||||
dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
继续深问吧,这些都是用 dubbo 必须知道的一些东西,你得知道基本原理,知道序列化是什么协议,还得知道具体用 dubbo 的时候,如何负载均衡,如何高可用,如何动态代理。
|
||||
|
||||
说白了,就是看你对 dubbo 熟悉不熟悉:
|
||||
- dubbo 工作原理:服务注册、注册中心、消费者、代理通信、负载均衡;
|
||||
- 网络通信、序列化:dubbo 协议、长连接、NIO、hessian 序列化协议;
|
||||
- 负载均衡策略、集群容错策略、动态代理策略:dubbo 跑起来的时候一些功能是如何运转的?怎么做负载均衡?怎么做集群容错?怎么生成动态代理?
|
||||
- dubbo SPI 机制:你了解不了解 dubbo 的 SPI 机制?如何基于 SPI 机制对 dubbo 进行扩展?
|
||||
|
||||
* dubbo 工作原理:服务注册、注册中心、消费者、代理通信、负载均衡;
|
||||
* 网络通信、序列化:dubbo 协议、长连接、NIO、hessian 序列化协议;
|
||||
* 负载均衡策略、集群容错策略、动态代理策略:dubbo 跑起来的时候一些功能是如何运转的?怎么做负载均衡?怎么做集群容错?怎么生成动态代理?
|
||||
* dubbo SPI 机制:你了解不了解 dubbo 的 SPI 机制?如何基于 SPI 机制对 dubbo 进行扩展?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### dubbo 负载均衡策略
|
||||
|
||||
#### random loadbalance
|
||||
|
||||
默认情况下,dubbo 是 random load balance ,即**随机**调用实现负载均衡,可以对 provider 不同实例**设置不同的权重**,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。
|
||||
|
||||
#### roundrobin loadbalance
|
||||
|
||||
这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。
|
||||
|
||||
举个栗子。
|
||||
@ -25,61 +31,70 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略
|
||||
这个时候,可以给两台 8 核 16G 的机器设置权重 4,给剩余 1 台 4 核 8G 的机器设置权重 2。
|
||||
|
||||
#### leastactive loadbalance
|
||||
|
||||
这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给**不活跃的性能差的机器更少的请求**。
|
||||
|
||||
#### consistanthash loadbalance
|
||||
|
||||
一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。**如果你需要的不是随机负载均衡**,是要一类请求都到一个节点,那就走这个一致性 Hash 策略。
|
||||
|
||||
### dubbo 集群容错策略
|
||||
|
||||
#### failover cluster 模式
|
||||
失败自动切换,自动重试其他机器,**默认**就是这个,常见于读操作。(失败重试其它机器)
|
||||
|
||||
可以通过以下几种方式配置重试次数:
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<dubbo:service retries="2" />
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<dubbo:reference retries="2" />
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<dubbo:reference>
|
||||
<dubbo:method name="findFoo" retries="2" />
|
||||
</dubbo:reference>
|
||||
```
|
||||
|
||||
#### failfast cluster 模式
|
||||
|
||||
一次调用失败就立即失败,常见于非幂等性的写操作,比如新增一条记录(调用失败就立即失败)
|
||||
|
||||
#### failsafe cluster 模式
|
||||
|
||||
出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。
|
||||
|
||||
配置示例如下:
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<dubbo:service cluster="failsafe" />
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<dubbo:reference cluster="failsafe" />
|
||||
```
|
||||
|
||||
#### failback cluster 模式
|
||||
|
||||
失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种。
|
||||
|
||||
#### forking cluster 模式
|
||||
|
||||
**并行调用**多个 provider,只要一个成功就立即返回。常用于实时性要求比较高的读操作,但是会浪费更多的服务资源,可通过 `forks="2"` 来设置最大并行数。
|
||||
|
||||
#### broadcacst cluster
|
||||
逐个调用所有的 provider。任何一个 provider 出错则报错(从`2.1.0` 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。
|
||||
|
||||
逐个调用所有的 provider。任何一个 provider 出错则报错(从 `2.1.0` 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。
|
||||
|
||||
### dubbo动态代理策略
|
||||
默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。
|
||||
|
||||
默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。
|
||||
|
@ -2,6 +2,7 @@
|
||||
说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
MQ、ES、Redis、Dubbo,上来先问你一些**思考性的问题**、**原理**,比如 kafka 高可用架构原理、es 分布式架构原理、redis 线程模型原理、Dubbo 工作原理;之后就是生产环境里可能会碰到的一些问题,因为每种技术引入之后生产环境都可能会碰到一些问题;再来点综合的,就是系统设计,比如让你设计一个 MQ、设计一个搜索引擎、设计一个缓存、设计一个 rpc 框架等等。
|
||||
|
||||
那既然开始聊分布式系统了,自然重点先聊聊 dubbo 了,毕竟 dubbo 是目前事实上大部分公司的分布式系统的 rpc 框架标准,基于 dubbo 也可以构建一整套的微服务架构。但是需要自己大量开发。
|
||||
@ -11,25 +12,29 @@ MQ、ES、Redis、Dubbo,上来先问你一些**思考性的问题**、**原理
|
||||
既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc 分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### dubbo 工作原理
|
||||
- 第一层:service 层,接口层,给服务提供者和消费者来实现的
|
||||
- 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的
|
||||
- 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信
|
||||
- 第四层:registry 层,服务注册层,负责服务的注册与发现
|
||||
- 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
|
||||
- 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
|
||||
- 第七层:protocal 层,远程调用层,封装 rpc 调用
|
||||
- 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步
|
||||
- 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口
|
||||
- 第十层:serialize 层,数据序列化层
|
||||
|
||||
* 第一层:service 层,接口层,给服务提供者和消费者来实现的
|
||||
* 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的
|
||||
* 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信
|
||||
* 第四层:registry 层,服务注册层,负责服务的注册与发现
|
||||
* 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务
|
||||
* 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控
|
||||
* 第七层:protocal 层,远程调用层,封装 rpc 调用
|
||||
* 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步
|
||||
* 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口
|
||||
* 第十层:serialize 层,数据序列化层
|
||||
|
||||
### 工作流程
|
||||
- 第一步:provider 向注册中心去注册
|
||||
- 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
|
||||
- 第三步:consumer 调用 provider
|
||||
- 第四步:consumer 和 provider 都异步通知监控中心
|
||||
|
||||
* 第一步:provider 向注册中心去注册
|
||||
* 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务
|
||||
* 第三步:consumer 调用 provider
|
||||
* 第四步:consumer 和 provider 都异步通知监控中心
|
||||
|
||||

|
||||
|
||||
### 注册中心挂了可以继续通信吗?
|
||||
可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息**拉取到本地缓存**,所以注册中心挂了可以继续通信。
|
||||
|
||||
可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息**拉取到本地缓存**,所以注册中心挂了可以继续通信。
|
||||
|
@ -2,21 +2,25 @@
|
||||
如何自己设计一个类似 Dubbo 的 RPC 框架?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
说实话,就这问题,其实就跟问你如何自己设计一个 MQ 一样的道理,就考两个:
|
||||
- 你有没有对某个 rpc 框架原理有非常深入的理解。
|
||||
- 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。
|
||||
|
||||
* 你有没有对某个 rpc 框架原理有非常深入的理解。
|
||||
* 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
其实问到你这问题,你起码不能认怂,因为是知识的扫盲,那我不可能给你深入讲解什么 kafka 源码剖析,dubbo 源码剖析,何况我就算讲了,你要真的消化理解和吸收,起码个把月以后了。
|
||||
|
||||
所以我给大家一个建议,遇到这类问题,起码从你了解的类似框架的原理入手,自己说说参照 dubbo 的原理,你来设计一下,举个例子,dubbo 不是有那么多分层么?而且每个分层是干啥的,你大概是不是知道?那就按照这个思路大致说一下吧,起码你不能懵逼,要比那些上来就懵,啥也说不出来的人要好一些。
|
||||
|
||||
举个栗子,我给大家说个最简单的回答思路:
|
||||
- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。
|
||||
- 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。
|
||||
- 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
|
||||
- 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。
|
||||
- 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。
|
||||
- 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。
|
||||
|
||||
这就是一个最最基本的 rpc 框架的思路,先不说你有多牛逼的技术功底,哪怕这个最简单的思路你先给出来行不行?
|
||||
* 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。
|
||||
* 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。
|
||||
* 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
|
||||
* 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。
|
||||
* 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。
|
||||
* 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。
|
||||
|
||||
这就是一个最最基本的 rpc 框架的思路,先不说你有多牛逼的技术功底,哪怕这个最简单的思路你先给出来行不行?
|
||||
|
@ -2,17 +2,20 @@
|
||||
dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
上一个问题,说说 dubbo 的基本工作原理,那是你必须知道的,至少要知道 dubbo 分成哪些层,然后平时怎么发起 rpc 请求的,注册、发现、调用,这些是基本的。
|
||||
|
||||
接着就可以针对底层进行深入的问问了,比如第一步就可以先问问序列化协议这块,就是平时 RPC 的时候怎么走的?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
**序列化**,就是把数据结构或者是一些对象,转换为二进制串的过程,而**反序列化**是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
|
||||
|
||||

|
||||
|
||||
### dubbo 支持不同的通信协议
|
||||
- dubbo 协议
|
||||
|
||||
* dubbo 协议
|
||||
|
||||
**默认**就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高。
|
||||
|
||||
@ -26,48 +29,51 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian
|
||||
|
||||

|
||||
|
||||
- rmi 协议
|
||||
* rmi 协议
|
||||
|
||||
走 Java 二进制序列化,多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用。
|
||||
|
||||
- hessian 协议
|
||||
* hessian 协议
|
||||
|
||||
走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用。
|
||||
|
||||
- http 协议
|
||||
* http 协议
|
||||
|
||||
走 json 序列化。
|
||||
|
||||
- webservice
|
||||
* webservice
|
||||
|
||||
走 SOAP 文本序列化。
|
||||
|
||||
### dubbo 支持的序列化协议
|
||||
|
||||
dubbo 支持 hession、Java 二进制序列化、json、SOAP 文本序列化多种序列化协议。但是 hessian 是其默认的序列化协议。
|
||||
|
||||
### 说一下 Hessian 的数据结构
|
||||
|
||||
Hessian 的对象序列化机制有 8 种原始类型:
|
||||
|
||||
- 原始二进制数据
|
||||
- boolean
|
||||
- 64-bit date(64 位毫秒值的日期)
|
||||
- 64-bit double
|
||||
- 32-bit int
|
||||
- 64-bit long
|
||||
- null
|
||||
- UTF-8 编码的 string
|
||||
* 原始二进制数据
|
||||
* boolean
|
||||
* 64-bit date(64 位毫秒值的日期)
|
||||
* 64-bit double
|
||||
* 32-bit int
|
||||
* 64-bit long
|
||||
* null
|
||||
* UTF-8 编码的 string
|
||||
|
||||
另外还包括 3 种递归类型:
|
||||
|
||||
- list for lists and arrays
|
||||
- map for maps and dictionaries
|
||||
- object for objects
|
||||
* list for lists and arrays
|
||||
* map for maps and dictionaries
|
||||
* object for objects
|
||||
|
||||
还有一种特殊的类型:
|
||||
|
||||
- ref:用来表示对共享对象的引用。
|
||||
* ref:用来表示对共享对象的引用。
|
||||
|
||||
### 为什么 PB 的效率是最高的?
|
||||
可能有一些同学比较习惯于 `JSON` or `XML` 数据存储格式,对于 `Protocol Buffer` 还比较陌生。`Protocol Buffer` 其实是 Google 出品的一种轻量并且高效的结构化数据存储格式,性能比 `JSON`、`XML` 要高很多。
|
||||
|
||||
其实 PB 之所以性能如此好,主要得益于两个:**第一**,它使用 proto 编译器,自动进行序列化和反序列化,速度非常快,应该比 `XML` 和 `JSON` 快上了 `20~100` 倍;**第二**,它的数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。
|
||||
可能有一些同学比较习惯于 `JSON` or `XML` 数据存储格式,对于 `Protocol Buffer` 还比较陌生。 `Protocol Buffer` 其实是 Google 出品的一种轻量并且高效的结构化数据存储格式,性能比 `JSON` 、 `XML` 要高很多。
|
||||
|
||||
其实 PB 之所以性能如此好,主要得益于两个:**第一**,它使用 proto 编译器,自动进行序列化和反序列化,速度非常快,应该比 `XML` 和 `JSON` 快上了 `20~100` 倍;**第二**,它的数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。
|
||||
|
@ -2,6 +2,7 @@
|
||||
如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
服务治理,这个问题如果问你,其实就是看看你有没有**服务治理**的思想,因为这个是做过复杂微服务的人肯定会遇到的一个问题。
|
||||
|
||||
**服务降级**,这个是涉及到复杂分布式系统中必备的一个话题,因为分布式系统互相来回调用,任何一个系统故障了,你不降级,直接就全盘崩溃?那就太坑爹了吧。
|
||||
@ -11,8 +12,11 @@
|
||||
**超时重试**,跟上面一样,如果不小心网络慢一点,超时了,如何重试?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 服务治理
|
||||
|
||||
#### 1. 调用链路自动生成
|
||||
|
||||
一个大型的分布式系统,或者说是用现在流行的微服务架构来说吧,**分布式系统由大量的服务组成**。那么这些服务之间互相是如何调用的?调用链路是啥?说实话,几乎到后面没人搞的清楚了,因为服务实在太多了,可能几百个甚至几千个服务。
|
||||
|
||||
那就需要基于 dubbo 做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将**各个服务之间的依赖关系和调用链路生成出来**,做成一张图,显示出来,大家才可以看到对吧。
|
||||
@ -20,25 +24,28 @@
|
||||

|
||||
|
||||
#### 2. 服务访问压力以及时长统计
|
||||
|
||||
需要自动统计**各个接口和服务之间的调用次数以及访问延时**,而且要分成两个级别。
|
||||
|
||||
- 一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少;
|
||||
- 第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的 TP50/TP90/TP99,分别是多少。
|
||||
* 一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少;
|
||||
* 第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的 TP50/TP90/TP99,分别是多少。
|
||||
|
||||
这些东西都搞定了之后,后面才可以来看当前系统的压力主要在哪里,如何来扩容和优化啊。
|
||||
|
||||
#### 3. 其它
|
||||
- 服务分层(避免循环依赖)
|
||||
- 调用链路失败监控和报警
|
||||
- 服务鉴权
|
||||
- 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%)
|
||||
|
||||
* 服务分层(避免循环依赖)
|
||||
* 调用链路失败监控和报警
|
||||
* 服务鉴权
|
||||
* 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%)
|
||||
|
||||
### 服务降级
|
||||
|
||||
比如说服务 A 调用服务 B,结果服务 B 挂掉了,服务 A 重试几次调用服务 B,还是不行,那么直接降级,走一个备用的逻辑,给用户返回响应。
|
||||
|
||||
举个栗子,我们有接口 `HelloService`。`HelloServiceImpl` 有该接口的具体实现。
|
||||
举个栗子,我们有接口 `HelloService` 。 `HelloServiceImpl` 有该接口的具体实现。
|
||||
|
||||
```java
|
||||
``` java
|
||||
public interface HelloService {
|
||||
void sayHello();
|
||||
}
|
||||
@ -50,7 +57,7 @@ public class HelloServiceImpl implements HelloService {
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
|
||||
@ -83,9 +90,9 @@ public class HelloServiceImpl implements HelloService {
|
||||
|
||||
我们调用接口失败的时候,可以通过 `mock` 统一返回 null。
|
||||
|
||||
mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+`Mock`” 后缀。然后在 Mock 类里实现自己的降级逻辑。
|
||||
mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+ `Mock` ” 后缀。然后在 Mock 类里实现自己的降级逻辑。
|
||||
|
||||
```java
|
||||
``` java
|
||||
public class HelloServiceMock implements HelloService {
|
||||
public void sayHello() {
|
||||
// 降级逻辑
|
||||
@ -94,9 +101,10 @@ public class HelloServiceMock implements HelloService {
|
||||
```
|
||||
|
||||
### 失败重试和超时重试
|
||||
|
||||
所谓失败重试,就是 consumer 调用 provider 要是失败了,比如抛异常了,此时应该是可以重试的,或者调用超时了也可以重试。配置如下:
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<dubbo:reference id="xxxx" interface="xx" check="true" async="false" retries="3" timeout="2000"/>
|
||||
```
|
||||
|
||||
@ -106,5 +114,5 @@ public class HelloServiceMock implements HelloService {
|
||||
|
||||
可以结合你们公司具体的场景来说说你是怎么设置这些参数的:
|
||||
|
||||
- `timeout`:一般设置为 `200ms`,我们认为不能超过 `200ms` 还没返回。
|
||||
- `retries`:设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。
|
||||
* `timeout` :一般设置为 `200ms` ,我们认为不能超过 `200ms` 还没返回。
|
||||
* `retries` :设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。
|
||||
|
@ -2,21 +2,24 @@
|
||||
dubbo 的 spi 思想是什么?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
继续深入问呗,前面一些基础性的东西问完了,确定你应该都 ok,了解 dubbo 的一些基本东西,那么问个稍微难一点点的问题,就是 spi,先问问你 spi 是啥?然后问问你 dubbo 的 spi 是怎么实现的?
|
||||
|
||||
其实就是看看你对 dubbo 的掌握如何。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### spi 是啥?
|
||||
spi,简单来说,就是 `service provider interface`,说白了是什么意思呢,比如你有个接口,现在这个接口有 3 个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要 spi 了,需要**根据指定的配置**或者是**默认的配置**,去**找到对应的实现类**加载进来,然后用这个实现类的实例对象。
|
||||
spi,简单来说,就是 `service provider interface` ,说白了是什么意思呢,比如你有个接口,现在这个接口有 3 个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要 spi 了,需要**根据指定的配置**或者是**默认的配置**,去**找到对应的实现类**加载进来,然后用这个实现类的实例对象。
|
||||
|
||||
举个栗子。
|
||||
|
||||
你有一个接口 A。A1/A2/A3 分别是接口A的不同实现。你通过配置 `接口 A = 实现 A2`,那么在系统实际运行的时候,会加载你的配置,用实现 A2 实例化一个对象来提供服务。
|
||||
你有一个接口 A。A1/A2/A3 分别是接口A的不同实现。你通过配置 `接口 A = 实现 A2` ,那么在系统实际运行的时候,会加载你的配置,用实现 A2 实例化一个对象来提供服务。
|
||||
|
||||
spi 机制一般用在哪儿?**插件扩展的场景**,比如说你开发了一个给别人使用的开源框架,如果你想让别人自己写个插件,插到你的开源框架里面,从而扩展某个功能,这个时候 spi 思想就用上了。
|
||||
|
||||
### Java spi 思想的体现
|
||||
|
||||
spi 经典的思想体现,大家平时都在用,比如说 jdbc。
|
||||
|
||||
Java 定义了一套 jdbc 的接口,但是 Java 并没有提供 jdbc 的实现类。
|
||||
@ -26,8 +29,10 @@ Java 定义了一套 jdbc 的接口,但是 Java 并没有提供 jdbc 的实现
|
||||
在系统跑的时候,碰到你使用 jdbc 的接口,他会在底层使用你引入的那个 jar 中提供的实现类。
|
||||
|
||||
### dubbo 的 spi 思想
|
||||
|
||||
dubbo 也用了 spi 思想,不过没有用 jdk 的 spi 机制,是自己实现的一套 spi 机制。
|
||||
```java
|
||||
|
||||
``` java
|
||||
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
|
||||
```
|
||||
|
||||
@ -35,9 +40,9 @@ Protocol 接口,在系统运行的时候,,dubbo 会判断一下应该选
|
||||
|
||||
它会去找一个你配置的 Protocol,将你配置的 Protocol 实现类,加载到 jvm 中来,然后实例化对象,就用你的那个 Protocol 实现类就可以了。
|
||||
|
||||
|
||||
上面那行代码就是 dubbo 里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现好了,没问题。
|
||||
```java
|
||||
|
||||
``` java
|
||||
@SPI("dubbo")
|
||||
public interface Protocol {
|
||||
|
||||
@ -54,15 +59,15 @@ public interface Protocol {
|
||||
}
|
||||
```
|
||||
|
||||
在 dubbo 自己的 jar 里,在`/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol`文件中:
|
||||
```xml
|
||||
在 dubbo 自己的 jar 里,在 `/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol` 文件中:
|
||||
|
||||
``` xml
|
||||
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
|
||||
http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
|
||||
hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
|
||||
```
|
||||
|
||||
所以说,这就看到了 dubbo 的 spi 机制默认是怎么玩儿的了,其实就是 Protocol 接口,`@SPI("dubbo")` 说的是,通过 SPI 机制来提供实现类,实现类是通过 dubbo 作为默认 key 去配置文件里找到的,配置文件名称与接口全限定名一样的,通过 dubbo 作为 key 可以找到默认的实现类就是 `com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol`。
|
||||
|
||||
所以说,这就看到了 dubbo 的 spi 机制默认是怎么玩儿的了,其实就是 Protocol 接口, `@SPI("dubbo")` 说的是,通过 SPI 机制来提供实现类,实现类是通过 dubbo 作为默认 key 去配置文件里找到的,配置文件名称与接口全限定名一样的,通过 dubbo 作为 key 可以找到默认的实现类就是 `com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol` 。
|
||||
|
||||
如果想要动态替换掉默认的实现类,需要使用 `@Adaptive` 接口,Protocol 接口中,有两个方法加了 `@Adaptive` 注解,就是说那俩接口会被代理实现。
|
||||
|
||||
@ -71,19 +76,21 @@ hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
|
||||
比如这个 Protocol 接口搞了俩 `@Adaptive` 注解标注了方法,在运行的时候会针对 Protocol 生成代理类,这个代理类的那俩方法里面会有代理代码,代理代码会在运行的时候动态根据 url 中的 protocol 来获取那个 key,默认是 dubbo,你也可以自己指定,你如果指定了别的 key,那么就会获取别的实现类的实例了。
|
||||
|
||||
### 如何自己扩展 dubbo 中的组件
|
||||
|
||||
下面来说说怎么来自己扩展 dubbo 中的组件。
|
||||
|
||||
自己写个工程,要是那种可以打成 jar 包的,里面的 `src/main/resources` 目录下,搞一个 `META-INF/services`,里面放个文件叫:`com.alibaba.dubbo.rpc.Protocol`,文件里搞一个`my=com.bingo.MyProtocol`。自己把 jar 弄到 nexus 私服里去。
|
||||
自己写个工程,要是那种可以打成 jar 包的,里面的 `src/main/resources` 目录下,搞一个 `META-INF/services` ,里面放个文件叫: `com.alibaba.dubbo.rpc.Protocol` ,文件里搞一个 `my=com.bingo.MyProtocol` 。自己把 jar 弄到 nexus 私服里去。
|
||||
|
||||
然后自己搞一个 `dubbo provider` 工程,在这个工程里面依赖你自己搞的那个 jar,然后在 spring 配置文件里给个配置:
|
||||
|
||||
```xml
|
||||
``` xml
|
||||
<dubbo:protocol name=”my” port=”20000” />
|
||||
```
|
||||
provider 启动的时候,就会加载到我们 jar 包里的`my=com.bingo.MyProtocol` 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。
|
||||
|
||||
provider 启动的时候,就会加载到我们 jar 包里的 `my=com.bingo.MyProtocol` 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。
|
||||
|
||||

|
||||
|
||||
dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类`。
|
||||
dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类` 。
|
||||
|
||||
然后对于对应的组件,类似 `<dubbo:protocol>` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。
|
||||
然后对于对应的组件,类似 `<dubbo:protocol>` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。
|
||||
|
@ -2,6 +2,7 @@
|
||||
为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
从这个问题开始就进行分布式系统环节了,现在出去面试分布式都成标配了,没有哪个公司不问问你分布式的事儿。你要是不会分布式的东西,简直这简历没法看,没人会让你去面试。
|
||||
|
||||
其实为啥会这样呢?这就是因为整个大行业技术发展的原因。
|
||||
@ -17,6 +18,7 @@
|
||||
直到今日,很高兴看到分布式系统都成行业面试标配了,任何一个普通的程序员都该掌握这个东西,其实这是行业的进步,也是所有 IT 码农的技术进步。所以既然分布式都成标配了,那么面试官当然会问了,因为很多公司现在都是分布式、微服务的架构,那面试官当然得考察考察你了。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 为什么要将系统进行拆分?
|
||||
网上查查,答案极度零散和复杂,很琐碎,原因一大坨。但是我这里给大家直观的感受:
|
||||
|
||||
@ -35,6 +37,7 @@ A 就检查了自己负责的 1 万行代码对应的功能,确保 ok 就闪
|
||||
但是同时,也要**提醒**的一点是,系统拆分成分布式系统之后,大量的分布式系统面临的问题也是接踵而来,所以后面的问题都是在**围绕分布式系统带来的复杂技术挑战**在说。
|
||||
|
||||
### 如何进行系统拆分?
|
||||
|
||||
这个问题说大可以很大,可以扯到领域驱动模型设计上去,说小了也很小,我不太想给大家太过于学术的说法,因为你也不可能背这个答案,过去了直接说吧。还是说的简单一点,大家自己到时候知道怎么回答就行了。
|
||||
|
||||
系统拆分为分布式系统,拆成多个服务,拆成微服务的架构,是需要拆很多轮的。并不是说上来一个架构师一次就给拆好了,而以后都不用拆。
|
||||
@ -52,6 +55,7 @@ A 就检查了自己负责的 1 万行代码对应的功能,确保 ok 就闪
|
||||
扯深了实在很深,所以这里先给大家举个例子,你自己感受一下,**核心意思就是根据情况,先拆分一轮,后面如果系统更复杂了,可以继续分拆**。你根据自己负责系统的例子,来考虑一下就好了。
|
||||
|
||||
### 拆分后不用 dubbo 可以吗?
|
||||
|
||||
当然可以了,大不了最次,就是各个系统之间,直接基于 spring mvc,就纯 http 接口互相通信呗,还能咋样。但是这个肯定是有问题的,因为 http 接口通信维护起来成本很高,你要考虑**超时重试**、**负载均衡**等等各种乱七八糟的问题,比如说你的订单系统调用商品系统,商品系统部署了 5 台机器,你怎么把请求均匀地甩给那 5 台机器?这不就是负载均衡?你要是都自己搞那是可以的,但是确实很痛苦。
|
||||
|
||||
所以 dubbo 说白了,是一种 rpc 框架,就是说本地就是进行接口调用,但是 dubbo 会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡、服务实例上下线自动感知、超时重试等等乱七八糟的问题。那你就不用自己做了,用 dubbo 就可以了。
|
||||
所以 dubbo 说白了,是一种 rpc 框架,就是说本地就是进行接口调用,但是 dubbo 会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡、服务实例上下线自动感知、超时重试等等乱七八糟的问题。那你就不用自己做了,用 dubbo 就可以了。
|
||||
|
@ -2,6 +2,7 @@
|
||||
zookeeper 都有哪些使用场景?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
现在聊的 topic 是分布式系统,面试官跟你聊完了 dubbo 相关的一些问题之后,已经确认你对分布式服务框架/RPC框架基本都有一些认知了。那么他可能开始要跟你聊分布式相关的其它问题了。
|
||||
|
||||
分布式锁这个东西,很常用的,你做 Java 系统开发,分布式系统,可能会有一些场景会用到。最常用的分布式锁就是基于 zookeeper 来实现的。
|
||||
@ -9,29 +10,34 @@ zookeeper 都有哪些使用场景?
|
||||
其实说实话,问这个问题,一般就是看看你是否了解 zookeeper,因为 zookeeper 是分布式系统中很常见的一个基础系统。而且问的话常问的就是说 zookeeper 的使用场景是什么?看你知道不知道一些基本的使用场景。但是其实 zookeeper 挖深了自然是可以问的很深很深的。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
大致来说,zookeeper 的使用场景如下,我就举几个简单的,大家能说几个就好了:
|
||||
|
||||
- 分布式协调
|
||||
- 分布式锁
|
||||
- 元数据/配置信息管理
|
||||
- HA高可用性
|
||||
* 分布式协调
|
||||
* 分布式锁
|
||||
* 元数据/配置信息管理
|
||||
* HA高可用性
|
||||
|
||||
### 分布式协调
|
||||
|
||||
这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上**对某个节点的值注册个监听器**,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知,完美解决。
|
||||
|
||||

|
||||
|
||||
### 分布式锁
|
||||
|
||||
举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也**尝试去创建**那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。
|
||||
|
||||

|
||||
|
||||
### 元数据/配置信息管理
|
||||
|
||||
zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么?
|
||||
|
||||

|
||||
|
||||
### HA高可用性
|
||||
|
||||
这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。
|
||||
|
||||

|
||||

|
||||
|
@ -1,15 +1,19 @@
|
||||
# 项目额外页面
|
||||
|
||||
## Offer 与进阶
|
||||
- [我的 Offer 在哪里?](https://doocs.gitee.io/advanced-java/#/docs/extra-page/offer)
|
||||
- [让我们同步进阶!](https://doocs.gitee.io/advanced-java/#/docs/extra-page/advanced)
|
||||
|
||||
* [我的 Offer 在哪里?](https://doocs.gitee.io/advanced-java/#/docs/extra-page/offer)
|
||||
* [让我们同步进阶!](https://doocs.gitee.io/advanced-java/#/docs/extra-page/advanced)
|
||||
|
||||
## 项目 Page 页
|
||||
- [GitHub Page](https://doocs.github.io/advanced-java/#/)
|
||||
- [Gitee Page](https://doocs.gitee.io/advanced-java/#/)
|
||||
|
||||
* [GitHub Page](https://doocs.github.io/advanced-java/#/)
|
||||
* [Gitee Page](https://doocs.gitee.io/advanced-java/#/)
|
||||
|
||||
---
|
||||
|
||||
## 公众号
|
||||
|
||||
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
<table>
|
||||
@ -27,4 +31,4 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
|
||||
</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
@ -5,4 +5,4 @@
|
||||
> 本系列知识由 Doocs 技术社区总结发布,内容涵盖高并发、分布式、高可用、微服务、海量数据处理等
|
||||
|
||||
[社区首页](https://doocs.github.io)
|
||||
[开始学习](#互联网-java-工程师进阶知识完全扫盲©)
|
||||
[开始学习](#互联网-java-工程师进阶知识完全扫盲©)
|
||||
|
@ -3,17 +3,19 @@
|
||||
</p>
|
||||
|
||||
## 声明
|
||||
|
||||
读者朋友们,你们好。[advanced-java](https://github.com/doocs/advanced-java) 项目自创建以来,一直收到很多读者的反馈,也在不断改进、完善内容,只希望可以用心做得更好。然而,网上抄袭、侵权的现象普遍存在,我想,不能任由这种恶劣行为肆虐。
|
||||
|
||||
因此,在此说明,除了 [doocs/advanced-java](https://github.com/doocs/advanced-java)、“**石杉的架构笔记**”,网上其它平台上如若出现了与本项目内容雷同甚至完全一致的文章,**不注明出处、甚至打着原创的标签忽悠读者**的,欢迎举报,也欢迎在此提供侵权名单,曝光抄袭者,谢谢。
|
||||
|
||||
希望各位朋友都注重**维护他人知识产权,尊重他人劳动成果**,我们共同构建一个健康的知识分享生态圈。
|
||||
|
||||
|
||||
## 抄袭名单列表
|
||||
|
||||
注:若以下某些链接失效,说明内容已被抄袭者删除,或者已被所在内容平台进行违规清除。
|
||||
|
||||
### 博客
|
||||
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
| 1 | [如何保证缓存与数据库的双写一致性](https://blog.51cto.com/14230003/2363051) | Java_老男孩-51CTO |
|
||||
@ -25,20 +27,23 @@
|
||||
| 7 | [消息中间件面试题:消息中间件的高可用](https://www.jianshu.com/p/92862edc7c51) | jsbintask-简书 |
|
||||
| 8 | [深入 Hystrix 执行时内部原理](https://www.jianshu.com/p/1a14401e219f) | kevin0016-简书 |
|
||||
|
||||
|
||||
### 公众号
|
||||
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
|
||||
### 头条号
|
||||
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
|
||||
### 掘金
|
||||
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
|
||||
### 知乎
|
||||
|
||||
| # | 文章 | 抄袭者 | 备注 |
|
||||
|---|---|---|---|
|
||||
| 1 | [Java消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java高级架构解析 | 严重抄袭 |
|
||||
| 1 | [Java消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java高级架构解析 | 严重抄袭 |
|
||||
|
@ -1,51 +1,55 @@
|
||||
# Doocs 开源社区的公众号来了
|
||||
GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专注于挖掘 IT 技术知识,助力开发者成长。
|
||||
|
||||
<div style="text-align:center;">
|
||||
<img src="./images/qrcode_for_doocs.jpg" width="200px;"/>
|
||||
<div style="text-align:center; ">
|
||||
<img src="./images/qrcode_for_doocs.jpg" width="200px; "/>
|
||||
</div>
|
||||
|
||||
来成为公众号的首批粉丝吗?一定不会辜负你们的期待。
|
||||
|
||||
## 为什么选择公众号?
|
||||
|
||||
微信公众号的**原创保护功能**做得比较好,内容阅读也比较方便。
|
||||
|
||||
后续我的所有原创文章,将第一时间通过微信公众号“Doocs开源社区”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。
|
||||
|
||||
## 公众号的定位是怎样的?
|
||||
- **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。
|
||||
- **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。
|
||||
|
||||
<div style="text-align:center;">
|
||||
<img src="./images/article-demo.png" width="300px;"/>
|
||||
* **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。
|
||||
* **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。
|
||||
|
||||
<div style="text-align:center; ">
|
||||
<img src="./images/article-demo.png" width="300px; "/>
|
||||
</div>
|
||||
|
||||
## 目前都有哪些文章?
|
||||
|
||||
因为刚刚推出公众号,目前有以下几篇文章,来先睹为快吧:
|
||||
|
||||
- [硬核!亿级流量秒杀系统设计](https://mp.weixin.qq.com/s/Mo_knIRBQQL2s-D2aieZLg)
|
||||
- [技术面试是否要看面经?面试官/面试者有话说!](https://mp.weixin.qq.com/s/fNiUmbY395rsPdEC0QDIrw)
|
||||
- [如何破解极验滑动验证码?成功率 100%!](https://mp.weixin.qq.com/s/Fsl6qYN5Dw4s6Du893MkFQ)
|
||||
- [免费且好用的图床,就你了,「图壳」!](https://mp.weixin.qq.com/s/0HhgHLo_tTRFZcC-CVjDbw)
|
||||
- [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson!](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow)
|
||||
- [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw)
|
||||
- [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA)
|
||||
- [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12!](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw)
|
||||
- [Google 搜索的即时自动补全功能究竟是如何“工作”的?](https://mp.weixin.qq.com/s/YlMISSc3Sn890BzTLytcLA)
|
||||
- [厉害了,原来 Redisson 这么好用!](https://mp.weixin.qq.com/s/lpZ7eRdImy0MyTEVH68HYw)
|
||||
- [一文带你搞懂 “缓存策略”](https://mp.weixin.qq.com/s/47A_iXY_nArURwUTPHr2IQ)
|
||||
- [Java Getter/Setter “防坑指南”](https://mp.weixin.qq.com/s/TZqcAw7NTlcvU-p930-eHA)
|
||||
- [太棒了,GitHub Review 代码能力小升级](https://mp.weixin.qq.com/s/Lok0epqn91Q51ygZo_FLkg)
|
||||
- [巧用 Redis Hyperloglog,轻松统计 UV 数据](https://mp.weixin.qq.com/s/w1r-M6YVvQSfUtzO_xe44Q)
|
||||
- [如何开启「GitHub+码云」双工作流模式?](https://mp.weixin.qq.com/s/byxAjr3-ifWfDYQcR7YA8Q)
|
||||
* [硬核!亿级流量秒杀系统设计](https://mp.weixin.qq.com/s/Mo_knIRBQQL2s-D2aieZLg)
|
||||
* [技术面试是否要看面经?面试官/面试者有话说!](https://mp.weixin.qq.com/s/fNiUmbY395rsPdEC0QDIrw)
|
||||
* [如何破解极验滑动验证码?成功率 100%!](https://mp.weixin.qq.com/s/Fsl6qYN5Dw4s6Du893MkFQ)
|
||||
* [免费且好用的图床,就你了,「图壳」!](https://mp.weixin.qq.com/s/0HhgHLo_tTRFZcC-CVjDbw)
|
||||
* [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson!](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow)
|
||||
* [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw)
|
||||
* [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA)
|
||||
* [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12!](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw)
|
||||
* [Google 搜索的即时自动补全功能究竟是如何“工作”的?](https://mp.weixin.qq.com/s/YlMISSc3Sn890BzTLytcLA)
|
||||
* [厉害了,原来 Redisson 这么好用!](https://mp.weixin.qq.com/s/lpZ7eRdImy0MyTEVH68HYw)
|
||||
* [一文带你搞懂 “缓存策略”](https://mp.weixin.qq.com/s/47A_iXY_nArURwUTPHr2IQ)
|
||||
* [Java Getter/Setter “防坑指南”](https://mp.weixin.qq.com/s/TZqcAw7NTlcvU-p930-eHA)
|
||||
* [太棒了,GitHub Review 代码能力小升级](https://mp.weixin.qq.com/s/Lok0epqn91Q51ygZo_FLkg)
|
||||
* [巧用 Redis Hyperloglog,轻松统计 UV 数据](https://mp.weixin.qq.com/s/w1r-M6YVvQSfUtzO_xe44Q)
|
||||
* [如何开启「GitHub+码云」双工作流模式?](https://mp.weixin.qq.com/s/byxAjr3-ifWfDYQcR7YA8Q)
|
||||
|
||||
后续将推出一系列原创干货文章,敬请期待。
|
||||
|
||||
## 是否有交流群?
|
||||
|
||||
有的,目前微信群“**Doocs的技术朋友们**”已经接近 200 号人,如果你希望加入,请通过微信与我联系。
|
||||
|
||||
**注意**,群内禁止一切垃圾广告信息,包括小程序助力信息、小游戏、社群推广、公众号推广、支付宝推广码等;交流 GitHub、开发相关,可以自由分享一些开发相关知识,但不提倡整天水群,建议还是多花点时间提升自己。
|
||||
|
||||
<div style="text-align:center;">
|
||||
<img src="./images/wechat-group-for-doocs.png" width="300px;"/>
|
||||
<div style="text-align:center; ">
|
||||
<img src="./images/wechat-group-for-doocs.png" width="300px; "/>
|
||||
</div>
|
||||
|
@ -1,46 +1,55 @@
|
||||
# 高并发架构
|
||||
|
||||
## [消息队列](/docs/high-concurrency/mq-interview.md)
|
||||
- [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](/docs/high-concurrency/why-mq.md)
|
||||
- [如何保证消息队列的高可用?](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
|
||||
- [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
|
||||
- [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)
|
||||
- [如何保证消息的顺序性?](/docs/high-concurrency/how-to-ensure-the-order-of-messages.md)
|
||||
- [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](/docs/high-concurrency/mq-time-delay-and-expired-failure.md)
|
||||
- [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](/docs/high-concurrency/mq-design.md)
|
||||
|
||||
* [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](/docs/high-concurrency/why-mq.md)
|
||||
* [如何保证消息队列的高可用?](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
|
||||
* [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
|
||||
* [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)
|
||||
* [如何保证消息的顺序性?](/docs/high-concurrency/how-to-ensure-the-order-of-messages.md)
|
||||
* [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](/docs/high-concurrency/mq-time-delay-and-expired-failure.md)
|
||||
* [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](/docs/high-concurrency/mq-design.md)
|
||||
|
||||
## [搜索引擎](/docs/high-concurrency/es-introduction.md)
|
||||
- [es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)?](/docs/high-concurrency/es-architecture.md)
|
||||
- [es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗?](/docs/high-concurrency/es-write-query-search.md)
|
||||
- [es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](/docs/high-concurrency/es-optimizing-query-performance.md)
|
||||
- [es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](/docs/high-concurrency/es-production-cluster.md)
|
||||
|
||||
* [es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)?](/docs/high-concurrency/es-architecture.md)
|
||||
* [es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗?](/docs/high-concurrency/es-write-query-search.md)
|
||||
* [es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](/docs/high-concurrency/es-optimizing-query-performance.md)
|
||||
* [es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](/docs/high-concurrency/es-production-cluster.md)
|
||||
|
||||
## 缓存
|
||||
- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/docs/high-concurrency/why-cache.md)
|
||||
- [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](/docs/high-concurrency/redis-single-thread-model.md)
|
||||
- [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](/docs/high-concurrency/redis-data-types.md)
|
||||
- [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](/docs/high-concurrency/redis-expiration-policies-and-lru.md)
|
||||
- [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
|
||||
- [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](/docs/high-concurrency/redis-persistence.md)
|
||||
- [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](/docs/high-concurrency/redis-cluster.md)
|
||||
- [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
- [如何保证缓存与数据库的双写一致性?](/docs/high-concurrency/redis-consistence.md)
|
||||
- [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](/docs/high-concurrency/redis-cas.md)
|
||||
- [生产环境中的 Redis 是怎么部署的?](/docs/high-concurrency/redis-production-environment.md)
|
||||
|
||||
* [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/docs/high-concurrency/why-cache.md)
|
||||
* [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](/docs/high-concurrency/redis-single-thread-model.md)
|
||||
* [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](/docs/high-concurrency/redis-data-types.md)
|
||||
* [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](/docs/high-concurrency/redis-expiration-policies-and-lru.md)
|
||||
* [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
|
||||
* [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](/docs/high-concurrency/redis-persistence.md)
|
||||
* [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](/docs/high-concurrency/redis-cluster.md)
|
||||
* [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
* [如何保证缓存与数据库的双写一致性?](/docs/high-concurrency/redis-consistence.md)
|
||||
* [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](/docs/high-concurrency/redis-cas.md)
|
||||
* [生产环境中的 Redis 是怎么部署的?](/docs/high-concurrency/redis-production-environment.md)
|
||||
|
||||
## 分库分表
|
||||
- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/docs/high-concurrency/database-shard.md)
|
||||
- [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](/docs/high-concurrency/database-shard-method.md)
|
||||
- [如何设计可以动态扩容缩容的分库分表方案?](/docs/high-concurrency/database-shard-dynamic-expand.md)
|
||||
- [分库分表之后,id 主键如何处理?](/docs/high-concurrency/database-shard-global-id-generate.md)
|
||||
|
||||
* [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/docs/high-concurrency/database-shard.md)
|
||||
* [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](/docs/high-concurrency/database-shard-method.md)
|
||||
* [如何设计可以动态扩容缩容的分库分表方案?](/docs/high-concurrency/database-shard-dynamic-expand.md)
|
||||
* [分库分表之后,id 主键如何处理?](/docs/high-concurrency/database-shard-global-id-generate.md)
|
||||
|
||||
## 读写分离
|
||||
- [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md)
|
||||
|
||||
* [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md)
|
||||
|
||||
## 高并发系统
|
||||
- [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md)
|
||||
|
||||
* [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md)
|
||||
|
||||
---
|
||||
|
||||
## 公众号
|
||||
|
||||
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
<table>
|
||||
@ -58,4 +67,4 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
|
||||
</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
@ -2,14 +2,15 @@
|
||||
如何设计可以动态扩容缩容的分库分表方案?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
对于分库分表来说,主要是面对以下问题:
|
||||
|
||||
- 选择一个数据库中间件,调研、学习、测试;
|
||||
- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
|
||||
- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
|
||||
- 完成单库单表到分库分表的**迁移**,双写方案;
|
||||
- 线上系统开始基于分库分表对外提供服务;
|
||||
- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
|
||||
* 选择一个数据库中间件,调研、学习、测试;
|
||||
* 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表;
|
||||
* 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写;
|
||||
* 完成单库单表到分库分表的**迁移**,双写方案;
|
||||
* 线上系统开始基于分库分表对外提供服务;
|
||||
* 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢?
|
||||
|
||||
这个是你必须面对的一个事儿,就是你已经弄好分库分表方案了,然后一堆库和表都建好了,基于分库分表中间件的代码开发啥的都好了,测试都 ok 了,数据能均匀分布到各个库和各个表里去,而且接着你还通过双写的方案咔嚓一下上了系统,已经直接基于分库分表方案在搞了。
|
||||
|
||||
@ -18,6 +19,7 @@
|
||||
这都是玩儿分库分表线上必须经历的事儿。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 停机扩容(不推荐)
|
||||
这个方案就跟停机迁移一样,步骤几乎一致,唯一的一点就是那个导数的工具,是把现有库表的数据抽出来慢慢倒入到新的库和表里去。但是最好别这么玩儿,有点不太靠谱,因为既然**分库分表**就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,你这么玩儿,可能会出问题。
|
||||
|
||||
@ -26,6 +28,7 @@
|
||||
如果 3 个库 + 12 个表,跑了一段时间了,数据量都 1~2 亿了。光是导 2 亿数据,都要导个几个小时,6 点,刚刚导完数据,还要搞后续的修改配置,重启系统,测试验证,10 点才可以搞完。所以不能这么搞。
|
||||
|
||||
### 优化后的方案
|
||||
|
||||
一开始上来就是 32 个库,每个库 32 个表,那么总共是 1024 张表。
|
||||
|
||||
我可以告诉各位同学,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支撑还是数据量支撑都没问题。
|
||||
@ -49,7 +52,6 @@
|
||||
| 352 | 0 | 11 |
|
||||
| 4593 | 17 | 15 |
|
||||
|
||||
|
||||
刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 mysql 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 mysql 服务器之间做迁移就可以了。然后系统配合改一下配置即可。
|
||||
|
||||
比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是 1024 个表。
|
||||
@ -65,4 +67,4 @@
|
||||
3. 扩容的时候,申请增加更多的数据库服务器,装好 mysql,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。
|
||||
4. 由 dba 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。
|
||||
5. 我们这边就是修改一下配置,调整迁移的库所在数据库服务器的地址。
|
||||
6. 重新发布系统,上线,原先的路由规则变都不用变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务。
|
||||
6. 重新发布系统,上线,原先的路由规则变都不用变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务。
|
||||
|
@ -2,11 +2,15 @@
|
||||
分库分表之后,id 主键如何处理?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个**全局唯一**的 id 来支持。所以这都是你实际生产环境中必须考虑的问题。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 基于数据库的实现方案
|
||||
|
||||
#### 数据库自增 id
|
||||
|
||||
这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。
|
||||
|
||||
这个方案的好处就是方便简单,谁都会用;**缺点就是单库生成**自增 id,要是高并发的话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前 id 最大值,然后自己递增几个 id,一次性返回一批 id,然后再把当前最大 id 值修改成递增几个 id 之后的一个值;但是**无论如何都是基于单个数据库**。
|
||||
@ -14,6 +18,7 @@
|
||||
**适合的场景**:你分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非是你**并发不高,但是数据量太大**导致的分库分表扩容,你可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。
|
||||
|
||||
#### 设置数据库 sequence 或者表自增字段步长
|
||||
|
||||
可以通过设置数据库 sequence 或者表的自增字段步长来进行水平伸缩。
|
||||
|
||||
比如说,现在有 8 个服务节点,每个服务节点使用一个 sequence 功能来产生 ID,每个 sequence 的起始 ID 不同,并且依次递增,步长都是 8。
|
||||
@ -23,32 +28,35 @@
|
||||
**适合的场景**:在用户防止产生的 ID 重复时,这种方案实现起来比较简单,也能达到性能目标。但是服务节点固定,步长也固定,将来如果还要增加服务节点,就不好搞了。
|
||||
|
||||
### UUID
|
||||
|
||||
好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了、占用空间大,**作为主键性能太差**了;更重要的是,UUID 不具有有序性,会导致 B+ 树索引在写的时候有过多的随机写操作(连续的 ID 可以产生部分顺序写),还有,由于在写的时候不能产生有顺序的 append 操作,而需要进行 insert 操作,将会读取整个 B+ 树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间比较大的情况下,性能下降明显。
|
||||
|
||||
适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的。
|
||||
|
||||
```java
|
||||
``` java
|
||||
UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf
|
||||
```
|
||||
|
||||
### 获取系统当前时间
|
||||
|
||||
这个就是获取当前时间即可,但是问题是,**并发很高的时候**,比如一秒并发几千,**会有重复的情况**,这个是肯定不合适的。基本就不用考虑了。
|
||||
|
||||
适合的场景:一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个 id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号。
|
||||
|
||||
### snowflake 算法
|
||||
|
||||
snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的,用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。
|
||||
|
||||
- 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
|
||||
- 41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 `2^41 - 1`,也就是可以标识 `2^41 - 1` 个毫秒值,换算成年就是表示69年的时间。
|
||||
- 10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10台机器上哪,也就是1024台机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 `2^5`个机房(32个机房),每个机房里可以代表 `2^5` 个机器(32台机器)。
|
||||
- 12 bit:这个是用来记录同一个毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 `2^12 - 1 = 4096`,也就是说可以用这个 12 bit 代表的数字来区分**同一个毫秒内**的 4096 个不同的 id。
|
||||
* 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。
|
||||
* 41 bit:表示的是时间戳,单位是毫秒。41 bit 可以表示的数字多达 `2^41 - 1` ,也就是可以标识 `2^41 - 1` 个毫秒值,换算成年就是表示69年的时间。
|
||||
* 10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10台机器上哪,也就是1024台机器。但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 `2^5` 个机房(32个机房),每个机房里可以代表 `2^5` 个机器(32台机器)。
|
||||
* 12 bit:这个是用来记录同一个毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 `2^12 - 1 = 4096` ,也就是说可以用这个 12 bit 代表的数字来区分**同一个毫秒内**的 4096 个不同的 id。
|
||||
|
||||
```
|
||||
```
|
||||
0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000
|
||||
```
|
||||
|
||||
```java
|
||||
``` java
|
||||
public class IdWorker {
|
||||
|
||||
private long workerId;
|
||||
|
@ -2,14 +2,17 @@
|
||||
现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
你看看,你现在已经明白为啥要分库分表了,你也知道常用的分库分表中间件了,你也设计好你们如何分库分表的方案了(水平拆分、垂直拆分、分表),那问题来了,你接下来该怎么把你那个单库单表的系统给迁移到分库分表上去?
|
||||
|
||||
所以这都是一环扣一环的,就是看你有没有全流程经历过这个过程。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
这个其实从 low 到高大上有好几种方案,我们都玩儿过,我都给你说一下。
|
||||
|
||||
### 停机迁移方案
|
||||
|
||||
我先给你说一个最 low 的方案,就是很简单,大家伙儿凌晨 12 点开始运维,网站或者 app 挂个公告,说 0 点到早上 6 点进行运维,无法访问。
|
||||
|
||||
接着到 0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个**导数的一次性工具**,此时直接跑起来,然后将单库单表的数据哗哗哗读出来,写到分库分表里面去。
|
||||
@ -23,6 +26,7 @@
|
||||

|
||||
|
||||
### 双写迁移方案
|
||||
|
||||
这个是我们常用的一种迁移方案,比较靠谱一些,不用停机,不用看北京凌晨 4 点的风景。
|
||||
|
||||
简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,**除了对老库增删改,都加上对新库的增删改**,这就是所谓的**双写**,同时写俩库,老库和新库。
|
||||
@ -33,4 +37,4 @@
|
||||
|
||||
接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干的。
|
||||
|
||||

|
||||

|
||||
|
@ -2,9 +2,11 @@
|
||||
为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实这块肯定是扯到**高并发**了,因为分库分表一定是为了**支撑高并发、数据量大**两个问题的。而且现在说实话,尤其是互联网类的公司面试,基本上都会来这么一下,分库分表如此普遍的技术问题,不问实在是不行,而如果你不知道那也实在是说不过去!
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 为什么要分库分表?(设计高并发系统的时候,数据库层面该如何设计?)
|
||||
说白了,分库分表是两回事儿,大家可别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。
|
||||
|
||||
@ -18,7 +20,7 @@
|
||||
|
||||
再接下来几个月,我的天,CEO 太牛逼了,公司用户数已经达到 1 亿,公司继续融资几十亿人民币啊!公司估值达到了惊人的几十亿美金,成为了国内今年最牛逼的明星创业公司!天,我们太幸运了。
|
||||
|
||||
但是我们同时也是不幸的,因为此时每天活跃用户数上千万,每天单表新增数据多达 50 万,目前一个表总数据量都已经达到了两三千万了!扛不住啊!数据库磁盘容量不断消耗掉!高峰期并发达到惊人的 `5000~8000`!别开玩笑了,哥。我跟你保证,你的系统支撑不到现在,已经挂掉了!
|
||||
但是我们同时也是不幸的,因为此时每天活跃用户数上千万,每天单表新增数据多达 50 万,目前一个表总数据量都已经达到了两三千万了!扛不住啊!数据库磁盘容量不断消耗掉!高峰期并发达到惊人的 `5000~8000` !别开玩笑了,哥。我跟你保证,你的系统支撑不到现在,已经挂掉了!
|
||||
|
||||
好吧,所以你看到这里差不多就理解分库分表是怎么回事儿了,实际上这是跟着你的公司业务发展走的,你公司业务发展越好,用户就越多,数据量越大,请求量越大,那你单个数据库一定扛不住。
|
||||
|
||||
@ -34,7 +36,6 @@
|
||||
|
||||
这就是所谓的**分库分表**,为啥要分库分表?你明白了吧。
|
||||
|
||||
|
||||
| # | 分库分表前 | 分库分表后 |
|
||||
|---|---|---|
|
||||
| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL从单机到多机,能承受的并发增加了多倍 |
|
||||
@ -42,32 +43,39 @@
|
||||
| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 |
|
||||
|
||||
### 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?
|
||||
|
||||
这个其实就是看看你了解哪些分库分表的中间件,各个中间件的优缺点是啥?然后你用过哪些分库分表的中间件。
|
||||
|
||||
比较常见的包括:
|
||||
|
||||
- Cobar
|
||||
- TDDL
|
||||
- Atlas
|
||||
- Sharding-jdbc
|
||||
- Mycat
|
||||
* Cobar
|
||||
* TDDL
|
||||
* Atlas
|
||||
* Sharding-jdbc
|
||||
* Mycat
|
||||
|
||||
#### Cobar
|
||||
|
||||
阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 Cobar 集群,Cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。
|
||||
|
||||
#### TDDL
|
||||
|
||||
淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。
|
||||
|
||||
#### Atlas
|
||||
|
||||
360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。
|
||||
|
||||
#### Sharding-jdbc
|
||||
当当开源的,属于 client 层方案,是[`ShardingSphere`](https://shardingsphere.apache.org)的 client 层方案,[`ShardingSphere`](https://shardingsphere.apache.org)还提供 proxy 层的方案 Sharding-Proxy。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且截至 2019.4,已经推出到了 `4.0.0-RC1` 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也**可以选择的方案**。
|
||||
|
||||
当当开源的,属于 client 层方案,是[ `ShardingSphere` ](https://shardingsphere.apache.org)的 client 层方案,[ `ShardingSphere` ](https://shardingsphere.apache.org)还提供 proxy 层的方案 Sharding-Proxy。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且截至 2019.4,已经推出到了 `4.0.0-RC1` 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也**可以选择的方案**。
|
||||
|
||||
#### Mycat
|
||||
|
||||
基于 Cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 Sharding jdbc 来说,年轻一些,经历的锤炼少一些。
|
||||
|
||||
#### 总结
|
||||
|
||||
综上,现在其实建议考量的,就是 Sharding-jdbc 和 Mycat,这两个都可以去考虑使用。
|
||||
|
||||
Sharding-jdbc 这种 client 层方案的**优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高**,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要**耦合** Sharding-jdbc 的依赖;
|
||||
@ -94,11 +102,10 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
|
||||
|
||||
你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都 ok 了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。
|
||||
|
||||
|
||||
而且这儿还有两种**分库分表的方式**:
|
||||
|
||||
- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
|
||||
- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
|
||||
* 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
|
||||
* 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
|
||||
|
||||
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
在搜索这块,lucene 是最流行的搜索库。几年前业内一般都问,你了解 lucene 吗?你知道倒排索引的原理吗?现在早已经 out 了,因为现在很多项目都是直接用基于 lucene 的分布式搜索引擎—— ElasticSearch,简称为 es。
|
||||
|
||||
而现在分布式搜索基本已经成为大部分互联网行业的 Java 系统的标配,其中尤为流行的就是 es,前几年 es 没火的时候,大家一般用 solr。但是这两年基本大部分企业和项目都开始转向 es 了。
|
||||
@ -11,11 +12,12 @@ es 的分布式架构原理能说一下么(es 是如何实现分布式的啊
|
||||
如果面试官问你第一个问题,确实一般都会问你 es 的分布式架构设计能介绍一下么?就看看你对分布式搜索引擎架构的一个基本理解。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
ElasticSearch 设计的理念就是分布式搜索引擎,底层其实还是基于 lucene 的。核心思想就是在多台机器上启动多个 es 进程实例,组成了一个 es 集群。
|
||||
|
||||
es 中存储数据的**基本单位是索引**,比如说你现在要在 es 中存储一些订单数据,你就应该在 es 中创建一个索引 `order_idx`,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。
|
||||
es 中存储数据的**基本单位是索引**,比如说你现在要在 es 中存储一些订单数据,你就应该在 es 中创建一个索引 `order_idx` ,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。
|
||||
|
||||
```
|
||||
```
|
||||
index -> type -> mapping -> document -> field。
|
||||
```
|
||||
|
||||
@ -25,13 +27,13 @@ index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,
|
||||
|
||||
所以就会在订单 index 里,建两个 type,一个是实物商品订单 type,一个是虚拟商品订单 type,这两个 type 大部分字段是一样的,少部分字段是不一样的。
|
||||
|
||||
很多情况下,一个 index 里可能就一个 type,但是确实如果说是一个 index 里有多个 type 的情况(**注意**,`mapping types` 这个概念在 ElasticSearch 7.X 已被完全移除,详细说明可以参考[官方文档](https://github.com/elastic/elasticsearch/blob/6.5/docs/reference/mapping/removal_of_types.asciidoc)),你可以认为 index 是一个类别的表,具体的每个 type 代表了 mysql 中的一个表。每个 type 有一个 mapping,如果你认为一个 type 是具体的一个表,index 就代表多个 type 同属于的一个类型,而 mapping 就是这个 type 的**表结构定义**,你在 mysql 中创建一个表,肯定是要定义表结构的,里面有哪些字段,每个字段是什么类型。实际上你往 index 里的一个 type 里面写的一条数据,叫做一条 document,一条 document 就代表了 mysql 中某个表里的一行,每个 document 有多个 field,每个 field 就代表了这个 document 中的一个字段的值。
|
||||
很多情况下,一个 index 里可能就一个 type,但是确实如果说是一个 index 里有多个 type 的情况(**注意**, `mapping types` 这个概念在 ElasticSearch 7. X 已被完全移除,详细说明可以参考[官方文档](https://github.com/elastic/elasticsearch/blob/6.5/docs/reference/mapping/removal_of_types.asciidoc)),你可以认为 index 是一个类别的表,具体的每个 type 代表了 mysql 中的一个表。每个 type 有一个 mapping,如果你认为一个 type 是具体的一个表,index 就代表多个 type 同属于的一个类型,而 mapping 就是这个 type 的**表结构定义**,你在 mysql 中创建一个表,肯定是要定义表结构的,里面有哪些字段,每个字段是什么类型。实际上你往 index 里的一个 type 里面写的一条数据,叫做一条 document,一条 document 就代表了 mysql 中某个表里的一行,每个 document 有多个 field,每个 field 就代表了这个 document 中的一个字段的值。
|
||||
|
||||

|
||||
|
||||
你搞一个索引,这个索引可以拆分成多个 `shard`,每个 shard 存储部分数据。拆分多个 shard 是有好处的,一是**支持横向扩展**,比如你数据量是 3T,3 个 shard,每个 shard 就 1T 的数据,若现在数据量增加到 4T,怎么扩展,很简单,重新建一个有 4 个 shard 的索引,将数据导进去;二是**提高性能**,数据分布在多个 shard,即多台服务器上,所有的操作,都会在多台机器上并行分布式执行,提高了吞吐量和性能。
|
||||
你搞一个索引,这个索引可以拆分成多个 `shard` ,每个 shard 存储部分数据。拆分多个 shard 是有好处的,一是**支持横向扩展**,比如你数据量是 3T,3 个 shard,每个 shard 就 1T 的数据,若现在数据量增加到 4T,怎么扩展,很简单,重新建一个有 4 个 shard 的索引,将数据导进去;二是**提高性能**,数据分布在多个 shard,即多台服务器上,所有的操作,都会在多台机器上并行分布式执行,提高了吞吐量和性能。
|
||||
|
||||
接着就是这个 shard 的数据实际是有多个备份,就是说每个 shard 都有一个 `primary shard`,负责写入数据,但是还有几个 `replica shard`。`primary shard` 写入数据之后,会将数据同步到其他几个 `replica shard` 上去。
|
||||
接着就是这个 shard 的数据实际是有多个备份,就是说每个 shard 都有一个 `primary shard` ,负责写入数据,但是还有几个 `replica shard` 。 `primary shard` 写入数据之后,会将数据同步到其他几个 `replica shard` 上去。
|
||||
|
||||

|
||||
|
||||
@ -43,4 +45,4 @@ es 集群多个节点,会自动选举一个节点为 master 节点,这个 ma
|
||||
|
||||
说得更简单一点,就是说如果某个非 master 节点宕机了。那么此节点上的 primary shard 不就没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。
|
||||
|
||||
其实上述就是 ElasticSearch 作为分布式搜索引擎最基本的一个架构设计。
|
||||
其实上述就是 ElasticSearch 作为分布式搜索引擎最基本的一个架构设计。
|
||||
|
@ -3,27 +3,31 @@ lucene 是最先进、功能最强大的搜索库。如果直接基于 lucene
|
||||
|
||||
elasticsearch 基于 lucene,隐藏了 lucene 的复杂性,提供了简单易用的 restful api / Java api 接口(另外还有其他语言的 api 接口)。
|
||||
|
||||
- 分布式的文档存储引擎
|
||||
- 分布式的搜索引擎和分析引擎
|
||||
- 分布式,支持 PB 级数据
|
||||
* 分布式的文档存储引擎
|
||||
* 分布式的搜索引擎和分析引擎
|
||||
* 分布式,支持 PB 级数据
|
||||
|
||||
## es 的核心概念
|
||||
|
||||
### Near Realtime
|
||||
近实时,有两层意思:
|
||||
|
||||
- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
|
||||
- 基于 es 执行搜索和分析可以达到秒级
|
||||
* 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
|
||||
* 基于 es 执行搜索和分析可以达到秒级
|
||||
|
||||
### Cluster 集群
|
||||
|
||||
集群包含多个节点,每个节点属于哪个集群都是通过一个配置来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常。
|
||||
|
||||
### Node 节点
|
||||
|
||||
Node 是集群中的一个节点,节点也有一个名称,默认是随机分配的。默认节点会去加入一个名称为 `elasticsearch` 的集群。如果直接启动一堆节点,那么它们会自动组成一个 elasticsearch 集群,当然一个节点也可以组成 elasticsearch 集群。
|
||||
|
||||
### Document & field
|
||||
|
||||
文档是 es 中最小的数据单元,一个 document 可以是一条客户数据、一条商品分类数据、一条订单数据,通常用 json 数据结构来表示。每个 index 下的 type,都可以存储多条 document。一个 document 里面有多个 field,每个 field 就是一个数据字段。
|
||||
|
||||
```json
|
||||
``` json
|
||||
{
|
||||
"product_id": "1",
|
||||
"product_name": "iPhone X",
|
||||
@ -34,15 +38,19 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
|
||||
```
|
||||
|
||||
### Index
|
||||
|
||||
索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 ducument。
|
||||
|
||||
### Type
|
||||
|
||||
类型,每个索引里可以有一个或者多个 type,type 是 index 的一个逻辑分类,比如商品 index 下有多个 type:日化商品 type、电器商品 type、生鲜商品 type。每个 type 下的 document 的 field 可能不太一样。
|
||||
|
||||
### shard
|
||||
|
||||
单台机器无法存储大量数据,es 可以将一个索引中的数据切分为多个 shard,分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个 shard 都是一个 lucene index。
|
||||
|
||||
### replica
|
||||
|
||||
任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5个 replica shard,最小的高可用配置,是 2 台服务器。
|
||||
|
||||
这么说吧,shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard,而 replica shard 一般简称为 replica。
|
||||
@ -50,10 +58,11 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
|
||||

|
||||
|
||||
## es 核心概念 vs. db 核心概念
|
||||
|
||||
| es | db |
|
||||
|---|---|
|
||||
| index | 数据库 |
|
||||
| type | 数据表 |
|
||||
| docuemnt | 一行数据 |
|
||||
|
||||
以上是一个简单的类比。
|
||||
以上是一个简单的类比。
|
||||
|
@ -2,35 +2,39 @@
|
||||
es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
|
||||
|
||||
## 面试官心理分析
|
||||
这个问题是肯定要问的,说白了,就是看你有没有实际干过 es,因为啥?其实 es 性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条数据的时候,可能你会懵逼的发现,跑个搜索怎么一下 `5~10s`,坑爹了。第一次搜索的时候,是 `5~10s`,后面反而就快了,可能就几百毫秒。
|
||||
|
||||
这个问题是肯定要问的,说白了,就是看你有没有实际干过 es,因为啥?其实 es 性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条数据的时候,可能你会懵逼的发现,跑个搜索怎么一下 `5~10s` ,坑爹了。第一次搜索的时候,是 `5~10s` ,后面反而就快了,可能就几百毫秒。
|
||||
|
||||
你就很懵,每个用户第一次访问都会比较慢,比较卡么?所以你要是没玩儿过 es,或者就是自己玩玩儿 demo,被问到这个问题容易懵逼,显示出你对 es 确实玩儿的不怎么样?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
说实话,es 性能优化是没有什么银弹的,啥意思呢?就是**不要期待着随手调一个参数,就可以万能的应对所有的性能慢的场景**。也许有的场景是你换个参数,或者调整一下语法,就可以搞定,但是绝对不是所有场景都可以这样。
|
||||
|
||||
### 性能优化的杀手锏——filesystem cache
|
||||
|
||||
你往 es 里写的数据,实际上都写到磁盘文件里去了,**查询的时候**,操作系统会将磁盘文件里的数据自动缓存到 `filesystem cache` 里面去。
|
||||
|
||||

|
||||
|
||||
es 的搜索引擎严重依赖于底层的 `filesystem cache`,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file ` 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
|
||||
es 的搜索引擎严重依赖于底层的 `filesystem cache` ,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file ` 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
|
||||
|
||||
性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1秒、5秒、10秒。但如果是走 `filesystem cache`,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。
|
||||
性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1秒、5秒、10秒。但如果是走 `filesystem cache` ,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。
|
||||
|
||||
这里有个真实的案例。某个公司 es 节点有 3 台机器,每台机器看起来内存很多,64G,总内存就是 `64 * 3 = 192G`。每台机器给 es jvm heap 是 `32G`,那么剩下来留给 `filesystem cache` 的就是每台机器才 `32G`,总共集群里给 `filesystem cache` 的就是 `32 * 3 = 96G` 内存。而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了 `1T` 的磁盘容量,es 数据量是 `1T`,那么每台机器的数据量是 `300G`。这样性能好吗? `filesystem cache` 的内存才 100G,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。
|
||||
这里有个真实的案例。某个公司 es 节点有 3 台机器,每台机器看起来内存很多,64G,总内存就是 `64 * 3 = 192G` 。每台机器给 es jvm heap 是 `32G` ,那么剩下来留给 `filesystem cache` 的就是每台机器才 `32G` ,总共集群里给 `filesystem cache` 的就是 `32 * 3 = 96G` 内存。而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了 `1T` 的磁盘容量,es 数据量是 `1T` ,那么每台机器的数据量是 `300G` 。这样性能好吗? `filesystem cache` 的内存才 100G,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。
|
||||
|
||||
归根结底,你要让 es 性能要好,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半。
|
||||
|
||||
根据我们自己的生产环境实践经验,最佳的情况下,是仅仅在 es 中就存少量的数据,就是你要**用来搜索的那些索引**,如果内存留给 `filesystem cache` 的是 100G,那么你就将索引数据控制在 `100G` 以内,这样的话,你的数据几乎全部走内存来搜索,性能非常之高,一般可以在 1 秒以内。
|
||||
|
||||
比如说你现在有一行数据。`id,name,age ....` 30 个字段。但是你现在搜索,只需要根据 `id,name,age` 三个字段来搜索。如果你傻乎乎往 es 里写入一行数据所有的字段,就会导致说 `90%` 的数据是不用来搜索的,结果硬是占据了 es 机器上的 `filesystem cache` 的空间,单条数据的数据量越大,就会导致 `filesystem cahce` 能缓存的数据就越少。其实,仅仅写入 es 中要用来检索的**少数几个字段**就可以了,比如说就写入 es `id,name,age` 三个字段,然后你可以把其他的字段数据存在 mysql/hbase 里,我们一般是建议用 `es + hbase` 这么一个架构。
|
||||
比如说你现在有一行数据。 `id,name,age ....` 30 个字段。但是你现在搜索,只需要根据 `id,name,age` 三个字段来搜索。如果你傻乎乎往 es 里写入一行数据所有的字段,就会导致说 `90%` 的数据是不用来搜索的,结果硬是占据了 es 机器上的 `filesystem cache` 的空间,单条数据的数据量越大,就会导致 `filesystem cahce` 能缓存的数据就越少。其实,仅仅写入 es 中要用来检索的**少数几个字段**就可以了,比如说就写入 es `id,name,age` 三个字段,然后你可以把其他的字段数据存在 mysql/hbase 里,我们一般是建议用 `es + hbase` 这么一个架构。
|
||||
|
||||
hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可以写入海量数据,但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 `doc id`,然后根据 `doc id` 到 hbase 里去查询每个 `doc id` 对应的**完整的数据**,给查出来,再返回给前端。
|
||||
hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可以写入海量数据,但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 `doc id` ,然后根据 `doc id` 到 hbase 里去查询每个 `doc id` 对应的**完整的数据**,给查出来,再返回给前端。
|
||||
|
||||
写入 es 的数据最好小于等于,或者是略微大于 es 的 filesystem cache 的内存容量。然后你从 es 检索可能就花费 20ms,然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,可能你原来那么玩儿,1T 数据都放 es,会每次查询都是 5~10s,现在可能性能就会很高,每次查询就是 50ms。
|
||||
|
||||
### 数据预热
|
||||
|
||||
假如说,哪怕是你就按照上述的方案去做了,es 集群中每个机器写入的数据量还是超过了 `filesystem cache` 一倍,比如说你写入一台机器 60G 数据,结果 `filesystem cache` 就 30G,还是有 30G 数据留在了磁盘上。
|
||||
|
||||
其实可以做**数据预热**。
|
||||
@ -42,11 +46,13 @@ hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可
|
||||
对于那些你觉得比较热的、经常会有人访问的数据,最好**做一个专门的缓存预热子系统**,就是对热数据每隔一段时间,就提前访问一下,让数据进入 `filesystem cache` 里面去。这样下次别人访问的时候,性能一定会好很多。
|
||||
|
||||
### 冷热分离
|
||||
|
||||
es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将**冷数据写入一个索引中,然后热数据写入另外一个索引中**,这样可以确保热数据在被预热之后,尽量都让他们留在 `filesystem os cache` 里,**别让冷数据给冲刷掉**。
|
||||
|
||||
你看,假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据,每个索引 3 个 shard。3 台机器放热数据 index,另外 3 台机器放冷数据 index。然后这样的话,你大量的时间是在访问热数据 index,热数据可能就占总数据量的 10%,此时数据量很少,几乎全都保留在 `filesystem cache` 里面了,就可以确保热数据的访问性能是很高的。但是对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,大家互相之间都没什么联系了。如果有人访问冷数据,可能大量数据是在磁盘上的,此时性能差点,就 10% 的人去访问冷数据,90% 的人在访问热数据,也无所谓了。
|
||||
|
||||
### document 模型设计
|
||||
|
||||
对于 MySQL,我们经常有一些复杂的关联查询。在 es 里该怎么玩儿,es 里面的复杂的关联查询尽量别用,一旦用了性能一般都不太好。
|
||||
|
||||
最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 es 中。搜索的时候,就不需要利用 es 的搜索语法来完成 join 之类的关联搜索了。
|
||||
@ -54,6 +60,7 @@ es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少
|
||||
document 模型设计是非常重要的,很多操作,不要在搜索的时候才想去执行各种复杂的乱七八糟的操作。es 能支持的操作就那么多,不要考虑用 es 做一些它不好操作的事情。如果真的有那种操作,尽量在 document 模型设计的时候,写入的时候就完成。另外对于一些太复杂的操作,比如 join/nested/parent-child 搜索都要尽量避免,性能都很差的。
|
||||
|
||||
### 分页性能优化
|
||||
|
||||
es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 1000 条数据都查到一个协调节点上,如果你有个 5 个 shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。
|
||||
|
||||
分布式的,你要查第 100 页的 10 条数据,不可能说从 5 个 shard,每个 shard 就查 2 条数据,最后到协调节点合并成 10 条数据吧?你**必须**得从每个 shard 都查 1000 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所以用 es 做分页的时候,你会发现越翻到后面,就越是慢。
|
||||
@ -61,11 +68,14 @@ es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10
|
||||
我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时候,基本上就要 5~10 秒才能查出来一页数据了。
|
||||
|
||||
有什么解决方案吗?
|
||||
|
||||
#### 不允许深度分页(默认深度分页性能很差)
|
||||
|
||||
跟产品经理说,你系统不允许翻那么深的页,默认翻的越深,性能就越差。
|
||||
|
||||
#### 类似于 app 里的推荐商品不断下拉出来一页一页的
|
||||
类似于微博中,下拉刷微博,刷出来一页一页的,你可以用 `scroll api`,关于如何使用,自行上网搜索。
|
||||
|
||||
类似于微博中,下拉刷微博,刷出来一页一页的,你可以用 `scroll api` ,关于如何使用,自行上网搜索。
|
||||
|
||||
scroll 会一次性给你生成**所有数据的一个快照**,然后每次滑动向后翻页就是通过**游标** `scroll_id` 移动,获取下一页下一页这样子,性能会比上面说的那种分页性能要高很多很多,基本上都是毫秒级的。
|
||||
|
||||
@ -73,4 +83,4 @@ scroll 会一次性给你生成**所有数据的一个快照**,然后每次滑
|
||||
|
||||
初始化时必须指定 `scroll` 参数,告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。
|
||||
|
||||
除了用 `scroll api`,你也可以用 `search_after` 来做,`search_after` 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 sort 字段。
|
||||
除了用 `scroll api` ,你也可以用 `search_after` 来做, `search_after` 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 sort 字段。
|
||||
|
@ -2,6 +2,7 @@
|
||||
es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
这个问题,包括后面的 redis 什么的,谈到 es、redis、mysql 分库分表等等技术,面试必问!就是你生产环境咋部署的?说白了,这个问题没啥技术含量,就是看你有没有在真正的生产环境里干过这事儿!
|
||||
|
||||
有些同学可能是没在生产环境中干过的,没实际去拿线上机器部署过 es 集群,也没实际玩儿过,也没往 es 集群里面导入过几千万甚至是几亿的数据量,可能你就不太清楚这里面的一些生产项目中的细节。
|
||||
@ -9,12 +10,13 @@ es 生产集群的部署架构是什么?每个索引的数据量大概有多
|
||||
如果你是自己就玩儿过 demo,没碰过真实的 es 集群,那你可能此时会懵。别懵,你一定要云淡风轻的回答出来这个问题,表示你确实干过这事儿。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
其实这个问题没啥,如果你确实干过 es,那你肯定了解你们生产 es 集群的实际情况,部署了几台机器?有多少个索引?每个索引有多大数据量?每个索引给了多少个分片?你肯定知道!
|
||||
|
||||
但是如果你确实没干过,也别虚,我给你说一个基本的版本,你到时候就简单说一下就好了。
|
||||
|
||||
- es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。
|
||||
- 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。
|
||||
- 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。
|
||||
* es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。
|
||||
* 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。
|
||||
* 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。
|
||||
|
||||
大概就这么说一下就行了。
|
||||
大概就这么说一下就行了。
|
||||
|
@ -2,41 +2,47 @@
|
||||
es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
问这个,其实面试官就是要看看你了解不了解 es 的一些基本原理,因为用 es 无非就是写入数据,搜索数据。你要是不明白你发起一个写入和搜索请求的时候,es 在干什么,那你真的是......
|
||||
|
||||
对 es 基本就是个黑盒,你还能干啥?你唯一能干的就是用 es 的 api 读写数据了。要是出点什么问题,你啥都不知道,那还能指望你什么呢?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### es 写数据过程
|
||||
- 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node`(协调节点)。
|
||||
- `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。
|
||||
- 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。
|
||||
- `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。
|
||||
|
||||
* 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node` (协调节点)。
|
||||
* `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。
|
||||
* 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node` 。
|
||||
* `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。
|
||||
|
||||

|
||||
|
||||
### es 读数据过程
|
||||
|
||||
可以通过 `doc id` 来查询,会根据 `doc id` 进行 hash,判断出来当时把 `doc id` 分配到了哪个 shard 上面去,从那个 shard 去查询。
|
||||
|
||||
- 客户端发送请求到**任意**一个 node,成为 `coordinate node`。
|
||||
- `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。
|
||||
- 接收请求的 node 返回 document 给 `coordinate node`。
|
||||
- `coordinate node` 返回 document 给客户端。
|
||||
* 客户端发送请求到**任意**一个 node,成为 `coordinate node` 。
|
||||
* `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。
|
||||
* 接收请求的 node 返回 document 给 `coordinate node` 。
|
||||
* `coordinate node` 返回 document 给客户端。
|
||||
|
||||
### es 搜索数据过程
|
||||
|
||||
es 最强大的是做全文检索,就是比如你有三条数据:
|
||||
```
|
||||
|
||||
```
|
||||
java真好玩儿啊
|
||||
java好难学啊
|
||||
j2ee特别牛
|
||||
```
|
||||
|
||||
你根据 `java` 关键词来搜索,将包含 `java`的 `document` 给搜索出来。es 就会给你返回:java真好玩儿啊,java好难学啊。
|
||||
你根据 `java` 关键词来搜索,将包含 `java` 的 `document` 给搜索出来。es 就会给你返回:java真好玩儿啊,java好难学啊。
|
||||
|
||||
- 客户端发送请求到一个 `coordinate node`。
|
||||
- 协调节点将搜索请求转发到**所有**的 shard 对应的 `primary shard` 或 `replica shard`,都可以。
|
||||
- query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id`)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
|
||||
- fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。
|
||||
* 客户端发送请求到一个 `coordinate node` 。
|
||||
* 协调节点将搜索请求转发到**所有**的 shard 对应的 `primary shard` 或 `replica shard` ,都可以。
|
||||
* query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id` )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
|
||||
* fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。
|
||||
|
||||
> 写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。
|
||||
|
||||
@ -46,21 +52,21 @@ j2ee特别牛
|
||||
|
||||
先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。
|
||||
|
||||
如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据 `refresh` 到一个新的 `segment file` 中,但是此时数据不是直接进入 `segment file` 磁盘文件,而是先进入 `os cache` 。这个过程就是 `refresh`。
|
||||
如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据 `refresh` 到一个新的 `segment file` 中,但是此时数据不是直接进入 `segment file` 磁盘文件,而是先进入 `os cache` 。这个过程就是 `refresh` 。
|
||||
|
||||
每隔 1 秒钟,es 将 buffer 中的数据写入一个**新的** `segment file`,每秒钟会产生一个**新的磁盘文件** `segment file`,这个 `segment file` 中就存储最近 1 秒内 buffer 中写入的数据。
|
||||
每隔 1 秒钟,es 将 buffer 中的数据写入一个**新的** `segment file` ,每秒钟会产生一个**新的磁盘文件** `segment file` ,这个 `segment file` 中就存储最近 1 秒内 buffer 中写入的数据。
|
||||
|
||||
但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。
|
||||
|
||||
操作系统里面,磁盘文件其实都有一个东西,叫做 `os cache`,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入 `os cache`,先进入操作系统级别的一个内存缓存中去。只要 `buffer` 中的数据被 refresh 操作刷入 `os cache`中,这个数据就可以被搜索到了。
|
||||
操作系统里面,磁盘文件其实都有一个东西,叫做 `os cache` ,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入 `os cache` ,先进入操作系统级别的一个内存缓存中去。只要 `buffer` 中的数据被 refresh 操作刷入 `os cache` 中,这个数据就可以被搜索到了。
|
||||
|
||||
为什么叫 es 是**准实时**的? `NRT`,全称 `near real-time`。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的 `restful api` 或者 `java api`,**手动**执行一次 refresh 操作,就是手动将 buffer 中的数据刷入 `os cache`中,让数据立马就可以被搜索到。只要数据被输入 `os cache` 中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。
|
||||
为什么叫 es 是**准实时**的? `NRT` ,全称 `near real-time` 。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的 `restful api` 或者 `java api` ,**手动**执行一次 refresh 操作,就是手动将 buffer 中的数据刷入 `os cache` 中,让数据立马就可以被搜索到。只要数据被输入 `os cache` 中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。
|
||||
|
||||
重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 `buffer` 数据写入一个又一个新的 `segment file` 中去,每次 `refresh` 完 buffer 清空,translog 保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 `commit` 操作。
|
||||
|
||||
commit 操作发生第一步,就是将 buffer 中现有数据 `refresh` 到 `os cache` 中去,清空 buffer。然后,将一个 `commit point` 写入磁盘文件,里面标识着这个 `commit point` 对应的所有 `segment file`,同时强行将 `os cache` 中目前所有的数据都 `fsync` 到磁盘文件中去。最后**清空** 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。
|
||||
commit 操作发生第一步,就是将 buffer 中现有数据 `refresh` 到 `os cache` 中去,清空 buffer。然后,将一个 `commit point` 写入磁盘文件,里面标识着这个 `commit point` 对应的所有 `segment file` ,同时强行将 `os cache` 中目前所有的数据都 `fsync` 到磁盘文件中去。最后**清空** 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。
|
||||
|
||||
这个 commit 操作叫做 `flush`。默认 30 分钟自动执行一次 `flush`,但如果 translog 过大,也会触发 `flush`。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。
|
||||
这个 commit 操作叫做 `flush` 。默认 30 分钟自动执行一次 `flush` ,但如果 translog 过大,也会触发 `flush` 。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。
|
||||
|
||||
translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 `translog` 中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。
|
||||
|
||||
@ -73,18 +79,21 @@ translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁
|
||||
> 数据写入 segment file 之后,同时就建立好了倒排索引。
|
||||
|
||||
### 删除/更新数据底层原理
|
||||
|
||||
如果是删除操作,commit 的时候会生成一个 `.del` 文件,里面将某个 doc 标识为 `deleted` 状态,那么搜索的时候根据 `.del` 文件就知道这个 doc 是否被删除了。
|
||||
|
||||
如果是更新操作,就是将原来的 doc 标识为 `deleted` 状态,然后新写入一条数据。
|
||||
|
||||
buffer 每 refresh 一次,就会产生一个 `segment file`,所以默认情况下是 1 秒钟一个 `segment file`,这样下来 `segment file` 会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个 `segment file` 合并成一个,同时这里会将标识为 `deleted` 的 doc 给**物理删除掉**,然后将新的 `segment file` 写入磁盘,这里会写一个 `commit point`,标识所有新的 `segment file`,然后打开 `segment file` 供搜索使用,同时删除旧的 `segment file`。
|
||||
buffer 每 refresh 一次,就会产生一个 `segment file` ,所以默认情况下是 1 秒钟一个 `segment file` ,这样下来 `segment file` 会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个 `segment file` 合并成一个,同时这里会将标识为 `deleted` 的 doc 给**物理删除掉**,然后将新的 `segment file` 写入磁盘,这里会写一个 `commit point` ,标识所有新的 `segment file` ,然后打开 `segment file` 供搜索使用,同时删除旧的 `segment file` 。
|
||||
|
||||
### 底层 lucene
|
||||
|
||||
简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。我们用 Java 开发的时候,引入 lucene jar,然后基于 lucene 的 api 去开发就可以了。
|
||||
|
||||
通过 lucene,我们可以将已有的数据建立索引,lucene 会在本地磁盘上面,给我们组织索引的数据结构。
|
||||
|
||||
### 倒排索引
|
||||
|
||||
在搜索引擎中,每个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。例如,文档 1 经过分词,提取了 20 个关键词,每个关键词都会记录它在文档中出现的次数和出现位置。
|
||||
|
||||
那么,倒排索引就是**关键词到文档** ID 的映射,每个关键词都对应着一系列的文件,这些文件中都出现了关键词。
|
||||
@ -105,25 +114,25 @@ buffer 每 refresh 一次,就会产生一个 `segment file`,所以默认情
|
||||
|
||||
| WordId | Word | DocIds |
|
||||
|---|---|---|
|
||||
| 1 | 谷歌 | 1,2,3,4,5 |
|
||||
| 2 | 地图 | 1,2,3,4,5 |
|
||||
| 3 | 之父 | 1,2,4,5 |
|
||||
| 4 | 跳槽 | 1,4 |
|
||||
| 5 | Facebook | 1,2,3,4,5 |
|
||||
| 6 | 加盟 | 2,3,5 |
|
||||
| 1 | 谷歌 | 1, 2, 3, 4, 5 |
|
||||
| 2 | 地图 | 1, 2, 3, 4, 5 |
|
||||
| 3 | 之父 | 1, 2, 4, 5 |
|
||||
| 4 | 跳槽 | 1, 4 |
|
||||
| 5 | Facebook | 1, 2, 3, 4, 5 |
|
||||
| 6 | 加盟 | 2, 3, 5 |
|
||||
| 7 | 创始人 | 3 |
|
||||
| 8 | 拉斯 | 3,5 |
|
||||
| 8 | 拉斯 | 3, 5 |
|
||||
| 9 | 离开 | 3 |
|
||||
| 10 | 与 | 4 |
|
||||
| .. | .. | .. |
|
||||
|
||||
另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。
|
||||
|
||||
那么,有了倒排索引,搜索引擎可以很方便地响应用户的查询。比如用户输入查询 `Facebook`,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。
|
||||
那么,有了倒排索引,搜索引擎可以很方便地响应用户的查询。比如用户输入查询 `Facebook` ,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。
|
||||
|
||||
要注意倒排索引的两个重要细节:
|
||||
|
||||
- 倒排索引中的所有词项对应一个或多个文档;
|
||||
- 倒排索引中的词项**根据字典顺序升序排列**
|
||||
* 倒排索引中的所有词项对应一个或多个文档;
|
||||
* 倒排索引中的词项**根据字典顺序升序排列**
|
||||
|
||||
> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。
|
||||
> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。
|
||||
|
@ -2,6 +2,7 @@
|
||||
如何设计一个高并发系统?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先。
|
||||
|
||||
如果你确实有真才实学,在互联网公司里干过高并发系统,那你确实拿 offer 基本如探囊取物,没啥问题。面试官也绝对不会这样来问你,否则他就是蠢。
|
||||
@ -16,8 +17,8 @@
|
||||
|
||||
所以这个时候你必须得做一把个人秀了,秀出你所有关于高并发的知识!
|
||||
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
其实所谓的高并发,如果你要理解这个问题呢,其实就得从高并发的根源出发,为啥会有高并发?为啥高并发就很牛逼?
|
||||
|
||||
我说的浅显一点,很简单,就是因为刚开始系统都是连接数据库的,但是要知道数据库支撑到每秒并发两三千的时候,基本就快完了。所以才有说,很多公司,刚开始干的时候,技术比较 low,结果业务发展太快,有的时候系统扛不住压力就挂了。
|
||||
@ -30,36 +31,41 @@
|
||||
|
||||
可以分为以下 6 点:
|
||||
|
||||
- 系统拆分
|
||||
- 缓存
|
||||
- MQ
|
||||
- 分库分表
|
||||
- 读写分离
|
||||
- ElasticSearch
|
||||
* 系统拆分
|
||||
* 缓存
|
||||
* MQ
|
||||
* 分库分表
|
||||
* 读写分离
|
||||
* ElasticSearch
|
||||
|
||||

|
||||
|
||||
### 系统拆分
|
||||
|
||||
将一个系统拆分为多个子系统,用 dubbo 来搞。然后每个系统连一个数据库,这样本来就一个库,现在多个数据库,不也可以扛高并发么。
|
||||
|
||||
### 缓存
|
||||
|
||||
缓存,必须得用缓存。大部分的高并发场景,都是**读多写少**,那你完全可以在数据库和缓存里都写一份,然后读的时候大量走缓存不就得了。毕竟人家 redis 轻轻松松单机几万的并发。所以你可以考虑考虑你的项目里,那些承载主要请求的**读场景,怎么用缓存来抗高并发**。
|
||||
|
||||
### MQ
|
||||
|
||||
MQ,必须得用 MQ。可能你还是会出现高并发写的场景,比如说一个业务操作里要频繁搞数据库几十次,增删改增删改,疯了。那高并发绝对搞挂你的系统,你要是用 redis 来承载写那肯定不行,人家是缓存,数据随时就被 LRU 了,数据格式还无比简单,没有事务支持。所以该用 mysql 还得用 mysql 啊。那你咋办?用 MQ 吧,大量的写请求灌入 MQ 里,排队慢慢玩儿,**后边系统消费后慢慢写**,控制在 mysql 承载范围之内。所以你得考虑考虑你的项目里,那些承载复杂写业务逻辑的场景里,如何用 MQ 来异步写,提升并发性。MQ 单机抗几万并发也是 ok 的,这个之前还特意说过。
|
||||
|
||||
### 分库分表
|
||||
|
||||
分库分表,可能到了最后数据库层面还是免不了抗高并发的要求,好吧,那么就将一个数据库拆分为多个库,多个库来扛更高的并发;然后将一个表**拆分为多个表**,每个表的数据量保持少一点,提高 sql 跑的性能。
|
||||
|
||||
### 读写分离
|
||||
|
||||
读写分离,这个就是说大部分时候数据库可能也是读多写少,没必要所有请求都集中在一个库上吧,可以搞个主从架构,**主库写**入,**从库读**取,搞一个读写分离。**读流量太多**的时候,还可以**加更多的从库**。
|
||||
|
||||
### ElasticSearch
|
||||
Elasticsearch,简称 es。es 是分布式的,可以随便扩容,分布式天然就可以支撑高并发,因为动不动就可以扩容加机器来扛更高的并发。那么一些比较简单的查询、统计类的操作,可以考虑用 es 来承载,还有一些全文搜索类的操作,也可以考虑用 es 来承载。
|
||||
|
||||
Elasticsearch,简称 es。es 是分布式的,可以随便扩容,分布式天然就可以支撑高并发,因为动不动就可以扩容加机器来扛更高的并发。那么一些比较简单的查询、统计类的操作,可以考虑用 es 来承载,还有一些全文搜索类的操作,也可以考虑用 es 来承载。
|
||||
|
||||
上面的 6 点,基本就是高并发系统肯定要干的一些事儿,大家可以仔细结合之前讲过的知识考虑一下,到时候你可以系统的把这块阐述一下,然后每个部分要注意哪些问题,之前都讲过了,你都可以阐述阐述,表明你对这块是有点积累的。
|
||||
|
||||
说句实话,毕竟你真正厉害的一点,不是在于弄明白一些技术,或者大概知道一个高并发系统应该长什么样?其实实际上在真正的复杂的业务系统里,做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑:哪些需要分库分表,哪些不需要分库分表,单库单表跟分库分表如何 join,哪些数据要放到缓存里去,放哪些数据才可以扛住高并发的请求,你需要完成对一个复杂业务系统的分析之后,然后逐步逐步的加入高并发的系统架构的改造,这个过程是无比复杂的,一旦做过一次,并且做好了,你在这个市场上就会非常的吃香。
|
||||
|
||||
其实大部分公司,真正看重的,不是说你掌握高并发相关的一些基本的架构知识,架构中的一些技术,RocketMQ、Kafka、Redis、Elasticsearch,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实践过高并发架构的人,这个经验是难能可贵的。
|
||||
其实大部分公司,真正看重的,不是说你掌握高并发相关的一些基本的架构知识,架构中的一些技术,RocketMQ、Kafka、Redis、Elasticsearch,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实践过高并发架构的人,这个经验是难能可贵的。
|
||||
|
@ -2,24 +2,29 @@
|
||||
如何保证消息队列的高可用?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](/docs/high-concurrency/why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。
|
||||
|
||||
要是你傻乎乎的就干用了一个 MQ,各种问题从来没考虑过,那你就杯具了,面试官对你的感觉就是,只会简单使用一些技术,没任何思考,马上对你的印象就不太好了。这样的同学招进来要是做个 20k 薪资以内的普通小弟还凑合,要是做薪资 20k+ 的高工,那就惨了,让你设计个系统,里面肯定一堆坑,出了事故公司受损失,团队一起背锅。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
这个问题这么问是很好的,因为不能问你 Kafka 的高可用性怎么保证?ActiveMQ 的高可用性怎么保证?一个面试官要是这么问就显得很没水平,人家可能用的就是 RabbitMQ,没用过 Kafka,你上来问人家 Kafka 干什么?这不是摆明了刁难人么。
|
||||
|
||||
所以有水平的面试官,问的是 MQ 的高可用性怎么保证?这样就是你用过哪个 MQ,你就说说你对那个 MQ 的高可用性的理解。
|
||||
|
||||
### RabbitMQ 的高可用性
|
||||
|
||||
RabbitMQ 是比较有代表性的,因为是**基于主从**(非分布式)做高可用性的,我们就以 RabbitMQ 为例子讲解第一种 MQ 的高可用性怎么实现。
|
||||
|
||||
RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。
|
||||
|
||||
#### 单机模式
|
||||
|
||||
单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的😄,没人生产用单机模式。
|
||||
|
||||
#### 普通集群模式(无高可用性)
|
||||
|
||||
普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
|
||||
|
||||

|
||||
@ -31,6 +36,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
|
||||
所以这个事儿就比较尴尬了,这就**没有什么所谓的高可用性**,**这方案主要是提高吞吐量的**,就是说让集群中多个节点来服务某个 queue 的读写操作。
|
||||
|
||||
#### 镜像集群模式(高可用性)
|
||||
|
||||
这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。
|
||||
|
||||

|
||||
@ -40,6 +46,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
|
||||
这样的话,好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!第二,这么玩儿,不是分布式的,就**没有扩展性可言**了,如果某个 queue 负载很重,你加机器,新增的机器也包含了这个 queue 的所有数据,并**没有办法线性扩展**你的 queue。你想,如果这个 queue 的数据量很大,大到这个机器上的容量无法容纳了,此时该怎么办呢?
|
||||
|
||||
### Kafka 的高可用性
|
||||
|
||||
Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker 是一个节点;你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据。
|
||||
|
||||
这就是**天然的分布式消息队列**,就是说一个 topic 的数据,是**分散放在多个机器上的,每个机器就放一部分数据**。
|
||||
|
@ -2,19 +2,22 @@
|
||||
如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实问这个问题,主要是考考你,redis 单机能承载多高并发?如果单机扛不住如何扩容扛更多的并发?redis 会不会挂?既然 redis 会挂那怎么保证 redis 是高可用的?
|
||||
|
||||
其实针对的都是项目中你肯定要考虑的一些问题,如果你没考虑过,那确实你对生产系统中的问题思考太少。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
如果你用 redis 缓存技术的话,肯定要考虑如何用 redis 来加多台机器,保证 redis 是高并发的,还有就是如何让 redis 保证自己不是挂掉以后就直接死掉了,即 redis 高可用。
|
||||
|
||||
由于此节内容较多,因此,会分为两个小节进行讲解。
|
||||
- [redis 主从架构](/docs/high-concurrency/redis-master-slave.md)
|
||||
- [redis 基于哨兵实现高可用](/docs/high-concurrency/redis-sentinel.md)
|
||||
|
||||
* [redis 主从架构](/docs/high-concurrency/redis-master-slave.md)
|
||||
* [redis 基于哨兵实现高可用](/docs/high-concurrency/redis-sentinel.md)
|
||||
|
||||
redis 实现**高并发**主要依靠**主从架构**,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。
|
||||
|
||||
如果想要在实现高并发的同时,容纳大量的数据,那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒几十万的读写并发。
|
||||
|
||||
redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。
|
||||
redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。
|
||||
|
@ -2,9 +2,11 @@
|
||||
如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实这是很常见的一个问题,这俩问题基本可以连起来问。既然是消费消息,那肯定要考虑会不会重复消费?能不能避免重复消费?或者重复消费了也别造成系统异常可以吗?这个是 MQ 领域的基本问题,其实本质上还是问你**使用消息队列如何保证幂等性**,这个是你架构里要考虑的一个问题。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
回答这个问题,首先你别听到重复消息这个事儿,就一无所知吧,你**先大概说一说可能会有哪些重复消费的问题**。
|
||||
|
||||
首先,比如 RabbitMQ、RocketMQ、Kafka,都有可能会出现消息重复消费的问题,正常。因为这问题通常不是 MQ 自己保证的,是由我们开发来保证的。挑一个 Kafka 来举个例子,说说怎么重复消费吧。
|
||||
@ -33,11 +35,11 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一
|
||||
|
||||
其实还是得结合业务来思考,我这里给几个思路:
|
||||
|
||||
- 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
|
||||
- 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
|
||||
- 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
|
||||
- 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
|
||||
* 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
|
||||
* 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
|
||||
* 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
|
||||
* 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
|
||||
|
||||

|
||||
|
||||
当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。
|
||||
当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。
|
||||
|
@ -2,9 +2,11 @@
|
||||
如何保证消息的顺序性?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实这个也是用 MQ 的时候必问的话题,第一看看你了不了解顺序这个事儿?第二看看你有没有办法保证消息是有顺序的?这是生产系统中常见的问题。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
我举个例子,我们以前做过一个 mysql `binlog` 同步的系统,压力还是非常大的,日同步数据要达到上亿,就是说数据从一个 mysql 库原封不动地同步到另一个 mysql 库里面去(mysql -> mysql)。常见的一点在于说比如大数据 team,就需要同步一个 mysql 库过来,对公司的业务系统的数据做各种复杂的操作。
|
||||
|
||||
你在 mysql 里增删改一条数据,对应出来了增删改 3 条 `binlog` 日志,接着这三条 `binlog` 发送到 MQ 里面,再消费出来依次执行,起码得保证人家是按照顺序来的吧?不然本来是:增加、修改、删除;你愣是换了顺序给执行成删除、修改、增加,不全错了么。
|
||||
@ -12,21 +14,25 @@
|
||||
本来这个数据同步过来,应该最后这个数据被删除了;结果你搞错了这个顺序,最后这个数据保留下来了,数据同步就出错了。
|
||||
|
||||
先看看顺序会错乱的俩场景:
|
||||
- **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。
|
||||
|
||||
* **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。
|
||||
|
||||

|
||||
|
||||
- **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。<br>消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。
|
||||
* **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。<br>消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。
|
||||
|
||||

|
||||
|
||||
### 解决方案
|
||||
|
||||
#### RabbitMQ
|
||||
拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
|
||||
|
||||

|
||||
|
||||
#### Kafka
|
||||
- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
|
||||
- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
|
||||
|
||||
* 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
|
||||
* 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
|
||||
|
||||

|
||||
|
@ -2,22 +2,26 @@
|
||||
如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
这个是肯定的,用 MQ 有个基本原则,就是**数据不能多一条,也不能少一条**,不能多,就是前面说的[重复消费和幂等性问题](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)。不能少,就是说这数据别搞丢了。那这个问题你必须得考虑一下。
|
||||
|
||||
如果说你这个是用 MQ 来传递非常核心的消息,比如说计费、扣费的一些消息,那必须确保这个 MQ 传递过程中**绝对不会把计费消息给弄丢**。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
数据的丢失问题,可能出现在生产者、MQ、消费者中,咱们从 RabbitMQ 和 Kafka 分别来分析一下吧。
|
||||
|
||||
### RabbitMQ
|
||||
|
||||

|
||||
|
||||
#### 生产者弄丢了数据
|
||||
|
||||
生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。
|
||||
|
||||
此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务`channel.txSelect`,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务`channel.txRollback`,然后重试发送消息;如果收到了消息,那么可以提交事务`channel.txCommit`。
|
||||
```java
|
||||
此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务 `channel.txSelect` ,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 `channel.txRollback` ,然后重试发送消息;如果收到了消息,那么可以提交事务 `channel.txCommit` 。
|
||||
|
||||
``` java
|
||||
// 开启事务
|
||||
channel.txSelect
|
||||
try {
|
||||
@ -41,25 +45,30 @@ channel.txCommit
|
||||
所以一般在生产者这块**避免数据丢失**,都是用 `confirm` 机制的。
|
||||
|
||||
#### RabbitMQ 弄丢了数据
|
||||
|
||||
就是 RabbitMQ 自己弄丢了数据,这个你必须**开启 RabbitMQ 的持久化**,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,**恢复之后会自动读取之前存储的数据**,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,**可能导致少量数据丢失**,但是这个概率较小。
|
||||
|
||||
设置持久化有**两个步骤**:
|
||||
|
||||
- 创建 queue 的时候将其设置为持久化<br>
|
||||
* 创建 queue 的时候将其设置为持久化<br>
|
||||
|
||||
这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。
|
||||
- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2<br>
|
||||
|
||||
* 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2<br>
|
||||
|
||||
就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。
|
||||
|
||||
必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。
|
||||
|
||||
注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。
|
||||
|
||||
所以,持久化可以跟生产者那边的 `confirm` 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 `ack` 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 `ack`,你也是可以自己重发的。
|
||||
所以,持久化可以跟生产者那边的 `confirm` 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 `ack` 了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 `ack` ,你也是可以自己重发的。
|
||||
|
||||
#### 消费端弄丢了数据
|
||||
|
||||
RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费到,还没处理,结果进程挂了**,比如重启了,那么就尴尬了,RabbitMQ 认为你都消费了,这数据就丢了。
|
||||
|
||||
这个时候得用 RabbitMQ 提供的 `ack` 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 `ack`,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 `ack` 一把。这样的话,如果你还没处理完,不就没有 `ack` 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
|
||||
这个时候得用 RabbitMQ 提供的 `ack` 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 `ack` ,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 `ack` 一把。这样的话,如果你还没处理完,不就没有 `ack` 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
|
||||
|
||||

|
||||
|
||||
@ -80,12 +89,13 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
|
||||
|
||||
所以此时一般是要求起码设置如下 4 个参数:
|
||||
|
||||
- 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
|
||||
- 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。
|
||||
- 在 producer 端设置 `acks=all`:这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。
|
||||
- 在 producer 端设置 `retries=MAX`(很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。
|
||||
* 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。
|
||||
* 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。
|
||||
* 在 producer 端设置 `acks=all` :这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。
|
||||
* 在 producer 端设置 `retries=MAX` (很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。
|
||||
|
||||
我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。
|
||||
|
||||
#### 生产者会不会弄丢数据?
|
||||
如果按照上述的思路设置了 `acks=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
|
||||
|
||||
如果按照上述的思路设置了 `acks=all` ,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
|
||||
|
@ -1,8 +1,10 @@
|
||||
# 如何限流?在工作中是怎么做的?说一下具体的实现?
|
||||
- Author: [HuiFer](https://github.com/huifer)
|
||||
- Description: 该文简单介绍限流相关技术以及实现
|
||||
|
||||
* Author: [HuiFer](https://github.com/huifer)
|
||||
* Description: 该文简单介绍限流相关技术以及实现
|
||||
|
||||
## 什么是限流
|
||||
|
||||
> 限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。
|
||||
|
||||
## 限流方法
|
||||
@ -10,8 +12,10 @@
|
||||
### 计数器
|
||||
|
||||
#### 实现方式
|
||||
- 控制单位时间内的请求数量
|
||||
```java
|
||||
|
||||
* 控制单位时间内的请求数量
|
||||
|
||||
``` java
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@ -50,17 +54,18 @@ public class Counter {
|
||||
|
||||
```
|
||||
|
||||
- 劣势
|
||||
* 劣势
|
||||
- 假设在 00:01 时发生一个请求,在 00:01-00:58 之间不在发送请求,在 00:59 时发送剩下的所有请求 `n-1` (n为限流请求数量),在下一分钟的 00:01 发送n个请求,这样在2秒钟内请求到达了 `2n - 1` 个.
|
||||
- 设每分钟请求数量为60个,每秒可以处理1个请求,用户在 00:59 发送 60 个请求,在 01:00 发送 60 个请求 此时2秒钟有120个请求(每秒60个请求),远远大于了每秒钟处理数量的阈值
|
||||
|
||||
|
||||
### 滑动窗口
|
||||
|
||||
#### 实现方式
|
||||
- 滑动窗口是对计数器方式的改进,增加一个时间粒度的度量单位
|
||||
|
||||
* 滑动窗口是对计数器方式的改进, 增加一个时间粒度的度量单位
|
||||
- 把一分钟分成若干等分(6份,每份10秒), 在每一份上设置独立计数器,在 00:00-00:09 之间发生请求计数器累加1.当等分数量越大限流统计就越详细
|
||||
|
||||
```java
|
||||
``` java
|
||||
package com.example.demo1.service;
|
||||
|
||||
import java.util.Iterator;
|
||||
@ -131,11 +136,9 @@ public class TimeWindow {
|
||||
*/
|
||||
public void take() {
|
||||
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
try {
|
||||
|
||||
|
||||
int size = sizeOfValid();
|
||||
if (size > max) {
|
||||
System.err.println("超限");
|
||||
@ -154,7 +157,6 @@ public class TimeWindow {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public int sizeOfValid() {
|
||||
Iterator<Long> it = queue.iterator();
|
||||
Long ms = System.currentTimeMillis() - seconds * 1000;
|
||||
@ -170,7 +172,6 @@ public class TimeWindow {
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清理过期的时间
|
||||
*/
|
||||
@ -189,9 +190,12 @@ public class TimeWindow {
|
||||
```
|
||||
|
||||
### Leaky Bucket 漏桶
|
||||
|
||||
#### 实现方式
|
||||
- 规定固定容量的桶,有水进入,有水流出. 对于流进的水我们无法估计进来的数量、速度,对于流出的水我们可以控制速度.
|
||||
```java
|
||||
|
||||
* 规定固定容量的桶, 有水进入, 有水流出. 对于流进的水我们无法估计进来的数量、速度, 对于流出的水我们可以控制速度.
|
||||
|
||||
``` java
|
||||
public class LeakBucket {
|
||||
/**
|
||||
* 时间
|
||||
@ -210,7 +214,6 @@ public class LeakBucket {
|
||||
*/
|
||||
private Double nowSize;
|
||||
|
||||
|
||||
public boolean limit() {
|
||||
long now = System.currentTimeMillis();
|
||||
nowSize = Math.max(0, (nowSize - (now - time) * rate));
|
||||
@ -227,10 +230,12 @@ public class LeakBucket {
|
||||
```
|
||||
|
||||
### Token Bucket 令牌桶
|
||||
#### 实现方式
|
||||
- 规定固定容量的桶,token 以固定速度往桶内填充,当桶满时 token 不会被继续放入,每过来一个请求把 token 从桶中移除,如果桶中没有 token 不能请求
|
||||
|
||||
```java
|
||||
#### 实现方式
|
||||
|
||||
* 规定固定容量的桶, token 以固定速度往桶内填充, 当桶满时 token 不会被继续放入, 每过来一个请求把 token 从桶中移除, 如果桶中没有 token 不能请求
|
||||
|
||||
``` java
|
||||
public class TokenBucket {
|
||||
/**
|
||||
* 时间
|
||||
@ -249,7 +254,6 @@ public class TokenBucket {
|
||||
*/
|
||||
private Double nowSize;
|
||||
|
||||
|
||||
public boolean limit() {
|
||||
long now = System.currentTimeMillis();
|
||||
nowSize = Math.min(total, nowSize + (now - time) * rate);
|
||||
@ -264,15 +268,16 @@ public class TokenBucket {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 工作中的使用
|
||||
|
||||
### spring cloud gateway
|
||||
- spring cloud gateway 默认使用redis进行限流,笔者一般只是修改修改参数属于拿来即用.并没有去从头实现上述那些算法.
|
||||
```xml
|
||||
|
||||
* spring cloud gateway 默认使用redis进行限流, 笔者一般只是修改修改参数属于拿来即用. 并没有去从头实现上述那些算法.
|
||||
|
||||
``` xml
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||
@ -283,42 +288,52 @@ public class TokenBucket {
|
||||
</dependency>
|
||||
```
|
||||
|
||||
```yaml
|
||||
``` yaml
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
|
||||
- id: requestratelimiter_route
|
||||
|
||||
uri: lb://pigx-upms
|
||||
order: 10000
|
||||
predicates:
|
||||
|
||||
- Path=/admin/**
|
||||
|
||||
filters:
|
||||
|
||||
- name: RequestRateLimiter
|
||||
|
||||
args:
|
||||
redis-rate-limiter.replenishRate: 1 # 令牌桶的容积
|
||||
redis-rate-limiter.burstCapacity: 3 # 流速 每秒
|
||||
key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean
|
||||
|
||||
- StripPrefix=1
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
``` java
|
||||
@Bean
|
||||
KeyResolver remoteAddrKeyResolver() {
|
||||
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### sentinel
|
||||
- 通过配置来控制每个url的流量
|
||||
```xml
|
||||
|
||||
* 通过配置来控制每个url的流量
|
||||
|
||||
``` xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
```
|
||||
```yaml
|
||||
|
||||
``` yaml
|
||||
spring:
|
||||
cloud:
|
||||
nacos:
|
||||
@ -337,8 +352,10 @@ spring:
|
||||
rule-type: flow
|
||||
namespace: xxxxxxxx
|
||||
```
|
||||
- 配置内容在nacos上进行编辑
|
||||
```json
|
||||
|
||||
* 配置内容在nacos上进行编辑
|
||||
|
||||
``` json
|
||||
[
|
||||
{
|
||||
"resource": "/hello",
|
||||
@ -351,12 +368,15 @@ spring:
|
||||
}
|
||||
]
|
||||
```
|
||||
- resource:资源名,即限流规则的作用对象。
|
||||
- limitApp:流控针对的调用来源,若为 default 则不区分调用来源。
|
||||
- grade:限流阈值类型,QPS 或线程数模式,0代表根据并发数量来限流,1代表根据QPS来进行流量控制。
|
||||
- count:限流阈值
|
||||
- strategy:判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口
|
||||
- controlBehavior:流控效果(直接拒绝 / 排队等待 / 慢启动模式)
|
||||
- clusterMode:是否为集群模式
|
||||
|
||||
* resource:资源名,即限流规则的作用对象。
|
||||
* limitApp:流控针对的调用来源,若为 default 则不区分调用来源。
|
||||
* grade:限流阈值类型,QPS 或线程数模式,0代表根据并发数量来限流,1代表根据QPS来进行流量控制。
|
||||
* count:限流阈值
|
||||
* strategy:判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口
|
||||
* controlBehavior:流控效果(直接拒绝 / 排队等待 / 慢启动模式)
|
||||
* clusterMode:是否为集群模式
|
||||
|
||||
### 总结
|
||||
> sentinel和spring cloud gateway两个框架都是很好的限流框架,但是在我使用中还没有将[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)接入到项目中进行使用,所以我会选择**spring cloud gateway**,当接入完整的或者接入Nacos项目使用setinel会有更加好的体验.
|
||||
|
||||
> sentinel和spring cloud gateway两个框架都是很好的限流框架, 但是在我使用中还没有将[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)接入到项目中进行使用, 所以我会选择**spring cloud gateway**, 当接入完整的或者接入Nacos项目使用setinel会有更加好的体验.
|
||||
|
@ -2,25 +2,26 @@
|
||||
如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实聊到这个问题,一般面试官要考察两块:
|
||||
|
||||
- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
|
||||
- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
|
||||
* 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
|
||||
* 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
|
||||
|
||||
说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,**大多数人就是平时埋头用,从来不去思考背后的一些东西**。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做?
|
||||
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
其实回答这类问题,说白了,不求你看过那技术的源码,起码你要大概知道那个技术的基本原理、核心组成部分、基本架构构成,然后参照一些开源的技术把一个系统设计出来的思路说一下就好。
|
||||
|
||||
比如说这个消息队列系统,我们从以下几个角度来考虑一下:
|
||||
|
||||
- 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
|
||||
* 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了?
|
||||
|
||||
- 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。
|
||||
* 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。
|
||||
|
||||
- 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
|
||||
* 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。
|
||||
|
||||
- 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。
|
||||
* 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。
|
||||
|
||||
mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
**候选人**:你好。
|
||||
|
||||
(面试官在你的简历上面看到了,呦,有个亮点,你在项目里用过 `MQ`,比如说你用过 `ActiveMQ`)
|
||||
(面试官在你的简历上面看到了,呦,有个亮点,你在项目里用过 `MQ` ,比如说你用过 `ActiveMQ` )
|
||||
|
||||
**面试官**:你在系统里用过消息队列吗?(面试官在随和的语气中展开了面试)
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
(部分同学在这里会进入一个误区,就是你仅仅就是知道以及回答你们是怎么用这个消息队列的,用这个消息队列来干了个什么事情?)
|
||||
|
||||
**面试官**:那你们为什么使用消息队列啊?你的订单系统不发送消息到 `MQ`,直接订单系统调用库存系统一个接口,咔嚓一下,直接就调用成功,库存不也就更新了。
|
||||
**面试官**:那你们为什么使用消息队列啊?你的订单系统不发送消息到 `MQ` ,直接订单系统调用库存系统一个接口,咔嚓一下,直接就调用成功,库存不也就更新了。
|
||||
|
||||
**候选人**:额。。。(楞了一下,为什么?我没怎么仔细想过啊,老大让用就用了),硬着头皮胡言乱语了几句。
|
||||
|
||||
@ -30,11 +30,11 @@
|
||||
|
||||
(面试官此时心里已经更觉得你这哥儿们不行,平时都没什么思考)
|
||||
|
||||
**面试官**:`Kafka`、`ActiveMQ`、`RabbitMQ`、`RocketMQ` 都有什么区别?
|
||||
**面试官**: `Kafka` 、 `ActiveMQ` 、 `RabbitMQ` 、 `RocketMQ` 都有什么区别?
|
||||
|
||||
(面试官问你这个问题,就是说,绕过比较虚的话题,直接看看你对各种 `MQ` 中间件是否了解,是否做过功课,是否做过调研)
|
||||
|
||||
**候选人**:我们就用过 `ActiveMQ`,所以别的没用过。。。区别,也不太清楚。。。
|
||||
**候选人**:我们就用过 `ActiveMQ` ,所以别的没用过。。。区别,也不太清楚。。。
|
||||
|
||||
(面试官此时更是觉得你这哥儿们平时就是瞎用,根本就没什么思考,觉得不行)
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
|
||||
**面试官**:如何保证消息不被重复消费啊?如何保证消费的时候是幂等的啊?
|
||||
|
||||
**候选人**:啥?(`MQ` 不就是写入&消费就可以了,哪来这么多问题)
|
||||
**候选人**:啥?( `MQ` 不就是写入&消费就可以了,哪来这么多问题)
|
||||
|
||||
**面试官**:如何保证消息的可靠性传输啊?要是消息丢失了怎么办啊?
|
||||
|
||||
@ -64,6 +64,6 @@
|
||||
|
||||
---
|
||||
|
||||
这其实是面试官的一种面试风格,就是说面试官的问题不是发散的,而是从一个小点慢慢铺开。比如说面试官可能会跟你聊聊高并发话题,就这个话题里面跟你聊聊缓存、`MQ` 等等东西,**由浅入深,一步步深挖**。
|
||||
这其实是面试官的一种面试风格,就是说面试官的问题不是发散的,而是从一个小点慢慢铺开。比如说面试官可能会跟你聊聊高并发话题,就这个话题里面跟你聊聊缓存、 `MQ` 等等东西,**由浅入深,一步步深挖**。
|
||||
|
||||
其实上面是一个非常典型的关于消息队列的技术考察过程,好的面试官一定是从你做过的某一个点切入,然后层层展开深入考察,一个接一个问,直到把这个技术点刨根问底,问到最底层。
|
||||
其实上面是一个非常典型的关于消息队列的技术考察过程,好的面试官一定是从你做过的某一个点切入,然后层层展开深入考察,一个接一个问,直到把这个技术点刨根问底,问到最底层。
|
||||
|
@ -2,26 +2,31 @@
|
||||
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
你看这问法,其实本质针对的场景,都是说,可能你的消费端出了问题,不消费了;或者消费的速度极其慢。接着就坑爹了,可能你的消息队列集群的磁盘都快写满了,都没人消费,这个时候怎么办?或者是这整个就积压了几个小时,你这个时候怎么办?或者是你积压的时间太长了,导致比如 RabbitMQ 设置了消息过期时间后就没了怎么办?
|
||||
|
||||
所以就这事儿,其实线上挺常见的,一般不出,一出就是大 case。一般常见于,举个例子,消费端每次消费之后要写 mysql,结果 mysql 挂了,消费端 hang 那儿了,不动了;或者是消费端出了个什么岔子,导致消费速度极其慢。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
关于这个事儿,我们一个一个来梳理吧,先假设一个场景,我们现在消费端出故障了,然后大量消息在 mq 里积压,现在出事故了,慌了。
|
||||
|
||||
### 大量消息在 mq 里积压了几个小时了还没解决
|
||||
|
||||
几千万条数据在 MQ 里积压了七八个小时,从下午 4 点多,积压到了晚上 11 点多。这个是我们真实遇到过的一个场景,确实是线上故障了,这个时候要不然就是修复 consumer 的问题,让它恢复消费速度,然后傻傻的等待几个小时消费完毕。这个肯定不能在面试的时候说吧。
|
||||
|
||||
一个消费者一秒是 1000 条,一秒 3 个消费者是 3000 条,一分钟就是 18 万条。所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概 1 小时的时间才能恢复过来。
|
||||
|
||||
一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下:
|
||||
- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。
|
||||
- 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
|
||||
- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
|
||||
- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
|
||||
- 等快速消费完积压数据之后,**得恢复原先部署的架构**,**重新**用原先的 consumer 机器来消费消息。
|
||||
|
||||
* 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。
|
||||
* 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。
|
||||
* 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。
|
||||
* 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。
|
||||
* 等快速消费完积压数据之后,**得恢复原先部署的架构**,**重新**用原先的 consumer 机器来消费消息。
|
||||
|
||||
### mq 中的消息过期失效了
|
||||
|
||||
假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是**大量的数据会直接搞丢**。
|
||||
|
||||
这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是**批量重导**,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。
|
||||
@ -29,4 +34,5 @@
|
||||
假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。
|
||||
|
||||
### mq 都快写满了
|
||||
|
||||
如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。
|
||||
|
@ -2,13 +2,16 @@
|
||||
你们有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决 MySQL 主从同步的延时问题?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
高并发这个阶段,肯定是需要做读写分离的,啥意思?因为实际上大部分的互联网公司,一些网站,或者是 app,其实都是读多写少。所以针对这个情况,就是写一个主库,但是主库挂多个从库,然后从多个从库来读,那不就可以支撑更高的读并发压力了吗?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 如何实现 MySQL 的读写分离?
|
||||
其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。
|
||||
|
||||
### MySQL 主从复制原理的是啥?
|
||||
|
||||
主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。
|
||||
|
||||

|
||||
@ -24,18 +27,22 @@
|
||||
所谓**并行复制**,指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后**并行重放不同库的日志**,这是库级别的并行。
|
||||
|
||||
### MySQL 主从同步延时问题(精华)
|
||||
|
||||
以前线上确实处理过因为主从同步延时问题而导致的线上的 bug,属于小型的生产事故。
|
||||
|
||||
是这个么场景。有个同学是这样写代码逻辑的。先插入一条数据,再把它查出来,然后更新这条数据。在生产环境高峰期,写并发达到了 2000/s,这个时候,主从复制延时大概是在小几十毫秒。线上会发现,每天总有那么一些数据,我们期望更新一些重要的数据状态,但在高峰期时候却没更新。用户跟客服反馈,而客服就会反馈给我们。
|
||||
|
||||
我们通过 MySQL 命令:
|
||||
```sql
|
||||
|
||||
``` sql
|
||||
show status
|
||||
```
|
||||
查看 `Seconds_Behind_Master`,可以看到从库复制主库的数据落后了几 ms。
|
||||
|
||||
查看 `Seconds_Behind_Master` ,可以看到从库复制主库的数据落后了几 ms。
|
||||
|
||||
一般来说,如果主从延迟较为严重,有以下解决方案:
|
||||
- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
|
||||
- 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
|
||||
- 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
|
||||
- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。
|
||||
|
||||
* 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。
|
||||
* 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。
|
||||
* 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。
|
||||
* 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。
|
||||
|
@ -2,9 +2,11 @@
|
||||
了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实这是问到缓存必问的,因为缓存雪崩和穿透,是缓存最大的两个问题,要么不出现,一旦出现就是致命性的问题,所以面试官一定会问你。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 缓存雪崩
|
||||
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
|
||||
|
||||
@ -15,9 +17,10 @@
|
||||
大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。
|
||||
|
||||
缓存雪崩的事前事中事后的解决方案如下:
|
||||
- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
|
||||
- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
|
||||
- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
|
||||
|
||||
* 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
|
||||
* 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
|
||||
* 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
|
||||
|
||||

|
||||
|
||||
@ -26,11 +29,13 @@
|
||||
限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?**走降级**!可以返回一些默认的值,或者友情提示,或者空值。
|
||||
|
||||
好处:
|
||||
- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
|
||||
- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
|
||||
- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
|
||||
|
||||
* 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。
|
||||
* 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。
|
||||
* 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。
|
||||
|
||||
### 缓存穿透
|
||||
|
||||
对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。
|
||||
|
||||
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
|
||||
@ -39,13 +44,14 @@
|
||||
|
||||

|
||||
|
||||
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
|
||||
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN` 。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
|
||||
|
||||
### 缓存击穿
|
||||
|
||||
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
|
||||
|
||||
不同场景下的解决方式可如下:
|
||||
|
||||
- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
|
||||
- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
|
||||
- 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
|
||||
* 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
|
||||
* 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
|
||||
* 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。
|
||||
|
@ -2,15 +2,17 @@
|
||||
redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
这个也是线上非常常见的一个问题,就是**多客户端同时并发写**一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。
|
||||
|
||||
而且 redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。
|
||||
|
||||

|
||||
|
||||
你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。
|
||||
|
||||
每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
|
||||
每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
|
||||
|
@ -2,7 +2,8 @@
|
||||
redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
|
||||
|
||||
## 面试官心理分析
|
||||
在前几年,redis 如果要搞几个节点,每个节点存储一部分的数据,得**借助一些中间件**来实现,比如说有 `codis`,或者 `twemproxy`,都有。有一些 redis 中间件,你读写 redis 中间件,redis 中间件负责将你的数据分布式存储在多台机器上的 redis 实例中。
|
||||
|
||||
在前几年,redis 如果要搞几个节点,每个节点存储一部分的数据,得**借助一些中间件**来实现,比如说有 `codis` ,或者 `twemproxy` ,都有。有一些 redis 中间件,你读写 redis 中间件,redis 中间件负责将你的数据分布式存储在多台机器上的 redis 实例中。
|
||||
|
||||
这两年,redis 不断在发展,redis 也不断有新的版本,现在的 redis 集群模式,可以做到在多台机器上,部署多个 redis 实例,每个实例存储一部分的数据,同时每个 redis 主实例可以挂 redis 从实例,自动确保说,如果 redis 主实例挂了,会自动切换到 redis 从实例上来。
|
||||
|
||||
@ -13,19 +14,22 @@ redis 集群模式的工作原理能说一下么?在集群模式下,redis
|
||||
redis cluster,主要是针对**海量数据+高并发+高可用**的场景。redis cluster 支撑 N 个 redis master node,每个 master node 都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### redis cluster 介绍
|
||||
- 自动将数据进行分片,每个 master 上放一部分数据
|
||||
- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
|
||||
|
||||
* 自动将数据进行分片,每个 master 上放一部分数据
|
||||
* 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的
|
||||
|
||||
在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。
|
||||
|
||||
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,`gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
|
||||
16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议, `gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
|
||||
|
||||
### 节点间的内部通信机制
|
||||
|
||||
#### 基本通信原理
|
||||
集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。
|
||||
|
||||
**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
|
||||
**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm` 。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
|
||||
|
||||

|
||||
|
||||
@ -37,43 +41,48 @@ redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节
|
||||
|
||||
gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。
|
||||
|
||||
- 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong`。
|
||||
* 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong` 。
|
||||
|
||||
- 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。
|
||||
* 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。
|
||||
|
||||
#### gossip 协议
|
||||
gossip 协议包含多种消息,包含 `ping`,`pong`,`meet`,`fail` 等等。
|
||||
|
||||
- meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。
|
||||
gossip 协议包含多种消息,包含 `ping` , `pong` , `meet` , `fail` 等等。
|
||||
|
||||
```bash
|
||||
* meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。
|
||||
|
||||
``` bash
|
||||
redis-trib.rb add-node
|
||||
```
|
||||
|
||||
其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。
|
||||
|
||||
- ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
|
||||
- pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。
|
||||
- fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
|
||||
* ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。
|
||||
* pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。
|
||||
* fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。
|
||||
|
||||
#### ping 消息深入
|
||||
|
||||
ping 时要携带一些元数据,如果很频繁,可能会加重网络负担。
|
||||
|
||||
每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 `cluster_node_timeout / 2`,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 `cluster_node_timeout` 可以调节,如果调得比较大,那么会降低 ping 的频率。
|
||||
每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 `cluster_node_timeout / 2` ,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 `cluster_node_timeout` 可以调节,如果调得比较大,那么会降低 ping 的频率。
|
||||
|
||||
每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 `3` 个其它节点的信息,最多包含 `总节点数减 2` 个其它节点的信息。
|
||||
|
||||
### 分布式寻址算法
|
||||
- hash 算法(大量缓存重建)
|
||||
- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
|
||||
- redis cluster 的 hash slot 算法
|
||||
|
||||
* hash 算法(大量缓存重建)
|
||||
* 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
|
||||
* redis cluster 的 hash slot 算法
|
||||
|
||||
#### hash 算法
|
||||
|
||||
来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致**大部分的请求过来,全部无法拿到有效的缓存**,导致大量的流量涌入数据库。
|
||||
|
||||

|
||||
|
||||
#### 一致性 hash 算法
|
||||
|
||||
一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。
|
||||
|
||||
来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环**顺时针“行走”**,遇到的第一个 master 节点就是 key 所在位置。
|
||||
@ -85,6 +94,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担
|
||||

|
||||
|
||||
#### redis cluster 的 hash slot 算法
|
||||
|
||||
redis cluster 有固定的 `16384` 个 hash slot,对每个 `key` 计算 `CRC16` 值,然后对 `16384` 取模,可以获取 key 对应的 hash slot。
|
||||
|
||||
redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 `hash tag` 来实现。
|
||||
@ -94,26 +104,31 @@ redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master
|
||||

|
||||
|
||||
### redis cluster 的高可用与主备切换原理
|
||||
|
||||
redis cluster 的高可用的原理,几乎跟哨兵是类似的。
|
||||
|
||||
#### 判断节点宕机
|
||||
如果一个节点认为另外一个节点宕机,那么就是 `pfail`,**主观宕机**。如果多个节点都认为另外一个节点宕机了,那么就是 `fail`,**客观宕机**,跟哨兵的原理几乎一样,sdown,odown。
|
||||
|
||||
在 `cluster-node-timeout` 内,某个节点一直没有返回 `pong`,那么就被认为 `pfail`。
|
||||
如果一个节点认为另外一个节点宕机,那么就是 `pfail` ,**主观宕机**。如果多个节点都认为另外一个节点宕机了,那么就是 `fail` ,**客观宕机**,跟哨兵的原理几乎一样,sdown,odown。
|
||||
|
||||
如果一个节点认为某个节点 `pfail` 了,那么会在 `gossip ping` 消息中,`ping` 给其他节点,如果**超过半数**的节点都认为 `pfail` 了,那么就会变成 `fail`。
|
||||
在 `cluster-node-timeout` 内,某个节点一直没有返回 `pong` ,那么就被认为 `pfail` 。
|
||||
|
||||
如果一个节点认为某个节点 `pfail` 了,那么会在 `gossip ping` 消息中, `ping` 给其他节点,如果**超过半数**的节点都认为 `pfail` 了,那么就会变成 `fail` 。
|
||||
|
||||
#### 从节点过滤
|
||||
|
||||
对宕机的 master node,从其所有的 slave node 中,选择一个切换成 master node。
|
||||
|
||||
检查每个 slave node 与 master node 断开连接的时间,如果超过了 `cluster-node-timeout * cluster-slave-validity-factor`,那么就**没有资格**切换成 `master`。
|
||||
检查每个 slave node 与 master node 断开连接的时间,如果超过了 `cluster-node-timeout * cluster-slave-validity-factor` ,那么就**没有资格**切换成 `master` 。
|
||||
|
||||
#### 从节点选举
|
||||
|
||||
每个从节点,都根据自己对 master 复制数据的 offset,来设置一个选举时间,offset 越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。
|
||||
|
||||
所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node`(N/2 + 1)`都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。
|
||||
所有的 master node 开始 slave 选举投票,给要进行选举的 slave 进行投票,如果大部分 master node `(N/2 + 1)` 都投票给了某个从节点,那么选举通过,那个从节点可以切换成 master。
|
||||
|
||||
从节点执行主备切换,从节点切换为主节点。
|
||||
|
||||
#### 与哨兵比较
|
||||
整个流程跟哨兵相比,非常类似,所以说,redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。
|
||||
|
||||
整个流程跟哨兵相比,非常类似,所以说,redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。
|
||||
|
@ -2,17 +2,21 @@
|
||||
如何保证缓存与数据库的双写一致性?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统**不是严格要求** “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:**读请求和写请求串行化**,串到一个**内存队列**里去。
|
||||
|
||||
串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
|
||||
|
||||
### Cache Aside Pattern
|
||||
|
||||
最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。
|
||||
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
|
||||
- 更新的时候,**先更新数据库,然后再删除缓存**。
|
||||
|
||||
* 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
|
||||
* 更新的时候,**先更新数据库,然后再删除缓存**。
|
||||
|
||||
**为什么是删除缓存,而不是更新缓存?**
|
||||
|
||||
@ -27,6 +31,7 @@
|
||||
其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都把里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。
|
||||
|
||||
### 最初级的缓存不一致问题及解决方案
|
||||
|
||||
问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
|
||||
|
||||

|
||||
@ -34,6 +39,7 @@
|
||||
解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
|
||||
|
||||
### 比较复杂的数据不一致问题分析
|
||||
|
||||
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,**查到了修改前的旧数据**,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了...
|
||||
|
||||
**为什么上亿流量高并发场景下,缓存会出现这个问题?**
|
||||
@ -54,7 +60,7 @@
|
||||
|
||||
高并发的场景下,该解决方案要注意的问题:
|
||||
|
||||
- 读请求长时阻塞
|
||||
* 读请求长时阻塞
|
||||
|
||||
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
|
||||
|
||||
@ -74,19 +80,19 @@
|
||||
|
||||
经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。
|
||||
|
||||
- 读请求并发量过高
|
||||
* 读请求并发量过高
|
||||
|
||||
这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。
|
||||
|
||||
但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。
|
||||
|
||||
- 多服务实例部署的请求路由
|
||||
* 多服务实例部署的请求路由
|
||||
|
||||
可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。
|
||||
|
||||
比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。
|
||||
|
||||
- 热点商品的路由问题,导致请求的倾斜
|
||||
* 热点商品的路由问题,导致请求的倾斜
|
||||
|
||||
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
|
||||
|
||||
|
@ -2,39 +2,46 @@
|
||||
redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
除非是面试官感觉看你简历,是工作 3 年以内的比较初级的同学,可能对技术没有很深入的研究,面试官才会问这类问题。否则,在宝贵的面试时间里,面试官实在不想多问。
|
||||
|
||||
其实问这个问题,主要有两个原因:
|
||||
- 看看你到底有没有全面的了解 redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
|
||||
- 看看你在实际项目里都怎么玩儿过 redis。
|
||||
|
||||
* 看看你到底有没有全面的了解 redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作;
|
||||
* 看看你在实际项目里都怎么玩儿过 redis。
|
||||
|
||||
要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
redis 主要有以下几种数据类型:
|
||||
- string
|
||||
- hash
|
||||
- list
|
||||
- set
|
||||
- sorted set
|
||||
|
||||
* string
|
||||
* hash
|
||||
* list
|
||||
* set
|
||||
* sorted set
|
||||
|
||||
### string
|
||||
|
||||
这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。
|
||||
```bash
|
||||
|
||||
``` bash
|
||||
set college szu
|
||||
```
|
||||
|
||||
### hash
|
||||
|
||||
这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是**这个对象没嵌套其他的对象**)给缓存在 redis 里,然后每次读写缓存的时候,可以就操作 hash 里的**某个字段**。
|
||||
|
||||
```bash
|
||||
``` bash
|
||||
hset person name bingo
|
||||
hset person age 20
|
||||
hset person id 1
|
||||
hget person name
|
||||
```
|
||||
|
||||
```json
|
||||
``` json
|
||||
person = {
|
||||
"name": "bingo",
|
||||
"age": 20,
|
||||
@ -43,18 +50,21 @@ person = {
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
list 是有序列表,这个可以玩儿出很多花样。
|
||||
|
||||
比如可以通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。
|
||||
|
||||
比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。
|
||||
```bash
|
||||
|
||||
``` bash
|
||||
# 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。
|
||||
lrange mylist 0 -1
|
||||
```
|
||||
|
||||
比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。
|
||||
```bash
|
||||
|
||||
``` bash
|
||||
lpush mylist 1
|
||||
lpush mylist 2
|
||||
lpush mylist 3 4 5
|
||||
@ -64,6 +74,7 @@ rpop mylist
|
||||
```
|
||||
|
||||
### set
|
||||
|
||||
set 是无序集合,自动去重。
|
||||
|
||||
直接基于 set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 jvm 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于 redis 进行全局的 set 去重。
|
||||
@ -71,7 +82,8 @@ set 是无序集合,自动去重。
|
||||
可以基于 set 玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧。
|
||||
|
||||
把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。
|
||||
```bash
|
||||
|
||||
``` bash
|
||||
#-------操作一个set-------
|
||||
# 添加元素
|
||||
sadd mySet 1
|
||||
@ -107,8 +119,10 @@ sdiff yourSet mySet
|
||||
```
|
||||
|
||||
### sorted set
|
||||
|
||||
sorted set 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。
|
||||
```bash
|
||||
|
||||
``` bash
|
||||
zadd board 85 zhangsan
|
||||
zadd board 72 lisi
|
||||
zadd board 96 wangwu
|
||||
@ -119,4 +133,4 @@ zrevrange board 0 3
|
||||
|
||||
# 获取某用户的排名
|
||||
zrank board zhaoliu
|
||||
```
|
||||
```
|
||||
|
@ -2,10 +2,12 @@
|
||||
redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
如果你连这个问题都不知道,上来就懵了,回答不出来,那线上你写代码的时候,想当然的认为写进 redis 的数据就一定会存在,后面导致系统各种 bug,谁来负责?
|
||||
|
||||
常见的有两个问题:
|
||||
- 往 redis 写入的数据怎么没了?
|
||||
|
||||
* 往 redis 写入的数据怎么没了?
|
||||
|
||||
可能有同学会遇到,在生产环境的 redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧?
|
||||
|
||||
@ -13,11 +15,12 @@ redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一
|
||||
|
||||
那既然内存是有限的,比如 redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。
|
||||
|
||||
- 数据明明过期了,怎么还占用着内存?
|
||||
* 数据明明过期了,怎么还占用着内存?
|
||||
|
||||
这是由 redis 的过期策略来决定。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### redis 过期策略
|
||||
redis 过期策略是:**定期删除+惰性删除**。
|
||||
|
||||
@ -34,20 +37,23 @@ redis 过期策略是:**定期删除+惰性删除**。
|
||||
答案是:**走内存淘汰机制**。
|
||||
|
||||
### 内存淘汰机制
|
||||
|
||||
redis 内存淘汰机制有以下几个:
|
||||
- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
|
||||
- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。
|
||||
- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
|
||||
- volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。
|
||||
- volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。
|
||||
- volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。
|
||||
|
||||
* noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
|
||||
* **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。
|
||||
* allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
|
||||
* volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。
|
||||
* volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。
|
||||
* volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。
|
||||
|
||||
### 手写一个 LRU 算法
|
||||
|
||||
你可以现场手写最原始的 LRU 算法,那个代码量太大了,似乎不太现实。
|
||||
|
||||
不求自己纯手工从底层开始打造出自己的 LRU,但是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。
|
||||
|
||||
```java
|
||||
``` java
|
||||
class LRUCache<K, V> extends LinkedHashMap<K, V> {
|
||||
private final int CACHE_SIZE;
|
||||
|
||||
|
@ -7,34 +7,39 @@
|
||||
redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发
|
||||
|
||||
## redis replication 的核心机制
|
||||
- redis 采用**异步方式**复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
|
||||
- 一个 master node 是可以配置多个 slave node 的;
|
||||
- slave node 也可以连接其他的 slave node;
|
||||
- slave node 做复制的时候,不会 block master node 的正常工作;
|
||||
- slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
|
||||
- slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
|
||||
|
||||
* redis 采用**异步方式**复制数据到 slave 节点,不过 redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量;
|
||||
* 一个 master node 是可以配置多个 slave node 的;
|
||||
* slave node 也可以连接其他的 slave node;
|
||||
* slave node 做复制的时候,不会 block master node 的正常工作;
|
||||
* slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
|
||||
* slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。
|
||||
|
||||
注意,如果采用了主从架构,那么建议必须**开启** master node 的[持久化](/docs/high-concurrency/redis-persistence.md),不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
|
||||
|
||||
另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能**确保启动的时候,是有数据的**,即使采用了后续讲解的[高可用机制](/docs/high-concurrency/redis-sentinel.md),slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。
|
||||
|
||||
## redis 主从复制的核心原理
|
||||
|
||||
当启动一个 slave node 的时候,它会发送一个 `PSYNC` 命令给 master node。
|
||||
|
||||
如果这是 slave node 初次连接到 master node,那么会触发一次 `full resynchronization` 全量复制。此时 master 会启动一个后台线程,开始生成一份 `RDB` 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。`RDB` 文件生成完毕后, master 会将这个 `RDB` 发送给 slave,slave 会先**写入本地磁盘,然后再从本地磁盘加载到内存**中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
|
||||
如果这是 slave node 初次连接到 master node,那么会触发一次 `full resynchronization` 全量复制。此时 master 会启动一个后台线程,开始生成一份 `RDB` 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。 `RDB` 文件生成完毕后, master 会将这个 `RDB` 发送给 slave,slave 会先**写入本地磁盘,然后再从本地磁盘加载到内存**中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
|
||||
|
||||

|
||||
|
||||
### 主从复制的断点续传
|
||||
|
||||
从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
|
||||
|
||||
master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 `resynchronization`。
|
||||
master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 `resynchronization` 。
|
||||
|
||||
> 如果根据 host+ip 定位 master node,是不靠谱的,如果 master node 重启或者数据出现了变化,那么 slave node 应该根据不同的 run id 区分。
|
||||
|
||||
### 无磁盘化复制
|
||||
master 在内存中直接创建 `RDB`,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 `repl-diskless-sync yes` 即可。
|
||||
```bash
|
||||
|
||||
master 在内存中直接创建 `RDB` ,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 `repl-diskless-sync yes` 即可。
|
||||
|
||||
``` bash
|
||||
repl-diskless-sync yes
|
||||
|
||||
# 等待 5s 后再开始复制,因为要等更多 slave 重新连接过来
|
||||
@ -42,40 +47,49 @@ repl-diskless-sync-delay 5
|
||||
```
|
||||
|
||||
### 过期 key 处理
|
||||
|
||||
slave 不会过期 key,只会等待 master 过期 key。如果 master 过期了一个 key,或者通过 LRU 淘汰了一个 key,那么会模拟一条 del 命令发送给 slave。
|
||||
|
||||
## 复制的完整流程
|
||||
slave node 启动时,会在自己本地保存 master node 的信息,包括 master node 的`host`和`ip`,但是复制流程没开始。
|
||||
|
||||
slave node 启动时,会在自己本地保存 master node 的信息,包括 master node 的 `host` 和 `ip` ,但是复制流程没开始。
|
||||
|
||||
slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接。然后 slave node 发送 `ping` 命令给 master node。如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证。master node **第一次执行全量复制**,将所有数据发给 slave node。而在后续,master node 持续将写命令,异步复制给 slave node。
|
||||
|
||||

|
||||
|
||||
### 全量复制
|
||||
- master 执行 bgsave ,在本地生成一份 rdb 快照文件。
|
||||
- master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
|
||||
- master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
|
||||
- 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
|
||||
```bash
|
||||
|
||||
* master 执行 bgsave ,在本地生成一份 rdb 快照文件。
|
||||
* master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s)
|
||||
* master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。
|
||||
* 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。
|
||||
|
||||
``` bash
|
||||
client-output-buffer-limit slave 256MB 64MB 60
|
||||
```
|
||||
- slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时**基于旧的数据版本**对外提供服务。
|
||||
- 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
|
||||
|
||||
* slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时**基于旧的数据版本**对外提供服务。
|
||||
* 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。
|
||||
|
||||
### 增量复制
|
||||
- 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
|
||||
- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
|
||||
- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
|
||||
|
||||
* 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。
|
||||
* master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。
|
||||
* master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。
|
||||
|
||||
### heartbeat
|
||||
|
||||
主从节点互相都会发送 heartbeat 信息。
|
||||
|
||||
master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat。
|
||||
|
||||
### 异步复制
|
||||
|
||||
master 每次接收到写命令之后,先在内部写入数据,然后异步发送给 slave node。
|
||||
|
||||
## redis 如何才能做到高可用
|
||||
|
||||
如果系统在 365 天内,有 99.99% 的时间,都是可以哗哗对外提供服务的,那么就说系统是高可用的。
|
||||
|
||||
一个 slave 挂掉了,是不会影响可用性的,还有其它的 slave 在提供相同数据下的相同的对外的查询服务。
|
||||
@ -86,4 +100,4 @@ redis 的高可用架构,叫做 `failover` **故障转移**,也可以叫做
|
||||
|
||||
master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。
|
||||
|
||||
后面会详细说明 redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.md)。
|
||||
后面会详细说明 redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.md)。
|
||||
|
@ -2,6 +2,7 @@
|
||||
redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了再重启,内存里的数据就全部都弄丢了啊。你必须得用 redis 的持久化机制,将数据写入内存的同时,异步的慢慢的将数据写入磁盘文件里,进行持久化。
|
||||
|
||||
如果 redis 宕机重启,自动从磁盘上加载之前持久化的一些数据就可以了,也许会丢失少许数据,但是至少不会将所有数据都弄丢。
|
||||
@ -9,6 +10,7 @@ redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了
|
||||
这个其实一样,针对的都是 redis 的生产环境可能遇到的一些问题,就是 redis 要是挂了再重启,内存里的数据不就全丢了?能不能重启的时候把数据给恢复了?
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
持久化主要是做灾难恢复、数据恢复,也可以归类到高可用的一个环节中去,比如你 redis 整个挂了,然后 redis 就不可用了,你要做的事情就是让 redis 变得可用,尽快变得可用。
|
||||
|
||||
重启 redis,尽快让它对外提供服务,如果没做数据备份,这时候 redis 启动了,也不可用啊,数据都没了。
|
||||
@ -18,8 +20,9 @@ redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了
|
||||
如果你把 redis 持久化做好,备份和恢复方案做到企业级的程度,那么即使你的 redis 故障了,也可以通过备份数据,快速恢复,一旦恢复立即对外提供服务。
|
||||
|
||||
### redis 持久化的两种方式
|
||||
- RDB:RDB 持久化机制,是对 redis 中的数据执行**周期性**的持久化。
|
||||
- AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。
|
||||
|
||||
* RDB:RDB 持久化机制,是对 redis 中的数据执行**周期性**的持久化。
|
||||
* AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。
|
||||
|
||||
通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。
|
||||
|
||||
@ -28,23 +31,26 @@ redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了
|
||||
如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 **AOF** 来重新构建数据,因为 AOF 中的**数据更加完整**。
|
||||
|
||||
#### RDB 优缺点
|
||||
- RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 redis 中的数据。
|
||||
- RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis **保持高性能**,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
|
||||
- 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
|
||||
|
||||
- 如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
|
||||
- RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
|
||||
* RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 redis 中的数据。
|
||||
* RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis **保持高性能**,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
|
||||
* 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
|
||||
|
||||
* 如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
|
||||
* RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
|
||||
|
||||
#### AOF 优缺点
|
||||
- AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次`fsync`操作,最多丢失 1 秒钟的数据。
|
||||
- AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
|
||||
- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
|
||||
- AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。
|
||||
- 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
|
||||
- AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync`,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
|
||||
- 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。
|
||||
|
||||
* AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 `fsync` 操作,最多丢失 1 秒钟的数据。
|
||||
* AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
|
||||
* AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
|
||||
* AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。
|
||||
* 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
|
||||
* AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync` ,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
|
||||
* 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。
|
||||
|
||||
### RDB 和 AOF 到底该如何选择
|
||||
- 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
|
||||
- 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
|
||||
- redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
|
||||
|
||||
* 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
|
||||
* 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
|
||||
* redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
|
||||
|
@ -2,11 +2,13 @@
|
||||
生产环境中的 redis 是怎么部署的?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
看看你了解不了解你们公司的 redis 生产集群的部署架构,如果你不了解,那么确实你就很失职了,你的 redis 是主从架构?集群架构?用了哪种集群方案?有没有做高可用保证?有没有开启持久化机制确保可以进行数据恢复?线上 redis 给几个 G 的内存?设置了哪些参数?压测后你们 redis 集群承载多少 QPS?
|
||||
|
||||
兄弟,这些你必须是门儿清的,否则你确实是没好好思考过。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。
|
||||
|
||||
机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。
|
||||
@ -17,4 +19,4 @@ redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5
|
||||
|
||||
你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。
|
||||
|
||||
其实大型的公司,会有基础架构的 team 负责缓存集群的运维。
|
||||
其实大型的公司,会有基础架构的 team 负责缓存集群的运维。
|
||||
|
@ -1,35 +1,37 @@
|
||||
# Redis 哨兵集群实现高可用
|
||||
|
||||
## 哨兵的介绍
|
||||
|
||||
sentinel,中文名是哨兵。哨兵是 redis 集群架构中非常重要的一个组件,主要有以下功能:
|
||||
|
||||
- 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
|
||||
- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
|
||||
- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
|
||||
- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
|
||||
* 集群监控:负责监控 redis master 和 slave 进程是否正常工作。
|
||||
* 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
|
||||
* 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
|
||||
* 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
|
||||
|
||||
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
|
||||
|
||||
- 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
|
||||
- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
|
||||
* 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
|
||||
* 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
|
||||
|
||||
## 哨兵的核心知识
|
||||
- 哨兵至少需要 3 个实例,来保证自己的健壮性。
|
||||
- 哨兵 + redis 主从的部署架构,是**不保证数据零丢失**的,只能保证 redis 集群的高可用性。
|
||||
- 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
|
||||
|
||||
* 哨兵至少需要 3 个实例,来保证自己的健壮性。
|
||||
* 哨兵 + redis 主从的部署架构,是**不保证数据零丢失**的,只能保证 redis 集群的高可用性。
|
||||
* 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
|
||||
|
||||
哨兵集群必须部署 2 个以上节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1。
|
||||
|
||||
```
|
||||
```
|
||||
+----+ +----+
|
||||
| M1 |---------| R1 |
|
||||
| S1 | | S2 |
|
||||
+----+ +----+
|
||||
```
|
||||
|
||||
配置 `quorum=1`,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候,需要 majority,也就是大多数哨兵都是运行的。
|
||||
配置 `quorum=1` ,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候,需要 majority,也就是大多数哨兵都是运行的。
|
||||
|
||||
```
|
||||
```
|
||||
2 个哨兵,majority=2
|
||||
3 个哨兵,majority=2
|
||||
4 个哨兵,majority=2
|
||||
@ -41,7 +43,7 @@ sentinel,中文名是哨兵。哨兵是 redis 集群架构中非常重要的
|
||||
|
||||
经典的 3 节点哨兵集群是这样的:
|
||||
|
||||
```
|
||||
```
|
||||
+----+
|
||||
| M1 |
|
||||
| S1 |
|
||||
@ -53,19 +55,20 @@ sentinel,中文名是哨兵。哨兵是 redis 集群架构中非常重要的
|
||||
+----+ +----+
|
||||
```
|
||||
|
||||
配置 `quorum=2`,如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机了,然后选举出一个来执行故障转移,同时 3 个哨兵的 majority 是 2,所以还剩下的 2 个哨兵运行着,就可以允许执行故障转移。
|
||||
配置 `quorum=2` ,如果 M1 所在机器宕机了,那么三个哨兵还剩下 2 个,S2 和 S3 可以一致认为 master 宕机了,然后选举出一个来执行故障转移,同时 3 个哨兵的 majority 是 2,所以还剩下的 2 个哨兵运行着,就可以允许执行故障转移。
|
||||
|
||||
## redis 哨兵主备切换的数据丢失问题
|
||||
|
||||
### 导致数据丢失的两种情况
|
||||
主备切换的过程,可能会导致数据丢失:
|
||||
|
||||
- 异步复制导致的数据丢失
|
||||
* 异步复制导致的数据丢失
|
||||
|
||||
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。
|
||||
|
||||

|
||||
|
||||
- 脑裂导致的数据丢失
|
||||
* 脑裂导致的数据丢失
|
||||
|
||||
脑裂,也就是说,某个 master 所在机器突然**脱离了正常的网络**,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会**认为** master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的**脑裂**。
|
||||
|
||||
@ -74,9 +77,10 @@ sentinel,中文名是哨兵。哨兵是 redis 集群架构中非常重要的
|
||||

|
||||
|
||||
### 数据丢失问题的解决方案
|
||||
|
||||
进行如下配置:
|
||||
|
||||
```bash
|
||||
``` bash
|
||||
min-slaves-to-write 1
|
||||
min-slaves-max-lag 10
|
||||
```
|
||||
@ -85,21 +89,23 @@ min-slaves-max-lag 10
|
||||
|
||||
如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。
|
||||
|
||||
- 减少异步复制数据的丢失
|
||||
* 减少异步复制数据的丢失
|
||||
|
||||
有了 `min-slaves-max-lag` 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。
|
||||
|
||||
- 减少脑裂的数据丢失
|
||||
* 减少脑裂的数据丢失
|
||||
|
||||
如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失 10 秒的数据。
|
||||
|
||||
## sdown 和 odown 转换机制
|
||||
- sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
|
||||
- odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机
|
||||
|
||||
* sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
|
||||
* odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机
|
||||
|
||||
sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 `is-master-down-after-milliseconds` 指定的毫秒数之后,就主观认为 master 宕机了;如果一个哨兵在指定时间内,收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。
|
||||
|
||||
## 哨兵集群的自动发现机制
|
||||
|
||||
哨兵互相之间的发现,是通过 redis 的 `pub/sub` 系统实现的,每个哨兵都会往 `__sentinel__:hello` 这个 channel 里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。
|
||||
|
||||
每隔两秒钟,每个哨兵都会往自己监控的某个 master+slaves 对应的 `__sentinel__:hello` channel 里**发送一个消息**,内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置。
|
||||
@ -109,29 +115,32 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过
|
||||
每个哨兵还会跟其他哨兵交换对 `master` 的监控配置,互相进行监控配置的同步。
|
||||
|
||||
## slave 配置的自动纠正
|
||||
|
||||
哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 复制现有 master 的数据;如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上。
|
||||
|
||||
## slave->master 选举算法
|
||||
|
||||
如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息:
|
||||
|
||||
- 跟 master 断开连接的时长
|
||||
- slave 优先级
|
||||
- 复制 offset
|
||||
- run id
|
||||
* 跟 master 断开连接的时长
|
||||
* slave 优先级
|
||||
* 复制 offset
|
||||
* run id
|
||||
|
||||
如果一个 slave 跟 master 断开连接的时间已经超过了 `down-after-milliseconds` 的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。
|
||||
|
||||
```
|
||||
```
|
||||
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
|
||||
```
|
||||
|
||||
接下来会对 slave 进行排序:
|
||||
|
||||
- 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。
|
||||
- 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。
|
||||
- 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。
|
||||
* 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。
|
||||
* 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。
|
||||
* 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。
|
||||
|
||||
## quorum 和 majority
|
||||
|
||||
每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还需要得到 majority 哨兵的授权,才能正式执行切换。
|
||||
|
||||
如果 quorum < majority,比如 5 个哨兵,majority 就是 3,quorum 设置为 2,那么就 3 个哨兵授权就可以执行切换。
|
||||
@ -139,6 +148,7 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过
|
||||
但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才能执行切换。
|
||||
|
||||
## configuration epoch
|
||||
|
||||
哨兵会对一套 redis master+slaves 进行监控,有相应的监控的配置。
|
||||
|
||||
执行切换的那个哨兵,会从要切换到的新 master(salve->master)那里得到一个 configuration epoch,这就是一个 version 号,每次切换的 version 号都必须是唯一的。
|
||||
@ -146,6 +156,7 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过
|
||||
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号。
|
||||
|
||||
## configuration 传播
|
||||
|
||||
哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 `pub/sub` 消息机制。
|
||||
|
||||
这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。
|
||||
|
@ -2,6 +2,7 @@
|
||||
redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
这个是问 redis 的时候,最基本的问题吧,redis 最基本的一个内部原理和特点,就是 redis 实际上是个**单线程工作模型**,你要是这个都不知道,那后面玩儿 redis 的时候,出了问题岂不是什么都不知道?
|
||||
|
||||
还有可能面试官会问问你 redis 和 memcached 的区别,但是 memcached 是早些年各大互联网公司常用的缓存方案,但是现在近几年基本都是 redis,没什么公司用 memcached 了。
|
||||
@ -11,23 +12,27 @@ redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么
|
||||
### redis 和 memcached 有啥区别?
|
||||
|
||||
#### redis 支持复杂的数据结构
|
||||
|
||||
redis 相比 memcached 来说,拥有[更多的数据结构](/docs/high-concurrency/redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。
|
||||
|
||||
#### redis 原生支持集群模式
|
||||
|
||||
在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。
|
||||
|
||||
#### 性能对比
|
||||
|
||||
由于 redis 只使用**单核**,而 memcached 可以使用**多核**,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis。虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。
|
||||
|
||||
### redis 的线程模型
|
||||
redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。
|
||||
|
||||
redis 内部使用文件事件处理器 `file event handler` ,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。
|
||||
|
||||
文件事件处理器的结构包含 4 个部分:
|
||||
|
||||
- 多个 socket
|
||||
- IO 多路复用程序
|
||||
- 文件事件分派器
|
||||
- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
|
||||
* 多个 socket
|
||||
* IO 多路复用程序
|
||||
* 文件事件分派器
|
||||
* 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
|
||||
|
||||
多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。
|
||||
|
||||
@ -43,12 +48,13 @@ redis 内部使用文件事件处理器 `file event handler`,这个文件事
|
||||
|
||||
假设此时客户端发送了一个 `set key value` 请求,此时 redis 中的 socket01 会产生 `AE_READABLE` 事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 `AE_READABLE` 事件,由于前面 socket01 的 `AE_READABLE` 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 `key value` 并在自己内存中完成 `key value` 的设置。操作完成后,它会将 socket01 的 `AE_WRITABLE` 事件与命令回复处理器关联。
|
||||
|
||||
如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 `AE_WRITABLE` 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 `ok`,之后解除 socket01 的 `AE_WRITABLE` 事件与命令回复处理器的关联。
|
||||
如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 `AE_WRITABLE` 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 `ok` ,之后解除 socket01 的 `AE_WRITABLE` 事件与命令回复处理器的关联。
|
||||
|
||||
这样便完成了一次通信。关于 Redis 的一次通信过程,推荐读者阅读《[Redis 设计与实现——黄健宏](https://github.com/doocs/technical-books#database)》进行系统学习。
|
||||
|
||||
### 为啥 redis 单线程模型也能效率这么高?
|
||||
- 纯内存操作。
|
||||
- 核心是基于非阻塞的 IO 多路复用机制。
|
||||
- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
|
||||
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
|
||||
|
||||
* 纯内存操作。
|
||||
* 核心是基于非阻塞的 IO 多路复用机制。
|
||||
* C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
|
||||
* 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
|
||||
|
@ -2,21 +2,25 @@
|
||||
项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
这个问题,互联网公司必问,要是一个人连缓存都不太清楚,那确实比较尴尬。
|
||||
|
||||
只要问到缓存,上来第一个问题,肯定是先问问你项目哪里用了缓存?为啥要用?不用行不行?如果用了以后可能会有什么不良的后果?
|
||||
|
||||
这就是看看你对缓存这个东西背后有没有思考,如果你就是傻乎乎的瞎用,没法给面试官一个合理的解答,那面试官对你印象肯定不太好,觉得你平时思考太少,就知道干活儿。
|
||||
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 项目中缓存是如何使用的?
|
||||
|
||||
这个,需要结合自己项目的业务来。
|
||||
|
||||
### 为什么要用缓存?
|
||||
|
||||
用缓存,主要有两个用途:**高性能**、**高并发**。
|
||||
|
||||
#### 高性能
|
||||
|
||||
假设这么个场景,你有个操作,一个请求过来,吭哧吭哧你各种乱七八糟操作 mysql,半天查出来一个结果,耗时 600ms。但是这个结果可能接下来几个小时都不会变了,或者变了也可以不用立即反馈给用户。那么此时咋办?
|
||||
|
||||
缓存啊,折腾 600ms 查出来的结果,扔缓存里,一个 key 对应一个 value,下次再有人查,别走 mysql 折腾 600ms 了,直接从缓存里,通过一个 key 查出来一个 value,2ms 搞定。性能提升 300 倍。
|
||||
@ -24,6 +28,7 @@
|
||||
就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好。
|
||||
|
||||
#### 高并发
|
||||
|
||||
mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql 单机支撑到 `2000QPS` 也开始容易报警了。
|
||||
|
||||
所以要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。
|
||||
@ -31,10 +36,11 @@ mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,
|
||||
> 缓存是走内存的,内存天然就支撑高并发。
|
||||
|
||||
### 用了缓存之后会有什么不良后果?
|
||||
|
||||
常见的缓存问题有以下几个:
|
||||
|
||||
- [缓存与数据库双写不一致](/docs/high-concurrency/redis-consistence.md)
|
||||
- [缓存雪崩、缓存穿透、缓存击穿](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
- [缓存并发竞争](/docs/high-concurrency/redis-cas.md)
|
||||
* [缓存与数据库双写不一致](/docs/high-concurrency/redis-consistence.md)
|
||||
* [缓存雪崩、缓存穿透、缓存击穿](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
* [缓存并发竞争](/docs/high-concurrency/redis-cas.md)
|
||||
|
||||
点击超链接,可直接查看缓存相关问题及解决方案。
|
||||
|
@ -1,23 +1,29 @@
|
||||
## 面试题
|
||||
- 为什么使用消息队列?
|
||||
- 消息队列有什么优点和缺点?
|
||||
- Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
|
||||
|
||||
* 为什么使用消息队列?
|
||||
* 消息队列有什么优点和缺点?
|
||||
* Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景?
|
||||
|
||||
## 面试官心理分析
|
||||
|
||||
其实面试官主要是想看看:
|
||||
|
||||
- **第一**,你知不知道你们系统里为什么要用消息队列这个东西?<br>
|
||||
* **第一**,你知不知道你们系统里为什么要用消息队列这个东西?<br>
|
||||
|
||||
不少候选人,说自己项目里用了 Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人设计的架构,他从头到尾都没思考过。<br>
|
||||
没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。
|
||||
|
||||
- **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?<br>
|
||||
* **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?<br>
|
||||
|
||||
你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。
|
||||
|
||||
- **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?<br>
|
||||
* **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?<br>
|
||||
|
||||
你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。<br>
|
||||
如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。
|
||||
|
||||
## 面试题剖析
|
||||
|
||||
### 为什么使用消息队列
|
||||
其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么?
|
||||
|
||||
@ -26,6 +32,7 @@
|
||||
先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有 3 个:**解耦**、**异步**、**削峰**。
|
||||
|
||||
#### 解耦
|
||||
|
||||
看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃......
|
||||
|
||||

|
||||
@ -41,6 +48,7 @@
|
||||
**面试技巧**:你需要去考虑一下你负责的系统中是否有类似的场景,就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦,也是可以的,你就需要去考虑在你的项目里,是不是可以运用这个 MQ 去进行系统的解耦。在简历中体现出来这块东西,用 MQ 作解耦。
|
||||
|
||||
#### 异步
|
||||
|
||||
再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。
|
||||
|
||||

|
||||
@ -52,6 +60,7 @@
|
||||

|
||||
|
||||
#### 削峰
|
||||
|
||||
每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。
|
||||
|
||||
一般的 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。
|
||||
@ -67,17 +76,21 @@
|
||||
这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。
|
||||
|
||||
### 消息队列有什么优缺点
|
||||
|
||||
优点上面已经说了,就是**在特殊场景下有其对应的好处**,**解耦**、**异步**、**削峰**。
|
||||
|
||||
缺点有以下几个:
|
||||
|
||||
- 系统可用性降低<br>
|
||||
* 系统可用性降低<br>
|
||||
|
||||
系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)。
|
||||
|
||||
- 系统复杂度提高<br>
|
||||
* 系统复杂度提高<br>
|
||||
|
||||
硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)?怎么[处理消息丢失的情况](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。
|
||||
|
||||
- 一致性问题<br>
|
||||
* 一致性问题<br>
|
||||
|
||||
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。
|
||||
|
||||
所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。
|
||||
@ -93,7 +106,6 @@ A 系统处理完了直接返回成功了,人都以为你这个请求就成功
|
||||
| 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ |
|
||||
| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
|
||||
|
||||
|
||||
综上,各种对比之后,有如下建议:
|
||||
|
||||
一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了;
|
||||
|
@ -1,20 +1,23 @@
|
||||
# 微服务架构
|
||||
- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
|
||||
- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
|
||||
|
||||
* [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
|
||||
* [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
|
||||
|
||||
## Spring Cloud 微服务架构
|
||||
- 什么是微服务?微服务之间是如何独立通讯的?
|
||||
- Spring Cloud 和 Dubbo 有哪些区别?
|
||||
- Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
|
||||
- 什么是服务熔断?什么是服务降级?
|
||||
- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
|
||||
- 你所知道的微服务技术栈都有哪些?
|
||||
- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
|
||||
- ......
|
||||
|
||||
* 什么是微服务?微服务之间是如何独立通讯的?
|
||||
* Spring Cloud 和 Dubbo 有哪些区别?
|
||||
* Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
|
||||
* 什么是服务熔断?什么是服务降级?
|
||||
* 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
|
||||
* 你所知道的微服务技术栈都有哪些?
|
||||
* Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
|
||||
* ......
|
||||
|
||||
---
|
||||
|
||||
## 公众号
|
||||
|
||||
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
<table>
|
||||
@ -32,4 +35,4 @@ GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**
|
||||
</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
@ -100,10 +100,10 @@ Lambda 函数 是无状态服务。一般通过激活 AWS 服务处理请求。
|
||||
|
||||
有四种方法激活 Lambda 函数:
|
||||
|
||||
- 直接方式,使用 web 服务请求
|
||||
- 自动方式,回应例如 AWS S3,DynamoDB,Kinesis 或者 Simple Email Service 等产生的事件
|
||||
- 自动方式,通过 AWS API 网关来处理应用客户端发出的 HTTP 请求
|
||||
- 定时方式,通过 cron 响应--很像定时器方式
|
||||
* 直接方式,使用 web 服务请求
|
||||
* 自动方式,回应例如 AWS S3,DynamoDB,Kinesis 或者 Simple Email Service 等产生的事件
|
||||
* 自动方式,通过 AWS API 网关来处理应用客户端发出的 HTTP 请求
|
||||
* 定时方式,通过 cron 响应--很像定时器方式
|
||||
|
||||
可以看出,AWS Lambda 是一种很方便部署微服务的方式。基于请求计费方式意味着用户只需要承担处理自己业务那部分的负载;另外,因为不需要了解基础架构,用户只需要开发自己的应用。
|
||||
|
||||
|
@ -107,4 +107,4 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来
|
||||
|
||||
在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务交易一致性;第二个挑战是如何从多服务环境中获取一致性数据。
|
||||
|
||||
最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法可以解决此问题,包括将数据库视为消息队列、交易日志挖掘和事件源。
|
||||
最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法可以解决此问题,包括将数据库视为消息队列、交易日志挖掘和事件源。
|
||||
|
@ -1,28 +1,30 @@
|
||||
# 服务发现组件 Eureka 的几个主要调用过程
|
||||
|
||||
- Author: [mghio](https://www.mghio.cn)
|
||||
- Description: 该文主要讲述服务发现组件 Eureka 的几个主要调用过程
|
||||
|
||||
* Author: [mghio](https://www.mghio.cn)
|
||||
* Description: 该文主要讲述服务发现组件 Eureka 的几个主要调用过程
|
||||
|
||||
## 前言
|
||||
现在流行的微服务体系结构正在改变我们构建应用程序的方式,从单一的单体服务转变为越来越小的可单独部署的服务(称为`微服务`),共同构成了我们的应用程序。当进行一个业务时不可避免就会存在多个服务之间调用,假如一个服务 A 要访问在另一台服务器部署的服务 B,那么前提是服务 A 要知道服务 B 所在机器的 IP 地址和服务对应的端口,最简单的方式就是让服务 A 自己去维护一份服务 B 的配置(包含 IP 地址和端口等信息),但是这种方式有几个明显的缺点:随着我们调用服务数量的增加,配置文件该如何维护;缺乏灵活性,如果服务 B 改变 IP 地址或者端口,服务 A 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。
|
||||
一个比较好的解决方案就是 `服务发现(Service Discovery)`。它抽象出来了一个注册中心,当一个新的服务上线时,它会将自己的 IP 和端口注册到注册中心去,会对注册的服务进行定期的心跳检测,当发现服务状态异常时将其从注册中心剔除下线。服务 A 只要从注册中心中获取服务 B 的信息即可,即使当服务 B 的 IP 或者端口变更了,服务 A 也无需修改,从一定程度上解耦了服务。服务发现目前业界有很多开源的实现,比如 `apache` 的 [zookeeper](https://github.com/apache/zookeeper)、 `Netflix` 的 [eureka](https://github.com/Netflix/eureka)、`hashicorp` 的 [consul](https://github.com/hashicorp/consul)、 `CoreOS` 的 [etcd](https://github.com/etcd-io/etcd)。
|
||||
|
||||
现在流行的微服务体系结构正在改变我们构建应用程序的方式,从单一的单体服务转变为越来越小的可单独部署的服务(称为 `微服务` ),共同构成了我们的应用程序。当进行一个业务时不可避免就会存在多个服务之间调用,假如一个服务 A 要访问在另一台服务器部署的服务 B,那么前提是服务 A 要知道服务 B 所在机器的 IP 地址和服务对应的端口,最简单的方式就是让服务 A 自己去维护一份服务 B 的配置(包含 IP 地址和端口等信息),但是这种方式有几个明显的缺点:随着我们调用服务数量的增加,配置文件该如何维护;缺乏灵活性,如果服务 B 改变 IP 地址或者端口,服务 A 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。
|
||||
一个比较好的解决方案就是 `服务发现(Service Discovery)` 。它抽象出来了一个注册中心,当一个新的服务上线时,它会将自己的 IP 和端口注册到注册中心去,会对注册的服务进行定期的心跳检测,当发现服务状态异常时将其从注册中心剔除下线。服务 A 只要从注册中心中获取服务 B 的信息即可,即使当服务 B 的 IP 或者端口变更了,服务 A 也无需修改,从一定程度上解耦了服务。服务发现目前业界有很多开源的实现,比如 `apache` 的 [zookeeper](https://github.com/apache/zookeeper)、 `Netflix` 的 [eureka](https://github.com/Netflix/eureka)、 `hashicorp` 的 [consul](https://github.com/hashicorp/consul)、 `CoreOS` 的 [etcd](https://github.com/etcd-io/etcd)。
|
||||
|
||||
## Eureka 是什么
|
||||
|
||||
`Eureka` 在 [GitHub](https://github.com/Netflix/eureka) 上对其的定义为
|
||||
>Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.
|
||||
|
||||
> Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.
|
||||
|
||||
At Netflix, Eureka is used for the following purposes apart from playing a critical part in mid-tier load balancing.
|
||||
|
||||
`Eureka` 是由 [Netflix](https://www.netflix.com) 公司开源,采用的是 Client / Server 模式进行设计,基于 http 协议和使用 Restful Api 开发的服务注册与发现组件,提供了完整的服务注册和服务发现,可以和 `Spring Cloud` 无缝集成。其中 Server 端扮演着服务注册中心的角色,主要是为 Client 端提供服务注册和发现等功能,维护着 Client 端的服务注册信息,同时定期心跳检测已注册的服务当不可用时将服务剔除下线,Client 端可以通过 Server 端获取自身所依赖服务的注册信息,从而完成服务间的调用。遗憾的是从其官方的 [github wiki](https://github.com/Netflix/eureka/wik) 可以发现,2.0 版本已经不再开源。但是不影响我们对其进行深入了解,毕竟服务注册、服务发现相对来说还是比较基础和通用的,其它开源实现框架的思想也是想通的。
|
||||
|
||||
|
||||
## 服务注册中心(Eureka Server)
|
||||
我们在项目中引入 `Eureka Server` 的相关依赖,然后在启动类加上注解 `@EnableEurekaServer`,就可以将其作为注册中心,启动服务后访问页面如下:
|
||||
|
||||
我们在项目中引入 `Eureka Server` 的相关依赖,然后在启动类加上注解 `@EnableEurekaServer` ,就可以将其作为注册中心,启动服务后访问页面如下:
|
||||
|
||||

|
||||
|
||||
我们继续添加两个模块 `service-provider`,`service-consumer`,然后在启动类加上注解 `@EnableEurekaClient` 并指定注册中心地址为我们刚刚启动的 `Eureka Server`,再次访问可以看到两个服务都已经注册进来了。
|
||||
我们继续添加两个模块 `service-provider` , `service-consumer` ,然后在启动类加上注解 `@EnableEurekaClient` 并指定注册中心地址为我们刚刚启动的 `Eureka Server` ,再次访问可以看到两个服务都已经注册进来了。
|
||||
|
||||

|
||||
|
||||
@ -31,11 +33,13 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
可以看到 `Eureka` 的使用非常简单,只需要添加几个注解和配置就实现了服务注册和服务发现,接下来我们看看它是如何实现这些功能的。
|
||||
|
||||
### 服务注册(Register)
|
||||
|
||||
注册中心提供了服务注册接口,用于当有新的服务启动后进行调用来实现服务注册,或者心跳检测到服务状态异常时,变更对应服务的状态。服务注册就是发送一个 `POST` 请求带上当前实例信息到类 `ApplicationResource` 的 `addInstance` 方法进行服务注册。
|
||||
|
||||

|
||||
|
||||
可以看到方法调用了类 `PeerAwareInstanceRegistryImpl` 的 `register` 方法,该方法主要分为两步:
|
||||
|
||||
1. 调用父类 `AbstractInstanceRegistry` 的 `register` 方法把当前服务注册到注册中心
|
||||
2. 调用 `replicateToPeers` 方法使用异步的方式向其它的 `Eureka Server` 节点同步服务注册信息
|
||||
|
||||
@ -43,12 +47,13 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
|
||||

|
||||
|
||||
第一层 `map` 的 `key` 是应用名称(对应 `Demo` 里的 `SERVICE-PROVIDER`),第二层 `map` 的 `key` 是应用对应的实例名称(对应 `Demo` 里的 `mghio-mbp:service-provider:9999`),一个应用可以有多个实例,主要调用流程如下图所示:
|
||||
第一层 `map` 的 `key` 是应用名称(对应 `Demo` 里的 `SERVICE-PROVIDER` ),第二层 `map` 的 `key` 是应用对应的实例名称(对应 `Demo` 里的 `mghio-mbp:service-provider:9999` ),一个应用可以有多个实例,主要调用流程如下图所示:
|
||||
|
||||

|
||||
|
||||
### 服务续约(Renew)
|
||||
服务续约会由服务提供者(比如 `Demo` 中的 `service-provider`)定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务时效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。
|
||||
|
||||
服务续约会由服务提供者(比如 `Demo` 中的 `service-provider` )定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务时效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。
|
||||
|
||||

|
||||
|
||||
@ -57,7 +62,8 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||

|
||||
|
||||
### 服务下线(Cancel)
|
||||
当服务提供者(比如 `Demo` 中的 `service-provider`)停止服务时,会发送请求告知注册中心 `Eureka Server` 进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个 `DELETE` 请求带上当前实例信息到类 `InstanceResource` 的 `cancelLease` 方法进行服务剔除下线操作。
|
||||
|
||||
当服务提供者(比如 `Demo` 中的 `service-provider` )停止服务时,会发送请求告知注册中心 `Eureka Server` 进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个 `DELETE` 请求带上当前实例信息到类 `InstanceResource` 的 `cancelLease` 方法进行服务剔除下线操作。
|
||||
|
||||

|
||||
|
||||
@ -66,50 +72,56 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||

|
||||
|
||||
### 服务剔除(Eviction)
|
||||
|
||||
服务剔除是注册中心 `Eureka Server` 在启动时就启动一个守护线程 `evictionTimer` 来定期(默认为 `60` 秒)执行检测服务的,判断标准就是超过一定时间没有进行 `Renew` 的服务,默认的失效时间是 `90` 秒,也就是说当一个已注册的服务在 `90` 秒内没有向注册中心 `Eureka Server` 进行服务续约(Renew),就会被从注册中心剔除下线。失效时间可以通过配置 `eureka.instance.leaseExpirationDurationInSeconds` 进行修改,定期执行检测服务可以通过配置 `eureka.server.evictionIntervalTimerInMs` 进行修改,主要调用流程如下图所示:
|
||||
|
||||

|
||||
|
||||
|
||||
## 服务提供者(Service Provider)
|
||||
对于服务提供方(比如 `Demo` 中的 `service-provider` 服务)来说,主要有三大类操作,分别为 `服务注册(Register)`、`服务续约(Renew)`、`服务下线(Cancel)`,接下来看看这三个操作是如何实现的。
|
||||
|
||||
对于服务提供方(比如 `Demo` 中的 `service-provider` 服务)来说,主要有三大类操作,分别为 `服务注册(Register)` 、 `服务续约(Renew)` 、 `服务下线(Cancel)` ,接下来看看这三个操作是如何实现的。
|
||||
|
||||
### 服务注册(Register)
|
||||
一个服务要对外提供服务,首先要在注册中心 `Eureka Server` 进行服务相关信息注册,能进行这一步的前提是你要配置 `eureka.client.register-with-eureka=true`,这个默认值为 `true`,注册中心不需要把自己注册到注册中心去,把这个配置设为 `false`,这个调用比较简单,主要调用流程如下图所示:
|
||||
|
||||
一个服务要对外提供服务,首先要在注册中心 `Eureka Server` 进行服务相关信息注册,能进行这一步的前提是你要配置 `eureka.client.register-with-eureka=true` ,这个默认值为 `true` ,注册中心不需要把自己注册到注册中心去,把这个配置设为 `false` ,这个调用比较简单,主要调用流程如下图所示:
|
||||
|
||||

|
||||
|
||||
### 服务续约(Renew)
|
||||
服务续约是由服务提供者方定期(默认为 `30` 秒)发起心跳的,主要是用来告知注册中心 `Eureka Server` 自己状态是正常的还活着,可以通过配置 `eureka.instance.lease-renewal-interval-in-seconds` 来修改,当然服务续约的前提是要配置 `eureka.client.register-with-eureka=true`,将该服务注册到注册中心中去,主要调用流程如下图所示:
|
||||
|
||||
服务续约是由服务提供者方定期(默认为 `30` 秒)发起心跳的,主要是用来告知注册中心 `Eureka Server` 自己状态是正常的还活着,可以通过配置 `eureka.instance.lease-renewal-interval-in-seconds` 来修改,当然服务续约的前提是要配置 `eureka.client.register-with-eureka=true` ,将该服务注册到注册中心中去,主要调用流程如下图所示:
|
||||
|
||||

|
||||
|
||||
### 服务下线(Cancel)
|
||||
当服务提供者方服务停止时,要发送 `DELETE` 请求告知注册中心 `Eureka Server` 自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类 `DiscoveryClient` 的 `shutdown` 方法加上注解 `@PreDestroy`,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示:
|
||||
|
||||
当服务提供者方服务停止时,要发送 `DELETE` 请求告知注册中心 `Eureka Server` 自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类 `DiscoveryClient` 的 `shutdown` 方法加上注解 `@PreDestroy` ,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示:
|
||||
|
||||

|
||||
|
||||
|
||||
## 服务消费者(Service Consumer)
|
||||
这里的服务消费者如果不需要被其它服务调用的话,其实只会涉及到两个操作,分别是从注册中心 `获取服务列表(Fetch)` 和 `更新服务列表(Update)`。如果同时也需要注册到注册中心对外提供服务的话,那么剩下的过程和上文提到的服务提供者是一致的,这里不再阐述,接下来看看这两个操作是如何实现的。
|
||||
|
||||
这里的服务消费者如果不需要被其它服务调用的话,其实只会涉及到两个操作,分别是从注册中心 `获取服务列表(Fetch)` 和 `更新服务列表(Update)` 。如果同时也需要注册到注册中心对外提供服务的话,那么剩下的过程和上文提到的服务提供者是一致的,这里不再阐述,接下来看看这两个操作是如何实现的。
|
||||
|
||||
### 获取服务列表(Fetch)
|
||||
|
||||
服务消费者方启动之后首先肯定是要先从注册中心 `Eureka Server` 获取到可用的服务列表同时本地也会缓存一份。这个获取服务列表的操作是在服务启动后 `DiscoverClient` 类实例化的时候执行的。
|
||||
|
||||

|
||||
|
||||
可以看出,能发生这个获取服务列表的操作前提是要保证配置了 `eureka.client.fetch-registry=true`,该配置的默认值为 `true`,主要调用流程如下图所示:
|
||||
可以看出,能发生这个获取服务列表的操作前提是要保证配置了 `eureka.client.fetch-registry=true` ,该配置的默认值为 `true` ,主要调用流程如下图所示:
|
||||
|
||||

|
||||
|
||||
### 更新服务列表(Update)
|
||||
由上面的 `获取服务列表(Fetch)` 操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心 `Eureka Server` 获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置 `eureka.client.registry-fetch-interval-seconds` 修改,默认为 `30` 秒,能进行这一步更新服务列表的前提是你要配置 `eureka.client.register-with-eureka=true`,这个默认值为 `true`。主要调用流程如下图所示:
|
||||
|
||||
由上面的 `获取服务列表(Fetch)` 操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心 `Eureka Server` 获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置 `eureka.client.registry-fetch-interval-seconds` 修改,默认为 `30` 秒,能进行这一步更新服务列表的前提是你要配置 `eureka.client.register-with-eureka=true` ,这个默认值为 `true` 。主要调用流程如下图所示:
|
||||
|
||||

|
||||
|
||||
|
||||
## 总结
|
||||
工作中项目使用的是 `Spring Cloud` 技术栈,它有一套非常完善的开源代码来整合 `Eureka`,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对 `Eureka` 服务发现组件有了更近一步的了解。
|
||||
|
||||
工作中项目使用的是 `Spring Cloud` 技术栈,它有一套非常完善的开源代码来整合 `Eureka` ,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对 `Eureka` 服务发现组件有了更近一步的了解。
|
||||
|
||||
---
|
||||
参考文章
|
||||
@ -117,4 +129,3 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
[Netflix Eureka](https://github.com/Netflix/eureka)
|
||||
|
||||
[Service Discovery in a Microservices Architecture](https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# 微服务治理策略
|
||||
|
||||
- Author: [HuiFer](https://github.com/huifer)
|
||||
- Description: 该文简单介绍微服务的治理策略以及应用技术
|
||||
* Author: [HuiFer](https://github.com/huifer)
|
||||
* Description: 该文简单介绍微服务的治理策略以及应用技术
|
||||
|
||||
## 服务的注册和发现
|
||||
|
||||
@ -15,21 +15,20 @@
|
||||
|
||||
> 解决方法: nginx 、 Ribbon
|
||||
|
||||
|
||||
## 通讯
|
||||
|
||||
> 解决问题: 各个服务之间的沟通桥梁
|
||||
|
||||
> 解决方法 :
|
||||
> - 同步消息
|
||||
> 1. rest
|
||||
> 1. rpc
|
||||
> - 异步消息
|
||||
> 1. MQ
|
||||
> - 同步消息
|
||||
> 1. rest
|
||||
> 1. rpc
|
||||
> - 异步消息
|
||||
> 1. MQ
|
||||
|
||||
## 配置管理
|
||||
|
||||
> 解决问题: 随着服务的增加配置也在增加,如何管理各个服务的配置
|
||||
> 解决问题: 随着服务的增加配置也在增加, 如何管理各个服务的配置
|
||||
|
||||
> 解决方法: nacos 、 spring cloud config 、 Apollo
|
||||
|
||||
@ -41,11 +40,11 @@
|
||||
|
||||
## 服务依赖关系
|
||||
|
||||
> 解决问题: 多个服务之间来回依赖,启动关系的不明确
|
||||
> 解决问题: 多个服务之间来回依赖, 启动关系的不明确
|
||||
|
||||
> 解决方法:
|
||||
|
||||
> 1. 应用分层: 数据层,业务层 数据层不需要依赖业务层,业务层依赖数据,规定上下依赖关系避免循环圈
|
||||
> 1. 应用分层: 数据层, 业务层 数据层不需要依赖业务层, 业务层依赖数据, 规定上下依赖关系避免循环圈
|
||||
|
||||
## 服务文档
|
||||
|
||||
@ -67,21 +66,21 @@
|
||||
|
||||
## 自动化测试
|
||||
|
||||
> 解决问题: 提前预知异常,确定服务是否可用
|
||||
> 解决问题: 提前预知异常, 确定服务是否可用
|
||||
|
||||
> 解决方法: junit
|
||||
|
||||
## 服务上线,下线的流程
|
||||
## 服务上线, 下线的流程
|
||||
|
||||
> 解决问题: 避免服务随意的上线下线
|
||||
|
||||
> 解决方法: 新服务上线需要经过管理人员审核.服务下线需要告知各个调用方进行修改,直到没有调用该服务才可以进行下线.
|
||||
> 解决方法: 新服务上线需要经过管理人员审核. 服务下线需要告知各个调用方进行修改, 直到没有调用该服务才可以进行下线.
|
||||
|
||||
## 兼容性
|
||||
|
||||
> 解决问题: 服务开发持续进行如何做到兼容
|
||||
|
||||
> 解决方法: 通过版本号的形式进行管理,修改完成进行回归测试
|
||||
> 解决方法: 通过版本号的形式进行管理, 修改完成进行回归测试
|
||||
|
||||
## 服务编排
|
||||
|
||||
@ -91,12 +90,12 @@
|
||||
|
||||
## 资源调度
|
||||
|
||||
> 解决问题: 每个服务的资源占用量不同,如何分配
|
||||
> 解决问题: 每个服务的资源占用量不同, 如何分配
|
||||
|
||||
> 解决方法: JVM 隔离、classload 隔离 ; 硬件隔离
|
||||
|
||||
## 容量规划
|
||||
|
||||
> 解决问题: 随着时间增长,调用逐步增加,什么时候追加机器
|
||||
> 解决问题: 随着时间增长, 调用逐步增加, 什么时候追加机器
|
||||
|
||||
> 解决方法: 统计每日调用量和响应时间, 根据机器情况设置阈值,超过阈值就可以追加机器
|
||||
> 解决方法: 统计每日调用量和响应时间, 根据机器情况设置阈值, 超过阈值就可以追加机器
|
||||
|
@ -1,211 +1,311 @@
|
||||
# 微服务技术栈
|
||||
- Author: [HuiFer](https://github.com/huifer)
|
||||
- Description: 该文简单介绍微服务技术栈有哪些分别用来做什么
|
||||
|
||||
* Author: [HuiFer](https://github.com/huifer)
|
||||
* Description: 该文简单介绍微服务技术栈有哪些分别用来做什么
|
||||
|
||||
## 技术栈
|
||||
|
||||
### 微服务开发
|
||||
|
||||
作用:快速开发服务。
|
||||
|
||||
- Spring
|
||||
- Spring MVC
|
||||
- Spring Boot
|
||||
* Spring
|
||||
* Spring MVC
|
||||
* Spring Boot
|
||||
|
||||
[官网](https://spring.io/),Spring 目前是 JavaWeb 开发人员必不可少的一个框架,SpringBoot 简化了 Spring 开发的配置目前也是业内主流开发框架。
|
||||
|
||||
### 微服务注册发现
|
||||
|
||||
作用:发现服务,注册服务,集中管理服务
|
||||
|
||||
#### Eureka
|
||||
- Eureka Server : 提供服务注册服务,各个节点启动后,会在 Eureka Server 中进行注册。
|
||||
- Eureka Client : 简化与 Eureka Server 的交互操作
|
||||
- Spring Cloud Netflix : [GitHub](https://github.com/spring-cloud/spring-cloud-netflix),[文档](https://cloud.spring.io/spring-cloud-netflix/reference/html/)
|
||||
|
||||
* Eureka Server : 提供服务注册服务, 各个节点启动后,会在 Eureka Server 中进行注册。
|
||||
* Eureka Client : 简化与 Eureka Server 的交互操作
|
||||
* Spring Cloud Netflix : [GitHub](https://github.com/spring-cloud/spring-cloud-netflix),[文档](https://cloud.spring.io/spring-cloud-netflix/reference/html/)
|
||||
|
||||
#### Zookeeper
|
||||
|
||||
> ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services.
|
||||
|
||||
[Zookeeper](https://github.com/apache/zookeeper) 是一个集中的服务,用于维护配置信息、命名、提供分布式同步和提供组服务。
|
||||
[Zookeeper](https://github.com/apache/zookeeper) 是一个集中的服务, 用于维护配置信息、命名、提供分布式同步和提供组服务。
|
||||
|
||||
#### Zookeeper 和 Eureka 区别
|
||||
|
||||
Zookeeper 保证 CP,Eureka 保证 AP:
|
||||
|
||||
- C:数据一致性;
|
||||
- A:服务可用性;
|
||||
- P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
|
||||
* C:数据一致性;
|
||||
* A:服务可用性;
|
||||
* P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。
|
||||
|
||||
### 微服务配置管理
|
||||
作用:统一管理一个或多个服务的配置信息,集中管理。
|
||||
|
||||
作用:统一管理一个或多个服务的配置信息, 集中管理。
|
||||
|
||||
#### Disconf
|
||||
Distributed Configuration Management Platform(分布式配置管理平台) ,它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务,是一套完整的基于 zookeeper 的分布式配置统一解决方案。
|
||||
|
||||
- [GitHub](https://github.com/knightliao/disconf)
|
||||
Distributed Configuration Management Platform(分布式配置管理平台) , 它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务, 是一套完整的基于 zookeeper 的分布式配置统一解决方案。
|
||||
|
||||
* [GitHub](https://github.com/knightliao/disconf)
|
||||
|
||||
#### SpringCloudConfig
|
||||
- [GitHub](https://github.com/spring-cloud/spring-cloud-config)
|
||||
|
||||
* [GitHub](https://github.com/spring-cloud/spring-cloud-config)
|
||||
|
||||
#### Apollo
|
||||
|
||||
Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,用于微服务配置管理场景。
|
||||
|
||||
- [GitHub](https://github.com/ctripcorp/apollo)
|
||||
* [GitHub](https://github.com/ctripcorp/apollo)
|
||||
|
||||
### 权限认证
|
||||
作用:根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少。
|
||||
|
||||
作用:根据系统设置的安全规则或者安全策略, 用户可以访问而且只能访问自己被授权的资源,不多不少。
|
||||
|
||||
#### Spring Security
|
||||
- [官网](https://spring.io/projects/spring-security)
|
||||
|
||||
* [官网](https://spring.io/projects/spring-security)
|
||||
|
||||
#### apache Shiro
|
||||
|
||||
> Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
|
||||
|
||||
- [官网](http://shiro.apache.org/)
|
||||
* [官网](http://shiro.apache.org/)
|
||||
|
||||
### 批处理
|
||||
|
||||
作用: 批量处理同类型数据或事物
|
||||
|
||||
#### Spring Batch
|
||||
- [官网](官网)
|
||||
### 定时任务
|
||||
> 作用: 定时做什么.
|
||||
#### Quartz
|
||||
- [官网](http://www.quartz-scheduler.org/)
|
||||
|
||||
* [官网](官网)
|
||||
|
||||
### 定时任务
|
||||
|
||||
> 作用: 定时做什么.
|
||||
|
||||
#### Quartz
|
||||
|
||||
* [官网](http://www.quartz-scheduler.org/)
|
||||
|
||||
### 微服务调用 (协议)
|
||||
|
||||
> 通讯协议
|
||||
|
||||
#### Rest
|
||||
- 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互
|
||||
|
||||
* 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互
|
||||
|
||||
#### RPC
|
||||
- Remote Procedure Call
|
||||
- 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。
|
||||
|
||||
* Remote Procedure Call
|
||||
* 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。
|
||||
|
||||
#### gRPC
|
||||
- [官网](https://www.grpc.io/)
|
||||
- > A high-performance, open-source universal RPC framework
|
||||
> 所谓 RPC(remote procedure call 远程过程调用) 框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从 server/client 模型。使用的时候客户端调用 server 端提供的接口就像是调用本地的函数一样。
|
||||
#### RMI
|
||||
- Remote Method Invocation
|
||||
- 纯 Java 调用
|
||||
|
||||
* [官网](https://www.grpc.io/)
|
||||
* > A high-performance, open-source universal RPC framework
|
||||
|
||||
> 所谓 RPC(remote procedure call 远程过程调用) 框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从 server/client 模型。使用的时候客户端调用 server 端提供的接口就像是调用本地的函数一样。
|
||||
|
||||
#### RMI
|
||||
|
||||
* Remote Method Invocation
|
||||
* 纯 Java 调用
|
||||
|
||||
### 服务接口调用
|
||||
|
||||
> 作用:多个服务之间的通讯
|
||||
|
||||
#### Feign(HTTP)
|
||||
|
||||
Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。
|
||||
|
||||
- [GitHub](https://github.com/OpenFeign/feign)
|
||||
* [GitHub](https://github.com/OpenFeign/feign)
|
||||
|
||||
### 服务熔断
|
||||
|
||||
> 作用: 当请求到达一定阈值时不让请求继续.
|
||||
|
||||
#### Hystrix
|
||||
- > Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.
|
||||
|
||||
* > Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.
|
||||
|
||||
>
|
||||
- [GitHub](https://github.com/Netflix/Hystrix)
|
||||
|
||||
* [GitHub](https://github.com/Netflix/Hystrix)
|
||||
|
||||
#### Sentinel
|
||||
- > A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库)
|
||||
- [GitHub](https://github.com/alibaba/Sentinel)
|
||||
|
||||
|
||||
|
||||
|
||||
* > A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库)
|
||||
* [GitHub](https://github.com/alibaba/Sentinel)
|
||||
|
||||
### 服务的负载均衡
|
||||
> 作用:降低服务压力,增加吞吐量
|
||||
#### Ribbon
|
||||
- >Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于 Netflix Ribbon 实现
|
||||
>
|
||||
- [GitHub](https://github.com/Netflix/ribbon)
|
||||
#### Nginx
|
||||
Nginx (engine x) 是一个高性能的 HTTP 和反向代理 web 服务器,同时也提供了 IMAP/POP3/SMTP 服务
|
||||
|
||||
- [GitHub](https://github.com/nginx/nginx)
|
||||
> 作用:降低服务压力, 增加吞吐量
|
||||
|
||||
#### Ribbon
|
||||
|
||||
* >Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具, 它基于 Netflix Ribbon 实现
|
||||
|
||||
>
|
||||
|
||||
* [GitHub](https://github.com/Netflix/ribbon)
|
||||
|
||||
#### Nginx
|
||||
|
||||
Nginx (engine x) 是一个高性能的 HTTP 和反向代理 web 服务器, 同时也提供了 IMAP/POP3/SMTP 服务
|
||||
|
||||
* [GitHub](https://github.com/nginx/nginx)
|
||||
|
||||
#### Nginx 与 Ribbon 区别
|
||||
|
||||
1. Nginx 属于服务端负载均衡,Ribbon 属于客户端负载均衡.Nginx 作用与 Tomcat,Ribbon 作用与各个服务之间的调用 (RPC)
|
||||
|
||||
### 消息队列
|
||||
> 作用: 解耦业务,异步化处理数据
|
||||
#### Kafka
|
||||
- [官网](http://kafka.apache.org/)
|
||||
#### RabbitMQ
|
||||
- [官网](https://www.rabbitmq.com/)
|
||||
#### RocketMQ
|
||||
- [官网](http://rocketmq.apache.org/)
|
||||
#### activeMQ
|
||||
- [官网](http://activemq.apache.org/)
|
||||
|
||||
> 作用: 解耦业务, 异步化处理数据
|
||||
|
||||
#### Kafka
|
||||
|
||||
* [官网](http://kafka.apache.org/)
|
||||
|
||||
#### RabbitMQ
|
||||
|
||||
* [官网](https://www.rabbitmq.com/)
|
||||
|
||||
#### RocketMQ
|
||||
|
||||
* [官网](http://rocketmq.apache.org/)
|
||||
|
||||
#### activeMQ
|
||||
|
||||
* [官网](http://activemq.apache.org/)
|
||||
|
||||
### 日志采集 (elk)
|
||||
> 作用:收集各服务日志提供日志分析、用户画像等
|
||||
#### Elasticsearch
|
||||
- [GitHub](https://github.com/elastic/elasticsearch)
|
||||
#### Logstash
|
||||
- [GitHub](https://github.com/elastic/logstash)
|
||||
#### Kibana
|
||||
- [GitHub](https://github.com/elastic/kibana)
|
||||
|
||||
> 作用: 收集各服务日志提供日志分析、用户画像等
|
||||
|
||||
#### Elasticsearch
|
||||
|
||||
* [GitHub](https://github.com/elastic/elasticsearch)
|
||||
|
||||
#### Logstash
|
||||
|
||||
* [GitHub](https://github.com/elastic/logstash)
|
||||
|
||||
#### Kibana
|
||||
|
||||
* [GitHub](https://github.com/elastic/kibana)
|
||||
|
||||
### API 网关
|
||||
> 作用:外部请求通过 API 网关进行拦截处理,再转发到真正的服务
|
||||
|
||||
> 作用: 外部请求通过 API 网关进行拦截处理, 再转发到真正的服务
|
||||
|
||||
#### Zuul
|
||||
|
||||
> Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more.
|
||||
>
|
||||
- [GitHub](https://github.com/Netflix/zuul)
|
||||
|
||||
* [GitHub](https://github.com/Netflix/zuul)
|
||||
|
||||
### 服务监控
|
||||
> 作用:以可视化或非可视化的形式展示出各个服务的运行情况 (CPU、内存、访问量等)
|
||||
|
||||
> 作用: 以可视化或非可视化的形式展示出各个服务的运行情况 (CPU、内存、访问量等)
|
||||
|
||||
#### Zabbix
|
||||
- [GitHub](https://github.com/jjmartres/Zabbix)
|
||||
|
||||
* [GitHub](https://github.com/jjmartres/Zabbix)
|
||||
|
||||
#### Nagios
|
||||
- [官网](https://www.nagios.org/)
|
||||
|
||||
* [官网](https://www.nagios.org/)
|
||||
|
||||
#### Metrics
|
||||
- [官网](https://metrics.dropwizard.io)
|
||||
|
||||
* [官网](https://metrics.dropwizard.io)
|
||||
|
||||
### 服务链路追踪
|
||||
> 作用:明确服务之间的调用关系
|
||||
|
||||
> 作用: 明确服务之间的调用关系
|
||||
|
||||
#### Zipkin
|
||||
- [GitHub](https://github.com/openzipkin/zipkin)
|
||||
|
||||
* [GitHub](https://github.com/openzipkin/zipkin)
|
||||
|
||||
#### Brave
|
||||
- [GitHub](https://github.com/openzipkin/brave)
|
||||
|
||||
* [GitHub](https://github.com/openzipkin/brave)
|
||||
|
||||
### 数据存储
|
||||
|
||||
> 作用: 存储数据
|
||||
|
||||
#### 关系型数据库
|
||||
|
||||
##### MySql
|
||||
- [官网](https://www.mysql.com/)
|
||||
|
||||
* [官网](https://www.mysql.com/)
|
||||
|
||||
##### Oracle
|
||||
- [官网](https://www.oracle.com/index.html)
|
||||
|
||||
* [官网](https://www.oracle.com/index.html)
|
||||
|
||||
##### MsSql
|
||||
- [官网](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15)
|
||||
|
||||
* [官网](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15)
|
||||
|
||||
##### PostgreSql
|
||||
- [官网](https://www.postgresql.org/)
|
||||
|
||||
* [官网](https://www.postgresql.org/)
|
||||
|
||||
#### 非关系型数据库
|
||||
|
||||
##### Mongodb
|
||||
- [官网](https://www.mongodb.com/)
|
||||
|
||||
* [官网](https://www.mongodb.com/)
|
||||
|
||||
##### Elasticsearch
|
||||
- [GitHub](https://github.com/elastic/elasticsearch)
|
||||
|
||||
* [GitHub](https://github.com/elastic/elasticsearch)
|
||||
|
||||
### 缓存
|
||||
|
||||
> 作用: 存储数据
|
||||
|
||||
#### redis
|
||||
- [官网](https://redis.io/)
|
||||
|
||||
* [官网](https://redis.io/)
|
||||
|
||||
### 分库分表
|
||||
|
||||
> 作用: 数据库分库分表方案.
|
||||
|
||||
#### shardingsphere
|
||||
- [官网](http://shardingsphere.apache.org/)
|
||||
|
||||
* [官网](http://shardingsphere.apache.org/)
|
||||
|
||||
#### Mycat
|
||||
- [官网](http://www.mycat.io/)
|
||||
|
||||
|
||||
|
||||
* [官网](http://www.mycat.io/)
|
||||
|
||||
### 服务部署
|
||||
|
||||
> 作用: 将项目快速部署、上线、持续集成.
|
||||
|
||||
#### Docker
|
||||
- [官网](http://www.docker.com/)
|
||||
|
||||
* [官网](http://www.docker.com/)
|
||||
|
||||
#### Jenkins
|
||||
- [官网](https://jenkins.io/zh/)
|
||||
|
||||
* [官网](https://jenkins.io/zh/)
|
||||
|
||||
#### Kubernetes(K8s)
|
||||
- [官网](https://kubernetes.io/)
|
||||
|
||||
* [官网](https://kubernetes.io/)
|
||||
|
||||
#### Mesos
|
||||
- [官网](http://mesos.apache.org/)
|
||||
|
||||
* [官网](http://mesos.apache.org/)
|
||||
|
@ -1,27 +1,32 @@
|
||||
# 什么是微服务?微服务之间是如何独立通讯的?
|
||||
- Author: [HuiFer](https://github.com/huifer)
|
||||
- Description: 介绍微服务的定义以及服务间的通信。
|
||||
|
||||
* Author: [HuiFer](https://github.com/huifer)
|
||||
* Description: 介绍微服务的定义以及服务间的通信。
|
||||
|
||||
## 什么是微服务
|
||||
- 微服务架构是一个分布式系统, 按照业务进行划分成为不同的服务单元, 解决单体系统性能等不足。
|
||||
- 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。
|
||||
|
||||
* 微服务架构是一个分布式系统, 按照业务进行划分成为不同的服务单元, 解决单体系统性能等不足。
|
||||
* 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。
|
||||
|
||||
> 微服务概念起源: [Microservices](https://martinfowler.com/articles/microservices.html)
|
||||
|
||||
## 微服务之间是如何独立通讯的
|
||||
|
||||
### 同步
|
||||
|
||||
#### REST HTTP 协议
|
||||
|
||||
REST 请求在微服务中是最为常用的一种通讯方式, 它依赖于 HTTP\HTTPS 协议。RESTFUL 的特点是:
|
||||
|
||||
1. 每一个 URI 代表 1 种资源
|
||||
2. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作: GET 用来获取资源, POST 用来新建资源(也可以用于更新资源), PUT 用来更新资源, DELETE 用来删除资源
|
||||
3. 通过操作资源的表现形式来操作资源
|
||||
4. 资源的表现形式是 XML 或者 HTML
|
||||
5. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
|
||||
1\. 每一个 URI 代表 1 种资源
|
||||
2\. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作: GET 用来获取资源, POST 用来新建资源\(也可以用于更新资源\), PUT 用来更新资源, DELETE 用来删除资源
|
||||
3\. 通过操作资源的表现形式来操作资源
|
||||
4\. 资源的表现形式是 XML 或者 HTML
|
||||
5\. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息
|
||||
|
||||
举个例子,有一个服务方提供了如下接口:
|
||||
|
||||
```java
|
||||
``` java
|
||||
@RestController
|
||||
@RequestMapping("/communication")
|
||||
public class RestControllerDemo {
|
||||
@ -34,7 +39,7 @@ public class RestControllerDemo {
|
||||
|
||||
另外一个服务需要去调用该接口,调用方只需要根据 API 文档发送请求即可获取返回结果。
|
||||
|
||||
```java
|
||||
``` java
|
||||
@RestController
|
||||
@RequestMapping("/demo")
|
||||
public class RestDemo{
|
||||
@ -51,26 +56,26 @@ public class RestDemo{
|
||||
|
||||
通过这样的方式可以实现服务之间的通讯。
|
||||
|
||||
|
||||
#### RPC TCP协议
|
||||
|
||||
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。它的工作流程是这样的:
|
||||
|
||||
1. 执行客户端调用语句,传送参数
|
||||
2. 调用本地系统发送网络消息
|
||||
3. 消息传送到远程主机
|
||||
4. 服务器得到消息并取得参数
|
||||
5. 根据调用请求以及参数执行远程过程(服务)
|
||||
6. 执行过程完毕,将结果返回服务器句柄
|
||||
7. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果
|
||||
8. 消息传回本地主机
|
||||
9. 客户端句柄由本地主机的网络服务接收消息
|
||||
1\. 执行客户端调用语句,传送参数
|
||||
2\. 调用本地系统发送网络消息
|
||||
3\. 消息传送到远程主机
|
||||
4\. 服务器得到消息并取得参数
|
||||
5\. 根据调用请求以及参数执行远程过程(服务)
|
||||
6\. 执行过程完毕,将结果返回服务器句柄
|
||||
7\. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果
|
||||
8\. 消息传回本地主机
|
||||
9\. 客户端句柄由本地主机的网络服务接收消息
|
||||
10. 客户端接收到调用语句返回的结果数据
|
||||
|
||||
举个例子。
|
||||
|
||||
首先需要一个服务端:
|
||||
|
||||
```java
|
||||
``` java
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
@ -178,7 +183,7 @@ public class RPCServer {
|
||||
|
||||
其次需要一个客户端:
|
||||
|
||||
```java
|
||||
``` java
|
||||
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
@ -237,7 +242,7 @@ public class RPCclient<T> {
|
||||
|
||||
再来一个测试的远程方法。
|
||||
|
||||
```java
|
||||
``` java
|
||||
public interface Tinterface {
|
||||
String send(String msg);
|
||||
}
|
||||
@ -253,7 +258,7 @@ public class TinterfaceImpl implements Tinterface {
|
||||
|
||||
测试代码如下:
|
||||
|
||||
```java
|
||||
``` java
|
||||
|
||||
import com.huifer.admin.rpc.Tinterface;
|
||||
import com.huifer.admin.rpc.TinterfaceImpl;
|
||||
@ -276,16 +281,16 @@ public class RunTest {
|
||||
Tinterface tinterface = RPCclient.getRemoteProxyObj(Tinterface.class, new InetSocketAddress("localhost", 10000));
|
||||
System.out.println(tinterface.send("rpc 测试用例"));
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
输出 `send message rpc 测试用例`。
|
||||
|
||||
输出 `send message rpc 测试用例` 。
|
||||
|
||||
### 异步
|
||||
|
||||
#### 消息中间件
|
||||
|
||||
> 常见的消息中间件有 Kafka、ActiveMQ、RabbitMQ、RocketMQ , 常见的协议有AMQP、MQTTP、STOMP、XMPP. 这里不对消息队列进行拓展了, 具体如何使用还是请移步官网.
|
||||
>
|
||||
|
@ -1,4 +1,5 @@
|
||||
# 微服务
|
||||
|
||||
> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。<br> 本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。
|
||||
|
||||
过去几年中出现了“微服务架构”这一术语,它描述了将软件应用程序设计为若干个可独立部署的服务套件的特定方法。尽管这种架构风格尚未有精确的定义,但围绕业务能力、自动部署、端点智能以及语言和数据的分散控制等组织来说,它们还是存在着某些共同特征。
|
||||
@ -20,9 +21,11 @@
|
||||
我们并不认为微服务风格是新颖的或创新的,其根源至少可以追溯到 Unix 的设计原则。但我们认为没有足够多的人考虑微服务架构,如果使用它,许多软件的开发会变得更好。
|
||||
|
||||
## 微服务架构的特征
|
||||
|
||||
虽然不能说微服务架构风格有正式的定义,但我们可以尝试描述一下我们认为的在符合这个标签的架构中,它们所具有的一些共同特征。与概述共同特征的任何定义一样,并非所有微服务架构都具有所有特征,但我们确实期望大多数微服务架构都具有大多数特征。虽然我们的作者一直是这个相当宽松的社区的活跃成员,但我们的本意还是尝试描述我们两人在自己和自己所了解的团队的工作中所看到的情况。特别要说明的是,我们没有制定一些相关的定义。
|
||||
|
||||
### 通过服务进行组件化
|
||||
|
||||
只要我们参与软件行业,就一直希望通过将组件集成在一起来构建系统,就像我们在物理世界中看到的事物的构建方式一样。在过去的几十年中,我们已经看到了大多数语言平台的公共软件库都取得了极大的进展。
|
||||
|
||||
在谈论组件时,就会碰到一个有关定义的难题,即什么是组件?我们的定义是,组件是可独立更换和升级的软件单元。
|
||||
@ -38,6 +41,7 @@
|
||||
近似地,我们可以把一个个服务映射为一个个运行时进程,但这仅仅是一个近似而已。一个服务可能包括多个始终一起开发和部署的进程,比如一个应用系统的进程和仅由该服务使用的数据库。
|
||||
|
||||
### 围绕业务能力进行组织
|
||||
|
||||
在将大型应用程序拆分为多个部分时,管理层往往侧重于技术层面,从而导致 UI 团队、服务器端逻辑团队、数据库团队的划分。当团队按照这些方式分开时,即便是简单的更改也可能导致跨团队项目的时间和预算批准。一个聪明的团队将围绕这个进行优化,“两害相权取其轻”——只需将逻辑强制应用到他们可以访问的任何应用程序中。换句话说,逻辑无处不在。这是康威定律[5]的一个例子。
|
||||
|
||||
> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。<br> —— 梅尔文•康威,1967年
|
||||
@ -53,6 +57,7 @@
|
||||
大型单体应用程序也可以围绕业务功能进行模块化,尽管这不是常见的情况。当然,我们会敦促构建单体应用系统的大型团队根据业务线来将自己分解为若干小团队。我们在这里看到的主要问题是,它们往往围绕太多的上下文进行组织。如果单体跨越了模块边界,对团队的个体成员来说,很难将它们装入短期的记忆中。此外,我们看到模块化生产线需要大量的规则来执行。服务组件所要求的更加明确的分离,使得它更容易保持团队边界清晰。
|
||||
|
||||
### 是产品不是项目
|
||||
|
||||
我们看到的大多数应用程序开发工作都使用这样一个项目模式:目标是交付一些软件,然后就完工了。一旦完成后,软件将移交给维护组织,然后构建它的项目团队也随之解散了。
|
||||
|
||||
微服务支持者倾向于避免这种模式,而是认为团队应该负责产品的整个生命周期。对此一个共同的启示是亚马逊的 [“you build, you run it”](https://queue.acm.org/detail.cfm?id=1142065) 的概念,开发团队对生产中的软件负全部责任。这使开发者经常接触他们的软件在生产环境如何工作,并增加与他们的用户联系,因为他们必须承担至少部分的支持工作。
|
||||
@ -62,6 +67,7 @@
|
||||
没有理由说为什么这种方法不能用在单一应用程序上,但较小的服务粒度,使得它更容易在服务开发者和用户之间建立个人关系。
|
||||
|
||||
### 智能端点和哑管
|
||||
|
||||
在不同进程之间建立通信时,我们已经看到许多产品和方法,都强调将大量的智能特性放入通信机制本身。一个很好的例子是企业服务总线(ESB),其中 ESB 产品通常包括用于消息路由、编排、转换和应用业务规则的复杂工具。
|
||||
|
||||
微服务社区倾向于采用另一种方法:智能端点和哑管。基于微服务构建的应用程序的目标是尽可能的解耦和尽可能的内聚——他们拥有自己的领域逻辑,他们的行为更像经典 UNIX 理念中的过滤器——接收请求,应用适当的逻辑并产生响应。使用简单的 REST 风格的协议来编排它们,而不是使用像 WS-Choreography 或者 BPEL 或者通过中心工具编制(orchestration)等复杂的协议。
|
||||
@ -77,6 +83,7 @@
|
||||
单体应用中,组件都在同一进程内执行,它们之间通过方法调用或函数调用通信。把单体变成微服务最大的问题在于通信模式的改变。一种幼稚的转换是从内存方法调用转变成 RPC,这导致频繁通信且性能不好。相反,你需要用粗粒度通信代替细粒度通信。
|
||||
|
||||
### 去中心化的治理
|
||||
|
||||
集中治理的一个后果是单一技术平台的标准化发展趋势。经验表明,这种方法正在收缩 ——不是每个问题都是钉子,不是每个问题都是锤子。我们更喜欢使用正确的工具来完成工作,而单体应用程序在一定程度上可以利用语言的优势,这是不常见的。
|
||||
|
||||
把单体的组件分裂成服务,在构建这些服务时可以有自己的选择。你想使用 Node.js 开发一个简单的报告页面?去吧。用 C++ 实现一个特别粗糙的近乎实时的组件?好极了。你想换用一个更适合组件读操作数据的不同风格的数据库?我们有技术来重建它。
|
||||
@ -92,6 +99,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
|
||||
也许去中心化治理的最高境界就是亚马逊广为流传的 build it/run it 理念。团队要对他们构建的软件的各方面负责,包括 7*24 小时的运营。这一级别的责任下放绝对是不规范的,但我们看到越来越多的公司让开发团队负起更多责任。Netflix 是采用这一理念的另一家公司[11]。每天凌晨 3 点被传呼机叫醒无疑是一个强有力的激励,使你在写代码时关注质量。这是关于尽可能远离传统的集中治理模式的一些想法。
|
||||
|
||||
### 分散数据管理
|
||||
|
||||
数据管理的去中心化有许多不同的呈现方式。在最抽象的层面上,这意味着使系统间存在差异的世界概念模型。在整合一个大型企业时,客户的销售视图将不同于支持视图,这是一个常见的问题。客户的销售视图中的一些事情可能不会出现在支持视图中。它们确实可能有不同的属性和(更坏的)共同属性,这些共同属性在语义上有微妙的不同。
|
||||
|
||||
这个问题常见于应用程序之间,但也可能发生在应用程序内部,尤其当应用程序被划分成分离的组件时。一个有用的思维方式是[有界上下文](http://martinfowler.com/bliki/BoundedContext.html)(Bounded Context)内的领域驱动设计(Domain-Driven Design, DDD)理念。DDD 把一个复杂域划分成多个有界的上下文,并且映射出它们之间的关系。这个过程对单体架构和微服务架构都是有用的,但在服务和上下文边界间有天然的相关性,边界有助于澄清和加强分离,就像业务能力部分描述的那样。
|
||||
@ -107,6 +115,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
|
||||
对很多开发团队来说,选择用这样的方式管理不一致性是一个新的挑战,但这通常与业务实践相匹配。通常业务处理一定程度的不一致,以快速响应需求,同时有某些类型的逆转过程来处理错误。这种权衡是值得的,只要修复错误的代价小于更大一致性下损失业务的代价。
|
||||
|
||||
### 基建自动化
|
||||
|
||||
基础设施自动化技术在过去几年中发生了巨大变化——特别是云和 AWS 的发展降低了构建、部署和运行微服务的操作复杂性。
|
||||
|
||||
许多使用微服务构建的产品或系统都是由具有丰富的持续交付和持续集成经验的团队构建的。以这种方式构建软件的团队广泛使用基础设施自动化技术。如下面显示的构建管道所示。
|
||||
@ -122,6 +131,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
|
||||

|
||||
|
||||
### 设计时为故障做好准备
|
||||
|
||||
使用服务作为组件的结果是,需要设计应用程序以便它们能够容忍服务的失败。如果服务提供者商不可用,任何服务呼叫都可能失败,客户必须尽可能优雅地对此做出响应。与单体设计相比,这是一个缺点,因为它这会引入额外的复杂性来处理它。结果是微服务团队不断反思服务失败是如何影响用户体验的。Netflix 的 [Simian Army](https://github.com/Netflix/SimianArmy) 能够引发服务甚至数据中心的故障在工作日发生故障,从而来测试应用程序的弹性和监控能力。
|
||||
|
||||
生产中的这种自动化测试足以让大多数运维团队兴奋得浑身颤栗,就像在一周的长假即将到来前一样。这并不是说单体架构风格不能构建先进的监控系统——只是根据我们的经验,这在单体系统中并不常见罢了。
|
||||
@ -135,6 +145,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
|
||||
微服务团队希望看到针对每个服务的复杂监控和日志记录,例如显示“运行/宕机”状态的仪表盘以及各种运维和业务相关的指标。有关断路器状态,当前吞吐量和延迟的详细信息也是我们在工作中经常遇到的其他例子。
|
||||
|
||||
### 演化设计
|
||||
|
||||
微服务从业者通常有进化设计的背景,并把服务分解视为进一步的工具,使应用程序开发人员能够控制应用程序中的更改,而不会降低变更速度。变更控制并不一定意味着变更的减少——在正确的态度和工具的帮助下,你可以对软件进行频繁,快速且有良好控制的更改。
|
||||
|
||||
每当要试图将软件系统分解为组件时,你就会面临这样的决策,即如何进行拆分——我们决定拆分应用程序的原则是什么?组件的关键属性具有独立替换和可升级性的特点[13]——这意味着我们寻找这些点,想象如何在不影响其协作者的情况下重写组件。实际上,许多微服务组通过明确地期望许多服务被废弃而不是长期演变来进一步考虑这一点。
|
||||
@ -146,6 +157,7 @@ Guardian 网站是设计和构建成单体应用程序的一个很好的例子
|
||||
将组件放入服务中可以为更细粒度的发布计划添加机会。对于单体来说,任何更改都需要完整构建和部署整个应用程序。但是,使用微服务,你只需要重新部署你修改的服务。这可以简化并加快发布过程。缺点是你必须担心一项服务的变化会打破其消费者。传统的集成方法是尝试使用版本控制来解决这个问题,但微服务世界中的偏好是仅仅把使用版本控制作为最后的手段。我们可以通过设计服务尽可能容忍服务提供者的变化来避免大量的版本控制。
|
||||
|
||||
## 微服务是未来吗?
|
||||
|
||||
我们写这篇文章的主要目的是解释微服务的主要思想和原则。通过花时间来做到这一点,我们清楚地认为微服务架构风格是一个重要的想法——在研发企业系统时,值得对它进行认真考虑。我们最近使用这种方式构建了几个系统,并且了解到其它团队也赞同这种风格。
|
||||
|
||||
我们了解到那些在某种程度上开创这种架构风格的先驱,包括亚马逊、Netflix、英国卫报、英国政府数字化服务中心、realestate.com.au、Forward 和 comparethemarket.com。2013 年的技术会议上充满了一些公司的例子,这些公司正在转向可以归类为微服务的公司,包括 Travis CI。此外,有很多组织长期以来一直在做我们称之为微服务的东西,但没有使用过这个名字。(通常这被标记为 SOA——尽管如我们所说,SOA 有许多相互矛盾的形式。[15])
|
||||
@ -165,6 +177,7 @@ Guardian 网站是设计和构建成单体应用程序的一个很好的例子
|
||||
所以我们谨慎乐观地写下这个。到目前为止,我们已经看到了足够多的微服务风格,觉得它可能是一条值得走的路。我们无法确定最终会在哪里结束,但软件开发的挑战之一是你只能根据你当前必须拥有的不完善信息做出决策。
|
||||
|
||||
## 脚注
|
||||
|
||||
1: The term "microservice" was discussed at a workshop of software architects near Venice in May, 2011 to describe what the participants saw as a common architectural style that many of them had been recently exploring. In May 2012, the same group decided on "microservices" as the most appropriate name. James presented some of these ideas as a case study in March 2012 at 33rd Degree in Krakow in [Microservices - Java, the Unix Way](http://2012.33degree.org/talk/show/67) as did Fred George [about the same time](http://www.slideshare.net/fredgeorge/micro-service-architecure). Adrian Cockcroft at Netflix, describing this approach as "fine grained SOA" was pioneering the style at web scale as were many of the others mentioned in this article - Joe Walnes, Dan North, Evan Botcher and Graham Tackley.
|
||||
|
||||
2: The term monolith has been in use by the Unix community for some time. It appears in [The Art of Unix Programming](https://www.amazon.com/gp/product/B003U2T5BA?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=B003U2T5BA) to describe systems that get too big.
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字来源于雨林中的绞杀藤(strangler vine),也叫绞杀榕 (strangler fig)。绞杀藤为了爬到森林顶端都要缠绕着大树生长,一段时间后,树死了,留下树形藤。这种应用也使用同一种模式,围绕着传统应用开发了新型微服务应用,传统应用会渐渐退出舞台。
|
||||
|
||||
|
||||
我们来看看其他可行策略。
|
||||
|
||||
# 策略 1——停止挖掘
|
||||
@ -25,9 +24,9 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
|
||||
|
||||
微服务有三种方式访问单体应用数据:
|
||||
|
||||
- 换气单体应用提供的远程 API
|
||||
- 直接访问单体应用数据库
|
||||
- 自己维护一份从单体应用中同步的数据
|
||||
* 换气单体应用提供的远程 API
|
||||
* 直接访问单体应用数据库
|
||||
* 自己维护一份从单体应用中同步的数据
|
||||
|
||||
胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 *Domain Driven Design*,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。
|
||||
|
||||
@ -46,6 +45,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
|
||||
3. 数据访问层——访问基础元素,例如数据库和消息代理。
|
||||
|
||||
在表现层与业务数据访问层之间有清晰的隔离。业务层有由若干方面组成的粗粒度(coarse-grained)的 API,内部包含了业务逻辑元素。API 是可以将单体业务分割成两个更小应用的天然边界,其中一个应用是表现层,另外一个是业务和数据访问逻辑。分割后,表现逻辑应用远程调用业务逻辑应用,下图表示迁移前后架构不同:
|
||||
|
||||

|
||||
|
||||
单体应用这么分割有两个好处,其一使得应用两部分开发、部署和扩展各自独立,特别地,允许表现层开发者在用户界面上快速选择,进行 A/B 测试;其二,使得一些远程 API 可以被微服务调用。
|
||||
|
@ -1,4 +1,5 @@
|
||||
- 高并发架构
|
||||
|
||||
- [消息队列](./docs/high-concurrency/mq-interview.md)
|
||||
- [为什么使用消息队列?](./docs/high-concurrency/why-mq.md)
|
||||
- [如何保证消息队列的高可用?](./docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
|
||||
@ -39,7 +40,7 @@
|
||||
- 高并发系统
|
||||
- [如何设计一个高并发系统?](./docs/high-concurrency/high-concurrency-design.md)
|
||||
|
||||
- 分布式系统
|
||||
* 分布式系统
|
||||
- [面试连环炮](./docs/distributed-system/distributed-system-interview.md)
|
||||
- 系统拆分
|
||||
- [为什么要进行系统拆分?](./docs/distributed-system/why-dubbo.md)
|
||||
@ -64,7 +65,7 @@
|
||||
- 分布式会话
|
||||
- [集群分布式 Session 如何实现?](./docs/distributed-system/distributed-session.md)
|
||||
|
||||
- 高可用架构
|
||||
* 高可用架构
|
||||
- 基于 Hystrix 实现高可用
|
||||
- [Hystrix 介绍](./docs/high-availability/hystrix-introduction.md)
|
||||
- [电商网站详情页系统架构](./docs/high-availability/e-commerce-website-detail-page-architecture.md)
|
||||
@ -92,7 +93,7 @@
|
||||
- 降级
|
||||
- 如何进行降级?
|
||||
|
||||
- 微服务架构
|
||||
* 微服务架构
|
||||
- 微服务的一些概念
|
||||
- [关于微服务架构的描述](./docs/micro-services/microservices-introduction.md)
|
||||
- [从单体式架构迁移到微服务架构](./docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md)
|
||||
@ -110,7 +111,7 @@
|
||||
- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
|
||||
- [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md)
|
||||
|
||||
- 海量数据处理
|
||||
* 海量数据处理
|
||||
- 10 道经典的海量数据处理面试题
|
||||
- [如何从大量的 URL 中找出相同的 URL?](./docs/big-data/find-common-urls.md)
|
||||
- [如何从大量数据中找出高频词?](./docs/big-data/find-top-100-words.md)
|
||||
|
Loading…
Reference in New Issue
Block a user