3
.gitattributes
vendored
@ -1,4 +1 @@
|
||||
* text=auto
|
||||
*.js linguist-language=java
|
||||
*.css linguist-language=java
|
||||
*.html linguist-language=java
|
33
.gitignore
vendored
@ -1,35 +1,2 @@
|
||||
/gradle/wrapper/gradle-wrapper.properties
|
||||
##----------Android----------
|
||||
# build
|
||||
*.apk
|
||||
*.ap_
|
||||
*.dex
|
||||
*.class
|
||||
bin/
|
||||
gen/
|
||||
build/
|
||||
|
||||
# gradle
|
||||
.gradle/
|
||||
gradle-app.setting
|
||||
!gradle-wrapper.jar
|
||||
build/
|
||||
|
||||
local.properties
|
||||
|
||||
##----------idea----------
|
||||
*.iml
|
||||
.idea/
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
##----------Other----------
|
||||
# osx
|
||||
*~
|
||||
.DS_Store
|
||||
gradle.properties
|
||||
|
||||
.vscode
|
431
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.
|
||||
|
25
README.md
@ -1,16 +1,20 @@
|
||||
# 互联网 Java 工程师进阶知识完全扫盲
|
||||
[![license](https://badgen.net/badge/license/Attribution-NonCommercial%204.0/green)](https://github.com/doocs/advanced-java/blob/master/LICENSE)
|
||||
# 互联网 Java 工程师进阶知识完全扫盲<sup>[©](https://github.com/yanglbme)</sup>
|
||||
[![license](https://badgen.net/github/license/doocs/advanced-java?color=green)](https://github.com/doocs/advanced-java/blob/master/LICENSE)
|
||||
[![original](https://badgen.net/badge/original/%E4%B8%AD%E5%8D%8E%E7%9F%B3%E6%9D%89/orange)](https://github.com/doocs/advanced-java)
|
||||
[![open-source-organization](https://badgen.net/badge/organization/join%20us/pink)](https://github.com/doocs/intro)
|
||||
[![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)
|
||||
[![doocs](https://badgen.net/badge/organization/join%20us/cyan)](https://doocs.github.io/#/?id=how-to-join)
|
||||
[![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)
|
||||
|
||||
本系列知识出自中华石杉,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)等领域知识。我对这部分知识做了一个[系统的整理](https://github.com/doocs/advanced-java/issues/1),方便学习查阅。配合《大型网站技术架构——李智慧》食用,[效果更佳](https://doocs.github.io/advanced-java/#/offer)。
|
||||
本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)等领域知识。我([@yanglbme](https://github.com/yanglbme))对这部分知识做了一个系统的整理,方便学习查阅。配合《[大型网站技术架构](https://github.com/doocs/technical-books#architecture)——李智慧》、《[Redis 设计与实现](https://github.com/doocs/technical-books#database)——[黄健宏](https://github.com/huangz1990)》、《[Redis 深度历险](https://github.com/doocs/technical-books#database)——钱文品》、《[亿级流量网站架构核心技术](https://github.com/doocs/technical-books#architecture)——张开涛》食用,[效果更佳](https://doocs.gitee.io/advanced-java/#/offer)。
|
||||
|
||||
学习之前,先来看看 [Issues 讨论区](https://github.com/doocs/advanced-java/issues/9#issue-394275038)的技术面试官是怎么说的吧。
|
||||
学习之前,先来看看 [Issues 讨论区](https://github.com/doocs/advanced-java/issues/9#issue-394275038)的技术面试官是怎么说的吧。本项目也欢迎各位开发者朋友到 [Issues 讨论区](https://github.com/doocs/advanced-java/issues)分享自己的一些想法和实践经验,参与或加入开源组织请看[这里](https://github.com/doocs/advanced-java/issues/61),你也访问 [GitHub Page](https://doocs.github.io) 详细了解一下 Doocs。
|
||||
|
||||
## 高并发架构
|
||||
### [消息队列](/docs/high-concurrency/mq-interview.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)
|
||||
@ -100,13 +104,16 @@
|
||||
### 熔断
|
||||
- 如何进行熔断?
|
||||
- 熔断框架都有哪些?具体实现原理知道吗?
|
||||
- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](/docs/high-availability/sentinel-vs-hystrix.md)
|
||||
|
||||
### 降级
|
||||
- 如何进行降级?
|
||||
|
||||
## 微服务架构
|
||||
- [微服务架构整个章节内容属额外新增,会陆续补充完善]()
|
||||
- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
|
||||
- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
|
||||
- [从单体式架构迁移到微服务架构](/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md)
|
||||
- [微服务的事件驱动数据管理](/docs/micro-services/event-driven-data-management-for-microservices.md)
|
||||
|
||||
### Spring Cloud 微服务架构
|
||||
- 什么是微服务?微服务之间是如何独立通讯的?
|
||||
@ -116,4 +123,4 @@
|
||||
- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
|
||||
- 你所知道的微服务技术栈都有哪些?
|
||||
- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
|
||||
- ......
|
||||
- ......
|
||||
|
@ -1,9 +0,0 @@
|
||||
![logo](images/icon.png)
|
||||
|
||||
# Java 进阶扫盲
|
||||
|
||||
> 高并发 分布式 高可用 微服务
|
||||
|
||||
[Doocs](https://github.com/doocs/intro)
|
||||
[GitHub](https://github.com/doocs/yanglbme/)
|
||||
[Get Started](#互联网-java-工程师进阶知识完全扫盲)
|
12
_navbar.md
@ -1,12 +0,0 @@
|
||||
* 分类
|
||||
* [高并发](/README#高并发架构)
|
||||
* [分布式](/README#分布式系统)
|
||||
* [高可用](/README#高可用架构)
|
||||
* [微服务](/README#微服务架构)
|
||||
|
||||
* 页面
|
||||
* [封面]()
|
||||
* [首页](README)
|
||||
* [Offer](offer)
|
||||
* [Doocs](https://github.com/doocs/intro)
|
||||
* [GitHub](https://github.com/yanglbme)
|
24
docs/distributed-system/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# 分布式系统
|
||||
## [面试连环炮](/docs/distributed-system/distributed-system-interview.md)
|
||||
## 系统拆分
|
||||
- [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md)
|
||||
|
||||
## 分布式服务框架
|
||||
- [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](/docs/distributed-system/dubbo-operating-principle.md)
|
||||
- [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](/docs/distributed-system/dubbo-serialization-protocol.md)
|
||||
- [Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](/docs/distributed-system/dubbo-load-balancing.md)
|
||||
- [Dubbo 的 spi 思想是什么?](/docs/distributed-system/dubbo-spi.md)
|
||||
- [如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](/docs/distributed-system/dubbo-service-management.md)
|
||||
- [分布式服务接口的幂等性如何设计(比如不能重复扣款)?](/docs/distributed-system/distributed-system-idempotency.md)
|
||||
- [分布式服务接口请求的顺序性如何保证?](/docs/distributed-system/distributed-system-request-sequence.md)
|
||||
- [如何自己设计一个类似 Dubbo 的 RPC 框架?](/docs/distributed-system/dubbo-rpc-design.md)
|
||||
|
||||
## 分布式锁
|
||||
- [Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md)
|
||||
- [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md)
|
||||
|
||||
## 分布式事务
|
||||
- [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md)
|
||||
|
||||
## 分布式会话
|
||||
- [集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.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,从而拿到锁。
|
||||
|
||||
@ -55,6 +56,8 @@ end
|
||||
|
||||
![redis-redlock](/images/redis-redlock.png)
|
||||
|
||||
[Redis 官方](https://redis.io/)给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看:https://redis.io/topics/distlock 。
|
||||
|
||||
### zk 分布式锁
|
||||
|
||||
zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能**注册个监听器**监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。
|
||||
|
@ -4,12 +4,12 @@
|
||||
## 面试官心理分析
|
||||
面试官问了你一堆 dubbo 是怎么玩儿的,你会玩儿 dubbo 就可以把单块系统弄成分布式系统,然后分布式之后接踵而来的就是一堆问题,最大的问题就是**分布式事务**、**接口幂等性**、**分布式锁**,还有最后一个就是**分布式 session**。
|
||||
|
||||
当然了,分布式系统中的问题何止这么一点,非常之多,复杂度很高,但是这里就是说下常见的几个,也是面试的时候常问的几个。
|
||||
当然了,分布式系统中的问题何止这么一点,非常之多,复杂度很高,这里只是说一下常见的几个问题,也是面试的时候常问的几个。
|
||||
|
||||
## 面试题剖析
|
||||
session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存在,然后每次发请求过来都带上一个特殊的 `jsessionid cookie`,就根据这个东西,在服务端可以维护一个对应的 session 域,里面可以放点数据。
|
||||
|
||||
一般只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。
|
||||
一般的话只要你没关掉浏览器,cookie 还在,那么对应的那个 session 就在,但是如果 cookie 没了,session 也就没了。常见于什么购物车之类的东西,还有登录状态保存之类的。
|
||||
|
||||
这个不多说了,懂 Java 的都该知道这个。
|
||||
|
||||
@ -18,7 +18,6 @@ session 是啥?浏览器有个 cookie,在一段时间内这个 cookie 都存
|
||||
其实方法很多,但是常见常用的是以下几种:
|
||||
|
||||
### 完全不用 session
|
||||
|
||||
使用 JWT Token 储存用户身份,然后再从数据库或者 cache 中获取其他的信息。这样无论请求分配到哪个服务器都无所谓。
|
||||
|
||||
### tomcat + redis
|
||||
@ -49,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
|
||||
@ -107,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;
|
||||
@ -131,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 来实现。
|
||||
实现分布式的会话有很多种方式,我说的只不过是比较常见的几种方式,tomcat + redis 早期比较常用,但是会重耦合到 tomcat 中;近些年,通过 spring session 来实现。
|
@ -6,7 +6,7 @@
|
||||
|
||||
一个分布式系统中的某个接口,该如何保证幂等性?这个事儿其实是你做分布式系统的时候必须要考虑的一个生产环境的技术问题。啥意思呢?
|
||||
|
||||
你看,假如你有个服务提供一个接口,结果这服务部署在了 5 台机器上,接着有个接口就是**付款接口**。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单**不小心发起了两次支付请求**,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次。
|
||||
你看,假如你有个服务提供一些接口供外部调用,这个服务部署在了 5 台机器上,接着有个接口就是**付款接口**。然后人家用户在前端上操作的时候,不知道为啥,总之就是一个订单**不小心发起了两次支付请求**,然后这俩请求分散在了这个服务部署的不同的机器上,好了,结果一个订单扣款扣两次。
|
||||
|
||||
或者是订单系统调用支付系统进行支付,结果不小心因为**网络超时**了,然后订单系统走了前面我们看到的那个重试机制,咔嚓给你重试了一把,好,支付系统收到一个支付请求两次,而且因为负载均衡算法落在了不同的机器上,尴尬了。。。
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
![simple-distributed-system-oa](/images/simple-distributed-system-oa.png)
|
||||
|
||||
> 这两年开始兴起和流行 Spring Cloud,刚流行,还没开始普及,目前普及的是 dubbo,因此这里也主要讲 dubbo。
|
||||
> 近几年开始兴起和流行 Spring Cloud,刚流行,还没开始普及,目前普及的是 dubbo,因此这里也主要讲 dubbo。
|
||||
|
||||
面试官可能会问你以下问题。
|
||||
### 为什么要进行系统拆分?
|
||||
|
@ -11,10 +11,10 @@
|
||||
## 面试题剖析
|
||||
首先,一般来说,个人建议是,你们从业务逻辑上设计的这个系统最好是不需要这种顺序性的保证,因为一旦引入顺序性保障,比如使用**分布式锁**,会**导致系统复杂度上升**,而且会带来**效率低下**,热点数据压力过大等问题。
|
||||
|
||||
下面我给个我们用过的方案吧,简单来说,首先你得用 dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去,强制排队,这样来确保他们的顺序性。
|
||||
下面我给个我们用过的方案吧,简单来说,首先你得用 dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上,因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去,强制排队,这样来确保他们的顺序性。
|
||||
|
||||
![distributed-system-request-sequence](/images/distributed-system-request-sequence.png)
|
||||
|
||||
但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成**热点**怎么办?解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已,所以,还是建议什么呢?
|
||||
|
||||
最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是什么,避免这种问题的产生。
|
||||
最好是比如说刚才那种,一个订单的插入和删除操作,能不能合并成一个操作,就是一个删除,或者是其它什么,避免这种问题的产生。
|
@ -29,7 +29,7 @@
|
||||
![distributed-transacion-XA](/images/distributed-transaction-XA.png)
|
||||
|
||||
### TCC 方案
|
||||
TCC 的全称是:Try、Confirm、Cancel。
|
||||
TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||
|
||||
- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。
|
||||
- Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。
|
||||
@ -41,7 +41,7 @@ TCC 的全称是:Try、Confirm、Cancel。
|
||||
|
||||
而且最好是你的各个业务执行的时间都比较短。
|
||||
|
||||
但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码很难维护。
|
||||
但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。
|
||||
|
||||
![distributed-transacion-TCC](/images/distributed-transaction-TCC.png)
|
||||
|
||||
@ -57,7 +57,7 @@ TCC 的全称是:Try、Confirm、Cancel。
|
||||
5. 如果 B 系统处理失败了,那么就不会更新消息表状态,那么此时 A 系统会定时扫描自己的消息表,如果有未处理的消息,会再次发送到 MQ 中去,让 B 再次处理;
|
||||
6. 这个方案保证了最终一致性,哪怕 B 事务失败了,但是 A 会不断重发消息,直到 B 那边成功为止。
|
||||
|
||||
这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,会导致如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。
|
||||
这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。
|
||||
|
||||
![distributed-transaction-local-message-table](/images/distributed-transaction-local-message-table.png)
|
||||
|
||||
|
@ -13,16 +13,16 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略
|
||||
## 面试题剖析
|
||||
### dubbo 负载均衡策略
|
||||
#### random loadbalance
|
||||
默认情况下,dubbo 是 random load balance **随机**调用实现负载均衡,可以对 provider 不同实例**设置不同的权重**,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。
|
||||
默认情况下,dubbo 是 random load balance ,即**随机**调用实现负载均衡,可以对 provider 不同实例**设置不同的权重**,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。
|
||||
|
||||
#### roundrobin loadbalance
|
||||
这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。
|
||||
|
||||
举个栗子。
|
||||
|
||||
跟运维同学申请机器,有的时候,我们运气好,正好公司资源比较充足,刚刚有一批热气腾腾、刚刚做好的一批虚拟机新鲜出炉,配置都比较高:8核+16G 机器,申请到 2 台。过了一段时间,我们感觉 2 台机器有点不太够,我就去找运维同学说,“哥儿们,你能不能再给我一台机器”,但是这时只剩下一台 4核+8G 的机器。我要还是得要。
|
||||
跟运维同学申请机器,有的时候,我们运气好,正好公司资源比较充足,刚刚有一批热气腾腾、刚刚做好的虚拟机新鲜出炉,配置都比较高:8 核 + 16G 机器,申请到 2 台。过了一段时间,我们感觉 2 台机器有点不太够,我就去找运维同学说,“哥儿们,你能不能再给我一台机器”,但是这时只剩下一台 4 核 + 8G 的机器。我要还是得要。
|
||||
|
||||
这个时候,可以给两台 8核16G 的机器设置权重 4,给剩余 1 台 4核8G 的机器设置权重 2。
|
||||
这个时候,可以给两台 8 核 16G 的机器设置权重 4,给剩余 1 台 4 核 8G 的机器设置权重 2。
|
||||
|
||||
#### leastactive loadbalance
|
||||
这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给**不活跃的性能差的机器更少的请求**。
|
||||
@ -32,22 +32,54 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略
|
||||
|
||||
### dubbo 集群容错策略
|
||||
#### failover cluster 模式
|
||||
失败自动切换,自动重试其他机器,默认就是这个,常见于读操作。(失败重试其它机器)
|
||||
失败自动切换,自动重试其他机器,**默认**就是这个,常见于读操作。(失败重试其它机器)
|
||||
|
||||
#### failfast cluster模式
|
||||
一次调用失败就立即失败,常见于写操作。(调用失败就立即失败)
|
||||
可以通过以下几种方式配置重试次数:
|
||||
|
||||
```xml
|
||||
<dubbo:service retries="2" />
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```xml
|
||||
<dubbo:reference retries="2" />
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```xml
|
||||
<dubbo:reference>
|
||||
<dubbo:method name="findFoo" retries="2" />
|
||||
</dubbo:reference>
|
||||
```
|
||||
|
||||
#### failfast cluster 模式
|
||||
一次调用失败就立即失败,常见于非幂等性的写操作,比如新增一条记录(调用失败就立即失败)
|
||||
|
||||
#### failsafe cluster 模式
|
||||
出现异常时忽略掉,常用于不重要的接口调用,比如记录日志。
|
||||
|
||||
配置示例如下:
|
||||
|
||||
```xml
|
||||
<dubbo:service cluster="failsafe" />
|
||||
```
|
||||
|
||||
或者
|
||||
|
||||
```xml
|
||||
<dubbo:reference cluster="failsafe" />
|
||||
```
|
||||
|
||||
#### failback cluster 模式
|
||||
失败了后台自动记录请求,然后定时重发,比较适合于写消息队列这种。
|
||||
|
||||
#### forking cluster 模式
|
||||
**并行调用**多个 provider,只要一个成功就立即返回。
|
||||
**并行调用**多个 provider,只要一个成功就立即返回。常用于实时性要求比较高的读操作,但是会浪费更多的服务资源,可通过 `forks="2"` 来设置最大并行数。
|
||||
|
||||
#### broadcacst cluster
|
||||
逐个调用所有的 provider。
|
||||
逐个调用所有的 provider。任何一个 provider 出错则报错(从`2.1.0` 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。
|
||||
|
||||
### dubbo动态代理策略
|
||||
默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。
|
||||
默认使用 javassist 动态字节码生成,创建代理类。但是可以通过 spi 扩展机制配置自己的动态代理策略。
|
@ -2,13 +2,13 @@
|
||||
说一下的 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 现在重启开源社区维护了,捐献给了 apache,未来应该也还是有一定市场和地位的。
|
||||
|
||||
既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。
|
||||
既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc 分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。
|
||||
|
||||
## 面试题剖析
|
||||
### dubbo 工作原理
|
||||
@ -32,4 +32,4 @@ MQ、ES、Redis、Dubbo,上来先问你一些思考的问题,原理(kafka
|
||||
![dubbo-operating-principle](/images/dubbo-operating-principle.png)
|
||||
|
||||
### 注册中心挂了可以继续通信吗?
|
||||
可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息**拉取到本地缓存**,所以注册中心挂了可以继续通信。
|
||||
可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息**拉取到本地缓存**,所以注册中心挂了可以继续通信。
|
@ -12,7 +12,7 @@
|
||||
所以我给大家一个建议,遇到这类问题,起码从你了解的类似框架的原理入手,自己说说参照 dubbo 的原理,你来设计一下,举个例子,dubbo 不是有那么多分层么?而且每个分层是干啥的,你大概是不是知道?那就按照这个思路大致说一下吧,起码你不能懵逼,要比那些上来就懵,啥也说不出来的人要好一些。
|
||||
|
||||
举个栗子,我给大家说个最简单的回答思路:
|
||||
- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信心,可以用 zookeeper 来做,对吧。
|
||||
- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。
|
||||
- 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。
|
||||
- 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。
|
||||
- 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。
|
||||
|
@ -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` 倍;**第二**,它的数据压缩效果好,就是说它序列化后的数据量体积小。因为体积小,传输起来带宽和速度上会有优化。
|
@ -8,7 +8,7 @@
|
||||
|
||||
**失败重试**,分布式系统中网络请求如此频繁,要是因为网络问题不小心失败了一次,是不是要重试?
|
||||
|
||||
**超时重试**,同上,如果不小心网络慢一点,超时了,如何重试?
|
||||
**超时重试**,跟上面一样,如果不小心网络慢一点,超时了,如何重试?
|
||||
|
||||
## 面试题剖析
|
||||
### 服务治理
|
||||
@ -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
|
||||
@ -89,14 +84,13 @@ public class HelloServiceImpl implements HelloService {
|
||||
我们调用接口失败的时候,可以通过 `mock` 统一返回 null。
|
||||
|
||||
mock 的值也可以修改为 true,然后再跟接口同一个路径下实现一个 Mock 类,命名规则是 “接口名称+`Mock`” 后缀。然后在 Mock 类里实现自己的降级逻辑。
|
||||
|
||||
```java
|
||||
public class HelloServiceMock implements HelloService {
|
||||
|
||||
public void sayHello() {
|
||||
// 降级逻辑
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 失败重试和超时重试
|
||||
|
@ -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 思想就用上了。
|
||||
|
||||
@ -77,8 +77,9 @@ hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
|
||||
|
||||
然后自己搞一个 `dubbo provider` 工程,在这个工程里面依赖你自己搞的那个 jar,然后在 spring 配置文件里给个配置:
|
||||
|
||||
```xml
|
||||
<dubbo:protocol name=”my” port=”20000” />
|
||||
|
||||
```
|
||||
provider 启动的时候,就会加载到我们 jar 包里的`my=com.bingo.MyProtocol` 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。
|
||||
|
||||
![dubbo-spi](/images/dubbo-spi.png)
|
||||
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.0 KiB |
@ -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 就可以了。
|
||||
所以 dubbo 说白了,是一种 rpc 框架,就是说本地就是进行接口调用,但是 dubbo 会代理这个调用请求,跟远程机器网络通信,给你处理掉负载均衡、服务实例上下线自动感知、超时重试等等乱七八糟的问题。那你就不用自己做了,用 dubbo 就可以了。
|
@ -17,7 +17,7 @@ zookeeper 都有哪些使用场景?
|
||||
- HA高可用性
|
||||
|
||||
### 分布式协调
|
||||
这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上**对某个节点的值注册个监听器**,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 立马就可以收到通知,完美解决。
|
||||
这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上**对某个节点的值注册个监听器**,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知,完美解决。
|
||||
|
||||
![zookeeper-distributed-coordination](/images/zookeeper-distributed-coordination.png)
|
||||
|
||||
|
8
docs/extra-page/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# 项目额外页面
|
||||
## Offer 与进阶
|
||||
- [我的 Offer 在哪里?](https://doocs.gitee.io/advanced-java/#/docs/extra-page/offer)
|
||||
- [让我们同步进阶!](https://doocs.gitee.io/advanced-java/#/docs/extra-page/advanced)
|
||||
|
||||
## 项目 Page 页
|
||||
- [GitHub Page](https://doocs.github.io/advanced-java/#/)
|
||||
- [Gitee Page](https://doocs.gitee.io/advanced-java/#/)
|
72
docs/extra-page/advanced.md
Normal file
@ -0,0 +1,72 @@
|
||||
<p align="center"><iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=1334849028&auto=1&height=66"></iframe></p>
|
||||
<p align="center">本单曲受版权保护,可<a href="https://music.163.com/#/mv?id=10859500">点击观看 MV</a>。</p>
|
||||
|
||||
|
||||
> 受伤的得到疗愈,挣扎的得到出口<br>
|
||||
*Let those who hurt heal, let those who struggle find hope*.
|
||||
|
||||
```
|
||||
告别的时刻已到了
|
||||
随身的行囊 装些快乐
|
||||
|
||||
前方被乌云笼罩了
|
||||
心中的拉扯 困兽在低吼
|
||||
|
||||
隐隐约约的沉默
|
||||
透露一丝丝苦涩
|
||||
愿你不再被伤透
|
||||
别再带着泪入睡
|
||||
|
||||
曲曲折折的世界
|
||||
答案不一定绝对
|
||||
伤口因为爱壮烈
|
||||
让我们同步进阶
|
||||
|
||||
重生的力量来自真我
|
||||
战胜可敬的对手 yeah~
|
||||
|
||||
坚持信念是我的所有
|
||||
抵达心中的辽阔 yeah~
|
||||
|
||||
有失去的 该进阶的
|
||||
有遗憾的 该进阶的
|
||||
放不下的 该进阶的 yeah~
|
||||
|
||||
圣所的光芒
|
||||
指引方向 oh~
|
||||
|
||||
跨越了浩劫
|
||||
曙光渐亮 oh~
|
||||
|
||||
希望因挣扎破灭了
|
||||
再别无所求 于是自由
|
||||
|
||||
逆着风孤独的英雄
|
||||
也渴望停泊 可说不出口
|
||||
|
||||
隐隐约约的沉默
|
||||
透露一丝丝苦涩
|
||||
愿你不再被伤透
|
||||
别再带着泪入睡
|
||||
|
||||
曲曲折折的世界
|
||||
答案不一定绝对
|
||||
伤口因为爱壮烈
|
||||
让我们同步进阶
|
||||
|
||||
重生的力量来自真我
|
||||
战胜可敬的对手 yeah~
|
||||
|
||||
坚持信念是我的所有
|
||||
抵达心中的辽阔 yeah~
|
||||
|
||||
有失去的 该进阶的
|
||||
有遗憾的 该进阶的
|
||||
放不下的 该进阶的 yeah~
|
||||
|
||||
圣所的光芒
|
||||
指引方向 oh~
|
||||
|
||||
跨越了浩劫
|
||||
曙光渐亮 oh~
|
||||
```
|
9
docs/extra-page/cover.md
Normal file
@ -0,0 +1,9 @@
|
||||
[![logo](images/icon.png)](https://github.com/doocs/advanced-java)
|
||||
|
||||
# 互联网 Java 工程师进阶知识完全扫盲
|
||||
|
||||
> 本系列知识由 Doocs 开源社区总结发布,内容涵盖高并发、分布式、高可用、微服务等
|
||||
|
||||
[Organization](https://github.com/doocs/doocs.github.io)
|
||||
[Author](https://github.com/yanglbme)
|
||||
[Get Started](#互联网-java-工程师进阶知识完全扫盲©)
|
BIN
docs/extra-page/images/get-up-and-study.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
docs/extra-page/images/where-is-my-offer.png
Normal file
After Width: | Height: | Size: 42 KiB |
15
docs/from-readers/README.md
Normal file
@ -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.
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
|
||||
<a href="https://github.com/doocs/advanced-java/graphs/contributors"><img src="https://opencollective.com/advanced-java/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
52
docs/from-readers/doocs-advanced-java-attention.md
Normal file
@ -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 将不予合并。
|
BIN
docs/from-readers/images/advanced-java-doocs-shishan.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
docs/from-readers/images/icon.png
Normal file
After Width: | Height: | Size: 74 KiB |
44
docs/from-readers/rights-defending-movement.md
Normal file
@ -0,0 +1,44 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/doocs/advanced-java"><img src="./images/advanced-java-doocs-shishan.png" alt="维权行动"></a>
|
||||
</p>
|
||||
|
||||
## 声明
|
||||
读者朋友们,你们好。[advanced-java](https://github.com/doocs/advanced-java) 项目自创建以来,一直收到很多读者的反馈,也在不断改进、完善内容,只希望可以用心做得更好。然而,网上抄袭、侵权的现象普遍存在,我想,不能任由这种恶劣行为肆虐。
|
||||
|
||||
因此,在此说明,除了 [doocs/advanced-java](https://github.com/doocs/advanced-java)、“**石杉的架构笔记**”,网上其它平台上如若出现了与本项目内容雷同甚至完全一致的文章,**不注明出处、甚至打着原创的标签忽悠读者**的,欢迎举报,也欢迎在此提供侵权名单,曝光抄袭者,谢谢。
|
||||
|
||||
希望各位朋友都注重**维护他人知识产权,尊重他人劳动成果**,我们共同构建一个健康的知识分享生态圈。
|
||||
|
||||
|
||||
## 抄袭名单列表
|
||||
注:若以下某些链接失效,说明内容已被抄袭者删除,或者已被所在内容平台进行违规清除。
|
||||
|
||||
### 博客
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
| 1 | [如何保证缓存与数据库的双写一致性](https://blog.51cto.com/14230003/2363051) | Java_老男孩-51CTO |
|
||||
| 2 | [了解什么是 redis 的雪崩和穿透?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?](https://blog.csdn.net/chang384915878/article/details/86756536) | 你是我的海啸-CSDN |
|
||||
| 3 | [深入 Hystrix 线程池隔离与接口限流](https://blog.csdn.net/u014513171/article/details/93461724) | 塔寨村村主任-CSDN |
|
||||
| 4 | [MySQL 面试题](https://jsbintask.cn/2019/02/17/interview/interview-high-concurrency-design/) | jsbintask |
|
||||
| 5 | [消息队列面试题](https://blog.51cto.com/13904503/2351522) | Java邵先生 |
|
||||
| 6 | [高并发架构消息队列面试题解析](https://www.cnblogs.com/yuxiang1/p/10542569.html) | 手留余香-博客园 |
|
||||
| 7 | [消息中间件面试题:消息中间件的高可用](https://www.jianshu.com/p/92862edc7c51) | jsbintask-简书 |
|
||||
| 8 | [深入 Hystrix 执行时内部原理](https://www.jianshu.com/p/1a14401e219f) | kevin0016-简书 |
|
||||
|
||||
|
||||
### 公众号
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
|
||||
### 头条号
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
|
||||
### 掘金
|
||||
| # | 文章 | 抄袭者 |
|
||||
|---|---|---|
|
||||
|
||||
### 知乎
|
||||
| # | 文章 | 抄袭者 | 备注 |
|
||||
|---|---|---|---|
|
||||
| 1 | [Java消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java高级架构解析 | 严重抄袭 |
|
26
docs/high-availability/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# 高可用架构
|
||||
- [Hystrix 介绍](/docs/high-availability/hystrix-introduction.md)
|
||||
- [电商网站详情页系统架构](/docs/high-availability/e-commerce-website-detail-page-architecture.md)
|
||||
- [Hystrix 线程池技术实现资源隔离](/docs/high-availability/hystrix-thread-pool-isolation.md)
|
||||
- [Hystrix 信号量机制实现资源隔离](/docs/high-availability/hystrix-semphore-isolation.md)
|
||||
- [Hystrix 隔离策略细粒度控制](/docs/high-availability/hystrix-execution-isolation.md)
|
||||
- [深入 Hystrix 执行时内部原理](/docs/high-availability/hystrix-process.md)
|
||||
- [基于 request cache 请求缓存技术优化批量商品数据查询接口](/docs/high-availability/hystrix-request-cache.md)
|
||||
- [基于本地缓存的 fallback 降级机制](/docs/high-availability/hystrix-fallback.md)
|
||||
- [深入 Hystrix 断路器执行原理](/docs/high-availability/hystrix-circuit-breaker.md)
|
||||
- [深入 Hystrix 线程池隔离与接口限流](/docs/high-availability/hystrix-thread-pool-current-limiting.md)
|
||||
- [基于 timeout 机制为服务接口调用超时提供安全保护](/docs/high-availability/hystrix-timeout.md)
|
||||
|
||||
## 高可用系统
|
||||
- 如何设计一个高可用系统?
|
||||
|
||||
## 限流
|
||||
- 如何限流?在工作中是怎么做的?说一下具体的实现?
|
||||
|
||||
## 熔断
|
||||
- 如何进行熔断?
|
||||
- 熔断框架都有哪些?具体实现原理知道吗?
|
||||
- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](/docs/high-availability/sentinel-vs-hystrix.md)
|
||||
|
||||
## 降级
|
||||
- 如何进行降级?
|
@ -42,6 +42,7 @@ public class BrandCache {
|
||||
|
||||
/**
|
||||
* brandId 获取 brandName
|
||||
*
|
||||
* @param brandId 品牌id
|
||||
* @return 品牌名
|
||||
*/
|
||||
|
@ -40,9 +40,9 @@ HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand
|
||||
- toObservable():返回一个 Observable 对象,如果我们订阅这个对象,就会执行 command 并且获取返回结果。
|
||||
|
||||
```java
|
||||
K value = hystrixCommand.execute();
|
||||
Future<K> fValue = hystrixCommand.queue();
|
||||
Observable<K> oValue = hystrixObservableCommand.observe();
|
||||
K value = hystrixCommand.execute();
|
||||
Future<K> fValue = hystrixCommand.queue();
|
||||
Observable<K> oValue = hystrixObservableCommand.observe();
|
||||
Observable<K> toOValue = hystrixObservableCommand.toObservable();
|
||||
```
|
||||
|
||||
@ -106,7 +106,8 @@ observable.subscribe(new Observer<ProductInfo>() {
|
||||
|
||||
/**
|
||||
* 获取完一条数据,就回调一次这个方法
|
||||
* @param productInfo
|
||||
*
|
||||
* @param productInfo 商品信息
|
||||
*/
|
||||
@Override
|
||||
public void onNext(ProductInfo productInfo) {
|
||||
|
@ -49,7 +49,7 @@ Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果
|
||||
|
||||
我们可以用 Hystrix semaphore 技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量。
|
||||
|
||||
semaphore 技术可以用来限流和削峰,但是不能用来对调研延迟的服务进行 timeout 和隔离。
|
||||
semaphore 技术可以用来限流和削峰,但是不能用来对调用延迟的服务进行 timeout 和隔离。
|
||||
|
||||
`execution.isolation.strategy` 设置为 `SEMAPHORE`,那么 Hystrix 就会用 semaphore 机制来替代线程池机制,来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候,底层的网络调用延迟很严重,那么是无法 timeout 的,只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后,就会立即开启限流。
|
||||
|
||||
@ -58,8 +58,8 @@ semaphore 技术可以用来限流和削峰,但是不能用来对调研延迟
|
||||
|
||||
在 command 内部,写死代码,做一个 sleep,比如 sleep 3s。
|
||||
|
||||
- withCoreSize:设置线程池大小
|
||||
- withMaxQueueSize:设置等待队列大小
|
||||
- withCoreSize:设置线程池大小。
|
||||
- withMaxQueueSize:设置等待队列大小。
|
||||
- withQueueSizeRejectionThreshold:这个与 withMaxQueueSize 配合使用,等待队列的大小,取得是这两个参数的较小值。
|
||||
|
||||
如果只设置了线程池大小,另外两个 queue 相关参数没有设置的话,等待队列是处于关闭的状态。
|
||||
@ -160,4 +160,4 @@ ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specificat
|
||||
{"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}
|
||||
// 后面都是一些正常的商品信息,就不贴出来了
|
||||
//...
|
||||
```
|
||||
```
|
||||
|
@ -1,5 +1,4 @@
|
||||
## 基于 Hystrix 线程池技术实现资源隔离
|
||||
|
||||
上一讲提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。
|
||||
|
||||
资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是线程池内就 10 个线程,最多就只会用这 10 个线程去执行,不会说,对商品服务的请求,因为接口调用延时,将 tomcat 内部所有的线程资源全部耗尽。
|
||||
|
BIN
docs/high-availability/images/BRP.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
docs/high-availability/images/Homogenizer-mode.jpg
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
docs/high-availability/images/Sentinel-Dashboard.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
docs/high-availability/images/Sentinel-VS-Hystrix.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
docs/high-availability/images/Slow-Start-Preheating-Mode.jpg
Normal file
After Width: | Height: | Size: 63 KiB |
102
docs/high-availability/sentinel-vs-hystrix.md
Normal file
@ -0,0 +1,102 @@
|
||||
# 如何做技术选型?Sentinel 还是 Hystrix?
|
||||
Sentinel 是阿里中间件团队研发的面向分布式服务架构的轻量级高可用流量控制组件,于 2018 年 7 月正式开源。Sentinel 主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户提升服务的稳定性。大家可能会问:Sentinel 和之前经常用到的熔断降级库 Netflix Hystrix 有什么异同呢?本文将从资源模型和执行模型、隔离设计、熔断降级、实时指标统计设计等角度将 Sentinel 和 Hystrix 进行对比,希望在面临技术选型的时候,对各位开发者能有所帮助。
|
||||
|
||||
Sentinel 项目地址:https://github.com/alibaba/Sentinel
|
||||
|
||||
## 总体说明
|
||||
先来看一下 Hystrix 的官方介绍:
|
||||
|
||||
> Hystrix is a library that helps you control the interactions between these distributed services by adding latency tolerance and fault tolerance logic. Hystrix does this by isolating points of access between the services, stopping cascading failures across them, and providing fallback options, all of which improve your system’s overall resiliency.
|
||||
|
||||
可以看到 Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。
|
||||
|
||||
而 Sentinel 的侧重点在于:
|
||||
|
||||
- 多样化的流量控制
|
||||
- 熔断降级
|
||||
- 系统负载保护
|
||||
- 实时监控和控制台
|
||||
|
||||
两者解决的问题还是有比较大的不同的,下面我们来具体对比一下。
|
||||
|
||||
## 共同特性
|
||||
### 1. 资源模型和执行模型上的对比
|
||||
Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象 `HystrixCommand` 或 `HystrixObservableCommand`,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 `commandKey` 和 `groupKey`(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。
|
||||
|
||||
**注**:关于 Hystrix 的详细介绍及代码演示,可以参考本项目[高可用架构](/docs/high-availability/README.md)-Hystrix 部分的详细说明。
|
||||
|
||||
Sentinel 的设计则更为简单。相比 Hystrix Command 强依赖隔离规则,Sentinel 的资源定义与规则配置的耦合度更低。Hystrix 的 Command 强依赖于隔离规则配置的原因是隔离规则会直接影响 Command 的执行。在执行的时候 Hystrix 会解析 Command 的隔离规则来创建 RxJava Scheduler 并在其上调度执行,若是线程池模式则 Scheduler 底层的线程池为配置的线程池,若是信号量模式则简单包装成当前线程执行的 Scheduler。
|
||||
|
||||
而 Sentinel 则不一样,开发的时候只需要考虑这个方法/代码是否需要保护,置于用什么来保护,可以任何时候动态实时的区修改。
|
||||
|
||||
从 `0.1.1` 版本开始,Sentinel 还支持基于注解的资源定义方式,可以通过注解参数指定异常处理函数和 fallback 函数。Sentinel 提供多样化的规则配置方式。除了直接通过 `loadRules` API 将规则注册到内存态之外,用户还可以注册各种外部数据源来提供动态的规则。用户可以根据系统当前的实时情况去动态地变更规则配置,数据源会将变更推送至 Sentinel 并即时生效。
|
||||
|
||||
### 2. 隔离设计上的对比
|
||||
隔离是 Hystrix 的核心功能之一。Hystrix 提供两种隔离策略:线程池隔离 `Bulkhead Pattern` 和信号量隔离,其中最推荐也是最常用的是**线程池隔离**。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。
|
||||
|
||||
但是,实际情况下,线程池隔离并没有带来非常多的好处。最直接的影响,就是会让机器资源碎片化。考虑这样一个常见的场景,在 Tomcat 之类的 Servlet 容器使用 Hystrix,本身 Tomcat 自身的线程数目就非常多了(可能到几十或一百多),如果加上 Hystrix 为各个资源创建的线程池,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。另外,线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。
|
||||
|
||||
Hystrix 的信号量隔离限制对某个资源调用的并发数。这样的隔离非常轻量级,仅限制对某个资源调用的并发数,而不是显式地去创建线程池,所以 overhead 比较小,但是效果不错。但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。
|
||||
|
||||
Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。
|
||||
|
||||
### 3. 熔断降级的对比
|
||||
Sentinel 和 Hystrix 的熔断降级功能本质上都是基于熔断器模式 `Circuit Breaker Pattern`。Sentinel 与 Hystrix 都支持基于失败比率(异常比率)的熔断降级,在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断,此时所有对该资源的调用都会被 block,直到过了指定的时间窗口后才启发性地恢复。上面提到过,Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。
|
||||
|
||||
### 4. 实时指标统计实现的对比
|
||||
Hystrix 和 Sentinel 的实时指标数据统计实现都是基于滑动窗口的。Hystrix 1.5 之前的版本是通过环形数组实现的滑动窗口,通过锁配合 CAS 的操作对每个桶的统计信息进行更新。Hystrix 1.5 开始对实时指标统计的实现进行了重构,将指标统计数据结构抽象成了响应式流(reactive stream)的形式,方便消费者去利用指标信息。同时底层改造成了基于 RxJava 的事件驱动模式,在服务调用成功/失败/超时的时候发布相应的事件,通过一系列的变换和聚合最终得到实时的指标统计数据流,可以被熔断器或 Dashboard 消费。
|
||||
|
||||
Sentinel 目前抽象出了 Metric 指标统计接口,底层可以有不同的实现,目前默认的实现是基于 LeapArray 的滑动窗口,后续根据需要可能会引入 reactive stream 等实现。
|
||||
|
||||
## Sentinel 特性
|
||||
除了之前提到的两者的共同特性之外,Sentinel 还提供以下的特色功能:
|
||||
|
||||
### 1. 轻量级、高性能
|
||||
Sentinel 作为一个功能完备的高可用流量管控组件,其核心 sentinel-core 没有任何多余依赖,打包后只有不到 200KB,非常轻量级。开发者可以放心地引入 sentinel-core 而不需担心依赖问题。同时,Sentinel 提供了多种扩展点,用户可以很方便地根据需求去进行扩展,并且无缝地切合到 Sentinel 中。
|
||||
|
||||
引入 Sentinel 带来的性能损耗非常小。只有在业务单机量级超过 25W QPS 的时候才会有一些显著的影响(5% - 10% 左右),单机 QPS 不太大的时候损耗几乎可以忽略不计。
|
||||
|
||||
### 2. 流量控制
|
||||
Sentinel 可以针对不同的调用关系,以不同的运行指标(如 QPS、并发调用数、系统负载等)为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状。
|
||||
|
||||
Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自动将流量调整成合适的形状。常用的有:
|
||||
|
||||
- **直接拒绝模式**:即超出的请求直接拒绝。
|
||||
- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
|
||||
![Slow-Start-Preheating-Mode](/images/Slow-Start-Preheating-Mode.jpg)
|
||||
|
||||
- **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
|
||||
![Homogenizer-mode](/images/Homogenizer-mode.jpg)
|
||||
|
||||
|
||||
目前 Sentinel 对异步调用链路的支持还不是很好,后续版本会着重改善支持异步调用。
|
||||
|
||||
### 3. 系统负载保护
|
||||
Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
|
||||
|
||||
![BRP](/images/BRP.jpg)
|
||||
|
||||
### 4. 实时监控和控制面板
|
||||
Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。如果用户正在使用 Spring Boot/Spring Cloud 并使用了Sentinel Spring Cloud Starter,还可以方便地通过其暴露的 Actuator Endpoint 来获取运行时的一些信息,如动态规则等。未来 Sentinel 还会支持标准化的指标监控 API,可以方便地整合各种监控系统和可视化系统,如 Prometheus、Grafana 等。
|
||||
|
||||
Sentinel 控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。
|
||||
|
||||
![Sentinel-Dashboard](/images/Sentinel-Dashboard.jpg)
|
||||
|
||||
### 5. 生态
|
||||
Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。未来 Sentinel 还会对更多常用框架进行适配,并且会为 Service Mesh 提供集群流量防护的能力。
|
||||
|
||||
## 总结
|
||||
| # | Sentinel | Hystrix |
|
||||
|---|---|---|
|
||||
| 隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
|
||||
| 熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
|
||||
| 实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
|
||||
| 规则配置 | 支持多种数据源 | 支持多种数据源 |
|
||||
| 扩展性 | 多个扩展点 | 插件的形式 |
|
||||
| 基于注解的支持 | 支持 | 支持 |
|
||||
| 限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 |
|
||||
| 流量整形 | 支持慢启动、匀速器模式 | 不支持 |
|
||||
| 系统负载保护 | 支持 | 不支持 |
|
||||
| 控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
|
||||
| 常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC | Servlet、Spring Cloud Netflix |
|
40
docs/high-concurrency/README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# 高并发架构
|
||||
## [消息队列](/docs/high-concurrency/mq-interview.md)
|
||||
- [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](/docs/high-concurrency/why-mq.md)
|
||||
- [如何保证消息队列的高可用?](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)
|
||||
- [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)
|
||||
- [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)
|
||||
- [如何保证消息的顺序性?](/docs/high-concurrency/how-to-ensure-the-order-of-messages.md)
|
||||
- [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](/docs/high-concurrency/mq-time-delay-and-expired-failure.md)
|
||||
- [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](/docs/high-concurrency/mq-design.md)
|
||||
|
||||
## [搜索引擎](/docs/high-concurrency/es-introduction.md)
|
||||
- [es 的分布式架构原理能说一下么(es 是如何实现分布式的啊)?](/docs/high-concurrency/es-architecture.md)
|
||||
- [es 写入数据的工作原理是什么啊?es 查询数据的工作原理是什么啊?底层的 lucene 介绍一下呗?倒排索引了解吗?](/docs/high-concurrency/es-write-query-search.md)
|
||||
- [es 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](/docs/high-concurrency/es-optimizing-query-performance.md)
|
||||
- [es 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](/docs/high-concurrency/es-production-cluster.md)
|
||||
|
||||
## 缓存
|
||||
- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/docs/high-concurrency/why-cache.md)
|
||||
- [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](/docs/high-concurrency/redis-single-thread-model.md)
|
||||
- [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](/docs/high-concurrency/redis-data-types.md)
|
||||
- [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](/docs/high-concurrency/redis-expiration-policies-and-lru.md)
|
||||
- [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md)
|
||||
- [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](/docs/high-concurrency/redis-persistence.md)
|
||||
- [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](/docs/high-concurrency/redis-cluster.md)
|
||||
- [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
- [如何保证缓存与数据库的双写一致性?](/docs/high-concurrency/redis-consistence.md)
|
||||
- [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](/docs/high-concurrency/redis-cas.md)
|
||||
- [生产环境中的 Redis 是怎么部署的?](/docs/high-concurrency/redis-production-environment.md)
|
||||
|
||||
## 分库分表
|
||||
- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/docs/high-concurrency/database-shard.md)
|
||||
- [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](/docs/high-concurrency/database-shard-method.md)
|
||||
- [如何设计可以动态扩容缩容的分库分表方案?](/docs/high-concurrency/database-shard-dynamic-expand.md)
|
||||
- [分库分表之后,id 主键如何处理?](/docs/high-concurrency/database-shard-global-id-generate.md)
|
||||
|
||||
## 读写分离
|
||||
- [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md)
|
||||
|
||||
## 高并发系统
|
||||
- [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.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个库。
|
||||
有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128 个库,256 个库,512 个库。
|
||||
|
||||
1024 张表,假设每个表放 500 万数据,在 MySQL 里可以放 50 亿条数据。
|
||||
|
||||
每秒的 5 万写并发,总共 50 亿条数据,对于国内大部分的互联网公司来说,其实一般来说都够了。
|
||||
每秒 5 万的写并发,总共 50 亿条数据,对于国内大部分的互联网公司来说,其实一般来说都够了。
|
||||
|
||||
谈分库分表的扩容,**第一次分库分表,就一次性给他分个够**,32 个库,1024 张表,可能对大部分的中小型互联网公司来说,已经可以支撑好几年了。
|
||||
|
||||
@ -50,9 +50,9 @@
|
||||
| 4593 | 17 | 15 |
|
||||
|
||||
|
||||
刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个mysql服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 mysql 服务器之间做迁移就可以了。然后系统配合改一下配置即可。
|
||||
刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 mysql 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 mysql 服务器之间做迁移就可以了。然后系统配合改一下配置即可。
|
||||
|
||||
比如说最多可以扩展到32个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是1024个表。
|
||||
比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是 1024 个表。
|
||||
|
||||
这么搞,是不用自己写代码做数据迁移的,都交给 dba 来搞好了,但是 dba 确实是需要做一些库表迁移的工作,但是总比你自己写代码,然后抽数据导数据来的效率高得多吧。
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
|
||||
这里对步骤做一个总结:
|
||||
|
||||
1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32库 * 32表,对于大部分公司来说,可能几年都够了。
|
||||
1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了。
|
||||
2. 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表
|
||||
3. 扩容的时候,申请增加更多的数据库服务器,装好 mysql,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。
|
||||
4. 由 dba 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。
|
||||
|
@ -46,39 +46,39 @@
|
||||
|
||||
比较常见的包括:
|
||||
|
||||
- cobar
|
||||
- Cobar
|
||||
- TDDL
|
||||
- atlas
|
||||
- sharding-jdbc
|
||||
- mycat
|
||||
- Atlas
|
||||
- Sharding-jdbc
|
||||
- Mycat
|
||||
|
||||
#### cobar
|
||||
阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 cobar 集群,cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 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](/images/database-split-horizon.png)
|
||||
|
||||
@ -92,13 +92,13 @@ mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
|
||||
|
||||
好了,无论分库还是分表,上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后,**中间件可以根据你指定的某个字段值**,比如说 userid,**自动路由到对应的库上去,然后再自动路由到对应的表里去**。
|
||||
|
||||
你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都ok了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。
|
||||
你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都 ok 了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。
|
||||
|
||||
|
||||
而且这儿还有两种**分库分表的方式**:
|
||||
|
||||
- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
|
||||
- 或者是按照某个字段hash一下均匀分散,这个较为常用。
|
||||
- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
|
||||
|
||||
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。
|
||||
|
||||
|
@ -19,7 +19,7 @@ es 中存储数据的**基本单位是索引**,比如说你现在要在 es 中
|
||||
index -> type -> mapping -> document -> field。
|
||||
```
|
||||
|
||||
这样吧,为了做个更直白的介绍,我在这里做个类比。
|
||||
这样吧,为了做个更直白的介绍,我在这里做个类比。但是切记,不要划等号,类比只是为了便于理解。
|
||||
|
||||
index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,一个 index 里可以有多个 type,每个 type 的字段都是差不多的,但是有一些略微的差别。假设有一个 index,是订单 index,里面专门是放订单数据的。就好比说你在 mysql 中建表,有些订单是实物商品的订单,比如一件衣服、一双鞋子;有些订单是虚拟商品的订单,比如游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字段可能有略微的一些差别。
|
||||
|
||||
@ -29,8 +29,7 @@ index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,
|
||||
|
||||
![es-index-type-mapping-document-field](/images/es-index-type-mapping-document-field.png)
|
||||
|
||||
你搞一个索引,这个索引可以拆分成多个 `shard`,每个 shard 存储部分数据。
|
||||
|
||||
你搞一个索引,这个索引可以拆分成多个 `shard`,每个 shard 存储部分数据。拆分多个 shard 是有好处的,一是**支持横向扩展**,比如你数据量是 3T,3 个 shard,每个 shard 就 1T 的数据,若现在数据量增加到 4T,怎么扩展,很简单,重新建一个有 4 个 shard 的索引,将数据导进去;二是**提高性能**,数据分布在多个 shard,即多台服务器上,所有的操作,都会在多台机器上并行分布式执行,提高了吞吐量和性能。
|
||||
|
||||
接着就是这个 shard 的数据实际是有多个备份,就是说每个 shard 都有一个 `primary shard`,负责写入数据,但是还有几个 `replica shard`。`primary shard` 写入数据之后,会将数据同步到其他几个 `replica shard` 上去。
|
||||
|
||||
|
@ -10,7 +10,7 @@ es 在数据量很大的情况下(数十亿级别)如何提高查询效率
|
||||
说实话,es 性能优化是没有什么银弹的,啥意思呢?就是**不要期待着随手调一个参数,就可以万能的应对所有的性能慢的场景**。也许有的场景是你换个参数,或者调整一下语法,就可以搞定,但是绝对不是所有场景都可以这样。
|
||||
|
||||
### 性能优化的杀手锏——filesystem cache
|
||||
你往 es 里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 `filesystem cache` 里面去。
|
||||
你往 es 里写的数据,实际上都写到磁盘文件里去了,**查询的时候**,操作系统会将磁盘文件里的数据自动缓存到 `filesystem cache` 里面去。
|
||||
|
||||
![es-search-process](/images/es-search-process.png)
|
||||
|
||||
@ -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~10s,现在可能性能就会很高,每次查询就是 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,7 +39,7 @@ hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可
|
||||
|
||||
或者是电商,你可以将平时查看最多的一些商品,比如说 iphone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 `filesystem cache` 里去。
|
||||
|
||||
对于那些你觉得比较热的,经常会有人访问的数据,最好**做一个专门的缓存预热子系统**,就是对热数据每隔一段时间,就提前访问一下,让数据进入 `filesystem cache` 里面去。这样下次别人访问的时候,一定性能会好一些。
|
||||
对于那些你觉得比较热的、经常会有人访问的数据,最好**做一个专门的缓存预热子系统**,就是对热数据每隔一段时间,就提前访问一下,让数据进入 `filesystem cache` 里面去。这样下次别人访问的时候,性能一定会好很多。
|
||||
|
||||
### 冷热分离
|
||||
es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。最好是将**冷数据写入一个索引中,然后热数据写入另外一个索引中**,这样可以确保热数据在被预热之后,尽量都让他们留在 `filesystem os cache` 里,**别让冷数据给冲刷掉**。
|
||||
@ -51,14 +51,14 @@ es 可以做类似于 mysql 的水平拆分,就是说将大量的访问很少
|
||||
|
||||
最好是先在 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 条数据。
|
||||
|
||||
分布式的,你要查第 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 页或者几十页的时候,基本上就要 5~10 秒 才能查出来一页数据了。
|
||||
我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时候,基本上就要 5~10 秒才能查出来一页数据了。
|
||||
|
||||
有什么解决方案吗?
|
||||
#### 不允许深度分页(默认深度分页性能很差)
|
||||
|
@ -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。
|
||||
|
||||
大概就这么说一下就行了。
|
@ -38,6 +38,7 @@ j2ee特别牛
|
||||
- query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id`)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
|
||||
- fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。
|
||||
|
||||
> 写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。
|
||||
|
||||
### 写数据底层原理
|
||||
|
||||
@ -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 去开发就可以了。
|
||||
@ -121,4 +124,6 @@ buffer 每次 refresh 一次,就会产生一个 `segment file`,所以默认
|
||||
要注意倒排索引的两个重要细节:
|
||||
|
||||
- 倒排索引中的所有词项对应一个或多个文档;
|
||||
- 倒排索引中的词项**根据字典顺序升序排列**。
|
||||
- 倒排索引中的词项**根据字典顺序升序排列**
|
||||
|
||||
> 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。
|
@ -12,7 +12,7 @@
|
||||
|
||||
如果有面试官问你个问题说,如何设计一个高并发系统?那么不好意思,**一定是因为你实际上没干过高并发系统**。面试官看你简历就没啥出彩的,感觉就不咋地,所以就会问问你,如何设计一个高并发系统?其实说白了本质就是看看你有没有自己研究过,有没有一定的知识积累。
|
||||
|
||||
最好的当然是招聘个真正干过高并发的哥儿们咯,但是这种哥儿们人数稀缺,不好招。所以可能次一点的就是招一个自己研究过的哥儿们,总比招一个傻也不会的哥儿们好吧!
|
||||
最好的当然是招聘个真正干过高并发的哥儿们咯,但是这种哥儿们人数稀缺,不好招。所以可能次一点的就是招一个自己研究过的哥儿们,总比招一个啥也不会的哥儿们好吧!
|
||||
|
||||
所以这个时候你必须得做一把个人秀了,秀出你所有关于高并发的知识!
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
当然会挂了,凭什么不挂?你数据库如果瞬间承载每秒 5000/8000,甚至上万的并发,一定会宕机,因为比如 mysql 就压根儿扛不住这么高的并发量。
|
||||
|
||||
所以为啥高并发牛逼?就是因为现在用互联网的人越来越多,很多app、网站、系统承载的都是高并发请求,可能高峰期每秒并发量几千,很正常的。如果是什么双十一之类的,每秒并发几万几十万都有可能。
|
||||
所以为啥高并发牛逼?就是因为现在用互联网的人越来越多,很多 app、网站、系统承载的都是高并发请求,可能高峰期每秒并发量几千,很正常的。如果是什么双十一之类的,每秒并发几万几十万都有可能。
|
||||
|
||||
那么如此之高的并发量,加上原本就如此之复杂的业务,咋玩儿?真正厉害的,一定是在复杂业务系统里玩儿过高并发架构的人,但是你没有,那么我给你说一下你该怎么回答这个问题:
|
||||
|
||||
@ -60,6 +60,6 @@ Elasticsearch,简称 es。es 是分布式的,可以随便扩容,分布式
|
||||
|
||||
上面的 6 点,基本就是高并发系统肯定要干的一些事儿,大家可以仔细结合之前讲过的知识考虑一下,到时候你可以系统的把这块阐述一下,然后每个部分要注意哪些问题,之前都讲过了,你都可以阐述阐述,表明你对这块是有点积累的。
|
||||
|
||||
说句实话,毕竟你真正厉害的一点,不是在于弄明白一些技术,或者大概知道一个高并发系统应该长什么样?其实实际上在真正的复杂的业务系统里,做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑:哪些需要分库分表,哪些不需要分库分表,单库单表跟分库分表如何 join,哪些数据要放到缓存里去,放哪些数据再可以扛住高并发的请求,你需要完成对一个复杂业务系统的分析之后,然后逐步逐步的加入高并发的系统架构的改造,这个过程是无比复杂的,一旦做过一次,并且做好了,你在这个市场上就会非常的吃香。
|
||||
说句实话,毕竟你真正厉害的一点,不是在于弄明白一些技术,或者大概知道一个高并发系统应该长什么样?其实实际上在真正的复杂的业务系统里,做高并发要远远比上面提到的点要复杂几十倍到上百倍。你需要考虑:哪些需要分库分表,哪些不需要分库分表,单库单表跟分库分表如何 join,哪些数据要放到缓存里去,放哪些数据才可以扛住高并发的请求,你需要完成对一个复杂业务系统的分析之后,然后逐步逐步的加入高并发的系统架构的改造,这个过程是无比复杂的,一旦做过一次,并且做好了,你在这个市场上就会非常的吃香。
|
||||
|
||||
其实大部分公司,真正看重的,不是说你掌握高并发相关的一些基本的架构知识,架构中的一些技术,RocketMQ、Kafka、Redis、Elasticsearch,高并发这一块,你了解了,也只能是次一等的人才。对一个有几十万行代码的复杂的分布式系统,一步一步架构、设计以及实践过高并发架构的人,这个经验是难能可贵的。
|
@ -56,7 +56,7 @@ Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机
|
||||
|
||||
![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 之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)
|
||||
|
||||
|
@ -36,7 +36,7 @@ channel.txCommit
|
||||
|
||||
所以一般来说,如果你要确保说写 RabbitMQ 的消息别丢,可以开启 `confirm` 模式,在生产者那里设置开启 `confirm` 模式之后,你每次写的消息都会分配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个 `ack` 消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没能处理这个消息,会回调你的一个 `nack` 接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息 id 的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。
|
||||
|
||||
事务机制和 `cnofirm` 机制最大的不同在于,**事务机制是同步的**,你提交一个事务之后会**阻塞**在那儿,但是 `confirm` 机制是**异步**的,你发送个消息之后就可以发送下一个消息,然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。
|
||||
事务机制和 `confirm` 机制最大的不同在于,**事务机制是同步的**,你提交一个事务之后会**阻塞**在那儿,但是 `confirm` 机制是**异步**的,你发送个消息之后就可以发送下一个消息,然后那个消息 RabbitMQ 接收了之后会异步回调你的一个接口通知你这个消息接收到了。
|
||||
|
||||
所以一般在生产者这块**避免数据丢失**,都是用 `confirm` 机制的。
|
||||
|
||||
@ -88,4 +88,4 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
|
||||
我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。
|
||||
|
||||
#### 生产者会不会弄丢数据?
|
||||
如果按照上述的思路设置了 `acks=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
|
||||
如果按照上述的思路设置了 `acks=all`,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 34 KiB |
@ -7,7 +7,7 @@
|
||||
- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。
|
||||
- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。
|
||||
|
||||
说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,大多数人就是平时埋头用,从来不去思考背后的一些东西。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做?
|
||||
说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,**大多数人就是平时埋头用,从来不去思考背后的一些东西**。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做?
|
||||
|
||||
|
||||
## 面试题剖析
|
||||
@ -23,4 +23,4 @@
|
||||
|
||||
- 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。
|
||||
|
||||
mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。
|
||||
mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。
|
||||
|
@ -1,12 +1,12 @@
|
||||
## 面试题
|
||||
了解什么是 redis 的雪崩和穿透?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?
|
||||
了解什么是 redis 的雪崩、穿透和击穿?redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 redis 的穿透?
|
||||
|
||||
## 面试官心理分析
|
||||
其实这是问到缓存必问的,因为缓存雪崩和穿透,是缓存最大的两个问题,要么不出现,一旦出现就是致命性的问题,所以面试官一定会问你。
|
||||
|
||||
## 面试题剖析
|
||||
### 缓存雪崩
|
||||
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
|
||||
对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
|
||||
|
||||
这就是缓存雪崩。
|
||||
|
||||
@ -35,8 +35,13 @@
|
||||
|
||||
黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。
|
||||
|
||||
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
|
||||
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“**视缓存于无物**”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
|
||||
|
||||
![redis-caching-penetration](/images/redis-caching-penetration.png)
|
||||
|
||||
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。这样的话,下次便能走缓存了。
|
||||
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
|
||||
|
||||
### 缓存击穿
|
||||
缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
|
||||
|
||||
解决方式也很简单,可以将热点数据设置为永远不过期;或者基于 redis or zookeeper 实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该 key 访问数据。
|
@ -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,8 +23,9 @@ redis cluster,主要是针对**海量数据+高并发+高可用**的场景。r
|
||||
|
||||
### 节点间的内部通信机制
|
||||
#### 基本通信原理
|
||||
- redis cluster 节点间采用 gossip 协议进行通信
|
||||
集中式是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
|
||||
集群元数据的维护有两种方式:集中式、Gossip 协议。redis cluster 节点间采用 gossip 协议进行通信。
|
||||
|
||||
**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
|
||||
|
||||
![zookeeper-centralized-storage](/images/zookeeper-centralized-storage.png)
|
||||
|
||||
@ -34,20 +35,21 @@ redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节
|
||||
|
||||
**集中式**的**好处**在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;**不好**在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。
|
||||
|
||||
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 算法(大量缓存重建)
|
||||
@ -92,7 +94,7 @@ redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master
|
||||
![hash-slot](/images/hash-slot.png)
|
||||
|
||||
### redis cluster 的高可用与主备切换原理
|
||||
redis cluster 的高可用的原理,几乎跟哨兵是类似的
|
||||
redis cluster 的高可用的原理,几乎跟哨兵是类似的。
|
||||
|
||||
#### 判断节点宕机
|
||||
如果一个节点认为另外一个节点宕机,那么就是 `pfail`,**主观宕机**。如果多个节点都认为另外一个节点宕机了,那么就是 `fail`,**客观宕机**,跟哨兵的原理几乎一样,sdown,odown。
|
||||
|
@ -24,14 +24,14 @@
|
||||
|
||||
举个栗子,一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100 次;但是这个缓存在 1 分钟内只被读取了 1 次,有**大量的冷数据**。实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。**用到缓存才去算缓存。**
|
||||
|
||||
其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。
|
||||
其实删除缓存,而不是更新缓存,就是一个 lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。像 mybatis,hibernate,都有懒加载思想。查询一个部门,部门带了一个员工的 list,没有必要说每次查询部门,都把里面的 1000 个员工的数据也同时查出来啊。80% 的情况,查这个部门,就只是要访问这个部门的信息就可以了。先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询 1000 个员工。
|
||||
|
||||
### 最初级的缓存不一致问题及解决方案
|
||||
问题:先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
|
||||
问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
|
||||
|
||||
![redis-junior-inconsistent](/images/redis-junior-inconsistent.png)
|
||||
|
||||
解决思路:先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。
|
||||
解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
|
||||
|
||||
### 比较复杂的数据不一致问题分析
|
||||
数据发生了变更,先删除了缓存,然后要去修改数据库,此时还没修改。一个请求过来,去读缓存,发现缓存空了,去查询数据库,**查到了修改前的旧数据**,放到了缓存中。随后数据变更的程序完成了数据库的修改。完了,数据库和缓存中的数据不一样了...
|
||||
@ -42,9 +42,9 @@
|
||||
|
||||
**解决方案如下:**
|
||||
|
||||
更新数据的时候,根据**数据的唯一标识**,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个 jvm 内部队列中。
|
||||
更新数据的时候,根据**数据的唯一标识**,将操作路由之后,发送到一个 jvm 内部队列中。读取数据的时候,如果发现数据不在缓存中,那么将重新执行“读取数据+更新缓存”的操作,根据唯一标识路由之后,也发送到同一个 jvm 内部队列中。
|
||||
|
||||
一个队列对应一个工作线程,每个工作线程**串行**拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
|
||||
一个队列对应一个工作线程,每个工作线程**串行**拿到对应的操作,然后一条一条的执行。这样的话,一个数据变更的操作,先删除缓存,然后再去更新数据库,但是还没完成更新。此时如果一个读请求过来,没有读到缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。
|
||||
|
||||
这里有一个**优化点**,一个队列中,其实**多个更新缓存请求串在一起是没意义的**,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可。
|
||||
|
||||
@ -53,13 +53,14 @@
|
||||
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。
|
||||
|
||||
高并发的场景下,该解决方案要注意的问题:
|
||||
|
||||
- 读请求长时阻塞
|
||||
|
||||
由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。
|
||||
|
||||
该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。
|
||||
|
||||
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每隔库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。
|
||||
另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。
|
||||
|
||||
一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。
|
||||
|
||||
@ -87,4 +88,4 @@
|
||||
|
||||
- 热点商品的路由问题,导致请求的倾斜
|
||||
|
||||
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
|
||||
万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。
|
||||
|
@ -47,7 +47,7 @@ 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](/images/redis-master-slave-replication-detail.png)
|
||||
|
||||
@ -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 **基于哨兵的高可用性**。
|
||||
后面会详细说明 redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.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 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。
|
||||
|
||||
### RDB和AOF到底该如何选择
|
||||
### RDB 和 AOF 到底该如何选择
|
||||
- 不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
|
||||
- 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
|
||||
- redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。
|
||||
|
@ -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](/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](/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 配置的。
|
@ -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](/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 的一次通信过程,推荐读者阅读《[Redis 设计与实现——黄健宏](https://github.com/doocs/technical-books#database)》进行系统学习。
|
||||
|
||||
### 为啥 redis 单线程模型也能效率这么高?
|
||||
- 纯内存操作
|
||||
- 核心是基于非阻塞的 IO 多路复用机制
|
||||
- 单线程反而避免了多线程的频繁上下文切换问题
|
||||
- 纯内存操作。
|
||||
- 核心是基于非阻塞的 IO 多路复用机制。
|
||||
- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。
|
||||
- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。
|
||||
|
@ -36,4 +36,4 @@ mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,
|
||||
- [缓存雪崩、缓存穿透](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md)
|
||||
- [缓存并发竞争](/docs/high-concurrency/redis-cas.md)
|
||||
|
||||
后面再详细说明。
|
||||
后面再详细说明。
|
13
docs/micro-services/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# 微服务架构
|
||||
- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java)
|
||||
- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md)
|
||||
|
||||
## Spring Cloud 微服务架构
|
||||
- 什么是微服务?微服务之间是如何独立通讯的?
|
||||
- Spring Cloud 和 Dubbo 有哪些区别?
|
||||
- Spring Boot 和 Spring Cloud,谈谈你对它们的理解?
|
||||
- 什么是服务熔断?什么是服务降级?
|
||||
- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑?
|
||||
- 你所知道的微服务技术栈都有哪些?
|
||||
- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别?
|
||||
- ......
|
@ -0,0 +1,110 @@
|
||||
# 1.1 微服务和分布式数据管理问题
|
||||
|
||||
单体式应用一般都会有一个关系型数据库,由此带来的好处是应用可以使用 ACID transactions,可以带来一些重要的操作特性:
|
||||
|
||||
1. 原子性 – 任何改变都是原子性的
|
||||
2. 一致性 – 数据库状态一直是一致性的
|
||||
3. 隔离性 – 即使交易并发执行,看起来也是串行的
|
||||
4. Durable – 一旦交易提交了就不可回滚
|
||||
|
||||
鉴于以上特性,应用可以简化为:开始一个交易,改变(插入,删除,更新)很多行,然后提交这些交易。
|
||||
|
||||
使用关系型数据库带来另外一个优势在于提供 SQL(功能强大,可声明的,表转化的查询语言)支持。用户可以非常容易通过查询将多个表的数据组合起来,RDBMS 查询调度器决定最佳实现方式,用户不需要担心例如如何访问数据库等底层问题。另外,因为所有应用的数据都在一个数据库中,很容易去查询。
|
||||
|
||||
然而,对于微服务架构来说,数据访问变得非常复杂,这是因为数据都是微服务私有的,唯一可访问的方式就是通过 API。这种打包数据访问方式使得微服务之间松耦合,并且彼此之间独立。如果多个服务访问同一个数据,schema 会更新访问时间,并在所有服务之间进行协调。
|
||||
|
||||
更甚于,不同的微服务经常使用不同的数据库。应用会产生各种不同数据,关系型数据库并不一定是最佳选择。某些场景,某个 NoSQL 数据库可能提供更方便的数据模型,提供更加的性能和可扩展性。例如,某个产生和查询字符串的应用采用例如 Elasticsearch 的字符搜索引擎。同样的,某个产生社交图片数据的应用可以采用图片数据库,例如,Neo4j ;因此,基于微服务的应用一般都使用 SQL 和 NoSQL 结合的数据库,也就是被称为 polyglot persistence 的方法。
|
||||
|
||||
分区的,polyglot-persistent 架构用于存储数据有许多优势,包括松耦合服务和更佳性能和可扩展性。然而,随之而来的则是分布式数据管理带来的挑战。
|
||||
|
||||
第一个挑战在于如何完成一笔交易的同时保持多个服务之间数据一致性。之所以会有这个问题,我们以一个在线 B2B 商店为例,客户服务维护包括客户的各种信息,例如 credit lines 。订单服务管理订单,需要验证某个新订单与客户的信用限制没有冲突。在单一式应用中,订单服务只需要使用 ACID 交易就可以检查可用信用和创建订单。
|
||||
|
||||
相反的,微服务架构下,订单和客户表分别是相对应服务的私有表,如下图所示:
|
||||
|
||||
![service table](/images/Private-table-of-the-corresponding-service.png)
|
||||
|
||||
订单服务不能直接访问客户表,只能通过客户服务发布的 API 来访问。订单服务也可以使用 distributed transactions, 也就是周知的两阶段提交 (2PC)。然而,2PC 在现在应用中不是可选性。根据 CAP 理论,必须在可用性(availability)和 ACID 一致性(consistency)之间做出选择,availability 一般是更好的选择。但是,许多现代科技,例如许多 NoSQL 数据库,并不支持 2PC。在服务和数据库之间维护数据一致性是非常根本的需求,因此我们需要找其他的方案。
|
||||
|
||||
第二个挑战是如何完成从多个服务中搜索数据。例如,设想应用需要显示客户和他的订单。如果订单服务提供 API 来接受用户订单信息,那么用户可以使用类应用型的 join 操作接收数据。应用从用户服务接受用户信息,从订单服务接受此用户订单。假设,订单服务只支持通过私有键(key)来查询订单(也许是在使用只支持基于主键接受的 NoSQL 数据库),此时,没有合适的方法来接收所需数据。
|
||||
|
||||
# 1.2 事件驱动架构
|
||||
|
||||
对许多应用来说,这个解决方案就是使用事件驱动架构(event-driven architecture)。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的时间发布。
|
||||
|
||||
可以使用事件来实现跨多服务的业务交易。交易一般由一系列步骤构成,每一步骤都由一个更新业务实体的微服务和发布激活下一步骤的事件构成。下图展现如何使用事件驱动方法,在创建订单时检查信用可用度,微服务通过消息代理(Messsage Broker)来交换事件。
|
||||
|
||||
1. 订单服务创建一个带有 NEW 状态的 Order (订单),发布了一个 “Order Created Event(创建订单)” 的事件。
|
||||
|
||||
![Order-Created-Event](/images/Order-Created-Event.png)
|
||||
|
||||
2. 客户服务消费 Order Created Event 事件,为此订单预留信用,发布 “Credit Reserved Event(信用预留)” 事件。
|
||||
|
||||
![Credit-Reserved-Event](/images/Credit-Reserved-Event.png)
|
||||
|
||||
3. 订单服务消费 Credit Reserved Event ,改变订单的状态为 OPEN。
|
||||
|
||||
![Status-is-OPEN](/images/Status-is-OPEN.png)
|
||||
|
||||
更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。
|
||||
|
||||
考虑到(a)每个服务原子性更新数据库和发布事件,然后,(b)消息代理确保事件传递至少一次,然后可以跨多个服务完成业务交易(此交易不是 ACID 交易)。这种模式提供弱确定性,例如最终一致性 eventual consistency。这种交易类型被称作 BASE model。
|
||||
|
||||
亦可以使用事件来维护不同微服务拥有数据预连接(pre-join)的实现视图。维护此视图的服务订阅相关事件并且更新视图。例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件。
|
||||
|
||||
![pre-join](/images/pre-join.png)
|
||||
|
||||
当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。可以使用文档数据库(例如 MongoDB)来实现客户订单视图,为每个用户存储一个文档。客户订单视图查询服务负责响应对客户以及最近订单(通过查询客户订单视图数据集)的查询。
|
||||
|
||||
事件驱动架构也是既有优点也有缺点,此架构可以使得交易跨多个服务且提供最终一致性,并且可以使应用维护最终视图;而缺点在于编程模式比 ACID 交易模式更加复杂:为了从应用层级失效中恢复,还需要完成补偿性交易,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)交易造成的改变是可见的,另外当应用读取未更新的最终视图时也会遇见数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事件。
|
||||
|
||||
# 1.3 原子操作 Achieving Atomicity
|
||||
|
||||
事件驱动架构还会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向 ORDER 表插入一行,然后发布 Order Created event,这两个操作需要原子性。如果更新数据库后,服务瘫了(crashes)造成事件未能发布,系统变成不一致状态。确保原子操作的标准方式是使用一个分布式交易,其中包括数据库和消息代理。然而,基于以上描述的 CAP 理论,这却并不是我们想要的。
|
||||
|
||||
## 1.3.1 使用本地交易发布事件
|
||||
|
||||
获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions,技巧在于一个 EVENT 表,此表在存储业务实体数据库中起到消息列表功能。应用发起一个(本地)数据库交易,更新业务实体状态,向 EVENT 表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此 EVENT 表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示:
|
||||
|
||||
![multi-step process](/images/multi-step-process.png)
|
||||
|
||||
订单服务向 ORDER 表插入一行,然后向 EVENT 表中插入 Order Created event,事件发布线程或者进程查询 EVENT 表,请求未发布事件,发布他们,然后更新 EVENT 表标志此事件为已发布。
|
||||
|
||||
此方法也是优缺点都有。优点是可以确保事件发布不依赖于 2PC,应用发布业务层级事件而不需要推断他们发生了什么;而缺点在于此方法由于开发人员必须牢记发布事件,因此有可能出现错误。另外此方法对于某些使用 NoSQL 数据库的应用是个挑战,因为 NoSQL 本身交易和查询能力有限。
|
||||
|
||||
此方法因为应用采用了本地交易更新状态和发布事件而不需要 2PC,现在再看看另外一种应用简单更新状态获得原子性的方法。
|
||||
|
||||
## 1.3.2 挖掘数据库交易日志
|
||||
|
||||
另外一种不需要 2PC 而获得线程或者进程发布事件原子性的方式就是挖掘数据库交易或者提交日志。应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见:
|
||||
|
||||
![No-2PC-required](/images/No-2PC-required.png)
|
||||
|
||||
此方法的例子如 LinkedIn Databus 项目,Databus 挖掘 Oracle 交易日志,根据变化发布事件,LinkedIn 使用 Databus 来保证系统内各记录之间的一致性。
|
||||
|
||||
另外的例子如:AWS 的 streams mechanism in AWS DynamoDB,是一个可管理的 NoSQL 数据库,一个 DynamoDB 流是由过去 24 小时对数据库表基于时序的变化(创建,更新和删除操作),应用可以从流中读取这些变化,然后以事件方式发布这些变化。
|
||||
|
||||
交易日志挖掘也是优缺点并存。优点是确保每次更新发布事件不依赖于 2PC。交易日志挖掘可以通过将发布事件和应用业务逻辑分离开得到简化;而主要缺点在于交易日志对不同数据库有不同格式,甚至不同数据库版本也有不同格式;而且很难从底层交易日志更新记录转换为高层业务事件。
|
||||
|
||||
交易日志挖掘方法通过应用直接更新数据库而不需要 2PC 介入。下面我们再看一种完全不同的方法:不需要更新只依赖事件的方法。
|
||||
|
||||
## 1.3.3 使用事件源
|
||||
|
||||
Event sourcing (事件源)通过使用根本不同的事件中心方式来获得不需 2PC 的原子性,保证业务实体的一致性。 这种应用保存业务实体一系列状态改变事件,而不是存储实体现在的状态。应用可以通过重放事件来重建实体现在状态。只要业务实体发生变化,新事件就会添加到时间表中。因为保存事件是单一操作,因此肯定是原子性的。
|
||||
|
||||
为了理解事件源工作方式,考虑事件实体作为一个例子。传统方式中,每个订单映射为 ORDER 表中一行,例如在 ORDER_LINE_ITEM 表中。但是对于事件源方式,订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够数据来重建订单状态。
|
||||
|
||||
![Event-sourcing](/images/Event-sourcing.png)
|
||||
|
||||
事件是长期保存在事件数据库中,提供 API 添加和获取实体事件。事件存储跟之前描述的消息代理类似,提供 API 来订阅事件。事件存储将事件递送到所有感兴趣的订阅者,事件存储是事件驱动微服务架构的基干。
|
||||
|
||||
事件源方法有很多优点:解决了事件驱动架构关键问题,使得只要有状态变化就可以可靠地发布事件,也就解决了微服务架构中数据一致性问题。另外,因为是持久化事件而不是对象,也就避免了 object relational impedance mismatch problem。
|
||||
|
||||
数据源方法提供了 100%可靠的业务实体变化监控日志,使得获取任何时点实体状态成为可能。另外,事件源方法可以使得业务逻辑可以由事件交换的松耦合业务实体构成。这些优势使得单体应用移植到微服务架构变的相对容易。
|
||||
|
||||
事件源方法也有不少缺点,因为采用不同或者不太熟悉的变成模式,使得重新学习不太容易;事件存储只支持主键查询业务实体,必须使用 Command Query Responsibility Segregation (CQRS) 来完成查询业务,因此,应用必须处理最终一致数据。
|
||||
|
||||
# 1.4 总结
|
||||
|
||||
在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务交易一致性;第二个挑战是如何从多服务环境中获取一致性数据。
|
||||
|
||||
最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法可以解决此问题,包括将数据库视为消息队列、交易日志挖掘和事件源。
|
BIN
docs/micro-services/images/30103116_ZCcM.png
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
docs/micro-services/images/Before-and-after-migration.png
Normal file
After Width: | Height: | Size: 95 KiB |
BIN
docs/micro-services/images/Credit-Reserved-Event.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
docs/micro-services/images/Event-sourcing.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
docs/micro-services/images/Law-of-Holes.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
docs/micro-services/images/No-2PC-required.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
docs/micro-services/images/Order-Created-Event.png
Normal file
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 68 KiB |
BIN
docs/micro-services/images/Status-is-OPEN.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
docs/micro-services/images/multi-step-process.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
docs/micro-services/images/pre-join.png
Normal file
After Width: | Height: | Size: 81 KiB |
@ -0,0 +1,80 @@
|
||||
# 迁移到微服务综述
|
||||
|
||||
迁移单体式应用到微服务架构意味着一系列现代化过程,有点像这几代开发者一直在做的事情,实时上,当迁移时,我们可以重用一些想法。
|
||||
|
||||
一个策略是:不要大规模(big bang)重写代码(只有当你承担重建一套全新基于微服务的应用时候可以采用重写这种方法)。重写代码听起来很不错,但实际上充满了风险最终可能会失败,就如 Martin Fowler 所说:“the only thing a Big Bang rewrite guarantees is a Big Bang!”
|
||||
|
||||
相反,应该采取逐步迁移单体式应用的策略,通过逐步生成微服务新应用,与旧的单体式应用集成,随着时间推移,单体式应用在整个架构中比例逐渐下降直到消失或者成为微服务架构一部分。这个策略有点像在高速路上限速到 70 迈对车做维护,尽管有挑战,但是比起重写的风险小很多。
|
||||
|
||||
Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字来源于雨林中的绞杀藤(strangler vine),也叫绞杀榕 (strangler fig)。绞杀藤为了爬到森林顶端都要缠绕着大叔生长,一段时间后,树死了,留下树形藤。这种应用也使用同一种模式,围绕着传统应用开发了新型微服务应用,传统应用会渐渐退出舞台。
|
||||
|
||||
|
||||
我们来看看其他可行策略。
|
||||
|
||||
# 策略 1——停止挖掘
|
||||
|
||||
Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用不可管理时这是最佳建议。换句话说,应该停止让单体式应用继续变大,也就是说当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。如下图所示:
|
||||
|
||||
![1](/images/Law-of-Holes.png)
|
||||
|
||||
除了新服务和传统应用,还有两个模块,其一是请求路由器,负责处理入口(http)请求,有点像之前提到的 API 网关。路由器将新功能请求发送给新开发的服务,而将传统请求还发给单体式应用。
|
||||
|
||||
另外一个是胶水代码(glue code),将微服务和单体应用集成起来,微服务很少能独立存在,经常会访问单体应用的数据。胶水代码,可能在单体应用或者为服务或者二者兼而有之,负责数据整合。微服务通过胶水代码从单体应用中读写数据。
|
||||
|
||||
微服务有三种方式访问单体应用数据:
|
||||
|
||||
- 换气单体应用提供的远程 API
|
||||
- 直接访问单体应用数据库
|
||||
- 自己维护一份从单体应用中同步的数据
|
||||
|
||||
胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 Domain Driven Design,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。
|
||||
|
||||
将新功能以轻量级微服务方式实现由很多优点,例如可以阻止单体应用变的更加无法管理。微服务本身可以开发、部署和独立扩展。采用微服务架构会给开发者带来不同的切身感受。
|
||||
|
||||
然而,这方法并不解决任何单体式本身问题,为了解决单体式本身问题必须深入单体应用做出改变。我们来看看这么做的策略。
|
||||
|
||||
# 策略 2——将前端和后端分离
|
||||
|
||||
减小单体式应用复杂度的策略是讲表现层和业务逻辑、数据访问层分开。典型的企业应用至少有三个不同元素构成:
|
||||
|
||||
1. 表现层——处理 HTTP 请求,要么响应一个 RESTAPI 请求,要么是提供一个基于 HTML 的图形接口。对于一个复杂用户接口应用,表现层经常是代码重要的部分。
|
||||
|
||||
2. 业务逻辑层——完成业务逻辑的应用核心。
|
||||
|
||||
3. 数据访问层——访问基础元素,例如数据库和消息代理。
|
||||
|
||||
在表现层与业务数据访问层之间有清晰的隔离。业务层有由若干方面组成的粗粒度(coarse-grained)的 API,内部包含了业务逻辑元素。API 是可以将单体业务分割成两个更小应用的天然边界,其中一个应用是表现层,另外一个是业务和数据访问逻辑。分割后,表现逻辑应用远程调用业务逻辑应用,下图表示迁移前后架构不同:
|
||||
![2](/images/Before-and-after-migration.png)
|
||||
|
||||
单体应用这么分割有两个好处,其一使得应用两部分开发、部署和扩展各自独立,特别地,允许表现层开发者在用户界面上快速选择,进行 A/B 测试;其二,使得一些远程 API 可以被微服务调用。
|
||||
|
||||
然而,这种策略只是部分的解决方案。很可能应用的两部分之一或者全部都是不可管理的,因此需要使用第三种策略来消除剩余的单体架构。
|
||||
|
||||
# 策略 3——抽出服务
|
||||
|
||||
第三种迁移策略就是从单体应用中抽取出某些模块成为独立微服务。每当抽取一个模块变成微服务,单体应用就变简单一些;一旦转换足够多的模块,单体应用本身已经不成为问题了,要么消失了,要么简单到成为一个服务。
|
||||
|
||||
## 排序那个模块应该被转成微服务
|
||||
|
||||
一个巨大的复杂单体应用由成十上百个模块构成,每个都是被抽取对象。决定第一个被抽取模块一般都是挑战,一般最好是从最容易抽取的模块开始,这会让开发者积累足够经验,这些经验可以为后续模块化工作带来巨大好处。
|
||||
|
||||
转换模块成为微服务一般很耗费时间,一般可以根据获益程度来排序,一般从经常变化模块开始会获益最大。一旦转换一个模块为微服务,就可以将其开发部署成独立模块,从而加速开发进程。
|
||||
|
||||
将资源消耗大户先抽取出来也是排序标准之一。例如,将内存数据库抽取出来成为一个微服务会非常有用,可以将其部署在大内存主机上。同样的,将对计算资源很敏感的算法应用抽取出来也是非常有益的,这种服务可以被部署在有很多 CPU 的主机上。通过将资源消耗模块转换成微服务,可以使得应用易于扩展。
|
||||
|
||||
查找现有粗粒度边界来决定哪个模块应该被抽取,也是很有益的,这使得移植工作更容易和简单。例如,只与其他应用异步同步消息的模块就是一个明显边界,可以很简单容易地将其转换为微服务。
|
||||
|
||||
## 如何抽取模块
|
||||
|
||||
抽取模块第一步就是定义好模块和单体应用之间粗粒度接口,由于单体应用需要微服务的数据,反之亦然,因此更像是一个双向 API。因为必须在负责依赖关系和细粒度接口模式之间做好平衡,因此开发这种 API 很有挑战性,尤其对使用域模型模式的业务逻辑层来说更具有挑战,因此经常需要改变代码来解决依赖性问题,如图所示:
|
||||
|
||||
一旦完成粗粒度接口,也就将此模块转换成独立微服务。为了实现,必须写代码使得单体应用和微服务之间通过使用进程间通信(IPC)机制的 API 来交换信息。如图所示迁移前后对比:
|
||||
|
||||
![3](/images/30103116_ZCcM.png)
|
||||
|
||||
此例中,正在使用 Y 模块的 Z 模块是备选抽取模块,其元素正在被 X 模块使用,迁移第一步就是定义一套粗粒度 APIs,第一个接口应该是被 X 模块使用的内部接口,用于激活 Z 模块;第二个接口是被 Z 模块使用的外部接口,用于激活 Y 模块。
|
||||
|
||||
迁移第二步就是将模块转换成独立服务。内部和外部接口都使用基于 IPC 机制的代码,一般都会将 Z 模块整合成一个微服务基础框架,来出来割接过程中的问题,例如服务发现。
|
||||
|
||||
抽取完模块,也就可以开发、部署和扩展另外一个服务,此服务独立于单体应用和其它服务。可以从头写代码实现服务;这种情况下,将服务和单体应用整合的 API 代码成为容灾层,在两种域模型之间进行翻译工作。每抽取一个服务,就朝着微服务方向前进一步。随着时间推移,单体应用将会越来越简单,用户就可以增加更多独立的微服务。
|
||||
将现有应用迁移成微服务架构的现代化应用,不应该通过从头重写代码方式实现,相反,应该通过逐步迁移的方式。有三种策略可以考虑:将新功能以微服务方式实现;将表现层与业务数据访问层分离;将现存模块抽取变成微服务。随着时间推移,微服务数量会增加,开发团队的弹性和效率将会大大增加。
|
BIN
images/30103116_ZCcM.png
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
images/BRP.jpg
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
images/Before-and-after-migration.png
Normal file
After Width: | Height: | Size: 95 KiB |
BIN
images/Credit-Reserved-Event.png
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
images/Event-sourcing.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
images/Homogenizer-mode.jpg
Normal file
After Width: | Height: | Size: 88 KiB |
BIN
images/Law-of-Holes.png
Normal file
After Width: | Height: | Size: 94 KiB |
BIN
images/No-2PC-required.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
images/Order-Created-Event.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
images/Private-table-of-the-corresponding-service.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
images/Sentinel-Dashboard.jpg
Normal file
After Width: | Height: | Size: 59 KiB |
BIN
images/Sentinel-VS-Hystrix.jpg
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
images/Slow-Start-Preheating-Mode.jpg
Normal file
After Width: | Height: | Size: 63 KiB |
BIN
images/Status-is-OPEN.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
images/advanced-java-doocs-shishan.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
images/doocs.png
Normal file
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |