diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..8ac19d6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: advanced-java +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with a single custom sponsorship URL diff --git a/LICENSE b/LICENSE index eecb76b..3b7b82d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,427 @@ -# Creative Commons Attribution-NonCommercial 4.0 International License +Attribution-ShareAlike 4.0 International -Disclaimer: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-nc/4.0/legalcode). +======================================================================= -You are free to: +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. -- Share — copy and redistribute the material in any medium or format -- Adapt — remix, transform, and build upon the material +Using Creative Commons Public Licenses -The licensor cannot revoke these freedoms as long as you follow the license terms. +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. -Under the following terms: + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors -- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. -- NonCommercial — You may not use the material for commercial purposes. -- No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees -Notices: +======================================================================= -You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. +Creative Commons Attribution-ShareAlike 4.0 International Public +License -No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/README.md b/README.md index ca92962..e886e6c 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,26 @@ -# 互联网 Java 工程师进阶知识完全扫盲 +# 互联网 Java 工程师进阶知识完全扫盲[©](https://github.com/doocs/advanced-java) +[![license](https://badgen.net/badge/license/CC-BY-SA%204.0/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)](https://github.com/doocs/advanced-java/issues/70) +[![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) +[![stars](https://badgen.net/github/stars/doocs/advanced-java)](https://github.com/doocs/advanced-java/stargazers) +[![forks](https://badgen.net/github/forks/doocs/advanced-java)](https://github.com/doocs/advanced-java/network/members) +[![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) -[![license](https://img.shields.io/badge/license-Attribution--NonCommercial%204.0%20-brightgreen.svg)](https://github.com/doocs/advanced-java/blob/master/LICENSE) -[![original](https://img.shields.io/badge/original-%E4%B8%AD%E5%8D%8E%E7%9F%B3%E6%9D%89-orange.svg)](https://github.com/doocs/advanced-java) -[![stars](https://img.shields.io/github/stars/doocs/advanced-java.svg)](https://github.com/doocs/advanced-java/stargazers) -[![forks](https://img.shields.io/github/forks/doocs/advanced-java.svg)](https://github.com/doocs/advanced-java/network/members) -[![issues](https://img.shields.io/github/issues/doocs/advanced-java.svg)](https://github.com/doocs/advanced-java/issues) -[![PRs Welcome](https://img.shields.io/badge/PRs-Welcome-brightgreen.svg)](http://makeapullrequest.com) +本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)等领域知识。我对这部分知识做了一个系统的整理,方便学习查阅。配合《[大型网站技术架构](https://github.com/doocs/technical-books#architecture)——李智慧》、《[Redis 设计与实现](https://github.com/doocs/technical-books#database)——[黄健宏](https://github.com/huangz1990)》食用,[效果更佳](https://doocs.gitee.io/advanced-java/#/offer)。 -本系列知识出自中华石杉,我对这部分知识做了一个系统的整理,方便学习查阅。By the way,微信公众号**石杉的架构笔记**(id:shishan100)有其它很多架构知识,墙裂推荐~ - -一点小建议:学习本系列知识之前,如果你完全没接触过 `MQ`、`ES`、`Redis`、`Dubbo`、`Hystrix` 等,那么我建议你可以先在网上搜一下每一块知识的快速入门,跟着入门 Demo 玩一下,然后再开始每一块知识的学习,这样效果更好噢~ +学习之前,先来看看 [Issues 讨论区](https://github.com/doocs/advanced-java/issues/9#issue-394275038)的技术面试官是怎么说的吧。本项目也欢迎各位开发者朋友来[分享自己的一些想法和实践经验](/docs/from-readers/README.md)。 ## 高并发架构 - ### [消息队列](/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-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) @@ -36,7 +40,7 @@ - [如何保证 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) +- [了解什么是 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) @@ -54,7 +58,6 @@ - [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md) ## 分布式系统 - ### [面试连环炮](/docs/distributed-system/distributed-system-interview.md) ### 系统拆分 - [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md) @@ -67,7 +70,7 @@ - [如何基于 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 的 RPC 框架?](/docs/distributed-system/dubbo-rpc-design.md) ### 分布式锁 - [Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md) @@ -84,6 +87,13 @@ - [电商网站详情页系统架构](/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) ### 高可用系统 - 如何设计一个高可用系统? @@ -96,4 +106,18 @@ - 熔断框架都有哪些?具体实现原理知道吗? ### 降级 -- 如何进行降级? \ No newline at end of file +- 如何进行降级? + +## 微服务架构 +- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java) +- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md) + +### Spring Cloud 微服务架构 +- 什么是微服务?微服务之间是如何独立通讯的? +- Spring Cloud 和 Dubbo 有哪些区别? +- Spring Boot 和 Spring Cloud,谈谈你对它们的理解? +- 什么是服务熔断?什么是服务降级? +- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑? +- 你所知道的微服务技术栈都有哪些? +- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? +- ...... \ No newline at end of file diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..bcb7f0d --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,90 @@ +# 目录 + +* Part1 高并发架构 + * [1.1 消息队列](/docs/high-concurrency/mq-interview.md) + * [1.1.1 为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](/docs/high-concurrency/why-mq.md) + * [1.1.2 如何保证消息队列的高可用?](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md) + * [1.1.3 如何保证消息不被重复消费?(如何保证消息消费的幂等性)](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md) + * [1.1.4 如何保证消息的可靠性传输?(如何处理消息丢失的问题)](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md) + * [1.1.5 如何保证消息的顺序性?](/docs/high-concurrency/how-to-ensure-the-order-of-messages.md) + * [1.1.6 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](/docs/high-concurrency/mq-time-delay-and-expired-failure.md) + * [1.1.7 如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](/docs/high-concurrency/mq-design.md) + * [1.2 搜索引擎](/docs/high-concurrency/es-introduction.md) + * [1.2.1 es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)?](/docs/high-concurrency/es-architecture.md) + * [1.2.2 es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗?](/docs/high-concurrency/es-write-query-search.md) + * [1.2.3 es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](/docs/high-concurrency/es-optimizing-query-performance.md) + * [1.2.4 es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](/docs/high-concurrency/es-production-cluster.md) + * 1.3 缓存 + * [1.3.1 在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/docs/high-concurrency/why-cache.md) + * [1.3.2 Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](/docs/high-concurrency/redis-single-thread-model.md) + * [1.3.3 Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](/docs/high-concurrency/redis-data-types.md) + * [1.3.4 Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](/docs/high-concurrency/redis-expiration-policies-and-lru.md) + * [1.3.5 如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md) + * [1.3.6 Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](/docs/high-concurrency/redis-persistence.md) + * [1.3.7 Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](/docs/high-concurrency/redis-cluster.md) + * [1.3.8 了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) + * [1.3.9 如何保证缓存与数据库的双写一致性?](/docs/high-concurrency/redis-consistence.md) + * [1.3.10 Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](/docs/high-concurrency/redis-cas.md) + * [1.3.11 生产环境中的 Redis 是怎么部署的?](/docs/high-concurrency/redis-production-environment.md) + * 1.4 分库分表 + * [1.4.1 为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/docs/high-concurrency/database-shard.md) + * [1.4.2 现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](/docs/high-concurrency/database-shard-method.md) + * [1.4.3 如何设计可以动态扩容缩容的分库分表方案?](/docs/high-concurrency/database-shard-dynamic-expand.md) + * [1.4.4 分库分表之后,id 主键如何处理?](/docs/high-concurrency/database-shard-global-id-generate.md) + * 1.5 读写分离 + * [1.5.1 如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md) + * 1.6 高并发系统 + * [1.6.1 如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md) +* Part2 分布式系统 + * [2.1 面试连环炮](/docs/distributed-system/distributed-system-interview.md) + * 2.2 系统拆分 + * [2.2.1 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md) + * 2.3 分布式服务框架 + * [2.3.1 说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](/docs/distributed-system/dubbo-operating-principle.md) + * [2.3.2 Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](/docs/distributed-system/dubbo-serialization-protocol.md) + * [2.3.3 Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](/docs/distributed-system/dubbo-load-balancing.md) + * [2.3.4 Dubbo 的 spi 思想是什么?](/docs/distributed-system/dubbo-spi.md) + * [2.3.5 如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](/docs/distributed-system/dubbo-service-management.md) + * [2.3.6 分布式服务接口的幂等性如何设计(比如不能重复扣款)?](/docs/distributed-system/distributed-system-idempotency.md) + * [2.3.7 分布式服务接口请求的顺序性如何保证?](/docs/distributed-system/distributed-system-request-sequence.md) + * [2.3.8 如何自己设计一个类似 Dubbo 的 RPC 框架?](/docs/distributed-system/dubbo-rpc-design.md) + * 2.4 分布式锁 + * [2.4.1 Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md) + * [2.4.2 使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md) + * 2.5 分布式事务 + * [2.5.1 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md) + * 2.6 分布式会话 + * [2.6.1 集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.md) +* Part3 高可用架构 + * 3.1 Hystrix + * [3.1.1 Hystrix 介绍](/docs/high-availability/hystrix-introduction.md) + * [3.1.2 电商网站详情页系统架构](/docs/high-availability/e-commerce-website-detail-page-architecture.md) + * [3.1.3 Hystrix 线程池技术实现资源隔离](/docs/high-availability/hystrix-thread-pool-isolation.md) + * [3.1.4 Hystrix 信号量机制实现资源隔离](/docs/high-availability/hystrix-semphore-isolation.md) + * [3.1.5 Hystrix 隔离策略细粒度控制](/docs/high-availability/hystrix-execution-isolation.md) + * [3.1.6 深入 Hystrix 执行时内部原理](/docs/high-availability/hystrix-process.md) + * [3.1.7 基于 request cache 请求缓存技术优化批量商品数据查询接口](/docs/high-availability/hystrix-request-cache.md) + * [3.1.8 基于本地缓存的 fallback 降级机制](/docs/high-availability/hystrix-fallback.md) + * [3.1.9 深入 Hystrix 断路器执行原理](/docs/high-availability/hystrix-circuit-breaker.md) + * [3.1.10 深入 Hystrix 线程池隔离与接口限流](/docs/high-availability/hystrix-thread-pool-current-limiting.md) + * [3.1.11 基于 timeout 机制为服务接口调用超时提供安全保护](/docs/high-availability/hystrix-timeout.md) + * 3.2 高可用系统 + * 3.2.1 如何设计一个高可用系统? + * 3.3 限流 + * 3.3.1 如何限流?在工作中是怎么做的?说一下具体的实现? + * 3.4 熔断 + * 3.4.1 如何进行熔断? + * 3.4.2 熔断框架都有哪些?具体实现原理知道吗? + * 3.5 降级 + * 3.5.1 如何进行降级? +* Part4 微服务架构 + * [4.1 关于微服务架构的描述](/docs/micro-services/microservices-introduction.md) + * 4.2 Spring Cloud 微服务架构 + * 4.2.1 什么是微服务?微服务之间是如何独立通讯的? + * 4.2.2 Spring Cloud 和 Dubbo 有哪些区别? + * 4.2.3 Spring Boot 和 Spring Cloud,谈谈你对它们的理解? + * 4.2.4 什么是服务熔断?什么是服务降级? + * 4.2.5 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑? + * 4.2.6 你所知道的微服务技术栈都有哪些? + * 4.2.7 Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? + * 4.2.8 ...... \ No newline at end of file diff --git a/_coverpage.md b/_coverpage.md index 95b94e1..6926870 100644 --- a/_coverpage.md +++ b/_coverpage.md @@ -1,8 +1,9 @@ -![logo](img/icon.png) +[![logo](images/icon.png)](https://github.com/doocs/advanced-java) -# Java 进阶面试 +# Java 进阶扫盲 -> 高并发 分布式 高可用 +> 高并发 分布式 高可用 微服务 -[GitHub](https://github.com/doocs/advanced-java/) +[Doocs](https://github.com/doocs/intro) +[GitHub](https://github.com/yanglbme) [Get Started](#互联网-java-工程师进阶知识完全扫盲) \ No newline at end of file diff --git a/_navbar.md b/_navbar.md index 76cd6a3..5287e2b 100644 --- a/_navbar.md +++ b/_navbar.md @@ -2,9 +2,13 @@ * [高并发](/README#高并发架构) * [分布式](/README#分布式系统) * [高可用](/README#高可用架构) + * [微服务](/README#微服务架构) + * [读者分享](/docs/from-readers/README) * 页面 * [封面]() * [首页](README) - * [GitHub](https://github.com/yanglbme) - * [Offer](offer) \ No newline at end of file + * [进阶](advanced) + * [Offer](offer) + * [Doocs](https://github.com/doocs/intro) + * [GitHub](https://github.com/yanglbme) \ No newline at end of file diff --git a/advanced.md b/advanced.md new file mode 100644 index 0000000..877cae1 --- /dev/null +++ b/advanced.md @@ -0,0 +1,72 @@ +

+

本单曲受版权保护,可点击观看 MV

+ + +> 受伤的得到疗愈,挣扎的得到出口
+*Let those who hurt heal, let those who struggle find hope*. + +``` +告别的时刻已到了 +随身的行囊 装些快乐 + +前方被乌云笼罩了 +心中的拉扯 困兽在低吼 + +隐隐约约的沉默 +透露一丝丝苦涩 +愿你不再被伤透 +别再带着泪入睡 + +曲曲折折的世界 +答案不一定绝对 +伤口因为爱壮烈 +让我们同步进阶 + +重生的力量来自真我 +战胜可敬的对手 yeah~ + +坚持信念是我的所有 +抵达心中的辽阔 yeah~ + +有失去的 该进阶的 +有遗憾的 该进阶的 +放不下的 该进阶的 yeah~ + +圣所的光芒 +指引方向 oh~ + +跨越了浩劫 +曙光渐亮 oh~ + +希望因挣扎破灭了 +再别无所求 于是自由 + +逆着风孤独的英雄 +也渴望停泊 可说不出口 + +隐隐约约的沉默 +透露一丝丝苦涩 +愿你不再被伤透 +别再带着泪入睡 + +曲曲折折的世界 +答案不一定绝对 +伤口因为爱壮烈 +让我们同步进阶 + +重生的力量来自真我 +战胜可敬的对手 yeah~ + +坚持信念是我的所有 +抵达心中的辽阔 yeah~ + +有失去的 该进阶的 +有遗憾的 该进阶的 +放不下的 该进阶的 yeah~ + +圣所的光芒 +指引方向 oh~ + +跨越了浩劫 +曙光渐亮 oh~ +``` \ No newline at end of file diff --git a/docs/distributed-system/README.md b/docs/distributed-system/README.md new file mode 100644 index 0000000..427c51a --- /dev/null +++ b/docs/distributed-system/README.md @@ -0,0 +1 @@ +# 分布式系统 \ 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 495ac97..751204a 100644 --- a/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md +++ b/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md @@ -2,7 +2,7 @@ 一般实现分布式锁都有哪些方式?使用 redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? ## 面试官心理分析 -其实一般问问题,都是这么问的,先问问你 zk,然后其实是要过度到 zk 关联的一些问题里去,比如分布式锁。因为在分布式系统开发中,分布式锁的使用场景还是很常见的。 +其实一般问问题,都是这么问的,先问问你 zk,然后其实是要过渡到 zk 相关的一些问题里去,比如分布式锁。因为在分布式系统开发中,分布式锁的使用场景还是很常见的。 ## 面试题剖析 ### redis 分布式锁 @@ -17,10 +17,11 @@ #### redis 最普通的分布式锁 -第一个最普通的实现方式,就是在 redis 里创建一个 key,这样就算加锁。 +第一个最普通的实现方式,就是在 redis 里使用 `setnx` 命令创建一个 key,这样就算加锁。 + ```r -SET my:lock 随机值 NX PX 30000 +SET resource_name my_random_value NX PX 30000 ``` 执行这个命令就 ok。 @@ -39,7 +40,7 @@ else end ``` -为啥要用随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 `lua` 脚本来释放锁。 +为啥要用 `random_value` 随机值呢?因为如果某个客户端获取到了锁,但是阻塞了很长时间才执行完,比如说超过了 30s,此时可能已经自动释放锁了,此时可能别的客户端已经获取到了这个锁,要是你这个时候直接删除 key 的话会有问题,所以得用随机值加上面的 `lua` 脚本来释放锁。 但是这样是肯定不行的。因为如果是普通的 redis 单实例,那就是单点故障。或者是 redis 普通主从,那 redis 主从异步复制,如果主节点挂了(key 就没有了),key 还没同步到从节点,此时从节点切换为主节点,别人就可以 set key,从而拿到锁。 @@ -53,7 +54,9 @@ end 5. 要是锁建立失败了,那么就依次之前建立过的锁删除; 6. 只要别人建立了一把分布式锁,你就得**不断轮询去尝试获取锁**。 -![redis-redlock](/img/redis-redlock.png) +![redis-redlock](/images/redis-redlock.png) + +[Redis 官方](https://redis.io/)给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看:https://redis.io/topics/distlock 。 ### zk 分布式锁 diff --git a/docs/distributed-system/distributed-session.md b/docs/distributed-system/distributed-session.md index 9f85494..a2b4b97 100644 --- a/docs/distributed-system/distributed-session.md +++ b/docs/distributed-system/distributed-session.md @@ -2,23 +2,26 @@ 集群部署时的分布式 session 如何实现? ## 面试官心理分析 -面试官问了你一堆 dubbo 是怎么玩儿的,你会玩儿 dubbo 就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最大的问题就是**分布式事务**、**接口幂等性**、**分布式锁**,还有最后一个就是**分布式session**。 +面试官问了你一堆 dubbo 是怎么玩儿的,你会玩儿 dubbo 就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最大的问题就是**分布式事务**、**接口幂等性**、**分布式锁**,还有最后一个就是**分布式 session**。 -当然了,分布式系统中的问题何止这么一点,非常之多,复杂度很高,但是这里就是说下常见的几个,也是面试的时候常问的几个。 +当然了,分布式系统中的问题何止这么一点,非常之多,复杂度很高,这里只是说一下常见的几个问题,也是面试的时候常问的几个。 ## 面试题剖析 session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 `jsessionid cookie`,就根据这个东西,在服务端可以维护一个对应的 session 域,里面可以放点数据。 -一般只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。 +一般的话只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。 这个不多说了,懂 Java 的都该知道这个。 单块系统的时候这么玩儿 session 没问题,但是你要是分布式系统呢,那么多的服务,session 状态在哪儿维护啊? -其实方法很多,但是常见常用的是两种: +其实方法很多,但是常见常用的是以下几种: + +### 完全不用 session +使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。 ### tomcat + redis -这个其实还挺方便的,就是使用 session 的代码跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署得 tomcat 都将 session 数据存储到 redis 即可。 +这个其实还挺方便的,就是使用 session 的代码,跟以前一样,还是基于 tomcat 原生的 session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 tomcat 都将 session 数据存储到 redis 即可。 在 tomcat 的配置文件中配置: @@ -45,12 +48,11 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存 还可以用上面这种方式基于 redis 哨兵支持的 redis 高可用集群来保存 session 数据,都是 ok 的。 ### spring session + redis - -第一种方式会与 tomcat 容器重耦合,如果我要将 web 容器迁移成 jetty,难道还要重新把 jetty 都配置一遍? +上面所说的第二种方式会与 tomcat 容器重耦合,如果我要将 web 容器迁移成 jetty,难道还要重新把 jetty 都配置一遍? 因为上面那种 tomcat + redis 的方式好用,但是会**严重依赖于web容器**,不好将代码移植到其他 web 容器上去,尤其是你要是换了技术栈咋整?比如换成了 spring cloud 或者是 spring boot 之类的呢? -所以现在比较好的还是基于 Java 一站式解决方案,也就是 spring。人家 spring 基本上包掉了大部分我们需要使用的框架,spirng cloud 做微服务,spring boot 做脚手架,所以用 sping session 是一个很好的选择。 +所以现在比较好的还是基于 Java 一站式解决方案,也就是 spring。人家 spring 基本上承包了大部分我们需要使用的框架,spirng cloud 做微服务,spring boot 做脚手架,所以用 sping session 是一个很好的选择。 在 pom.xml 中配置: ```xml @@ -103,20 +105,17 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存 示例代码: ```java -@Controller +@RestController @RequestMapping("/test") public class TestController { @RequestMapping("/putIntoSession") - @ResponseBody public String putIntoSession(HttpServletRequest request, String username) { - request.getSession().setAttribute("name", “leo”); - + request.getSession().setAttribute("name", "leo"); return "ok"; } @RequestMapping("/getFromSession") - @ResponseBody public String getFromSession(HttpServletRequest request, Model model){ String name = request.getSession().getAttribute("name"); return name; @@ -127,4 +126,4 @@ public class TestController { 上面的代码就是 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 来实现。 \ No newline at end of file diff --git a/docs/distributed-system/distributed-system-idempotency.md b/docs/distributed-system/distributed-system-idempotency.md index 0696d45..ca576e4 100644 --- a/docs/distributed-system/distributed-system-idempotency.md +++ b/docs/distributed-system/distributed-system-idempotency.md @@ -6,7 +6,7 @@ 一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢? -你看,假如你有个服务提供一个接口,结果这服务部署在了 5 台机器上,接着有个接口就是**付款接口**。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单**不小心发起了两次支付请求**,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次。 +你看,假如你有个服务提供一些接口供外部调用,这个服务部署在了 5 台机器上,接着有个接口就是**付款接口**。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单**不小心发起了两次支付请求**,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次。 或者是订单系统调用支付系统进行支付,结果不小心因为**网络超时**了,然后订单系统走了前面我们看到的那个重试机制,咔嚓给你重试了一把,好,支付系统收到一个支付请求两次,而且因为负载均衡算法落在了不同的机器上,尴尬了。。。 diff --git a/docs/distributed-system/distributed-system-interview.md b/docs/distributed-system/distributed-system-interview.md index 6a63bb1..ae2133b 100644 --- a/docs/distributed-system/distributed-system-interview.md +++ b/docs/distributed-system/distributed-system-interview.md @@ -3,7 +3,7 @@ 分布式业务系统,就是把原来用 Java 开发的一个大块系统,给拆分成**多个子系统**,多个子系统之间互相调用,形成一个大系统的整体。假设原来你做了一个 OA 系统,里面包含了权限模块、员工模块、请假模块、财务模块,一个工程,里面包含了一堆模块,模块与模块之间会互相去调用,1 台机器部署。现在如果你把这个系统给拆开,权限系统、员工系统、请假系统、财务系统 4 个系统,4 个工程,分别在 4 台机器上部署。一个请求过来,完成这个请求,这个员工系统,调用权限系统,调用请假系统,调用财务系统,4 个系统分别完成了一部分的事情,最后 4 个系统都干完了以后,才认为是这个请求已经完成了。 -![simple-distributed-system-oa](/img/simple-distributed-system-oa.png) +![simple-distributed-system-oa](/images/simple-distributed-system-oa.png) > 这两年开始兴起和流行 Spring Cloud,刚流行,还没开始普及,目前普及的是 dubbo,因此这里也主要讲 dubbo。 diff --git a/docs/distributed-system/distributed-system-request-sequence.md b/docs/distributed-system/distributed-system-request-sequence.md index 295e0ae..70f6852 100644 --- a/docs/distributed-system/distributed-system-request-sequence.md +++ b/docs/distributed-system/distributed-system-request-sequence.md @@ -8,13 +8,12 @@ 所以这都是分布式系统一些很常见的问题。 - ## 面试题剖析 首先,一般来说,个人建议是,你们从业务逻辑上设计的这个系统最好是不需要这种顺序性的保证,因为一旦引入顺序性保障,比如使用**分布式锁**,会**导致系统复杂度上升**,而且会带来**效率低下**,热点数据压力过大等问题。 下面我给个我们用过的方案吧,简单来说,首先你得用 dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去,强制排队,这样来确保他们的顺序性。 -![distributed-system-request-sequence](/img/distributed-system-request-sequence.png) +![distributed-system-request-sequence](/images/distributed-system-request-sequence.png) 但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成**热点**怎么办?解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已,所以,还是建议什么呢? diff --git a/docs/distributed-system/distributed-transaction.md b/docs/distributed-system/distributed-transaction.md index 3d8720a..01f7ea1 100644 --- a/docs/distributed-system/distributed-transaction.md +++ b/docs/distributed-system/distributed-transaction.md @@ -18,18 +18,18 @@ ### 两阶段提交方案/XA方案 所谓的 XA 方案,即:两阶段提交,有一个**事务管理器**的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。 -这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 `spring + JTA` 就可以搞定,自己随便搜个 demo 看看就知道了。 +这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。如果要玩儿,那么基于 `Spring + JTA` 就可以搞定,自己随便搜个 demo 看看就知道了。 -这个方案,我们很少用,一般来说**某个系统内部如果出现跨多个库**的这么一个操作,是**不合规**的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几百个服务,几十个服务。一般来说,我们的规定和规范,是要求**每个服务只能操作自己对应的一个数据库**。 +这个方案,我们很少用,一般来说**某个系统内部如果出现跨多个库**的这么一个操作,是**不合规**的。我可以给大家介绍一下, 现在微服务,一个大的系统分成几十个甚至几百个服务。一般来说,我们的规定和规范,是要求**每个服务只能操作自己对应的一个数据库**。 如果你要操作别的服务对应的库,不允许直连别的服务的库,违反微服务架构的规范,你随便交叉胡乱访问,几百个服务的话,全体乱套,这样的一套服务是没法管理的,没法治理的,可能会出现数据被别人改错,自己的库被别人写挂等情况。 如果你要操作别人的服务的库,你必须是通过**调用别的服务的接口**来实现,绝对不允许交叉访问别人的数据库。 -![distributed-transacion-XA](/img/distributed-transaction-XA.png) +![distributed-transacion-XA](/images/distributed-transaction-XA.png) ### TCC 方案 -TCC 的全称是:Try、Confirm、Cancel。 +TCC 的全称是:`Try`、`Confirm`、`Cancel`。 - Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 - Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 @@ -41,9 +41,9 @@ TCC 的全称是:Try、Confirm、Cancel。 而且最好是你的各个业务执行的时间都比较短。 -但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码很难维护。 +但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。 -![distributed-transacion-TCC](/img/distributed-transaction-TCC.png) +![distributed-transacion-TCC](/images/distributed-transaction-TCC.png) ### 本地消息表 本地消息表其实是国外的 ebay 搞出来的这么一套思想。 @@ -57,9 +57,9 @@ TCC 的全称是:Try、Confirm、Cancel。 5. 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理; 6. 这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。 -这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,会导致如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。 +这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。 -![distributed-transaction-local-message-table](/img/distributed-transaction-local-message-table.png) +![distributed-transaction-local-message-table](/images/distributed-transaction-local-message-table.png) ### 可靠消息最终一致性方案 这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。 @@ -73,7 +73,7 @@ TCC 的全称是:Try、Confirm、Cancel。 5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。 6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。 -![distributed-transaction-reliable-message](/img/distributed-transaction-reliable-message.png) +![distributed-transaction-reliable-message](/images/distributed-transaction-reliable-message.png) ### 最大努力通知方案 这个方案的大致意思就是: @@ -83,7 +83,7 @@ TCC 的全称是:Try、Confirm、Cancel。 3. 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。 ### 你们公司是如何处理分布式事务的? -如果你真的被问到,可以这么说,我们某某特别严格的场景,用的是 TCC 来保证强一致性;然后其他的一些场景基于阿里的 RocketMQ 来实现了分布式事务。 +如果你真的被问到,可以这么说,我们某某特别严格的场景,用的是 TCC 来保证强一致性;然后其他的一些场景基于阿里的 RocketMQ 来实现分布式事务。 你找一个严格资金要求绝对不能错的场景,你可以说你是用的 TCC 方案;如果是一般的分布式事务场景,订单插入之后要调用库存服务更新库存,库存数据没有资金那么的敏感,可以用可靠消息最终一致性方案。 diff --git a/docs/distributed-system/dubbo-load-balancing.md b/docs/distributed-system/dubbo-load-balancing.md index 43c2a08..36dfd12 100644 --- a/docs/distributed-system/dubbo-load-balancing.md +++ b/docs/distributed-system/dubbo-load-balancing.md @@ -5,54 +5,81 @@ 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 不同实例**设置不同的权重**,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。 +默认情况下,dubbo 是 random load balance ,即**随机**调用实现负载均衡,可以对 provider 不同实例**设置不同的权重**,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。 #### roundrobin loadbalance 这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。 举个栗子。 -跟运维同学申请机器,有的时候,我们运气好,正好公司资源比较充足,刚刚有一批热气腾腾、刚刚做好的一批虚拟机新鲜出炉,配置都比较高。8核+16g,机器,2 台。过了一段时间,我感觉 2 台机器有点不太够,我去找运维同学,哥儿们,你能不能再给我 1 台机器,4核+8G的机器。我还是得要。 - -这个时候,可以给两台 8核16g 的机器设置权重 4,给剩余 1 台 4核8G 的机器设置权重 2。 +跟运维同学申请机器,有的时候,我们运气好,正好公司资源比较充足,刚刚有一批热气腾腾、刚刚做好的虚拟机新鲜出炉,配置都比较高:8 核 + 16G 机器,申请到 2 台。过了一段时间,我们感觉 2 台机器有点不太够,我就去找运维同学说,“哥儿们,你能不能再给我一台机器”,但是这时只剩下一台 4 核 + 8G 的机器。我要还是得要。 +这个时候,可以给两台 8 核 16G 的机器设置权重 4,给剩余 1 台 4 核 8G 的机器设置权重 2。 #### leastactive loadbalance 这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给**不活跃的性能差的机器更少的请求**。 #### consistanthash loadbalance -一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。**如果你需要的不是随机负载均衡**,是要一类请求都到一个节点,那就走这个一致性 hash 策略。 +一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。**如果你需要的不是随机负载均衡**,是要一类请求都到一个节点,那就走这个一致性 Hash 策略。 ### dubbo 集群容错策略 #### failover cluster 模式 -失败自动切换,自动重试其他机器,默认就是这个,常见于读操作。(失败重试其它机器) +失败自动切换,自动重试其他机器,**默认**就是这个,常见于读操作。(失败重试其它机器) -#### failfast cluster模式 -一次调用失败就立即失败,常见于写操作。(调用失败就立即失败) +可以通过以下几种方式配置重试次数: + +```xml + +``` + +或者 + +```xml + +``` + +或者 + +```xml + + + +``` + +#### failfast cluster 模式 +一次调用失败就立即失败,常见于非幂等性的写操作,比如新增一条记录(调用失败就立即失败) #### failsafe cluster 模式 出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。 +配置示例如下: + +```xml + +``` + +或者 + +```xml + +``` + #### failback cluster 模式 失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种。 #### forking cluster 模式 -**并行调用**多个 provider,只要一个成功就立即返回。 +**并行调用**多个 provider,只要一个成功就立即返回。常用于实时性要求比较高的读操作,但是会浪费更多的服务资源,可通过 `forks="2"` 来设置最大并行数。 #### broadcacst cluster -逐个调用所有的 provider。 +逐个调用所有的 provider。任何一个 provider 出错则报错(从`2.1.0` 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。 ### dubbo动态代理策略 -默认使用 javassist 动态字节码生成,创建代理类。 - -但是可以通过 spi 扩展机制配置自己的动态代理策略。 - - +默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。 \ No newline at end of file diff --git a/docs/distributed-system/dubbo-operating-principle.md b/docs/distributed-system/dubbo-operating-principle.md index 47a8707..2a06d73 100644 --- a/docs/distributed-system/dubbo-operating-principle.md +++ b/docs/distributed-system/dubbo-operating-principle.md @@ -2,20 +2,20 @@ 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程? ## 面试官心理分析 -MQ、ES、Redis、Dubbo,上来先问你一些思考的问题,原理(kafka 高可用架构原理、es 分布式架构原理、redis 线程模型原理、Dubbo 工作原理),生产环境里可能会碰到的一些问题(每种技术引入之后生产环境都可能会碰到一些问题),系统设计(设计MQ,设计搜索引擎,设计一个缓存,设计 rpc 框架) +MQ、ES、Redis、Dubbo,上来先问你一些**思考性的问题**、**原理**,比如 kafka 高可用架构原理、es 分布式架构原理、redis 线程模型原理、Dubbo 工作原理;之后就是生产环境里可能会碰到的一些问题,因为每种技术引入之后生产环境都可能会碰到一些问题;再来点综合的,就是系统设计,比如让你设计一个 MQ、设计一个搜索引擎、设计一个缓存、设计一个 rpc 框架等等。 那既然开始聊分布式系统了,自然重点先聊聊 dubbo 了,毕竟 dubbo 是目前事实上大部分公司的分布式系统的 rpc 框架标准,基于 dubbo 也可以构建一整套的微服务架构。但是需要自己大量开发。 -当然去年开始 spring cloud 非常火,现在大量的公司开始转向spring cloud了,spring cloud 人家毕竟是微服务架构的全家桶式的这么一个东西。但是因为很多公司还在用 dubbo,所以 dubbo 肯定会是目前面试的重点,何况人家 dubbo 现在重启开源社区维护了,未来应该也还是有一定市场和地位的。 +当然去年开始 spring cloud 非常火,现在大量的公司开始转向 spring cloud 了,spring cloud 人家毕竟是微服务架构的全家桶式的这么一个东西。但是因为很多公司还在用 dubbo,所以 dubbo 肯定会是目前面试的重点,何况人家 dubbo 现在重启开源社区维护了,捐献给了 apache,未来应该也还是有一定市场和地位的。 -既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。 +既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc 分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。 ## 面试题剖析 ### dubbo 工作原理 - 第一层:service 层,接口层,给服务提供者和消费者来实现的 - 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的 - 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信 -- 第四层:register 层,服务注册层,负责服务的注册与发现 +- 第四层:registry 层,服务注册层,负责服务的注册与发现 - 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务 - 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控 - 第七层:protocal 层,远程调用层,封装 rpc 调用 @@ -29,7 +29,7 @@ MQ、ES、Redis、Dubbo,上来先问你一些思考的问题,原理(kafka - 第三步:consumer 调用 provider - 第四步:consumer 和 provider 都异步通知监控中心 -![dubbo-operating-principle](/img/dubbo-operating-principle.png) +![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 5feafb5..faf02e4 100644 --- a/docs/distributed-system/dubbo-rpc-design.md +++ b/docs/distributed-system/dubbo-rpc-design.md @@ -1,5 +1,5 @@ ## 面试题 -如何自己设计一个类似dubbo的rpc框架? +如何自己设计一个类似 Dubbo 的 RPC 框架? ## 面试官心理分析 说实话,就这问题,其实就跟问你如何自己设计一个 MQ 一样的道理,就考两个: @@ -12,7 +12,7 @@ 所以我给大家一个建议,遇到这类问题,起码从你了解的类似框架的原理入手,自己说说参照 dubbo 的原理,你来设计一下,举个例子,dubbo 不是有那么多分层么?而且每个分层是干啥的,你大概是不是知道?那就按照这个思路大致说一下吧,起码你不能懵逼,要比那些上来就懵,啥也说不出来的人要好一些。 举个栗子,我给大家说个最简单的回答思路: -- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信心,可以用 zookeeper 来做,对吧。 +- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。 - 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。 - 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。 - 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。 diff --git a/docs/distributed-system/dubbo-serialization-protocol.md b/docs/distributed-system/dubbo-serialization-protocol.md index 879d1a6..008dda5 100644 --- a/docs/distributed-system/dubbo-serialization-protocol.md +++ b/docs/distributed-system/dubbo-serialization-protocol.md @@ -9,7 +9,7 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian ## 面试题剖析 **序列化**,就是把数据结构或者是一些对象,转换为二进制串的过程,而**反序列化**是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。 -![serialize-deserialize](/img/serialize-deserialize.png) +![serialize-deserialize](/images/serialize-deserialize.png) ### dubbo 支持不同的通信协议 - dubbo 协议 @@ -20,11 +20,11 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 长连接,通俗点说,就是建立连接过后可以持续发送请求,无须再建立连接。 -![dubbo-keep-connection](/img/dubbo-keep-connection.png) +![dubbo-keep-connection](/images/dubbo-keep-connection.png) 而短连接,每次要发送请求之前,需要先重新建立一次连接。 -![dubbo-not-keep-connection](/img/dubbo-not-keep-connection.png) +![dubbo-not-keep-connection](/images/dubbo-not-keep-connection.png) - rmi 协议 @@ -68,6 +68,6 @@ Hessian 的对象序列化机制有 8 种原始类型: - ref:用来表示对共享对象的引用。 ### 为什么 PB 的效率是最高的? -可能有一些同学比较习惯于 `JSON` or `XML` 数据存储格式,对于 `Protocal Buffer` 还比较陌生。`Protocal Buffer` 其实是 Google 出品的一种轻量并且高效的结构化数据存储格式,性能比 `JSON`、`XML` 要高很多。 +可能有一些同学比较习惯于 `JSON` or `XML` 数据存储格式,对于 `Protocol Buffer` 还比较陌生。`Protocol Buffer` 其实是 Google 出品的一种轻量并且高效的结构化数据存储格式,性能比 `JSON`、`XML` 要高很多。 其实 PB 之所以性能如此好,主要得益于两个:**第一**,它使用 proto 编译器,自动进行序列化和反序列化,速度非常快,应该比 `XML` 和 `JSON` 快上了 `20~100` 倍;**第二**,它的数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。 \ No newline at end of file diff --git a/docs/distributed-system/dubbo-service-management.md b/docs/distributed-system/dubbo-service-management.md index 02c928d..9a91c08 100644 --- a/docs/distributed-system/dubbo-service-management.md +++ b/docs/distributed-system/dubbo-service-management.md @@ -8,7 +8,7 @@ **失败重试**,分布式系统中网络请求如此频繁,要是因为网络问题不小心失败了一次,是不是要重试? -**超时重试**,同上,如果不小心网络慢一点,超时了,如何重试? +**超时重试**,跟上面一样,如果不小心网络慢一点,超时了,如何重试? ## 面试题剖析 ### 服务治理 @@ -17,7 +17,7 @@ 那就需要基于 dubbo 做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将**各个服务之间的依赖关系和调用链路生成出来**,做成一张图,显示出来,大家才可以看到对吧。 -![dubbo-service-invoke-road](/img/dubbo-service-invoke-road.png) +![dubbo-service-invoke-road](/images/dubbo-service-invoke-road.png) #### 2. 服务访问压力以及时长统计 需要自动统计**各个接口和服务之间的调用次数以及访问延时**,而且要分成两个级别。 @@ -31,28 +31,23 @@ - 服务分层(避免循环依赖) - 调用链路失败监控和报警 - 服务鉴权 -- 每个服务的可用性的监控(接口调用成功率?几个9?99.99%,99.9%,99%。) +- 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%) ### 服务降级 -比如说服务 A调用服务 B,结果服务 B 挂掉了,服务 A 重试几次调用服务 B,还是不行,那么直接降级,走一个备用的逻辑,给用户返回响应。 +比如说服务 A 调用服务 B,结果服务 B 挂掉了,服务 A 重试几次调用服务 B,还是不行,那么直接降级,走一个备用的逻辑,给用户返回响应。 举个栗子,我们有接口 `HelloService`。`HelloServiceImpl` 有该接口的具体实现。 ```java public interface HelloService { - void sayHello(); - } public class HelloServiceImpl implements HelloService { - public void sayHello() { System.out.println("hello world......"); } - } - ``` ```xml @@ -88,16 +83,14 @@ public class HelloServiceImpl implements HelloService { 我们调用接口失败的时候,可以通过 `mock` 统一返回 null。 -mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 `接口名称+Mock` 后缀。然后在 Mock 类里实现自己的降级逻辑。 +mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+`Mock`” 后缀。然后在 Mock 类里实现自己的降级逻辑。 + ```java public class HelloServiceMock implements HelloService { - public void sayHello() { // 降级逻辑 } - } - ``` ### 失败重试和超时重试 @@ -109,9 +102,9 @@ public class HelloServiceMock implements HelloService { 举个栗子。 -某个服务的接口,要耗费 5s,你这边不能干等着,你这边配置了 timeout之后,我等待2s,还没返回,我直接就撤了,不能干等你。 +某个服务的接口,要耗费 5s,你这边不能干等着,你这边配置了 timeout 之后,我等待 2s,还没返回,我直接就撤了,不能干等你。 可以结合你们公司具体的场景来说说你是怎么设置这些参数的: -- `timeout`:一般设置为 `200ms`,我们认为不能超过 `200ms`还没返回。 +- `timeout`:一般设置为 `200ms`,我们认为不能超过 `200ms` 还没返回。 - `retries`:设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。 \ No newline at end of file diff --git a/docs/distributed-system/dubbo-spi.md b/docs/distributed-system/dubbo-spi.md index 6cb5ba1..bce20b0 100644 --- a/docs/distributed-system/dubbo-spi.md +++ b/docs/distributed-system/dubbo-spi.md @@ -12,7 +12,7 @@ spi,简单来说,就是 `service provider interface`,说白了是什么意 举个栗子。 -你有一个接口A。A1/A2/A3 分别是接口A的不同实现。你通过配置 `接口A=实现A2`,那么在系统实际运行的时候,会加载你的配置,用实现A2实例化一个对象来提供服务。 +你有一个接口 A。A1/A2/A3 分别是接口A的不同实现。你通过配置 `接口 A = 实现 A2`,那么在系统实际运行的时候,会加载你的配置,用实现 A2 实例化一个对象来提供服务。 spi 机制一般用在哪儿?**插件扩展的场景**,比如说你开发了一个给别人使用的开源框架,如果你想让别人自己写个插件,插到你的开源框架里面,从而扩展某个功能,这个时候 spi 思想就用上了。 @@ -33,7 +33,7 @@ Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdapti Protocol 接口,在系统运行的时候,,dubbo 会判断一下应该选用这个 Protocol 接口的哪个实现类来实例化对象来使用。 -它会去找一个你配置的 Protocol,将你配置的 Protocol 实现类,加载到 jvm 中来,然后实例化对象,就用你的那个 Protocol 实现类就可以了 +它会去找一个你配置的 Protocol,将你配置的 Protocol 实现类,加载到 jvm 中来,然后实例化对象,就用你的那个 Protocol 实现类就可以了。 上面那行代码就是 dubbo 里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现好了,没问题。 @@ -61,7 +61,7 @@ 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` 注解,就是说那俩接口会被代理实现。 @@ -77,12 +77,13 @@ hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol 然后自己搞一个 `dubbo provider` 工程,在这个工程里面依赖你自己搞的那个 jar,然后在 spring 配置文件里给个配置: +```xml - +``` provider 启动的时候,就会加载到我们 jar 包里的`my=com.bingo.MyProtocol` 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。 -![dubbo-spi](/img/dubbo-spi.png) +![dubbo-spi](/images/dubbo-spi.png) dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类`。 -然后对对应的组件,用类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。 \ No newline at end of file +然后对于对应的组件,类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。 \ No newline at end of file diff --git a/docs/distributed-system/img/async-replication-data-lose-case.png b/docs/distributed-system/images/async-replication-data-lose-case.png similarity index 100% rename from docs/distributed-system/img/async-replication-data-lose-case.png rename to docs/distributed-system/images/async-replication-data-lose-case.png diff --git a/docs/distributed-system/img/consistent-hashing-algorithm.png b/docs/distributed-system/images/consistent-hashing-algorithm.png similarity index 100% rename from docs/distributed-system/img/consistent-hashing-algorithm.png rename to docs/distributed-system/images/consistent-hashing-algorithm.png diff --git a/docs/distributed-system/img/distributed-system-request-sequence.png b/docs/distributed-system/images/distributed-system-request-sequence.png similarity index 100% rename from docs/distributed-system/img/distributed-system-request-sequence.png rename to docs/distributed-system/images/distributed-system-request-sequence.png diff --git a/docs/distributed-system/img/distributed-transaction-TCC.png b/docs/distributed-system/images/distributed-transaction-TCC.png similarity index 100% rename from docs/distributed-system/img/distributed-transaction-TCC.png rename to docs/distributed-system/images/distributed-transaction-TCC.png diff --git a/docs/distributed-system/img/distributed-transaction-XA.png b/docs/distributed-system/images/distributed-transaction-XA.png similarity index 100% rename from docs/distributed-system/img/distributed-transaction-XA.png rename to docs/distributed-system/images/distributed-transaction-XA.png diff --git a/docs/distributed-system/img/distributed-transaction-local-message-table.png b/docs/distributed-system/images/distributed-transaction-local-message-table.png similarity index 100% rename from docs/distributed-system/img/distributed-transaction-local-message-table.png rename to docs/distributed-system/images/distributed-transaction-local-message-table.png diff --git a/docs/distributed-system/img/distributed-transaction-reliable-message.png b/docs/distributed-system/images/distributed-transaction-reliable-message.png similarity index 100% rename from docs/distributed-system/img/distributed-transaction-reliable-message.png rename to docs/distributed-system/images/distributed-transaction-reliable-message.png diff --git a/docs/distributed-system/images/dubbo-keep-connection.png b/docs/distributed-system/images/dubbo-keep-connection.png new file mode 100644 index 0000000..b9388d2 Binary files /dev/null and b/docs/distributed-system/images/dubbo-keep-connection.png differ diff --git a/docs/distributed-system/images/dubbo-not-keep-connection.png b/docs/distributed-system/images/dubbo-not-keep-connection.png new file mode 100644 index 0000000..f74590c Binary files /dev/null and b/docs/distributed-system/images/dubbo-not-keep-connection.png differ diff --git a/docs/distributed-system/img/dubbo-operating-principle.png b/docs/distributed-system/images/dubbo-operating-principle.png similarity index 100% rename from docs/distributed-system/img/dubbo-operating-principle.png rename to docs/distributed-system/images/dubbo-operating-principle.png diff --git a/docs/distributed-system/img/dubbo-service-invoke-road.png b/docs/distributed-system/images/dubbo-service-invoke-road.png similarity index 100% rename from docs/distributed-system/img/dubbo-service-invoke-road.png rename to docs/distributed-system/images/dubbo-service-invoke-road.png diff --git a/docs/distributed-system/img/dubbo-spi.png b/docs/distributed-system/images/dubbo-spi.png similarity index 100% rename from docs/distributed-system/img/dubbo-spi.png rename to docs/distributed-system/images/dubbo-spi.png diff --git a/docs/distributed-system/img/e-commerce-website-detail-page-architecture-1.png b/docs/distributed-system/images/e-commerce-website-detail-page-architecture-1.png similarity index 100% rename from docs/distributed-system/img/e-commerce-website-detail-page-architecture-1.png rename to docs/distributed-system/images/e-commerce-website-detail-page-architecture-1.png diff --git a/docs/distributed-system/img/e-commerce-website-detail-page-architecture-2.png b/docs/distributed-system/images/e-commerce-website-detail-page-architecture-2.png similarity index 100% rename from docs/distributed-system/img/e-commerce-website-detail-page-architecture-2.png rename to docs/distributed-system/images/e-commerce-website-detail-page-architecture-2.png diff --git a/docs/distributed-system/img/favicon-16x16.png b/docs/distributed-system/images/favicon-16x16.png similarity index 100% rename from docs/distributed-system/img/favicon-16x16.png rename to docs/distributed-system/images/favicon-16x16.png diff --git a/docs/distributed-system/img/favicon-32x32.png b/docs/distributed-system/images/favicon-32x32.png similarity index 100% rename from docs/distributed-system/img/favicon-32x32.png rename to docs/distributed-system/images/favicon-32x32.png diff --git a/docs/distributed-system/img/hash-slot.png b/docs/distributed-system/images/hash-slot.png similarity index 100% rename from docs/distributed-system/img/hash-slot.png rename to docs/distributed-system/images/hash-slot.png diff --git a/docs/distributed-system/img/hash.png b/docs/distributed-system/images/hash.png similarity index 100% rename from docs/distributed-system/img/hash.png rename to docs/distributed-system/images/hash.png diff --git a/docs/distributed-system/img/icon.png b/docs/distributed-system/images/icon.png similarity index 100% rename from docs/distributed-system/img/icon.png rename to docs/distributed-system/images/icon.png diff --git a/docs/distributed-system/img/serialize-deserialize.png b/docs/distributed-system/images/serialize-deserialize.png similarity index 100% rename from docs/distributed-system/img/serialize-deserialize.png rename to docs/distributed-system/images/serialize-deserialize.png diff --git a/docs/distributed-system/img/service-invoke-road.png b/docs/distributed-system/images/service-invoke-road.png similarity index 100% rename from docs/distributed-system/img/service-invoke-road.png rename to docs/distributed-system/images/service-invoke-road.png diff --git a/docs/distributed-system/img/simple-distributed-system-oa.png b/docs/distributed-system/images/simple-distributed-system-oa.png similarity index 100% rename from docs/distributed-system/img/simple-distributed-system-oa.png rename to docs/distributed-system/images/simple-distributed-system-oa.png diff --git a/docs/distributed-system/img/zookeeper-active-standby.png b/docs/distributed-system/images/zookeeper-active-standby.png similarity index 100% rename from docs/distributed-system/img/zookeeper-active-standby.png rename to docs/distributed-system/images/zookeeper-active-standby.png diff --git a/docs/distributed-system/img/zookeeper-centralized-storage.png b/docs/distributed-system/images/zookeeper-centralized-storage.png similarity index 100% rename from docs/distributed-system/img/zookeeper-centralized-storage.png rename to docs/distributed-system/images/zookeeper-centralized-storage.png diff --git a/docs/distributed-system/img/zookeeper-distributed-coordination.png b/docs/distributed-system/images/zookeeper-distributed-coordination.png similarity index 100% rename from docs/distributed-system/img/zookeeper-distributed-coordination.png rename to docs/distributed-system/images/zookeeper-distributed-coordination.png diff --git a/docs/distributed-system/img/zookeeper-distributed-lock-demo.png b/docs/distributed-system/images/zookeeper-distributed-lock-demo.png similarity index 100% rename from docs/distributed-system/img/zookeeper-distributed-lock-demo.png rename to docs/distributed-system/images/zookeeper-distributed-lock-demo.png diff --git a/docs/distributed-system/images/zookeeper-distributed-lock.png b/docs/distributed-system/images/zookeeper-distributed-lock.png new file mode 100644 index 0000000..898361b Binary files /dev/null and b/docs/distributed-system/images/zookeeper-distributed-lock.png differ diff --git a/docs/distributed-system/img/zookeeper-meta-data-manage.png b/docs/distributed-system/images/zookeeper-meta-data-manage.png similarity index 100% rename from docs/distributed-system/img/zookeeper-meta-data-manage.png rename to docs/distributed-system/images/zookeeper-meta-data-manage.png diff --git a/docs/distributed-system/img/dubbo-keep-connection.png b/docs/distributed-system/img/dubbo-keep-connection.png deleted file mode 100644 index acbd619..0000000 Binary files a/docs/distributed-system/img/dubbo-keep-connection.png and /dev/null differ diff --git a/docs/distributed-system/img/dubbo-not-keep-connection.png b/docs/distributed-system/img/dubbo-not-keep-connection.png deleted file mode 100644 index e9e31b2..0000000 Binary files a/docs/distributed-system/img/dubbo-not-keep-connection.png and /dev/null differ diff --git a/docs/distributed-system/img/zookeeper-distributed-lock.png b/docs/distributed-system/img/zookeeper-distributed-lock.png deleted file mode 100644 index b109514..0000000 Binary files a/docs/distributed-system/img/zookeeper-distributed-lock.png and /dev/null differ diff --git a/docs/distributed-system/why-dubbo.md b/docs/distributed-system/why-dubbo.md index f504775..b911786 100644 --- a/docs/distributed-system/why-dubbo.md +++ b/docs/distributed-system/why-dubbo.md @@ -8,7 +8,7 @@ 早些年,印象中在 2010 年初的时候,整个 IT 行业,很少有人谈分布式,更不用说微服务,虽然很多 BAT 等大型公司,因为系统的复杂性,很早就是分布式架构,大量的服务,只不过微服务大多基于自己搞的一套框架来实现而已。 -但是确实,那个年代,大家很重视 ssh2,很多中小型公司几乎大部分都是玩儿 struts2、spring、hibernate,稍晚一些,才进入了spring mvc、spring、mybatis 的组合。那个时候整个行业的技术水平就是那样,当年 oracle 很火,oracle 管理员很吃香,oracle 性能优化啥的都是 IT 男的大杀招啊。连大数据都没人提,当年 OCP、OCM 等认证培训机构,火的不行。 +但是确实,那个年代,大家很重视 ssh2,很多中小型公司几乎大部分都是玩儿 struts2、spring、hibernate,稍晚一些,才进入了 spring mvc、spring、mybatis 的组合。那个时候整个行业的技术水平就是那样,当年 oracle 很火,oracle 管理员很吃香,oracle 性能优化啥的都是 IT 男的大杀招啊。连大数据都没人提,当年 OCP、OCM 等认证培训机构,火的不行。 但是确实随着时代的发展,慢慢的,很多公司开始接受分布式系统架构了,这里面尤为对行业有至关重要影响的,是阿里的 dubbo,**某种程度上而言,阿里在这里推动了行业技术的前进**。 @@ -22,20 +22,19 @@ 要是**不拆分**,一个大系统几十万行代码,20 个人维护一份代码,简直是悲剧啊。代码经常改着改着就冲突了,各种代码冲突和合并要处理,非常耗费时间;经常我改动了我的代码,你调用了我的,导致你的代码也得重新测试,麻烦的要死;然后每次发布都是几十万行代码的系统一起发布,大家得一起提心吊胆准备上线,几十万行代码的上线,可能每次上线都要做很多的检查,很多异常问题的处理,简直是又麻烦又痛苦;而且如果我现在打算把技术升级到最新的 spring 版本,还不行,因为这可能导致你的代码报错,我不敢随意乱改技术。 -假设一个系统是 20 万行代码,其中 小A 在里面改了 1000 行代码,但是此时发布的时候是这个 20 万行代码的大系统一块儿发布。就意味着 20 万上代码在线上就可能出现各种变化,20 个人,每个人都要紧张地等在电脑面前,上线之后,检查日志,看自己负责的那一块儿有没有什么问题。 +假设一个系统是 20 万行代码,其中 A 在里面改了 1000 行代码,但是此时发布的时候是这个 20 万行代码的大系统一块儿发布。就意味着 20 万上代码在线上就可能出现各种变化,20 个人,每个人都要紧张地等在电脑面前,上线之后,检查日志,看自己负责的那一块儿有没有什么问题。 -小A 就检查了自己负责的 1 万行代码对应的功能,确保ok就闪人了;结果不巧的是,小A 上线的时候不小心修改了线上机器的某个配置,导致另外 小B 和 小C 负责的 2 万行代码对应的一些功能,出错了。 +A 就检查了自己负责的 1 万行代码对应的功能,确保 ok 就闪人了;结果不巧的是,A 上线的时候不小心修改了线上机器的某个配置,导致另外 B 和 C 负责的 2 万行代码对应的一些功能,出错了。 几十个人负责维护一个几十万行代码的单块应用,每次上线,准备几个礼拜,上线 -> 部署 -> 检查自己负责的功能。 -**拆分了以后**,整个世界清爽了,几十万行代码的系统,拆分成 20 个服务,平均每个服务就 1~2 万行代码,每个服务部署到单独的机器上。20 个工程,20 个 git 代码仓库里,20 个码农,每个人维护自己的那个服务就可以了,是自己独立的代码,跟别人没关系。再也没有代码冲突了,爽。每次就测试我自己的代码就可以了,爽。每次就发布我自己的一个小服务就可以了,爽。技术上想怎么升级就怎么升级,保持接口不变就可以了,爽。 +**拆分了以后**,整个世界清爽了,几十万行代码的系统,拆分成 20 个服务,平均每个服务就 1~2 万行代码,每个服务部署到单独的机器上。20 个工程,20 个 git 代码仓库,20 个开发人员,每个人维护自己的那个服务就可以了,是自己独立的代码,跟别人没关系。再也没有代码冲突了,爽。每次就测试我自己的代码就可以了,爽。每次就发布我自己的一个小服务就可以了,爽。技术上想怎么升级就怎么升级,保持接口不变就可以了,真爽。 所以简单来说,一句话总结,如果是那种代码量多达几十万行的中大型项目,团队里有几十个人,那么如果不拆分系统,**开发效率极其低下**,问题很多。但是拆分系统之后,每个人就负责自己的一小部分就好了,可以随便玩儿随便弄。分布式系统拆分之后,可以大幅度提升复杂系统大型团队的开发效率。 但是同时,也要**提醒**的一点是,系统拆分成分布式系统之后,大量的分布式系统面临的问题也是接踵而来,所以后面的问题都是在**围绕分布式系统带来的复杂技术挑战**在说。 ### 如何进行系统拆分? - 这个问题说大可以很大,可以扯到领域驱动模型设计上去,说小了也很小,我不太想给大家太过于学术的说法,因为你也不可能背这个答案,过去了直接说吧。还是说的简单一点,大家自己到时候知道怎么回答就行了。 系统拆分为分布式系统,拆成多个服务,拆成微服务的架构,是需要拆很多轮的。并不是说上来一个架构师一次就给拆好了,而以后都不用拆。 @@ -44,7 +43,7 @@ 如果是多人维护一个服务,最理想的情况下,几十个人,1 个人负责 1 个或 2~3 个服务;某个服务工作量变大了,代码量越来越多,某个同学,负责一个服务,代码量变成了 10 万行了,他自己不堪重负,他现在一个人拆开,5 个服务,1 个人顶着,负责 5 个人,接着招人,2 个人,给那个同学带着,3 个人负责 5 个服务,其中 2 个人每个人负责 2 个服务,1 个人负责 1 个服务。 -个人建议,一个服务的代码不要太多,1万行左右,两三万撑死了吧。 +个人建议,一个服务的代码不要太多,1 万行左右,两三万撑死了吧。 大部分的系统,是要进行**多轮拆分**的,第一次拆分,可能就是将以前的多个模块该拆分开来了,比如说将电商系统拆分成订单系统、商品系统、采购系统、仓储系统、用户系统,等等吧。 @@ -53,7 +52,6 @@ 扯深了实在很深,所以这里先给大家举个例子,你自己感受一下,**核心意思就是根据情况,先拆分一轮,后面如果系统更复杂了,可以继续分拆**。你根据自己负责系统的例子,来考虑一下就好了。 ### 拆分后不用 dubbo 可以吗? - 当然可以了,大不了最次,就是各个系统之间,直接基于 spring mvc,就纯 http 接口互相通信呗,还能咋样。但是这个肯定是有问题的,因为 http 接口通信维护起来成本很高,你要考虑**超时重试**、**负载均衡**等等各种乱七八糟的问题,比如说你的订单系统调用商品系统,商品系统部署了 5 台机器,你怎么把请求均匀地甩给那 5 台机器?这不就是负载均衡?你要是都自己搞那是可以的,但是确实很痛苦。 -所以 dubbo 说白了,是一种 rpc 框架,就是说本地就是进行接口调用,但是 dubbo 会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡了、服务实例上下线自动感知了、超时重试了,等等乱七八糟的问题。那你就不用自己做了,用 dubbo 就可以了。 \ No newline at end of file +所以 dubbo 说白了,是一种 rpc 框架,就是说本地就是进行接口调用,但是 dubbo 会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡、服务实例上下线自动感知、超时重试等等乱七八糟的问题。那你就不用自己做了,用 dubbo 就可以了。 \ No newline at end of file diff --git a/docs/distributed-system/zookeeper-application-scenarios.md b/docs/distributed-system/zookeeper-application-scenarios.md index 530cea9..b07d2c9 100644 --- a/docs/distributed-system/zookeeper-application-scenarios.md +++ b/docs/distributed-system/zookeeper-application-scenarios.md @@ -4,12 +4,12 @@ zookeeper 都有哪些使用场景? ## 面试官心理分析 现在聊的 topic 是分布式系统,面试官跟你聊完了 dubbo 相关的一些问题之后,已经确认你对分布式服务框架/RPC框架基本都有一些认知了。那么他可能开始要跟你聊分布式相关的其它问题了。 -分布式锁这个东西,很常用的,你做 Java系统开发,分布式系统,可能会有一些场景会用到。最常用的分布式锁就是基于 zookeeper 来实现的。 +分布式锁这个东西,很常用的,你做 Java 系统开发,分布式系统,可能会有一些场景会用到。最常用的分布式锁就是基于 zookeeper 来实现的。 -其实说实话,问这个问题,一般就是看看你是否了解 zookeeper,因为 zk 是分布式系统中很常见的一个基础系统。而且问的话常问的就是说 zk 的使用场景是什么?看你知道不知道一些基本的使用场景。但是其实 zk 挖深了自然是可以问的很深很深的。 +其实说实话,问这个问题,一般就是看看你是否了解 zookeeper,因为 zookeeper 是分布式系统中很常见的一个基础系统。而且问的话常问的就是说 zookeeper 的使用场景是什么?看你知道不知道一些基本的使用场景。但是其实 zookeeper 挖深了自然是可以问的很深很深的。 ## 面试题剖析 -大致来说,zk 的使用场景如下,我就举几个简单的,大家能说几个就好了: +大致来说,zookeeper 的使用场景如下,我就举几个简单的,大家能说几个就好了: - 分布式协调 - 分布式锁 @@ -17,21 +17,21 @@ zookeeper 都有哪些使用场景? - HA高可用性 ### 分布式协调 -这个其实是 zk 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zk 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zk 上**对某个节点的值注册个监听器**,一旦 B 系统处理完了就修改 zk 那个节点的值,A 立马就可以收到通知,完美解决。 +这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上**对某个节点的值注册个监听器**,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知,完美解决。 -![zookeeper-distributed-coordination](/img/zookeeper-distributed-coordination.png) +![zookeeper-distributed-coordination](/images/zookeeper-distributed-coordination.png) ### 分布式锁 -举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zk 分布式锁,一个机器接收到了请求之后先获取 zk 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也**尝试去创建**那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。 +举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也**尝试去创建**那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。 -![zookeeper-distributed-lock-demo](/img/zookeeper-distributed-lock-demo.png) +![zookeeper-distributed-lock-demo](/images/zookeeper-distributed-lock-demo.png) ### 元数据/配置信息管理 -zk 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zk 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zk 么? +zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么? -![zookeeper-meta-data-manage](/img/zookeeper-meta-data-manage.png) +![zookeeper-meta-data-manage](/images/zookeeper-meta-data-manage.png) ### HA高可用性 -这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zk 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zk 感知到切换到备用进程。 +这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。 -![zookeeper-active-standby](/img/zookeeper-active-standby.png) \ No newline at end of file +![zookeeper-active-standby](/images/zookeeper-active-standby.png) \ No newline at end of file diff --git a/docs/from-readers/README.md b/docs/from-readers/README.md new file mode 100644 index 0000000..a324369 --- /dev/null +++ b/docs/from-readers/README.md @@ -0,0 +1,15 @@ +# 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)。 + +## Articles +- [示例文章](/docs/from-readers/doocs-advanced-java-attention.md) +- [示例文章](/docs/from-readers/doocs-advanced-java-attention.md) + +## Contributors +This project exists thanks to all the people who contribute. + + + + + + \ 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 new file mode 100644 index 0000000..a2e4028 --- /dev/null +++ b/docs/from-readers/doocs-advanced-java-attention.md @@ -0,0 +1,52 @@ +# 提交注意事项 +项目需要有一个统一的内容提交规范,没有规范的项目将会是一团乱麻,维护起来也会很费劲儿。以下列出了几个小点,看似很多,实际上都非常容易做到,供朋友们参考。 + +> 如果你有好的 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/advanced-java-doocs-shishan.png b/docs/from-readers/images/advanced-java-doocs-shishan.png new file mode 100644 index 0000000..6d3e138 Binary files /dev/null and b/docs/from-readers/images/advanced-java-doocs-shishan.png differ diff --git a/docs/high-availability/img/icon.png b/docs/from-readers/images/icon.png similarity index 100% rename from docs/high-availability/img/icon.png rename to docs/from-readers/images/icon.png diff --git a/docs/from-readers/rights-defending-movement.md b/docs/from-readers/rights-defending-movement.md new file mode 100644 index 0000000..bee3362 --- /dev/null +++ b/docs/from-readers/rights-defending-movement.md @@ -0,0 +1,39 @@ +

