diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 0000000..7097cc9 --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,24 @@ +name: Sync + +on: + push: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Sync to Gitee + uses: wearerequired/git-mirror-action@master + env: + SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} + with: + source-repo: "git@github.com:doocs/advanced-java.git" + destination-repo: "git@gitee.com:Doocs/advanced-java.git" + + - name: Build Gitee Pages + uses: yanglbme/gitee-pages-action@master + with: + gitee-username: yanglbme + gitee-password: ${{ secrets.GITEE_PASSWORD }} + gitee-repo: doocs/advanced-java diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index d20aa62..0000000 --- a/CODEOWNERS +++ /dev/null @@ -1,7 +0,0 @@ -*.md @yanglbme -*.html @yanglbme -LICENSE @yanglbme -.gitignore @yanglbme - -docs/ @yanglbme -images/ @yanglbme diff --git a/README.md b/README.md index 01a4762..86a4bad 100644 --- a/README.md +++ b/README.md @@ -1,159 +1,189 @@ # 互联网 Java 工程师进阶知识完全扫盲[©](https://github.com/yanglbme) +[![actions status](https://github.com/doocs/advanced-java/workflows/Sync/badge.svg)](https://github.com/doocs/advanced-java/actions) [![license](https://badgen.net/github/license/doocs/advanced-java?color=green)](https://github.com/doocs/advanced-java/blob/master/LICENSE) [![original](https://badgen.net/badge/original/%E4%B8%AD%E5%8D%8E%E7%9F%B3%E6%9D%89/orange)](https://github.com/doocs/advanced-java) -[![notice](https://badgen.net/badge/notice/%E7%BB%B4%E6%9D%83%E8%A1%8C%E5%8A%A8/red)](/docs/from-readers/rights-defending-movement.md) [![wechat-group](https://badgen.net/badge/chat/%E5%BE%AE%E4%BF%A1%E4%BA%A4%E6%B5%81/138c7b)](#公众号) [![reading](https://badgen.net/badge/books/read%20together/cyan)](https://github.com/doocs/technical-books) [![coding](https://badgen.net/badge/leetcode/coding%20together/cyan)](https://github.com/doocs/leetcode) -[![doocs](https://badgen.net/badge/organization/join%20us/cyan)](https://doocs.github.io/#/?id=how-to-join) +[![notice](https://badgen.net/badge/notice/%E7%BB%B4%E6%9D%83%E8%A1%8C%E5%8A%A8/red)](/docs/extra-page/rights-defending-action.md) [![stars](https://badgen.net/github/stars/doocs/advanced-java)](https://github.com/doocs/advanced-java/stargazers) [![contributors](https://badgen.net/github/contributors/doocs/advanced-java)](https://github.com/doocs/advanced-java/tree/master/docs/from-readers#contributors) [![help-wanted](https://badgen.net/github/label-issues/doocs/advanced-java/help%20wanted/open)](https://github.com/doocs/advanced-java/labels/help%20wanted) [![issues](https://badgen.net/github/open-issues/doocs/advanced-java)](https://github.com/doocs/advanced-java/issues) [![PRs Welcome](https://badgen.net/badge/PRs/welcome/green)](http://makeapullrequest.com) +[![doocs](https://badgen.net/badge/organization/join%20us/green)](https://doocs.github.io/#/?id=how-to-join) -本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)、[海量数据处理](#海量数据处理)等领域知识。我([@yanglbme](https://github.com/yanglbme))对这部分知识做了一个系统的整理,方便学习查阅。配合《[大型网站技术架构](https://github.com/doocs/technical-books#architecture)——李智慧》、《[Redis 设计与实现](https://github.com/doocs/technical-books#database)——[黄健宏](https://github.com/huangz1990)》、《[Redis 深度历险](https://github.com/doocs/technical-books#database)——钱文品》、《[亿级流量网站架构核心技术](https://github.com/doocs/technical-books#architecture)——张开涛》食用,[效果更佳](https://doocs.github.io/advanced-java/#/docs/extra-page/offer)。 +本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)、[海量数据处理](#海量数据处理)等领域知识。我([@yanglbme](https://github.com/yanglbme))对这部分知识做了一个系统的整理,方便学习查阅。配合《[大型网站技术架构](https://github.com/doocs/technical-books#architecture)——李智慧》、《[Redis 设计与实现](https://github.com/doocs/technical-books#database)——[黄健宏](https://github.com/huangz1990)》、《[Redis 深度历险](https://github.com/doocs/technical-books#database)——钱文品》、《[亿级流量网站架构核心技术](https://github.com/doocs/technical-books#architecture)——张开涛》食用,[效果更佳](https://doocs.github.io/advanced-java/#/docs/extra-page/offer)。本项目将不断更新更多优质内容,欢迎各位开发者朋友们保持关注。 -学习之前,先来看看 [Issues 讨论区](https://github.com/doocs/advanced-java/issues/9#issue-394275038)的技术面试官是怎么说的吧。本项目也欢迎各位开发者朋友到 [Issues 讨论区](https://github.com/doocs/advanced-java/issues)分享自己的一些想法和实践经验,参与或加入开源组织请看[这里](https://github.com/doocs/advanced-java/issues/61),你也访问 [GitHub Page](https://doocs.github.io) 详细了解一下 Doocs。 +学习之前,先来看看 [Issues 讨论区](https://github.com/doocs/advanced-java/issues/9#issue-394275038)的技术面试官是怎么说的吧。本项目也欢迎各位开发者朋友到 [Issues 讨论区](https://github.com/doocs/advanced-java/issues)分享自己的一些想法和实践经验,参与或加入 Doocs 开源社区请看[这里](https://github.com/doocs/advanced-java/issues/61),你也访问 [GitHub Pages](https://doocs.github.io) 详细了解一下 Doocs。 -[另外,我还将在这里更新内容,感兴趣的朋友可以进来看看。](/docs/extra-page/subscriptions-for-doocs.md) +[另外,我还将在这里更新内容,感兴趣的朋友可以进来看看](/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 ## 高并发架构 + ### [消息队列](./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%20.md) -- [微服务治理策略](/docs/micro-services/huifer-micro-service-governance.md) -- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? -- ...... + +* [什么是微服务?微服务之间是如何独立通讯的?](/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)。 - + - + ## 公众号 -GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 + +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 @@ -170,4 +200,4 @@ GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎
-
\ No newline at end of file + diff --git a/docs/big-data/README.md b/docs/big-data/README.md index 140b150..39dee6f 100644 --- a/docs/big-data/README.md +++ b/docs/big-data/README.md @@ -1,19 +1,21 @@ # 海量数据处理 -- [如何从大量的 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 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 + +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 @@ -30,4 +32,4 @@ GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎
-
\ No newline at end of file + diff --git a/docs/big-data/count-different-phone-numbers.md b/docs/big-data/count-different-phone-numbers.md index 992e2ae..2d7a4c1 100644 --- a/docs/big-data/count-different-phone-numbers.md +++ b/docs/big-data/count-different-phone-numbers.md @@ -1,9 +1,11 @@ ## 如何统计不同电话号码的个数? ### 题目描述 + 已知某个文件内包含一些电话号码,每个号码为 8 位数字,统计不同号码的个数。 ### 解答思路 + 这道题本质还是求解**数据重复**的问题,对于这类问题,一般首先考虑位图法。 对于本题,8 位电话号码可以表示的号码个数为 108 个,即 1 亿个。我们每个号码用一个 bit 来表示,则总共需要 1 亿个 bit,内存占用约 100M。 @@ -13,4 +15,5 @@ 申请一个位图数组,长度为 1 亿,初始化为 0。然后遍历所有电话号码,把号码对应的位图中的位置置为 1。遍历完成后,如果 bit 为 1,则表示这个电话号码在文件中存在,否则不存在。bit 值为 1 的数量即为 不同电话号码的个数。 ### 方法总结 -求解数据重复问题,记得考虑位图法。 \ No newline at end of file + +求解数据重复问题,记得考虑位图法。 diff --git a/docs/big-data/find-a-number-if-exists.md b/docs/big-data/find-a-number-if-exists.md index 1df6376..2862db6 100644 --- a/docs/big-data/find-a-number-if-exists.md +++ b/docs/big-data/find-a-number-if-exists.md @@ -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 表示不存在。 ### 方法总结 -**判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。 \ No newline at end of file + +**判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。 diff --git a/docs/big-data/find-common-urls.md b/docs/big-data/find-common-urls.md index 4059ba8..bb42271 100644 --- a/docs/big-data/find-common-urls.md +++ b/docs/big-data/find-common-urls.md @@ -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 存储到 a0, a1, a2, ..., a999,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b0, b1, b2, ..., b999 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a0 对应 b0, ..., a999 对应 b999,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。 +首先遍历文件 a,对遍历到的 URL 求 `hash(URL) % 1000` ,根据计算结果把遍历到的 URL 存储到 a0, a1, a2, ..., a999,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b0, b1, b2, ..., b999 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a0 对应 b0, ..., a999 对应 b999,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。 -接着遍历 ai( `i∈[0,999]`),把 URL 存储到一个 HashSet 集合中。然后遍历 bi 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。 +接着遍历 ai( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 bi 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。 ### 方法总结 + 1. 分而治之,进行哈希取余; -2. 对每个子文件进行 HashSet 统计。 \ No newline at end of file +2. 对每个子文件进行 HashSet 统计。 diff --git a/docs/big-data/find-hotest-query-string.md b/docs/big-data/find-hotest-query-string.md index 9a9a061..6ef41d1 100644 --- a/docs/big-data/find-hotest-query-string.md +++ b/docs/big-data/find-hotest-query-string.md @@ -1,14 +1,17 @@ ## 如何查询最热门的查询串? ### 题目描述 -搜索引擎会通过日志文件把用户每次检索使用的所有查询串都记录下来,每个查询床的长度不超过 255 字节。 + +搜索引擎会通过日志文件把用户每次检索使用的所有查询串都记录下来,每个查询串的长度不超过 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 @@ 最后依然使用小顶堆来对字符串的出现次数进行排序。 ### 方法总结 -前缀树经常被用来统计字符串的出现次数。它的另外一个大的用途是字符串查找,判断是否有重复的字符串等。 \ No newline at end of file + +前缀树经常被用来统计字符串的出现次数。它的另外一个大的用途是字符串查找,判断是否有重复的字符串等。 diff --git a/docs/big-data/find-mid-value-in-500-millions.md b/docs/big-data/find-mid-value-in-500-millions.md index f436a54..213dd2a 100644 --- a/docs/big-data/find-mid-value-in-500-millions.md +++ b/docs/big-data/find-mid-value-in-500-millions.md @@ -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 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 { > **注意**,当数据总数为偶数,如果划分后两个文件中的数据有相同个数,那么中位数就是数据较小的文件中的最大值与数据较大的文件中的最小值的平均值。 ### 方法总结 + 分治法,真香! diff --git a/docs/big-data/find-no-repeat-number.md b/docs/big-data/find-no-repeat-number.md index 665e05b..9a265d8 100644 --- a/docs/big-data/find-no-repeat-number.md +++ b/docs/big-data/find-no-repeat-number.md @@ -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 表示这个数字出现了多次。 那么这 232 个整数,总共所需内存为 232*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作: 遍历 2.5 亿个整数,查看位图中对应的位,如果是 00,则变为 01,如果是 01 则变为 10,如果是 10 则保持不变。遍历结束后,查看位图,把对应位是 01 的整数输出即可。 ### 方法总结 -**判断数字是否重复的问题**,位图法是一种非常高效的方法。 \ No newline at end of file + +**判断数字是否重复的问题**,位图法是一种非常高效的方法。 diff --git a/docs/big-data/find-rank-top-500-numbers.md b/docs/big-data/find-rank-top-500-numbers.md index 882f4e7..9ba0559 100644 --- a/docs/big-data/find-rank-top-500-numbers.md +++ b/docs/big-data/find-rank-top-500-numbers.md @@ -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 { } } - class Test { public static int[] getTop(int[][] data) { int rowSize = data.length; @@ -104,4 +105,5 @@ class Test { ``` ### 方法总结 -求 TopK,不妨考虑一下堆排序? \ No newline at end of file + +求 TopK,不妨考虑一下堆排序? diff --git a/docs/big-data/find-top-1-ip.md b/docs/big-data/find-top-1-ip.md index e4274c6..8289b6e 100644 --- a/docs/big-data/find-top-1-ip.md +++ b/docs/big-data/find-top-1-ip.md @@ -1,14 +1,17 @@ ## 如何找出某一天访问百度网站最多的 IP? ### 题目描述 + 现有海量日志数据保存在一个超大文件中,该文件无法直接读入内存,要求从中提取某天访问百度次数最多的那个 IP。 ### 解答思路 + 这道题只关心某一天访问百度最多的 IP,因此,可以首先对文件进行一次遍历,把这一天访问百度 IP 的相关信息记录到一个单独的大文件中。接下来采用的方法与上一题一样,大致就是先对 IP 进行哈希映射,接着使用 HashMap 统计重复 IP 的次数,最后计算出重复次数最多的 IP。 > 注:这里只需要找出出现次数最多的 IP,可以不必使用堆,直接用一个变量 max 即可。 ### 方法总结 + 1. 分而治之,进行哈希取余; 2. 使用 HashMap 统计频数; -3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**。 \ No newline at end of file +3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**。 diff --git a/docs/big-data/find-top-100-words.md b/docs/big-data/find-top-100-words.md index 90697dc..391c7a0 100644 --- a/docs/big-data/find-top-100-words.md +++ b/docs/big-data/find-top-100-words.md @@ -1,20 +1,23 @@ ## 如何从大量数据中找出高频词? ### 题目描述 + 有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。 ### 解答思路 + 由于内存限制,我们依然无法直接将大文件的所有词一次读到内存中。因此,同样可以采用**分治策略**,把一个大文件分解成多个小文件,保证每个文件的大小小于 1MB,进而直接将单个小文件读取到内存中进行处理。 **思路如下**: -首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000`,将结果为 i 的词存放到文件 ai 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。 +首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000` ,将结果为 i 的词存放到文件 ai 中。遍历结束后,我们可以得到 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 个,用**大顶堆**。 \ No newline at end of file +3. 求解**最大**的 TopN 个,用**小顶堆**;求解**最小**的 TopN 个,用**大顶堆**。 diff --git a/docs/big-data/sort-the-query-strings-by-counts.md b/docs/big-data/sort-the-query-strings-by-counts.md index c724356..1f386f2 100644 --- a/docs/big-data/sort-the-query-strings-by-counts.md +++ b/docs/big-data/sort-the-query-strings-by-counts.md @@ -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 都读入内存,因此需要使用外排序)。 ### 方法总结 -- 内存若够,直接读入进行排序; -- 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。 \ No newline at end of file + +* 内存若够,直接读入进行排序; +* 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。 diff --git a/docs/distributed-system/README.md b/docs/distributed-system/README.md index 840a642..33a79aa 100644 --- a/docs/distributed-system/README.md +++ b/docs/distributed-system/README.md @@ -1,32 +1,40 @@ # 分布式系统 + ## [面试连环炮](/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 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 + +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 @@ -43,4 +51,4 @@ GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎
-
\ No newline at end of file + diff --git a/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md b/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md index 67cf070..3f6f4f9 100644 --- a/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md +++ b/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md @@ -1,37 +1,39 @@ ## 面试题 -一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? +一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? ## 面试官心理分析 + 其实一般问问题,都是这么问的,先问问你 zk,然后其实是要过渡到 zk 相关的一些问题里去,比如分布式锁。因为在分布式系统开发中,分布式锁的使用场景还是很常见的。 ## 面试题剖析 -### redis 分布式锁 -官方叫做 `RedLock` 算法,是 redis 官方支持的分布式锁算法。 +### Redis 分布式锁 + +官方叫做 `RedLock` 算法,是 Redis 官方支持的分布式锁算法。 这个分布式锁有 3 个重要的考量点: -- 互斥(只能有一个客户端获取锁) -- 不能死锁 -- 容错(只要大部分 redis 节点创建了这把锁就可以) +* 互斥(只能有一个客户端获取锁) +* 不能死锁 +* 容错(只要大部分 Redis 节点创建了这把锁就可以) -#### redis 最普通的分布式锁 +#### Redis 最普通的分布式锁 -第一个最普通的实现方式,就是在 redis 里使用 `setnx` 命令创建一个 key,这样就算加锁。 +第一个最普通的实现方式,就是在 Redis 里使用 `SET key value [EX seconds] [PX milliseconds] NX` 创建一个 key,这样就算加锁。其中: +- `NX`:表示只有 `key` 不存在的时候才会设置成功,如果此时 redis 中存在这个 `key`,那么设置失败,返回 `nil`。 +- `EX seconds`:设置 `key` 的过期时间,精确到秒级。意思是 `seconds` 秒后锁自动释放,别人创建的时候如果发现已经有了就不能加锁了。 +- `PX milliseconds`:同样是设置 `key` 的过期时间,精确到毫秒级。 + +比如执行以下命令: ```r -SET resource_name my_random_value NX PX 30000 +SET resource_name my_random_value PX 30000 NX ``` -执行这个命令就 ok。 - -- `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]) @@ -42,19 +44,20 @@ end 为啥要用 `random_value` 随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 `lua` 脚本来释放锁。 -但是这样是肯定不行的。因为如果是普通的 redis 单实例,那就是单点故障。或者是 redis 普通主从,那 redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。 +但是这样是肯定不行的。因为如果是普通的 Redis 单实例,那就是单点故障。或者是 Redis 普通主从,那 Redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。 #### RedLock 算法 -这个场景是假设有一个 redis cluster,有 5 个 redis master 实例。然后执行如下步骤获取一把锁: + +这个场景是假设有一个 Redis cluster,有 5 个 Redis master 实例。然后执行如下步骤获取一把锁: 1. 获取当前时间戳,单位是毫秒; 2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒; -3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1`; +3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1` ; 4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了; 5. 要是锁建立失败了,那么就依次之前建立过的锁删除; 6. 只要别人建立了一把分布式锁,你就得**不断轮询去尝试获取锁**。 -![redis-redlock](/images/redis-redlock.png) +![redis-redlock](./images/redis-redlock.png) [Redis 官方](https://redis.io/)给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看:https://redis.io/topics/distlock 。 @@ -62,7 +65,7 @@ end zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能**注册个监听器**监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。 -```java +``` java /** * ZooKeeperSession */ @@ -191,12 +194,11 @@ public class ZooKeeperSession { } ``` - 也可以采用另一种方式,创建临时顺序节点: -如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 zookeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。 +如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 ZooKeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。 -```java +``` java public class ZooKeeperDistributedLock implements Watcher { private ZooKeeper zk; @@ -325,11 +327,11 @@ public class ZooKeeperDistributedLock implements Watcher { ### redis 分布式锁和 zk 分布式锁的对比 -- redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。 -- zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 +* redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。 +* zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 -另外一点就是,如果是 redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。 +另外一点就是,如果是 Redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。 -redis 分布式锁大家没发现好麻烦吗?遍历上锁,计算时间等等......zk 的分布式锁语义清晰实现简单。 +Redis 分布式锁大家没发现好麻烦吗?遍历上锁,计算时间等等......zk 的分布式锁语义清晰实现简单。 -所以先不分析太多的东西,就说这两点,我个人实践认为 zk 的分布式锁比 redis 的分布式锁牢靠、而且模型简单易用。 +所以先不分析太多的东西,就说这两点,我个人实践认为 zk 的分布式锁比 Redis 的分布式锁牢靠、而且模型简单易用。 diff --git a/docs/distributed-system/distributed-session.md b/docs/distributed-system/distributed-session.md index a2b4b97..fd3d87e 100644 --- a/docs/distributed-system/distributed-session.md +++ b/docs/distributed-system/distributed-session.md @@ -1,31 +1,35 @@ ## 面试题 -集群部署时的分布式 session 如何实现? +集群部署时的分布式 Session 如何实现? ## 面试官心理分析 -面试官问了你一堆 dubbo 是怎么玩儿的,你会玩儿 dubbo 就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最大的问题就是**分布式事务**、**接口幂等性**、**分布式锁**,还有最后一个就是**分布式 session**。 + +面试官问了你一堆 Dubbo 是怎么玩儿的,你会玩儿 Dubbo 就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最大的问题就是**分布式事务**、**接口幂等性**、**分布式锁**,还有最后一个就是**分布式 Session**。 当然了,分布式系统中的问题何止这么一点,非常之多,复杂度很高,这里只是说一下常见的几个问题,也是面试的时候常问的几个。 ## 面试题剖析 -session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 `jsessionid cookie`,就根据这个东西,在服务端可以维护一个对应的 session 域,里面可以放点数据。 -一般的话只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。 +Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存在,然后每次发请求过来都带上一个特殊的 `jsessionid cookie` ,就根据这个东西,在服务端可以维护一个对应的 Session 域,里面可以放点数据。 + +一般的话只要你没关掉浏览器,Cookie 还在,那么对应的那个 Session 就在,但是如果 Cookie 没了,Session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。 这个不多说了,懂 Java 的都该知道这个。 -单块系统的时候这么玩儿 session 没问题,但是你要是分布式系统呢,那么多的服务,session 状态在哪儿维护啊? +单块系统的时候这么玩儿 Session 没问题,但是你要是分布式系统呢,那么多的服务,Session 状态在哪儿维护啊? 其实方法很多,但是常见常用的是以下几种: -### 完全不用 session +### 完全不用 Session + 使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。 -### tomcat + redis -这个其实还挺方便的,就是使用 session 的代码,跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 tomcat 都将 session 数据存储到 redis 即可。 +### Tomcat + Redis -在 tomcat 的配置文件中配置: +这个其实还挺方便的,就是使用 Session 的代码,跟以前一样,还是基于 Tomcat 原生的 Session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 Tomcat 都将 Session 数据存储到 Redis 即可。 -```xml +在 Tomcat 的配置文件中配置: + +``` xml ``` -然后指定 redis 的 host 和 port 就 ok 了。 +然后指定 Redis 的 host 和 port 就 ok 了。 ```xml @@ -45,17 +49,19 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存 maxInactiveInterval="60"/> ``` -还可以用上面这种方式基于 redis 哨兵支持的 redis 高可用集群来保存 session 数据,都是 ok 的。 +还可以用上面这种方式基于 Redis 哨兵支持的 Redis 高可用集群来保存 Session 数据,都是 ok 的。 -### spring session + redis -上面所说的第二种方式会与 tomcat 容器重耦合,如果我要将 web 容器迁移成 jetty,难道还要重新把 jetty 都配置一遍? +### Spring Session + Redis -因为上面那种 tomcat + redis 的方式好用,但是会**严重依赖于web容器**,不好将代码移植到其他 web 容器上去,尤其是你要是换了技术栈咋整?比如换成了 spring cloud 或者是 spring boot 之类的呢? +上面所说的第二种方式会与 Tomcat 容器重耦合,如果我要将 Web 容器迁移成 Jetty,难道还要重新把 Jetty 都配置一遍? -所以现在比较好的还是基于 Java 一站式解决方案,也就是 spring。人家 spring 基本上承包了大部分我们需要使用的框架,spirng cloud 做微服务,spring boot 做脚手架,所以用 sping session 是一个很好的选择。 +因为上面那种 Tomcat + Redis 的方式好用,但是会**严重依赖于 Web 容器**,不好将代码移植到其他 Web 容器上去,尤其是你要是换了技术栈咋整?比如换成了 Spring Cloud 或者是 Spring Boot 之类的呢? + +所以现在比较好的还是基于 Java 一站式解决方案,也就是 Spring。人家 Spring 基本上承包了大部分我们需要使用的框架,Spirng Cloud 做微服务,Spring Boot 做脚手架,所以用 Spring Session 是一个很好的选择。 在 pom.xml 中配置: -```xml + +``` xml org.springframework.session spring-session-data-redis @@ -68,8 +74,9 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存 ``` -在 spring 配置文件中配置: -```xml +在 Spring 配置文件中配置: + +``` xml @@ -92,7 +99,8 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存 ``` 在 web.xml 中配置: -```xml + +``` xml springSessionRepositoryFilter org.springframework.web.filter.DelegatingFilterProxy @@ -104,7 +112,8 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存 ``` 示例代码: -```java + +``` java @RestController @RequestMapping("/test") public class TestController { @@ -123,7 +132,6 @@ public class TestController { } ``` +上面的代码就是 ok 的,给 Spring Session 配置基于 Redis 来存储 Session 数据,然后配置了一个 Spring Session 的过滤器,这样的话,Session 相关操作都会交给 Spring Session 来管了。接着在代码中,就用原生的 Session 操作,就是直接基于 Spring Session 从 Redis 中获取数据了。 -上面的代码就是 ok 的,给 sping session 配置基于 redis 来存储 session 数据,然后配置了一个 spring session 的过滤器,这样的话,session 相关操作都会交给 spring session 来管了。接着在代码中,就用原生的 session 操作,就是直接基于 spring sesion 从 redis 中获取数据了。 - -实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。 \ No newline at end of file +实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,Tomcat + Redis 早期比较常用,但是会重耦合到 Tomcat 中;近些年,通过 Spring Session 来实现。 diff --git a/docs/distributed-system/distributed-system-idempotency.md b/docs/distributed-system/distributed-system-idempotency.md index ca576e4..4b5a78c 100644 --- a/docs/distributed-system/distributed-system-idempotency.md +++ b/docs/distributed-system/distributed-system-idempotency.md @@ -2,6 +2,7 @@ 分布式服务接口的幂等性如何设计(比如不能重复扣款)? ## 面试官心理分析 + 从这个问题开始,面试官就已经进入了**实际的生产问题**的面试了。 一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢? @@ -13,15 +14,17 @@ 所以你肯定得知道这事儿,否则你做出来的分布式系统恐怕容易埋坑。 ## 面试题剖析 + 这个不是技术问题,这个没有通用的一个方法,这个应该**结合业务**来保证幂等性。 所谓**幂等性**,就是说一个接口,多次发起同一个请求,你这个接口得保证结果是准确的,比如不能多扣款、不能多插入一条数据、不能将统计值多加了 1。这就是幂等性。 其实保证幂等性主要是三点: -- 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。 -- 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。 -- 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。 -实际运作过程中,你要结合自己的业务来,比如说利用 redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。 +* 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。 +* 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。 +* 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。 -要求是支付一个订单,必须插入一条支付流水,order_id 建一个唯一键 `unique key`。你在支付一个订单之前,先插入一条支付流水,order_id 就已经进去了。你就可以写一个标识到 redis 里面去,`set order_id payed`,下一次重复请求过来了,先查 redis 的 order_id 对应的 value,如果是 `payed` 就说明已经支付过了,你就别重复支付了。 \ No newline at end of file +实际运作过程中,你要结合自己的业务来,比如说利用 Redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。 + +要求是支付一个订单,必须插入一条支付流水,order_id 建一个唯一键 `unique key` 。你在支付一个订单之前,先插入一条支付流水,order_id 就已经进去了。你就可以写一个标识到 Redis 里面去, `set order_id payed` ,下一次重复请求过来了,先查 Redis 的 order_id 对应的 value,如果是 `payed` 就说明已经支付过了,你就别重复支付了。 diff --git a/docs/distributed-system/distributed-system-interview.md b/docs/distributed-system/distributed-system-interview.md index b27a8b6..2c2f32a 100644 --- a/docs/distributed-system/distributed-system-interview.md +++ b/docs/distributed-system/distributed-system-interview.md @@ -1,30 +1,37 @@ ## 分布式系统面试连环炮 -有一些同学,之前呢主要是做传统行业,或者外包项目,一直是在那种小的公司,技术一直都搞的比较简单。他们有共同的一个问题,就是都没怎么搞过分布式系统,现在互联网公司,一般都是做分布式的系统,大家都不是做底层的分布式系统、分布式存储系统 hadoop hdfs、分布式计算系统 hadoop mapreduce / spark、分布式流式计算系统 storm。 +有一些同学,之前呢主要是做传统行业,或者外包项目,一直是在那种小的公司,技术一直都搞的比较简单。他们有共同的一个问题,就是都没怎么搞过分布式系统,现在互联网公司,一般都是做分布式的系统,大家都不是做底层的分布式系统、分布式存储系统 Hadoop HDFS、分布式计算系统 Hadoop MapReduce / Spark、分布式流式计算系统 Storm。 分布式业务系统,就是把原来用 Java 开发的一个大块系统,给拆分成**多个子系统**,多个子系统之间互相调用,形成一个大系统的整体。假设原来你做了一个 OA 系统,里面包含了权限模块、员工模块、请假模块、财务模块,一个工程,里面包含了一堆模块,模块与模块之间会互相去调用,1 台机器部署。现在如果你把这个系统给拆开,权限系统、员工系统、请假系统、财务系统 4 个系统,4 个工程,分别在 4 台机器上部署。一个请求过来,完成这个请求,这个员工系统,调用权限系统,调用请假系统,调用财务系统,4 个系统分别完成了一部分的事情,最后 4 个系统都干完了以后,才认为是这个请求已经完成了。 -![simple-distributed-system-oa](/images/simple-distributed-system-oa.png) +![simple-distributed-system-oa](./images/simple-distributed-system-oa.png) -> 近几年开始兴起和流行 Spring Cloud,刚流行,还没开始普及,目前普及的是 dubbo,因此这里也主要讲 dubbo。 +> 近几年开始兴起和流行 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 如何实现? diff --git a/docs/distributed-system/distributed-system-request-sequence.md b/docs/distributed-system/distributed-system-request-sequence.md index 2712d4c..3c8476f 100644 --- a/docs/distributed-system/distributed-system-request-sequence.md +++ b/docs/distributed-system/distributed-system-request-sequence.md @@ -2,6 +2,7 @@ 分布式服务接口请求的顺序性如何保证? ## 面试官心理分析 + 其实分布式系统接口的调用顺序,也是个问题,一般来说是不用保证顺序的。但是**有时候**可能确实是需要**严格的顺序**保证。给大家举个例子,你服务 A 调用服务 B,先插入再删除。好,结果俩请求过去了,落在不同机器上,可能插入请求因为某些原因执行慢了一些,导致删除请求先执行了,此时因为没数据所以啥效果也没有;结果这个时候插入请求过来了,好,数据插入进去了,那就尴尬了。 本来应该是 “先插入 -> 再删除”,这条数据应该没了,结果现在 “先删除 -> 再插入”,数据还存在,最后你死都想不明白是怎么回事。 @@ -9,12 +10,13 @@ 所以这都是分布式系统一些很常见的问题。 ## 面试题剖析 + 首先,一般来说,个人建议是,你们从业务逻辑上设计的这个系统最好是不需要这种顺序性的保证,因为一旦引入顺序性保障,比如使用**分布式锁**,会**导致系统复杂度上升**,而且会带来**效率低下**,热点数据压力过大等问题。 -下面我给个我们用过的方案吧,简单来说,首先你得用 dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上,因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去,强制排队,这样来确保他们的顺序性。 +下面我给个我们用过的方案吧,简单来说,首先你得用 Dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上,因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去,强制排队,这样来确保他们的顺序性。 -![distributed-system-request-sequence](/images/distributed-system-request-sequence.png) +![distributed-system-request-sequence](./images/distributed-system-request-sequence.png) -但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成**热点**怎么办?解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已,所以,还是建议什么呢? +但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成**热点**怎么办?解决这些问题又要开启后续一连串的复杂技术方案...... 曾经这类问题弄的我们头疼不已,所以,还是建议什么呢? -最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是其它什么,避免这种问题的产生。 \ No newline at end of file +最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是其它什么,避免这种问题的产生。 diff --git a/docs/distributed-system/distributed-transaction.md b/docs/distributed-system/distributed-transaction.md index 01f7ea1..c2ea6c3 100644 --- a/docs/distributed-system/distributed-transaction.md +++ b/docs/distributed-system/distributed-transaction.md @@ -2,20 +2,23 @@ 分布式事务了解吗?你们是如何解决分布式事务问题的? ## 面试官心理分析 + 只要聊到你做了分布式系统,必问分布式事务,你对分布式事务一无所知的话,确实会很坑,你起码得知道有哪些方案,一般怎么来做,每个方案的优缺点是什么。 现在面试,分布式系统成了标配,而分布式系统带来的**分布式事务**也成了标配了。因为你做系统肯定要用事务吧,如果是分布式系统,肯定要用分布式事务吧。先不说你搞过没有,起码你得明白有哪几种方案,每种方案可能有啥坑?比如 TCC 方案的网络问题、XA 方案的一致性问题。 ## 面试题剖析 + 分布式事务的实现主要有以下 5 种方案: -- XA 方案 -- TCC 方案 -- 本地消息表 -- 可靠消息最终一致性方案 -- 最大努力通知方案 +* XA 方案 +* TCC 方案 +* 本地消息表 +* 可靠消息最终一致性方案 +* 最大努力通知方案 ### 两阶段提交方案/XA方案 + 所谓的 XA 方案,即:两阶段提交,有一个**事务管理器**的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。 这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 `Spring + JTA` 就可以搞定,自己随便搜个 demo 看看就知道了。 @@ -26,14 +29,15 @@ 如果你要操作别人的服务的库,你必须是通过**调用别的服务的接口**来实现,绝对不允许交叉访问别人的数据库。 -![distributed-transacion-XA](/images/distributed-transaction-XA.png) +![distributed-transacion-XA](./images/distributed-transaction-XA.png) ### TCC 方案 -TCC 的全称是:`Try`、`Confirm`、`Cancel`。 -- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 -- Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 -- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) +TCC 的全称是: `Try` 、 `Confirm` 、 `Cancel` 。 + +* Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 +* Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 +* Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) 这种方案说实话几乎很少人使用,我们用的也比较少,但是也有使用的场景。因为这个**事务回滚**实际上是**严重依赖于你自己写代码来回滚和补偿**了,会造成补偿代码巨大,非常之恶心。 @@ -43,9 +47,10 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。 但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。 -![distributed-transacion-TCC](/images/distributed-transaction-TCC.png) +![distributed-transacion-TCC](./images/distributed-transaction-TCC.png) ### 本地消息表 + 本地消息表其实是国外的 ebay 搞出来的这么一套思想。 这个大概意思是这样的: @@ -59,9 +64,10 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。 这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。 -![distributed-transaction-local-message-table](/images/distributed-transaction-local-message-table.png) +![distributed-transaction-local-message-table](./images/distributed-transaction-local-message-table.png) ### 可靠消息最终一致性方案 + 这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。 大概的意思就是: @@ -71,11 +77,12 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。 3. 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务; 4. mq 会自动**定时轮询**所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。 5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。 -6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 +6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你就用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 -![distributed-transaction-reliable-message](/images/distributed-transaction-reliable-message.png) +![distributed-transaction-reliable-message](./images/distributed-transaction-reliable-message.png) ### 最大努力通知方案 + 这个方案的大致意思就是: 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 来玩儿。 \ No newline at end of file +当然如果你愿意,你可以参考可靠消息最终一致性方案来自己实现一套分布式事务,比如基于 RocketMQ 来玩儿。 diff --git a/docs/distributed-system/dubbo-load-balancing.md b/docs/distributed-system/dubbo-load-balancing.md index 36dfd12..62130bb 100644 --- a/docs/distributed-system/dubbo-load-balancing.md +++ b/docs/distributed-system/dubbo-load-balancing.md @@ -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 +### dubbo 负载均衡策略 + +#### RandomLoadBalance + +默认情况下,dubbo 是 RandomLoadBalance ,即**随机**调用实现负载均衡,可以对 provider 不同实例**设置不同的权重**,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。 + +#### RoundRobinLoadBalance + 这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。 举个栗子。 @@ -24,62 +30,75 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略 这个时候,可以给两台 8 核 16G 的机器设置权重 4,给剩余 1 台 4 核 8G 的机器设置权重 2。 -#### leastactive loadbalance +#### LeastActiveLoadBalance + 这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给**不活跃的性能差的机器更少的请求**。 -#### consistanthash loadbalance +#### ConsistentHashLoadBalance + 一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。**如果你需要的不是随机负载均衡**,是要一类请求都到一个节点,那就走这个一致性 Hash 策略。 +> 关于 dubbo 负载均衡策略更加详细的描述,可以查看官网 http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html 。 + ### dubbo 集群容错策略 -#### failover cluster 模式 + +#### Failover Cluster 模式 失败自动切换,自动重试其他机器,**默认**就是这个,常见于读操作。(失败重试其它机器) 可以通过以下几种方式配置重试次数: -```xml +``` xml ``` 或者 -```xml +``` xml ``` 或者 -```xml +``` xml ``` -#### failfast cluster 模式 +#### Failfast Cluster 模式 + 一次调用失败就立即失败,常见于非幂等性的写操作,比如新增一条记录(调用失败就立即失败) -#### failsafe cluster 模式 +#### Failsafe Cluster 模式 + 出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。 配置示例如下: -```xml +``` xml ``` 或者 -```xml +``` xml ``` -#### failback cluster 模式 +#### Failback Cluster 模式 + 失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种。 -#### forking cluster 模式 +#### Forking Cluster 模式 + **并行调用**多个 provider,只要一个成功就立即返回。常用于实时性要求比较高的读操作,但是会浪费更多的服务资源,可通过 `forks="2"` 来设置最大并行数。 -#### broadcacst cluster -逐个调用所有的 provider。任何一个 provider 出错则报错(从`2.1.0` 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。 +#### Broadcast Cluster 模式 -### dubbo动态代理策略 -默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。 \ No newline at end of file +逐个调用所有的 provider。任何一个 provider 出错则报错(从 `2.1.0` 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。 + +> 关于 dubbo 集群容错策略更加详细的描述,可以查看官网 http://dubbo.apache.org/zh-cn/docs/source_code_guide/cluster.html 。 + +### dubbo 动态代理策略 + +默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。 diff --git a/docs/distributed-system/dubbo-operating-principle.md b/docs/distributed-system/dubbo-operating-principle.md index 2a06d73..b5c04cb 100644 --- a/docs/distributed-system/dubbo-operating-principle.md +++ b/docs/distributed-system/dubbo-operating-principle.md @@ -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 都异步通知监控中心 -![dubbo-operating-principle](/images/dubbo-operating-principle.png) +* 第一步:provider 向注册中心去注册 +* 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务 +* 第三步:consumer 调用 provider +* 第四步:consumer 和 provider 都异步通知监控中心 + +![dubbo-operating-principle](./images/dubbo-operating-principle.png) ### 注册中心挂了可以继续通信吗? -可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息**拉取到本地缓存**,所以注册中心挂了可以继续通信。 \ No newline at end of file + +可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息**拉取到本地缓存**,所以注册中心挂了可以继续通信。 diff --git a/docs/distributed-system/dubbo-rpc-design.md b/docs/distributed-system/dubbo-rpc-design.md index faf02e4..98926cd 100644 --- a/docs/distributed-system/dubbo-rpc-design.md +++ b/docs/distributed-system/dubbo-rpc-design.md @@ -2,21 +2,25 @@ 如何自己设计一个类似 Dubbo 的 RPC 框架? ## 面试官心理分析 + 说实话,就这问题,其实就跟问你如何自己设计一个 MQ 一样的道理,就考两个: -- 你有没有对某个 rpc 框架原理有非常深入的理解。 -- 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。 + +* 你有没有对某个 rpc 框架原理有非常深入的理解。 +* 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。 ## 面试题剖析 + 其实问到你这问题,你起码不能认怂,因为是知识的扫盲,那我不可能给你深入讲解什么 kafka 源码剖析,dubbo 源码剖析,何况我就算讲了,你要真的消化理解和吸收,起码个把月以后了。 所以我给大家一个建议,遇到这类问题,起码从你了解的类似框架的原理入手,自己说说参照 dubbo 的原理,你来设计一下,举个例子,dubbo 不是有那么多分层么?而且每个分层是干啥的,你大概是不是知道?那就按照这个思路大致说一下吧,起码你不能懵逼,要比那些上来就懵,啥也说不出来的人要好一些。 举个栗子,我给大家说个最简单的回答思路: -- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。 -- 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。 -- 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。 -- 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。 -- 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。 -- 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。 -这就是一个最最基本的 rpc 框架的思路,先不说你有多牛逼的技术功底,哪怕这个最简单的思路你先给出来行不行? \ No newline at end of file +* 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。 +* 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。 +* 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。 +* 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。 +* 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。 +* 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。 + +这就是一个最最基本的 rpc 框架的思路,先不说你有多牛逼的技术功底,哪怕这个最简单的思路你先给出来行不行? diff --git a/docs/distributed-system/dubbo-serialization-protocol.md b/docs/distributed-system/dubbo-serialization-protocol.md index 008dda5..ebf99a5 100644 --- a/docs/distributed-system/dubbo-serialization-protocol.md +++ b/docs/distributed-system/dubbo-serialization-protocol.md @@ -2,17 +2,20 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的? ## 面试官心理分析 + 上一个问题,说说 dubbo 的基本工作原理,那是你必须知道的,至少要知道 dubbo 分成哪些层,然后平时怎么发起 rpc 请求的,注册、发现、调用,这些是基本的。 接着就可以针对底层进行深入的问问了,比如第一步就可以先问问序列化协议这块,就是平时 RPC 的时候怎么走的? ## 面试题剖析 + **序列化**,就是把数据结构或者是一些对象,转换为二进制串的过程,而**反序列化**是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。 -![serialize-deserialize](/images/serialize-deserialize.png) +![serialize-deserialize](./images/serialize-deserialize.png) ### dubbo 支持不同的通信协议 -- dubbo 协议 + +* dubbo 协议 **默认**就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高。 @@ -20,54 +23,55 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 长连接,通俗点说,就是建立连接过后可以持续发送请求,无须再建立连接。 -![dubbo-keep-connection](/images/dubbo-keep-connection.png) +![dubbo-keep-connection](./images/dubbo-keep-connection.png) 而短连接,每次要发送请求之前,需要先重新建立一次连接。 -![dubbo-not-keep-connection](/images/dubbo-not-keep-connection.png) +![dubbo-not-keep-connection](./images/dubbo-not-keep-connection.png) -- 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` 倍;**第二**,它的数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。 \ No newline at end of file +其实 PB 之所以性能如此好,主要得益于两个:**第一**,它使用 proto 编译器,自动进行序列化和反序列化,速度非常快,应该比 `XML` 和 `JSON` 快上了 `20~100` 倍;**第二**,它的数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。 diff --git a/docs/distributed-system/dubbo-service-management.md b/docs/distributed-system/dubbo-service-management.md index 9a91c08..126e5bb 100644 --- a/docs/distributed-system/dubbo-service-management.md +++ b/docs/distributed-system/dubbo-service-management.md @@ -2,6 +2,7 @@ 如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试? ## 面试官心理分析 + 服务治理,这个问题如果问你,其实就是看看你有没有**服务治理**的思想,因为这个是做过复杂微服务的人肯定会遇到的一个问题。 **服务降级**,这个是涉及到复杂分布式系统中必备的一个话题,因为分布式系统互相来回调用,任何一个系统故障了,你不降级,直接就全盘崩溃?那就太坑爹了吧。 @@ -11,34 +12,40 @@ **超时重试**,跟上面一样,如果不小心网络慢一点,超时了,如何重试? ## 面试题剖析 + ### 服务治理 + #### 1. 调用链路自动生成 + 一个大型的分布式系统,或者说是用现在流行的微服务架构来说吧,**分布式系统由大量的服务组成**。那么这些服务之间互相是如何调用的?调用链路是啥?说实话,几乎到后面没人搞的清楚了,因为服务实在太多了,可能几百个甚至几千个服务。 那就需要基于 dubbo 做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将**各个服务之间的依赖关系和调用链路生成出来**,做成一张图,显示出来,大家才可以看到对吧。 -![dubbo-service-invoke-road](/images/dubbo-service-invoke-road.png) +![dubbo-service-invoke-road](./images/dubbo-service-invoke-road.png) #### 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 ``` @@ -106,5 +114,5 @@ public class HelloServiceMock implements HelloService { 可以结合你们公司具体的场景来说说你是怎么设置这些参数的: -- `timeout`:一般设置为 `200ms`,我们认为不能超过 `200ms` 还没返回。 -- `retries`:设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。 \ No newline at end of file +* `timeout` :一般设置为 `200ms` ,我们认为不能超过 `200ms` 还没返回。 +* `retries` :设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。 diff --git a/docs/distributed-system/dubbo-spi.md b/docs/distributed-system/dubbo-spi.md index bce20b0..b825e3b 100644 --- a/docs/distributed-system/dubbo-spi.md +++ b/docs/distributed-system/dubbo-spi.md @@ -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 ``` -provider 启动的时候,就会加载到我们 jar 包里的`my=com.bingo.MyProtocol` 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。 -![dubbo-spi](/images/dubbo-spi.png) +provider 启动的时候,就会加载到我们 jar 包里的 `my=com.bingo.MyProtocol` 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。 -dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类`。 +![dubbo-spi](./images/dubbo-spi.png) -然后对于对应的组件,类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。 \ No newline at end of file +dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类` 。 + +然后对于对应的组件,类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。 diff --git a/images/redis-redlock.png b/docs/distributed-system/images/redis-redlock.png similarity index 100% rename from images/redis-redlock.png rename to docs/distributed-system/images/redis-redlock.png diff --git a/docs/distributed-system/why-dubbo.md b/docs/distributed-system/why-dubbo.md index b911786..89e8ba6 100644 --- a/docs/distributed-system/why-dubbo.md +++ b/docs/distributed-system/why-dubbo.md @@ -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 就可以了。 \ No newline at end of file +所以 dubbo 说白了,是一种 rpc 框架,就是说本地就是进行接口调用,但是 dubbo 会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡、服务实例上下线自动感知、超时重试等等乱七八糟的问题。那你就不用自己做了,用 dubbo 就可以了。 diff --git a/docs/distributed-system/zookeeper-application-scenarios.md b/docs/distributed-system/zookeeper-application-scenarios.md index b07d2c9..37c58a5 100644 --- a/docs/distributed-system/zookeeper-application-scenarios.md +++ b/docs/distributed-system/zookeeper-application-scenarios.md @@ -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-distributed-coordination](/images/zookeeper-distributed-coordination.png) +![zookeeper-distributed-coordination](./images/zookeeper-distributed-coordination.png) ### 分布式锁 + 举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也**尝试去创建**那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。 -![zookeeper-distributed-lock-demo](/images/zookeeper-distributed-lock-demo.png) +![zookeeper-distributed-lock-demo](./images/zookeeper-distributed-lock-demo.png) ### 元数据/配置信息管理 + zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么? -![zookeeper-meta-data-manage](/images/zookeeper-meta-data-manage.png) +![zookeeper-meta-data-manage](./images/zookeeper-meta-data-manage.png) ### HA高可用性 + 这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。 -![zookeeper-active-standby](/images/zookeeper-active-standby.png) \ No newline at end of file +![zookeeper-active-standby](./images/zookeeper-active-standby.png) diff --git a/docs/extra-page/README.md b/docs/extra-page/README.md index a2de610..43faee5 100644 --- a/docs/extra-page/README.md +++ b/docs/extra-page/README.md @@ -1,16 +1,20 @@ # 项目额外页面 + ## 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 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 + +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 @@ -27,4 +31,4 @@ GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎
-
\ No newline at end of file + diff --git a/docs/extra-page/cover.md b/docs/extra-page/cover.md index 1377448..84d488a 100644 --- a/docs/extra-page/cover.md +++ b/docs/extra-page/cover.md @@ -1,9 +1,8 @@ -[![logo](images/icon.png)](https://github.com/doocs/advanced-java) +[![logo](./images/icon.png)](https://github.com/doocs/advanced-java) # 互联网 Java 工程师进阶知识完全扫盲 > 本系列知识由 Doocs 技术社区总结发布,内容涵盖高并发、分布式、高可用、微服务、海量数据处理等 -[社区首页](https://github.com/doocs/doocs.github.io) -[维护者](https://github.com/yanglbme) -[开始学习](#互联网-java-工程师进阶知识完全扫盲©) \ No newline at end of file +[社区首页](https://doocs.github.io) +[开始学习](#互联网-java-工程师进阶知识完全扫盲©) diff --git a/docs/from-readers/images/advanced-java-doocs-shishan.png b/docs/extra-page/images/advanced-java-doocs-shishan.png similarity index 100% rename from docs/from-readers/images/advanced-java-doocs-shishan.png rename to docs/extra-page/images/advanced-java-doocs-shishan.png diff --git a/docs/from-readers/images/icon.png b/docs/extra-page/images/icon.png similarity index 99% rename from docs/from-readers/images/icon.png rename to docs/extra-page/images/icon.png index 7070965..a39d36b 100644 Binary files a/docs/from-readers/images/icon.png and b/docs/extra-page/images/icon.png differ diff --git a/docs/extra-page/offer.md b/docs/extra-page/offer.md index 20deffc..32f2010 100644 --- a/docs/extra-page/offer.md +++ b/docs/extra-page/offer.md @@ -1,4 +1,4 @@ -[![where-is-my-offer](/images/where-is-my-offer.png)](https://doocs.github.io/advanced-java/#/offer) +[![where-is-my-offer](./images/where-is-my-offer.png)](https://doocs.github.io/advanced-java/#/offer)

@@ -122,4 +122,4 @@ HR ``` -[![get-up-and-study](/images/get-up-and-study.png)](https://doocs.github.io/advanced-java) \ No newline at end of file +[![get-up-and-study](./images/get-up-and-study.png)](https://doocs.github.io/advanced-java) \ No newline at end of file diff --git a/docs/from-readers/rights-defending-movement.md b/docs/extra-page/rights-defending-action.md similarity index 99% rename from docs/from-readers/rights-defending-movement.md rename to docs/extra-page/rights-defending-action.md index 8ecb974..a3cf72a 100644 --- a/docs/from-readers/rights-defending-movement.md +++ b/docs/extra-page/rights-defending-action.md @@ -3,17 +3,19 @@

## 声明 + 读者朋友们,你们好。[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高级架构解析 | 严重抄袭 | \ No newline at end of file +| 1 | [Java消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java高级架构解析 | 严重抄袭 | diff --git a/docs/extra-page/subscriptions-for-doocs.md b/docs/extra-page/subscriptions-for-doocs.md index 70e181c..5d5fc8b 100644 --- a/docs/extra-page/subscriptions-for-doocs.md +++ b/docs/extra-page/subscriptions-for-doocs.md @@ -1,46 +1,55 @@ # Doocs 开源社区的公众号来了 GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专注于挖掘 IT 技术知识,助力开发者成长。 -
- +
+
来成为公众号的首批粉丝吗?一定不会辜负你们的期待。 ## 为什么选择公众号? + 微信公众号的**原创保护功能**做得比较好,内容阅读也比较方便。 后续我的所有原创文章,将第一时间通过微信公众号“Doocs开源社区”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。 ## 公众号的定位是怎样的? -- **内容原创**:所有技术文章均原创发布,不随意从网上复制粘贴一些文章,确保质量。 -- **定期更新**:每周输出 1-2 篇文章,保证一定的更新频率。 -
- +* **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。 +* **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。 + +
+
## 目前都有哪些文章? + 因为刚刚推出公众号,目前有以下几篇文章,来先睹为快吧: -- [阿里又一个 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) +* [硬核!亿级流量秒杀系统设计](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、开发相关,可以自由分享一些开发相关知识,但不提倡整天水群,建议还是多花点时间提升自己。 -
- +
+
diff --git a/docs/from-readers/README.md b/docs/from-readers/README.md deleted file mode 100644 index f06356f..0000000 --- a/docs/from-readers/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# GitHub 开发者参与专区 -[Doocs/advanced-java](https://github.com/doocs/advanced-java) 欢迎各位开发朋友们分享自己或他人的实践经验与总结。如果你想参与,请参考[提交注意事项](/docs/from-readers/doocs-advanced-java-attention.md)。感谢 [@jerryldh](https://github.com/jerryldh), [@BigBlackSheep](https://github.com/BigBlackSheep), [@sunyuanpinggithub](https://github.com/sunyuanpinggithub) 等多位朋友的反馈,具体请参考 [#46](https://github.com/doocs/advanced-java/issues/46)。 - -## 文章 -- [示例文章](/docs/from-readers/doocs-advanced-java-attention.md) -- [示例文章](/docs/from-readers/doocs-advanced-java-attention.md) - -## 贡献者 -感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。 - - - - - - - ---- - -## 公众号 -GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 - - - - - - -
- -
- 公众平台 -

-
- -
- 个人微信 -

-
\ No newline at end of file diff --git a/docs/from-readers/doocs-advanced-java-attention.md b/docs/from-readers/doocs-advanced-java-attention.md deleted file mode 100644 index a2e4028..0000000 --- a/docs/from-readers/doocs-advanced-java-attention.md +++ /dev/null @@ -1,52 +0,0 @@ -# 提交注意事项 -项目需要有一个统一的内容提交规范,没有规范的项目将会是一团乱麻,维护起来也会很费劲儿。以下列出了几个小点,看似很多,实际上都非常容易做到,供朋友们参考。 - -> 如果你有好的 idea,欢迎 issues 交流。 - -## 关于提交形式 -本项目**不希望以外链的形式引入内容**。如果你有好的内容推荐,请在此项目基础上创建新的文件,完善内容后再提交。 - -## 关于文件命名与存放位置 -文件请以 “`GitHub ID` + 文章主题” 命名,确保每位朋友的提交内容不会冲突。文章主题统一采用**英文**命名,请勿使用中文或者汉语拼音,文件类型统一选择 `.md`。 - -给个示例。某位朋友的 GitHub ID 是 [SnailClimb](https://github.com/snailclimb),想分享一篇关于 Kafka 实践相关的文章,那么文件名可以是 `snailclimb-kafka-in-action.md`。 - -最终文件存放于 `docs/from-readers/` 目录下,即与[本文件](/docs/from-readers/doocs-advanced-java-attention.md)处于同一级别。**文件命名、存放位置不规范的文章将不予采纳**。 - -## 关于文章内容 -仅收录与此项目主题相关的优质文章,可以是[高并发](https://github.com/doocs/advanced-java#高并发架构)、[分布式](https://github.com/doocs/advanced-java#分布式系统)、[高可用](https://github.com/doocs/advanced-java#高可用架构)、[微服务](https://github.com/doocs/advanced-java#高并发架构微服务架构)等相关领域的内容。**其它主题的文章将不会被采纳**。 - -## 关于文章排版 -文章排版保持整洁美观。中英文之间、中文与数字之间用空格隔开是最基本的。 - -> 有研究显示,打字的时候不喜欢在中文和英文之间加空格的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟自己不爱的人结婚,而其余三成的人最后只能把遗产留给自己的猫。毕竟爱情跟书写都需要适时地留白。 - -图片统一使用 `![](/images/xxx.png)` 进行相对路径的引用,并同时存放于根目录 `images/` 和本专区目录 `docs/from-readers/images/` **两个位置**之下(这是为了确保在 GitHub 和 GitHub Page 都能正常显示图片;图片并不限定 `.png` 格式),作图推荐使用在线工具 [ProcessOn](https://www.processon.com/i/594a16f7e4b0e1bb14fe2fac)。具体文章书写规范请参考《[中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide)》。 - -以下是文章基本的结构,供朋友们参考。 - -```markdown -# 这是文章标题 -- Author: [GitHub ID](https://github.com/your-github-id) -- Description: 文章的简单描述信息。 -- ... - -## 这是一级索引 -... -### 这是二级索引 -... - -## 这是一级索引 -... -### 这是二级索引 -... -``` - -## 关于 Git 提交信息 -Git 提交信息统一使用英文,本项目遵从 [Angular JS Git 提交规范](https://github.com/angular/angular.js/commits/master)。e.g. - -```bash -git commit -m "docs(from-readers): add an article about Kafka" -``` - -Git 提交信息不规范的 PR 将不予合并。 \ No newline at end of file diff --git a/docs/from-readers/images/qrcode-for-doocs.jpg b/docs/from-readers/images/qrcode-for-doocs.jpg deleted file mode 100644 index bd1db5d..0000000 Binary files a/docs/from-readers/images/qrcode-for-doocs.jpg and /dev/null differ diff --git a/docs/from-readers/images/qrcode-for-yanglbme.jpg b/docs/from-readers/images/qrcode-for-yanglbme.jpg deleted file mode 100644 index 5bd385b..0000000 Binary files a/docs/from-readers/images/qrcode-for-yanglbme.jpg and /dev/null differ diff --git a/docs/high-availability/README.md b/docs/high-availability/README.md index d331c58..0c10a2d 100644 --- a/docs/high-availability/README.md +++ b/docs/high-availability/README.md @@ -28,7 +28,7 @@ --- ## 公众号 -GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 diff --git a/docs/high-availability/hystrix-timeout.md b/docs/high-availability/hystrix-timeout.md index 8911b7c..9fd356e 100644 --- a/docs/high-availability/hystrix-timeout.md +++ b/docs/high-availability/hystrix-timeout.md @@ -5,7 +5,7 @@ Peter Steiner 说过,"[On the Internet, nobody knows you're a dog](https://en.wikipedia.org/wiki/On_the_Internet,_nobody_knows_you%27re_a_dog)",也就是说在互联网的另外一头,你都不知道甚至坐着一条狗。 -![220px-Internet_dog.jpg](/images/220px-Internet_dog.jpg) +![220px-Internet_dog.jpg](./images/220px-Internet_dog.jpg) 像特别复杂的分布式系统,特别是在大公司里,多个团队、大型协作,你可能都不知道服务是谁的,很可能说开发服务的那个哥儿们甚至是一个实习生。依赖服务的接口性能可能很不稳定,有时候 2ms,有时候 200ms,甚至 2s,都有可能。 diff --git a/docs/high-availability/sentinel-vs-hystrix.md b/docs/high-availability/sentinel-vs-hystrix.md index e271f85..50a307f 100644 --- a/docs/high-availability/sentinel-vs-hystrix.md +++ b/docs/high-availability/sentinel-vs-hystrix.md @@ -63,10 +63,10 @@ Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自 - **直接拒绝模式**:即超出的请求直接拒绝。 - **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。 -![Slow-Start-Preheating-Mode](/images/Slow-Start-Preheating-Mode.jpg) +![Slow-Start-Preheating-Mode](./images/Slow-Start-Preheating-Mode.jpg) - **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。 -![Homogenizer-mode](/images/Homogenizer-mode.jpg) +![Homogenizer-mode](./images/Homogenizer-mode.jpg) 目前 Sentinel 对异步调用链路的支持还不是很好,后续版本会着重改善支持异步调用。 @@ -74,14 +74,14 @@ Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自 ### 3. 系统负载保护 Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。 -![BRP](/images/BRP.jpg) +![BRP](./images/BRP.jpg) ### 4. 实时监控和控制面板 Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。如果用户正在使用 Spring Boot/Spring Cloud 并使用了Sentinel Spring Cloud Starter,还可以方便地通过其暴露的 Actuator Endpoint 来获取运行时的一些信息,如动态规则等。未来 Sentinel 还会支持标准化的指标监控 API,可以方便地整合各种监控系统和可视化系统,如 Prometheus、Grafana 等。 Sentinel 控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。 -![Sentinel-Dashboard](/images/Sentinel-Dashboard.jpg) +![Sentinel-Dashboard](./images/Sentinel-Dashboard.jpg) ### 5. 生态 Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。未来 Sentinel 还会对更多常用框架进行适配,并且会为 Service Mesh 提供集群流量防护的能力。 diff --git a/docs/high-concurrency/README.md b/docs/high-concurrency/README.md index 8e49d60..20eb73e 100644 --- a/docs/high-concurrency/README.md +++ b/docs/high-concurrency/README.md @@ -1,47 +1,56 @@ # 高并发架构 + ## [消息队列](/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 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 + +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
@@ -58,4 +67,4 @@ GitHub 技术社区 Doocs 旗下唯一公众号“Doocs 开源社区”,欢迎
-
\ No newline at end of file + diff --git a/docs/high-concurrency/database-shard-dynamic-expand.md b/docs/high-concurrency/database-shard-dynamic-expand.md index 5376aec..67277dc 100644 --- a/docs/high-concurrency/database-shard-dynamic-expand.md +++ b/docs/high-concurrency/database-shard-dynamic-expand.md @@ -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,12 +52,11 @@ | 352 | 0 | 11 | | 4593 | 17 | 15 | - -刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 mysql 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 mysql 服务器之间做迁移就可以了。然后系统配合改一下配置即可。 +刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 MySQL 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 MySQL 服务器之间做迁移就可以了。然后系统配合改一下配置即可。 比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是 1024 个表。 -这么搞,是不用自己写代码做数据迁移的,都交给 dba 来搞好了,但是 dba 确实是需要做一些库表迁移的工作,但是总比你自己写代码,然后抽数据导数据来的效率高得多吧。 +这么搞,是不用自己写代码做数据迁移的,都交给 DBA 来搞好了,但是 DBA 确实是需要做一些库表迁移的工作,但是总比你自己写代码,然后抽数据导数据来的效率高得多吧。 哪怕是要减少库的数量,也很简单,其实说白了就是按倍数缩容就可以了,然后修改一下路由规则。 @@ -62,7 +64,7 @@ 1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了。 2. 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表 -3. 扩容的时候,申请增加更多的数据库服务器,装好 mysql,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。 -4. 由 dba 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。 +3. 扩容的时候,申请增加更多的数据库服务器,装好 MySQL,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。 +4. 由 DBA 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。 5. 我们这边就是修改一下配置,调整迁移的库所在数据库服务器的地址。 -6. 重新发布系统,上线,原先的路由规则变都不用变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务。 \ No newline at end of file +6. 重新发布系统,上线,原先的路由规则变都不用变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务。 diff --git a/docs/high-concurrency/database-shard-global-id-generate.md b/docs/high-concurrency/database-shard-global-id-generate.md index ea25594..d8ef356 100644 --- a/docs/high-concurrency/database-shard-global-id-generate.md +++ b/docs/high-concurrency/database-shard-global-id-generate.md @@ -2,11 +2,15 @@ 分库分表之后,id 主键如何处理? ## 面试官心理分析 + 其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个**全局唯一**的 id 来支持。所以这都是你实际生产环境中必须考虑的问题。 ## 面试题剖析 + ### 基于数据库的实现方案 + #### 数据库自增 id + 这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。 这个方案的好处就是方便简单,谁都会用;**缺点就是单库生成**自增 id,要是高并发的话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前 id 最大值,然后自己递增几个 id,一次性返回一批 id,然后再把当前最大 id 值修改成递增几个 id 之后的一个值;但是**无论如何都是基于单个数据库**。 @@ -14,41 +18,45 @@ **适合的场景**:你分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非是你**并发不高,但是数据量太大**导致的分库分表扩容,你可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。 #### 设置数据库 sequence 或者表自增字段步长 + 可以通过设置数据库 sequence 或者表的自增字段步长来进行水平伸缩。 比如说,现在有 8 个服务节点,每个服务节点使用一个 sequence 功能来产生 ID,每个 sequence 的起始 ID 不同,并且依次递增,步长都是 8。 -![database-id-sequence-step](/images/database-id-sequence-step.png) +![database-id-sequence-step](./images/database-id-sequence-step.png) **适合的场景**:在用户防止产生的 ID 重复时,这种方案实现起来比较简单,也能达到性能目标。但是服务节点固定,步长也固定,将来如果还要增加服务节点,就不好搞了。 ### UUID + 好处就是本地生成,不要基于数据库来了;不好之处就是,UUID 太长了、占用空间大,**作为主键性能太差**了;更重要的是,UUID 不具有有序性,会导致 B+ 树索引在写的时候有过多的随机写操作(连续的 ID 可以产生部分顺序写),还有,由于在写的时候不能产生有顺序的 append 操作,而需要进行 insert 操作,将会读取整个 B+ 树节点到内存,在插入这条记录后会将整个节点写回磁盘,这种操作在记录占用空间比较大的情况下,性能下降明显。 适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的。 -```java -UUID.randomUUID().toString().replace(“-”, “”) -> sfsdf23423rr234sfdaf +``` 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。 +snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的,用其中的 41 bits 作为毫秒数,用 10 bits 作为工作机器 id,12 bits 作为序列号。 -``` +* 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。 +* 41 bits:表示的是时间戳,单位是毫秒。41 bits 可以表示的数字多达 `2^41 - 1` ,也就是可以标识 `2^41 - 1` 个毫秒值,换算成年就是表示69年的时间。 +* 10 bits:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。但是 10 bits 里 5 个 bits 代表机房 id,5 个 bits 代表机器 id。意思就是最多代表 `2^5` 个机房(32 个机房),每个机房里可以代表 `2^5` 个机器(32台机器)。 +* 12 bits:这个是用来记录同一个毫秒内产生的不同 id,12 bits 可以代表的最大正整数是 `2^12 - 1 = 4096` ,也就是说可以用这个 12 bits 代表的数字来区分**同一个毫秒内**的 4096 个不同的 id。 + +``` 0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000 ``` -```java +``` java public class IdWorker { private long workerId; @@ -168,4 +176,4 @@ public class IdWorker { 利用这个 snowflake 算法,你可以开发自己公司的服务,甚至对于机房 id 和机器 id,反正给你预留了 5 bit + 5 bit,你换成别的有业务含义的东西也可以的。 -这个 snowflake 算法相对来说还是比较靠谱的,所以你要真是搞分布式 id 生成,如果是高并发啥的,那么用这个应该性能比较好,一般每秒几万并发的场景,也足够你用了。 \ No newline at end of file +这个 snowflake 算法相对来说还是比较靠谱的,所以你要真是搞分布式 id 生成,如果是高并发啥的,那么用这个应该性能比较好,一般每秒几万并发的场景,也足够你用了。 diff --git a/docs/high-concurrency/database-shard-method.md b/docs/high-concurrency/database-shard-method.md index 590dbec..5264209 100644 --- a/docs/high-concurrency/database-shard-method.md +++ b/docs/high-concurrency/database-shard-method.md @@ -2,14 +2,17 @@ 现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上? ## 面试官心理分析 + 你看看,你现在已经明白为啥要分库分表了,你也知道常用的分库分表中间件了,你也设计好你们如何分库分表的方案了(水平拆分、垂直拆分、分表),那问题来了,你接下来该怎么把你那个单库单表的系统给迁移到分库分表上去? 所以这都是一环扣一环的,就是看你有没有全流程经历过这个过程。 ## 面试题剖析 + 这个其实从 low 到高大上有好几种方案,我们都玩儿过,我都给你说一下。 ### 停机迁移方案 + 我先给你说一个最 low 的方案,就是很简单,大家伙儿凌晨 12 点开始运维,网站或者 app 挂个公告,说 0 点到早上 6 点进行运维,无法访问。 接着到 0 点停机,系统停掉,没有流量写入了,此时老的单库单表数据库静止了。然后你之前得写好一个**导数的一次性工具**,此时直接跑起来,然后将单库单表的数据哗哗哗读出来,写到分库分表里面去。 @@ -20,9 +23,10 @@ 但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。 -![database-shard-method-1](/images/database-shard-method-1.png) +![database-shard-method-1](./images/database-shard-method-1.png) ### 双写迁移方案 + 这个是我们常用的一种迁移方案,比较靠谱一些,不用停机,不用看北京凌晨 4 点的风景。 简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,**除了对老库增删改,都加上对新库的增删改**,这就是所谓的**双写**,同时写俩库,老库和新库。 @@ -33,4 +37,4 @@ 接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干的。 -![database-shard-method-2](/images/database-shard-method-2.png) \ No newline at end of file +![database-shard-method-2](./images/database-shard-method-2.png) diff --git a/docs/high-concurrency/database-shard.md b/docs/high-concurrency/database-shard.md index 4e08d36..eaaaa3a 100644 --- a/docs/high-concurrency/database-shard.md +++ b/docs/high-concurrency/database-shard.md @@ -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://github.com/apache/incubator-shardingsphere)(后文所提到的 `Sharding-jdbc`,等同于 `ShardingSphere`)。确实之前用的还比较多一些,因为 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 的依赖; @@ -80,11 +88,11 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套 **水平拆分**的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。 -![database-split-horizon](/images/database-split-horizon.png) +![database-split-horizon](./images/database-split-horizon.png) **垂直拆分**的意思,就是**把一个有很多字段的表给拆分成多个表**,**或者是多个库上去**。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会**将较少的访问频率很高的字段放到一个表里去**,然后**将较多的访问频率很低的字段放到另外一个表里去**。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。 -![database-split-vertically](/images/database-split-vertically.png) +![database-split-vertically](./images/database-split-vertically.png) 这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。 @@ -94,11 +102,10 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套 你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都 ok 了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。 - 而且这儿还有两种**分库分表的方式**: -- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。 -- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。 +* 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。 +* 或者是按照某个字段 hash 一下均匀分散,这个较为常用。 range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。 diff --git a/docs/high-concurrency/es-architecture.md b/docs/high-concurrency/es-architecture.md index e92634e..6542c2a 100644 --- a/docs/high-concurrency/es-architecture.md +++ b/docs/high-concurrency/es-architecture.md @@ -1,21 +1,23 @@ ## 面试题 -es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)? +ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)? ## 面试官心理分析 -在搜索这块,lucene 是最流行的搜索库。几年前业内一般都问,你了解 lucene 吗?你知道倒排索引的原理吗?现在早已经 out 了,因为现在很多项目都是直接用基于 lucene 的分布式搜索引擎—— ElasticSearch,简称为 es。 -而现在分布式搜索基本已经成为大部分互联网行业的 Java 系统的标配,其中尤为流行的就是 es,前几年 es 没火的时候,大家一般用 solr。但是这两年基本大部分企业和项目都开始转向 es 了。 +在搜索这块,lucene 是最流行的搜索库。几年前业内一般都问,你了解 lucene 吗?你知道倒排索引的原理吗?现在早已经 out 了,因为现在很多项目都是直接用基于 lucene 的分布式搜索引擎—— ElasticSearch,简称为 ES。 -所以互联网面试,肯定会跟你聊聊分布式搜索引擎,也就一定会聊聊 es,如果你确实不知道,那你真的就 out 了。 +而现在分布式搜索基本已经成为大部分互联网行业的 Java 系统的标配,其中尤为流行的就是 ES,前几年 ES 没火的时候,大家一般用 solr。但是这两年基本大部分企业和项目都开始转向 ES 了。 -如果面试官问你第一个问题,确实一般都会问你 es 的分布式架构设计能介绍一下么?就看看你对分布式搜索引擎架构的一个基本理解。 +所以互联网面试,肯定会跟你聊聊分布式搜索引擎,也就一定会聊聊 ES,如果你确实不知道,那你真的就 out 了。 + +如果面试官问你第一个问题,确实一般都会问你 ES 的分布式架构设计能介绍一下么?就看看你对分布式搜索引擎架构的一个基本理解。 ## 面试题剖析 -ElasticSearch 设计的理念就是分布式搜索引擎,底层其实还是基于 lucene 的。核心思想就是在多台机器上启动多个 es 进程实例,组成了一个 es 集群。 -es 中存储数据的**基本单位是索引**,比如说你现在要在 es 中存储一些订单数据,你就应该在 es 中创建一个索引 `order_idx`,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。 +ElasticSearch 设计的理念就是分布式搜索引擎,底层其实还是基于 lucene 的。核心思想就是在多台机器上启动多个 ES 进程实例,组成了一个 ES 集群。 -``` +ES 中存储数据的**基本单位是索引**,比如说你现在要在 ES 中存储一些订单数据,你就应该在 ES 中创建一个索引 `order_idx` ,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。 + +``` index -> type -> mapping -> document -> field。 ``` @@ -25,22 +27,22 @@ 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 中的一个字段的值。 -![es-index-type-mapping-document-field](/images/es-index-type-mapping-document-field.png) +![es-index-type-mapping-document-field](./images/es-index-type-mapping-document-field.png) -你搞一个索引,这个索引可以拆分成多个 `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` 上去。 -![es-cluster](/images/es-cluster.png) +![es-cluster](./images/es-cluster.png) 通过这个 replica 的方案,每个 shard 的数据都有多个备份,如果某个机器宕机了,没关系啊,还有别的数据副本在别的机器上呢。高可用了吧。 -es 集群多个节点,会自动选举一个节点为 master 节点,这个 master 节点其实就是干一些管理的工作的,比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了,那么会重新选举一个节点为 master 节点。 +ES 集群多个节点,会自动选举一个节点为 master 节点,这个 master 节点其实就是干一些管理的工作的,比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了,那么会重新选举一个节点为 master 节点。 如果是非 master节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器,重启了之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。 说得更简单一点,就是说如果某个非 master 节点宕机了。那么此节点上的 primary shard 不就没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。 -其实上述就是 ElasticSearch 作为分布式搜索引擎最基本的一个架构设计。 \ No newline at end of file +其实上述就是 ElasticSearch 作为分布式搜索引擎最基本的一个架构设计。 diff --git a/docs/high-concurrency/es-introduction.md b/docs/high-concurrency/es-introduction.md index c255a82..d9d42a8 100644 --- a/docs/high-concurrency/es-introduction.md +++ b/docs/high-concurrency/es-introduction.md @@ -1,29 +1,33 @@ -## lucene 和 es 的前世今生 -lucene 是最先进、功能最强大的搜索库。如果直接基于 lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。 +## Lucene 和 ES 的前世今生 +Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。 -elasticsearch 基于 lucene,隐藏了 lucene 的复杂性,提供了简单易用的 restful api / Java api 接口(另外还有其他语言的 api 接口)。 +ElasticSearch 基于 Lucene,隐藏了 lucene 的复杂性,提供了简单易用的 RESTful api / Java api 接口(另外还有其他语言的 api 接口)。 -- 分布式的文档存储引擎 -- 分布式的搜索引擎和分析引擎 -- 分布式,支持 PB 级数据 +* 分布式的文档存储引擎 +* 分布式的搜索引擎和分析引擎 +* 分布式,支持 PB 级数据 + +## ES 的核心概念 -## 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 +文档是 ES 中最小的数据单元,一个 document 可以是一条客户数据、一条商品分类数据、一条订单数据,通常用 json 数据结构来表示。每个 index 下的 type,都可以存储多条 document。一个 document 里面有多个 field,每个 field 就是一个数据字段。 + +``` json { "product_id": "1", "product_name": "iPhone X", @@ -34,26 +38,31 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分 ``` ### Index + 索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 ducument。 ### Type + 类型,每个索引里可以有一个或者多个 type,type 是 index 的一个逻辑分类,比如商品 index 下有多个 type:日化商品 type、电器商品 type、生鲜商品 type。每个 type 下的 document 的 field 可能不太一样。 ### shard -单台机器无法存储大量数据,es 可以将一个索引中的数据切分为多个 shard,分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个 shard 都是一个 lucene index。 + +单台机器无法存储大量数据,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。 -![es-cluster-0](/images/es-cluster-0.png) +![es-cluster-0](./images/es-cluster-0.png) -## es 核心概念 vs. db 核心概念 -| es | db | +## ES 核心概念 vs. DB 核心概念 + +| ES | DB | |---|---| | index | 数据库 | | type | 数据表 | | docuemnt | 一行数据 | -以上是一个简单的类比。 \ No newline at end of file +以上是一个简单的类比。 diff --git a/docs/high-concurrency/es-optimizing-query-performance.md b/docs/high-concurrency/es-optimizing-query-performance.md index e8b7081..91ec7a3 100644 --- a/docs/high-concurrency/es-optimizing-query-performance.md +++ b/docs/high-concurrency/es-optimizing-query-performance.md @@ -1,36 +1,40 @@ ## 面试题 -es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊? +ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊? ## 面试官心理分析 -这个问题是肯定要问的,说白了,就是看你有没有实际干过 es,因为啥?其实 es 性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条数据的时候,可能你会懵逼的发现,跑个搜索怎么一下 `5~10s`,坑爹了。第一次搜索的时候,是 `5~10s`,后面反而就快了,可能就几百毫秒。 + +这个问题是肯定要问的,说白了,就是看你有没有实际干过 es,因为啥?其实 es 性能并没有你想象中那么好的。很多时候数据量大了,特别是有几亿条数据的时候,可能你会懵逼的发现,跑个搜索怎么一下 `5~10s` ,坑爹了。第一次搜索的时候,是 `5~10s` ,后面反而就快了,可能就几百毫秒。 你就很懵,每个用户第一次访问都会比较慢,比较卡么?所以你要是没玩儿过 es,或者就是自己玩玩儿 demo,被问到这个问题容易懵逼,显示出你对 es 确实玩儿的不怎么样? ## 面试题剖析 + 说实话,es 性能优化是没有什么银弹的,啥意思呢?就是**不要期待着随手调一个参数,就可以万能的应对所有的性能慢的场景**。也许有的场景是你换个参数,或者调整一下语法,就可以搞定,但是绝对不是所有场景都可以这样。 ### 性能优化的杀手锏——filesystem cache + 你往 es 里写的数据,实际上都写到磁盘文件里去了,**查询的时候**,操作系统会将磁盘文件里的数据自动缓存到 `filesystem cache` 里面去。 -![es-search-process](/images/es-search-process.png) +![es-search-process](./images/es-search-process.png) -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 字段。 \ No newline at end of file +除了用 `scroll api` ,你也可以用 `search_after` 来做, `search_after` 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 sort 字段。 diff --git a/docs/high-concurrency/es-production-cluster.md b/docs/high-concurrency/es-production-cluster.md index 18d2bb7..ea64087 100644 --- a/docs/high-concurrency/es-production-cluster.md +++ b/docs/high-concurrency/es-production-cluster.md @@ -1,7 +1,8 @@ ## 面试题 -es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片? +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。 -大概就这么说一下就行了。 \ No newline at end of file +大概就这么说一下就行了。 diff --git a/docs/high-concurrency/es-write-query-search.md b/docs/high-concurrency/es-write-query-search.md index 360e5ae..90a06e5 100644 --- a/docs/high-concurrency/es-write-query-search.md +++ b/docs/high-concurrency/es-write-query-search.md @@ -1,66 +1,72 @@ ## 面试题 -es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗? +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` 都搞定之后,就返回响应结果给客户端。 -![es-write](/images/es-write.png) +### es 写数据过程 + +* 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node` (协调节点)。 +* `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。 +* 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node` 。 +* `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 + +![es-write](./images/es-write.png) ### 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 读取,采用的是随机轮询算法。 ### 写数据底层原理 -![es-write-detail](/images/es-write-detail.png) +![es-write-detail](./images/es-write-detail.png) 先写入内存 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` ,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。 要注意倒排索引的两个重要细节: -- 倒排索引中的所有词项对应一个或多个文档; -- 倒排索引中的词项**根据字典顺序升序排列** +* 倒排索引中的所有词项对应一个或多个文档; +* 倒排索引中的词项**根据字典顺序升序排列** -> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。 \ No newline at end of file +> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。 diff --git a/docs/high-concurrency/high-concurrency-design.md b/docs/high-concurrency/high-concurrency-design.md index 05f0901..3e940e2 100644 --- a/docs/high-concurrency/high-concurrency-design.md +++ b/docs/high-concurrency/high-concurrency-design.md @@ -2,6 +2,7 @@ 如何设计一个高并发系统? ## 面试官心理分析 + 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先。 如果你确实有真才实学,在互联网公司里干过高并发系统,那你确实拿 offer 基本如探囊取物,没啥问题。面试官也绝对不会这样来问你,否则他就是蠢。 @@ -16,8 +17,8 @@ 所以这个时候你必须得做一把个人秀了,秀出你所有关于高并发的知识! - ## 面试题剖析 + 其实所谓的高并发,如果你要理解这个问题呢,其实就得从高并发的根源出发,为啥会有高并发?为啥高并发就很牛逼? 我说的浅显一点,很简单,就是因为刚开始系统都是连接数据库的,但是要知道数据库支撑到每秒并发两三千的时候,基本就快完了。所以才有说,很多公司,刚开始干的时候,技术比较 low,结果业务发展太快,有的时候系统扛不住压力就挂了。 @@ -30,36 +31,41 @@ 可以分为以下 6 点: -- 系统拆分 -- 缓存 -- MQ -- 分库分表 -- 读写分离 -- ElasticSearch +* 系统拆分 +* 缓存 +* MQ +* 分库分表 +* 读写分离 +* ElasticSearch -![high-concurrency-system-design](/images/high-concurrency-system-design.png) +![high-concurrency-system-design](./images/high-concurrency-system-design.png) ### 系统拆分 + 将一个系统拆分为多个子系统,用 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,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实践过高并发架构的人,这个经验是难能可贵的。 \ No newline at end of file +其实大部分公司,真正看重的,不是说你掌握高并发相关的一些基本的架构知识,架构中的一些技术,RocketMQ、Kafka、Redis、Elasticsearch,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实践过高并发架构的人,这个经验是难能可贵的。 diff --git a/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md b/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md index cf9ae13..4ac2e27 100644 --- a/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md +++ b/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md @@ -2,27 +2,32 @@ 如何保证消息队列的高可用? ## 面试官心理分析 + 如果有人问到你 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 所在实例上拉取数据过来。 -![mq-7](/images/mq-7.png) +![mq-7](./images/mq-7.png) 这种方式确实很麻烦,也不怎么好,**没做到所谓的分布式**,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有**数据拉取的开销**,后者导致**单实例性能瓶颈**。 @@ -31,30 +36,32 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 所以这个事儿就比较尴尬了,这就**没有什么所谓的高可用性**,**这方案主要是提高吞吐量的**,就是说让集群中多个节点来服务某个 queue 的读写操作。 #### 镜像集群模式(高可用性) + 这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。 -![mq-8](/images/mq-8.png) +![mq-8](./images/mq-8.png) 那么**如何开启这个镜像集群模式**呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是**镜像集群模式的策略**,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。 这样的话,好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,第一,这个性能开销也太大了吧,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!第二,这么玩儿,不是分布式的,就**没有扩展性可言**了,如果某个 queue 负载很重,你加机器,新增的机器也包含了这个 queue 的所有数据,并**没有办法线性扩展**你的 queue。你想,如果这个 queue 的数据量很大,大到这个机器上的容量无法容纳了,此时该怎么办呢? ### Kafka 的高可用性 + Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker 是一个节点;你创建一个 topic,这个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据。 这就是**天然的分布式消息队列**,就是说一个 topic 的数据,是**分散放在多个机器上的,每个机器就放一部分数据**。 -实际上 RabbmitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。 +实际上 RabbitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。 Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了,那个 broker 上的 partition 就废了,没法写也没法读,没有什么高可用性可言。 比如说,我们假设创建了一个 topic,指定其 partition 数量是 3 个,分别在三台机器上。但是,如果第二台机器宕机了,会导致这个 topic 的 1/3 的数据就丢了,因此这个是做不到高可用的。 -![kafka-before](/images/kafka-before.png) +![kafka-before](./images/kafka-before.png) Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。只能读写 leader?很简单,**要是你可以随意读写每个 follower,那么就要 care 数据一致性的问题**,系统复杂度太高,很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。 -![kafka-after](/images/kafka-after.png) +![kafka-after](./images/kafka-after.png) 这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 @@ -62,4 +69,4 @@ Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机 **消费**的时候,只会从 leader 去读,但是只有当一个消息已经被所有 follower 都同步成功返回 ack 的时候,这个消息才会被消费者读到。 -看到这里,相信你大致明白了 Kafka 是如何保证高可用机制的了,对吧?不至于一无所知,现场还能给面试官画画图。要是遇上面试官确实是 Kafka 高手,深挖了问,那你只能说不好意思,太深入的你没研究过。 \ No newline at end of file +看到这里,相信你大致明白了 Kafka 是如何保证高可用机制的了,对吧?不至于一无所知,现场还能给面试官画画图。要是遇上面试官确实是 Kafka 高手,深挖了问,那你只能说不好意思,太深入的你没研究过。 diff --git a/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md b/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md index 70c952a..8fefa0c 100644 --- a/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md +++ b/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md @@ -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 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。 \ No newline at end of file +redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。 diff --git a/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md b/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md index 33dbaf6..db42f80 100644 --- a/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md +++ b/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md @@ -2,9 +2,11 @@ 如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性? ## 面试官心理分析 + 其实这是很常见的一个问题,这俩问题基本可以连起来问。既然是消费消息,那肯定要考虑会不会重复消费?能不能避免重复消费?或者重复消费了也别造成系统异常可以吗?这个是 MQ 领域的基本问题,其实本质上还是问你**使用消息队列如何保证幂等性**,这个是你架构里要考虑的一个问题。 ## 面试题剖析 + 回答这个问题,首先你别听到重复消息这个事儿,就一无所知吧,你**先大概说一说可能会有哪些重复消费的问题**。 首先,比如 RabbitMQ、RocketMQ、Kafka,都有可能会出现消息重复消费的问题,正常。因为这问题通常不是 MQ 自己保证的,是由我们开发来保证的。挑一个 Kafka 来举个例子,说说怎么重复消费吧。 @@ -17,7 +19,7 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一 有这么个场景。数据 1/2/3 依次进入 kafka,kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,我们就假设分配的 offset 依次是 152/153/154。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功,那么数据 1/2 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。 -![mq-10](/images/mq-10.png) +![mq-10](./images/mq-10.png) 如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说,你可能就把数据 1/2 在数据库里插入了 2 次,那么数据就错啦。 @@ -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-11](/images/mq-11.png) +![mq-11](./images/mq-11.png) -当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。 \ No newline at end of file +当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。 diff --git a/docs/high-concurrency/how-to-ensure-the-order-of-messages.md b/docs/high-concurrency/how-to-ensure-the-order-of-messages.md index f461ba5..6984757 100644 --- a/docs/high-concurrency/how-to-ensure-the-order-of-messages.md +++ b/docs/high-concurrency/how-to-ensure-the-order-of-messages.md @@ -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-order-01](/images/rabbitmq-order-01.png) +* **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。 -- **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。 +![rabbitmq-order-01](./images/rabbitmq-order-01.png) -![kafka-order-01](/images/kafka-order-01.png) +* **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。 + +![kafka-order-01](./images/kafka-order-01.png) ### 解决方案 + #### RabbitMQ 拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。 -![rabbitmq-order-02](/images/rabbitmq-order-02.png) + +![rabbitmq-order-02](./images/rabbitmq-order-02.png) #### Kafka -- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。 -- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。 -![kafka-order-02](/images/kafka-order-02.png) +* 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。 +* 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。 + +![kafka-order-02](./images/kafka-order-02.png) diff --git a/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md b/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md index 5a49254..de901f9 100644 --- a/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md +++ b/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md @@ -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-message-lose](/images/rabbitmq-message-lose.png) + +![rabbitmq-message-lose](./images/rabbitmq-message-lose.png) #### 生产者弄丢了数据 生产者将数据发送到 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,27 +45,32 @@ channel.txCommit 所以一般在生产者这块**避免数据丢失**,都是用 `confirm` 机制的。 #### RabbitMQ 弄丢了数据 + 就是 RabbitMQ 自己弄丢了数据,这个你必须**开启 RabbitMQ 的持久化**,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,**恢复之后会自动读取之前存储的数据**,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,**可能导致少量数据丢失**,但是这个概率较小。 设置持久化有**两个步骤**: -- 创建 queue 的时候将其设置为持久化
+* 创建 queue 的时候将其设置为持久化
+ 这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。 -- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2
+ +* 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2
+ 就是将消息设置为持久化的,此时 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 去处理,消息是不会丢的。 -![rabbitmq-message-lose-solution](/images/rabbitmq-message-lose-solution.png) +![rabbitmq-message-lose-solution](./images/rabbitmq-message-lose-solution.png) ### Kafka @@ -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 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。 diff --git a/docs/high-concurrency/huifer-how-to-limit-current.md b/docs/high-concurrency/huifer-how-to-limit-current.md index 88c9b90..cffc1b0 100644 --- a/docs/high-concurrency/huifer-how-to-limit-current.md +++ b/docs/high-concurrency/huifer-how-to-limit-current.md @@ -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 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 org.springframework.cloud spring-cloud-starter-gateway @@ -283,42 +288,52 @@ public class TokenBucket { ``` -```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 com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` -```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会有更加好的体验. \ No newline at end of file + +> sentinel和spring cloud gateway两个框架都是很好的限流框架, 但是在我使用中还没有将[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)接入到项目中进行使用, 所以我会选择**spring cloud gateway**, 当接入完整的或者接入Nacos项目使用setinel会有更加好的体验. diff --git a/docs/high-concurrency/mq-design.md b/docs/high-concurrency/mq-design.md index 979fdf6..dbb7467 100644 --- a/docs/high-concurrency/mq-design.md +++ b/docs/high-concurrency/mq-design.md @@ -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 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。 diff --git a/docs/high-concurrency/mq-interview.md b/docs/high-concurrency/mq-interview.md index 4ec170c..25abde6 100644 --- a/docs/high-concurrency/mq-interview.md +++ b/docs/high-concurrency/mq-interview.md @@ -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` 等等东西,**由浅入深,一步步深挖**。 -其实上面是一个非常典型的关于消息队列的技术考察过程,好的面试官一定是从你做过的某一个点切入,然后层层展开深入考察,一个接一个问,直到把这个技术点刨根问底,问到最底层。 \ No newline at end of file +其实上面是一个非常典型的关于消息队列的技术考察过程,好的面试官一定是从你做过的某一个点切入,然后层层展开深入考察,一个接一个问,直到把这个技术点刨根问底,问到最底层。 diff --git a/docs/high-concurrency/mq-time-delay-and-expired-failure.md b/docs/high-concurrency/mq-time-delay-and-expired-failure.md index d69b7d0..7561864 100644 --- a/docs/high-concurrency/mq-time-delay-and-expired-failure.md +++ b/docs/high-concurrency/mq-time-delay-and-expired-failure.md @@ -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 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。 diff --git a/docs/high-concurrency/mysql-read-write-separation.md b/docs/high-concurrency/mysql-read-write-separation.md index c63ef4f..a1358bd 100644 --- a/docs/high-concurrency/mysql-read-write-separation.md +++ b/docs/high-concurrency/mysql-read-write-separation.md @@ -2,16 +2,19 @@ 你们有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决 MySQL 主从同步的延时问题? ## 面试官心理分析 + 高并发这个阶段,肯定是需要做读写分离的,啥意思?因为实际上大部分的互联网公司,一些网站,或者是 app,其实都是读多写少。所以针对这个情况,就是写一个主库,但是主库挂多个从库,然后从多个从库来读,那不就可以支撑更高的读并发压力了吗? ## 面试题剖析 + ### 如何实现 MySQL 的读写分离? 其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。 ### MySQL 主从复制原理的是啥? + 主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。 -![mysql-master-slave](/images/mysql-master-slave.png) +![mysql-master-slave](./images/mysql-master-slave.png) 这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行 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,并行复制还是没意义。 +* 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。 +* 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。 diff --git a/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md b/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md index 9646bff..30c6a1f 100644 --- a/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md +++ b/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md @@ -1,50 +1,57 @@ ## 面试题 -了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透? +了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透? ## 面试官心理分析 + 其实这是问到缓存必问的,因为缓存雪崩和穿透,是缓存最大的两个问题,要么不出现,一旦出现就是致命性的问题,所以面试官一定会问你。 ## 面试题剖析 + ### 缓存雪崩 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 这就是缓存雪崩。 -![redis-caching-avalanche](/images/redis-caching-avalanche.png) +![redis-caching-avalanche](./images/redis-caching-avalanche.png) 大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。 缓存雪崩的事前事中事后的解决方案如下: -- 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。 -- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 -- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 -![redis-caching-avalanche-solution](/images/redis-caching-avalanche-solution.png) +* 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。 +* 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 +* 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 -用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。 +![redis-caching-avalanche-solution](./images/redis-caching-avalanche-solution.png) -限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?**走降级**!可以返回一些默认的值,或者友情提示,或者空白的值。 +用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 Redis。如果 ehcache 和 Redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 Redis 中。 + +限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?**走降级**!可以返回一些默认的值,或者友情提示,或者空值。 好处: -- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 -- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 -- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。 + +* 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 +* 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 +* 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。 ### 缓存穿透 + 对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。 黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。 举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“**视缓存于无物**”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。 -![redis-caching-penetration](/images/redis-caching-penetration.png) +![redis-caching-penetration](./images/redis-caching-penetration.png) -解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 +解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN` 。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 ### 缓存击穿 + 缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。 不同场景下的解决方式可如下: -- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。 -- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。 -- 若缓存的数据更新频繁或者缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动的重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。 + +* 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。 +* 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。 +* 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。 diff --git a/docs/high-concurrency/redis-cas.md b/docs/high-concurrency/redis-cas.md index d9e59ac..2d96ab7 100644 --- a/docs/high-concurrency/redis-cas.md +++ b/docs/high-concurrency/redis-cas.md @@ -1,16 +1,18 @@ ## 面试题 -redis 的并发竞争问题是什么?如何解决这个问题?了解 redis 事务的 CAS 方案吗? +Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗? ## 面试官心理分析 + 这个也是线上非常常见的一个问题,就是**多客户端同时并发写**一个 key,可能本来应该先到的数据后到了,导致数据版本错了;或者是多客户端同时获取一个 key,修改值之后再写回去,只要顺序错了,数据就错了。 -而且 redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。 +而且 Redis 自己就有天然解决这个问题的 CAS 类的乐观锁方案。 ## 面试题剖析 + 某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。 -![zookeeper-distributed-lock](/images/zookeeper-distributed-lock.png) +![zookeeper-distributed-lock](./images/zookeeper-distributed-lock.png) 你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。 -每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。 \ No newline at end of file +每次要**写之前,先判断**一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。 diff --git a/docs/high-concurrency/redis-cluster.md b/docs/high-concurrency/redis-cluster.md index deb5f9c..54403a5 100644 --- a/docs/high-concurrency/redis-cluster.md +++ b/docs/high-concurrency/redis-cluster.md @@ -1,79 +1,88 @@ ## 面试题 -redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗? +Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗? ## 面试官心理分析 -在前几年,redis 如果要搞几个节点,每个节点存储一部分的数据,得**借助一些中间件**来实现,比如说有 `codis`,或者 `twemproxy`,都有。有一些 redis 中间件,你读写 redis 中间件,redis 中间件负责将你的数据分布式存储在多台机器上的 redis 实例中。 -这两年,redis 不断在发展,redis 也不断有新的版本,现在的 redis 集群模式,可以做到在多台机器上,部署多个 redis 实例,每个实例存储一部分的数据,同时每个 redis 主实例可以挂 redis 从实例,自动确保说,如果 redis 主实例挂了,会自动切换到 redis 从实例上来。 +在前几年,Redis 如果要搞几个节点,每个节点存储一部分的数据,得**借助一些中间件**来实现,比如说有 `codis` ,或者 `twemproxy` ,都有。有一些 Redis 中间件,你读写 Redis 中间件,Redis 中间件负责将你的数据分布式存储在多台机器上的 Redis 实例中。 -现在 redis 的新版本,大家都是用 redis cluster 的,也就是 redis 原生支持的 redis 集群模式,那么面试官肯定会就 redis cluster 对你来个几连炮。要是你没用过 redis cluster,正常,以前很多人用 codis 之类的客户端来支持集群,但是起码你得研究一下 redis cluster 吧。 +这两年,Redis 不断在发展,Redis 也不断有新的版本,现在的 Redis 集群模式,可以做到在多台机器上,部署多个 Redis 实例,每个实例存储一部分的数据,同时每个 Redis 主实例可以挂 Redis 从实例,自动确保说,如果 Redis 主实例挂了,会自动切换到 Redis 从实例上来。 -如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个 G,单机就足够了,可以使用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 sentinel 集群去保证 redis 主从架构的高可用性。 +现在 Redis 的新版本,大家都是用 Redis cluster 的,也就是 Redis 原生支持的 Redis 集群模式,那么面试官肯定会就 Redis cluster 对你来个几连炮。要是你没用过 Redis cluster,正常,以前很多人用 codis 之类的客户端来支持集群,但是起码你得研究一下 Redis cluster 吧。 -redis cluster,主要是针对**海量数据+高并发+高可用**的场景。redis cluster 支撑 N 个 redis master node,每个 master node 都可以挂载多个 slave node。这样整个 redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。 +如果你的数据量很少,主要是承载高并发高性能的场景,比如你的缓存一般就几个 G,单机就足够了,可以使用 replication,一个 master 多个 slaves,要几个 slave 跟你要求的读吞吐量有关,然后自己搭建一个 sentinel 集群去保证 Redis 主从架构的高可用性。 + +Redis cluster,主要是针对**海量数据+高并发+高可用**的场景。Redis cluster 支撑 N 个 Redis master node,每个 master node 都可以挂载多个 slave node。这样整个 Redis 就可以横向扩容了。如果你要支撑更大数据量的缓存,那就横向扩容更多的 master 节点,每个 master 节点就能存放更多的数据了。 ## 面试题剖析 -### redis cluster 介绍 -- 自动将数据进行分片,每个 master 上放一部分数据 -- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 -在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。 +### Redis cluster 介绍 -16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议,`gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。 +* 自动将数据进行分片,每个 master 上放一部分数据 +* 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 + +在 Redis cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。 + +16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议, `gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。 ### 节点间的内部通信机制 + #### 基本通信原理 -集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。 +集群元数据的维护有两种方式:集中式、Gossip 协议。Redis cluster 节点间采用 gossip 协议进行通信。 -**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 +**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm` 。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 -![zookeeper-centralized-storage](/images/zookeeper-centralized-storage.png) +![zookeeper-centralized-storage](./images/zookeeper-centralized-storage.png) -redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。 +Redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。 -![redis-gossip](/images/redis-gossip.png) +![Redis-gossip](./images/redis-gossip.png) **集中式**的**好处**在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;**不好**在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。 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 -redis-trib.rb add-node +* 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](/images/hash.png) +![hash](./images/hash.png) #### 一致性 hash 算法 + 一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。 来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环**顺时针“行走”**,遇到的第一个 master 节点就是 key 所在位置。 @@ -82,38 +91,44 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担 燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成**缓存热点**的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。 -![consistent-hashing-algorithm](/images/consistent-hashing-algorithm.png) +![consistent-hashing-algorithm](./images/consistent-hashing-algorithm.png) -#### redis cluster 的 hash slot 算法 -redis cluster 有固定的 `16384` 个 hash slot,对每个 `key` 计算 `CRC16` 值,然后对 `16384` 取模,可以获取 key 对应的 hash slot。 +#### Redis cluster 的 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` 来实现。 +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` 来实现。 任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。 -![hash-slot](/images/hash-slot.png) +![hash-slot](./images/hash-slot.png) -### redis cluster 的高可用与主备切换原理 -redis cluster 的高可用的原理,几乎跟哨兵是类似的。 +### 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 的功能。 \ No newline at end of file + +整个流程跟哨兵相比,非常类似,所以说,Redis cluster 功能强大,直接集成了 replication 和 sentinel 的功能。 diff --git a/docs/high-concurrency/redis-consistence.md b/docs/high-concurrency/redis-consistence.md index d3f5441..31f2f41 100644 --- a/docs/high-concurrency/redis-consistence.md +++ b/docs/high-concurrency/redis-consistence.md @@ -2,17 +2,21 @@ 如何保证缓存与数据库的双写一致性? ## 面试官心理分析 + 你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? ## 面试题剖析 + 一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统**不是严格要求** “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:**读请求和写请求串行化**,串到一个**内存队列**里去。 串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。 ### Cache Aside Pattern + 最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。 -- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 -- 更新的时候,**先更新数据库,然后再删除缓存**。 + +* 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 +* 更新的时候,**先更新数据库,然后再删除缓存**。 **为什么是删除缓存,而不是更新缓存?** @@ -27,13 +31,15 @@ 其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都把里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。 ### 最初级的缓存不一致问题及解决方案 + 问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。 -![redis-junior-inconsistent](/images/redis-junior-inconsistent.png) +![redis-junior-inconsistent](./images/redis-junior-inconsistent.png) 解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。 ### 比较复杂的数据不一致问题分析 + 数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,**查到了修改前的旧数据**,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了... **为什么上亿流量高并发场景下,缓存会出现这个问题?** @@ -54,7 +60,7 @@ 高并发的场景下,该解决方案要注意的问题: -- 读请求长时阻塞 +* 读请求长时阻塞 由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。 @@ -74,18 +80,22 @@ 经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。 -- 读请求并发量过高 +* 读请求并发量过高 这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。 但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。 -- 多服务实例部署的请求路由 +* 多服务实例部署的请求路由 可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。 比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。 -- 热点商品的路由问题,导致请求的倾斜 +* 热点商品的路由问题,导致请求的倾斜 万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。 + +--- + +关于这道面试题的详细讨论,见 [#54](https://github.com/doocs/advanced-java/issues/54)。 diff --git a/docs/high-concurrency/redis-data-types.md b/docs/high-concurrency/redis-data-types.md index 849893b..abf57e5 100644 --- a/docs/high-concurrency/redis-data-types.md +++ b/docs/high-concurrency/redis-data-types.md @@ -1,40 +1,49 @@ ## 面试题 -redis 都有哪些数据类型?分别在哪些场景下使用比较合适? +Redis 都有哪些数据类型?分别在哪些场景下使用比较合适? ## 面试官心理分析 + 除非是面试官感觉看你简历,是工作 3 年以内的比较初级的同学,可能对技术没有很深入的研究,面试官才会问这类问题。否则,在宝贵的面试时间里,面试官实在不想多问。 其实问这个问题,主要有两个原因: -- 看看你到底有没有全面的了解 redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作; -- 看看你在实际项目里都怎么玩儿过 redis。 + +* 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作; +* 看看你在实际项目里都怎么玩儿过 Redis。 要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。 ## 面试题剖析 -redis 主要有以下几种数据类型: -- string -- hash -- list -- set -- sorted set -### string +Redis 主要有以下几种数据类型: + +* Strings +* Hashes +* Lists +* Sets +* Sorted Sets + +> Redis 除了这 5 种数据类型之外,还有 Bitmaps、HyperLogLogs、Streams 等。 + +### Strings + 这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。 -```bash + +``` bash set college szu ``` -### hash -这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是**这个对象没嵌套其他的对象**)给缓存在 redis 里,然后每次读写缓存的时候,可以就操作 hash 里的**某个字段**。 +### Hashes -```bash +这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是**这个对象没嵌套其他的对象**)给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 hash 里的**某个字段**。 + +``` bash hset person name bingo hset person age 20 hset person id 1 hget person name ``` -```json +``` json person = { "name": "bingo", "age": 20, @@ -42,19 +51,22 @@ person = { } ``` -### list -list 是有序列表,这个可以玩儿出很多花样。 +### Lists + +Lists 是有序列表,这个可以玩儿出很多花样。 比如可以通过 list 存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。 -比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。 -```bash +比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。 + +``` bash # 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。 lrange mylist 0 -1 ``` 比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。 -```bash + +``` bash lpush mylist 1 lpush mylist 2 lpush mylist 3 4 5 @@ -63,15 +75,17 @@ lpush mylist 3 4 5 rpop mylist ``` -### set -set 是无序集合,自动去重。 +### Sets -直接基于 set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 jvm 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于 redis 进行全局的 set 去重。 +Sets 是无序集合,自动去重。 + +直接基于 set 将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于 jvm 内存里的 HashSet 进行去重,但是如果你的某个系统部署在多台机器上呢?得基于 Redis 进行全局的 set 去重。 可以基于 set 玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧。 把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。 -```bash + +``` bash #-------操作一个set------- # 添加元素 sadd mySet 1 @@ -106,9 +120,11 @@ sunion yourSet mySet sdiff yourSet mySet ``` -### sorted set -sorted set 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。 -```bash +### Sorted Sets + +Sorted Sets 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。 + +``` bash zadd board 85 zhangsan zadd board 72 lisi zadd board 96 wangwu @@ -119,4 +135,4 @@ zrevrange board 0 3 # 获取某用户的排名 zrank board zhaoliu -``` \ No newline at end of file +``` diff --git a/docs/high-concurrency/redis-expiration-policies-and-lru.md b/docs/high-concurrency/redis-expiration-policies-and-lru.md index 9fea56c..7fccd94 100644 --- a/docs/high-concurrency/redis-expiration-policies-and-lru.md +++ b/docs/high-concurrency/redis-expiration-policies-and-lru.md @@ -1,53 +1,59 @@ ## 面试题 -redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? +Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? ## 面试官心理分析 -如果你连这个问题都不知道,上来就懵了,回答不出来,那线上你写代码的时候,想当然的认为写进 redis 的数据就一定会存在,后面导致系统各种 bug,谁来负责? + +如果你连这个问题都不知道,上来就懵了,回答不出来,那线上你写代码的时候,想当然的认为写进 Redis 的数据就一定会存在,后面导致系统各种 bug,谁来负责? 常见的有两个问题: -- 往 redis 写入的数据怎么没了? -可能有同学会遇到,在生产环境的 redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 redis 你就没用对啊。redis 是缓存,你给当存储了是吧? +* 往 Redis 写入的数据怎么没了? -啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个 G 的内存,但是可以有几个 T 的硬盘空间。redis 主要是基于内存来进行高性能、高并发的读写操作的。 +可能有同学会遇到,在生产环境的 Redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 Redis 你就没用对啊。Redis 是缓存,你给当存储了是吧? -那既然内存是有限的,比如 redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。 +啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个 G 的内存,但是可以有几个 T 的硬盘空间。Redis 主要是基于内存来进行高性能、高并发的读写操作的。 -- 数据明明过期了,怎么还占用着内存? +那既然内存是有限的,比如 Redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。 -这是由 redis 的过期策略来决定。 +* 数据明明过期了,怎么还占用着内存? + +这是由 Redis 的过期策略来决定。 ## 面试题剖析 -### redis 过期策略 -redis 过期策略是:**定期删除+惰性删除**。 -所谓**定期删除**,指的是 redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。 +### Redis 过期策略 +Redis 过期策略是:**定期删除+惰性删除**。 -假设 redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的**灾难**。实际上 redis 是每隔 100ms **随机抽取**一些 key 来检查和删除的。 +所谓**定期删除**,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。 -但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 +假设 Redis 里放了 10w 个 key,都设置了过期时间,你每隔几百毫秒,就检查 10w 个 key,那 Redis 基本上就死了,cpu 负载会很高的,消耗在你的检查过期 key 上了。注意,这里可不是每隔 100ms 就遍历所有的设置过期时间的 key,那样就是一场性能上的**灾难**。实际上 Redis 是每隔 100ms **随机抽取**一些 key 来检查和删除的。 + +但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个 key 的时候,Redis 会检查一下 ,这个 key 如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。 > 获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。 -但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整? +但是实际上这还是有问题的,如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期 key 堆积在内存里,导致 Redis 内存块耗尽了,咋整? 答案是:**走内存淘汰机制**。 ### 内存淘汰机制 -redis 内存淘汰机制有以下几个: -- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。 -- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。 -- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。 -- volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。 -- volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。 -- volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。 + +Redis 内存淘汰机制有以下几个: + +* 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 extends LinkedHashMap { private final int CACHE_SIZE; @@ -62,10 +68,14 @@ class LRUCache extends LinkedHashMap { CACHE_SIZE = cacheSize; } + /** + * 钩子方法,通过put新增键值对的时候,若该方法返回true + * 便移除该map中最老的键和值 + */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 return size() > CACHE_SIZE; } } -``` \ No newline at end of file +``` diff --git a/docs/high-concurrency/redis-master-slave.md b/docs/high-concurrency/redis-master-slave.md index 7996800..47fe1b4 100644 --- a/docs/high-concurrency/redis-master-slave.md +++ b/docs/high-concurrency/redis-master-slave.md @@ -1,40 +1,45 @@ # Redis 主从架构 -单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑**读高并发**的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的**读请求全部走从节点**。这样也可以很轻松实现水平扩容,**支撑读高并发**。 +单机的 Redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑**读高并发**的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的**读请求全部走从节点**。这样也可以很轻松实现水平扩容,**支撑读高并发**。 -![redis-master-slave](/images/redis-master-slave.png) +![Redis-master-slave](./images/redis-master-slave.png) -redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发 +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 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 可以提高读的吞吐量。 注意,如果采用了主从架构,那么建议必须**开启** 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 主从复制的核心原理 +## 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 部分缺少的数据。 -![redis-master-slave-replication](/images/redis-master-slave-replication.png) +![Redis-master-slave-replication](./images/redis-master-slave-replication.png) ### 主从复制的断点续传 -从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。 -master node 会在内存中维护一个 backlog,master 和 slave 都会保存一个 replica offset 还有一个 master run id,offset 就是保存在 backlog 中的。如果 master 和 slave 网络连接断掉了,slave 会让 master 从上次 replica offset 开始继续复制,如果没有找到对应的 offset,那么就会执行一次 `resynchronization`。 +从 Redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。 + +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,48 +47,57 @@ 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。 -![redis-master-slave-replication-detail](/images/redis-master-slave-replication-detail.png) +![Redis-master-slave-replication-detail](./images/redis-master-slave-replication-detail.png) ### 全量复制 -- 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 如何才能做到高可用 +## Redis 如何才能做到高可用 + 如果系统在 365 天内,有 99.99% 的时间,都是可以哗哗对外提供服务的,那么就说系统是高可用的。 一个 slave 挂掉了,是不会影响可用性的,还有其它的 slave 在提供相同数据下的相同的对外的查询服务。 但是,如果 master node 死掉了,会怎么样?没法写数据了,写缓存的时候,全部失效了。slave node 还有什么用呢,没有 master 给它们复制数据了,系统相当于不可用了。 -redis 的高可用架构,叫做 `failover` **故障转移**,也可以叫做主备切换。 +Redis 的高可用架构,叫做 `failover` **故障转移**,也可以叫做主备切换。 -master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。 +master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 Redis 的主从架构下的高可用。 -后面会详细说明 redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.md)。 \ No newline at end of file +后面会详细说明 Redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.md)。 diff --git a/docs/high-concurrency/redis-persistence.md b/docs/high-concurrency/redis-persistence.md index db9fc72..7dba2cf 100644 --- a/docs/high-concurrency/redis-persistence.md +++ b/docs/high-concurrency/redis-persistence.md @@ -1,50 +1,56 @@ ## 面试题 -redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的? +Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的? ## 面试官心理分析 -redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了再重启,内存里的数据就全部都弄丢了啊。你必须得用 redis 的持久化机制,将数据写入内存的同时,异步的慢慢的将数据写入磁盘文件里,进行持久化。 -如果 redis 宕机重启,自动从磁盘上加载之前持久化的一些数据就可以了,也许会丢失少许数据,但是至少不会将所有数据都弄丢。 +Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了再重启,内存里的数据就全部都弄丢了啊。你必须得用 Redis 的持久化机制,将数据写入内存的同时,异步的慢慢的将数据写入磁盘文件里,进行持久化。 -这个其实一样,针对的都是 redis 的生产环境可能遇到的一些问题,就是 redis 要是挂了再重启,内存里的数据不就全丢了?能不能重启的时候把数据给恢复了? +如果 Redis 宕机重启,自动从磁盘上加载之前持久化的一些数据就可以了,也许会丢失少许数据,但是至少不会将所有数据都弄丢。 + +这个其实一样,针对的都是 Redis 的生产环境可能遇到的一些问题,就是 Redis 要是挂了再重启,内存里的数据不就全丢了?能不能重启的时候把数据给恢复了? ## 面试题剖析 -持久化主要是做灾难恢复、数据恢复,也可以归类到高可用的一个环节中去,比如你 redis 整个挂了,然后 redis 就不可用了,你要做的事情就是让 redis 变得可用,尽快变得可用。 -重启 redis,尽快让它对外提供服务,如果没做数据备份,这时候 redis 启动了,也不可用啊,数据都没了。 +持久化主要是做灾难恢复、数据恢复,也可以归类到高可用的一个环节中去,比如你 Redis 整个挂了,然后 Redis 就不可用了,你要做的事情就是让 Redis 变得可用,尽快变得可用。 -很可能说,大量的请求过来,缓存全部无法命中,在 redis 里根本找不到数据,这个时候就死定了,出现**缓存雪崩**问题。所有请求没有在 redis 命中,就会去 mysql 数据库这种数据源头中去找,一下子 mysql 承接高并发,然后就挂了... +重启 Redis,尽快让它对外提供服务,如果没做数据备份,这时候 Redis 启动了,也不可用啊,数据都没了。 -如果你把 redis 持久化做好,备份和恢复方案做到企业级的程度,那么即使你的 redis 故障了,也可以通过备份数据,快速恢复,一旦恢复立即对外提供服务。 +很可能说,大量的请求过来,缓存全部无法命中,在 Redis 里根本找不到数据,这个时候就死定了,出现**缓存雪崩**问题。所有请求没有在 Redis 命中,就会去 mysql 数据库这种数据源头中去找,一下子 mysql 承接高并发,然后就挂了... -### redis 持久化的两种方式 -- RDB:RDB 持久化机制,是对 redis 中的数据执行**周期性**的持久化。 -- AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。 +如果你把 Redis 持久化做好,备份和恢复方案做到企业级的程度,那么即使你的 Redis 故障了,也可以通过备份数据,快速恢复,一旦恢复立即对外提供服务。 -通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。 +### Redis 持久化的两种方式 -如果 redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。 +* RDB:RDB 持久化机制,是对 Redis 中的数据执行**周期性**的持久化。 +* AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。 -如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 **AOF** 来重新构建数据,因为 AOF 中的**数据更加完整**。 +通过 RDB 或 AOF,都可以将 Redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。 + +如果 Redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动 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 来进行快速的数据恢复。 diff --git a/docs/high-concurrency/redis-production-environment.md b/docs/high-concurrency/redis-production-environment.md index 3048fd2..f743b7c 100644 --- a/docs/high-concurrency/redis-production-environment.md +++ b/docs/high-concurrency/redis-production-environment.md @@ -1,20 +1,22 @@ ## 面试题 -生产环境中的 redis 是怎么部署的? +生产环境中的 Redis 是怎么部署的? ## 面试官心理分析 -看看你了解不了解你们公司的 redis 生产集群的部署架构,如果你不了解,那么确实你就很失职了,你的 redis 是主从架构?集群架构?用了哪种集群方案?有没有做高可用保证?有没有开启持久化机制确保可以进行数据恢复?线上 redis 给几个 G 的内存?设置了哪些参数?压测后你们 redis 集群承载多少 QPS? + +看看你了解不了解你们公司的 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 可能会有问题。 +Redis cluster,10 台机器,5 台机器部署了 Redis 主实例,另外 5 台机器部署了 Redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。 + +机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 Redis 进程的是10g内存,一般线上生产环境,Redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。 5 台机器对外提供读写,一共有 50g 内存。 -因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。 +因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,Redis 从实例会自动变成主实例继续提供读写服务。 你往内存里写的是什么数据?每条数据的大小是多少?商品数据,每条数据是 10kb。100 条数据是 1mb,10 万条数据是 1g。常驻内存的是 200 万条商品数据,占用内存是 20g,仅仅不到总内存的 50%。目前高峰期每秒就是 3500 左右的请求量。 -其实大型的公司,会有基础架构的 team 负责缓存集群的运维。 \ No newline at end of file +其实大型的公司,会有基础架构的 team 负责缓存集群的运维。 diff --git a/docs/high-concurrency/redis-sentinel.md b/docs/high-concurrency/redis-sentinel.md index d755a23..0564368 100644 --- a/docs/high-concurrency/redis-sentinel.md +++ b/docs/high-concurrency/redis-sentinel.md @@ -1,35 +1,37 @@ # Redis 哨兵集群实现高可用 ## 哨兵的介绍 -sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能: -- 集群监控:负责监控 redis master 和 slave 进程是否正常工作。 -- 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。 -- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。 -- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。 +sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的一个组件,主要有以下功能: -哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。 +* 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。 +* 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。 +* 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。 +* 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。 -- 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。 -- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。 +哨兵用于实现 Redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。 + +* 故障转移时,判断一个 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,30 +55,32 @@ 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 哨兵主备切换的数据丢失问题 -### 两种情况和导致数据丢失 +## Redis 哨兵主备切换的数据丢失问题 + +### 导致数据丢失的两种情况 主备切换的过程,可能会导致数据丢失: -- 异步复制导致的数据丢失 +* 异步复制导致的数据丢失 因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。 -![async-replication-data-lose-case](/images/async-replication-data-lose-case.png) +![async-replication-data-lose-case](./images/async-replication-data-lose-case.png) -- 脑裂导致的数据丢失 +* 脑裂导致的数据丢失 脑裂,也就是说,某个 master 所在机器突然**脱离了正常的网络**,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会**认为** master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的**脑裂**。 此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了。 -![redis-cluster-split-brain](/images/redis-cluster-split-brain.png) +![Redis-cluster-split-brain](./images/redis-cluster-split-brain.png) ### 数据丢失问题的解决方案 + 进行如下配置: -```bash +``` bash min-slaves-to-write 1 min-slaves-max-lag 10 ``` @@ -85,22 +89,24 @@ 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 里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在。 + +哨兵互相之间的发现,是通过 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,13 +148,15 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过 但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才能执行切换。 ## configuration epoch -哨兵会对一套 redis master+slaves 进行监控,有相应的监控的配置。 + +哨兵会对一套 Redis master+slaves 进行监控,有相应的监控的配置。 执行切换的那个哨兵,会从要切换到的新 master(salve->master)那里得到一个 configuration epoch,这就是一个 version 号,每次切换的 version 号都必须是唯一的。 如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号。 ## configuration 传播 + 哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 `pub/sub` 消息机制。 -这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。 \ No newline at end of file +这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。 diff --git a/docs/high-concurrency/redis-single-thread-model.md b/docs/high-concurrency/redis-single-thread-model.md index 18f63a6..ff45a22 100644 --- a/docs/high-concurrency/redis-single-thread-model.md +++ b/docs/high-concurrency/redis-single-thread-model.md @@ -1,54 +1,60 @@ ## 面试题 -redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发? +Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么 Redis 单线程却能支撑高并发? ## 面试官心理分析 -这个是问 redis 的时候,最基本的问题吧,redis 最基本的一个内部原理和特点,就是 redis 实际上是个**单线程工作模型**,你要是这个都不知道,那后面玩儿 redis 的时候,出了问题岂不是什么都不知道? -还有可能面试官会问问你 redis 和 memcached 的区别,但是 memcached 是早些年各大互联网公司常用的缓存方案,但是现在近几年基本都是 redis,没什么公司用 memcached 了。 +这个是问 Redis 的时候,最基本的问题吧,Redis 最基本的一个内部原理和特点,就是 Redis 实际上是个**单线程工作模型**,你要是这个都不知道,那后面玩儿 Redis 的时候,出了问题岂不是什么都不知道? + +还有可能面试官会问问你 Redis 和 Memcached 的区别,但是 Memcached 是早些年各大互联网公司常用的缓存方案,但是现在近几年基本都是 Redis,没什么公司用 Memcached 了。 ## 面试题剖析 -### redis 和 memcached 有啥区别? +### Redis 和 Memcached 有啥区别? -#### redis 支持复杂的数据结构 -redis 相比 memcached 来说,拥有[更多的数据结构](/docs/high-concurrency/redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。 +#### Redis 支持复杂的数据结构 -#### redis 原生支持集群模式 -在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。 +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 只使用**单核**,而 Memcached 可以使用**多核**,所以平均每一个核上 Redis 在存储小数据时比 Memcached 性能更高。而在 100k 以上的数据中,Memcached 性能要高于 Redis。虽然 Redis 最近也在存储大数据的性能上进行优化,但是比起 Memcached,还是稍有逊色。 + +### Redis 的线程模型 + +Redis 内部使用文件事件处理器 `file event handler` ,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。 文件事件处理器的结构包含 4 个部分: -- 多个 socket -- IO 多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) +* 多个 socket +* IO 多路复用程序 +* 文件事件分派器 +* 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) 多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。 -来看客户端与 redis 的一次通信过程: +来看客户端与 Redis 的一次通信过程: -![redis-single-thread-model](/images/redis-single-thread-model.png) +![Redis-single-thread-model](./images/redis-single-thread-model.png) 要明白,通信是通过 socket 来完成的,不懂的同学可以先去看一看 socket 网络编程。 -首先,redis 服务端进程初始化的时候,会将 server socket 的 `AE_READABLE` 事件与连接应答处理器关联。 +首先,Redis 服务端进程初始化的时候,会将 server socket 的 `AE_READABLE` 事件与连接应答处理器关联。 -客户端 socket01 向 redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个 `AE_READABLE` 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。文件事件分派器从队列中获取 socket,交给**连接应答处理器**。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 `AE_READABLE` 事件与命令请求处理器关联。 +客户端 socket01 向 Redis 进程的 server socket 请求建立连接,此时 server socket 会产生一个 `AE_READABLE` 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该 socket 压入队列中。文件事件分派器从队列中获取 socket,交给**连接应答处理器**。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 `AE_READABLE` 事件与命令请求处理器关联。 -假设此时客户端发送了一个 `set key value` 请求,此时 redis 中的 socket01 会产生 `AE_READABLE` 事件,IO 多路复用程序将 socket01 压入队列,此时事件分派器从队列中获取到 socket01 产生的 `AE_READABLE` 事件,由于前面 socket01 的 `AE_READABLE` 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 `key value` 并在自己内存中完成 `key value` 的设置。操作完成后,它会将 socket01 的 `AE_WRITABLE` 事件与命令回复处理器关联。 +假设此时客户端发送了一个 `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 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 -- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 +### 为啥 Redis 单线程模型也能效率这么高? + +* 纯内存操作。 +* 核心是基于非阻塞的 IO 多路复用机制。 +* C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 +* 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 diff --git a/docs/high-concurrency/why-cache.md b/docs/high-concurrency/why-cache.md index dccd71f..7a9365f 100644 --- a/docs/high-concurrency/why-cache.md +++ b/docs/high-concurrency/why-cache.md @@ -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,9 +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) -后面再详细说明。 \ No newline at end of file +常见的缓存问题有以下几个: + +* [缓存与数据库双写不一致](/docs/high-concurrency/redis-consistence.md) +* [缓存雪崩、缓存穿透、缓存击穿](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) +* [缓存并发竞争](/docs/high-concurrency/redis-cas.md) + +点击超链接,可直接查看缓存相关问题及解决方案。 diff --git a/docs/high-concurrency/why-mq.md b/docs/high-concurrency/why-mq.md index 6815fa9..cb8fac0 100644 --- a/docs/high-concurrency/why-mq.md +++ b/docs/high-concurrency/why-mq.md @@ -1,23 +1,29 @@ ## 面试题 -- 为什么使用消息队列? -- 消息队列有什么优点和缺点? -- Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景? + +* 为什么使用消息队列? +* 消息队列有什么优点和缺点? +* Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景? ## 面试官心理分析 + 其实面试官主要是想看看: -- **第一**,你知不知道你们系统里为什么要用消息队列这个东西?
+* **第一**,你知不知道你们系统里为什么要用消息队列这个东西?
+ 不少候选人,说自己项目里用了 Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人设计的架构,他从头到尾都没思考过。
没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。 -- **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?
+* **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?
+ 你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。 -- **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?
+* **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?
+ 你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。
如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。 ## 面试题剖析 + ### 为什么使用消息队列 其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么? @@ -26,58 +32,65 @@ 先说一下消息队列常见的使用场景吧,其实场景有很多,但是比较核心的有 3 个:**解耦**、**异步**、**削峰**。 #### 解耦 + 看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃...... -![mq-1](/images/mq-1.png) +![mq-1](./images/mq-1.png) 在这个场景中,A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?头发都白了啊! 如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。 -![mq-2](/images/mq-2.png) +![mq-2](./images/mq-2.png) **总结**:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。 **面试技巧**:你需要去考虑一下你负责的系统中是否有类似的场景,就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦,也是可以的,你就需要去考虑在你的项目里,是不是可以运用这个 MQ 去进行系统的解耦。在简历中体现出来这块东西,用 MQ 作解耦。 #### 异步 + 再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。 -![mq-3](/images/mq-3.png) +![mq-3](./images/mq-3.png) 一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。 如果**使用 MQ**,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快! -![mq-4](/images/mq-4.png) +![mq-4](./images/mq-4.png) #### 削峰 + 每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。 一般的 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。 但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。 -![mq-5](/images/mq-5.png) +![mq-5](./images/mq-5.png) 如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。 -![mq-6](/images/mq-6.png) +![mq-6](./images/mq-6.png) 这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。 ### 消息队列有什么优缺点 + 优点上面已经说了,就是**在特殊场景下有其对应的好处**,**解耦**、**异步**、**削峰**。 缺点有以下几个: -- 系统可用性降低
+* 系统可用性降低
+ 系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)。 -- 系统复杂度提高
+* 系统复杂度提高
+ 硬生生加个 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)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。 -- 一致性问题
+* 一致性问题
+ A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。 所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。 @@ -93,7 +106,6 @@ A 系统处理完了直接返回成功了,人都以为你这个请求就成功 | 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | | 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | - 综上,各种对比之后,有如下建议: 一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了; diff --git a/docs/micro-services/README.md b/docs/micro-services/README.md index 6e0a6de..6c5d096 100644 --- a/docs/micro-services/README.md +++ b/docs/micro-services/README.md @@ -1,21 +1,24 @@ # 微服务架构 -- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](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 旗下唯一公众号“Doocs 开源社区”,欢迎关注,专注于分享有价值的文章;当然,也可以加我个人微信(备注:GitHub)。 + +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 diff --git a/docs/micro-services/choose-microservice-deployment-strategy.md b/docs/micro-services/choose-microservice-deployment-strategy.md index e7a4532..037843b 100644 --- a/docs/micro-services/choose-microservice-deployment-strategy.md +++ b/docs/micro-services/choose-microservice-deployment-strategy.md @@ -12,7 +12,7 @@ 下图展示的是这种架构: -![deployment-strategy-1](/images/deployment-strategy-1.png) +![deployment-strategy-1](./images/deployment-strategy-1.png) 这种模式有一些参数,一个参数代表每个服务实例由多少进程构成。例如,需要在 Apache Tomcat Server 上部署一个 Java 服务实例作为 web 应用。一个 Node.js 服务实例可能有一个父进程和若干个子进程构成。 @@ -40,7 +40,7 @@ 但是用单虚拟机单实例模式,一般将服务打包成虚拟机映像(image),例如一个 Amazon EC2 AMI。每个服务实例是一个使用此映像启动的 VM(例如,EC2 实例)。下图展示了此架构: -![deployment-strategy-2](/images/deployment-strategy-2.png) +![deployment-strategy-2](./images/deployment-strategy-2.png) Netfix 采用这种架构部署 video streaming service。Netfix 使用 Aminator 将每个服务打包成一个 EC2 AMI。每个运行服务实例就是一个 EC2 实例。 @@ -56,7 +56,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用 还有一个好处在于服务实施技术被自包含了。一旦服务被打包成 VM 就成为一个黑盒子。VM 的管理 API 成为部署服务的 API,部署成为一个非常简单和可靠的事情。 -单虚拟机单实例模式也有缺点。一个缺点就是资源利用效率不高。每个服务实例战友整个虚机的资源,包括操作系统。而且,在一个典型的公有 IaaS 环境,虚机资源都是标准化的,有可能未被充分利用。 +单虚拟机单实例模式也有缺点。一个缺点就是资源利用效率不高。每个服务实例占用整个虚机的资源,包括操作系统。而且,在一个典型的公有 IaaS 环境,虚机资源都是标准化的,有可能未被充分利用。 而且,公有 IaaS 根据 VM 来收费,而不管虚机是否繁忙;例如 AWS 提供了自动扩展功能,但是对随需应用缺乏快速响应,使得用户不得不多部署虚机,从而增加了部署费用。 @@ -72,7 +72,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用 下图展示了这种模式: -![deployment-strategy-3](/images/deployment-strategy-3.png) +![deployment-strategy-3](./images/deployment-strategy-3.png) 使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统​。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。 @@ -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 是一种很方便部署微服务的方式。基于请求计费方式意味着用户只需要承担处理自己业务那部分的负载;另外,因为不需要了解基础架构,用户只需要开发自己的应用。 diff --git a/docs/micro-services/event-driven-data-management-for-microservices.md b/docs/micro-services/event-driven-data-management-for-microservices.md index 63cdab7..43fbcd0 100644 --- a/docs/micro-services/event-driven-data-management-for-microservices.md +++ b/docs/micro-services/event-driven-data-management-for-microservices.md @@ -21,7 +21,7 @@ 相反的,微服务架构下,订单和客户表分别是相对应服务的私有表,如下图所示: -![service table](/images/Private-table-of-the-corresponding-service.png) +![service table](./images/Private-table-of-the-corresponding-service.png) 订单服务不能直接访问客户表,只能通过客户服务发布的 API 来访问。订单服务也可以使用 distributed transactions, 也就是周知的两阶段提交 (2PC)。然而,2PC 在现在应用中不是可选性。根据 CAP 理论,必须在可用性(availability)和 ACID 一致性(consistency)之间做出选择,availability 一般是更好的选择。但是,许多现代科技,例如许多 NoSQL 数据库,并不支持 2PC。在服务和数据库之间维护数据一致性是非常根本的需求,因此我们需要找其他的方案。 @@ -35,15 +35,15 @@ 1. 订单服务创建一个带有 NEW 状态的 Order (订单),发布了一个 “Order Created Event(创建订单)” 的事件。 -![Order-Created-Event](/images/Order-Created-Event.png) +![Order-Created-Event](./images/Order-Created-Event.png) 2. 客户服务消费 Order Created Event 事件,为此订单预留信用,发布 “Credit Reserved Event(信用预留)” 事件。 -![Credit-Reserved-Event](/images/Credit-Reserved-Event.png) +![Credit-Reserved-Event](./images/Credit-Reserved-Event.png) 3. 订单服务消费 Credit Reserved Event ,改变订单的状态为 OPEN。 -![Status-is-OPEN](/images/Status-is-OPEN.png) +![Status-is-OPEN](./images/Status-is-OPEN.png) 更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。 @@ -51,7 +51,7 @@ 亦可以使用事件来维护不同微服务拥有数据预连接(pre-join)的实现视图。维护此视图的服务订阅相关事件并且更新视图。例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件。 -![pre-join](/images/pre-join.png) +![pre-join](./images/pre-join.png) 当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。可以使用文档数据库(例如 MongoDB)来实现客户订单视图,为每个用户存储一个文档。客户订单视图查询服务负责响应对客户以及最近订单(通过查询客户订单视图数据集)的查询。 @@ -65,7 +65,7 @@ 获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions,技巧在于一个 EVENT 表,此表在存储业务实体数据库中起到消息列表功能。应用发起一个(本地)数据库交易,更新业务实体状态,向 EVENT 表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此 EVENT 表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示: -![multi-step process](/images/multi-step-process.png) +![multi-step process](./images/multi-step-process.png) 订单服务向 ORDER 表插入一行,然后向 EVENT 表中插入 Order Created event,事件发布线程或者进程查询 EVENT 表,请求未发布事件,发布他们,然后更新 EVENT 表标志此事件为已发布。 @@ -77,7 +77,7 @@ 另外一种不需要 2PC 而获得线程或者进程发布事件原子性的方式就是挖掘数据库交易或者提交日志。应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见: -![No-2PC-required](/images/No-2PC-required.png) +![No-2PC-required](./images/No-2PC-required.png) 此方法的例子如 LinkedIn Databus 项目,Databus 挖掘 Oracle 交易日志,根据变化发布事件,LinkedIn 使用 Databus 来保证系统内各记录之间的一致性。 @@ -93,7 +93,7 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来 为了理解事件源工作方式,考虑事件实体作为一个例子。传统方式中,每个订单映射为 ORDER 表中一行,例如在 ORDER_LINE_ITEM 表中。但是对于事件源方式,订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够数据来重建订单状态。 -![Event-sourcing](/images/Event-sourcing.png) +![Event-sourcing](./images/Event-sourcing.png) 事件是长期保存在事件数据库中,提供 API 添加和获取实体事件。事件存储跟之前描述的消息代理类似,提供 API 来订阅事件。事件存储将事件递送到所有感兴趣的订阅者,事件存储是事件驱动微服务架构的基干。 @@ -107,4 +107,4 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来 在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务交易一致性;第二个挑战是如何从多服务环境中获取一致性数据。 -最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法可以解决此问题,包括将数据库视为消息队列、交易日志挖掘和事件源。 \ No newline at end of file +最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法可以解决此问题,包括将数据库视为消息队列、交易日志挖掘和事件源。 diff --git a/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md b/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md new file mode 100644 index 0000000..d3a9f45 --- /dev/null +++ b/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md @@ -0,0 +1,131 @@ +# 服务发现组件 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)。 + +## 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. + +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-homepage.png](./images/eureka-server-homepage.png) + +我们继续添加两个模块 `service-provider` , `service-consumer` ,然后在启动类加上注解 `@EnableEurekaClient` 并指定注册中心地址为我们刚刚启动的 `Eureka Server` ,再次访问可以看到两个服务都已经注册进来了。 + +![eureka-instance-registered-currently.png](./images/eureka-instance-registered-currently.png) + +`Demo` 仓库地址:https://github.com/mghio/depth-in-springcloud + +可以看到 `Eureka` 的使用非常简单,只需要添加几个注解和配置就实现了服务注册和服务发现,接下来我们看看它是如何实现这些功能的。 + +### 服务注册(Register) + +注册中心提供了服务注册接口,用于当有新的服务启动后进行调用来实现服务注册,或者心跳检测到服务状态异常时,变更对应服务的状态。服务注册就是发送一个 `POST` 请求带上当前实例信息到类 `ApplicationResource` 的 `addInstance` 方法进行服务注册。 + +![eureka-server-applicationresource-addinstance.png](./images/eureka-server-applicationresource-addinstance.png) + +可以看到方法调用了类 `PeerAwareInstanceRegistryImpl` 的 `register` 方法,该方法主要分为两步: + +1. 调用父类 `AbstractInstanceRegistry` 的 `register` 方法把当前服务注册到注册中心 +2. 调用 `replicateToPeers` 方法使用异步的方式向其它的 `Eureka Server` 节点同步服务注册信息 + +服务注册信息保存在一个嵌套的 `map` 中,它的结构如下: + +![eureka-server-registry-structure.png](./images/eureka-server-registry-structure.png) + +第一层 `map` 的 `key` 是应用名称(对应 `Demo` 里的 `SERVICE-PROVIDER` ),第二层 `map` 的 `key` 是应用对应的实例名称(对应 `Demo` 里的 `mghio-mbp:service-provider:9999` ),一个应用可以有多个实例,主要调用流程如下图所示: + +![eureka-server-register-sequence-chart.png](./images/eureka-server-register-sequence-chart.png) + +### 服务续约(Renew) + +服务续约会由服务提供者(比如 `Demo` 中的 `service-provider` )定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务时效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。 + +![eureka-server-instanceresource-renew.png](./images/eureka-server-instanceresource-renew.png) + +进入到 `PeerAwareInstanceRegistryImpl` 的 `renew` 方法可以看到,服务续约步骤大体上和服务注册一致,先更新当前 `Eureka Server` 节点的状态,服务续约成功后再用异步的方式同步状态到其它 `Eureka Server` 节上,主要调用流程如下图所示: + +![eureka-server-renew-sequence-chart.png](./images/eureka-server-renew-sequence-chart.png) + +### 服务下线(Cancel) + +当服务提供者(比如 `Demo` 中的 `service-provider` )停止服务时,会发送请求告知注册中心 `Eureka Server` 进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个 `DELETE` 请求带上当前实例信息到类 `InstanceResource` 的 `cancelLease` 方法进行服务剔除下线操作。 + +![eureka-server-instanceresource-cancellease.png](./images/eureka-server-instanceresource-cancellease.png) + +进入到 `PeerAwareInstanceRegistryImpl` 的 `cancel` 方法可以看到,服务续约步骤大体上和服务注册一致,先在当前 `Eureka Server` 节点剔除下线该服务,服务下线成功后再用异步的方式同步状态到其它 `Eureka Server` 节上,主要调用流程如下图所示: + +![eureka-server-cancellease-sequence-chart.png](./images/eureka-server-cancellease-sequence-chart.png) + +### 服务剔除(Eviction) + +服务剔除是注册中心 `Eureka Server` 在启动时就启动一个守护线程 `evictionTimer` 来定期(默认为 `60` 秒)执行检测服务的,判断标准就是超过一定时间没有进行 `Renew` 的服务,默认的失效时间是 `90` 秒,也就是说当一个已注册的服务在 `90` 秒内没有向注册中心 `Eureka Server` 进行服务续约(Renew),就会被从注册中心剔除下线。失效时间可以通过配置 `eureka.instance.leaseExpirationDurationInSeconds` 进行修改,定期执行检测服务可以通过配置 `eureka.server.evictionIntervalTimerInMs` 进行修改,主要调用流程如下图所示: + +![eureka-server-evict-sequence-chart.png](./images/eureka-server-evict-sequence-chart.png) + +## 服务提供者(Service Provider) + +对于服务提供方(比如 `Demo` 中的 `service-provider` 服务)来说,主要有三大类操作,分别为 `服务注册(Register)` 、 `服务续约(Renew)` 、 `服务下线(Cancel)` ,接下来看看这三个操作是如何实现的。 + +### 服务注册(Register) + +一个服务要对外提供服务,首先要在注册中心 `Eureka Server` 进行服务相关信息注册,能进行这一步的前提是你要配置 `eureka.client.register-with-eureka=true` ,这个默认值为 `true` ,注册中心不需要把自己注册到注册中心去,把这个配置设为 `false` ,这个调用比较简单,主要调用流程如下图所示: + +![eureka-service-provider-register-sequence-chart.png](./images/eureka-server-register-sequence-chart.png) + +### 服务续约(Renew) + +服务续约是由服务提供者方定期(默认为 `30` 秒)发起心跳的,主要是用来告知注册中心 `Eureka Server` 自己状态是正常的还活着,可以通过配置 `eureka.instance.lease-renewal-interval-in-seconds` 来修改,当然服务续约的前提是要配置 `eureka.client.register-with-eureka=true` ,将该服务注册到注册中心中去,主要调用流程如下图所示: + +![eureka-service-provider-renew-sequence-chart.png](./images/eureka-service-provider-renew-sequence-chart.png) + +### 服务下线(Cancel) + +当服务提供者方服务停止时,要发送 `DELETE` 请求告知注册中心 `Eureka Server` 自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类 `DiscoveryClient` 的 `shutdown` 方法加上注解 `@PreDestroy` ,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示: + +![eureka-service-provider-cancel-sequence-chart.png](./images/eureka-service-provider-cancel-sequence-chart.png) + +## 服务消费者(Service Consumer) + +这里的服务消费者如果不需要被其它服务调用的话,其实只会涉及到两个操作,分别是从注册中心 `获取服务列表(Fetch)` 和 `更新服务列表(Update)` 。如果同时也需要注册到注册中心对外提供服务的话,那么剩下的过程和上文提到的服务提供者是一致的,这里不再阐述,接下来看看这两个操作是如何实现的。 + +### 获取服务列表(Fetch) + +服务消费者方启动之后首先肯定是要先从注册中心 `Eureka Server` 获取到可用的服务列表同时本地也会缓存一份。这个获取服务列表的操作是在服务启动后 `DiscoverClient` 类实例化的时候执行的。 + +![eureka-service-consumer-fetchregistry.png](./images/eureka-service-consumer-fetchregistry.png) + +可以看出,能发生这个获取服务列表的操作前提是要保证配置了 `eureka.client.fetch-registry=true` ,该配置的默认值为 `true` ,主要调用流程如下图所示: + +![eureka-service-consumer-fetch-sequence-chart.png](./images/eureka-service-consumer-fetch-sequence-chart.png) + +### 更新服务列表(Update) + +由上面的 `获取服务列表(Fetch)` 操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心 `Eureka Server` 获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置 `eureka.client.registry-fetch-interval-seconds` 修改,默认为 `30` 秒,能进行这一步更新服务列表的前提是你要配置 `eureka.client.register-with-eureka=true` ,这个默认值为 `true` 。主要调用流程如下图所示: + +![eureka-service-consumer-update-sequence-chart.png](./images/eureka-service-consumer-update-sequence-chart.png) + +## 总结 + +工作中项目使用的是 `Spring Cloud` 技术栈,它有一套非常完善的开源代码来整合 `Eureka` ,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对 `Eureka` 服务发现组件有了更近一步的了解。 + +--- +参考文章 + +[Netflix Eureka](https://github.com/Netflix/eureka) + +[Service Discovery in a Microservices Architecture](https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture) diff --git a/docs/micro-services/huifer-micro-service-governance.md b/docs/micro-services/huifer-micro-service-governance.md index 01bf912..8296066 100644 --- a/docs/micro-services/huifer-micro-service-governance.md +++ b/docs/micro-services/huifer-micro-service-governance.md @@ -1,9 +1,9 @@ # 微服务治理策略 -- 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 隔离 ; 硬件隔离 ## 容量规划 -> 解决问题: 随着时间增长,调用逐步增加,什么时候追加机器 +> 解决问题: 随着时间增长, 调用逐步增加, 什么时候追加机器 -> 解决方法: 统计每日调用量和响应时间, 根据机器情况设置阈值,超过阈值就可以追加机器 +> 解决方法: 统计每日调用量和响应时间, 根据机器情况设置阈值, 超过阈值就可以追加机器 diff --git a/docs/micro-services/huifer-micro-services-technology-stack .md b/docs/micro-services/huifer-micro-services-technology-stack .md deleted file mode 100644 index ff8b124..0000000 --- a/docs/micro-services/huifer-micro-services-technology-stack .md +++ /dev/null @@ -1,204 +0,0 @@ -# 微服务技术栈 -- Author: [HuiFer](https://github.com/huifer) -- Description: 该文简单介绍微服务技术栈有哪些分别用来做什么 -## 技术栈 -### 微服务开发 -> 作用: 快速开发服务. -- Spring -- SpringMvc -- SpringBoot -> [官网](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/) -#### Zookeeper -- > ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. - > - > zookeeper是一个集中的服务,用于维护配置信息、命名、提供分布式同步和提供组服务. -- [GitHub](https://github.com/apache/zookeeper) -#### Zookeeper 和 Eureka 区别 -1. Zookeeper保证CP , Eureka 保证AP - - C-数据一致性;A-服务可用性;P-服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个. - -### 微服务配置管理 -> 作用:统一管理一个或多个服务的配置信息,集中管理. -#### Disconf -- > Distributed Configuration Management Platform(分布式配置管理平台) ,它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务,是一套完整的基于zookeeper的分布式配置统一解决方案. -- [GitHub](https://github.com/knightliao/disconf) - -#### SpringCloudConfig -- [GitHub](https://github.com/spring-cloud/spring-cloud-config) -#### Apollo -- > Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景. -- [GitHub](https://github.com/ctripcorp/apollo) - - -### 权限认证 -> 作用:根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源,不多不少. -#### 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/) - - -### 批处理 -> 作用: 批量处理同类型数据或事物 -#### Spring Batch -- [官网](官网) -### 定时任务 -> 作用: 定时做什么. -#### Quartz -- [官网](http://www.quartz-scheduler.org/) - - -### 微服务调用(协议) -> 通讯协议 -#### Rest -- 通过HTTP/HTTPS发送Rest请求进行数据交互 -#### RPC -- 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调用 - - -### 服务接口调用 -> 作用: 多个服务之间的通讯 -#### Feign(HTTP) -- > Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法. -- [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. - > -- [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) - - - - - -### 服务的负载均衡 -> 作用:降低服务压力,增加吞吐量 -#### 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/) - - -### 日志采集(elk) -> 作用:收集各服务日志提供日志分析、用户画像等 -#### Elasticsearch -- [GitHub](https://github.com/elastic/elasticsearch) -#### Logstash -- [GitHub](https://github.com/elastic/logstash) -#### Kibana -- [GitHub](https://github.com/elastic/kibana) - - -### API网关 -> 作用:外部请求通过API网关进行拦截处理,再转发到真正的服务 -#### Zuul -> Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more. -> -- [GitHub](https://github.com/Netflix/zuul) - - -### 服务监控 -> 作用:以可视化或非可视化的形式展示出各个服务的运行情况(CPU、内存、访问量等) -#### Zabbix -- [GitHub](https://github.com/jjmartres/Zabbix) -#### Nagios -- [官网](https://www.nagios.org/) -#### Metrics -- [官网](https://metrics.dropwizard.io) - -### 服务链路追踪 -> 作用:明确服务之间的调用关系 -#### Zipkin -- [GitHub](https://github.com/openzipkin/zipkin) - -#### Brave -- [GitHub](https://github.com/openzipkin/brave) - - -### 数据存储 -> 作用: 存储数据 -#### 关系型数据库 -##### MySql -- [官网](https://www.mysql.com/) -##### Oracle -- [官网](https://www.oracle.com/index.html) -##### MsSql -- [官网](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15) -##### PostgreSql -- [官网](https://www.postgresql.org/) -#### 非关系型数据库 -##### Mongodb -- [官网](https://www.mongodb.com/) -##### Elasticsearch -- [GitHub](https://github.com/elastic/elasticsearch) -### 缓存 -> 作用: 存储数据 -#### redis -- [官网](https://redis.io/) - -### 分库分表 -> 作用: 数据库分库分表方案. -#### shardingsphere -- [官网](http://shardingsphere.apache.org/) -#### Mycat -- [官网](http://www.mycat.io/) - - - - -### 服务部署 -> 作用: 将项目快速部署、上线、持续集成. -#### Docker -- [官网](http://www.docker.com/) -#### Jenkins -- [官网](https://jenkins.io/zh/) -#### Kubernetes(K8s) -- [官网](https://kubernetes.io/) -#### Mesos -- [官网](http://mesos.apache.org/) diff --git a/docs/micro-services/huifer-micro-services-technology-stack.md b/docs/micro-services/huifer-micro-services-technology-stack.md new file mode 100644 index 0000000..36d6c1c --- /dev/null +++ b/docs/micro-services/huifer-micro-services-technology-stack.md @@ -0,0 +1,311 @@ +# 微服务技术栈 + +* Author: [HuiFer](https://github.com/huifer) +* Description: 该文简单介绍微服务技术栈有哪些分别用来做什么 + +## 技术栈 + +### 微服务开发 + +作用:快速开发服务。 + +* 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/) + +#### 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 和 Eureka 区别 + +Zookeeper 保证 CP,Eureka 保证 AP: + +* C:数据一致性; +* A:服务可用性; +* P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。 + +### 微服务配置管理 + +作用:统一管理一个或多个服务的配置信息, 集中管理。 + +#### Disconf + +Distributed Configuration Management Platform(分布式配置管理平台) , 它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务, 是一套完整的基于 zookeeper 的分布式配置统一解决方案。 + +* [GitHub](https://github.com/knightliao/disconf) + +#### SpringCloudConfig + +* [GitHub](https://github.com/spring-cloud/spring-cloud-config) + +#### Apollo + +Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,用于微服务配置管理场景。 + +* [GitHub](https://github.com/ctripcorp/apollo) + +### 权限认证 + +作用:根据系统设置的安全规则或者安全策略, 用户可以访问而且只能访问自己被授权的资源,不多不少。 + +#### 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/) + +### 批处理 + +作用: 批量处理同类型数据或事物 + +#### Spring Batch + +* [官网](官网) + +### 定时任务 + +> 作用: 定时做什么. + +#### Quartz + +* [官网](http://www.quartz-scheduler.org/) + +### 微服务调用 (协议) + +> 通讯协议 + +#### Rest + +* 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互 + +#### RPC + +* 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 调用 + +### 服务接口调用 + +> 作用:多个服务之间的通讯 + +#### Feign(HTTP) + +Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。 + +* [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. + + > + +* [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) + +### 服务的负载均衡 + +> 作用:降低服务压力, 增加吞吐量 + +#### 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/) + +### 日志采集 (elk) + +> 作用: 收集各服务日志提供日志分析、用户画像等 + +#### Elasticsearch + +* [GitHub](https://github.com/elastic/elasticsearch) + +#### Logstash + +* [GitHub](https://github.com/elastic/logstash) + +#### Kibana + +* [GitHub](https://github.com/elastic/kibana) + +### API 网关 + +> 作用: 外部请求通过 API 网关进行拦截处理, 再转发到真正的服务 + +#### Zuul + +> Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more. +> + +* [GitHub](https://github.com/Netflix/zuul) + +### 服务监控 + +> 作用: 以可视化或非可视化的形式展示出各个服务的运行情况 (CPU、内存、访问量等) + +#### Zabbix + +* [GitHub](https://github.com/jjmartres/Zabbix) + +#### Nagios + +* [官网](https://www.nagios.org/) + +#### Metrics + +* [官网](https://metrics.dropwizard.io) + +### 服务链路追踪 + +> 作用: 明确服务之间的调用关系 + +#### Zipkin + +* [GitHub](https://github.com/openzipkin/zipkin) + +#### Brave + +* [GitHub](https://github.com/openzipkin/brave) + +### 数据存储 + +> 作用: 存储数据 + +#### 关系型数据库 + +##### MySql + +* [官网](https://www.mysql.com/) + +##### Oracle + +* [官网](https://www.oracle.com/index.html) + +##### MsSql + +* [官网](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15) + +##### PostgreSql + +* [官网](https://www.postgresql.org/) + +#### 非关系型数据库 + +##### Mongodb + +* [官网](https://www.mongodb.com/) + +##### Elasticsearch + +* [GitHub](https://github.com/elastic/elasticsearch) + +### 缓存 + +> 作用: 存储数据 + +#### redis + +* [官网](https://redis.io/) + +### 分库分表 + +> 作用: 数据库分库分表方案. + +#### shardingsphere + +* [官网](http://shardingsphere.apache.org/) + +#### Mycat + +* [官网](http://www.mycat.io/) + +### 服务部署 + +> 作用: 将项目快速部署、上线、持续集成. + +#### Docker + +* [官网](http://www.docker.com/) + +#### Jenkins + +* [官网](https://jenkins.io/zh/) + +#### Kubernetes(K8s) + +* [官网](https://kubernetes.io/) + +#### Mesos + +* [官网](http://mesos.apache.org/) diff --git a/docs/micro-services/huifer-what's-microservice-how-to-communicate.md b/docs/micro-services/huifer-what's-microservice-how-to-communicate.md index 80591cf..2444270 100644 --- a/docs/micro-services/huifer-what's-microservice-how-to-communicate.md +++ b/docs/micro-services/huifer-what's-microservice-how-to-communicate.md @@ -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 { 再来一个测试的远程方法。 -```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. 这里不对消息队列进行拓展了, 具体如何使用还是请移步官网. > diff --git a/docs/micro-services/images/deployment-strategy-1.png b/docs/micro-services/images/deployment-strategy-1.png index 329c149..f30401d 100644 Binary files a/docs/micro-services/images/deployment-strategy-1.png and b/docs/micro-services/images/deployment-strategy-1.png differ diff --git a/docs/micro-services/images/deployment-strategy-2.png b/docs/micro-services/images/deployment-strategy-2.png index f4a471e..165ec0a 100644 Binary files a/docs/micro-services/images/deployment-strategy-2.png and b/docs/micro-services/images/deployment-strategy-2.png differ diff --git a/docs/micro-services/images/deployment-strategy-3.png b/docs/micro-services/images/deployment-strategy-3.png index ae54919..beaeec5 100644 Binary files a/docs/micro-services/images/deployment-strategy-3.png and b/docs/micro-services/images/deployment-strategy-3.png differ diff --git a/docs/micro-services/images/eureka-instance-registered-currently.png b/docs/micro-services/images/eureka-instance-registered-currently.png new file mode 100644 index 0000000..56404e6 Binary files /dev/null and b/docs/micro-services/images/eureka-instance-registered-currently.png differ diff --git a/docs/micro-services/images/eureka-server-applicationresource-addinstance.png b/docs/micro-services/images/eureka-server-applicationresource-addinstance.png new file mode 100644 index 0000000..b09c9f0 Binary files /dev/null and b/docs/micro-services/images/eureka-server-applicationresource-addinstance.png differ diff --git a/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png b/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png new file mode 100644 index 0000000..2d5704d Binary files /dev/null and b/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-server-evict-sequence-chart.png b/docs/micro-services/images/eureka-server-evict-sequence-chart.png new file mode 100644 index 0000000..49af8fb Binary files /dev/null and b/docs/micro-services/images/eureka-server-evict-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-server-homepage.png b/docs/micro-services/images/eureka-server-homepage.png new file mode 100644 index 0000000..6152d05 Binary files /dev/null and b/docs/micro-services/images/eureka-server-homepage.png differ diff --git a/docs/micro-services/images/eureka-server-instanceresource-cancellease.png b/docs/micro-services/images/eureka-server-instanceresource-cancellease.png new file mode 100644 index 0000000..478984e Binary files /dev/null and b/docs/micro-services/images/eureka-server-instanceresource-cancellease.png differ diff --git a/docs/micro-services/images/eureka-server-instanceresource-renew.png b/docs/micro-services/images/eureka-server-instanceresource-renew.png new file mode 100644 index 0000000..02424ed Binary files /dev/null and b/docs/micro-services/images/eureka-server-instanceresource-renew.png differ diff --git a/docs/micro-services/images/eureka-server-register-sequence-chart.png b/docs/micro-services/images/eureka-server-register-sequence-chart.png new file mode 100644 index 0000000..6bc2fd6 Binary files /dev/null and b/docs/micro-services/images/eureka-server-register-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-server-registry-structure.png b/docs/micro-services/images/eureka-server-registry-structure.png new file mode 100644 index 0000000..e3f4870 Binary files /dev/null and b/docs/micro-services/images/eureka-server-registry-structure.png differ diff --git a/docs/micro-services/images/eureka-server-renew-sequence-chart.png b/docs/micro-services/images/eureka-server-renew-sequence-chart.png new file mode 100644 index 0000000..3e8dd10 Binary files /dev/null and b/docs/micro-services/images/eureka-server-renew-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png b/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png new file mode 100644 index 0000000..b3b8956 Binary files /dev/null and b/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-consumer-fetchregistry.png b/docs/micro-services/images/eureka-service-consumer-fetchregistry.png new file mode 100644 index 0000000..0782482 Binary files /dev/null and b/docs/micro-services/images/eureka-service-consumer-fetchregistry.png differ diff --git a/docs/micro-services/images/eureka-service-consumer-update-sequence-chart.png b/docs/micro-services/images/eureka-service-consumer-update-sequence-chart.png new file mode 100644 index 0000000..b344597 Binary files /dev/null and b/docs/micro-services/images/eureka-service-consumer-update-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png new file mode 100644 index 0000000..becf2ea Binary files /dev/null and b/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png new file mode 100644 index 0000000..8132d5c Binary files /dev/null and b/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png new file mode 100644 index 0000000..e239928 Binary files /dev/null and b/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png differ diff --git a/docs/micro-services/microservices-introduction.md b/docs/micro-services/microservices-introduction.md index 857ad01..93d66eb 100644 --- a/docs/micro-services/microservices-introduction.md +++ b/docs/micro-services/microservices-introduction.md @@ -1,4 +1,5 @@ # 微服务 + > 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。
本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。 过去几年中出现了“微服务架构”这一术语,它描述了将软件应用程序设计为若干个可独立部署的服务套件的特定方法。尽管这种架构风格尚未有精确的定义,但围绕业务能力、自动部署、端点智能以及语言和数据的分散控制等组织来说,它们还是存在着某些共同特征。 @@ -13,16 +14,18 @@ 单体应用程序可以取得成功,但越来越多的人对它们感到不满——尤其是在将更多应用程序部署到云的时候。变更周期被捆绑在一起——即使只是对应用程序的一小部分进行了更改,也需要重建和部署整个单体应用。随着时间的推移,通常很难保持良好的模块化结构,也更难以保持应该只影响该模块中的一个模块的更改。对系统进行扩展时,不得不扩展整个应用系统,而不能仅扩展该系统中需要更多资源的那些部分。 -![sketch](/images/sketch.png) +![sketch](./images/sketch.png) 这些不满催生了微服务架构风格:将应用程序构建为服务套件。除了服务可独立部署、独立扩展的事实之外,每个服务还提供了一个牢固的模块边界,甚至允许以不同的编程语言编写不同的服务。他们也可以由不同的团队管理。 我们并不认为微服务风格是新颖的或创新的,其根源至少可以追溯到 Unix 的设计原则。但我们认为没有足够多的人考虑微服务架构,如果使用它,许多软件的开发会变得更好。 ## 微服务架构的特征 + 虽然不能说微服务架构风格有正式的定义,但我们可以尝试描述一下我们认为的在符合这个标签的架构中,它们所具有的一些共同特征。与概述共同特征的任何定义一样,并非所有微服务架构都具有所有特征,但我们确实期望大多数微服务架构都具有大多数特征。虽然我们的作者一直是这个相当宽松的社区的活跃成员,但我们的本意还是尝试描述我们两人在自己和自己所了解的团队的工作中所看到的情况。特别要说明的是,我们没有制定一些相关的定义。 ### 通过服务进行组件化 + 只要我们参与软件行业,就一直希望通过将组件集成在一起来构建系统,就像我们在物理世界中看到的事物的构建方式一样。在过去的几十年中,我们已经看到了大多数语言平台的公共软件库都取得了极大的进展。 在谈论组件时,就会碰到一个有关定义的难题,即什么是组件?我们的定义是,组件是可独立更换和升级的软件单元。 @@ -38,21 +41,23 @@ 近似地,我们可以把一个个服务映射为一个个运行时进程,但这仅仅是一个近似而已。一个服务可能包括多个始终一起开发和部署的进程,比如一个应用系统的进程和仅由该服务使用的数据库。 ### 围绕业务能力进行组织 + 在将大型应用程序拆分为多个部分时,管理层往往侧重于技术层面,从而导致 UI 团队、服务器端逻辑团队、数据库团队的划分。当团队按照这些方式分开时,即便是简单的更改也可能导致跨团队项目的时间和预算批准。一个聪明的团队将围绕这个进行优化,“两害相权取其轻”——只需将逻辑强制应用到他们可以访问的任何应用程序中。换句话说,逻辑无处不在。这是康威定律[5]的一个例子。 > 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。
—— 梅尔文•康威,1967年 -![conways-law](/images/conways-law.png) +![conways-law](./images/conways-law.png) 微服务采用不同的划分方式,它是围绕业务功能将系统拆分为多个服务 。这些服务为该业务领域采用广泛的软件实现,包括用户界面、持久化存储和任何外部协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验、数据库和项目管理。 -![PreferFunctionalStaffOrganization](/images/PreferFunctionalStaffOrganization.png) +![PreferFunctionalStaffOrganization](./images/PreferFunctionalStaffOrganization.png) 以这种方式组建的一家公司是 [www.comparethemarket.com](http://www.comparethemarket.com/)。跨职能团队负责构建和运营每个产品,每个产品拆分为多个独立的服务,彼此通过消息总线来通信。 大型单体应用程序也可以围绕业务功能进行模块化,尽管这不是常见的情况。当然,我们会敦促构建单体应用系统的大型团队根据业务线来将自己分解为若干小团队。我们在这里看到的主要问题是,它们往往围绕太多的上下文进行组织。如果单体跨越了模块边界,对团队的个体成员来说,很难将它们装入短期的记忆中。此外,我们看到模块化生产线需要大量的规则来执行。服务组件所要求的更加明确的分离,使得它更容易保持团队边界清晰。 ### 是产品不是项目 + 我们看到的大多数应用程序开发工作都使用这样一个项目模式:目标是交付一些软件,然后就完工了。一旦完成后,软件将移交给维护组织,然后构建它的项目团队也随之解散了。 微服务支持者倾向于避免这种模式,而是认为团队应该负责产品的整个生命周期。对此一个共同的启示是亚马逊的 [“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,13 +99,14 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形 也许去中心化治理的最高境界就是亚马逊广为流传的 build it/run it 理念。团队要对他们构建的软件的各方面负责,包括 7*24 小时的运营。这一级别的责任下放绝对是不规范的,但我们看到越来越多的公司让开发团队负起更多责任。Netflix 是采用这一理念的另一家公司[11]。每天凌晨 3 点被传呼机叫醒无疑是一个强有力的激励,使你在写代码时关注质量。这是关于尽可能远离传统的集中治理模式的一些想法。 ### 分散数据管理 + 数据管理的去中心化有许多不同的呈现方式。在最抽象的层面上,这意味着使系统间存在差异的世界概念模型。在整合一个大型企业时,客户的销售视图将不同于支持视图,这是一个常见的问题。客户的销售视图中的一些事情可能不会出现在支持视图中。它们确实可能有不同的属性和(更坏的)共同属性,这些共同属性在语义上有微妙的不同。 这个问题常见于应用程序之间,但也可能发生在应用程序内部,尤其当应用程序被划分成分离的组件时。一个有用的思维方式是[有界上下文](http://martinfowler.com/bliki/BoundedContext.html)(Bounded Context)内的领域驱动设计(Domain-Driven Design, DDD)理念。DDD 把一个复杂域划分成多个有界的上下文,并且映射出它们之间的关系。这个过程对单体架构和微服务架构都是有用的,但在服务和上下文边界间有天然的相关性,边界有助于澄清和加强分离,就像业务能力部分描述的那样。 和概念模型的去中心化决策一样,微服务也去中心化数据存储决策。虽然单体应用程序更喜欢单一的逻辑数据库做持久化存储,但企业往往倾向于一系列应用程序共用一个单一的数据库——这些决定是供应商授权许可的商业模式驱动的。微服务更倾向于让每个服务管理自己的数据库,或者同一数据库技术的不同实例,或完全不同的数据库系统 - 这就是所谓的[混合持久化](https://martinfowler.com/bliki/PolyglotPersistence.html)(Polyglot Persistence)。你可以在单体应用程序中使用混合持久化,但它更常出现在为服务里。 -![decentralised-data](/images/decentralised-data.png) +![decentralised-data](./images/decentralised-data.png) 对跨微服务的数据来说,去中心化责任对管理升级有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这个方法通常用在单体中。 @@ -107,11 +115,12 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形 对很多开发团队来说,选择用这样的方式管理不一致性是一个新的挑战,但这通常与业务实践相匹配。通常业务处理一定程度的不一致,以快速响应需求,同时有某些类型的逆转过程来处理错误。这种权衡是值得的,只要修复错误的代价小于更大一致性下损失业务的代价。 ### 基建自动化 + 基础设施自动化技术在过去几年中发生了巨大变化——特别是云和 AWS 的发展降低了构建、部署和运行微服务的操作复杂性。 许多使用微服务构建的产品或系统都是由具有丰富的持续交付和持续集成经验的团队构建的。以这种方式构建软件的团队广泛使用基础设施自动化技术。如下面显示的构建管道所示。 -![basic-pipeline](/images/basic-pipeline.png) +![basic-pipeline](./images/basic-pipeline.png) 由于这并不是一篇关于持续交付的文章,我们在这里只关注持续交付的几个关键特性。我们希望有尽可能多的信心确保我们的软件正常运行,因此我们进行了大量的**自动化测试**。想让软件达到“晋级”(Promotion)状态从而“推上”流水线,就意味着要在每一个新的环境中,对软件进行**自动化部署**。 @@ -119,9 +128,10 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形 我们看到团队大量的基础设施自动化的另一个领域是在管理生产环境中的微服务。与我们上面的断言(只要部署很无聊)相比,单体和微服务之间没有太大的区别,但是每个部署的运行环境可能会截然不同。 -![micro-deployment](/images/micro-deployment.png) +![micro-deployment](./images/micro-deployment.png) ### 设计时为故障做好准备 + 使用服务作为组件的结果是,需要设计应用程序以便它们能够容忍服务的失败。如果服务提供者商不可用,任何服务呼叫都可能失败,客户必须尽可能优雅地对此做出响应。与单体设计相比,这是一个缺点,因为它这会引入额外的复杂性来处理它。结果是微服务团队不断反思服务失败是如何影响用户体验的。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. diff --git a/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md b/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md index 8db028c..b11ad56 100644 --- a/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md +++ b/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md @@ -8,8 +8,7 @@ 相反,应该采取逐步迁移单体式应用的策略,通过逐步生成微服务新应用,与旧的单体式应用集成,随着时间推移,单体式应用在整个架构中比例逐渐下降直到消失或者成为微服务架构一部分。这个策略有点像在高速路上限速到 70 迈对车做维护,尽管有挑战,但是比起重写的风险小很多。 -Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字来源于雨林中的绞杀藤(strangler vine),也叫绞杀榕 (strangler fig)。绞杀藤为了爬到森林顶端都要缠绕着大叔生长,一段时间后,树死了,留下树形藤。这种应用也使用同一种模式,围绕着传统应用开发了新型微服务应用,传统应用会渐渐退出舞台。 - +Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字来源于雨林中的绞杀藤(strangler vine),也叫绞杀榕 (strangler fig)。绞杀藤为了爬到森林顶端都要缠绕着大树生长,一段时间后,树死了,留下树形藤。这种应用也使用同一种模式,围绕着传统应用开发了新型微服务应用,传统应用会渐渐退出舞台。 我们来看看其他可行策略。 @@ -17,7 +16,7 @@ Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字 Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用不可管理时这是最佳建议。换句话说,应该停止让单体式应用继续变大,也就是说当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。如下图所示: -![1](/images/Law-of-Holes.png) +![1](./images/Law-of-Holes.png) 除了新服务和传统应用,还有两个模块,其一是请求路由器,负责处理入口(http)请求,有点像之前提到的 API 网关。路由器将新功能请求发送给新开发的服务,而将传统请求还发给单体式应用。 @@ -25,9 +24,9 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 微服务有三种方式访问单体应用数据: -- 换气单体应用提供的远程 API -- 直接访问单体应用数据库 -- 自己维护一份从单体应用中同步的数据 +* 换气单体应用提供的远程 API +* 直接访问单体应用数据库 +* 自己维护一份从单体应用中同步的数据 胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 *Domain Driven Design*,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。 @@ -46,7 +45,8 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 3. 数据访问层——访问基础元素,例如数据库和消息代理。 在表现层与业务数据访问层之间有清晰的隔离。业务层有由若干方面组成的粗粒度(coarse-grained)的 API,内部包含了业务逻辑元素。API 是可以将单体业务分割成两个更小应用的天然边界,其中一个应用是表现层,另外一个是业务和数据访问逻辑。分割后,表现逻辑应用远程调用业务逻辑应用,下图表示迁移前后架构不同:​ -![2](/images/Before-and-after-migration.png) + +![2](./images/Before-and-after-migration.png) 单体应用这么分割有两个好处,其一使得应用两部分开发、部署和扩展各自独立,特别地,允许表现层开发者在用户界面上快速选择,进行 A/B 测试;其二,使得一些远程 API 可以被微服务调用。 @@ -72,7 +72,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 一旦完成粗粒度接口,也就将此模块转换成独立微服务。为了实现,必须写代码使得单体应用和微服务之间通过使用进程间通信(IPC)机制的 API 来交换信息。如图所示迁移前后对比: -![3](/images/30103116_ZCcM.png) +![3](./images/30103116_ZCcM.png) 此例中,正在使用 Y 模块的 Z 模块是备选抽取模块,其元素正在被 X 模块使用,迁移第一步就是定义一套粗粒度 APIs,第一个接口应该是被 X 模块使用的内部接口,用于激活 Z 模块;第二个接口是被 Z 模块使用的外部接口,用于激活 Y 模块。 diff --git a/images/220px-Internet_dog.jpg b/images/220px-Internet_dog.jpg deleted file mode 100644 index 676cc28..0000000 Binary files a/images/220px-Internet_dog.jpg and /dev/null differ diff --git a/images/30103116_ZCcM.png b/images/30103116_ZCcM.png deleted file mode 100644 index 474a8a8..0000000 Binary files a/images/30103116_ZCcM.png and /dev/null differ diff --git a/images/BRP.jpg b/images/BRP.jpg deleted file mode 100644 index a3c4602..0000000 Binary files a/images/BRP.jpg and /dev/null differ diff --git a/images/Before-and-after-migration.png b/images/Before-and-after-migration.png deleted file mode 100644 index 3ec40a8..0000000 Binary files a/images/Before-and-after-migration.png and /dev/null differ diff --git a/images/Credit-Reserved-Event.png b/images/Credit-Reserved-Event.png deleted file mode 100644 index 02f93ec..0000000 Binary files a/images/Credit-Reserved-Event.png and /dev/null differ diff --git a/images/Event-sourcing.png b/images/Event-sourcing.png deleted file mode 100644 index 6ea8016..0000000 Binary files a/images/Event-sourcing.png and /dev/null differ diff --git a/images/Homogenizer-mode.jpg b/images/Homogenizer-mode.jpg deleted file mode 100644 index ecf35bf..0000000 Binary files a/images/Homogenizer-mode.jpg and /dev/null differ diff --git a/images/Law-of-Holes.png b/images/Law-of-Holes.png deleted file mode 100644 index 696fc25..0000000 Binary files a/images/Law-of-Holes.png and /dev/null differ diff --git a/images/No-2PC-required.png b/images/No-2PC-required.png deleted file mode 100644 index 7be7d25..0000000 Binary files a/images/No-2PC-required.png and /dev/null differ diff --git a/images/Order-Created-Event.png b/images/Order-Created-Event.png deleted file mode 100644 index 712fa91..0000000 Binary files a/images/Order-Created-Event.png and /dev/null differ diff --git a/images/PreferFunctionalStaffOrganization.png b/images/PreferFunctionalStaffOrganization.png deleted file mode 100644 index e7f5a6f..0000000 Binary files a/images/PreferFunctionalStaffOrganization.png and /dev/null differ diff --git a/images/Private-table-of-the-corresponding-service.png b/images/Private-table-of-the-corresponding-service.png deleted file mode 100644 index 9a2ac47..0000000 Binary files a/images/Private-table-of-the-corresponding-service.png and /dev/null differ diff --git a/images/Sentinel-Dashboard.jpg b/images/Sentinel-Dashboard.jpg deleted file mode 100644 index c8bfc21..0000000 Binary files a/images/Sentinel-Dashboard.jpg and /dev/null differ diff --git a/images/Sentinel-VS-Hystrix.jpg b/images/Sentinel-VS-Hystrix.jpg deleted file mode 100644 index 43f9492..0000000 Binary files a/images/Sentinel-VS-Hystrix.jpg and /dev/null differ diff --git a/images/Slow-Start-Preheating-Mode.jpg b/images/Slow-Start-Preheating-Mode.jpg deleted file mode 100644 index 9a3af1a..0000000 Binary files a/images/Slow-Start-Preheating-Mode.jpg and /dev/null differ diff --git a/images/Status-is-OPEN.png b/images/Status-is-OPEN.png deleted file mode 100644 index 8e75eaf..0000000 Binary files a/images/Status-is-OPEN.png and /dev/null differ diff --git a/images/advanced-java-doocs-shishan.png b/images/advanced-java-doocs-shishan.png deleted file mode 100644 index 6d3e138..0000000 Binary files a/images/advanced-java-doocs-shishan.png and /dev/null differ diff --git a/images/article-demo.png b/images/article-demo.png deleted file mode 100644 index aba5bd7..0000000 Binary files a/images/article-demo.png and /dev/null differ diff --git a/images/async-replication-data-lose-case.png b/images/async-replication-data-lose-case.png deleted file mode 100644 index 945add7..0000000 Binary files a/images/async-replication-data-lose-case.png and /dev/null differ diff --git a/images/basic-pipeline.png b/images/basic-pipeline.png deleted file mode 100644 index 4d9fa62..0000000 Binary files a/images/basic-pipeline.png and /dev/null differ diff --git a/images/bulkhead-partition.jpg b/images/bulkhead-partition.jpg deleted file mode 100644 index c35653e..0000000 Binary files a/images/bulkhead-partition.jpg and /dev/null differ diff --git a/images/consistent-hashing-algorithm.png b/images/consistent-hashing-algorithm.png deleted file mode 100644 index ed5bcf3..0000000 Binary files a/images/consistent-hashing-algorithm.png and /dev/null differ diff --git a/images/conways-law.png b/images/conways-law.png deleted file mode 100644 index 5956773..0000000 Binary files a/images/conways-law.png and /dev/null differ diff --git a/images/database-id-sequence-step.png b/images/database-id-sequence-step.png deleted file mode 100644 index 3cf6ae3..0000000 Binary files a/images/database-id-sequence-step.png and /dev/null differ diff --git a/images/database-shard-method-1.png b/images/database-shard-method-1.png deleted file mode 100644 index 6e96b15..0000000 Binary files a/images/database-shard-method-1.png and /dev/null differ diff --git a/images/database-shard-method-2.png b/images/database-shard-method-2.png deleted file mode 100644 index d925680..0000000 Binary files a/images/database-shard-method-2.png and /dev/null differ diff --git a/images/database-split-horizon.png b/images/database-split-horizon.png deleted file mode 100644 index bb4fde6..0000000 Binary files a/images/database-split-horizon.png and /dev/null differ diff --git a/images/database-split-vertically.png b/images/database-split-vertically.png deleted file mode 100644 index a6fa220..0000000 Binary files a/images/database-split-vertically.png and /dev/null differ diff --git a/images/decentralised-data.png b/images/decentralised-data.png deleted file mode 100644 index c2b8519..0000000 Binary files a/images/decentralised-data.png and /dev/null differ diff --git a/images/deployment-strategy-1.png b/images/deployment-strategy-1.png deleted file mode 100644 index 329c149..0000000 Binary files a/images/deployment-strategy-1.png and /dev/null differ diff --git a/images/deployment-strategy-2.png b/images/deployment-strategy-2.png deleted file mode 100644 index f4a471e..0000000 Binary files a/images/deployment-strategy-2.png and /dev/null differ diff --git a/images/deployment-strategy-3.png b/images/deployment-strategy-3.png deleted file mode 100644 index ae54919..0000000 Binary files a/images/deployment-strategy-3.png and /dev/null differ diff --git a/images/distributed-system-request-sequence.png b/images/distributed-system-request-sequence.png deleted file mode 100644 index 31de2c5..0000000 Binary files a/images/distributed-system-request-sequence.png and /dev/null differ diff --git a/images/distributed-transaction-TCC.png b/images/distributed-transaction-TCC.png deleted file mode 100644 index 96dc37b..0000000 Binary files a/images/distributed-transaction-TCC.png and /dev/null differ diff --git a/images/distributed-transaction-XA.png b/images/distributed-transaction-XA.png deleted file mode 100644 index f24a62f..0000000 Binary files a/images/distributed-transaction-XA.png and /dev/null differ diff --git a/images/distributed-transaction-local-message-table.png b/images/distributed-transaction-local-message-table.png deleted file mode 100644 index 385e7e3..0000000 Binary files a/images/distributed-transaction-local-message-table.png and /dev/null differ diff --git a/images/distributed-transaction-reliable-message.png b/images/distributed-transaction-reliable-message.png deleted file mode 100644 index f9588d6..0000000 Binary files a/images/distributed-transaction-reliable-message.png and /dev/null differ diff --git a/images/doocs.png b/images/doocs.png deleted file mode 100644 index d7d9d60..0000000 Binary files a/images/doocs.png and /dev/null differ diff --git a/images/dubbo-keep-connection.png b/images/dubbo-keep-connection.png deleted file mode 100644 index 70993f9..0000000 Binary files a/images/dubbo-keep-connection.png and /dev/null differ diff --git a/images/dubbo-not-keep-connection.png b/images/dubbo-not-keep-connection.png deleted file mode 100644 index e666976..0000000 Binary files a/images/dubbo-not-keep-connection.png and /dev/null differ diff --git a/images/dubbo-operating-principle.png b/images/dubbo-operating-principle.png deleted file mode 100644 index 259d96d..0000000 Binary files a/images/dubbo-operating-principle.png and /dev/null differ diff --git a/images/dubbo-service-invoke-road.png b/images/dubbo-service-invoke-road.png deleted file mode 100644 index 97bbaf9..0000000 Binary files a/images/dubbo-service-invoke-road.png and /dev/null differ diff --git a/images/dubbo-spi.png b/images/dubbo-spi.png deleted file mode 100644 index 0eacf57..0000000 Binary files a/images/dubbo-spi.png and /dev/null differ diff --git a/images/e-commerce-website-detail-page-architecture-1.png b/images/e-commerce-website-detail-page-architecture-1.png deleted file mode 100644 index 0e1565c..0000000 Binary files a/images/e-commerce-website-detail-page-architecture-1.png and /dev/null differ diff --git a/images/e-commerce-website-detail-page-architecture-2.png b/images/e-commerce-website-detail-page-architecture-2.png deleted file mode 100644 index a5f1bb4..0000000 Binary files a/images/e-commerce-website-detail-page-architecture-2.png and /dev/null differ diff --git a/images/es-cluster-0.png b/images/es-cluster-0.png deleted file mode 100644 index 3ecdcac..0000000 Binary files a/images/es-cluster-0.png and /dev/null differ diff --git a/images/es-cluster.png b/images/es-cluster.png deleted file mode 100644 index 8ead4d1..0000000 Binary files a/images/es-cluster.png and /dev/null differ diff --git a/images/es-index-type-mapping-document-field.png b/images/es-index-type-mapping-document-field.png deleted file mode 100644 index c582a3e..0000000 Binary files a/images/es-index-type-mapping-document-field.png and /dev/null differ diff --git a/images/es-search-process.png b/images/es-search-process.png deleted file mode 100644 index 6066998..0000000 Binary files a/images/es-search-process.png and /dev/null differ diff --git a/images/es-write-detail.png b/images/es-write-detail.png deleted file mode 100644 index 4f08a26..0000000 Binary files a/images/es-write-detail.png and /dev/null differ diff --git a/images/es-write.png b/images/es-write.png deleted file mode 100644 index d34db99..0000000 Binary files a/images/es-write.png and /dev/null differ diff --git a/images/get-up-and-study.png b/images/get-up-and-study.png deleted file mode 100644 index f1cfe80..0000000 Binary files a/images/get-up-and-study.png and /dev/null differ diff --git a/images/hash-slot.png b/images/hash-slot.png deleted file mode 100644 index 5bdca39..0000000 Binary files a/images/hash-slot.png and /dev/null differ diff --git a/images/hash.png b/images/hash.png deleted file mode 100644 index a5ce518..0000000 Binary files a/images/hash.png and /dev/null differ diff --git a/images/high-concurrency-system-design.png b/images/high-concurrency-system-design.png deleted file mode 100644 index fc8ba7c..0000000 Binary files a/images/high-concurrency-system-design.png and /dev/null differ diff --git a/images/hystrix-process.png b/images/hystrix-process.png deleted file mode 100644 index dfb4ba7..0000000 Binary files a/images/hystrix-process.png and /dev/null differ diff --git a/images/hystrix-request-cache.png b/images/hystrix-request-cache.png deleted file mode 100644 index e0f8fbf..0000000 Binary files a/images/hystrix-request-cache.png and /dev/null differ diff --git a/images/hystrix-semphore-thread-pool.png b/images/hystrix-semphore-thread-pool.png deleted file mode 100644 index 13e382d..0000000 Binary files a/images/hystrix-semphore-thread-pool.png and /dev/null differ diff --git a/images/hystrix-semphore.png b/images/hystrix-semphore.png deleted file mode 100644 index 9ca8194..0000000 Binary files a/images/hystrix-semphore.png and /dev/null differ diff --git a/images/hystrix-thread-pool-isolation.png b/images/hystrix-thread-pool-isolation.png deleted file mode 100644 index 1929515..0000000 Binary files a/images/hystrix-thread-pool-isolation.png and /dev/null differ diff --git a/images/hystrix-thread-pool-queue.png b/images/hystrix-thread-pool-queue.png deleted file mode 100644 index 914e450..0000000 Binary files a/images/hystrix-thread-pool-queue.png and /dev/null differ diff --git a/images/kafka-after.png b/images/kafka-after.png deleted file mode 100644 index 6ef557b..0000000 Binary files a/images/kafka-after.png and /dev/null differ diff --git a/images/kafka-before.png b/images/kafka-before.png deleted file mode 100644 index a537ad1..0000000 Binary files a/images/kafka-before.png and /dev/null differ diff --git a/images/kafka-order-01.png b/images/kafka-order-01.png deleted file mode 100644 index 5b3ea7d..0000000 Binary files a/images/kafka-order-01.png and /dev/null differ diff --git a/images/kafka-order-02.png b/images/kafka-order-02.png deleted file mode 100644 index 2159d54..0000000 Binary files a/images/kafka-order-02.png and /dev/null differ diff --git a/images/kafka-order-1.png b/images/kafka-order-1.png deleted file mode 100644 index 92cd4d1..0000000 Binary files a/images/kafka-order-1.png and /dev/null differ diff --git a/images/kafka-order-2.png b/images/kafka-order-2.png deleted file mode 100644 index 1b1fa9a..0000000 Binary files a/images/kafka-order-2.png and /dev/null differ diff --git a/images/micro-deployment.png b/images/micro-deployment.png deleted file mode 100644 index 55c710e..0000000 Binary files a/images/micro-deployment.png and /dev/null differ diff --git a/images/mq-1.png b/images/mq-1.png deleted file mode 100644 index 907c3da..0000000 Binary files a/images/mq-1.png and /dev/null differ diff --git a/images/mq-10.png b/images/mq-10.png deleted file mode 100644 index d522b07..0000000 Binary files a/images/mq-10.png and /dev/null differ diff --git a/images/mq-11.png b/images/mq-11.png deleted file mode 100644 index 6d03589..0000000 Binary files a/images/mq-11.png and /dev/null differ diff --git a/images/mq-2.png b/images/mq-2.png deleted file mode 100644 index 501f342..0000000 Binary files a/images/mq-2.png and /dev/null differ diff --git a/images/mq-3.png b/images/mq-3.png deleted file mode 100644 index 632ab71..0000000 Binary files a/images/mq-3.png and /dev/null differ diff --git a/images/mq-4.png b/images/mq-4.png deleted file mode 100644 index c4dd007..0000000 Binary files a/images/mq-4.png and /dev/null differ diff --git a/images/mq-5.png b/images/mq-5.png deleted file mode 100644 index cd2ac1a..0000000 Binary files a/images/mq-5.png and /dev/null differ diff --git a/images/mq-6.png b/images/mq-6.png deleted file mode 100644 index ff218dd..0000000 Binary files a/images/mq-6.png and /dev/null differ diff --git a/images/mq-7.png b/images/mq-7.png deleted file mode 100644 index 13d1921..0000000 Binary files a/images/mq-7.png and /dev/null differ diff --git a/images/mq-8.png b/images/mq-8.png deleted file mode 100644 index 3994fa6..0000000 Binary files a/images/mq-8.png and /dev/null differ diff --git a/images/multi-step-process.png b/images/multi-step-process.png deleted file mode 100644 index 41c0ea3..0000000 Binary files a/images/multi-step-process.png and /dev/null differ diff --git a/images/mysql-master-slave.png b/images/mysql-master-slave.png deleted file mode 100644 index d903097..0000000 Binary files a/images/mysql-master-slave.png and /dev/null differ diff --git a/images/owner-favicon-16x16.png b/images/owner-favicon-16x16.png deleted file mode 100644 index 116025c..0000000 Binary files a/images/owner-favicon-16x16.png and /dev/null differ diff --git a/images/owner-favicon-32x32.png b/images/owner-favicon-32x32.png deleted file mode 100644 index ec17515..0000000 Binary files a/images/owner-favicon-32x32.png and /dev/null differ diff --git a/images/pre-join.png b/images/pre-join.png deleted file mode 100644 index 1971edc..0000000 Binary files a/images/pre-join.png and /dev/null differ diff --git a/images/rabbitmq-message-lose-solution.png b/images/rabbitmq-message-lose-solution.png deleted file mode 100644 index 5ebd1af..0000000 Binary files a/images/rabbitmq-message-lose-solution.png and /dev/null differ diff --git a/images/rabbitmq-message-lose.png b/images/rabbitmq-message-lose.png deleted file mode 100644 index 4841731..0000000 Binary files a/images/rabbitmq-message-lose.png and /dev/null differ diff --git a/images/rabbitmq-order-01.png b/images/rabbitmq-order-01.png deleted file mode 100644 index f022e2d..0000000 Binary files a/images/rabbitmq-order-01.png and /dev/null differ diff --git a/images/rabbitmq-order-02.png b/images/rabbitmq-order-02.png deleted file mode 100644 index 1936f45..0000000 Binary files a/images/rabbitmq-order-02.png and /dev/null differ diff --git a/images/rabbitmq-order-1.png b/images/rabbitmq-order-1.png deleted file mode 100644 index 9335723..0000000 Binary files a/images/rabbitmq-order-1.png and /dev/null differ diff --git a/images/rabbitmq-order-2.png b/images/rabbitmq-order-2.png deleted file mode 100644 index c6dd540..0000000 Binary files a/images/rabbitmq-order-2.png and /dev/null differ diff --git a/images/redis-caching-avalanche-solution.png b/images/redis-caching-avalanche-solution.png deleted file mode 100644 index 205755d..0000000 Binary files a/images/redis-caching-avalanche-solution.png and /dev/null differ diff --git a/images/redis-caching-avalanche.png b/images/redis-caching-avalanche.png deleted file mode 100644 index c5ce411..0000000 Binary files a/images/redis-caching-avalanche.png and /dev/null differ diff --git a/images/redis-caching-penetration.png b/images/redis-caching-penetration.png deleted file mode 100644 index 5e3f740..0000000 Binary files a/images/redis-caching-penetration.png and /dev/null differ diff --git a/images/redis-cluster-split-brain.png b/images/redis-cluster-split-brain.png deleted file mode 100644 index ce6e09e..0000000 Binary files a/images/redis-cluster-split-brain.png and /dev/null differ diff --git a/images/redis-gossip.png b/images/redis-gossip.png deleted file mode 100644 index 38e7ff6..0000000 Binary files a/images/redis-gossip.png and /dev/null differ diff --git a/images/redis-junior-inconsistent.png b/images/redis-junior-inconsistent.png deleted file mode 100644 index 4489950..0000000 Binary files a/images/redis-junior-inconsistent.png and /dev/null differ diff --git a/images/redis-master-slave-replication-detail.png b/images/redis-master-slave-replication-detail.png deleted file mode 100644 index ad4eb80..0000000 Binary files a/images/redis-master-slave-replication-detail.png and /dev/null differ diff --git a/images/redis-master-slave-replication.png b/images/redis-master-slave-replication.png deleted file mode 100644 index 6144c24..0000000 Binary files a/images/redis-master-slave-replication.png and /dev/null differ diff --git a/images/redis-master-slave.png b/images/redis-master-slave.png deleted file mode 100644 index bc3a299..0000000 Binary files a/images/redis-master-slave.png and /dev/null differ diff --git a/images/redis-single-thread-model.png b/images/redis-single-thread-model.png deleted file mode 100644 index cf105f4..0000000 Binary files a/images/redis-single-thread-model.png and /dev/null differ diff --git a/images/serialize-deserialize.png b/images/serialize-deserialize.png deleted file mode 100644 index bf8e723..0000000 Binary files a/images/serialize-deserialize.png and /dev/null differ diff --git a/images/service-invoke-road.png b/images/service-invoke-road.png deleted file mode 100644 index 3a6cf5c..0000000 Binary files a/images/service-invoke-road.png and /dev/null differ diff --git a/images/simple-distributed-system-oa.png b/images/simple-distributed-system-oa.png deleted file mode 100644 index 05016eb..0000000 Binary files a/images/simple-distributed-system-oa.png and /dev/null differ diff --git a/images/sketch.png b/images/sketch.png deleted file mode 100644 index ac8827d..0000000 Binary files a/images/sketch.png and /dev/null differ diff --git a/images/wechat-group-for-doocs.png b/images/wechat-group-for-doocs.png deleted file mode 100644 index 2e0af3a..0000000 Binary files a/images/wechat-group-for-doocs.png and /dev/null differ diff --git a/images/where-is-my-offer.png b/images/where-is-my-offer.png deleted file mode 100644 index 8d2f844..0000000 Binary files a/images/where-is-my-offer.png and /dev/null differ diff --git a/images/zookeeper-active-standby.png b/images/zookeeper-active-standby.png deleted file mode 100644 index 9c6e8ac..0000000 Binary files a/images/zookeeper-active-standby.png and /dev/null differ diff --git a/images/zookeeper-centralized-storage.png b/images/zookeeper-centralized-storage.png deleted file mode 100644 index 925b5ed..0000000 Binary files a/images/zookeeper-centralized-storage.png and /dev/null differ diff --git a/images/zookeeper-distributed-coordination.png b/images/zookeeper-distributed-coordination.png deleted file mode 100644 index ad43c06..0000000 Binary files a/images/zookeeper-distributed-coordination.png and /dev/null differ diff --git a/images/zookeeper-distributed-lock-demo.png b/images/zookeeper-distributed-lock-demo.png deleted file mode 100644 index e33bfa3..0000000 Binary files a/images/zookeeper-distributed-lock-demo.png and /dev/null differ diff --git a/images/zookeeper-distributed-lock.png b/images/zookeeper-distributed-lock.png deleted file mode 100644 index 898361b..0000000 Binary files a/images/zookeeper-distributed-lock.png and /dev/null differ diff --git a/images/zookeeper-meta-data-manage.png b/images/zookeeper-meta-data-manage.png deleted file mode 100644 index d81bb20..0000000 Binary files a/images/zookeeper-meta-data-manage.png and /dev/null differ diff --git a/index.html b/index.html index 1178b26..4141baf 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,7 @@ + @@ -21,7 +22,6 @@
  • 高可用
  • 微服务
  • 海量数据
  • -
  • 读者说
  • 页面 @@ -30,8 +30,6 @@
  • 首页
  • 进阶
  • Offer
  • -
  • Doocs
  • -
  • Author
  • @@ -46,19 +44,56 @@ auto2top: true, coverpage: true, coverpage: 'docs/extra-page/cover.md', - search: [ - '/' - ], + loadSidebar: 'summary.md', + alias: { + '/.*/.*/summary': 'summary.md', + '/.*/summary.md': 'summary.md' + }, + pagination: { + previousText: '上一篇', + nextText: '下一篇', + crossChapter: true, + crossChapterText: true + }, + search: { + maxAge: 1800000, + paths: [ + '/docs/high-concurrency/', + '/docs/distributed-system/', + '/docs/high-availability/', + '/docs/micor-services/', + '/docs/big-data/', + ], + depth: 3 + }, + darkMode: { + light: { + toggleBtnBg: '#42b983' + } + }, plugins: [ - function (hook) { - var footer = [ - '
    ', - '' - ].join('') + function (hook, vm) { + hook.beforeEach(function (html) { + const en = vm.route.file.indexOf('README_EN') > -1 + if (/githubusercontent\.com/.test(vm.route.file)) { + url = vm.route.file + .replace('raw.githubusercontent.com', 'github.com') + .replace(/\/master/, '/blob/master') + } else { + url = 'https://github.com/doocs/advanced-java/blob/master/' + vm.route.file + } + const edit = en ? 'Edit on GitHub' : '在 GitHub 编辑' + const editHtml = `[:memo: ${edit}](${url})\n` + return editHtml + html + }) hook.afterEach(function (html) { + const footer = [ + '
    ', + '
    ', + 'Copyright © 2018-2020 Doocs. All rights reserved.', + '
    ' + ].join('') return html + footer }) } @@ -76,5 +111,7 @@ + + \ No newline at end of file diff --git a/summary.md b/summary.md new file mode 100644 index 0000000..25ffec8 --- /dev/null +++ b/summary.md @@ -0,0 +1,125 @@ +- 高并发架构 + + - [消息队列](./docs/high-concurrency/mq-interview.md) + - [为什么使用消息队列?](./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 的分布式架构原理是什么?](./docs/high-concurrency/es-architecture.md) + - [ES 写入数据的工作原理是什么?](./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 有什么区别?](./docs/high-concurrency/redis-single-thread-model.md) + - [Redis 都有哪些数据类型以及适用场景?](./docs/high-concurrency/redis-data-types.md) + - [Redis 的过期策略都有哪些?](./docs/high-concurrency/redis-expiration-policies-and-lru.md) + - [如何保证 Redis 高并发、高可用?](./docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md) + - [Redis 的持久化有哪几种方式?](./docs/high-concurrency/redis-persistence.md) + - [Redis 集群模式的工作原理能说一下么?](./docs/high-concurrency/redis-cluster.md) + - [Redis 的雪崩、穿透和击穿,如何应对?](./docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) + - [如何保证缓存与数据库双写一致性?](./docs/high-concurrency/redis-consistence.md) + - [如何解决 Redis 的并发竞争问题?](./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) + + - 读写分离 + - [如何实现 MySQL 的读写分离?](./docs/high-concurrency/mysql-read-write-separation.md) + + - 高并发系统 + - [如何设计一个高并发系统?](./docs/high-concurrency/high-concurrency-design.md) + +* 分布式系统 + - [面试连环炮](./docs/distributed-system/distributed-system-interview.md) + - 系统拆分 + - [为什么要进行系统拆分?](./docs/distributed-system/why-dubbo.md) + + - 分布式服务框架 + - [说一下 Dubbo 的工作原理?](./docs/distributed-system/dubbo-operating-principle.md) + - [Dubbo 支持哪些序列化协议?](./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) + - [分布式锁如何设计?](./docs/distributed-system/distributed-lock-redis-vs-zookeeper.md) + + - 分布式事务 + - [分布式事务了解吗?](./docs/distributed-system/distributed-transaction.md) + + - 分布式会话 + - [集群分布式 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) + - [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) + + - 熔断 + - 如何进行熔断? + - 熔断框架都有哪些?具体实现原理知道吗? + - [熔断框架,选用 Sentinel 还是 Hystrix?](./docs/high-availability/sentinel-vs-hystrix.md) + + - 降级 + - 如何进行降级? + +* 微服务架构 + - 微服务的一些概念 + - [关于微服务架构的描述](./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) + +* 海量数据处理 + - 10 道经典的海量数据处理面试题 + - [如何从大量的 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)