+ 维权行动 +

+ +## 声明 +读者朋友们,你们好。[advanced-java](https://github.com/doocs/advanced-java) 项目自创建以来,一直收到很多读者的反馈,也在不断改进、完善内容,只希望可以用心做得更好。然而,网上抄袭、侵权的现象普遍存在,我想,不能任由这种恶劣行为肆虐。 + +因此,在此说明,除了 [doocs/advanced-java](https://github.com/doocs/advanced-java)、“**石杉的架构笔记**”,网上其它平台上如若出现了与本项目内容雷同甚至完全一致的文章,**不注明出处、甚至打着原创的标签忽悠读者**的,欢迎举报,也欢迎在此提供侵权名单,曝光抄袭者,谢谢。 + +希望各位朋友都注重**维护他人知识产权,尊重他人劳动成果**,我们共同构建一个健康的知识分享生态圈。 + +## 抄袭名单列表 + +### 博客 +| # | 文章 | 抄袭者 | +|---|---|---| +| 1 | [MySQL 面试题](https://jsbintask.cn/2019/02/17/interview/interview-high-concurrency-design/) | jsbintask | +| 2 | [消息队列面试题](https://blog.51cto.com/13904503/2351522) | Java邵先生 | +| 3 | [高并发架构消息队列面试题解析](https://www.cnblogs.com/yuxiang1/p/10542569.html) | 手留余香-博客园 | +| 4 | [消息中间件面试题:消息中间件的高可用](https://www.jianshu.com/p/92862edc7c51) | jsbintask-简书 | +| 5 | [深入 Hystrix 执行时内部原理](https://www.jianshu.com/p/1a14401e219f) | kevin0016-简书 | + + +### 公众号 +| # | 文章 | 抄袭者 | +|---|---|---| + +### 头条号 +| # | 文章 | 抄袭者 | +|---|---|---| + +### 掘金 +| # | 文章 | 抄袭者 | +|---|---|---| + +### 知乎 +| # | 文章 | 抄袭者 | 备注 | +|---|---|---|---| +| 1 | [Java消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java高级架构解析 | 严重抄袭 | \ No newline at end of file diff --git a/docs/high-availability/README.md b/docs/high-availability/README.md new file mode 100644 index 0000000..9313304 --- /dev/null +++ b/docs/high-availability/README.md @@ -0,0 +1 @@ +# 高可用架构 \ No newline at end of file diff --git a/docs/high-availability/e-commerce-website-detail-page-architecture.md b/docs/high-availability/e-commerce-website-detail-page-architecture.md index 43c341f..0a91a2a 100644 --- a/docs/high-availability/e-commerce-website-detail-page-architecture.md +++ b/docs/high-availability/e-commerce-website-detail-page-architecture.md @@ -3,20 +3,35 @@ ### 小型电商网站的商品详情页系统架构 小型电商网站的页面展示采用页面全量静态化的思想。数据库中存放了所有的商品信息,页面静态化系统,将数据填充进静态模板中,形成静态化页面,推入 Nginx 服务器。用户浏览网站页面时,取用一个已经静态化好的 html 页面,直接返回回去,不涉及任何的业务逻辑处理。 -![e-commerce-website-detail-page-architecture-1](/img/e-commerce-website-detail-page-architecture-1.png) +![e-commerce-website-detail-page-architecture-1](/images/e-commerce-website-detail-page-architecture-1.png) -- 好处:用户每次浏览一个页面,不需要进行任何的跟数据库的交互逻辑,也不需要执行任何的代码,直接返回一个 html 页面就可以了,速度和性能非常高。 -- 坏处:仅仅适用于一些小型的网站,比如页面的规模在几十到几万不等。对于一些大型的电商网站,亿级数量的页面,你说你每次页面模板修改了,都需要将这么多页面全量静态化,靠谱吗? +下面是页面模板的简单 Demo 。 + +```html + + + 商品名称:#{productName}
+ 商品价格:#{productPrice}
+ 商品描述:#{productDesc} + + +``` + +这样做,**好处**在于,用户每次浏览一个页面,不需要进行任何的跟数据库的交互逻辑,也不需要执行任何的代码,直接返回一个 html 页面就可以了,速度和性能非常高。 + +对于小网站,页面很少,很实用,非常简单,Java 中可以使用 velocity、freemarker、thymeleaf 等等,然后做个 cms 页面内容管理系统,模板变更的时候,点击按钮或者系统自动化重新进行全量渲染。 + +**坏处**在于,仅仅适用于一些小型的网站,比如页面的规模在几十到几万不等。对于一些大型的电商网站,亿级数量的页面,你说你每次页面模板修改了,都需要将这么多页面全量静态化,靠谱吗?每次渲染花个好几天时间,那你整个网站就废掉了。 ### 大型电商网站的商品详情页系统架构 -大型电商网站商品详情页的系统设计中,当商品信息发生变更时,会将变更消息压入消息队列中。**缓存服务**从消息队列中消费此消息时,发现有信息发生变更,便通过调用接口,获取变更后的数据。将整合好的数据推送至 redis 中。Nginx 获取到最新的缓存数据,并且缓存到 Nginx 自己本地中。 +大型电商网站商品详情页的系统设计中,当商品数据发生变更时,会将变更消息压入 MQ 消息队列中。**缓存服务**从消息队列中消费这条消息时,感知到有数据发生变更,便通过调用数据服务接口,获取变更后的数据,然后将整合好的数据推送至 redis 中。Nginx 本地缓存的数据是有一定的时间期限的,比如说 10 分钟,当数据过期之后,它就会从 redis 获取到最新的缓存数据,并且缓存到自己本地。 用户浏览网页时,动态将 Nginx 本地数据渲染到本地 html 模板并返回给用户。 -![e-commerce-website-detail-page-architecture-2](/img/e-commerce-website-detail-page-architecture-2.png) +![e-commerce-website-detail-page-architecture-2](/images/e-commerce-website-detail-page-architecture-2.png) -虽然没有直接返回 html 页面那么快,但是因为数据在本地缓存,所以也很快,其实耗费的也就是动态渲染一个 html 页面的性能。如果 html 模板发生了变更,不需要将所有的页面重新静态化,直接将数据渲染进最新的 html 页面模板后响应即可。 +虽然没有直接返回 html 页面那么快,但是因为数据在本地缓存,所以也很快,其实耗费的也就是动态渲染一个 html 页面的性能。如果 html 模板发生了变更,不需要将所有的页面重新静态化,也不需要发送请求,没有网络请求的开销,直接将数据渲染进最新的 html 页面模板后响应即可。 在这种架构下,我们需要**保证系统的高可用性**。 diff --git a/docs/high-availability/hystrix-circuit-breaker.md b/docs/high-availability/hystrix-circuit-breaker.md new file mode 100644 index 0000000..5fd28a8 --- /dev/null +++ b/docs/high-availability/hystrix-circuit-breaker.md @@ -0,0 +1,181 @@ +## 深入 Hystrix 断路器执行原理 + +### RequestVolumeThreshold + +```java +HystrixCommandProperties.Setter() + .withCircuitBreakerRequestVolumeThreshold(int) +``` + +表示在滑动窗口中,至少有多少个请求,才可能触发断路。 + +Hystrix 经过断路器的流量超过了一定的阈值,才有可能触发断路。比如说,要求在 10s 内经过断路器的流量必须达到 20 个,而实际经过断路器的流量才 10 个,那么根本不会去判断要不要断路。 + +### ErrorThresholdPercentage + +```java +HystrixCommandProperties.Setter() + .withCircuitBreakerErrorThresholdPercentage(int) +``` + +表示异常比例达到多少,才会触发断路,默认值是 50(%)。 + +如果断路器统计到的异常调用的占比超过了一定的阈值,比如说在 10s 内,经过断路器的流量达到了 30 个,同时其中异常访问的数量也达到了一定的比例,比如 60% 的请求都是异常(报错 / 超时 / reject),就会开启断路。 + +### SleepWindowInMilliseconds + +```java +HystrixCommandProperties.Setter() + .withCircuitBreakerSleepWindowInMilliseconds(int) +``` + +断路开启,也就是由 close 转换到 open 状态(close -> open)。那么之后在 `SleepWindowInMilliseconds` 时间内,所有经过该断路器的请求全部都会被断路,不调用后端服务,直接走 fallback 降级机制。 + +而在该参数时间过后,断路器会变为 `half-open` 半开闭状态,尝试让一条请求经过断路器,看能不能正常调用。如果调用成功了,那么就自动恢复,断路器转为 close 状态。 + +### Enabled + +```java +HystrixCommandProperties.Setter() + .withCircuitBreakerEnabled(boolean) +``` + +控制是否允许断路器工作,包括跟踪依赖服务调用的健康状况,以及对异常情况过多时是否允许触发断路。默认值是 `true`。 + +### ForceOpen + +```java +HystrixCommandProperties.Setter() + .withCircuitBreakerForceOpen(boolean) +``` + +如果设置为 true 的话,直接强迫打开断路器,相当于是手动断路了,手动降级,默认值是 `false`。 + +### ForceClosed + +```java +HystrixCommandProperties.Setter() + .withCircuitBreakerForceClosed(boolean) +``` + +如果设置为 true,直接强迫关闭断路器,相当于手动停止断路了,手动升级,默认值是 `false`。 + +## 实例 Demo + +### HystrixCommand 配置参数 +在 GetProductInfoCommand 中配置 Setter 断路器相关参数。 + +- 滑动窗口中,最少 20 个请求,才可能触发断路。 +- 异常比例达到 40% 时,才触发断路。 +- 断路后 3000ms 内,所有请求都被 reject,直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。 + +run() 方法中,我们判断一下 productId 是否为 -1,是的话,直接抛出异常。这么写,我们之后测试的时候就可以传入 productId=-1,**模拟服务执行异常**了。 + +在降级逻辑中,我们直接给它返回降级商品就好了。 + +```java +public class GetProductInfoCommand extends HystrixCommand { + + private Long productId; + + private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand"); + + public GetProductInfoCommand(Long productId) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService")) + .andCommandKey(KEY) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + // 是否允许断路器工作 + .withCircuitBreakerEnabled(true) + // 滑动窗口中,最少有多少个请求,才可能触发断路 + .withCircuitBreakerRequestVolumeThreshold(20) + // 异常比例达到多少,才触发断路,默认50% + .withCircuitBreakerErrorThresholdPercentage(40) + // 断路后多少时间内直接reject请求,之后进入half-open状态,默认5000ms + .withCircuitBreakerSleepWindowInMilliseconds(3000))); + this.productId = productId; + } + + @Override + protected ProductInfo run() throws Exception { + System.out.println("调用接口查询商品数据,productId=" + productId); + + if (productId == -1L) { + throw new Exception(); + } + + String url = "http://localhost:8081/getProductInfo?productId=" + productId; + String response = HttpClientUtils.sendGetRequest(url); + return JSONObject.parseObject(response, ProductInfo.class); + } + + @Override + protected ProductInfo getFallback() { + ProductInfo productInfo = new ProductInfo(); + productInfo.setName("降级商品"); + return productInfo; + } +} +``` + +### 断路测试类 +我们在测试类中,前 30 次请求,传入 productId=-1,然后休眠 3s,之后 70 次请求,传入 productId=1。 + +```java +@SpringBootTest +@RunWith(SpringRunner.class) +public class CircuitBreakerTest { + + @Test + public void testCircuitBreaker() { + String baseURL = "http://localhost:8080/getProductInfo?productId="; + + for (int i = 0; i < 30; ++i) { + // 传入-1,会抛出异常,然后走降级逻辑 + HttpClientUtils.sendGetRequest(baseURL + "-1"); + } + + TimeUtils.sleep(3); + System.out.println("After sleeping..."); + + for (int i = 31; i < 100; ++i) { + // 传入1,走服务正常调用 + HttpClientUtils.sendGetRequest(baseURL + "1"); + } + } +} +``` + +### 测试结果 + +测试结果,我们可以明显看出系统断路与恢复的整个过程。 + +```c +调用接口查询商品数据,productId=-1 +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +// ... +// 这里重复打印了 20 次上面的结果 + + +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +// ... +// 这里重复打印了 8 次上面的结果 + + +// 休眠 3s 后 +调用接口查询商品数据,productId=1 +ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, specification=iphone7的规格, service=iphone7的售后服务, color=红色,白色,黑色, size=5.5, shopId=1, modifiedTime=2017-01-01 12:00:00, cityId=1, cityName=null, brandId=1, brandName=null) +// ... +// 这里重复打印了 69 次上面的结果 +``` + +前 30 次请求,我们传入的 productId 为 -1,所以服务执行过程中会抛出异常。我们设置了最少 20 次请求通过断路器并且异常比例超出 40% 就触发断路。因此执行了 21 次接口调用,每次都抛异常并且走降级,21 次过后,断路器就被打开了。 + +之后的 9 次请求,都不会执行 run() 方法,也就不会打印以下信息。 + +```c +调用接口查询商品数据,productId=-1 +``` + +而是直接走降级逻辑,调用 getFallback() 执行。 + +休眠了 3s 后,我们在之后的 70 次请求中,都传入 productId 为 1。由于我们前面设置了 3000ms 过后断路器变为 `half-open` 状态。因此 Hystrix 会尝试执行请求,发现成功了,那么断路器关闭,之后的所有请求也都能正常调用了。 \ No newline at end of file diff --git a/docs/high-availability/hystrix-execution-isolation.md b/docs/high-availability/hystrix-execution-isolation.md new file mode 100644 index 0000000..44c954b --- /dev/null +++ b/docs/high-availability/hystrix-execution-isolation.md @@ -0,0 +1,106 @@ +## Hystrix 隔离策略细粒度控制 +Hystrix 实现资源隔离,有两种策略: + +- 线程池隔离 +- 信号量隔离 + +对资源隔离这一块东西,其实可以做一定细粒度的一些控制。 + +### execution.isolation.strategy +指定了 HystrixCommand.run() 的资源隔离策略:`THREAD` or `SEMAPHORE`,一种基于线程池,一种基于信号量。 + +```java +// to use thread isolation +HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD) + +// to use semaphore isolation +HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE) +``` + +线程池机制,每个 command 运行在一个线程中,限流是通过线程池的大小来控制的;信号量机制,command 是运行在调用线程中,通过信号量的容量来进行限流。 + +如何在线程池和信号量之间做选择? + +**默认的策略**就是线程池。 + +**线程池**其实最大的好处就是对于网络访问请求,如果有超时的话,可以避免调用线程阻塞住。 + +而使用信号量的场景,通常是针对超大并发量的场景下,每个服务实例每秒都几百的 `QPS`,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护。一般用信号量常见于那种基于纯内存的一些业务逻辑服务,而不涉及到任何网络访问请求。 + +### command key & command group +我们使用线程池隔离,要怎么对**依赖服务**、**依赖服务接口**、**线程池**三者做划分呢? + +每一个 command,都可以设置一个自己的名称 command key,同时可以设置一个自己的组 command group。 +```java +private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) + .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")); + +public CommandHelloWorld(String name) { + super(cachedSetter); + this.name = name; +} +``` + +command group 是一个非常重要的概念,默认情况下,就是通过 command group 来定义一个线程池的,而且还会通过 command group 来聚合一些监控和报警信息。同一个 command group 中的请求,都会进入同一个线程池中。 + +### command thread pool +ThreadPoolKey 代表了一个 HystrixThreadPool,用来进行统一监控、统计、缓存。默认的 ThreadPoolKey 就是 command group 的名称。每个 command 都会跟它的 ThreadPoolKey 对应的 ThreadPool 绑定在一起。 + +如果不想直接用 command group,也可以手动设置 ThreadPool 的名称。 +```java +private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) + .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")) + .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("HelloWorldPool")); + +public CommandHelloWorld(String name) { + super(cachedSetter); + this.name = name; +} +``` + +### command key & command group & command thread pool +**command key** ,代表了一类 command,一般来说,代表了底层的依赖服务的一个接口。 + +**command group** ,代表了某一个底层的依赖服务,这是很合理的,一个依赖服务可能会暴露出来多个接口,每个接口就是一个 command key。command group 在逻辑上去组织起来一堆 command key 的调用、统计信息、成功次数、timeout 超时次数、失败次数等,可以看到某一个服务整体的一些访问情况。一般来说,**推荐**根据一个服务区划分出一个线程池,command key 默认都是属于同一个线程池的。 + +比如说你以一个服务为粒度,估算出来这个服务每秒的所有接口加起来的整体 `QPS` 在 100 左右,你调用这个服务,当前这个服务部署了 10 个服务实例,每个服务实例上,其实用这个 command group 对应这个服务,给一个线程池,量大概在 10 个左右就可以了,你对整个服务的整体的访问 QPS 就大概在每秒 100 左右。 + +但是,如果说 command group 对应了一个服务,而这个服务暴露出来的几个接口,访问量很不一样,差异非常之大。你可能就希望在这个服务 command group 内部,包含的对应多个接口的 command key,做一些细粒度的资源隔离。就是说,对同一个服务的不同接口,使用不同的线程池。 + +``` +command key -> command group + +command key -> 自己的 thread pool key +``` + +逻辑上来说,多个 command key 属于一个command group,在做统计的时候,会放在一起统计。每个 command key 有自己的线程池,每个接口有自己的线程池,去做资源隔离和限流。 + +说白点,就是说如果你的 command key 要用自己的线程池,可以定义自己的 thread pool key,就 ok 了。 + +### coreSize +设置线程池的大小,默认是 10。一般来说,用这个默认的 10 个线程大小就够了。 +```java +HystrixThreadPoolProperties.Setter().withCoreSize(int value); +``` + +### queueSizeRejectionThreshold +如果说线程池中的 10 个线程都在工作中,没有空闲的线程来做其它的事情,此时再有请求过来,会先进入队列积压。如果说队列积压满了,再有请求过来,就直接 reject,拒绝请求,执行 fallback 降级的逻辑,快速返回。 + +![hystrix-thread-pool-queue](/images/hystrix-thread-pool-queue.png) + +控制 queue 满了之后 reject 的 threshold,因为 maxQueueSize 不允许热修改,因此提供这个参数可以热修改,控制队列的最大大小。 + +```java +HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value); +``` + +### execution.isolation.semaphore.maxConcurrentRequests +设置使用 SEMAPHORE 隔离策略的时候允许访问的最大并发量,超过这个最大并发量,请求直接被 reject。 + +这个并发量的设置,跟线程池大小的设置,应该是类似的,但是基于信号量的话,性能会好很多,而且 Hystrix 框架本身的开销会小很多。 + +默认值是 10,尽量设置的小一些,因为一旦设置的太大,而且有延时发生,可能瞬间导致 tomcat 本身的线程资源被占满。 + +```java +HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(int value); +``` \ No newline at end of file diff --git a/docs/high-availability/hystrix-fallback.md b/docs/high-availability/hystrix-fallback.md new file mode 100644 index 0000000..cb9e06c --- /dev/null +++ b/docs/high-availability/hystrix-fallback.md @@ -0,0 +1,125 @@ +## 基于本地缓存的 fallback 降级机制 +Hystrix 出现以下四种情况,都会去调用 fallback 降级机制: + +- 断路器处于打开的状态。 +- 资源池已满(线程池+队列 / 信号量)。 +- Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。 +- 访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。 + +### 两种最经典的降级机制 + +- 纯内存数据
+在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。 + +- 默认值
+fallback 降级逻辑中,也可以直接返回一个默认值。 + +在 `HystrixCommand`,降级逻辑的书写,是通过实现 getFallback() 接口;而在 `HystrixObservableCommand` 中,则是实现 resumeWithFallback() 方法。 + + +现在,我们用一个简单的栗子,来演示 fallback 降级是怎么做的。 + +比如,有这么个**场景**。我们现在有个包含 brandId 的商品数据,假设正常的逻辑是这样:拿到一个商品数据,根据 brandId 去调用品牌服务的接口,获取品牌的最新名称 brandName。 + +假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。 + +### 步骤一:本地缓存获取数据 +本地获取品牌名称的代码大致如下。 + +```java +/** + * 品牌名称本地缓存 + * + */ + +public class BrandCache { + + private static Map brandMap = new HashMap<>(); + + static { + brandMap.put(1L, "Nike"); + } + + /** + * brandId 获取 brandName + * + * @param brandId 品牌id + * @return 品牌名 + */ + public static String getBrandName(Long brandId) { + return brandMap.get(brandId); + } +``` + +### 步骤二:实现 GetBrandNameCommand +在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。 + +这里,我们直接**模拟接口调用报错**,给它抛出个异常。 + +而在 getFallback() 方法中,就是我们的**降级逻辑**,我们直接从本地的缓存中,**获取到品牌名称**的数据。 + +```java +/** + * 获取品牌名称的command + * + */ + +public class GetBrandNameCommand extends HystrixCommand { + + private Long brandId; + + public GetBrandNameCommand(Long brandId) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("BrandService")) + .andCommandKey(HystrixCommandKey.Factory.asKey("GetBrandNameCommand")) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + // 设置降级机制最大并发请求数 + .withFallbackIsolationSemaphoreMaxConcurrentRequests(15))); + this.brandId = brandId; + } + + @Override + protected String run() throws Exception { + // 这里正常的逻辑应该是去调用一个品牌服务的接口获取名称 + // 如果调用失败,报错了,那么就会去调用fallback降级机制 + + // 这里我们直接模拟调用报错,抛出异常 + throw new Exception(); + } + + @Override + protected String getFallback() { + return BrandCache.getBrandName(brandId); + } +} +``` + +`FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。 + +### 步骤三:CacheController 调用接口 +在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。 + +```java +@Controller +public class CacheController { + + @RequestMapping("/getProductInfo") + @ResponseBody + public String getProductInfo(Long productId) { + HystrixCommand getProductInfoCommand = new GetProductInfoCommand(productId); + + ProductInfo productInfo = getProductInfoCommand.execute(); + Long brandId = productInfo.getBrandId(); + + HystrixCommand getBrandNameCommand = new GetBrandNameCommand(brandId); + + // 执行会抛异常报错,然后走降级 + String brandName = getBrandNameCommand.execute(); + productInfo.setBrandName(brandName); + + System.out.println(productInfo); + return "success"; + } +} +``` + +关于降级逻辑的演示,基本上就结束了。 \ No newline at end of file diff --git a/docs/high-availability/hystrix-introduction.md b/docs/high-availability/hystrix-introduction.md index edfc8c1..9411420 100644 --- a/docs/high-availability/hystrix-introduction.md +++ b/docs/high-availability/hystrix-introduction.md @@ -1,4 +1,5 @@ ## 用 Hystrix 构建高可用服务架构 +参考 [Hystrix Home](https://github.com/Netflix/Hystrix/wiki#what)。 ### Hystrix 是什么? 在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是**依赖服务**,有的时候某些依赖服务出现故障也是很正常的。 @@ -16,6 +17,8 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外 时至今日,Netflix 中每天都有数十亿次的服务间调用,通过 Hystrix 框架在进行,而 Hystrix 也帮助 Netflix 网站提升了整体的可用性和稳定性。 +[2018 年 11 月,Hystrix 在其 Github 主页宣布,不再开放新功能,推荐开发者使用其他仍然活跃的开源项目](https://github.com/Netflix/Hystrix/blob/master/README.md#hystrix-status)。维护模式的转变绝不意味着 Hystrix 不再有价值。相反,Hystrix 激发了很多伟大的想法和项目,我们高可用的这一块知识还是会针对 Hystrix 进行讲解。 + ### Hystrix 的设计原则 - 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。 - 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。 @@ -30,7 +33,7 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外 调用服务 C,只需要 20ms,现在因为服务 C 故障了,比如延迟,或者挂了,此时线程会 hang 住 2s 左右。40 个线程全部被卡住,由于请求不断涌入,其它的线程也用来调用服务 C,同样也会被卡住。这样导致服务 B 的线程资源被耗尽,无法接收新的请求,甚至可能因为大量线程不断的运转,导致自己宕机。服务 A 也挂。 -![service-invoke-road](/img/service-invoke-road.png) +![service-invoke-road](/images/service-invoke-road.png) Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线程调用服务 C。当此 40 个线程被 hang 住时,其它 60 个线程依然能正常调用工作。从而确保整个系统不会被拖垮。 @@ -39,7 +42,7 @@ Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线 - 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。 - 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。 - 提供 fallback 降级机制来应对故障。 -- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(短路技术)来限制任何一个依赖服务的故障的影响。 +- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(断路技术)来限制任何一个依赖服务的故障的影响。 - 通过近实时的统计/监控/报警功能,来提高故障发现的速度。 - 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。 - 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。 \ No newline at end of file diff --git a/docs/high-availability/hystrix-process.md b/docs/high-availability/hystrix-process.md new file mode 100644 index 0000000..0f2f0e4 --- /dev/null +++ b/docs/high-availability/hystrix-process.md @@ -0,0 +1,160 @@ +## 深入 Hystrix 执行时内部原理 +前面我们了解了 Hystrix 最基本的支持高可用的技术:**资源隔离** + **限流**。 + +- 创建 command; +- 执行这个 command; +- 配置这个 command 对应的 group 和线程池。 + +这里,我们要讲一下,你开始执行这个 command,调用了这个 command 的 execute() 方法之后,Hystrix 底层的执行流程和步骤以及原理是什么。 + +在讲解这个流程的过程中,我会带出来 Hystrix 其他的一些核心以及重要的功能。 + +这里是整个 8 大步骤的流程图,我会对每个步骤进行细致的讲解。学习的过程中,对照着这个流程图,相信思路会比较清晰。 + +![hystrix-process](/images/hystrix-process.png) + +### 步骤一:创建 command +一个 HystrixCommand 或 HystrixObservableCommand 对象,代表了对某个依赖服务发起的一次请求或者调用。创建的时候,可以在构造函数中传入任何需要的参数。 + +- HystrixCommand 主要用于仅仅会返回一个结果的调用。 +- HystrixObservableCommand 主要用于可能会返回多条结果的调用。 + +```java +// 创建 HystrixCommand +HystrixCommand hystrixCommand = new HystrixCommand(arg1, arg2); + +// 创建 HystrixObservableCommand +HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand(arg1, arg2); +``` + +### 步骤二:调用 command 执行方法 +执行 command,就可以发起一次对依赖服务的调用。 + +要执行 command,可以在 4 个方法中选择其中的一个:execute()、queue()、observe()、toObservable()。 + +其中 execute() 和 queue() 方法仅仅对 HystrixCommand 适用。 + +- execute():调用后直接 block 住,属于同步调用,直到依赖服务返回单条结果,或者抛出异常。 +- queue():返回一个 Future,属于异步调用,后面可以通过 Future 获取单条结果。 +- observe():订阅一个 Observable 对象,Observable 代表的是依赖服务返回的结果,获取到一个那个代表结果的 Observable 对象的拷贝对象。 +- toObservable():返回一个 Observable 对象,如果我们订阅这个对象,就会执行 command 并且获取返回结果。 + +```java +K value = hystrixCommand.execute(); +Future fValue = hystrixCommand.queue(); +Observable oValue = hystrixObservableCommand.observe(); +Observable toOValue = hystrixObservableCommand.toObservable(); +``` + +execute() 实际上会调用 queue().get() 方法,可以看一下 Hystrix 源码。 +```java +public R execute() { + try { + return queue().get(); + } catch (Exception e) { + throw Exceptions.sneakyThrow(decomposeException(e)); + } +} +``` + +而在 queue() 方法中,会调用 toObservable().toBlocking().toFuture()。 +```java +final Future delegate = toObservable().toBlocking().toFuture(); +``` + +也就是说,先通过 toObservable() 获得 Future 对象,然后调用 Future 的 get() 方法。那么,其实无论是哪种方式执行 command,最终都是依赖于 toObservable() 去执行的。 + +![hystrix-process](/images/hystrix-process.png) + +### 步骤三:检查是否开启缓存 +从这一步开始,就进入到 Hystrix 底层运行原理啦,看一下 Hystrix 一些更高级的功能和特性。 + +如果这个 command 开启了请求缓存 Request Cache,而且这个调用的结果在缓存中存在,那么直接从缓存中返回结果。否则,继续往后的步骤。 + +### 步骤四:检查是否开启了断路器 +检查这个 command 对应的依赖服务是否开启了断路器。如果断路器被打开了,那么 Hystrix 就不会执行这个 command,而是直接去执行 fallback 降级机制,返回降级结果。 + +### 步骤五:检查线程池/队列/信号量是否已满 +如果这个 command 线程池和队列已满,或者 semaphore 信号量已满,那么也不会执行 command,而是直接去调用 fallback 降级机制,同时发送 reject 信息给断路器统计。 + +### 步骤六:执行 command +调用 HystrixObservableCommand 对象的 construct() 方法,或者 HystrixCommand 的 run() 方法来实际执行这个 command。 + +- HystrixCommand.run() 返回单条结果,或者抛出异常。 + +```java +// 通过command执行,获取最新一条商品数据 +ProductInfo productInfo = getProductInfoCommand.execute(); +``` + +- HystrixObservableCommand.construct() 返回一个 Observable 对象,可以获取多条结果。 + +```java +Observable observable = getProductInfosCommand.observe(); + +// 订阅获取多条结果 +observable.subscribe(new Observer() { + @Override + public void onCompleted() { + System.out.println("获取完了所有的商品数据"); + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + } + + /** + * 获取完一条数据,就回调一次这个方法 + * + * @param productInfo 商品信息 + */ + @Override + public void onNext(ProductInfo productInfo) { + System.out.println(productInfo); + } +}); +``` + +如果是采用线程池方式,并且 HystrixCommand.run() 或者 HystrixObservableCommand.construct() 的执行时间超过了 timeout 时长的话,那么 command 所在的线程会抛出一个 TimeoutException,这时会执行 fallback 降级机制,不会去管 run() 或 construct() 返回的值了。另一种情况,如果 command 执行出错抛出了其它异常,那么也会走 fallback 降级。这两种情况下,Hystrix 都会发送异常事件给断路器统计。 + +**注意**,我们是不可能终止掉一个调用严重延迟的依赖服务的线程的,只能说给你抛出来一个 TimeoutException。 + +如果没有 timeout,也正常执行的话,那么调用线程就会拿到一些调用依赖服务获取到的结果,然后 Hystrix 也会做一些 logging 记录和 metric 度量统计。 + +![hystrix-process](/images/hystrix-process.png) + +### 步骤七:断路健康检查 +Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout 等事件发送给 circuit breaker 断路器。断路器就会对这些事件的次数进行统计,根据异常事件发生的比例来决定是否要进行断路(熔断)。如果打开了断路器,那么在接下来一段时间内,会直接断路,返回降级结果。 + +如果在之后,断路器尝试执行 command,调用没有出错,返回了正常结果,那么 Hystrix 就会把断路器关闭。 + +### 步骤八:调用 fallback 降级机制 +在以下几种情况中,Hystrix 会调用 fallback 降级机制。 + +- 断路器处于打开状态; +- 线程池/队列/semaphore满了; +- command 执行超时; +- run() 或者 construct() 抛出异常。 + +一般在降级机制中,都建议给出一些默认的返回值,比如静态的一些代码逻辑,或者从内存中的缓存中提取一些数据,在这里尽量不要再进行网络请求了。 + +在降级中,如果一定要进行网络调用的话,也应该将那个调用放在一个 HystrixCommand 中进行隔离。 + +- HystrixCommand 中,实现 getFallback() 方法,可以提供降级机制。 +- HystrixObservableCommand 中,实现 resumeWithFallback() 方法,返回一个 Observable 对象,可以提供降级结果。 + +如果没有实现 fallback,或者 fallback 抛出了异常,Hystrix 会返回一个 Observable,但是不会返回任何数据。 + +不同的 command 执行方式,其 fallback 为空或者异常时的返回结果不同。 + +- 对于 execute(),直接抛出异常。 +- 对于 queue(),返回一个 Future,调用 get() 时抛出异常。 +- 对于 observe(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。 +- 对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。 + +### 不同的执行方式 +- execute(),获取一个 Future.get(),然后拿到单个结果。 +- queue(),返回一个 Future。 +- observe(),立即订阅 Observable,然后启动 8 大执行步骤,返回一个拷贝的 Observable,订阅时立即回调给你结果。 +- toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。 \ No newline at end of file diff --git a/docs/high-availability/hystrix-request-cache.md b/docs/high-availability/hystrix-request-cache.md new file mode 100644 index 0000000..04585ee --- /dev/null +++ b/docs/high-availability/hystrix-request-cache.md @@ -0,0 +1,200 @@ +## 基于 request cache 请求缓存技术优化批量商品数据查询接口 +Hystrix command 执行时 8 大步骤第三步,就是检查 Request cache 是否有缓存。 + +首先,有一个概念,叫做 Request Context 请求上下文,一般来说,在一个 web 应用中,如果我们用到了 Hystrix,我们会在一个 filter 里面,对每一个请求都施加一个请求上下文。就是说,每一次请求,就是一次请求上下文。然后在这次请求上下文中,我们会去执行 N 多代码,调用 N 多依赖服务,有的依赖服务可能还会调用好几次。 + +在一次请求上下文中,如果有多个 command,参数都是一样的,调用的接口也是一样的,而结果可以认为也是一样的。那么这个时候,我们可以让第一个 command 执行返回的结果缓存在内存中,然后这个请求上下文后续的其它对这个依赖的调用全部从内存中取出缓存结果就可以了。 + +这样的话,好处在于不用在一次请求上下文中反复多次执行一样的 command,**避免重复执行网络请求,提升整个请求的性能**。 + +举个栗子。比如说我们在一次请求上下文中,请求获取 productId 为 1 的数据,第一次缓存中没有,那么会从商品服务中获取数据,返回最新数据结果,同时将数据缓存在内存中。后续同一次请求上下文中,如果还有获取 productId 为 1 的数据的请求,直接从缓存中取就好了。 + +![hystrix-request-cache](/images/hystrix-request-cache.png) + +HystrixCommand 和 HystrixObservableCommand 都可以指定一个缓存 key,然后 Hystrix 会自动进行缓存,接着在同一个 request context 内,再次访问的话,就会直接取用缓存。 + +下面,我们结合一个具体的**业务场景**,来看一下如何使用 request cache 请求缓存技术。当然,以下代码只作为一个基本的 Demo 而已。 + +现在,假设我们要做一个**批量查询商品数据**的接口,在这个里面,我们是用 HystrixCommand 一次性批量查询多个商品 id 的数据。但是这里有个问题,如果说 Nginx 在本地缓存失效了,重新获取一批缓存,传递过来的 productIds 都没有进行去重,比如 `productIds=1,1,1,2,2`,那么可能说,商品 id 出现了重复,如果按照我们之前的业务逻辑,可能就会重复对 productId=1 的商品查询三次,productId=2 的商品查询两次。 + +我们对批量查询商品数据的接口,可以用 request cache 做一个优化,就是说一次请求,就是一次 request context,对相同的商品查询只执行一次,其余重复的都走 request cache。 + +### 实现 Hystrix 请求上下文过滤器并注册 +定义 HystrixRequestContextFilter 类,实现 Filter 接口。 + +```java +/** + * Hystrix 请求上下文过滤器 + */ +public class HystrixRequestContextFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) { + HystrixRequestContext context = HystrixRequestContext.initializeContext(); + try { + filterChain.doFilter(servletRequest, servletResponse); + } catch (IOException | ServletException e) { + e.printStackTrace(); + } finally { + context.shutdown(); + } + } + + @Override + public void destroy() { + + } +} +``` + +然后将该 filter 对象注册到 SpringBoot Application 中。 + +```java +@SpringBootApplication +public class EshopApplication { + + public static void main(String[] args) { + SpringApplication.run(EshopApplication.class, args); + } + + @Bean + public FilterRegistrationBean filterRegistrationBean() { + FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new HystrixRequestContextFilter()); + filterRegistrationBean.addUrlPatterns("/*"); + return filterRegistrationBean; + } +} +``` + +### command 重写 getCacheKey() 方法 +在 GetProductInfoCommand 中,重写 getCacheKey() 方法,这样的话,每一次请求的结果,都会放在 Hystrix 请求上下文中。下一次同一个 productId 的数据请求,直接取缓存,无须再调用 run() 方法。 + +```java +public class GetProductInfoCommand extends HystrixCommand { + + private Long productId; + + private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand"); + + public GetProductInfoCommand(Long productId) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService")) + .andCommandKey(KEY)); + this.productId = productId; + } + + @Override + protected ProductInfo run() { + String url = "http://localhost:8081/getProductInfo?productId=" + productId; + String response = HttpClientUtils.sendGetRequest(url); + System.out.println("调用接口查询商品数据,productId=" + productId); + return JSONObject.parseObject(response, ProductInfo.class); + } + + /** + * 每次请求的结果,都会放在Hystrix绑定的请求上下文上 + * + * @return cacheKey 缓存key + */ + @Override + public String getCacheKey() { + return "product_info_" + productId; + } + + /** + * 将某个商品id的缓存清空 + * + * @param productId 商品id + */ + public static void flushCache(Long productId) { + HystrixRequestCache.getInstance(KEY, + HystrixConcurrencyStrategyDefault.getInstance()).clear("product_info_" + productId); + } +} +``` + +这里写了一个 flushCache() 方法,用于我们开发手动删除缓存。 + +### controller 调用 command 查询商品信息 +在一次 web 请求上下文中,传入商品 id 列表,查询多条商品数据信息。对于每个 productId,都创建一个 command。 + +如果 id 列表没有去重,那么重复的 id,第二次查询的时候就会直接走缓存。 + +```java +@Controller +public class CacheController { + + /** + * 一次性批量查询多条商品数据的请求 + * + * @param productIds 以,分隔的商品id列表 + * @return 响应状态 + */ + @RequestMapping("/getProductInfos") + @ResponseBody + public String getProductInfos(String productIds) { + for (String productId : productIds.split(",")) { + // 对每个productId,都创建一个command + GetProductInfoCommand getProductInfoCommand = new GetProductInfoCommand(Long.valueOf(productId)); + ProductInfo productInfo = getProductInfoCommand.execute(); + System.out.println("是否是从缓存中取的结果:" + getProductInfoCommand.isResponseFromCache()); + } + + return "success"; + } +} +``` + +### 发起请求 +调用接口,查询多个商品的信息。 + +``` +http://localhost:8080/getProductInfos?productIds=1,1,1,2,2,5 +``` + +在控制台,我们可以看到以下结果。 + +``` +调用接口查询商品数据,productId=1 +是否是从缓存中取的结果:false +是否是从缓存中取的结果:true +是否是从缓存中取的结果:true +调用接口查询商品数据,productId=2 +是否是从缓存中取的结果:false +是否是从缓存中取的结果:true +调用接口查询商品数据,productId=5 +是否是从缓存中取的结果:false +``` + +第一次查询 productId=1 的数据,会调用接口进行查询,不是从缓存中取结果。而随后再出现查询 productId=1 的请求,就直接取缓存了,这样的话,效率明显高很多。 + +### 删除缓存 +我们写一个 UpdateProductInfoCommand,在更新商品信息之后,手动调用之前写的 flushCache(),手动将缓存删除。 + +```java +public class UpdateProductInfoCommand extends HystrixCommand { + + private Long productId; + + public UpdateProductInfoCommand(Long productId) { + super(HystrixCommandGroupKey.Factory.asKey("UpdateProductInfoGroup")); + this.productId = productId; + } + + @Override + protected Boolean run() throws Exception { + // 这里执行一次商品信息的更新 + // ... + + // 然后清空缓存 + GetProductInfoCommand.flushCache(productId); + return true; + } +} +``` + +这样,以后查询该商品的请求,第一次就会走接口调用去查询最新的商品信息。 \ No newline at end of file diff --git a/docs/high-availability/hystrix-semphore-isolation.md b/docs/high-availability/hystrix-semphore-isolation.md index 63e03b5..c643b5a 100644 --- a/docs/high-availability/hystrix-semphore-isolation.md +++ b/docs/high-availability/hystrix-semphore-isolation.md @@ -13,14 +13,14 @@ Hystrix 实现资源隔离,主要有两种技术: ### 信号量机制 信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。 -![hystrix-semphore](/img/hystrix-semphore.png) +![hystrix-semphore](/images/hystrix-semphore.png) ### 线程池与信号量区别 线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。 线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。 -![hystrix-semphore-thread-pool](/img/hystrix-semphore-thread-pool.png) +![hystrix-semphore-thread-pool](/images/hystrix-semphore-thread-pool.png) **适用场景**: - **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。 @@ -58,11 +58,6 @@ public class LocationCache { 写一个 GetCityNameCommand,策略设置为**信号量**。run() 方法中获取本地缓存。我们目的就是对获取本地缓存的代码进行资源隔离。 ```java -/** - * @author bingo - * @since 2018/12/29 - */ - public class GetCityNameCommand extends HystrixCommand { private Long cityId; diff --git a/docs/high-availability/hystrix-thread-pool-current-limiting.md b/docs/high-availability/hystrix-thread-pool-current-limiting.md new file mode 100644 index 0000000..4614f0a --- /dev/null +++ b/docs/high-availability/hystrix-thread-pool-current-limiting.md @@ -0,0 +1,163 @@ +## 深入 Hystrix 线程池隔离与接口限流 +前面讲了 Hystrix 的 request cache 请求缓存、fallback 优雅降级、circuit breaker 断路器快速熔断,这一讲,我们来详细说说 Hystrix 的线程池隔离与接口限流。 + +![hystrix-process](/images/hystrix-process.png) + +Hystrix 通过判断线程池或者信号量是否已满,超出容量的请求,直接 Reject 走降级,从而达到限流的作用。 + +限流是限制对后端的服务的访问量,比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制,其实是为了避免过大的流量直接打死后端的服务。 + +### 线程池隔离技术的设计 +Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃。 + +**舱壁隔离**,是说将船体内部空间区隔划分成若干个隔舱,一旦某几个隔舱发生破损进水,水流不会在其间相互流动,如此一来船舶在受损时,依然能具有足够的浮力和稳定性,进而减低立即沉船的危险。 + +![bulkhead-partition](/images/bulkhead-partition.jpg) + +Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。 + +### Hystrix 应用线程池机制的场景 +- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。 +- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。 +- client 调用库随时会变更。 +- client 调用库随时可能会增加新的网络请求的逻辑。 +- client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。 +- client 调用库一般都对调用者来说是个黑盒,包括实现细节、网络访问、默认配置等等。 +- 在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client 调用库发生了某些变化。 +- 即使 client 调用库没有改变,依赖服务本身可能有会发生逻辑上的变化。 +- 有些依赖的 client 调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确。 +- 大多数网络请求都是同步调用的。 +- 调用失败和延迟,也有可能会发生在 client 调用库本身的代码中,不一定就是发生在网络请求中。 + +简单来说,就是你必须默认 client 调用库很不靠谱,而且随时可能发生各种变化,所以就要用强制隔离的方式来确保任何服务的故障不会影响当前服务。 + +### 线程池机制的优点 +- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。 +- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。 +- 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦。 +- 如果一个 client 调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机。 +- 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层。 + +简单来说,最大的好处,就是资源隔离,确保说任何一个依赖服务故障,不会拖垮当前的这个服务。 + +### 线程池机制的缺点 +- 线程池机制最大的缺点就是增加了 CPU 的开销。
+除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。 + +- 每个 command 的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换。 +- Hystrix 官方自己做了一个多线程异步带来的额外开销统计,通过对比多线程异步调用+同步调用得出,Netflix API 每天通过 Hystrix 执行 10 亿次调用,每个服务实例有 40 个以上的线程池,每个线程池有 10 个左右的线程。)最后发现说,用 Hystrix 的额外开销,就是给请求带来了 3ms 左右的延时,最多延时在 10ms 以内,相比于可用性和稳定性的提升,这是可以接受的。 + +我们可以用 Hystrix semaphore 技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量。 + +semaphore 技术可以用来限流和削峰,但是不能用来对调研延迟的服务进行 timeout 和隔离。 + +`execution.isolation.strategy` 设置为 `SEMAPHORE`,那么 Hystrix 就会用 semaphore 机制来替代线程池机制,来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候,底层的网络调用延迟很严重,那么是无法 timeout 的,只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后,就会立即开启限流。 + +### 接口限流 Demo +假设一个线程池大小为 8,等待队列的大小为 10。timeout 时长我们设置长一些,20s。 + +在 command 内部,写死代码,做一个 sleep,比如 sleep 3s。 + +- withCoreSize:设置线程池大小。 +- withMaxQueueSize:设置等待队列大小。 +- withQueueSizeRejectionThreshold:这个与 withMaxQueueSize 配合使用,等待队列的大小,取得是这两个参数的较小值。 + +如果只设置了线程池大小,另外两个 queue 相关参数没有设置的话,等待队列是处于关闭的状态。 + +```java +public class GetProductInfoCommand extends HystrixCommand { + + private Long productId; + + private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand"); + + public GetProductInfoCommand(Long productId) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService")) + .andCommandKey(KEY) + // 线程池相关配置信息 + .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() + // 设置线程池大小为8 + .withCoreSize(8) + // 设置等待队列大小为10 + .withMaxQueueSize(10) + .withQueueSizeRejectionThreshold(12)) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withCircuitBreakerEnabled(true) + .withCircuitBreakerRequestVolumeThreshold(20) + .withCircuitBreakerErrorThresholdPercentage(40) + .withCircuitBreakerSleepWindowInMilliseconds(3000) + // 设置超时时间 + .withExecutionTimeoutInMilliseconds(20000) + // 设置fallback最大请求并发数 + .withFallbackIsolationSemaphoreMaxConcurrentRequests(30))); + this.productId = productId; + } + + @Override + protected ProductInfo run() throws Exception { + System.out.println("调用接口查询商品数据,productId=" + productId); + + if (productId == -1L) { + throw new Exception(); + } + + // 请求过来,会在这里hang住3秒钟 + if (productId == -2L) { + TimeUtils.sleep(3); + } + + String url = "http://localhost:8081/getProductInfo?productId=" + productId; + String response = HttpClientUtils.sendGetRequest(url); + System.out.println(response); + return JSONObject.parseObject(response, ProductInfo.class); + } + + @Override + protected ProductInfo getFallback() { + ProductInfo productInfo = new ProductInfo(); + productInfo.setName("降级商品"); + return productInfo; + } +} +``` + +我们模拟 25 个请求。前 8 个请求,调用接口时会直接被 hang 住 3s,那么后面的 10 个请求会先进入等待队列中等待前面的请求执行完毕。最后的 7 个请求过来,会直接被 reject,调用 fallback 降级逻辑。 + +```java +@SpringBootTest +@RunWith(SpringRunner.class) +public class RejectTest { + + @Test + public void testReject() { + for (int i = 0; i < 25; ++i) { + new Thread(() -> HttpClientUtils.sendGetRequest("http://localhost:8080/getProductInfo?productId=-2")).start(); + } + // 防止主线程提前结束执行 + TimeUtils.sleep(50); + } +} +``` + +从执行结果中,我们可以明显看出一共打印出了 7 个降级商品。这也就是请求数超过线程池+队列的数量而直接被 reject 的结果。 + +```c +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +调用接口查询商品数据,productId=-2 +调用接口查询商品数据,productId=-2 +调用接口查询商品数据,productId=-2 +调用接口查询商品数据,productId=-2 +调用接口查询商品数据,productId=-2 +调用接口查询商品数据,productId=-2 +调用接口查询商品数据,productId=-2 +调用接口查询商品数据,productId=-2 +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +{"id": -2, "name": "iphone7手机", "price": 5599, "pictureList":"a.jpg,b.jpg", "specification": "iphone7的规格", "service": "iphone7的售后服务", "color": "红色,白色,黑色", "size": "5.5", "shopId": 1, "modifiedTime": "2017-01-01 12:00:00", "cityId": 1, "brandId": 1} +// 后面都是一些正常的商品信息,就不贴出来了 +//... +``` \ No newline at end of file diff --git a/docs/high-availability/hystrix-thread-pool-isolation.md b/docs/high-availability/hystrix-thread-pool-isolation.md index 84a7357..fc5a75e 100644 --- a/docs/high-availability/hystrix-thread-pool-isolation.md +++ b/docs/high-availability/hystrix-thread-pool-isolation.md @@ -1,5 +1,4 @@ ## 基于 Hystrix 线程池技术实现资源隔离 - 上一讲提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。 资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是线程池内就 10 个线程,最多就只会用这 10 个线程去执行,不会说,对商品服务的请求,因为接口调用延时,将 tomcat 内部所有的线程资源全部耗尽。 @@ -111,6 +110,6 @@ public String getProductInfos(String productIds) { 我们回过头来,看看 Hystrix 线程池技术是如何实现资源隔离的。 -![hystrix-thread-pool-isolation](/img/hystrix-thread-pool-isolation.png) +![hystrix-thread-pool-isolation](/images/hystrix-thread-pool-isolation.png) 从 Nginx 开始,缓存都失效了,那么 Nginx 通过缓存服务去调用商品服务。缓存服务默认的线程大小是 10 个,最多就只有 10 个线程去调用商品服务的接口。即使商品服务接口故障了,最多就只有 10 个线程会 hang 死在调用商品服务接口的路上,缓存服务的 tomcat 内其它的线程还是可以用来调用其它的服务,干其它的事情。 \ No newline at end of file diff --git a/docs/high-availability/hystrix-timeout.md b/docs/high-availability/hystrix-timeout.md new file mode 100644 index 0000000..8911b7c --- /dev/null +++ b/docs/high-availability/hystrix-timeout.md @@ -0,0 +1,103 @@ +## 基于 timeout 机制为服务接口调用超时提供安全保护 +一般来说,在调用依赖服务的接口的时候,比较常见的一个问题就是**超时**。超时是在一个复杂的分布式系统中,导致系统不稳定,或者系统抖动。出现大量超时,线程资源会被 hang 死,从而导致吞吐量大幅度下降,甚至服务崩溃。 + +你去调用各种各样的依赖服务,特别是在大公司,你甚至都不认识开发一个服务的人,你都不知道那个人的技术水平怎么样,对那个人根本不了解。 + +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) + +像特别复杂的分布式系统,特别是在大公司里,多个团队、大型协作,你可能都不知道服务是谁的,很可能说开发服务的那个哥儿们甚至是一个实习生。依赖服务的接口性能可能很不稳定,有时候 2ms,有时候 200ms,甚至 2s,都有可能。 + +如果你不对各种依赖服务接口的调用做超时控制,来给你的服务提供安全保护措施,那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了。大量的接口调用很慢,大量的线程被卡死。如果你做了资源的隔离,那么也就是线程池的线程被卡死,但其实我们可以做超时控制,没必要让它们全卡死。 + +### TimeoutMilliseconds +在 Hystrix 中,我们可以手动设置 timeout 时长,如果一个 command 运行时间超过了设定的时长,那么就被认为是 timeout,然后 Hystrix command 标识为 timeout,同时执行 fallback 降级逻辑。 + +`TimeoutMilliseconds` 默认值是 1000,也就是 1000ms。 + +```java +HystrixCommandProperties.Setter() + ..withExecutionTimeoutInMilliseconds(int) +``` + +### TimeoutEnabled +这个参数用于控制是否要打开 timeout 机制,默认值是 true。 + +```java +HystrixCommandProperties.Setter() + .withExecutionTimeoutEnabled(boolean) +``` + +## 实例 Demo +我们在 command 中,将超时时间设置为 500ms,然后在 run() 方法中,设置休眠时间 1s,这样一个请求过来,直接休眠 1s,结果就会因为超时而执行降级逻辑。 + +```java +public class GetProductInfoCommand extends HystrixCommand { + + private Long productId; + + private static final HystrixCommandKey KEY = HystrixCommandKey.Factory.asKey("GetProductInfoCommand"); + + public GetProductInfoCommand(Long productId) { + super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductInfoService")) + .andCommandKey(KEY) + .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() + .withCoreSize(8) + .withMaxQueueSize(10) + .withQueueSizeRejectionThreshold(8)) + .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() + .withCircuitBreakerEnabled(true) + .withCircuitBreakerRequestVolumeThreshold(20) + .withCircuitBreakerErrorThresholdPercentage(40) + .withCircuitBreakerSleepWindowInMilliseconds(3000) + // 设置是否打开超时,默认是true + .withExecutionTimeoutEnabled(true) + // 设置超时时间,默认1000(ms) + .withExecutionTimeoutInMilliseconds(500) + .withFallbackIsolationSemaphoreMaxConcurrentRequests(30))); + this.productId = productId; + } + + @Override + protected ProductInfo run() throws Exception { + System.out.println("调用接口查询商品数据,productId=" + productId); + + // 休眠1s + TimeUtils.sleep(1); + + String url = "http://localhost:8081/getProductInfo?productId=" + productId; + String response = HttpClientUtils.sendGetRequest(url); + System.out.println(response); + return JSONObject.parseObject(response, ProductInfo.class); + } + + @Override + protected ProductInfo getFallback() { + ProductInfo productInfo = new ProductInfo(); + productInfo.setName("降级商品"); + return productInfo; + } +} +``` + +在测试类中,我们直接发起请求。 + +```java +@SpringBootTest +@RunWith(SpringRunner.class) +public class TimeoutTest { + + @Test + public void testTimeout() { + HttpClientUtils.sendGetRequest("http://localhost:8080/getProductInfo?productId=1"); + } +} +``` + +结果中可以看到,打印出了降级商品相关信息。 + +```c +ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) +{"id": 1, "name": "iphone7手机", "price": 5599, "pictureList":"a.jpg,b.jpg", "specification": "iphone7的规格", "service": "iphone7的售后服务", "color": "红色,白色,黑色", "size": "5.5", "shopId": 1, "modifiedTime": "2017-01-01 12:00:00", "cityId": 1, "brandId": 1} +``` \ No newline at end of file diff --git a/docs/high-availability/images/220px-Internet_dog.jpg b/docs/high-availability/images/220px-Internet_dog.jpg new file mode 100644 index 0000000..676cc28 Binary files /dev/null and b/docs/high-availability/images/220px-Internet_dog.jpg differ diff --git a/docs/high-availability/img/async-replication-data-lose-case.png b/docs/high-availability/images/async-replication-data-lose-case.png similarity index 100% rename from docs/high-availability/img/async-replication-data-lose-case.png rename to docs/high-availability/images/async-replication-data-lose-case.png diff --git a/docs/high-availability/images/bulkhead-partition.jpg b/docs/high-availability/images/bulkhead-partition.jpg new file mode 100644 index 0000000..c35653e Binary files /dev/null and b/docs/high-availability/images/bulkhead-partition.jpg differ diff --git a/docs/high-availability/img/consistent-hashing-algorithm.png b/docs/high-availability/images/consistent-hashing-algorithm.png similarity index 100% rename from docs/high-availability/img/consistent-hashing-algorithm.png rename to docs/high-availability/images/consistent-hashing-algorithm.png diff --git a/docs/high-availability/img/distributed-system-request-sequence.png b/docs/high-availability/images/distributed-system-request-sequence.png similarity index 100% rename from docs/high-availability/img/distributed-system-request-sequence.png rename to docs/high-availability/images/distributed-system-request-sequence.png diff --git a/docs/high-availability/img/distributed-transaction-TCC.png b/docs/high-availability/images/distributed-transaction-TCC.png similarity index 100% rename from docs/high-availability/img/distributed-transaction-TCC.png rename to docs/high-availability/images/distributed-transaction-TCC.png diff --git a/docs/high-availability/img/distributed-transaction-XA.png b/docs/high-availability/images/distributed-transaction-XA.png similarity index 100% rename from docs/high-availability/img/distributed-transaction-XA.png rename to docs/high-availability/images/distributed-transaction-XA.png diff --git a/docs/high-availability/img/distributed-transaction-local-message-table.png b/docs/high-availability/images/distributed-transaction-local-message-table.png similarity index 100% rename from docs/high-availability/img/distributed-transaction-local-message-table.png rename to docs/high-availability/images/distributed-transaction-local-message-table.png diff --git a/docs/high-availability/img/distributed-transaction-reliable-message.png b/docs/high-availability/images/distributed-transaction-reliable-message.png similarity index 100% rename from docs/high-availability/img/distributed-transaction-reliable-message.png rename to docs/high-availability/images/distributed-transaction-reliable-message.png diff --git a/docs/high-availability/img/dubbo-operating-principle.png b/docs/high-availability/images/dubbo-operating-principle.png similarity index 100% rename from docs/high-availability/img/dubbo-operating-principle.png rename to docs/high-availability/images/dubbo-operating-principle.png diff --git a/docs/high-availability/img/dubbo-service-invoke-road.png b/docs/high-availability/images/dubbo-service-invoke-road.png similarity index 100% rename from docs/high-availability/img/dubbo-service-invoke-road.png rename to docs/high-availability/images/dubbo-service-invoke-road.png diff --git a/docs/high-availability/img/dubbo-spi.png b/docs/high-availability/images/dubbo-spi.png similarity index 100% rename from docs/high-availability/img/dubbo-spi.png rename to docs/high-availability/images/dubbo-spi.png diff --git a/docs/high-availability/img/e-commerce-website-detail-page-architecture-1.png b/docs/high-availability/images/e-commerce-website-detail-page-architecture-1.png similarity index 100% rename from docs/high-availability/img/e-commerce-website-detail-page-architecture-1.png rename to docs/high-availability/images/e-commerce-website-detail-page-architecture-1.png diff --git a/docs/high-availability/img/e-commerce-website-detail-page-architecture-2.png b/docs/high-availability/images/e-commerce-website-detail-page-architecture-2.png similarity index 100% rename from docs/high-availability/img/e-commerce-website-detail-page-architecture-2.png rename to docs/high-availability/images/e-commerce-website-detail-page-architecture-2.png diff --git a/docs/high-availability/img/favicon-16x16.png b/docs/high-availability/images/favicon-16x16.png similarity index 100% rename from docs/high-availability/img/favicon-16x16.png rename to docs/high-availability/images/favicon-16x16.png diff --git a/docs/high-availability/img/favicon-32x32.png b/docs/high-availability/images/favicon-32x32.png similarity index 100% rename from docs/high-availability/img/favicon-32x32.png rename to docs/high-availability/images/favicon-32x32.png diff --git a/docs/high-availability/img/hash-slot.png b/docs/high-availability/images/hash-slot.png similarity index 100% rename from docs/high-availability/img/hash-slot.png rename to docs/high-availability/images/hash-slot.png diff --git a/docs/high-availability/img/hash.png b/docs/high-availability/images/hash.png similarity index 100% rename from docs/high-availability/img/hash.png rename to docs/high-availability/images/hash.png diff --git a/docs/high-availability/images/hystrix-process.png b/docs/high-availability/images/hystrix-process.png new file mode 100644 index 0000000..9c8d0b7 Binary files /dev/null and b/docs/high-availability/images/hystrix-process.png differ diff --git a/docs/high-availability/images/hystrix-request-cache.png b/docs/high-availability/images/hystrix-request-cache.png new file mode 100644 index 0000000..4e670d4 Binary files /dev/null and b/docs/high-availability/images/hystrix-request-cache.png differ diff --git a/docs/high-availability/img/hystrix-semphore-thread-pool.png b/docs/high-availability/images/hystrix-semphore-thread-pool.png similarity index 100% rename from docs/high-availability/img/hystrix-semphore-thread-pool.png rename to docs/high-availability/images/hystrix-semphore-thread-pool.png diff --git a/docs/high-availability/img/hystrix-semphore.png b/docs/high-availability/images/hystrix-semphore.png similarity index 100% rename from docs/high-availability/img/hystrix-semphore.png rename to docs/high-availability/images/hystrix-semphore.png diff --git a/docs/high-availability/img/hystrix-thread-pool-isolation.png b/docs/high-availability/images/hystrix-thread-pool-isolation.png similarity index 100% rename from docs/high-availability/img/hystrix-thread-pool-isolation.png rename to docs/high-availability/images/hystrix-thread-pool-isolation.png diff --git a/docs/high-availability/images/hystrix-thread-pool-queue.png b/docs/high-availability/images/hystrix-thread-pool-queue.png new file mode 100644 index 0000000..914e450 Binary files /dev/null and b/docs/high-availability/images/hystrix-thread-pool-queue.png differ diff --git a/docs/high-availability/images/icon.png b/docs/high-availability/images/icon.png new file mode 100644 index 0000000..7070965 Binary files /dev/null and b/docs/high-availability/images/icon.png differ diff --git a/docs/high-availability/img/service-invoke-road.png b/docs/high-availability/images/service-invoke-road.png similarity index 100% rename from docs/high-availability/img/service-invoke-road.png rename to docs/high-availability/images/service-invoke-road.png diff --git a/docs/high-availability/img/simple-distributed-system-oa.png b/docs/high-availability/images/simple-distributed-system-oa.png similarity index 100% rename from docs/high-availability/img/simple-distributed-system-oa.png rename to docs/high-availability/images/simple-distributed-system-oa.png diff --git a/docs/high-availability/img/zookeeper-active-standby.png b/docs/high-availability/images/zookeeper-active-standby.png similarity index 100% rename from docs/high-availability/img/zookeeper-active-standby.png rename to docs/high-availability/images/zookeeper-active-standby.png diff --git a/docs/high-availability/img/zookeeper-centralized-storage.png b/docs/high-availability/images/zookeeper-centralized-storage.png similarity index 100% rename from docs/high-availability/img/zookeeper-centralized-storage.png rename to docs/high-availability/images/zookeeper-centralized-storage.png diff --git a/docs/high-availability/img/zookeeper-distributed-coordination.png b/docs/high-availability/images/zookeeper-distributed-coordination.png similarity index 100% rename from docs/high-availability/img/zookeeper-distributed-coordination.png rename to docs/high-availability/images/zookeeper-distributed-coordination.png diff --git a/docs/high-availability/img/zookeeper-distributed-lock-demo.png b/docs/high-availability/images/zookeeper-distributed-lock-demo.png similarity index 100% rename from docs/high-availability/img/zookeeper-distributed-lock-demo.png rename to docs/high-availability/images/zookeeper-distributed-lock-demo.png diff --git a/docs/high-availability/img/zookeeper-distributed-lock.png b/docs/high-availability/images/zookeeper-distributed-lock.png similarity index 100% rename from docs/high-availability/img/zookeeper-distributed-lock.png rename to docs/high-availability/images/zookeeper-distributed-lock.png diff --git a/docs/high-availability/img/zookeeper-meta-data-manage.png b/docs/high-availability/images/zookeeper-meta-data-manage.png similarity index 100% rename from docs/high-availability/img/zookeeper-meta-data-manage.png rename to docs/high-availability/images/zookeeper-meta-data-manage.png diff --git a/docs/high-concurrency/README.md b/docs/high-concurrency/README.md new file mode 100644 index 0000000..22afbf7 --- /dev/null +++ b/docs/high-concurrency/README.md @@ -0,0 +1 @@ +# 高并发架构 \ 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 3cc8c8a..5fc0d1a 100644 --- a/docs/high-concurrency/database-shard-dynamic-expand.md +++ b/docs/high-concurrency/database-shard-dynamic-expand.md @@ -30,13 +30,13 @@ 我可以告诉各位同学,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支撑还是数据量支撑都没问题。 -每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500 = 48000 的写并发,接近 5万/s 的写入并发,前面再加一个MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。 +每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500 = 48000 的写并发,接近 5 万每秒的写入并发,前面再加一个MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。 有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128个库,256个库,512个库。 1024 张表,假设每个表放 500 万数据,在 MySQL 里可以放 50 亿条数据。 -每秒的 5 万写并发,总共 50 亿条数据,对于国内大部分的互联网公司来说,其实一般来说都够了。 +每秒 5 万的写并发,总共 50 亿条数据,对于国内大部分的互联网公司来说,其实一般来说都够了。 谈分库分表的扩容,**第一次分库分表,就一次性给他分个够**,32 个库,1024 张表,可能对大部分的中小型互联网公司来说,已经可以支撑好几年了。 diff --git a/docs/high-concurrency/database-shard-global-id-generate.md b/docs/high-concurrency/database-shard-global-id-generate.md index 772893d..ea25594 100644 --- a/docs/high-concurrency/database-shard-global-id-generate.md +++ b/docs/high-concurrency/database-shard-global-id-generate.md @@ -5,17 +5,27 @@ 其实这是分库分表之后你必然要面对的一个问题,就是 id 咋生成?因为要是分成多个表之后,每个表都是从 1 开始累加,那肯定不对啊,需要一个**全局唯一**的 id 来支持。所以这都是你实际生产环境中必须考虑的问题。 ## 面试题剖析 -### 数据库自增 id +### 基于数据库的实现方案 +#### 数据库自增 id 这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。 这个方案的好处就是方便简单,谁都会用;**缺点就是单库生成**自增 id,要是高并发的话,就会有瓶颈的;如果你硬是要改进一下,那么就专门开一个服务出来,这个服务每次就拿到当前 id 最大值,然后自己递增几个 id,一次性返回一批 id,然后再把当前最大 id 值修改成递增几个 id 之后的一个值;但是**无论如何都是基于单个数据库**。 **适合的场景**:你分库分表就俩原因,要不就是单库并发太高,要不就是单库数据量太大;除非是你**并发不高,但是数据量太大**导致的分库分表扩容,你可以用这个方案,因为可能每秒最高并发最多就几百,那么就走单独的一个库和表生成自增主键即可。 -### uuid -好处就是本地生成,不要基于数据库来了;不好之处就是,uuid 太长了,**作为主键性能太差**了,不适合用于主键。 +#### 设置数据库 sequence 或者表自增字段步长 +可以通过设置数据库 sequence 或者表的自增字段步长来进行水平伸缩。 -适合的场景:如果你是要随机生成个什么文件名了,编号之类的,你可以用uuid,但是作为主键是不能用uuid的。 +比如说,现在有 8 个服务节点,每个服务节点使用一个 sequence 功能来产生 ID,每个 sequence 的起始 ID 不同,并且依次递增,步长都是 8。 + +![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 @@ -24,10 +34,11 @@ UUID.randomUUID().toString().replace(“-”, “”) -> sfsdf23423rr234sfdaf ### 获取系统当前时间 这个就是获取当前时间即可,但是问题是,**并发很高的时候**,比如一秒并发几千,**会有重复的情况**,这个是肯定不合适的。基本就不用考虑了。 -适合的场景:一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号。 +适合的场景:一般如果用这个方案,是将当前时间跟很多其他的业务字段拼接起来,作为一个 id,如果业务上你觉得可以接受,那么也是可以的。你可以将别的业务字段值跟当前时间拼接起来,组成一个全局唯一的编号。 ### snowflake 算法 -snowflake 算法是 twitter 开源的分布式 id 生成算法,就是把一个 64 位的 long 型的 id,1 个bit是不用的,用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。 +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台机器)。 @@ -151,10 +162,10 @@ public class IdWorker { ``` -怎么说呢,大概这个意思吧,就是说 41 bit 是当前毫秒单位的一个时间戳,就这意思;然后 5 bit 是你传递进来的一个机房 id(但是最大只能是32以内),5 bit 是你传递进来的机器 id(但是最大只能是32以内),剩下的那个 12 bit序列号,就是如果跟你上次生成 id 的时间还在一个毫秒内,那么会把顺序给你累加,最多在 4096 个序号以内。 +怎么说呢,大概这个意思吧,就是说 41 bit 是当前毫秒单位的一个时间戳,就这意思;然后 5 bit 是你传递进来的一个**机房** id(但是最大只能是 32 以内),另外 5 bit 是你传递进来的**机器** id(但是最大只能是 32 以内),剩下的那个 12 bit序列号,就是如果跟你上次生成 id 的时间还在一个毫秒内,那么会把顺序给你累加,最多在 4096 个序号以内。 所以你自己利用这个工具类,自己搞一个服务,然后对每个机房的每个机器都初始化这么一个东西,刚开始这个机房的这个机器的序号就是 0。然后每次接收到一个请求,说这个机房的这个机器要生成一个 id,你就找到对应的 Worker 生成。 -利用这个 snowflake 算法,你可以开发自己公司的服务,甚至对于机房 id 和机器 id,反正给你预留了5 bit + 5 bit,你换成别的有业务含义的东西也可以的。 +利用这个 snowflake 算法,你可以开发自己公司的服务,甚至对于机房 id 和机器 id,反正给你预留了 5 bit + 5 bit,你换成别的有业务含义的东西也可以的。 这个 snowflake 算法相对来说还是比较靠谱的,所以你要真是搞分布式 id 生成,如果是高并发啥的,那么用这个应该性能比较好,一般每秒几万并发的场景,也足够你用了。 \ No newline at end of file diff --git a/docs/high-concurrency/database-shard-method.md b/docs/high-concurrency/database-shard-method.md index 73fedd5..590dbec 100644 --- a/docs/high-concurrency/database-shard-method.md +++ b/docs/high-concurrency/database-shard-method.md @@ -20,7 +20,7 @@ 但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。 -![database-shard-method-1](/img/database-shard-method-1.png) +![database-shard-method-1](/images/database-shard-method-1.png) ### 双写迁移方案 这个是我们常用的一种迁移方案,比较靠谱一些,不用停机,不用看北京凌晨 4 点的风景。 @@ -33,4 +33,4 @@ 接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干的。 -![database-shard-method-2](/img/database-shard-method-2.png) \ No newline at end of file +![database-shard-method-2](/images/database-shard-method-2.png) \ No newline at end of file diff --git a/docs/high-concurrency/database-shard.md b/docs/high-concurrency/database-shard.md index d3412a6..da58313 100644 --- a/docs/high-concurrency/database-shard.md +++ b/docs/high-concurrency/database-shard.md @@ -46,45 +46,45 @@ 比较常见的包括: -- cobar +- Cobar - TDDL -- atlas -- sharding-jdbc -- mycat +- Atlas +- Sharding-jdbc +- Mycat -#### cobar -阿里 b2b 团队开发和开源的,属于 proxy 层方案。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。 +#### Cobar +阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 Cobar 集群,Cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。 #### TDDL 淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多,因为还依赖淘宝的 diamond 配置管理系统。 -#### atlas +#### Atlas 360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。 -#### sharding-jdbc -当当开源的,属于 client 层方案。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且目前推出到了 2.0 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也**可以选择的方案**。 +#### Sharding-jdbc +当当开源的,属于 client 层方案,目前已经更名为 [`ShardingSphere`](https://github.com/apache/incubator-shardingsphere)(后文所提到的 `Sharding-jdbc`,等同于 `ShardingSphere`)。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且截至 2019.4,已经推出到了 `4.0.0-RC1` 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务(最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也**可以选择的方案**。 -#### mycat -基于 cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 sharding jdbc 来说,年轻一些,经历的锤炼少一些。 +#### Mycat +基于 Cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 Sharding jdbc 来说,年轻一些,经历的锤炼少一些。 #### 总结 -综上,现在其实建议考量的,就是 sharding-jdbc 和 mycat,这两个都可以去考虑使用。 +综上,现在其实建议考量的,就是 Sharding-jdbc 和 Mycat,这两个都可以去考虑使用。 -sharding-jdbc 这种 client 层方案的**优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高**,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要**耦合** sharding-jdbc 的依赖; +Sharding-jdbc 这种 client 层方案的**优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高**,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要**耦合** Sharding-jdbc 的依赖; -mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套中间件,运维成本高,但是**好处在于对于各个项目是透明的**,如果遇到升级之类的都是自己中间件那里搞就行了。 +Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套中间件,运维成本高,但是**好处在于对于各个项目是透明的**,如果遇到升级之类的都是自己中间件那里搞就行了。 -通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用 sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用 mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 mycat,然后大量项目直接透明使用即可。 +通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用 Sharding-jdbc,client 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用 Mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 Mycat,然后大量项目直接透明使用即可。 ### 你们具体是如何对数据库如何进行垂直拆分或水平拆分的? -**水平拆分**的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来抗更高的并发,还有就是用多个库的存储容量来进行扩容。 +**水平拆分**的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。 -![database-split-horizon](/img/database-split-horizon.png) +![database-split-horizon](/images/database-split-horizon.png) **垂直拆分**的意思,就是**把一个有很多字段的表给拆分成多个表**,**或者是多个库上去**。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会**将较少的访问频率很高的字段放到一个表里去**,然后**将较多的访问频率很低的字段放到另外一个表里去**。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。 -![database-split-vertically](/img/database-split-vertically.png) +![database-split-vertically](/images/database-split-vertically.png) 这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。 @@ -92,14 +92,14 @@ mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套 好了,无论分库还是分表,上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后,**中间件可以根据你指定的某个字段值**,比如说 userid,**自动路由到对应的库上去,然后再自动路由到对应的表里去**。 -你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都ok了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。 +你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都 ok 了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。 而且这儿还有两种**分库分表的方式**: - 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。 -- 或者是按照某个字段hash一下均匀分散,这个较为常用。 +- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。 range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。 -hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表。 \ No newline at end of file +hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表。 diff --git a/docs/high-concurrency/es-architecture.md b/docs/high-concurrency/es-architecture.md index 9c1209d..e92634e 100644 --- a/docs/high-concurrency/es-architecture.md +++ b/docs/high-concurrency/es-architecture.md @@ -19,22 +19,21 @@ es 中存储数据的**基本单位是索引**,比如说你现在要在 es 中 index -> type -> mapping -> document -> field。 ``` -这样吧,为了做个更直白的介绍,我在这里做个类比。 +这样吧,为了做个更直白的介绍,我在这里做个类比。但是切记,不要划等号,类比只是为了便于理解。 index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,一个 index 里可以有多个 type,每个 type 的字段都是差不多的,但是有一些略微的差别。假设有一个 index,是订单 index,里面专门是放订单数据的。就好比说你在 mysql 中建表,有些订单是实物商品的订单,比如一件衣服、一双鞋子;有些订单是虚拟商品的订单,比如游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字段可能有略微的一些差别。 所以就会在订单 index 里,建两个 type,一个是实物商品订单 type,一个是虚拟商品订单 type,这两个 type 大部分字段是一样的,少部分字段是不一样的。 -很多情况下,一个 index 里可能就一个 type,但是确实如果说是一个 index 里有多个 type 的情况,你可以认为 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](/img/es-index-type-mapping-document-field.png) - -你搞一个索引,这个索引可以拆分成多个 `shard`,每个 shard 存储部分数据。 +![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 都有一个 `primary shard`,负责写入数据,但是还有几个 `replica shard`。`primary shard` 写入数据之后,会将数据同步到其他几个 `replica shard` 上去。 -![es-cluster](/img/es-cluster.png) +![es-cluster](/images/es-cluster.png) 通过这个 replica 的方案,每个 shard 的数据都有多个备份,如果某个机器宕机了,没关系啊,还有别的数据副本在别的机器上呢。高可用了吧。 @@ -44,4 +43,4 @@ es 集群多个节点,会自动选举一个节点为 master 节点,这个 ma 说得更简单一点,就是说如果某个非 master 节点宕机了。那么此节点上的 primary shard 不就没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。 -其实上述就是 ElasticSearch 作为一个分布式搜索引擎最基本的一个架构设计。 \ No newline at end of file +其实上述就是 ElasticSearch 作为分布式搜索引擎最基本的一个架构设计。 \ No newline at end of file diff --git a/docs/high-concurrency/es-introduction.md b/docs/high-concurrency/es-introduction.md index 7fcb251..c255a82 100644 --- a/docs/high-concurrency/es-introduction.md +++ b/docs/high-concurrency/es-introduction.md @@ -47,7 +47,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分 这么说吧,shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard,而 replica shard 一般简称为 replica。 -![es-cluster-0](/img/es-cluster-0.png) +![es-cluster-0](/images/es-cluster-0.png) ## es 核心概念 vs. db 核心概念 | es | db | diff --git a/docs/high-concurrency/es-optimizing-query-performance.md b/docs/high-concurrency/es-optimizing-query-performance.md index d974e90..e8b7081 100644 --- a/docs/high-concurrency/es-optimizing-query-performance.md +++ b/docs/high-concurrency/es-optimizing-query-performance.md @@ -10,9 +10,9 @@ es 在数据量很大的情况下(数十亿级别)如何提高查询效率 说实话,es 性能优化是没有什么银弹的,啥意思呢?就是**不要期待着随手调一个参数,就可以万能的应对所有的性能慢的场景**。也许有的场景是你换个参数,或者调整一下语法,就可以搞定,但是绝对不是所有场景都可以这样。 ### 性能优化的杀手锏——filesystem cache -你往es里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 `filesystem cache` 里面去。 +你往 es 里写的数据,实际上都写到磁盘文件里去了,**查询的时候**,操作系统会将磁盘文件里的数据自动缓存到 `filesystem cache` 里面去。 -![es-search-process](/img/es-search-process.png) +![es-search-process](/images/es-search-process.png) es 的搜索引擎严重依赖于底层的 `filesystem cache`,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file ` 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。 @@ -24,11 +24,11 @@ es 的搜索引擎严重依赖于底层的 `filesystem cache`,你如果给 `fi 根据我们自己的生产环境实践经验,最佳的情况下,是仅仅在 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` 对应的**完整的数据**,给查出来,再返回给前端。 -写入 es 的数据最好小于等于,或者是略微大于 es 的 filesystem cache 的内存容量。然后你从 es 检索可能就花费 20ms,然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,可能你原来那么玩儿,1T 数据都放es,会每次查询都是 5~10秒,现在可能性能就会很高,每次查询就是 50ms。 +写入 es 的数据最好小于等于,或者是略微大于 es 的 filesystem cache 的内存容量。然后你从 es 检索可能就花费 20ms,然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,可能你原来那么玩儿,1T 数据都放 es,会每次查询都是 5~10s,现在可能性能就会很高,每次查询就是 50ms。 ### 数据预热 假如说,哪怕是你就按照上述的方案去做了,es 集群中每个机器写入的数据量还是超过了 `filesystem cache` 一倍,比如说你写入一台机器 60G 数据,结果 `filesystem cache` 就 30G,还是有 30G 数据留在了磁盘上。 @@ -39,36 +39,38 @@ hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可 或者是电商,你可以将平时查看最多的一些商品,比如说 iphone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 `filesystem cache` 里去。 -对于那些你觉得比较热的,经常会有人访问的数据,最好**做一个专门的缓存预热子系统**,就是对热数据每隔一段时间,就提前访问一下,让数据进入 `filesystem cache` 里面去。这样下次别人访问的时候,一定性能会好一些。 +对于那些你觉得比较热的、经常会有人访问的数据,最好**做一个专门的缓存预热子系统**,就是对热数据每隔一段时间,就提前访问一下,让数据进入 `filesystem cache` 里面去。这样下次别人访问的时候,性能一定会好很多。 ### 冷热分离 es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将**冷数据写入一个索引中,然后热数据写入另外一个索引中**,这样可以确保热数据在被预热之后,尽量都让他们留在 `filesystem os cache` 里,**别让冷数据给冲刷掉**。 -你看,假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据,每个索引 3 个shard。3 台机器放热数据 index;另外 3 台机器放冷数据 index。然后这样的话,你大量的时候是在访问热数据 index,热数据可能就占总数据量的 10%,此时数据量很少,几乎全都保留在 `filesystem cache` 里面了,就可以确保热数据的访问性能是很高的。但是对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,大家互相之间都没什么联系了。如果有人访问冷数据,可能大量数据是在磁盘上的,此时性能差点,就 10% 的人去访问冷数据,90% 的人在访问热数据,也无所谓了。 +你看,假设你有 6 台机器,2 个索引,一个放冷数据,一个放热数据,每个索引 3 个 shard。3 台机器放热数据 index,另外 3 台机器放冷数据 index。然后这样的话,你大量的时间是在访问热数据 index,热数据可能就占总数据量的 10%,此时数据量很少,几乎全都保留在 `filesystem cache` 里面了,就可以确保热数据的访问性能是很高的。但是对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,大家互相之间都没什么联系了。如果有人访问冷数据,可能大量数据是在磁盘上的,此时性能差点,就 10% 的人去访问冷数据,90% 的人在访问热数据,也无所谓了。 ### document 模型设计 对于 MySQL,我们经常有一些复杂的关联查询。在 es 里该怎么玩儿,es 里面的复杂的关联查询尽量别用,一旦用了性能一般都不太好。 最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 es 中。搜索的时候,就不需要利用 es 的搜索语法来完成 join 之类的关联搜索了。 -document 模型设计是非常重要的,很多操作,不要在搜索的时候才想去执行各种复杂的乱七八糟的操作。es 能支持的操作就是那么多,不要考虑用 es 做一些它不好操作的事情。如果真的有那种操作,尽量在 document 模型设计的时候,写入的时候就完成。另外对于一些太复杂的操作,比如 join/nested/parent-child 搜索都要尽量避免,性能都很差的。 +document 模型设计是非常重要的,很多操作,不要在搜索的时候才想去执行各种复杂的乱七八糟的操作。es 能支持的操作就那么多,不要考虑用 es 做一些它不好操作的事情。如果真的有那种操作,尽量在 document 模型设计的时候,写入的时候就完成。另外对于一些太复杂的操作,比如 join/nested/parent-child 搜索都要尽量避免,性能都很差的。 ### 分页性能优化 -es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 `1000` 条数据都查到一个协调节点上,如果你有个 5 个shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。 +es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 1000 条数据都查到一个协调节点上,如果你有个 5 个 shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到最终第 100 页的 10 条数据。 -分布式的,你要查第100页的10条数据,不可能说从5个 shard,每个 shard 就查 2 条数据?最后到协调节点合并成 10 条数据?你必须得从每个 shard 都查 1000 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长。非常坑爹。所以用 es 做分页的时候,你会发现越翻到后面,就越是慢。 +分布式的,你要查第 100 页的 10 条数据,不可能说从 5 个 shard,每个 shard 就查 2 条数据,最后到协调节点合并成 10 条数据吧?你**必须**得从每个 shard 都查 1000 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所以用 es 做分页的时候,你会发现越翻到后面,就越是慢。 -我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页 or 几十页的时候,基本上就要 5~10秒 才能查出来一页数据了。 +我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时候,基本上就要 5~10 秒才能查出来一页数据了。 有什么解决方案吗? -#### 不允许深度分页/默认深度分页性能很惨 -你系统不允许翻那么深的页,跟产品经理说,默认翻的越深,性能就越差。 +#### 不允许深度分页(默认深度分页性能很差) +跟产品经理说,你系统不允许翻那么深的页,默认翻的越深,性能就越差。 #### 类似于 app 里的推荐商品不断下拉出来一页一页的 类似于微博中,下拉刷微博,刷出来一页一页的,你可以用 `scroll api`,关于如何使用,自行上网搜索。 -scroll 会一次性给你生成**所有数据的一个快照**,然后每次翻页就是通过**游标移动**,获取下一页下一页这样子,性能会比上面说的那种分页性能也高很多很多,基本上都是毫秒级的。 +scroll 会一次性给你生成**所有数据的一个快照**,然后每次滑动向后翻页就是通过**游标** `scroll_id` 移动,获取下一页下一页这样子,性能会比上面说的那种分页性能要高很多很多,基本上都是毫秒级的。 -但是 唯一的一点就是,这个适合于那种类似微博下拉翻页的,**不能随意跳到任何一页的场景**。也就是说,你不能先进入第 10 页,然后去 120 页,然后又回到 58 页,不能随意乱跳页。所以现在很多产品,都是不允许你随意翻页的,app,也有一些网站,做的就是你只能往下拉,一页一页的翻。 +但是,唯一的一点就是,这个适合于那种类似微博下拉翻页的,**不能随意跳到任何一页的场景**。也就是说,你不能先进入第 10 页,然后去第 120 页,然后又回到第 58 页,不能随意乱跳页。所以现在很多产品,都是不允许你随意翻页的,app,也有一些网站,做的就是你只能往下拉,一页一页的翻。 -另外,这个 scroll 是要保留一段时间内的数据快照的,你需要确保用户不会持续不断翻页翻几个小时。 \ No newline at end of file +初始化时必须指定 `scroll` 参数,告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。 + +除了用 `scroll api`,你也可以用 `search_after` 来做,`search_after` 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 sort 字段。 \ No newline at end of file diff --git a/docs/high-concurrency/es-production-cluster.md b/docs/high-concurrency/es-production-cluster.md index b7e475a..18d2bb7 100644 --- a/docs/high-concurrency/es-production-cluster.md +++ b/docs/high-concurrency/es-production-cluster.md @@ -15,6 +15,6 @@ es 生产集群的部署架构是什么?每个索引的数据量大概有多 - es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。 - 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。 -- 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个shard。 +- 目前线上有 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 b7d22c6..360e5ae 100644 --- a/docs/high-concurrency/es-write-query-search.md +++ b/docs/high-concurrency/es-write-query-search.md @@ -13,7 +13,7 @@ es 写入数据的工作原理是什么啊?es 查询数据的工作原理是 - 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。 - `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 -![es-write](/img/es-write.png) +![es-write](/images/es-write.png) ### es 读数据过程 可以通过 `doc id` 来查询,会根据 `doc id` 进行 hash,判断出来当时把 `doc id` 分配到了哪个 shard 上面去,从那个 shard 去查询。 @@ -38,10 +38,11 @@ j2ee特别牛 - query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id`)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。 - fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。 +> 写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。 ### 写数据底层原理 -![es-write-detail](/img/es-write-detail.png) +![es-write-detail](/images/es-write-detail.png) 先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。 @@ -49,13 +50,13 @@ j2ee特别牛 每隔 1 秒钟,es 将 buffer 中的数据写入一个**新的** `segment file`,每秒钟会产生一个**新的磁盘文件** `segment file`,这个 `segment file` 中就存储最近 1 秒内 buffer 中写入的数据。 -但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果buffer里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。 +但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。 操作系统里面,磁盘文件其实都有一个东西,叫做 `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 里面已经持久化到磁盘去一份了。 -重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 `buffer` 数据写入一个又一个新的 `segment file` 中去,每次 `refresh` 完 buffer 清空,translog保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 `commit` 操作。 +重复上面的步骤,新的数据不断进入 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 操作完成。 @@ -67,6 +68,8 @@ translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁 实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的**数据丢失**。 +**总结一下**,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。 + > 数据写入 segment file 之后,同时就建立好了倒排索引。 ### 删除/更新数据底层原理 @@ -74,7 +77,7 @@ translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁 如果是更新操作,就是将原来的 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 去开发就可以了。 @@ -117,3 +120,10 @@ buffer 每次 refresh 一次,就会产生一个 `segment file`,所以默认 另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。 那么,有了倒排索引,搜索引擎可以很方便地响应用户的查询。比如用户输入查询 `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 1852863..05f0901 100644 --- a/docs/high-concurrency/high-concurrency-design.md +++ b/docs/high-concurrency/high-concurrency-design.md @@ -12,7 +12,7 @@ 如果有面试官问你个问题说,如何设计一个高并发系统?那么不好意思,**一定是因为你实际上没干过高并发系统**。面试官看你简历就没啥出彩的,感觉就不咋地,所以就会问问你,如何设计一个高并发系统?其实说白了本质就是看看你有没有自己研究过,有没有一定的知识积累。 -最好的当然是招聘个真正干过高并发的哥儿们咯,但是这种哥儿们人数稀缺,不好招。所以可能次一点的就是招一个自己研究过的哥儿们,总比招一个傻也不会的哥儿们好吧! +最好的当然是招聘个真正干过高并发的哥儿们咯,但是这种哥儿们人数稀缺,不好招。所以可能次一点的就是招一个自己研究过的哥儿们,总比招一个啥也不会的哥儿们好吧! 所以这个时候你必须得做一把个人秀了,秀出你所有关于高并发的知识! @@ -24,7 +24,7 @@ 当然会挂了,凭什么不挂?你数据库如果瞬间承载每秒 5000/8000,甚至上万的并发,一定会宕机,因为比如 mysql 就压根儿扛不住这么高的并发量。 -所以为啥高并发牛逼?就是因为现在用互联网的人越来越多,很多app、网站、系统承载的都是高并发请求,可能高峰期每秒并发量几千,很正常的。如果是什么双十一之类的,每秒并发几万几十万都有可能。 +所以为啥高并发牛逼?就是因为现在用互联网的人越来越多,很多 app、网站、系统承载的都是高并发请求,可能高峰期每秒并发量几千,很正常的。如果是什么双十一之类的,每秒并发几万几十万都有可能。 那么如此之高的并发量,加上原本就如此之复杂的业务,咋玩儿?真正厉害的,一定是在复杂业务系统里玩儿过高并发架构的人,但是你没有,那么我给你说一下你该怎么回答这个问题: @@ -37,7 +37,7 @@ - 读写分离 - ElasticSearch -![high-concurrency-system-design](/img/high-concurrency-system-design.png) +![high-concurrency-system-design](/images/high-concurrency-system-design.png) ### 系统拆分 将一个系统拆分为多个子系统,用 dubbo 来搞。然后每个系统连一个数据库,这样本来就一个库,现在多个数据库,不也可以扛高并发么。 @@ -60,6 +60,6 @@ Elasticsearch,简称 es。es 是分布式的,可以随便扩容,分布式 上面的 6 点,基本就是高并发系统肯定要干的一些事儿,大家可以仔细结合之前讲过的知识考虑一下,到时候你可以系统的把这块阐述一下,然后每个部分要注意哪些问题,之前都讲过了,你都可以阐述阐述,表明你对这块是有点积累的。 -说句实话,毕竟你真正厉害的一点,不是在于弄明白一些技术,或者大概知道一个高并发系统应该长什么样?其实实际上在真正的复杂的业务系统里,做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑:哪些需要分库分表,哪些不需要分库分表,单库单表跟分库分表如何 join,哪些数据要放到缓存里去,放哪些数据再可以扛住高并发的请求,你需要完成对一个复杂业务系统的分析之后,然后逐步逐步的加入高并发的系统架构的改造,这个过程是无比复杂的,一旦做过一次,并且做好了,你在这个市场上就会非常的吃香。 +说句实话,毕竟你真正厉害的一点,不是在于弄明白一些技术,或者大概知道一个高并发系统应该长什么样?其实实际上在真正的复杂的业务系统里,做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑:哪些需要分库分表,哪些不需要分库分表,单库单表跟分库分表如何 join,哪些数据要放到缓存里去,放哪些数据才可以扛住高并发的请求,你需要完成对一个复杂业务系统的分析之后,然后逐步逐步的加入高并发的系统架构的改造,这个过程是无比复杂的,一旦做过一次,并且做好了,你在这个市场上就会非常的吃香。 其实大部分公司,真正看重的,不是说你掌握高并发相关的一些基本的架构知识,架构中的一些技术,RocketMQ、Kafka、Redis、Elasticsearch,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实践过高并发架构的人,这个经验是难能可贵的。 \ No newline at end of file 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 f977f0a..cf9ae13 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 @@ -22,7 +22,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 #### 普通集群模式(无高可用性) 普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。 -![mq-7](/img/mq-7.png) +![mq-7](/images/mq-7.png) 这种方式确实很麻烦,也不怎么好,**没做到所谓的分布式**,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有**数据拉取的开销**,后者导致**单实例性能瓶颈**。 @@ -33,7 +33,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 #### 镜像集群模式(高可用性) 这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。 -![mq-8](/img/mq-8.png) +![mq-8](/images/mq-8.png) 那么**如何开启这个镜像集群模式**呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是**镜像集群模式的策略**,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。 @@ -50,13 +50,13 @@ Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了 比如说,我们假设创建了一个 topic,指定其 partition 数量是 3 个,分别在三台机器上。但是,如果第二台机器宕机了,会导致这个 topic 的 1/3 的数据就丢了,因此这个是做不到高可用的。 -![kafka-before](/img/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](/img/kafka-after.png) +![kafka-after](/images/kafka-after.png) -这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker上面的 partition 在其他机器上都有副本的,如果这上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 +这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 **写数据**的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为) 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 36d897a..33dbaf6 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 @@ -1,5 +1,5 @@ ## 面试题 -如何保证消息不被重复消费?或者说,如何保证消息消费时的幂等性? +如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性? ## 面试官心理分析 其实这是很常见的一个问题,这俩问题基本可以连起来问。既然是消费消息,那肯定要考虑会不会重复消费?能不能避免重复消费?或者重复消费了也别造成系统异常可以吗?这个是 MQ 领域的基本问题,其实本质上还是问你**使用消息队列如何保证幂等性**,这个是你架构里要考虑的一个问题。 @@ -13,15 +13,14 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一 但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接 kill 进程了,再重启。这会导致 consumer 有些消息处理了,但是没来得及提交 offset,尴尬了。重启之后,少数消息会再次消费一次。 -![mq-10](/img/mq-10.png) - 举个栗子。 -有这么个场景。数据 1/2/3 依次进入 kafka,kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,分配的 offset 依次是 152/153/154。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。数据 1/2 再次被消费。 +有这么个场景。数据 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) 如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说,你可能就把数据 1/2 在数据库里插入了 2 次,那么数据就错啦。 - 其实重复消费不可怕,可怕的是你没考虑到重复消费之后,**怎么保证幂等性**。 举个例子吧。假设你有个系统,消费一条消息就往数据库里插入一条数据,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下是否已经消费过了,若是就直接扔了,这样不就保留了一条数据,从而保证了数据的正确性。 @@ -39,6 +38,6 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一 - 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 - 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。 -![mq-11](/img/mq-11.png) +![mq-11](/images/mq-11.png) 当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。 \ No newline at end of file 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 c806289..882e895 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 @@ -14,19 +14,19 @@ 先看看顺序会错乱的俩场景: - **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。 -![rabbitmq-order-01](/img/rabbitmq-order-01.png) +![rabbitmq-order-01](/images/rabbitmq-order-01.png) - **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。 -![kafka-order-01](/img/kafka-order-01.png) +![kafka-order-01](/images/kafka-order-01.png) ### 解决方案 #### RabbitMQ 拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。 -![rabbitmq-order-02](/img/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](/img/kafka-order-02.png) \ No newline at end of file +![kafka-order-02](/images/kafka-order-02.png) \ No newline at end of file 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 a1582f2..5a49254 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 @@ -10,7 +10,7 @@ 数据的丢失问题,可能出现在生产者、MQ、消费者中,咱们从 RabbitMQ 和 Kafka 分别来分析一下吧。 ### RabbitMQ -![rabbitmq-message-lose](/img/rabbitmq-message-lose.png) +![rabbitmq-message-lose](/images/rabbitmq-message-lose.png) #### 生产者弄丢了数据 @@ -34,11 +34,11 @@ channel.txCommit 但是问题是,RabbitMQ 事务机制(同步)一搞,基本上**吞吐量会下来,因为太耗性能**。 -所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启`confirm`模式,在生产者那里设置开启`confirm`模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个`ack`消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你一个`nack`接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。 +所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启 `confirm` 模式,在生产者那里设置开启 `confirm` 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 `ack` 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 `nack` 接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。 -事务机制和`cnofirm`机制最大的不同在于,**事务机制是同步的**,你提交一个事务之后会**阻塞**在那儿,但是`confirm`机制是**异步**的,你发送个消息之后就可以发送下一个消息,然后那个消息RabbitMQ 接收了之后会异步回调你一个接口通知你这个消息接收到了。 +事务机制和 `confirm` 机制最大的不同在于,**事务机制是同步的**,你提交一个事务之后会**阻塞**在那儿,但是 `confirm` 机制是**异步**的,你发送个消息之后就可以发送下一个消息,然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。 -所以一般在生产者这块**避免数据丢失**,都是用`confirm`机制的。 +所以一般在生产者这块**避免数据丢失**,都是用 `confirm` 机制的。 #### RabbitMQ 弄丢了数据 就是 RabbitMQ 自己弄丢了数据,这个你必须**开启 RabbitMQ 的持久化**,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,**恢复之后会自动读取之前存储的数据**,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,**可能导致少量数据丢失**,但是这个概率较小。 @@ -46,29 +46,29 @@ channel.txCommit 设置持久化有**两个步骤**: - 创建 queue 的时候将其设置为持久化
-这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是不会持久化 queue 里的数据。 +这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。 - 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2
就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。 必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。 -持久化可以跟生产者那边的`confirm`机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者`ack`了,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到`ack`,你也是可以自己重发的。 - 注意,哪怕是你给 RabbitMQ 开启了持久化机制,也有一种可能,就是这个消息写到了 RabbitMQ 中,但是还没来得及持久化到磁盘上,结果不巧,此时 RabbitMQ 挂了,就会导致内存里的一点点数据丢失。 +所以,持久化可以跟生产者那边的 `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](/img/rabbitmq-message-lose-solution.png) +![rabbitmq-message-lose-solution](/images/rabbitmq-message-lose-solution.png) ### Kafka #### 消费端弄丢了数据 唯一可能导致消费者弄丢数据的情况,就是说,你消费到了这个消息,然后消费者那边**自动提交了 offset**,让 Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。 -这不是跟 RabbitMQ 差不多吗,大家都知道 Kafka 会自动提交 offset,那么只要**关闭自动提交** offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是**可能会有重复消费**,比如你刚处理完,还没提交offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。 +这不是跟 RabbitMQ 差不多吗,大家都知道 Kafka 会自动提交 offset,那么只要**关闭自动提交** offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是**可能会有重复消费**,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。 生产环境碰到的一个问题,就是说我们的 Kafka 消费者消费到了数据之后是写到一个内存的 queue 里先缓冲一下,结果有的时候,你刚把消息写入内存 queue,然后消费者会自动提交 offset。然后此时我们重启了系统,就会导致内存 queue 里还没来得及处理的数据就丢失了。 @@ -88,4 +88,4 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费 我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。 #### 生产者会不会弄丢数据? -如果按照上述的思路设置了 `acks=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。 \ No newline at end of file +如果按照上述的思路设置了 `acks=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。 diff --git a/docs/high-concurrency/img/async-replication-data-lose-case.png b/docs/high-concurrency/images/async-replication-data-lose-case.png similarity index 100% rename from docs/high-concurrency/img/async-replication-data-lose-case.png rename to docs/high-concurrency/images/async-replication-data-lose-case.png diff --git a/docs/high-concurrency/img/consistent-hashing-algorithm.png b/docs/high-concurrency/images/consistent-hashing-algorithm.png similarity index 100% rename from docs/high-concurrency/img/consistent-hashing-algorithm.png rename to docs/high-concurrency/images/consistent-hashing-algorithm.png diff --git a/docs/high-concurrency/images/database-id-sequence-step.png b/docs/high-concurrency/images/database-id-sequence-step.png new file mode 100644 index 0000000..3cf6ae3 Binary files /dev/null and b/docs/high-concurrency/images/database-id-sequence-step.png differ diff --git a/docs/high-concurrency/img/database-shard-method-1.png b/docs/high-concurrency/images/database-shard-method-1.png similarity index 100% rename from docs/high-concurrency/img/database-shard-method-1.png rename to docs/high-concurrency/images/database-shard-method-1.png diff --git a/docs/high-concurrency/img/database-shard-method-2.png b/docs/high-concurrency/images/database-shard-method-2.png similarity index 100% rename from docs/high-concurrency/img/database-shard-method-2.png rename to docs/high-concurrency/images/database-shard-method-2.png diff --git a/docs/high-concurrency/img/database-split-horizon.png b/docs/high-concurrency/images/database-split-horizon.png similarity index 100% rename from docs/high-concurrency/img/database-split-horizon.png rename to docs/high-concurrency/images/database-split-horizon.png diff --git a/docs/high-concurrency/img/database-split-vertically.png b/docs/high-concurrency/images/database-split-vertically.png similarity index 100% rename from docs/high-concurrency/img/database-split-vertically.png rename to docs/high-concurrency/images/database-split-vertically.png diff --git a/docs/high-concurrency/img/distributed-system-request-sequence.png b/docs/high-concurrency/images/distributed-system-request-sequence.png similarity index 100% rename from docs/high-concurrency/img/distributed-system-request-sequence.png rename to docs/high-concurrency/images/distributed-system-request-sequence.png diff --git a/docs/high-concurrency/img/distributed-transaction-TCC.png b/docs/high-concurrency/images/distributed-transaction-TCC.png similarity index 100% rename from docs/high-concurrency/img/distributed-transaction-TCC.png rename to docs/high-concurrency/images/distributed-transaction-TCC.png diff --git a/docs/high-concurrency/img/distributed-transaction-XA.png b/docs/high-concurrency/images/distributed-transaction-XA.png similarity index 100% rename from docs/high-concurrency/img/distributed-transaction-XA.png rename to docs/high-concurrency/images/distributed-transaction-XA.png diff --git a/docs/high-concurrency/img/distributed-transaction-local-message-table.png b/docs/high-concurrency/images/distributed-transaction-local-message-table.png similarity index 100% rename from docs/high-concurrency/img/distributed-transaction-local-message-table.png rename to docs/high-concurrency/images/distributed-transaction-local-message-table.png diff --git a/docs/high-concurrency/img/distributed-transaction-reliable-message.png b/docs/high-concurrency/images/distributed-transaction-reliable-message.png similarity index 100% rename from docs/high-concurrency/img/distributed-transaction-reliable-message.png rename to docs/high-concurrency/images/distributed-transaction-reliable-message.png diff --git a/docs/high-concurrency/img/dubbo-operating-principle.png b/docs/high-concurrency/images/dubbo-operating-principle.png similarity index 100% rename from docs/high-concurrency/img/dubbo-operating-principle.png rename to docs/high-concurrency/images/dubbo-operating-principle.png diff --git a/docs/high-concurrency/img/dubbo-service-invoke-road.png b/docs/high-concurrency/images/dubbo-service-invoke-road.png similarity index 100% rename from docs/high-concurrency/img/dubbo-service-invoke-road.png rename to docs/high-concurrency/images/dubbo-service-invoke-road.png diff --git a/docs/high-concurrency/img/dubbo-spi.png b/docs/high-concurrency/images/dubbo-spi.png similarity index 100% rename from docs/high-concurrency/img/dubbo-spi.png rename to docs/high-concurrency/images/dubbo-spi.png diff --git a/docs/high-concurrency/img/e-commerce-website-detail-page-architecture-1.png b/docs/high-concurrency/images/e-commerce-website-detail-page-architecture-1.png similarity index 100% rename from docs/high-concurrency/img/e-commerce-website-detail-page-architecture-1.png rename to docs/high-concurrency/images/e-commerce-website-detail-page-architecture-1.png diff --git a/docs/high-concurrency/img/e-commerce-website-detail-page-architecture-2.png b/docs/high-concurrency/images/e-commerce-website-detail-page-architecture-2.png similarity index 100% rename from docs/high-concurrency/img/e-commerce-website-detail-page-architecture-2.png rename to docs/high-concurrency/images/e-commerce-website-detail-page-architecture-2.png diff --git a/docs/high-concurrency/img/es-cluster-0.png b/docs/high-concurrency/images/es-cluster-0.png similarity index 100% rename from docs/high-concurrency/img/es-cluster-0.png rename to docs/high-concurrency/images/es-cluster-0.png diff --git a/docs/high-concurrency/img/es-cluster.png b/docs/high-concurrency/images/es-cluster.png similarity index 100% rename from docs/high-concurrency/img/es-cluster.png rename to docs/high-concurrency/images/es-cluster.png diff --git a/docs/high-concurrency/img/es-index-type-mapping-document-field.png b/docs/high-concurrency/images/es-index-type-mapping-document-field.png similarity index 100% rename from docs/high-concurrency/img/es-index-type-mapping-document-field.png rename to docs/high-concurrency/images/es-index-type-mapping-document-field.png diff --git a/docs/high-concurrency/img/es-search-process.png b/docs/high-concurrency/images/es-search-process.png similarity index 100% rename from docs/high-concurrency/img/es-search-process.png rename to docs/high-concurrency/images/es-search-process.png diff --git a/docs/high-concurrency/img/es-write-detail.png b/docs/high-concurrency/images/es-write-detail.png similarity index 100% rename from docs/high-concurrency/img/es-write-detail.png rename to docs/high-concurrency/images/es-write-detail.png diff --git a/docs/high-concurrency/img/es-write.png b/docs/high-concurrency/images/es-write.png similarity index 100% rename from docs/high-concurrency/img/es-write.png rename to docs/high-concurrency/images/es-write.png diff --git a/docs/high-concurrency/img/favicon-16x16.png b/docs/high-concurrency/images/favicon-16x16.png similarity index 100% rename from docs/high-concurrency/img/favicon-16x16.png rename to docs/high-concurrency/images/favicon-16x16.png diff --git a/docs/high-concurrency/img/favicon-32x32.png b/docs/high-concurrency/images/favicon-32x32.png similarity index 100% rename from docs/high-concurrency/img/favicon-32x32.png rename to docs/high-concurrency/images/favicon-32x32.png diff --git a/docs/high-concurrency/img/hash-slot.png b/docs/high-concurrency/images/hash-slot.png similarity index 100% rename from docs/high-concurrency/img/hash-slot.png rename to docs/high-concurrency/images/hash-slot.png diff --git a/docs/high-concurrency/img/hash.png b/docs/high-concurrency/images/hash.png similarity index 100% rename from docs/high-concurrency/img/hash.png rename to docs/high-concurrency/images/hash.png diff --git a/docs/high-concurrency/img/high-concurrency-system-design.png b/docs/high-concurrency/images/high-concurrency-system-design.png similarity index 100% rename from docs/high-concurrency/img/high-concurrency-system-design.png rename to docs/high-concurrency/images/high-concurrency-system-design.png diff --git a/docs/high-concurrency/img/icon.png b/docs/high-concurrency/images/icon.png similarity index 100% rename from docs/high-concurrency/img/icon.png rename to docs/high-concurrency/images/icon.png diff --git a/docs/high-concurrency/img/kafka-after.png b/docs/high-concurrency/images/kafka-after.png similarity index 100% rename from docs/high-concurrency/img/kafka-after.png rename to docs/high-concurrency/images/kafka-after.png diff --git a/docs/high-concurrency/img/kafka-before.png b/docs/high-concurrency/images/kafka-before.png similarity index 100% rename from docs/high-concurrency/img/kafka-before.png rename to docs/high-concurrency/images/kafka-before.png diff --git a/docs/high-concurrency/img/kafka-order-01.png b/docs/high-concurrency/images/kafka-order-01.png similarity index 100% rename from docs/high-concurrency/img/kafka-order-01.png rename to docs/high-concurrency/images/kafka-order-01.png diff --git a/docs/high-concurrency/img/kafka-order-02.png b/docs/high-concurrency/images/kafka-order-02.png similarity index 100% rename from docs/high-concurrency/img/kafka-order-02.png rename to docs/high-concurrency/images/kafka-order-02.png diff --git a/docs/high-concurrency/img/mq-1.png b/docs/high-concurrency/images/mq-1.png similarity index 100% rename from docs/high-concurrency/img/mq-1.png rename to docs/high-concurrency/images/mq-1.png diff --git a/docs/high-concurrency/images/mq-10.png b/docs/high-concurrency/images/mq-10.png new file mode 100644 index 0000000..e8fd8f7 Binary files /dev/null and b/docs/high-concurrency/images/mq-10.png differ diff --git a/docs/high-concurrency/images/mq-11.png b/docs/high-concurrency/images/mq-11.png new file mode 100644 index 0000000..f1a38ae Binary files /dev/null and b/docs/high-concurrency/images/mq-11.png differ diff --git a/docs/high-concurrency/img/mq-2.png b/docs/high-concurrency/images/mq-2.png similarity index 100% rename from docs/high-concurrency/img/mq-2.png rename to docs/high-concurrency/images/mq-2.png diff --git a/docs/high-concurrency/img/mq-3.png b/docs/high-concurrency/images/mq-3.png similarity index 100% rename from docs/high-concurrency/img/mq-3.png rename to docs/high-concurrency/images/mq-3.png diff --git a/docs/high-concurrency/img/mq-4.png b/docs/high-concurrency/images/mq-4.png similarity index 100% rename from docs/high-concurrency/img/mq-4.png rename to docs/high-concurrency/images/mq-4.png diff --git a/docs/high-concurrency/img/mq-5.png b/docs/high-concurrency/images/mq-5.png similarity index 100% rename from docs/high-concurrency/img/mq-5.png rename to docs/high-concurrency/images/mq-5.png diff --git a/docs/high-concurrency/img/mq-6.png b/docs/high-concurrency/images/mq-6.png similarity index 100% rename from docs/high-concurrency/img/mq-6.png rename to docs/high-concurrency/images/mq-6.png diff --git a/docs/high-concurrency/img/mq-7.png b/docs/high-concurrency/images/mq-7.png similarity index 100% rename from docs/high-concurrency/img/mq-7.png rename to docs/high-concurrency/images/mq-7.png diff --git a/docs/high-concurrency/img/mq-8.png b/docs/high-concurrency/images/mq-8.png similarity index 100% rename from docs/high-concurrency/img/mq-8.png rename to docs/high-concurrency/images/mq-8.png diff --git a/docs/high-concurrency/img/mysql-master-slave.png b/docs/high-concurrency/images/mysql-master-slave.png similarity index 100% rename from docs/high-concurrency/img/mysql-master-slave.png rename to docs/high-concurrency/images/mysql-master-slave.png diff --git a/docs/high-concurrency/img/rabbitmq-message-lose-solution.png b/docs/high-concurrency/images/rabbitmq-message-lose-solution.png similarity index 100% rename from docs/high-concurrency/img/rabbitmq-message-lose-solution.png rename to docs/high-concurrency/images/rabbitmq-message-lose-solution.png diff --git a/docs/high-concurrency/img/rabbitmq-message-lose.png b/docs/high-concurrency/images/rabbitmq-message-lose.png similarity index 100% rename from docs/high-concurrency/img/rabbitmq-message-lose.png rename to docs/high-concurrency/images/rabbitmq-message-lose.png diff --git a/docs/high-concurrency/img/rabbitmq-order-01.png b/docs/high-concurrency/images/rabbitmq-order-01.png similarity index 100% rename from docs/high-concurrency/img/rabbitmq-order-01.png rename to docs/high-concurrency/images/rabbitmq-order-01.png diff --git a/docs/high-concurrency/img/rabbitmq-order-02.png b/docs/high-concurrency/images/rabbitmq-order-02.png similarity index 100% rename from docs/high-concurrency/img/rabbitmq-order-02.png rename to docs/high-concurrency/images/rabbitmq-order-02.png diff --git a/docs/high-concurrency/img/redis-caching-avalanche-solution.png b/docs/high-concurrency/images/redis-caching-avalanche-solution.png similarity index 100% rename from docs/high-concurrency/img/redis-caching-avalanche-solution.png rename to docs/high-concurrency/images/redis-caching-avalanche-solution.png diff --git a/docs/high-concurrency/img/redis-caching-avalanche.png b/docs/high-concurrency/images/redis-caching-avalanche.png similarity index 100% rename from docs/high-concurrency/img/redis-caching-avalanche.png rename to docs/high-concurrency/images/redis-caching-avalanche.png diff --git a/docs/high-concurrency/img/redis-caching-penetration.png b/docs/high-concurrency/images/redis-caching-penetration.png similarity index 100% rename from docs/high-concurrency/img/redis-caching-penetration.png rename to docs/high-concurrency/images/redis-caching-penetration.png diff --git a/docs/high-concurrency/img/redis-cluster-split-brain.png b/docs/high-concurrency/images/redis-cluster-split-brain.png similarity index 100% rename from docs/high-concurrency/img/redis-cluster-split-brain.png rename to docs/high-concurrency/images/redis-cluster-split-brain.png diff --git a/docs/high-concurrency/img/redis-gossip.png b/docs/high-concurrency/images/redis-gossip.png similarity index 100% rename from docs/high-concurrency/img/redis-gossip.png rename to docs/high-concurrency/images/redis-gossip.png diff --git a/docs/high-concurrency/img/redis-junior-inconsistent.png b/docs/high-concurrency/images/redis-junior-inconsistent.png similarity index 100% rename from docs/high-concurrency/img/redis-junior-inconsistent.png rename to docs/high-concurrency/images/redis-junior-inconsistent.png diff --git a/docs/high-concurrency/img/redis-master-slave-replication-detail.png b/docs/high-concurrency/images/redis-master-slave-replication-detail.png similarity index 100% rename from docs/high-concurrency/img/redis-master-slave-replication-detail.png rename to docs/high-concurrency/images/redis-master-slave-replication-detail.png diff --git a/docs/high-concurrency/img/redis-master-slave-replication.png b/docs/high-concurrency/images/redis-master-slave-replication.png similarity index 100% rename from docs/high-concurrency/img/redis-master-slave-replication.png rename to docs/high-concurrency/images/redis-master-slave-replication.png diff --git a/docs/high-concurrency/img/redis-master-slave.png b/docs/high-concurrency/images/redis-master-slave.png similarity index 100% rename from docs/high-concurrency/img/redis-master-slave.png rename to docs/high-concurrency/images/redis-master-slave.png diff --git a/docs/high-concurrency/img/redis-redlock.png b/docs/high-concurrency/images/redis-redlock.png similarity index 100% rename from docs/high-concurrency/img/redis-redlock.png rename to docs/high-concurrency/images/redis-redlock.png diff --git a/docs/high-concurrency/images/redis-single-thread-model.png b/docs/high-concurrency/images/redis-single-thread-model.png new file mode 100644 index 0000000..f052c77 Binary files /dev/null and b/docs/high-concurrency/images/redis-single-thread-model.png differ diff --git a/docs/high-concurrency/img/service-invoke-road.png b/docs/high-concurrency/images/service-invoke-road.png similarity index 100% rename from docs/high-concurrency/img/service-invoke-road.png rename to docs/high-concurrency/images/service-invoke-road.png diff --git a/docs/high-concurrency/img/simple-distributed-system-oa.png b/docs/high-concurrency/images/simple-distributed-system-oa.png similarity index 100% rename from docs/high-concurrency/img/simple-distributed-system-oa.png rename to docs/high-concurrency/images/simple-distributed-system-oa.png diff --git a/docs/high-concurrency/img/zookeeper-active-standby.png b/docs/high-concurrency/images/zookeeper-active-standby.png similarity index 100% rename from docs/high-concurrency/img/zookeeper-active-standby.png rename to docs/high-concurrency/images/zookeeper-active-standby.png diff --git a/docs/high-concurrency/img/zookeeper-centralized-storage.png b/docs/high-concurrency/images/zookeeper-centralized-storage.png similarity index 100% rename from docs/high-concurrency/img/zookeeper-centralized-storage.png rename to docs/high-concurrency/images/zookeeper-centralized-storage.png diff --git a/docs/high-concurrency/img/zookeeper-distributed-coordination.png b/docs/high-concurrency/images/zookeeper-distributed-coordination.png similarity index 100% rename from docs/high-concurrency/img/zookeeper-distributed-coordination.png rename to docs/high-concurrency/images/zookeeper-distributed-coordination.png diff --git a/docs/high-concurrency/img/zookeeper-distributed-lock-demo.png b/docs/high-concurrency/images/zookeeper-distributed-lock-demo.png similarity index 100% rename from docs/high-concurrency/img/zookeeper-distributed-lock-demo.png rename to docs/high-concurrency/images/zookeeper-distributed-lock-demo.png diff --git a/docs/high-concurrency/img/zookeeper-distributed-lock.png b/docs/high-concurrency/images/zookeeper-distributed-lock.png similarity index 100% rename from docs/high-concurrency/img/zookeeper-distributed-lock.png rename to docs/high-concurrency/images/zookeeper-distributed-lock.png diff --git a/docs/high-concurrency/img/zookeeper-meta-data-manage.png b/docs/high-concurrency/images/zookeeper-meta-data-manage.png similarity index 100% rename from docs/high-concurrency/img/zookeeper-meta-data-manage.png rename to docs/high-concurrency/images/zookeeper-meta-data-manage.png diff --git a/docs/high-concurrency/img/mq-10.png b/docs/high-concurrency/img/mq-10.png deleted file mode 100644 index 506298d..0000000 Binary files a/docs/high-concurrency/img/mq-10.png and /dev/null differ diff --git a/docs/high-concurrency/img/mq-11.png b/docs/high-concurrency/img/mq-11.png deleted file mode 100644 index d4d834c..0000000 Binary files a/docs/high-concurrency/img/mq-11.png and /dev/null differ diff --git a/docs/high-concurrency/img/redis-single-thread-model.png b/docs/high-concurrency/img/redis-single-thread-model.png deleted file mode 100644 index 1dfd024..0000000 Binary files a/docs/high-concurrency/img/redis-single-thread-model.png and /dev/null differ diff --git a/docs/high-concurrency/mq-interview.md b/docs/high-concurrency/mq-interview.md index 6e30484..4ec170c 100644 --- a/docs/high-concurrency/mq-interview.md +++ b/docs/high-concurrency/mq-interview.md @@ -24,7 +24,7 @@ **面试官**:那你说说用消息队列都有什么优点和缺点? -(面试官此时心里想的是,你的 `MQ` 在项目里为啥要用?你没考虑过,那我稍微简单点儿,我问问你消息队列你之前有没有考虑过如果用的话,优点和缺点分别是啥?) +(面试官此时心里想的是,你的 `MQ` 在项目里为啥要用,你没怎么考虑过,那我稍微简单点儿,我问问你消息队列你之前有没有考虑过如果用的话,优点和缺点分别是啥?) **候选人**:这个。。。(确实平时没怎么考虑过这个问题啊。。。胡言乱语了) @@ -36,7 +36,7 @@ **候选人**:我们就用过 `ActiveMQ`,所以别的没用过。。。区别,也不太清楚。。。 -(面试官此时却是觉得你这哥儿们平时就是瞎用,根本就没什么思考,觉得不行) +(面试官此时更是觉得你这哥儿们平时就是瞎用,根本就没什么思考,觉得不行) **面试官**:那你们是如何保证消息队列的高可用啊? @@ -52,7 +52,7 @@ **面试官**:那如何保证消息的顺序性? -**候选人**:顺序性?什么意思?我为什么要保证消息的顺序性? +**候选人**:顺序性?什么意思?我为什么要保证消息的顺序性?它不是本来就有顺序吗? **面试官**:如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决? @@ -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 cf90a38..d69b7d0 100644 --- a/docs/high-concurrency/mq-time-delay-and-expired-failure.md +++ b/docs/high-concurrency/mq-time-delay-and-expired-failure.md @@ -15,7 +15,7 @@ 一个消费者一秒是 1000 条,一秒 3 个消费者是 3000 条,一分钟就是 18 万条。所以如果你积压了几百万到上千万的数据,即使消费者恢复了,也需要大概 1 小时的时间才能恢复过来。 一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下: -- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 cnosumer 都停掉。 +- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。 - 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。 - 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。 - 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。 @@ -29,4 +29,4 @@ 假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。 ### mq 都快写满了 -如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。 \ No newline at end of file +如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。 diff --git a/docs/high-concurrency/mysql-read-write-separation.md b/docs/high-concurrency/mysql-read-write-separation.md index 52d80a2..c63ef4f 100644 --- a/docs/high-concurrency/mysql-read-write-separation.md +++ b/docs/high-concurrency/mysql-read-write-separation.md @@ -11,7 +11,7 @@ ### MySQL 主从复制原理的是啥? 主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。 -![mysql-master-slave](/img/mysql-master-slave.png) +![mysql-master-slave](/images/mysql-master-slave.png) 这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是**有延时**的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。 @@ -38,4 +38,4 @@ show status - 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。 - 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。 - 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。 -- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你这么搞导致读写分离的意义就丧失了。 \ No newline at end of file +- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。 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 259ce9f..9842eab 100644 --- a/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md +++ b/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md @@ -1,16 +1,16 @@ ## 面试题 -了解什么是 redis 的雪崩和穿透?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透? +了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透? ## 面试官心理分析 其实这是问到缓存必问的,因为缓存雪崩和穿透,是缓存最大的两个问题,要么不出现,一旦出现就是致命性的问题,所以面试官一定会问你。 ## 面试题剖析 ### 缓存雪崩 -对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 +对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 这就是缓存雪崩。 -![redis-caching-avalanche](/img/redis-caching-avalanche.png) +![redis-caching-avalanche](/images/redis-caching-avalanche.png) 大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。 @@ -19,7 +19,7 @@ - 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 - 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 -![redis-caching-avalanche-solution](/img/redis-caching-avalanche-solution.png) +![redis-caching-avalanche-solution](/images/redis-caching-avalanche-solution.png) 用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。 @@ -37,6 +37,11 @@ 举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。 -![redis-caching-penetration](/img/redis-caching-penetration.png) +![redis-caching-penetration](/images/redis-caching-penetration.png) -解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。这样的话,下次便能走缓存了。 \ No newline at end of file +解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 + +### 缓存击穿 +缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。 + +解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。 \ No newline at end of file diff --git a/docs/high-concurrency/redis-cas.md b/docs/high-concurrency/redis-cas.md index f966678..d9e59ac 100644 --- a/docs/high-concurrency/redis-cas.md +++ b/docs/high-concurrency/redis-cas.md @@ -9,7 +9,7 @@ redis 的并发竞争问题是什么?如何解决这个问题?了解 redis ## 面试题剖析 某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。 -![zookeeper-distributed-lock](/img/zookeeper-distributed-lock.png) +![zookeeper-distributed-lock](/images/zookeeper-distributed-lock.png) 你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。 diff --git a/docs/high-concurrency/redis-cluster.md b/docs/high-concurrency/redis-cluster.md index fa8fca1..deb5f9c 100644 --- a/docs/high-concurrency/redis-cluster.md +++ b/docs/high-concurrency/redis-cluster.md @@ -4,7 +4,7 @@ redis 集群模式的工作原理能说一下么?在集群模式下,redis ## 面试官心理分析 在前几年,redis 如果要搞几个节点,每个节点存储一部分的数据,得**借助一些中间件**来实现,比如说有 `codis`,或者 `twemproxy`,都有。有一些 redis 中间件,你读写 redis 中间件,redis 中间件负责将你的数据分布式存储在多台机器上的 redis 实例中。 -这两年,redis 不断在发展,redis 也不断的有新的版本,现在的 redis 集群模式,可以做到在多台机器上,部署多个 redis 实例,每个实例存储一部分的数据,同时每个 redis 实例可以挂 redis 从实例,自动确保说,如果 redis 主实例挂了,会自动切换到 redis 从实例顶上来。 +这两年,redis 不断在发展,redis 也不断有新的版本,现在的 redis 集群模式,可以做到在多台机器上,部署多个 redis 实例,每个实例存储一部分的数据,同时每个 redis 主实例可以挂 redis 从实例,自动确保说,如果 redis 主实例挂了,会自动切换到 redis 从实例上来。 现在 redis 的新版本,大家都是用 redis cluster 的,也就是 redis 原生支持的 redis 集群模式,那么面试官肯定会就 redis cluster 对你来个几连炮。要是你没用过 redis cluster,正常,以前很多人用 codis 之类的客户端来支持集群,但是起码你得研究一下 redis cluster 吧。 @@ -23,31 +23,33 @@ redis cluster,主要是针对**海量数据+高并发+高可用**的场景。r ### 节点间的内部通信机制 #### 基本通信原理 -- redis cluster 节点间采用 gossip 协议进行通信 -集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 +集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。 -![zookeeper-centralized-storage](/img/zookeeper-centralized-storage.png) +**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 + +![zookeeper-centralized-storage](/images/zookeeper-centralized-storage.png) redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。 -![redis-gossip](/img/redis-gossip.png) +![redis-gossip](/images/redis-gossip.png) **集中式**的**好处**在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;**不好**在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。 -gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。 +gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。 -- 10000 端口 -每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong`。 +- 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong`。 -- 交换的信息 -信息包括故障信息,节点的增加和删除,hash slot 信息 等等。 +- 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。 #### gossip 协议 gossip 协议包含多种消息,包含 `ping`,`pong`,`meet`,`fail` 等等。 + - meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。 + ```bash redis-trib.rb add-node ``` + 其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。 - ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。 @@ -59,7 +61,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担 每个节点每秒会执行 10 次 ping,每次会选择 5 个最久没有通信的其它节点。当然如果发现某个节点通信延时达到了 `cluster_node_timeout / 2`,那么立即发送 ping,避免数据交换延时过长,落后的时间太长了。比如说,两个节点之间都 10 分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。所以 `cluster_node_timeout` 可以调节,如果调得比较大,那么会降低 ping 的频率。 -每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 `3` 个其它节点的信息,最多包含`总结点-2` 个其它节点的信息。 +每次 ping,会带上自己节点的信息,还有就是带上 1/10 其它节点的信息,发送出去,进行交换。至少包含 `3` 个其它节点的信息,最多包含 `总节点数减 2` 个其它节点的信息。 ### 分布式寻址算法 - hash 算法(大量缓存重建) @@ -69,7 +71,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担 #### hash 算法 来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致**大部分的请求过来,全部无法拿到有效的缓存**,导致大量的流量涌入数据库。 -![hash](/img/hash.png) +![hash](/images/hash.png) #### 一致性 hash 算法 一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。 @@ -80,7 +82,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担 燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成**缓存热点**的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。 -![consistent-hashing-algorithm](/img/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。 @@ -89,10 +91,10 @@ redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master 任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。 -![hash-slot](/img/hash-slot.png) +![hash-slot](/images/hash-slot.png) ### redis cluster 的高可用与主备切换原理 -redis cluster 的高可用的原理,几乎跟哨兵是类似的 +redis cluster 的高可用的原理,几乎跟哨兵是类似的。 #### 判断节点宕机 如果一个节点认为另外一个节点宕机,那么就是 `pfail`,**主观宕机**。如果多个节点都认为另外一个节点宕机了,那么就是 `fail`,**客观宕机**,跟哨兵的原理几乎一样,sdown,odown。 diff --git a/docs/high-concurrency/redis-consistence.md b/docs/high-concurrency/redis-consistence.md index f8a3f0b..a9166ad 100644 --- a/docs/high-concurrency/redis-consistence.md +++ b/docs/high-concurrency/redis-consistence.md @@ -27,11 +27,11 @@ 其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。 ### 最初级的缓存不一致问题及解决方案 -问题:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。 +问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。 -![redis-junior-inconsistent](/img/redis-junior-inconsistent.png) +![redis-junior-inconsistent](/images/redis-junior-inconsistent.png) -解决思路:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。 +解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。 ### 比较复杂的数据不一致问题分析 数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,**查到了修改前的旧数据**,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了... @@ -44,7 +44,7 @@ 更新数据的时候,根据**数据的唯一标识**,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。 -一个队列对应一个工作线程,每个工作线程**串行**拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。 +一个队列对应一个工作线程,每个工作线程**串行**拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,没有读到缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。 这里有一个**优化点**,一个队列中,其实**多个更新缓存请求串在一起是没意义的**,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。 @@ -53,6 +53,7 @@ 如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。 高并发的场景下,该解决方案要注意的问题: + - 读请求长时阻塞 由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。 @@ -63,16 +64,28 @@ 一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。 +**如果一个内存队列中可能积压的更新操作特别多**,那么你就要**加机器**,让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少。 + +其实根据之前的项目经验,一般来说,数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的。像这种针对读高并发、读缓存架构的项目,一般来说写请求是非常少的,每秒的 QPS 能到几百就不错了。 + +我们来**实际粗略测算一下**。 + +如果一秒有 500 的写操作,如果分成 5 个时间片,每 200ms 就 100 个写操作,放到 20 个内存队列中,每个内存队列,可能就积压 5 个写操作。每个写操作性能测试后,一般是在 20ms 左右就完成,那么针对每个内存队列的数据的读请求,也就最多 hang 一会儿,200ms 以内肯定能返回了。 + +经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。 + - 读请求并发量过高 -这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值。 +这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。 但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。 - 多服务实例部署的请求路由 -可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 nginx 服务器**路由到相同的服务实例上**。 +可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。 + +比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。 - 热点商品的路由问题,导致请求的倾斜 -万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。 \ No newline at end of file +万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。 \ 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 757fcad..7996800 100644 --- a/docs/high-concurrency/redis-master-slave.md +++ b/docs/high-concurrency/redis-master-slave.md @@ -2,7 +2,7 @@ 单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑**读高并发**的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的**读请求全部走从节点**。这样也可以很轻松实现水平扩容,**支撑读高并发**。 -![redis-master-slave](/img/redis-master-slave.png) +![redis-master-slave](/images/redis-master-slave.png) redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发 @@ -23,7 +23,7 @@ redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并 如果这是 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](/img/redis-master-slave-replication.png) +![redis-master-slave-replication](/images/redis-master-slave-replication.png) ### 主从复制的断点续传 从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。 @@ -47,9 +47,9 @@ slave 不会过期 key,只会等待 master 过期 key。如果 master 过期 ## 复制的完整流程 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。 +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](/img/redis-master-slave-replication-detail.png) +![redis-master-slave-replication-detail](/images/redis-master-slave-replication-detail.png) ### 全量复制 - master 执行 bgsave ,在本地生成一份 rdb 快照文件。 @@ -64,8 +64,8 @@ client-output-buffer-limit slave 256MB 64MB 60 ### 增量复制 - 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。 -- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是1MB。 -- msater就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。 +- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。 +- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。 ### heartbeat 主从节点互相都会发送 heartbeat 信息。 @@ -84,6 +84,6 @@ master 每次接收到写命令之后,先在内部写入数据,然后异步 redis 的高可用架构,叫做 `failover` **故障转移**,也可以叫做主备切换。 -master node 在故障时,自动检测,并且将某个 slave node 自动切换位 master node的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。 +master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 redis 的主从架构下的高可用。 -后面会详细说明 redis **基于哨兵的高可用性**。 \ No newline at end of file +后面会详细说明 redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.md)。 \ No newline at end of file diff --git a/docs/high-concurrency/redis-persistence.md b/docs/high-concurrency/redis-persistence.md index 0165330..db9fc72 100644 --- a/docs/high-concurrency/redis-persistence.md +++ b/docs/high-concurrency/redis-persistence.md @@ -11,7 +11,7 @@ redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了 ## 面试题剖析 持久化主要是做灾难恢复、数据恢复,也可以归类到高可用的一个环节中去,比如你 redis 整个挂了,然后 redis 就不可用了,你要做的事情就是让 redis 变得可用,尽快变得可用。 -重启 redis,尽快让它堆外提供服务,如果没做数据备份,这时候 redis 启动了,也不可用啊,数据都没了。 +重启 redis,尽快让它对外提供服务,如果没做数据备份,这时候 redis 启动了,也不可用啊,数据都没了。 很可能说,大量的请求过来,缓存全部无法命中,在 redis 里根本找不到数据,这个时候就死定了,出现**缓存雪崩**问题。所有请求没有在 redis 命中,就会去 mysql 数据库这种数据源头中去找,一下子 mysql 承接高并发,然后就挂了... @@ -28,7 +28,7 @@ redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了 如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 **AOF** 来重新构建数据,因为 AOF 中的**数据更加完整**。 #### RDB 优缺点 -- RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份redis中的数据。 +- RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 redis 中的数据。 - RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis **保持高性能**,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。 - 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。 @@ -38,13 +38,13 @@ redis 如果仅仅只是将数据缓存在内存里面,如果 redis 宕机了 #### AOF 优缺点 - AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次`fsync`操作,最多丢失 1 秒钟的数据。 - AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。 -- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。 +- 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 发生过 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 来进行快速的数据恢复。 \ No newline at end of file +### RDB 和 AOF 到底该如何选择 +- 不要仅仅使用 RDB,因为那样会导致你丢失很多数据; +- 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug; +- redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。 diff --git a/docs/high-concurrency/redis-sentinel.md b/docs/high-concurrency/redis-sentinel.md index dd3d8ac..d755a23 100644 --- a/docs/high-concurrency/redis-sentinel.md +++ b/docs/high-concurrency/redis-sentinel.md @@ -2,12 +2,14 @@ ## 哨兵的介绍 sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的一个组件,主要有以下功能: + - 集群监控:负责监控 redis master 和 slave 进程是否正常工作。 - 消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。 - 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。 - 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。 哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。 + - 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。 - 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。 @@ -17,13 +19,16 @@ sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的 - 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。 哨兵集群必须部署 2 个以上节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1。 + ``` +----+ +----+ | M1 |---------| R1 | | S1 | | S2 | +----+ +----+ ``` + 配置 `quorum=1`,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候,需要 majority,也就是大多数哨兵都是运行的。 + ``` 2 个哨兵,majority=2 3 个哨兵,majority=2 @@ -31,9 +36,11 @@ sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的 5 个哨兵,majority=3 ... ``` + 如果此时仅仅是 M1 进程宕机了,哨兵 s1 正常运行,那么故障转移是 OK 的。但是如果是整个 M1 和 S1 运行的机器宕机了,那么哨兵只有 1 个,此时就没有 majority 来允许执行故障转移,虽然另外一台机器上还有一个 R1,但是故障转移不会执行。 经典的 3 节点哨兵集群是这样的: + ``` +----+ | M1 | @@ -51,51 +58,58 @@ sentinel,中文名是哨兵。哨兵是 redis 集群机构中非常重要的 ## redis 哨兵主备切换的数据丢失问题 ### 两种情况和导致数据丢失 主备切换的过程,可能会导致数据丢失: + - 异步复制导致的数据丢失 + 因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。 -![async-replication-data-lose-case](/img/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 写入的数据,因此,这部分数据也就丢失了。 +此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了。 -![redis-cluster-split-brain](/img/redis-cluster-split-brain.png) +![redis-cluster-split-brain](/images/redis-cluster-split-brain.png) ### 数据丢失问题的解决方案 进行如下配置: + ```bash min-slaves-to-write 1 min-slaves-max-lag 10 ``` + 表示,要求至少有 1 个 slave,数据复制和同步的延迟不能超过 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 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 `is-master-down-after-milliseconds` 指定的毫秒数之后,就主观认为 master 宕机了;如果一个哨兵在指定时间内,收到了 quorum 数量的 其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。 +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 的监控配置。 +每隔两秒钟,每个哨兵都会往自己监控的某个 master+slaves 对应的 `__sentinel__:hello` channel 里**发送一个消息**,内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置。 -每个哨兵也会去**监听**自己监控的每个 master+slaves 对应的`__sentinel__:hello` channel,然后去感知到同样在监听这个 master+slaves 的其他哨兵的存在。 +每个哨兵也会去**监听**自己监控的每个 master+slaves 对应的 `__sentinel__:hello` channel,然后去感知到同样在监听这个 master+slaves 的其他哨兵的存在。 每个哨兵还会跟其他哨兵交换对 `master` 的监控配置,互相进行监控配置的同步。 ## slave 配置的自动纠正 -哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 复制现有 master 的数据; 如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上。 +哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 复制现有 master 的数据;如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上。 ## slave->master 选举算法 如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息: @@ -105,20 +119,22 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过 - 复制 offset - run id -如果一个 slave 跟 master 断开连接的时间已经超过了`down-after-milliseconds`的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。 +如果一个 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。 ## quorum 和 majority -每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还得得到 majority 哨兵的授权,才能正式执行切换。 +每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还需要得到 majority 哨兵的授权,才能正式执行切换。 -如果 quorum < majority,比如 5 个哨兵,majority 就是 3,quorum 设置为2,那么就 3 个哨兵授权就可以执行切换。 +如果 quorum < majority,比如 5 个哨兵,majority 就是 3,quorum 设置为 2,那么就 3 个哨兵授权就可以执行切换。 但是如果 quorum >= majority,那么必须 quorum 数量的哨兵都授权,比如 5 个哨兵,quorum 是 5,那么必须 5 个哨兵都同意授权,才能执行切换。 @@ -129,7 +145,7 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过 如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待 failover-timeout 时间,然后接替继续执行切换,此时会重新获取一个新的 configuration epoch,作为新的 version 号。 -## configuraiton 传播 -哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 pub/sub 消息机制。 +## configuration 传播 +哨兵完成切换之后,会在自己本地更新生成最新的 master 配置,然后同步给其他的哨兵,就是通过之前说的 `pub/sub` 消息机制。 这里之前的 version 号就很重要了,因为各种消息都是通过一个 channel 去发布和监听的,所以一个哨兵完成一次新的切换之后,新的 master 配置是跟着新的 version 号的。其他的哨兵都是根据版本号的大小来更新自己的 master 配置的。 \ No newline at end of file diff --git a/docs/high-concurrency/redis-single-thread-model.md b/docs/high-concurrency/redis-single-thread-model.md index 8227724..f86a10a 100644 --- a/docs/high-concurrency/redis-single-thread-model.md +++ b/docs/high-concurrency/redis-single-thread-model.md @@ -11,38 +11,44 @@ redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 ### redis 和 memcached 有啥区别? #### redis 支持复杂的数据结构 -redis 相比 memcached 来说,拥有更多的数据结构,能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。 +redis 相比 memcached 来说,拥有[更多的数据结构](/docs/high-concurrency/redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, redis 会是不错的选择。 #### redis 原生支持集群模式 在 redis3.x 版本中,便能支持 cluster 模式,而 memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。 #### 性能对比 -由于 redis 只使用单核,而 memcached 可以使用多核,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis,虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。 +由于 redis 只使用**单核**,而 memcached 可以使用**多核**,所以平均每一个核上 redis 在存储小数据时比 memcached 性能更高。而在 100k 以上的数据中,memcached 性能要高于 redis。虽然 redis 最近也在存储大数据的性能上进行优化,但是比起 memcached,还是稍有逊色。 ### redis 的线程模型 -redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,根据 socket 上的事件来选择对应的事件处理器进行处理。 +redis 内部使用文件事件处理器 `file event handler`,这个文件事件处理器是单线程的,所以 redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理。 文件事件处理器的结构包含 4 个部分: + - 多个 socket - IO 多路复用程序 - 文件事件分派器 - 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) -多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将 socket 产生的事件放入队列中排队,事件分派器每次从队列中取出一个事件,把该事件交给对应的事件处理器进行处理。 +多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。 来看客户端与 redis 的一次通信过程: -![redis-single-thread-model](/img/redis-single-thread-model.png) +![redis-single-thread-model](/images/redis-single-thread-model.png) -客户端 socket01 向 redis 的 server socket 请求建立连接,此时 server socket 会产生一个 `AE_READABLE` 事件,IO 多路复用程序监听到 server socket 产生的事件后,将该事件压入队列中。文件事件分派器从队列中获取该事件,交给**连接应答处理器**。连接应答处理器会创建一个能与客户端通信的 socket01,并将该 socket01 的 `AE_READABLE` 事件与命令请求处理器关联。 +要明白,通信是通过 socket 来完成的,不懂的同学可以先去看一看 socket 网络编程。 -假设此时客户端发送了一个 `set key value` 请求,此时 redis 中的 socket01 会产生 `AE_READABLE` 事件,IO 多路复用程序将事件压入队列,此时事件分派器从队列中获取到该事件,由于前面 socket01 的 `AE_READABLE` 事件已经与命令请求处理器关联,因此事件分派器将事件交给命令请求处理器来处理。命令请求处理器读取 socket01 的 `key value` 并在自己内存中完成 `key value` 的设置。操作完成后,它会将 socket01 的 `AE_WRITABLE` 事件与命令回复处理器关联。 +首先,redis 服务端进程初始化的时候,会将 server socket 的 `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` 事件与命令回复处理器关联。 如果此时客户端准备好接收返回结果了,那么 redis 中的 socket01 会产生一个 `AE_WRITABLE` 事件,同样压入队列中,事件分派器找到相关联的命令回复处理器,由命令回复处理器对 socket01 输入本次操作的一个结果,比如 `ok`,之后解除 socket01 的 `AE_WRITABLE` 事件与命令回复处理器的关联。 这样便完成了一次通信。 ### 为啥 redis 单线程模型也能效率这么高? -- 纯内存操作 -- 核心是基于非阻塞的 IO 多路复用机制 -- 单线程反而避免了多线程的频繁上下文切换问题 \ No newline at end of file +- 纯内存操作。 +- 核心是基于非阻塞的 IO 多路复用机制。 +- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 +- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 \ No newline at end of file diff --git a/docs/high-concurrency/why-cache.md b/docs/high-concurrency/why-cache.md index ab28873..dccd71f 100644 --- a/docs/high-concurrency/why-cache.md +++ b/docs/high-concurrency/why-cache.md @@ -21,12 +21,12 @@ 缓存啊,折腾 600ms 查出来的结果,扔缓存里,一个 key 对应一个 value,下次再有人查,别走 mysql 折腾 600ms 了,直接从缓存里,通过一个 key 查出来一个 value,2ms 搞定。性能提升 300 倍。 -就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么结果直接放在缓存,后面直接读缓存就好。 +就是说对于一些需要复杂操作耗时查出来的结果,且确定后面不怎么变化,但是有很多读请求,那么直接将查询出来的结果放在缓存中,后面直接读缓存就好。 #### 高并发 mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql 单机支撑到 `2000QPS` 也开始容易报警了。 -所以要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 key-value 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。 +所以要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。 > 缓存是走内存的,内存天然就支撑高并发。 diff --git a/docs/high-concurrency/why-mq.md b/docs/high-concurrency/why-mq.md index 4aef272..2f611da 100644 --- a/docs/high-concurrency/why-mq.md +++ b/docs/high-concurrency/why-mq.md @@ -28,13 +28,13 @@ #### 解耦 看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃...... -![mq-1](/img/mq-1.png) +![mq-1](/images/mq-1.png) 在这个场景中,A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?头发都白了啊! 如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。 -![mq-2](/img/mq-2.png) +![mq-2](/images/mq-2.png) **总结**:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。 @@ -43,26 +43,26 @@ #### 异步 再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。 -![mq-3](/img/mq-3.png) +![mq-3](/images/mq-3.png) 一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。 如果**使用 MQ**,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快! -![mq-4](/img/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。 +每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。 一般的 MySQL,扛到每秒 2k 个请求就差不多了,如果每秒请求到 5k 的话,可能就直接把 MySQL 给打死了,导致系统崩溃,用户也就没法再使用系统了。 但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。 -![mq-5](/img/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](/img/mq-6.png) +![mq-6](/images/mq-6.png) 这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。 @@ -90,7 +90,7 @@ A 系统处理完了直接返回成功了,人都以为你这个请求就成功 | topic 数量对吞吐量的影响 | | | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | | 时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 | | 可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | -| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | +| 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | | 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | @@ -100,8 +100,8 @@ A 系统处理完了直接返回成功了,人都以为你这个请求就成功 后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高; -不过现在确实越来越多的公司,会去用 RocketMQ,确实很不错(阿里出品),但社区可能有突然黄掉的风险,对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。 +不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 [Apache](https://github.com/apache/rocketmq),但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。 所以**中小型公司**,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;**大型公司**,基础架构研发实力较强,用 RocketMQ 是很好的选择。 -如果是**大数据领域**的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。 \ No newline at end of file +如果是**大数据领域**的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。 diff --git a/docs/micro-services/README.md b/docs/micro-services/README.md new file mode 100644 index 0000000..4a4ebc6 --- /dev/null +++ b/docs/micro-services/README.md @@ -0,0 +1 @@ +# 微服务架构 \ No newline at end of file diff --git a/docs/micro-services/images/PreferFunctionalStaffOrganization.png b/docs/micro-services/images/PreferFunctionalStaffOrganization.png new file mode 100644 index 0000000..e7f5a6f Binary files /dev/null and b/docs/micro-services/images/PreferFunctionalStaffOrganization.png differ diff --git a/docs/micro-services/images/basic-pipeline.png b/docs/micro-services/images/basic-pipeline.png new file mode 100644 index 0000000..4d9fa62 Binary files /dev/null and b/docs/micro-services/images/basic-pipeline.png differ diff --git a/docs/micro-services/images/conways-law.png b/docs/micro-services/images/conways-law.png new file mode 100644 index 0000000..5956773 Binary files /dev/null and b/docs/micro-services/images/conways-law.png differ diff --git a/docs/micro-services/images/decentralised-data.png b/docs/micro-services/images/decentralised-data.png new file mode 100644 index 0000000..c2b8519 Binary files /dev/null and b/docs/micro-services/images/decentralised-data.png differ diff --git a/docs/micro-services/images/micro-deployment.png b/docs/micro-services/images/micro-deployment.png new file mode 100644 index 0000000..55c710e Binary files /dev/null and b/docs/micro-services/images/micro-deployment.png differ diff --git a/docs/micro-services/images/sketch.png b/docs/micro-services/images/sketch.png new file mode 100644 index 0000000..ac8827d Binary files /dev/null and b/docs/micro-services/images/sketch.png differ diff --git a/docs/micro-services/microservices-introduction.md b/docs/micro-services/microservices-introduction.md new file mode 100644 index 0000000..5decf29 --- /dev/null +++ b/docs/micro-services/microservices-introduction.md @@ -0,0 +1,196 @@ +# 微服务 +> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。
本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。 + +过去几年中出现了“微服务架构”这一术语,它描述了将软件应用程序设计为若干个可独立部署的服务套件的特定方法。尽管这种架构风格尚未有精确的定义,但围绕业务能力、自动部署、端点智能以及语言和数据的分散控制等组织来说,它们还是存在着某些共同特征。 + +“微服务”——在拥挤的软件架构街道上又一个新名词。虽然我们的自然倾向是对它轻蔑一瞥,但这一术语描述了一种越来越具有吸引力的软件系统风格。在过去几年中,我们已经看到许多项目使用了这种风格,到目前为止其结果都是正向的,以至于它变成了我们 ThoughtWorks 许多同事构建企业应用程序的默认风格。然而遗憾的是,并没有太多信息可以概述微服务的风格以及如何实现。 + +简而言之,微服务架构风格[1]是一种将单个应用程序开发为一套小型服务的方法,每个小型服务都在自己的进程中运行,并以轻量级机制(通常是 HTTP 资源 API)进行通信。这些服务围绕业务功能构建,可通过全自动部署机制来独立部署。这些服务共用一个最小型的集中式管理,它们可以使用不同的编程语言编写,并使用不同的数据存储技术。 + +在开始解释微服务风格之前,将它与单片(monolithic)风格进行比较是有用的:单片应用程序被构建为单一单元。企业应用程序通常由三个部分构成:客户端用户界面(由用户机器上的浏览器中运行的 HTML 页面和 Javascript 组成)、数据库(由许多表组成,通常是在关系型数据库中管理)系统、服务器端应用程序。服务器端应用程序处理 HTTP 请求,执行一些逻辑处理,从数据库检索和更新数据,选择数据并填充到要发送到浏览器的 HTML 视图中。这个服务器端应用程序是一个整体——一个逻辑可执行文件[2]。对系统的任何更改都涉及构建和部署新版本的服务器端应用程序。 + +这种单片服务器是构建这种系统的自然方式。处理一个请求的所有逻辑都在一个进程中运行,允许你使用语言的基本功能将应用程序划分为类、函数和命名空间。需要注意的是,你可以在开发人员的笔记本电脑上运行和测试应用程序,并使用部署管道确保对程序做出的改动被适当测试并部署到生产环境中。你可以通过在负载均衡器后面运行许多实例来水平扩展整体块。 + +单片应用程序可以取得成功,但越来越多的人对它们感到不满——尤其是在将更多应用程序部署到云的时候。变更周期被捆绑在一起——即使只是对应用程序的一小部分进行了更改,也需要重建和部署整个单片应用。随着时间的推移,通常很难保持良好的模块化结构,也更难以保持应该只影响该模块中的一个模块的更改。对系统进行扩展时,不得不扩展整个应用系统,而不能仅扩展该系统中需要更多资源的那些部分。 + +![sketch](/images/sketch.png) + +这些不满催生了微服务架构风格:将应用程序构建为服务套件。除了服务可独立部署、独立扩展的事实之外,每个服务还提供了一个牢固的模块边界,甚至允许以不同的编程语言编写不同的服务。他们也可以由不同的团队管理。 + +我们并不认为微服务风格是新颖的或创新的,其根源至少可以追溯到 Unix 的设计原则。但我们认为没有足够多的人考虑微服务架构,如果使用它,许多软件的开发会变得更好。 + +## 微服务架构的特征 +虽然不能说微服务架构风格有正式的定义,但我们可以尝试描述一下我们认为的在符合这个标签的架构中,它们所具有的一些共同特征。与概述共同特征的任何定义一样,并非所有微服务架构都具有所有特征,但我们确实期望大多数微服务架构都具有大多数特征。虽然我们的作者一直是这个相当宽松的社区的活跃成员,但我们的本意还是尝试描述我们两人在自己和自己所了解的团队的工作中所看到的情况。特别要说明的是,我们没有制定一些相关的定义。 + +### 通过服务进行组件化 +只要我们参与软件行业,就一直希望通过将组件集成在一起来构建系统,就像我们在物理世界中看到的事物的构建方式一样。在过去的几十年中,我们已经看到了大多数语言平台的公共软件库都取得了极大的进展。 + +在谈论组件时,就会碰到一个有关定义的难题,即什么是组件?我们的定义是,组件是可独立更换和升级的软件单元。 + +微服务架构也会使用软件库,但组件化软件的主要方式是拆分为多个服务。我们把库定义为链接到程序并使用内存函数调用来调用的组件,而服务是一种进程外组件,通过 Web 服务请求或远程过程调用等机制进行通信。(这与许多面向对象程序中的服务对象的概念是不同的[3]。) + +将服务作为组件(而不是库)的一个主要原因是服务可以独立部署。如果你有一个应用程序[4]是由单一进程里的多个库组成,任何一个组件的更改都会导致整个应用程序的重新部署。但如果应用程序可拆分为多个服务,那么单个服务的变更只需要重新部署该服务即可。当然这也不是绝对的,一些服务接口的修改可能会导致多个服务之间的协同修改,但一个好的微服务架构的目的是通过内聚服务边界和服务协议的演进机制来最小化这些协同修改。 + +将服务用作组件的另一个结果是更明确的组件接口。大多数语言没有一个良好的机制来定义显式发布的接口。通常,它只是文档和规则来阻止客户端破坏组件的封装,这会导致组件之间过于紧耦合。通过使用显式远程调用机制,服务可以更轻松地避免这种情况。 + +像这样使用服务确实存在一些不好的地方。远程调用比进程内调用更昂贵,远程 API 需要设计成较粗的粒度,这通常更难以使用。如果你需要更改组件之间的职责分配,那么当你跨越进程边界时,这种组件行为的改动会更加难以实现。 + +近似地,我们可以把一个个服务映射为一个个运行时进程,但这仅仅是一个近似而已。一个服务可能包括多个始终一起开发和部署的进程,比如一个应用系统的进程和仅由该服务使用的数据库。 + +### 围绕业务能力进行组织 +在将大型应用程序拆分为多个部分时,管理层往往侧重于技术层面,从而导致 UI 团队、服务器端逻辑团队、数据库团队的划分。当团队按照这些方式分开时,即便是简单的更改也可能导致跨团队项目的时间和预算批准。一个聪明的团队将围绕这个进行优化,“两害相权取其轻”——只需将逻辑强制应用到他们可以访问的任何应用程序中。换句话说,逻辑无处不在。这是康威定律[5]的一个例子。 + +> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。
—— 梅尔文•康威,1967年 + +![conways-law](/images/conways-law.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) 的概念,开发团队对生产中的软件负全部责任。这使开发者经常接触他们的软件在生产环境如何工作,并增加与他们的用户联系,因为他们必须承担至少部分的支持工作。 + +产品心态与业务能力的联系紧密相连。要持续关注软件如何帮助用户提升业务能力,而不是把软件看成是将要完成的一组功能。 + +没有理由说为什么这种方法不能用在单一应用程序上,但较小的服务粒度,使得它更容易在服务开发者和用户之间建立个人关系。 + +### 智能端点和哑管 +在不同进程之间建立通信时,我们已经看到许多产品和方法,都强调将大量的智能特性放入通信机制本身。一个很好的例子是企业服务总线(ESB),其中 ESB 产品通常包括用于消息路由、编排、转换和应用业务规则的复杂工具。 + +微服务社区倾向于采用另一种方法:智能端点和哑管。基于微服务构建的应用程序的目标是尽可能的解耦和尽可能的内聚——他们拥有自己的领域逻辑,他们的行为更像经典 UNIX 理念中的过滤器——接收请求,应用适当的逻辑并产生响应。使用简单的 REST 风格的协议来编排它们,而不是使用像 WS-Choreography 或者 BPEL 或者通过中心工具编制(orchestration)等复杂的协议。 + +最常用的两种协议是带有资源 API 的 HTTP 请求-响应和轻量级消息传递[8]。对第一种协议最好的表述是 + +> 本身就是 web,而不是隐藏在 web 的后面。
——[Ian Robinson](http://www.amazon.com/gp/product/0596805829?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0596805829) + +微服务团队使用的规则和协议,正是构建万维网的规则和协议(在更大程度上是 UNIX 的)。从开发者和运营人员的角度讲,通常使用的资源可以很容易的缓存。 + +第二种常用方法是在轻量级消息总线上传递消息。选择的基础设施是典型的哑的(哑在这里只充当消息路由器)——像 RabbitMQ 或 ZeroMQ 这样简单的实现仅仅提供一个可靠的异步交换结构 ——在服务里,智能特性仍旧存在于那些生产和消费诸多消息的各个端点中,即存在于各个服务中。 + +单体应用中,组件都在同一进程内执行,它们之间通过方法调用或函数调用通信。把单体变成微服务最大的问题在于通信模式的改变。一种幼稚的转换是从内存方法调用转变成 RPC,这导致频繁通信且性能不好。相反,你需要用粗粒度通信代替细粒度通信。 + +### 去中心化的治理 +集中治理的一个后果是单一技术平台的标准化发展趋势。经验表明,这种方法正在收缩 ——不是每个问题都是钉子,不是每个问题都是锤子。我们更喜欢使用正确的工具来完成工作,而单体应用程序在一定程度上可以利用语言的优势,这是不常见的。 + +把单体的组件分裂成服务,在构建这些服务时可以有自己的选择。你想使用 Node.js 开发一个简单的报告页面?去吧。用 C++ 实现一个特别粗糙的近乎实时的组件?好极了。你想换用一个更适合组件读操作数据的不同风格的数据库?我们有技术来重建它。 + +当然,仅仅因为你可以做些什么,而不意味着你应该这样做——但用这种方式划分系统意味着你可以选择。 + +团队在构建微服务时也更喜欢用不同的方法来达标。他们更喜欢生产有用的工具这种想法,而不是写在纸上的标准,这样其他开发者可以用这些工具解决他们所面临的相似的问题。有时,这些工具通常在实施中收获并与更广泛的群体共享,但不完全使用一个内部开源模型。现在 git 和 github 已经成为事实上的版本控制系统的选择,在内部开放源代码的实践也正变得越来越常见。 + +Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形式分享有用的且经过市场检验的代码,这激励其他开发者用类似的方式解决相似的问题,同时还为采用不同方法敞开了大门。共享库倾向于聚焦在数据存储、进程间通信和我们接下来要深入讨论的基础设施自动化的共性问题。 + +对于微服务社区来说,开销特别缺乏吸引力。这并不是说社区不重视服务合约。恰恰相反,因为他们有更多的合约。只是他们正在寻找不同的方式来管理这些合约。像 [Tolerant Reader](https://martinfowler.com/bliki/TolerantReader.html) 和 [Consumer-Driven Contracts](https://martinfowler.com/articles/consumerDrivenContracts.html) 这样的模式通常被用于微服务。这些援助服务合约在独立进化。执行消费者驱动的合约作为构建的一部分,增加了信心并对服务是否在运作提供了更快的反馈。事实上,我们知道澳大利亚的一个团队用消费者驱动的合约这种模式来驱动新业务的构建。他们使用简单的工具定义服务的合约。这已变成自动构建的一部分,即使新服务的代码还没写。服务仅在满足合约的时候才被创建出来 - 这是在构建新软件时避免 "YAGNI"[9] 困境的一个优雅的方法。围绕这些成长起来的技术和工具,通过减少服务间的临时耦合,限制了中心合约管理的需要。 + +也许去中心化治理的最高境界就是亚马逊广为流传的 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) + +对跨微服务的数据来说,去中心化责任对管理升级有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这个方法通常用在单体中。 + +像这样使用事务有助于一致性,但会产生显著地临时耦合,这在横跨多个服务时是有问题的。分布式事务是出了名的难以实现,因此微服务架构强调[服务间的无事务协作](http://www.eaipatterns.com/ramblings/18_starbucks.html),对一致性可能只是最后一致性和通过补偿操作处理问题有明确的认知。 + +对很多开发团队来说,选择用这样的方式管理不一致性是一个新的挑战,但这通常与业务实践相匹配。通常业务处理一定程度的不一致,以快速响应需求,同时有某些类型的逆转过程来处理错误。这种权衡是值得的,只要修复错误的代价小于更大一致性下损失业务的代价。 + +### 基建自动化 +基础设施自动化技术在过去几年中发生了巨大变化——特别是云和 AWS 的发展降低了构建、部署和运行微服务的操作复杂性。 + +许多使用微服务构建的产品或系统都是由具有丰富的持续交付和持续集成经验的团队构建的。以这种方式构建软件的团队广泛使用基础设施自动化技术。如下面显示的构建管道所示。 + +![basic-pipeline](/images/basic-pipeline.png) + +由于这并不是一篇关于持续交付的文章,我们在这里只关注持续交付的几个关键特性。我们希望有尽可能多的信心确保我们的软件正常运行,因此我们进行了大量的**自动化测试**。想让软件达到“晋级”(Promotion)状态从而“推上”流水线,就意味着要在每一个新的环境中,对软件进行**自动化部署**。 + +一个单块应用程序可以非常愉快地通过这些环境构建、测试和推动。事实证明,一旦你为单体投入了自动化整体生产,那么部署更多的应用程序似乎不再那么可怕了。请记住,持续交付的目标之一就是让“部署”工作变得“枯燥”,所以无论是一个还是三个应用程序,只要部署工作依旧很“枯燥”,那么就没什么可担心的了[12]。 + +我们看到团队大量的基础设施自动化的另一个领域是在管理生产环境中的微服务。与我们上面的断言(只要部署很无聊)相比,单块和微服务之间没有太大的区别,但是每个部署的运行环境可能会截然不同。 + +![micro-deployment](/images/micro-deployment.png) + +### 设计时为故障做好准备 +使用服务作为组件的结果是,需要设计应用程序以便它们能够容忍服务的失败。如果服务提供者商不可用,任何服务呼叫都可能失败,客户必须尽可能优雅地对此做出响应。与单片设计相比,这是一个缺点,因为它这会引入额外的复杂性来处理它。结果是微服务团队不断反思服务失败是如何影响用户体验的。Netflix 的 [Simian Army](https://github.com/Netflix/SimianArmy) 能够引发服务甚至数据中心的故障在工作日发生故障,从而来测试应用程序的弹性和监控能力。 + +生产中的这种自动化测试足以让大多数运维团队兴奋得浑身颤栗,就像在一周的长假即将到来前一样。这并不是说单块架构风格不能构建先进的监控系统——只是根据我们的经验,这在单块系统中并不常见罢了。 + +由于服务可能随时发生故障,因此能够快速检测故障并在可能的情况下自动恢复服务就显得至关重要。微服务应用程序非常重视应用程序的实时监控,比如检查架构元素(数据库每秒获得多少请求)和业务相关度量(例如每分钟收到多少订单)。语义监控可以提供出现问题的早期预警系统,从而触发开发团队跟进和调查。 + +这对于微服务架构来说尤为重要,因为微服务偏好编排和事件写作,这会导致一些紧急状况。虽然许多权威人士对于偶然事件的价值持积极态度,但事实是,“突发行为”有时可能是一件坏事。监控至关重要,它能够快速发现不良紧急行为并进行修复。 + +单块系统也可以像微服务一样实现透明的监控——事实上,它们也应该如此。不同之处在于你必须能够知道在不同进程中运行的服务在何时断开了连接。对于同一过程中的库,这种透明性用处并不大。 + +微服务团队希望看到针对每个服务的复杂监控和日志记录,例如显示“运行/宕机”状态的仪表盘以及各种运维和业务相关的指标。有关断路器状态,当前吞吐量和延迟的详细信息也是我们在工作中经常遇到的其他例子。 + +### 演化设计 +微服务从业者通常有进化设计的背景,并把服务分解视为进一步的工具,使应用程序开发人员能够控制应用程序中的更改,而不会降低变更速度。变更控制并不一定意味着变更的减少——在正确的态度和工具的帮助下,你可以对软件进行频繁,快速且有良好控制的更改。 + +每当要试图将软件系统分解为组件时,你就会面临这样的决策,即如何进行拆分——我们决定拆分应用程序的原则是什么?组件的关键属性具有独立替换和可升级性的特点[13]——这意味着我们寻找这些点,想象如何在不影响其协作者的情况下重写组件。实际上,许多微服务组通过明确地期望许多服务被废弃而不是长期演变来进一步考虑这一点。 + +Guardian 网站是设计和构建成单块应用程序的一个很好的例子,但是它也在微服务方向上不断发展演化。原先的单块系统仍然是网站的核心,但他们更喜欢通过构建一些微服务 API 的方式来添加新的功能。这种方法对于本质上是临时的功能尤其方便,例如处理体育赛事的专用页面。网站的这一部分可以使用快速开发语言快速组合在一起,在赛事结束后立即删除。我们在金融机构看到过类似的方法,为市场机会增加新服务,并在几个月甚至几周后丢弃。 + +这种强调可替换性的特点,是模块化设计一般性原则的一个特例,即通过变化模式来驱动模块化的实现[14]。大家都愿意将那些同时发生变化的东西放在同一个模块,很少变化的系统模块应该与目前正在经历大量变动的系统处于不同的服务中。如果你发现自己反复更改两项服务,那就表明它们应该合并了。 + +将组件放入服务中可以为更细粒度的发布计划添加机会。对于单体来说,任何更改都需要完整构建和部署整个应用程序。但是,使用微服务,你只需要重新部署你修改的服务。这可以简化并加快发布过程。缺点是你必须担心一项服务的变化会打破其消费者。传统的集成方法是尝试使用版本控制来解决这个问题,但微服务世界中的偏好是仅仅把使用版本控制作为最后的手段。我们可以通过设计服务尽可能容忍服务提供者的变化来避免大量的版本控制。 + +## 微服务是未来吗? +我们写这篇文章的主要目的是解释微服务的主要思想和原则。通过花时间来做到这一点,我们清楚地认为微服务架构风格是一个重要的想法——在研发企业系统时,值得对它进行认真考虑。我们最近使用这种方式构建了几个系统,并且了解到其它团队也赞同这种风格。 + +我们了解到那些在某种程度上开创这种架构风格的先驱,包括亚马逊、Netflix、英国卫报、英国政府数字化服务中心、realestate.com.au、Forward 和 comparethemarket.com。2013 年的技术会议上充满了一些公司的例子,这些公司正在转向可以归类为微服务的公司,包括 Travis CI。此外,有很多组织长期以来一直在做我们称之为微服务的东西,但没有使用过这个名字。(通常这被标记为 SOA——尽管如我们所说,SOA 有许多相互矛盾的形式。[15]) + +然而,尽管有这些积极的经验,但并不是说我们确信微服务是软件架构的未来发展方向。虽然到目前为止我们的经验与整体应用相比是积极的,但我们意识到没有足够的时间让我们做出充分完整的判断。 + +通常,架构决策所产生的真正效果,只有在该决策做出若干年后才能真正显现。我们已经看到由带着强烈的模块化愿望的优秀团队所做的一些项目,最终却构建出一个单块架构,并在几年之内不断腐化。许多人认为,如果使用微服务就不大可能出现这种腐化,因为服务的边界是明确的,而且难以随意搞乱。然而,对于那些开发时间足够长的各种系统,除非我们已经见识得足够多,否则我们无法真正评价微服务架构是如何成熟的。 + +有人觉得微服务或许很难成熟起来,这当然是有原因的。在组件化上所做的任何工作的成功与否,取决于软件与组件的匹配程度。准确地搞清楚某个组件的边界的位置应该出现在哪里,是一项困难的工作。进化设计承认难以对边界进行正确定位,所以它将工作的重点放到了易于对边界进行重构之上。但是当各个组件成为各个进行远程通信的服务后,比起在单一进程内进行各个软件库之间的调用,此时的重构就变得更加困难。跨越服务边界的代码移动就变得困难起来。接口的任何变化,都需要在其各个参与者之间进行协调。向后兼容的层次也需要被添加进来。测试也会变得更加复杂。 + +另一个问题是,如果这些组件不能干净利落地组合成一个系统,那么所做的一切工作,仅仅是将组件内的复杂性转移到组件之间的连接之上。这样做的后果,不仅仅是将复杂性搬了家,它还将复杂性转移到那些不再明确且难以控制的边界之上。当在观察一个小型且简单的组件内部时,人们很容易觉得事情已经变得更好了,然而他们却忽视了服务之间杂乱的连接。 + +最后,还有一个团队技能的因素。新技术往往会被技术更加过硬的团队所采用。对于技术更加过硬的团队而更有效的一项技术,不一定适用于一个技术略逊一筹的团队。我们已经看到大量这样的案例,那些技术略逊一筹的团队构建出了杂乱的单块架构。当这种杂乱发生到微服务身上时,会出现什么情况?这需要花时间来观察。一个糟糕的团队,总会构建一个糟糕的系统——在这种情况下,很难讲微服务究竟是减少了杂乱,还是让事情变得更糟。 + +我们听到的一个合理的论点是,你不应该从微服务架构开始,而是从整体开始,保持模块化,并在整体出现问题时将其拆分为微服务。(这个建议并不理想,因为好的进程内接口通常不是一个好的服务接口。) + +所以我们谨慎乐观地写下这个。到目前为止,我们已经看到了足够多的微服务风格,觉得它可能是一条值得走的路。我们无法确定最终会在哪里结束,但软件开发的挑战之一是你只能根据你当前必须拥有的不完善信息做出决策。 + +## 脚注 +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. + +3: Many object-oriented designers, including ourselves, use the term service object in the [Domain-Driven Design](https://www.amazon.com/gp/product/0321125215?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321125215) sense for an object that carries out a significant process that isn't tied to an entity. This is a different concept to how we're using "service" in this article. Sadly the term service has both meanings and we have to live with the polyseme. + +4: We consider [an application to be a social construction](https://martinfowler.com/bliki/ApplicationBoundary.html) that binds together a code base, group of functionality, and body of funding. + +5: The original paper can be found on Melvyn Conway's website [here](http://www.melconway.com/Home/Committees_Paper.html). + +6: We can't resist mentioning Jim Webber's statement that ESB stands for ["Egregious Spaghetti Box"](http://www.infoq.com/presentations/soa-without-esb). + +7: Netflix makes the link explicit - until recently referring to their architectural style as fine-grained SOA. + +8: At extremes of scale, organisations often move to binary protocols - [protobufs](https://code.google.com/p/protobuf/) for example. Systems using these still exhibit the characteristic of smart endpoints, dumb pipes - and trade off transparency for scale. Most web properties and certainly the vast majority of enterprises don't need to make this tradeoff - transparency can be a big win. + +9: "YAGNI" or "You Aren't Going To Need It" is an [XP principle](http://c2.com/cgi/wiki?YouArentGonnaNeedIt) and exhortation to not add features until you know you need them. + +10: It's a little disengenuous of us to claim that monoliths are single language - in order to build systems on todays web, you probably need to know JavaScript and XHTML, CSS, your server side language of choice, SQL and an ORM dialect. Hardly single language, but you know what we mean. + +11: Adrian Cockcroft specifically mentions "developer self-service" and "Developers run what they wrote"(sic) in [this excellent presentation](http://www.slideshare.net/adrianco/flowcon-added-to-for-cmg-keynote-talk-on-how-speed-wins-and-how-netflix-is-doing-continuous-delivery) delivered at Flowcon in November, 2013. + +12: We are being a little disengenuous here. Obviously deploying more services, in more complex topologies is more difficult than deploying a single monolith. Fortunately, patterns reduce this complexity - investment in tooling is still a must though. + +13: In fact, Dan North refers to this style as *Replaceable Component Architecture* rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter. + +14: Kent Beck highlights this as one his design principles in [Implementation Patterns](https://www.amazon.com/gp/product/0321413091?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321413091). + +15: And SOA is hardly the root of this history. I remember people saying "we've been doing this for years" when the SOA term appeared at the beginning of the century. One argument was that this style sees its roots as the way COBOL programs communicated via data files in the earliest days of enterprise computing. In another direction, one could argue that microservices are the same thing as the Erlang programming model, but applied to an enterprise application context. \ No newline at end of file diff --git a/images/220px-Internet_dog.jpg b/images/220px-Internet_dog.jpg new file mode 100644 index 0000000..676cc28 Binary files /dev/null and b/images/220px-Internet_dog.jpg differ diff --git a/images/PreferFunctionalStaffOrganization.png b/images/PreferFunctionalStaffOrganization.png new file mode 100644 index 0000000..e7f5a6f Binary files /dev/null and b/images/PreferFunctionalStaffOrganization.png differ diff --git a/images/advanced-java-doocs-shishan.png b/images/advanced-java-doocs-shishan.png new file mode 100644 index 0000000..6d3e138 Binary files /dev/null and b/images/advanced-java-doocs-shishan.png differ diff --git a/img/async-replication-data-lose-case.png b/images/async-replication-data-lose-case.png similarity index 100% rename from img/async-replication-data-lose-case.png rename to images/async-replication-data-lose-case.png diff --git a/images/basic-pipeline.png b/images/basic-pipeline.png new file mode 100644 index 0000000..4d9fa62 Binary files /dev/null and b/images/basic-pipeline.png differ diff --git a/images/bulkhead-partition.jpg b/images/bulkhead-partition.jpg new file mode 100644 index 0000000..c35653e Binary files /dev/null and b/images/bulkhead-partition.jpg differ diff --git a/img/consistent-hashing-algorithm.png b/images/consistent-hashing-algorithm.png similarity index 100% rename from img/consistent-hashing-algorithm.png rename to images/consistent-hashing-algorithm.png diff --git a/images/conways-law.png b/images/conways-law.png new file mode 100644 index 0000000..5956773 Binary files /dev/null and b/images/conways-law.png differ diff --git a/images/database-id-sequence-step.png b/images/database-id-sequence-step.png new file mode 100644 index 0000000..3cf6ae3 Binary files /dev/null and b/images/database-id-sequence-step.png differ diff --git a/img/database-shard-method-1.png b/images/database-shard-method-1.png similarity index 100% rename from img/database-shard-method-1.png rename to images/database-shard-method-1.png diff --git a/img/database-shard-method-2.png b/images/database-shard-method-2.png similarity index 100% rename from img/database-shard-method-2.png rename to images/database-shard-method-2.png diff --git a/img/database-split-horizon.png b/images/database-split-horizon.png similarity index 100% rename from img/database-split-horizon.png rename to images/database-split-horizon.png diff --git a/img/database-split-vertically.png b/images/database-split-vertically.png similarity index 100% rename from img/database-split-vertically.png rename to images/database-split-vertically.png diff --git a/images/decentralised-data.png b/images/decentralised-data.png new file mode 100644 index 0000000..c2b8519 Binary files /dev/null and b/images/decentralised-data.png differ diff --git a/img/distributed-system-request-sequence.png b/images/distributed-system-request-sequence.png similarity index 100% rename from img/distributed-system-request-sequence.png rename to images/distributed-system-request-sequence.png diff --git a/img/distributed-transaction-TCC.png b/images/distributed-transaction-TCC.png similarity index 100% rename from img/distributed-transaction-TCC.png rename to images/distributed-transaction-TCC.png diff --git a/img/distributed-transaction-XA.png b/images/distributed-transaction-XA.png similarity index 100% rename from img/distributed-transaction-XA.png rename to images/distributed-transaction-XA.png diff --git a/img/distributed-transaction-local-message-table.png b/images/distributed-transaction-local-message-table.png similarity index 100% rename from img/distributed-transaction-local-message-table.png rename to images/distributed-transaction-local-message-table.png diff --git a/img/distributed-transaction-reliable-message.png b/images/distributed-transaction-reliable-message.png similarity index 100% rename from img/distributed-transaction-reliable-message.png rename to images/distributed-transaction-reliable-message.png diff --git a/images/dubbo-keep-connection.png b/images/dubbo-keep-connection.png new file mode 100644 index 0000000..70993f9 Binary files /dev/null and b/images/dubbo-keep-connection.png differ diff --git a/images/dubbo-not-keep-connection.png b/images/dubbo-not-keep-connection.png new file mode 100644 index 0000000..e666976 Binary files /dev/null and b/images/dubbo-not-keep-connection.png differ diff --git a/img/dubbo-operating-principle.png b/images/dubbo-operating-principle.png similarity index 100% rename from img/dubbo-operating-principle.png rename to images/dubbo-operating-principle.png diff --git a/img/dubbo-service-invoke-road.png b/images/dubbo-service-invoke-road.png similarity index 100% rename from img/dubbo-service-invoke-road.png rename to images/dubbo-service-invoke-road.png diff --git a/img/dubbo-spi.png b/images/dubbo-spi.png similarity index 100% rename from img/dubbo-spi.png rename to images/dubbo-spi.png diff --git a/img/e-commerce-website-detail-page-architecture-1.png b/images/e-commerce-website-detail-page-architecture-1.png similarity index 100% rename from img/e-commerce-website-detail-page-architecture-1.png rename to images/e-commerce-website-detail-page-architecture-1.png diff --git a/img/e-commerce-website-detail-page-architecture-2.png b/images/e-commerce-website-detail-page-architecture-2.png similarity index 100% rename from img/e-commerce-website-detail-page-architecture-2.png rename to images/e-commerce-website-detail-page-architecture-2.png diff --git a/img/es-cluster-0.png b/images/es-cluster-0.png similarity index 100% rename from img/es-cluster-0.png rename to images/es-cluster-0.png diff --git a/img/es-cluster.png b/images/es-cluster.png similarity index 100% rename from img/es-cluster.png rename to images/es-cluster.png diff --git a/img/es-index-type-mapping-document-field.png b/images/es-index-type-mapping-document-field.png similarity index 100% rename from img/es-index-type-mapping-document-field.png rename to images/es-index-type-mapping-document-field.png diff --git a/img/es-search-process.png b/images/es-search-process.png similarity index 100% rename from img/es-search-process.png rename to images/es-search-process.png diff --git a/img/es-write-detail.png b/images/es-write-detail.png similarity index 100% rename from img/es-write-detail.png rename to images/es-write-detail.png diff --git a/img/es-write.png b/images/es-write.png similarity index 100% rename from img/es-write.png rename to images/es-write.png diff --git a/img/favicon-16x16.png b/images/favicon-16x16.png similarity index 100% rename from img/favicon-16x16.png rename to images/favicon-16x16.png diff --git a/img/favicon-32x32.png b/images/favicon-32x32.png similarity index 100% rename from img/favicon-32x32.png rename to images/favicon-32x32.png diff --git a/img/get-up-and-study.png b/images/get-up-and-study.png similarity index 100% rename from img/get-up-and-study.png rename to images/get-up-and-study.png diff --git a/img/hash-slot.png b/images/hash-slot.png similarity index 100% rename from img/hash-slot.png rename to images/hash-slot.png diff --git a/img/hash.png b/images/hash.png similarity index 100% rename from img/hash.png rename to images/hash.png diff --git a/img/high-concurrency-system-design.png b/images/high-concurrency-system-design.png similarity index 100% rename from img/high-concurrency-system-design.png rename to images/high-concurrency-system-design.png diff --git a/images/hystrix-process.png b/images/hystrix-process.png new file mode 100644 index 0000000..dfb4ba7 Binary files /dev/null and b/images/hystrix-process.png differ diff --git a/images/hystrix-request-cache.png b/images/hystrix-request-cache.png new file mode 100644 index 0000000..e0f8fbf Binary files /dev/null and b/images/hystrix-request-cache.png differ diff --git a/img/hystrix-semphore-thread-pool.png b/images/hystrix-semphore-thread-pool.png similarity index 100% rename from img/hystrix-semphore-thread-pool.png rename to images/hystrix-semphore-thread-pool.png diff --git a/img/hystrix-semphore.png b/images/hystrix-semphore.png similarity index 100% rename from img/hystrix-semphore.png rename to images/hystrix-semphore.png diff --git a/img/hystrix-thread-pool-isolation.png b/images/hystrix-thread-pool-isolation.png similarity index 100% rename from img/hystrix-thread-pool-isolation.png rename to images/hystrix-thread-pool-isolation.png diff --git a/images/hystrix-thread-pool-queue.png b/images/hystrix-thread-pool-queue.png new file mode 100644 index 0000000..914e450 Binary files /dev/null and b/images/hystrix-thread-pool-queue.png differ diff --git a/img/icon.png b/images/icon.png similarity index 100% rename from img/icon.png rename to images/icon.png diff --git a/img/kafka-after.png b/images/kafka-after.png similarity index 100% rename from img/kafka-after.png rename to images/kafka-after.png diff --git a/img/kafka-before.png b/images/kafka-before.png similarity index 100% rename from img/kafka-before.png rename to images/kafka-before.png diff --git a/img/kafka-order-01.png b/images/kafka-order-01.png similarity index 100% rename from img/kafka-order-01.png rename to images/kafka-order-01.png diff --git a/img/kafka-order-02.png b/images/kafka-order-02.png similarity index 100% rename from img/kafka-order-02.png rename to images/kafka-order-02.png diff --git a/img/kafka-order-1.png b/images/kafka-order-1.png similarity index 100% rename from img/kafka-order-1.png rename to images/kafka-order-1.png diff --git a/img/kafka-order-2.png b/images/kafka-order-2.png similarity index 100% rename from img/kafka-order-2.png rename to images/kafka-order-2.png diff --git a/images/micro-deployment.png b/images/micro-deployment.png new file mode 100644 index 0000000..55c710e Binary files /dev/null and b/images/micro-deployment.png differ diff --git a/img/mq-1.png b/images/mq-1.png similarity index 100% rename from img/mq-1.png rename to images/mq-1.png diff --git a/images/mq-10.png b/images/mq-10.png new file mode 100644 index 0000000..d522b07 Binary files /dev/null and b/images/mq-10.png differ diff --git a/images/mq-11.png b/images/mq-11.png new file mode 100644 index 0000000..6d03589 Binary files /dev/null and b/images/mq-11.png differ diff --git a/img/mq-2.png b/images/mq-2.png similarity index 100% rename from img/mq-2.png rename to images/mq-2.png diff --git a/img/mq-3.png b/images/mq-3.png similarity index 100% rename from img/mq-3.png rename to images/mq-3.png diff --git a/img/mq-4.png b/images/mq-4.png similarity index 100% rename from img/mq-4.png rename to images/mq-4.png diff --git a/img/mq-5.png b/images/mq-5.png similarity index 100% rename from img/mq-5.png rename to images/mq-5.png diff --git a/img/mq-6.png b/images/mq-6.png similarity index 100% rename from img/mq-6.png rename to images/mq-6.png diff --git a/img/mq-7.png b/images/mq-7.png similarity index 100% rename from img/mq-7.png rename to images/mq-7.png diff --git a/img/mq-8.png b/images/mq-8.png similarity index 100% rename from img/mq-8.png rename to images/mq-8.png diff --git a/img/mysql-master-slave.png b/images/mysql-master-slave.png similarity index 100% rename from img/mysql-master-slave.png rename to images/mysql-master-slave.png diff --git a/img/rabbitmq-message-lose-solution.png b/images/rabbitmq-message-lose-solution.png similarity index 100% rename from img/rabbitmq-message-lose-solution.png rename to images/rabbitmq-message-lose-solution.png diff --git a/img/rabbitmq-message-lose.png b/images/rabbitmq-message-lose.png similarity index 100% rename from img/rabbitmq-message-lose.png rename to images/rabbitmq-message-lose.png diff --git a/img/rabbitmq-order-01.png b/images/rabbitmq-order-01.png similarity index 100% rename from img/rabbitmq-order-01.png rename to images/rabbitmq-order-01.png diff --git a/img/rabbitmq-order-02.png b/images/rabbitmq-order-02.png similarity index 100% rename from img/rabbitmq-order-02.png rename to images/rabbitmq-order-02.png diff --git a/img/rabbitmq-order-1.png b/images/rabbitmq-order-1.png similarity index 100% rename from img/rabbitmq-order-1.png rename to images/rabbitmq-order-1.png diff --git a/img/rabbitmq-order-2.png b/images/rabbitmq-order-2.png similarity index 100% rename from img/rabbitmq-order-2.png rename to images/rabbitmq-order-2.png diff --git a/img/redis-caching-avalanche-solution.png b/images/redis-caching-avalanche-solution.png similarity index 100% rename from img/redis-caching-avalanche-solution.png rename to images/redis-caching-avalanche-solution.png diff --git a/img/redis-caching-avalanche.png b/images/redis-caching-avalanche.png similarity index 100% rename from img/redis-caching-avalanche.png rename to images/redis-caching-avalanche.png diff --git a/img/redis-caching-penetration.png b/images/redis-caching-penetration.png similarity index 100% rename from img/redis-caching-penetration.png rename to images/redis-caching-penetration.png diff --git a/img/redis-cluster-split-brain.png b/images/redis-cluster-split-brain.png similarity index 100% rename from img/redis-cluster-split-brain.png rename to images/redis-cluster-split-brain.png diff --git a/img/redis-gossip.png b/images/redis-gossip.png similarity index 100% rename from img/redis-gossip.png rename to images/redis-gossip.png diff --git a/img/redis-junior-inconsistent.png b/images/redis-junior-inconsistent.png similarity index 100% rename from img/redis-junior-inconsistent.png rename to images/redis-junior-inconsistent.png diff --git a/img/redis-master-slave-replication-detail.png b/images/redis-master-slave-replication-detail.png similarity index 100% rename from img/redis-master-slave-replication-detail.png rename to images/redis-master-slave-replication-detail.png diff --git a/img/redis-master-slave-replication.png b/images/redis-master-slave-replication.png similarity index 100% rename from img/redis-master-slave-replication.png rename to images/redis-master-slave-replication.png diff --git a/img/redis-master-slave.png b/images/redis-master-slave.png similarity index 100% rename from img/redis-master-slave.png rename to images/redis-master-slave.png diff --git a/img/redis-redlock.png b/images/redis-redlock.png similarity index 100% rename from img/redis-redlock.png rename to images/redis-redlock.png diff --git a/images/redis-single-thread-model.png b/images/redis-single-thread-model.png new file mode 100644 index 0000000..cf105f4 Binary files /dev/null and b/images/redis-single-thread-model.png differ diff --git a/img/serialize-deserialize.png b/images/serialize-deserialize.png similarity index 100% rename from img/serialize-deserialize.png rename to images/serialize-deserialize.png diff --git a/img/service-invoke-road.png b/images/service-invoke-road.png similarity index 100% rename from img/service-invoke-road.png rename to images/service-invoke-road.png diff --git a/img/simple-distributed-system-oa.png b/images/simple-distributed-system-oa.png similarity index 100% rename from img/simple-distributed-system-oa.png rename to images/simple-distributed-system-oa.png diff --git a/images/sketch.png b/images/sketch.png new file mode 100644 index 0000000..ac8827d Binary files /dev/null and b/images/sketch.png differ diff --git a/img/where-is-my-offer.png b/images/where-is-my-offer.png similarity index 100% rename from img/where-is-my-offer.png rename to images/where-is-my-offer.png diff --git a/img/zookeeper-active-standby.png b/images/zookeeper-active-standby.png similarity index 100% rename from img/zookeeper-active-standby.png rename to images/zookeeper-active-standby.png diff --git a/img/zookeeper-centralized-storage.png b/images/zookeeper-centralized-storage.png similarity index 100% rename from img/zookeeper-centralized-storage.png rename to images/zookeeper-centralized-storage.png diff --git a/img/zookeeper-distributed-coordination.png b/images/zookeeper-distributed-coordination.png similarity index 100% rename from img/zookeeper-distributed-coordination.png rename to images/zookeeper-distributed-coordination.png diff --git a/img/zookeeper-distributed-lock-demo.png b/images/zookeeper-distributed-lock-demo.png similarity index 100% rename from img/zookeeper-distributed-lock-demo.png rename to images/zookeeper-distributed-lock-demo.png diff --git a/images/zookeeper-distributed-lock.png b/images/zookeeper-distributed-lock.png new file mode 100644 index 0000000..898361b Binary files /dev/null and b/images/zookeeper-distributed-lock.png differ diff --git a/img/zookeeper-meta-data-manage.png b/images/zookeeper-meta-data-manage.png similarity index 100% rename from img/zookeeper-meta-data-manage.png rename to images/zookeeper-meta-data-manage.png diff --git a/img/dubbo-keep-connection.png b/img/dubbo-keep-connection.png deleted file mode 100644 index 6f5a593..0000000 Binary files a/img/dubbo-keep-connection.png and /dev/null differ diff --git a/img/dubbo-not-keep-connection.png b/img/dubbo-not-keep-connection.png deleted file mode 100644 index a85a09f..0000000 Binary files a/img/dubbo-not-keep-connection.png and /dev/null differ diff --git a/img/mq-10.png b/img/mq-10.png deleted file mode 100644 index 506298d..0000000 Binary files a/img/mq-10.png and /dev/null differ diff --git a/img/mq-11.png b/img/mq-11.png deleted file mode 100644 index d4d834c..0000000 Binary files a/img/mq-11.png and /dev/null differ diff --git a/img/redis-single-thread-model.png b/img/redis-single-thread-model.png deleted file mode 100644 index 1dfd024..0000000 Binary files a/img/redis-single-thread-model.png and /dev/null differ diff --git a/img/zookeeper-distributed-lock.png b/img/zookeeper-distributed-lock.png deleted file mode 100644 index 7b93e51..0000000 Binary files a/img/zookeeper-distributed-lock.png and /dev/null differ diff --git a/index.html b/index.html index 6a6b0ab..7631e26 100644 --- a/index.html +++ b/index.html @@ -2,13 +2,13 @@ - Java 进阶面试 + 让我们同步进阶 - - + +
Welcome to Advanced-Java
@@ -22,14 +22,14 @@ coverpage: true, mergeNavbar: true, search: [ - '/' // => /README.md + '/' ], plugins: [ function (hook) { var footer = [ '
', '' ].join('') @@ -53,8 +53,9 @@ + - + \ No newline at end of file diff --git a/offer.md b/offer.md index dcc5a4d..20deffc 100644 --- a/offer.md +++ b/offer.md @@ -1,4 +1,4 @@ -[![where-is-my-offer](/img/where-is-my-offer.png)](https://doocs.github.io/advanced-java) +[![where-is-my-offer](/images/where-is-my-offer.png)](https://doocs.github.io/advanced-java/#/offer)

@@ -91,6 +91,35 @@ HR 燃烧我的卡路里 我要变成收割机 + + + _.._ ,------------. + ,' `. ( We want you! ) + / __) __` \ `-,----------' + ( (`-`(-') ) _.-' + /) \ = / ( + /' |--' . \ + ( ,---| `-.)__` + )( `-.,--' _`-. + '/,' ( Uu", + (_ , `/,-' ) + `.__, : `-'/ /`--' + | `--' | + ` `-._ / + \ ( + /\ . \. offer + / |` \ ,-\ + / \| .) / \ + ( ,'|\ ,' : + | \,`.`--"/ } + `,' \ |,' / + / "-._ `-/ | + "-. "-.,'| ; + / _/["---'""] + : / |"- ' + ' | / + ` | + ``` -[![get-up-and-study](/img/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