feat: update duplicate files and generate pagination
移除冗余文件,激活页面底部导航
@ -5,7 +5,7 @@
|
||||
[![wechat-group](https://badgen.net/badge/chat/%E5%BE%AE%E4%BF%A1%E4%BA%A4%E6%B5%81/138c7b)](#公众号)
|
||||
[![reading](https://badgen.net/badge/books/read%20together/cyan)](https://github.com/doocs/technical-books)
|
||||
[![coding](https://badgen.net/badge/leetcode/coding%20together/cyan)](https://github.com/doocs/leetcode)
|
||||
[![notice](https://badgen.net/badge/notice/%E7%BB%B4%E6%9D%83%E8%A1%8C%E5%8A%A8/red)](/docs/from-readers/rights-defending-action.md)
|
||||
[![notice](https://badgen.net/badge/notice/%E7%BB%B4%E6%9D%83%E8%A1%8C%E5%8A%A8/red)](/docs/extra-page/rights-defending-action.md)
|
||||
[![stars](https://badgen.net/github/stars/doocs/advanced-java)](https://github.com/doocs/advanced-java/stargazers)
|
||||
[![contributors](https://badgen.net/github/contributors/doocs/advanced-java)](https://github.com/doocs/advanced-java/tree/master/docs/from-readers#contributors)
|
||||
[![help-wanted](https://badgen.net/github/label-issues/doocs/advanced-java/help%20wanted/open)](https://github.com/doocs/advanced-java/labels/help%20wanted)
|
||||
|
@ -54,7 +54,7 @@ end
|
||||
5. 要是锁建立失败了,那么就依次之前建立过的锁删除;
|
||||
6. 只要别人建立了一把分布式锁,你就得**不断轮询去尝试获取锁**。
|
||||
|
||||
![redis-redlock](/images/redis-redlock.png)
|
||||
![redis-redlock](./images/redis-redlock.png)
|
||||
|
||||
[Redis 官方](https://redis.io/)给出了以上两种基于 Redis 实现分布式锁的方法,详细说明可以查看:https://redis.io/topics/distlock 。
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
分布式业务系统,就是把原来用 Java 开发的一个大块系统,给拆分成**多个子系统**,多个子系统之间互相调用,形成一个大系统的整体。假设原来你做了一个 OA 系统,里面包含了权限模块、员工模块、请假模块、财务模块,一个工程,里面包含了一堆模块,模块与模块之间会互相去调用,1 台机器部署。现在如果你把这个系统给拆开,权限系统、员工系统、请假系统、财务系统 4 个系统,4 个工程,分别在 4 台机器上部署。一个请求过来,完成这个请求,这个员工系统,调用权限系统,调用请假系统,调用财务系统,4 个系统分别完成了一部分的事情,最后 4 个系统都干完了以后,才认为是这个请求已经完成了。
|
||||
|
||||
![simple-distributed-system-oa](/images/simple-distributed-system-oa.png)
|
||||
![simple-distributed-system-oa](./images/simple-distributed-system-oa.png)
|
||||
|
||||
> 近几年开始兴起和流行 Spring Cloud,刚流行,还没开始普及,目前普及的是 dubbo,因此这里也主要讲 dubbo。
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
下面我给个我们用过的方案吧,简单来说,首先你得用 dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去,接着就是在那个机器上,因为可能还是多线程并发执行的,你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去,强制排队,这样来确保他们的顺序性。
|
||||
|
||||
![distributed-system-request-sequence](/images/distributed-system-request-sequence.png)
|
||||
![distributed-system-request-sequence](./images/distributed-system-request-sequence.png)
|
||||
|
||||
但是这样引发的后续问题就很多,比如说要是某个订单对应的请求特别多,造成某台机器成**热点**怎么办?解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已,所以,还是建议什么呢?
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
如果你要操作别人的服务的库,你必须是通过**调用别的服务的接口**来实现,绝对不允许交叉访问别人的数据库。
|
||||
|
||||
![distributed-transacion-XA](/images/distributed-transaction-XA.png)
|
||||
![distributed-transacion-XA](./images/distributed-transaction-XA.png)
|
||||
|
||||
### TCC 方案
|
||||
TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||
@ -43,7 +43,7 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||
|
||||
但是说实话,一般尽量别这么搞,自己手写回滚逻辑,或者是补偿逻辑,实在太恶心了,那个业务代码是很难维护的。
|
||||
|
||||
![distributed-transacion-TCC](/images/distributed-transaction-TCC.png)
|
||||
![distributed-transacion-TCC](./images/distributed-transaction-TCC.png)
|
||||
|
||||
### 本地消息表
|
||||
本地消息表其实是国外的 ebay 搞出来的这么一套思想。
|
||||
@ -59,7 +59,7 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||
|
||||
这个方案说实话最大的问题就在于**严重依赖于数据库的消息表来管理事务**啥的,如果是高并发场景咋办呢?咋扩展呢?所以一般确实很少用。
|
||||
|
||||
![distributed-transaction-local-message-table](/images/distributed-transaction-local-message-table.png)
|
||||
![distributed-transaction-local-message-table](./images/distributed-transaction-local-message-table.png)
|
||||
|
||||
### 可靠消息最终一致性方案
|
||||
这个的意思,就是干脆不要用本地的消息表了,直接基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。
|
||||
@ -73,7 +73,7 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
|
||||
5. 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
|
||||
6. 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你就用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的。
|
||||
|
||||
![distributed-transaction-reliable-message](/images/distributed-transaction-reliable-message.png)
|
||||
![distributed-transaction-reliable-message](./images/distributed-transaction-reliable-message.png)
|
||||
|
||||
### 最大努力通知方案
|
||||
这个方案的大致意思就是:
|
||||
|
@ -29,7 +29,7 @@ MQ、ES、Redis、Dubbo,上来先问你一些**思考性的问题**、**原理
|
||||
- 第三步:consumer 调用 provider
|
||||
- 第四步:consumer 和 provider 都异步通知监控中心
|
||||
|
||||
![dubbo-operating-principle](/images/dubbo-operating-principle.png)
|
||||
![dubbo-operating-principle](./images/dubbo-operating-principle.png)
|
||||
|
||||
### 注册中心挂了可以继续通信吗?
|
||||
可以,因为刚开始初始化的时候,消费者会将提供者的地址等信息**拉取到本地缓存**,所以注册中心挂了可以继续通信。
|
@ -9,7 +9,7 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian
|
||||
## 面试题剖析
|
||||
**序列化**,就是把数据结构或者是一些对象,转换为二进制串的过程,而**反序列化**是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
|
||||
|
||||
![serialize-deserialize](/images/serialize-deserialize.png)
|
||||
![serialize-deserialize](./images/serialize-deserialize.png)
|
||||
|
||||
### dubbo 支持不同的通信协议
|
||||
- dubbo 协议
|
||||
@ -20,11 +20,11 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian
|
||||
|
||||
长连接,通俗点说,就是建立连接过后可以持续发送请求,无须再建立连接。
|
||||
|
||||
![dubbo-keep-connection](/images/dubbo-keep-connection.png)
|
||||
![dubbo-keep-connection](./images/dubbo-keep-connection.png)
|
||||
|
||||
而短连接,每次要发送请求之前,需要先重新建立一次连接。
|
||||
|
||||
![dubbo-not-keep-connection](/images/dubbo-not-keep-connection.png)
|
||||
![dubbo-not-keep-connection](./images/dubbo-not-keep-connection.png)
|
||||
|
||||
- rmi 协议
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
那就需要基于 dubbo 做的分布式系统中,对各个服务之间的调用自动记录下来,然后自动将**各个服务之间的依赖关系和调用链路生成出来**,做成一张图,显示出来,大家才可以看到对吧。
|
||||
|
||||
![dubbo-service-invoke-road](/images/dubbo-service-invoke-road.png)
|
||||
![dubbo-service-invoke-road](./images/dubbo-service-invoke-road.png)
|
||||
|
||||
#### 2. 服务访问压力以及时长统计
|
||||
需要自动统计**各个接口和服务之间的调用次数以及访问延时**,而且要分成两个级别。
|
||||
|
@ -82,7 +82,7 @@ hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
|
||||
```
|
||||
provider 启动的时候,就会加载到我们 jar 包里的`my=com.bingo.MyProtocol` 这行配置里,接着会根据你的配置使用你定义好的 MyProtocol 了,这个就是简单说明一下,你通过上述方式,可以替换掉大量的 dubbo 内部的组件,就是扔个你自己的 jar 包,然后配置一下即可。
|
||||
|
||||
![dubbo-spi](/images/dubbo-spi.png)
|
||||
![dubbo-spi](./images/dubbo-spi.png)
|
||||
|
||||
dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类`。
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@ -19,19 +19,19 @@ zookeeper 都有哪些使用场景?
|
||||
### 分布式协调
|
||||
这个其实是 zookeeper 很经典的一个用法,简单来说,就好比,你 A 系统发送个请求到 mq,然后 B 系统消息消费之后处理了。那 A 系统如何知道 B 系统的处理结果?用 zookeeper 就可以实现分布式系统之间的协调工作。A 系统发送请求之后可以在 zookeeper 上**对某个节点的值注册个监听器**,一旦 B 系统处理完了就修改 zookeeper 那个节点的值,A 系统立马就可以收到通知,完美解决。
|
||||
|
||||
![zookeeper-distributed-coordination](/images/zookeeper-distributed-coordination.png)
|
||||
![zookeeper-distributed-coordination](./images/zookeeper-distributed-coordination.png)
|
||||
|
||||
### 分布式锁
|
||||
举个栗子。对某一个数据连续发出两个修改操作,两台机器同时收到了请求,但是只能一台机器先执行完另外一个机器再执行。那么此时就可以使用 zookeeper 分布式锁,一个机器接收到了请求之后先获取 zookeeper 上的一把分布式锁,就是可以去创建一个 znode,接着执行操作;然后另外一个机器也**尝试去创建**那个 znode,结果发现自己创建不了,因为被别人创建了,那只能等着,等第一个机器执行完了自己再执行。
|
||||
|
||||
![zookeeper-distributed-lock-demo](/images/zookeeper-distributed-lock-demo.png)
|
||||
![zookeeper-distributed-lock-demo](./images/zookeeper-distributed-lock-demo.png)
|
||||
|
||||
### 元数据/配置信息管理
|
||||
zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、storm 等等很多分布式系统都会选用 zookeeper 来做一些元数据、配置信息的管理,包括 dubbo 注册中心不也支持 zookeeper 么?
|
||||
|
||||
![zookeeper-meta-data-manage](/images/zookeeper-meta-data-manage.png)
|
||||
![zookeeper-meta-data-manage](./images/zookeeper-meta-data-manage.png)
|
||||
|
||||
### HA高可用性
|
||||
这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。
|
||||
|
||||
![zookeeper-active-standby](/images/zookeeper-active-standby.png)
|
||||
![zookeeper-active-standby](./images/zookeeper-active-standby.png)
|
@ -1,4 +1,4 @@
|
||||
[![logo](images/icon.png)](https://github.com/doocs/advanced-java)
|
||||
[![logo](./images/icon.png)](https://github.com/doocs/advanced-java)
|
||||
|
||||
# 互联网 Java 工程师进阶知识完全扫盲
|
||||
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
@ -1,4 +1,4 @@
|
||||
[![where-is-my-offer](/images/where-is-my-offer.png)](https://doocs.github.io/advanced-java/#/offer)
|
||||
[![where-is-my-offer](./images/where-is-my-offer.png)](https://doocs.github.io/advanced-java/#/offer)
|
||||
|
||||
<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=1321616516&auto=1&height=66"></iframe></p>
|
||||
|
||||
@ -122,4 +122,4 @@ HR
|
||||
|
||||
```
|
||||
|
||||
[![get-up-and-study](/images/get-up-and-study.png)](https://doocs.github.io/advanced-java)
|
||||
[![get-up-and-study](./images/get-up-and-study.png)](https://doocs.github.io/advanced-java)
|
@ -1,37 +0,0 @@
|
||||
# GitHub 开发者参与专区
|
||||
[Doocs/advanced-java](https://github.com/doocs/advanced-java) 欢迎各位开发朋友们分享自己或他人的实践经验与总结。如果你想参与,请参考[提交注意事项](/docs/from-readers/doocs-advanced-java-attention.md)。感谢 [@jerryldh](https://github.com/jerryldh), [@BigBlackSheep](https://github.com/BigBlackSheep), [@sunyuanpinggithub](https://github.com/sunyuanpinggithub) 等多位朋友的反馈,具体请参考 [#46](https://github.com/doocs/advanced-java/issues/46)。
|
||||
|
||||
## 文章
|
||||
- [示例文章](/docs/from-readers/doocs-advanced-java-attention.md)
|
||||
- [示例文章](/docs/from-readers/doocs-advanced-java-attention.md)
|
||||
|
||||
## 贡献者
|
||||
感谢以下所有朋友对 [GitHub 技术社区 Doocs](https://github.com/doocs) 所做出的贡献,[参与项目维护请戳这儿](https://doocs.github.io/#/?id=how-to-join)。
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
|
||||
<a href="https://opencollective.com/doocs/contributors.svg?width=890&button=true"><img src="https://opencollective.com/doocs/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
---
|
||||
|
||||
## 公众号
|
||||
GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" style="width: 200px;">
|
||||
<a href="https://github.com/doocs">
|
||||
<img src="./images/qrcode-for-doocs.jpg" style="width: 400px;"><br>
|
||||
<sub>公众平台</sub>
|
||||
</a><br>
|
||||
</td>
|
||||
<td align="center" style="width: 200px;">
|
||||
<a href="https://github.com/yanglbme">
|
||||
<img src="./images/qrcode-for-yanglbme.jpg" style="width: 400px;"><br>
|
||||
<sub>个人微信</sub>
|
||||
</a><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
@ -1,52 +0,0 @@
|
||||
# 提交注意事项
|
||||
项目需要有一个统一的内容提交规范,没有规范的项目将会是一团乱麻,维护起来也会很费劲儿。以下列出了几个小点,看似很多,实际上都非常容易做到,供朋友们参考。
|
||||
|
||||
> 如果你有好的 idea,欢迎 issues 交流。
|
||||
|
||||
## 关于提交形式
|
||||
本项目**不希望以外链的形式引入内容**。如果你有好的内容推荐,请在此项目基础上创建新的文件,完善内容后再提交。
|
||||
|
||||
## 关于文件命名与存放位置
|
||||
文件请以 “`GitHub ID` + 文章主题” 命名,确保每位朋友的提交内容不会冲突。文章主题统一采用**英文**命名,请勿使用中文或者汉语拼音,文件类型统一选择 `.md`。
|
||||
|
||||
给个示例。某位朋友的 GitHub ID 是 [SnailClimb](https://github.com/snailclimb),想分享一篇关于 Kafka 实践相关的文章,那么文件名可以是 `snailclimb-kafka-in-action.md`。
|
||||
|
||||
最终文件存放于 `docs/from-readers/` 目录下,即与[本文件](/docs/from-readers/doocs-advanced-java-attention.md)处于同一级别。**文件命名、存放位置不规范的文章将不予采纳**。
|
||||
|
||||
## 关于文章内容
|
||||
仅收录与此项目主题相关的优质文章,可以是[高并发](https://github.com/doocs/advanced-java#高并发架构)、[分布式](https://github.com/doocs/advanced-java#分布式系统)、[高可用](https://github.com/doocs/advanced-java#高可用架构)、[微服务](https://github.com/doocs/advanced-java#高并发架构微服务架构)等相关领域的内容。**其它主题的文章将不会被采纳**。
|
||||
|
||||
## 关于文章排版
|
||||
文章排版保持整洁美观。中英文之间、中文与数字之间用空格隔开是最基本的。
|
||||
|
||||
> 有研究显示,打字的时候不喜欢在中文和英文之间加空格的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟自己不爱的人结婚,而其余三成的人最后只能把遗产留给自己的猫。毕竟爱情跟书写都需要适时地留白。
|
||||
|
||||
图片统一使用 `![](/images/xxx.png)` 进行相对路径的引用,并同时存放于根目录 `images/` 和本专区目录 `docs/from-readers/images/` **两个位置**之下(这是为了确保在 GitHub 和 GitHub Page 都能正常显示图片;图片并不限定 `.png` 格式),作图推荐使用在线工具 [ProcessOn](https://www.processon.com/i/594a16f7e4b0e1bb14fe2fac)。具体文章书写规范请参考《[中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide)》。
|
||||
|
||||
以下是文章基本的结构,供朋友们参考。
|
||||
|
||||
```markdown
|
||||
# 这是文章标题
|
||||
- Author: [GitHub ID](https://github.com/your-github-id)
|
||||
- Description: 文章的简单描述信息。
|
||||
- ...
|
||||
|
||||
## 这是一级索引
|
||||
...
|
||||
### 这是二级索引
|
||||
...
|
||||
|
||||
## 这是一级索引
|
||||
...
|
||||
### 这是二级索引
|
||||
...
|
||||
```
|
||||
|
||||
## 关于 Git 提交信息
|
||||
Git 提交信息统一使用英文,本项目遵从 [Angular JS Git 提交规范](https://github.com/angular/angular.js/commits/master)。e.g.
|
||||
|
||||
```bash
|
||||
git commit -m "docs(from-readers): add an article about Kafka"
|
||||
```
|
||||
|
||||
Git 提交信息不规范的 PR 将不予合并。
|
Before Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 70 KiB |
@ -5,7 +5,7 @@
|
||||
|
||||
Peter Steiner 说过,"[On the Internet, nobody knows you're a dog](https://en.wikipedia.org/wiki/On_the_Internet,_nobody_knows_you%27re_a_dog)",也就是说在互联网的另外一头,你都不知道甚至坐着一条狗。
|
||||
|
||||
![220px-Internet_dog.jpg](/images/220px-Internet_dog.jpg)
|
||||
![220px-Internet_dog.jpg](./images/220px-Internet_dog.jpg)
|
||||
|
||||
像特别复杂的分布式系统,特别是在大公司里,多个团队、大型协作,你可能都不知道服务是谁的,很可能说开发服务的那个哥儿们甚至是一个实习生。依赖服务的接口性能可能很不稳定,有时候 2ms,有时候 200ms,甚至 2s,都有可能。
|
||||
|
||||
|
@ -63,10 +63,10 @@ Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自
|
||||
|
||||
- **直接拒绝模式**:即超出的请求直接拒绝。
|
||||
- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
|
||||
![Slow-Start-Preheating-Mode](/images/Slow-Start-Preheating-Mode.jpg)
|
||||
![Slow-Start-Preheating-Mode](./images/Slow-Start-Preheating-Mode.jpg)
|
||||
|
||||
- **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
|
||||
![Homogenizer-mode](/images/Homogenizer-mode.jpg)
|
||||
![Homogenizer-mode](./images/Homogenizer-mode.jpg)
|
||||
|
||||
|
||||
目前 Sentinel 对异步调用链路的支持还不是很好,后续版本会着重改善支持异步调用。
|
||||
@ -74,14 +74,14 @@ Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自
|
||||
### 3. 系统负载保护
|
||||
Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
|
||||
|
||||
![BRP](/images/BRP.jpg)
|
||||
![BRP](./images/BRP.jpg)
|
||||
|
||||
### 4. 实时监控和控制面板
|
||||
Sentinel 提供 HTTP API 用于获取实时的监控信息,如调用链路统计信息、簇点信息、规则信息等。如果用户正在使用 Spring Boot/Spring Cloud 并使用了Sentinel Spring Cloud Starter,还可以方便地通过其暴露的 Actuator Endpoint 来获取运行时的一些信息,如动态规则等。未来 Sentinel 还会支持标准化的指标监控 API,可以方便地整合各种监控系统和可视化系统,如 Prometheus、Grafana 等。
|
||||
|
||||
Sentinel 控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。
|
||||
|
||||
![Sentinel-Dashboard](/images/Sentinel-Dashboard.jpg)
|
||||
![Sentinel-Dashboard](./images/Sentinel-Dashboard.jpg)
|
||||
|
||||
### 5. 生态
|
||||
Sentinel 目前已经针对 Servlet、Dubbo、Spring Boot/Spring Cloud、gRPC 等进行了适配,用户只需引入相应依赖并进行简单配置即可非常方便地享受 Sentinel 的高可用流量防护能力。未来 Sentinel 还会对更多常用框架进行适配,并且会为 Service Mesh 提供集群流量防护的能力。
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
比如说,现在有 8 个服务节点,每个服务节点使用一个 sequence 功能来产生 ID,每个 sequence 的起始 ID 不同,并且依次递增,步长都是 8。
|
||||
|
||||
![database-id-sequence-step](/images/database-id-sequence-step.png)
|
||||
![database-id-sequence-step](./images/database-id-sequence-step.png)
|
||||
|
||||
**适合的场景**:在用户防止产生的 ID 重复时,这种方案实现起来比较简单,也能达到性能目标。但是服务节点固定,步长也固定,将来如果还要增加服务节点,就不好搞了。
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。
|
||||
|
||||
![database-shard-method-1](/images/database-shard-method-1.png)
|
||||
![database-shard-method-1](./images/database-shard-method-1.png)
|
||||
|
||||
### 双写迁移方案
|
||||
这个是我们常用的一种迁移方案,比较靠谱一些,不用停机,不用看北京凌晨 4 点的风景。
|
||||
@ -33,4 +33,4 @@
|
||||
|
||||
接着当数据完全一致了,就 ok 了,基于仅仅使用分库分表的最新代码,重新部署一次,不就仅仅基于分库分表在操作了么,还没有几个小时的停机时间,很稳。所以现在基本玩儿数据迁移之类的,都是这么干的。
|
||||
|
||||
![database-shard-method-2](/images/database-shard-method-2.png)
|
||||
![database-shard-method-2](./images/database-shard-method-2.png)
|
@ -80,11 +80,11 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套
|
||||
|
||||
**水平拆分**的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。
|
||||
|
||||
![database-split-horizon](/images/database-split-horizon.png)
|
||||
![database-split-horizon](./images/database-split-horizon.png)
|
||||
|
||||
**垂直拆分**的意思,就是**把一个有很多字段的表给拆分成多个表**,**或者是多个库上去**。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会**将较少的访问频率很高的字段放到一个表里去**,然后**将较多的访问频率很低的字段放到另外一个表里去**。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。
|
||||
|
||||
![database-split-vertically](/images/database-split-vertically.png)
|
||||
![database-split-vertically](./images/database-split-vertically.png)
|
||||
|
||||
这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。
|
||||
|
||||
|
@ -27,13 +27,13 @@ index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,
|
||||
|
||||
很多情况下,一个 index 里可能就一个 type,但是确实如果说是一个 index 里有多个 type 的情况(**注意**,`mapping types` 这个概念在 ElasticSearch 7.X 已被完全移除,详细说明可以参考[官方文档](https://github.com/elastic/elasticsearch/blob/6.5/docs/reference/mapping/removal_of_types.asciidoc)),你可以认为 index 是一个类别的表,具体的每个 type 代表了 mysql 中的一个表。每个 type 有一个 mapping,如果你认为一个 type 是具体的一个表,index 就代表多个 type 同属于的一个类型,而 mapping 就是这个 type 的**表结构定义**,你在 mysql 中创建一个表,肯定是要定义表结构的,里面有哪些字段,每个字段是什么类型。实际上你往 index 里的一个 type 里面写的一条数据,叫做一条 document,一条 document 就代表了 mysql 中某个表里的一行,每个 document 有多个 field,每个 field 就代表了这个 document 中的一个字段的值。
|
||||
|
||||
![es-index-type-mapping-document-field](/images/es-index-type-mapping-document-field.png)
|
||||
![es-index-type-mapping-document-field](./images/es-index-type-mapping-document-field.png)
|
||||
|
||||
你搞一个索引,这个索引可以拆分成多个 `shard`,每个 shard 存储部分数据。拆分多个 shard 是有好处的,一是**支持横向扩展**,比如你数据量是 3T,3 个 shard,每个 shard 就 1T 的数据,若现在数据量增加到 4T,怎么扩展,很简单,重新建一个有 4 个 shard 的索引,将数据导进去;二是**提高性能**,数据分布在多个 shard,即多台服务器上,所有的操作,都会在多台机器上并行分布式执行,提高了吞吐量和性能。
|
||||
|
||||
接着就是这个 shard 的数据实际是有多个备份,就是说每个 shard 都有一个 `primary shard`,负责写入数据,但是还有几个 `replica shard`。`primary shard` 写入数据之后,会将数据同步到其他几个 `replica shard` 上去。
|
||||
|
||||
![es-cluster](/images/es-cluster.png)
|
||||
![es-cluster](./images/es-cluster.png)
|
||||
|
||||
通过这个 replica 的方案,每个 shard 的数据都有多个备份,如果某个机器宕机了,没关系啊,还有别的数据副本在别的机器上呢。高可用了吧。
|
||||
|
||||
|
@ -47,7 +47,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分
|
||||
|
||||
这么说吧,shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard,而 replica shard 一般简称为 replica。
|
||||
|
||||
![es-cluster-0](/images/es-cluster-0.png)
|
||||
![es-cluster-0](./images/es-cluster-0.png)
|
||||
|
||||
## es 核心概念 vs. db 核心概念
|
||||
| es | db |
|
||||
|
@ -12,7 +12,7 @@ es 在数据量很大的情况下(数十亿级别)如何提高查询效率
|
||||
### 性能优化的杀手锏——filesystem cache
|
||||
你往 es 里写的数据,实际上都写到磁盘文件里去了,**查询的时候**,操作系统会将磁盘文件里的数据自动缓存到 `filesystem cache` 里面去。
|
||||
|
||||
![es-search-process](/images/es-search-process.png)
|
||||
![es-search-process](./images/es-search-process.png)
|
||||
|
||||
es 的搜索引擎严重依赖于底层的 `filesystem cache`,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file ` 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。
|
||||
|
||||
|
@ -13,7 +13,7 @@ es 写入数据的工作原理是什么啊?es 查询数据的工作原理是
|
||||
- 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node`。
|
||||
- `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。
|
||||
|
||||
![es-write](/images/es-write.png)
|
||||
![es-write](./images/es-write.png)
|
||||
|
||||
### es 读数据过程
|
||||
可以通过 `doc id` 来查询,会根据 `doc id` 进行 hash,判断出来当时把 `doc id` 分配到了哪个 shard 上面去,从那个 shard 去查询。
|
||||
@ -42,7 +42,7 @@ j2ee特别牛
|
||||
|
||||
### 写数据底层原理
|
||||
|
||||
![es-write-detail](/images/es-write-detail.png)
|
||||
![es-write-detail](./images/es-write-detail.png)
|
||||
|
||||
先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。
|
||||
|
||||
|
@ -37,7 +37,7 @@
|
||||
- 读写分离
|
||||
- ElasticSearch
|
||||
|
||||
![high-concurrency-system-design](/images/high-concurrency-system-design.png)
|
||||
![high-concurrency-system-design](./images/high-concurrency-system-design.png)
|
||||
|
||||
### 系统拆分
|
||||
将一个系统拆分为多个子系统,用 dubbo 来搞。然后每个系统连一个数据库,这样本来就一个库,现在多个数据库,不也可以扛高并发么。
|
||||
|
@ -22,7 +22,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
|
||||
#### 普通集群模式(无高可用性)
|
||||
普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。
|
||||
|
||||
![mq-7](/images/mq-7.png)
|
||||
![mq-7](./images/mq-7.png)
|
||||
|
||||
这种方式确实很麻烦,也不怎么好,**没做到所谓的分布式**,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有**数据拉取的开销**,后者导致**单实例性能瓶颈**。
|
||||
|
||||
@ -33,7 +33,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模
|
||||
#### 镜像集群模式(高可用性)
|
||||
这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。
|
||||
|
||||
![mq-8](/images/mq-8.png)
|
||||
![mq-8](./images/mq-8.png)
|
||||
|
||||
那么**如何开启这个镜像集群模式**呢?其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是**镜像集群模式的策略**,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。
|
||||
|
||||
@ -50,11 +50,11 @@ Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了
|
||||
|
||||
比如说,我们假设创建了一个 topic,指定其 partition 数量是 3 个,分别在三台机器上。但是,如果第二台机器宕机了,会导致这个 topic 的 1/3 的数据就丢了,因此这个是做不到高可用的。
|
||||
|
||||
![kafka-before](/images/kafka-before.png)
|
||||
![kafka-before](./images/kafka-before.png)
|
||||
|
||||
Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机制。每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本。所有 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其他 replica 就是 follower。写的时候,leader 会负责把数据同步到所有 follower 上去,读的时候就直接读 leader 上的数据即可。只能读写 leader?很简单,**要是你可以随意读写每个 follower,那么就要 care 数据一致性的问题**,系统复杂度太高,很容易出问题。Kafka 会均匀地将一个 partition 的所有 replica 分布在不同的机器上,这样才可以提高容错性。
|
||||
|
||||
![kafka-after](/images/kafka-after.png)
|
||||
![kafka-after](./images/kafka-after.png)
|
||||
|
||||
这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。
|
||||
|
||||
|
@ -17,7 +17,7 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一
|
||||
|
||||
有这么个场景。数据 1/2/3 依次进入 kafka,kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号,我们就假设分配的 offset 依次是 152/153/154。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 `offset=153` 的这条数据,刚准备去提交 offset 到 zookeeper,此时消费者进程被重启了。那么此时消费过的数据 1/2 的 offset 并没有提交,kafka 也就不知道你已经消费了 `offset=153` 这条数据。那么重启之后,消费者会找 kafka 说,嘿,哥儿们,你给我接着把上次我消费到的那个地方后面的数据继续给我传递过来。由于之前的 offset 没有提交成功,那么数据 1/2 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。
|
||||
|
||||
![mq-10](/images/mq-10.png)
|
||||
![mq-10](./images/mq-10.png)
|
||||
|
||||
如果消费者干的事儿是拿一条数据就往数据库里写一条,会导致说,你可能就把数据 1/2 在数据库里插入了 2 次,那么数据就错啦。
|
||||
|
||||
@ -38,6 +38,6 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一
|
||||
- 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
|
||||
- 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。
|
||||
|
||||
![mq-11](/images/mq-11.png)
|
||||
![mq-11](./images/mq-11.png)
|
||||
|
||||
当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。
|
@ -14,19 +14,19 @@
|
||||
先看看顺序会错乱的俩场景:
|
||||
- **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。
|
||||
|
||||
![rabbitmq-order-01](/images/rabbitmq-order-01.png)
|
||||
![rabbitmq-order-01](./images/rabbitmq-order-01.png)
|
||||
|
||||
- **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。<br>消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。
|
||||
|
||||
![kafka-order-01](/images/kafka-order-01.png)
|
||||
![kafka-order-01](./images/kafka-order-01.png)
|
||||
|
||||
### 解决方案
|
||||
#### RabbitMQ
|
||||
拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。
|
||||
![rabbitmq-order-02](/images/rabbitmq-order-02.png)
|
||||
![rabbitmq-order-02](./images/rabbitmq-order-02.png)
|
||||
|
||||
#### Kafka
|
||||
- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。
|
||||
- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
|
||||
|
||||
![kafka-order-02](/images/kafka-order-02.png)
|
||||
![kafka-order-02](./images/kafka-order-02.png)
|
||||
|
@ -10,7 +10,7 @@
|
||||
数据的丢失问题,可能出现在生产者、MQ、消费者中,咱们从 RabbitMQ 和 Kafka 分别来分析一下吧。
|
||||
|
||||
### RabbitMQ
|
||||
![rabbitmq-message-lose](/images/rabbitmq-message-lose.png)
|
||||
![rabbitmq-message-lose](./images/rabbitmq-message-lose.png)
|
||||
|
||||
#### 生产者弄丢了数据
|
||||
|
||||
@ -61,7 +61,7 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费
|
||||
|
||||
这个时候得用 RabbitMQ 提供的 `ack` 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 `ack`,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 `ack` 一把。这样的话,如果你还没处理完,不就没有 `ack` 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。
|
||||
|
||||
![rabbitmq-message-lose-solution](/images/rabbitmq-message-lose-solution.png)
|
||||
![rabbitmq-message-lose-solution](./images/rabbitmq-message-lose-solution.png)
|
||||
|
||||
### Kafka
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
### MySQL 主从复制原理的是啥?
|
||||
主库将变更写入 binlog 日志,然后从库连接到主库之后,从库有一个 IO 线程,将主库的 binlog 日志拷贝到自己本地,写入一个 relay 中继日志中。接着从库中有一个 SQL 线程会从中继日志读取 binlog,然后执行 binlog 日志中的内容,也就是在自己本地再次执行一遍 SQL,这样就可以保证自己跟主库的数据是一样的。
|
||||
|
||||
![mysql-master-slave](/images/mysql-master-slave.png)
|
||||
![mysql-master-slave](./images/mysql-master-slave.png)
|
||||
|
||||
这里有一个非常重要的一点,就是从库同步主库数据的过程是串行化的,也就是说主库上并行的操作,在从库上会串行执行。所以这就是一个非常重要的点了,由于从库从主库拷贝日志以及串行执行 SQL 的特点,在高并发场景下,从库的数据一定会比主库慢一些,是**有延时**的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。
|
||||
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
这就是缓存雪崩。
|
||||
|
||||
![redis-caching-avalanche](/images/redis-caching-avalanche.png)
|
||||
![redis-caching-avalanche](./images/redis-caching-avalanche.png)
|
||||
|
||||
大约在 3 年前,国内比较知名的一个互联网公司,曾因为缓存事故,导致雪崩,后台系统全部崩溃,事故从当天下午持续到晚上凌晨 3~4 点,公司损失了几千万。
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
|
||||
- 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
|
||||
|
||||
![redis-caching-avalanche-solution](/images/redis-caching-avalanche-solution.png)
|
||||
![redis-caching-avalanche-solution](./images/redis-caching-avalanche-solution.png)
|
||||
|
||||
用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
|
||||
举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“**视缓存于无物**”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
|
||||
|
||||
![redis-caching-penetration](/images/redis-caching-penetration.png)
|
||||
![redis-caching-penetration](./images/redis-caching-penetration.png)
|
||||
|
||||
解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN`。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。
|
||||
|
||||
|
@ -9,7 +9,7 @@ redis 的并发竞争问题是什么?如何解决这个问题?了解 redis
|
||||
## 面试题剖析
|
||||
某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。
|
||||
|
||||
![zookeeper-distributed-lock](/images/zookeeper-distributed-lock.png)
|
||||
![zookeeper-distributed-lock](./images/zookeeper-distributed-lock.png)
|
||||
|
||||
你要写入缓存的数据,都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也查出来。
|
||||
|
||||
|
@ -27,11 +27,11 @@ redis cluster,主要是针对**海量数据+高并发+高可用**的场景。r
|
||||
|
||||
**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm`。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。
|
||||
|
||||
![zookeeper-centralized-storage](/images/zookeeper-centralized-storage.png)
|
||||
![zookeeper-centralized-storage](./images/zookeeper-centralized-storage.png)
|
||||
|
||||
redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。
|
||||
|
||||
![redis-gossip](/images/redis-gossip.png)
|
||||
![redis-gossip](./images/redis-gossip.png)
|
||||
|
||||
**集中式**的**好处**在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;**不好**在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。
|
||||
|
||||
@ -71,7 +71,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担
|
||||
#### hash 算法
|
||||
来了一个 key,首先计算 hash 值,然后对节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致**大部分的请求过来,全部无法拿到有效的缓存**,导致大量的流量涌入数据库。
|
||||
|
||||
![hash](/images/hash.png)
|
||||
![hash](./images/hash.png)
|
||||
|
||||
#### 一致性 hash 算法
|
||||
一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。
|
||||
@ -82,7 +82,7 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担
|
||||
|
||||
燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成**缓存热点**的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。
|
||||
|
||||
![consistent-hashing-algorithm](/images/consistent-hashing-algorithm.png)
|
||||
![consistent-hashing-algorithm](./images/consistent-hashing-algorithm.png)
|
||||
|
||||
#### redis cluster 的 hash slot 算法
|
||||
redis cluster 有固定的 `16384` 个 hash slot,对每个 `key` 计算 `CRC16` 值,然后对 `16384` 取模,可以获取 key 对应的 hash slot。
|
||||
@ -91,7 +91,7 @@ redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master
|
||||
|
||||
任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。
|
||||
|
||||
![hash-slot](/images/hash-slot.png)
|
||||
![hash-slot](./images/hash-slot.png)
|
||||
|
||||
### redis cluster 的高可用与主备切换原理
|
||||
redis cluster 的高可用的原理,几乎跟哨兵是类似的。
|
||||
|
@ -29,7 +29,7 @@
|
||||
### 最初级的缓存不一致问题及解决方案
|
||||
问题:先更新数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
|
||||
|
||||
![redis-junior-inconsistent](/images/redis-junior-inconsistent.png)
|
||||
![redis-junior-inconsistent](./images/redis-junior-inconsistent.png)
|
||||
|
||||
解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
单机的 redis,能够承载的 QPS 大概就在上万到几万不等。对于缓存来说,一般都是用来支撑**读高并发**的。因此架构做成主从(master-slave)架构,一主多从,主负责写,并且将数据复制到其它的 slave 节点,从节点负责读。所有的**读请求全部走从节点**。这样也可以很轻松实现水平扩容,**支撑读高并发**。
|
||||
|
||||
![redis-master-slave](/images/redis-master-slave.png)
|
||||
![redis-master-slave](./images/redis-master-slave.png)
|
||||
|
||||
redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并发
|
||||
|
||||
@ -23,7 +23,7 @@ redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并
|
||||
|
||||
如果这是 slave node 初次连接到 master node,那么会触发一次 `full resynchronization` 全量复制。此时 master 会启动一个后台线程,开始生成一份 `RDB` 快照文件,同时还会将从客户端 client 新收到的所有写命令缓存在内存中。`RDB` 文件生成完毕后, master 会将这个 `RDB` 发送给 slave,slave 会先**写入本地磁盘,然后再从本地磁盘加载到内存**中,接着 master 会将内存中缓存的写命令发送到 slave,slave 也会同步这些数据。slave node 如果跟 master node 有网络故障,断开了连接,会自动重连,连接之后 master node 仅会复制给 slave 部分缺少的数据。
|
||||
|
||||
![redis-master-slave-replication](/images/redis-master-slave-replication.png)
|
||||
![redis-master-slave-replication](./images/redis-master-slave-replication.png)
|
||||
|
||||
### 主从复制的断点续传
|
||||
从 redis2.8 开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
|
||||
@ -49,7 +49,7 @@ slave node 启动时,会在自己本地保存 master node 的信息,包括 m
|
||||
|
||||
slave node 内部有个定时任务,每秒检查是否有新的 master node 要连接和复制,如果发现,就跟 master node 建立 socket 网络连接。然后 slave node 发送 `ping` 命令给 master node。如果 master 设置了 requirepass,那么 slave node 必须发送 masterauth 的口令过去进行认证。master node **第一次执行全量复制**,将所有数据发给 slave node。而在后续,master node 持续将写命令,异步复制给 slave node。
|
||||
|
||||
![redis-master-slave-replication-detail](/images/redis-master-slave-replication-detail.png)
|
||||
![redis-master-slave-replication-detail](./images/redis-master-slave-replication-detail.png)
|
||||
|
||||
### 全量复制
|
||||
- master 执行 bgsave ,在本地生成一份 rdb 快照文件。
|
||||
|
@ -63,7 +63,7 @@ sentinel,中文名是哨兵。哨兵是 redis 集群架构中非常重要的
|
||||
|
||||
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。
|
||||
|
||||
![async-replication-data-lose-case](/images/async-replication-data-lose-case.png)
|
||||
![async-replication-data-lose-case](./images/async-replication-data-lose-case.png)
|
||||
|
||||
- 脑裂导致的数据丢失
|
||||
|
||||
@ -71,7 +71,7 @@ sentinel,中文名是哨兵。哨兵是 redis 集群架构中非常重要的
|
||||
|
||||
此时虽然某个 slave 被切换成了 master,但是可能 client 还没来得及切换到新的 master,还继续向旧 master 写数据。因此旧 master 再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 复制数据。而新的 master 并没有后来 client 写入的数据,因此,这部分数据也就丢失了。
|
||||
|
||||
![redis-cluster-split-brain](/images/redis-cluster-split-brain.png)
|
||||
![redis-cluster-split-brain](./images/redis-cluster-split-brain.png)
|
||||
|
||||
### 数据丢失问题的解决方案
|
||||
进行如下配置:
|
||||
|
@ -33,7 +33,7 @@ redis 内部使用文件事件处理器 `file event handler`,这个文件事
|
||||
|
||||
来看客户端与 redis 的一次通信过程:
|
||||
|
||||
![redis-single-thread-model](/images/redis-single-thread-model.png)
|
||||
![redis-single-thread-model](./images/redis-single-thread-model.png)
|
||||
|
||||
要明白,通信是通过 socket 来完成的,不懂的同学可以先去看一看 socket 网络编程。
|
||||
|
||||
|
@ -28,13 +28,13 @@
|
||||
#### 解耦
|
||||
看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃......
|
||||
|
||||
![mq-1](/images/mq-1.png)
|
||||
![mq-1](./images/mq-1.png)
|
||||
|
||||
在这个场景中,A 系统跟其它各种乱七八糟的系统严重耦合,A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?头发都白了啊!
|
||||
|
||||
如果使用 MQ,A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。
|
||||
|
||||
![mq-2](/images/mq-2.png)
|
||||
![mq-2](./images/mq-2.png)
|
||||
|
||||
**总结**:通过一个 MQ,Pub/Sub 发布订阅消息这么一个模型,A 系统就跟其它系统彻底解耦了。
|
||||
|
||||
@ -43,13 +43,13 @@
|
||||
#### 异步
|
||||
再来看一个场景,A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求,等待个 1s,这几乎是不可接受的。
|
||||
|
||||
![mq-3](/images/mq-3.png)
|
||||
![mq-3](./images/mq-3.png)
|
||||
|
||||
一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。
|
||||
|
||||
如果**使用 MQ**,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms,对于用户而言,其实感觉上就是点个按钮,8ms 以后就直接返回了,爽!网站做得真好,真快!
|
||||
|
||||
![mq-4](/images/mq-4.png)
|
||||
![mq-4](./images/mq-4.png)
|
||||
|
||||
#### 削峰
|
||||
每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到 12:00 ~ 13:00 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。
|
||||
@ -58,11 +58,11 @@
|
||||
|
||||
但是高峰期一过,到了下午的时候,就成了低峰期,可能也就 1w 的用户同时在网站上操作,每秒中的请求数量可能也就 50 个请求,对整个系统几乎没有任何的压力。
|
||||
|
||||
![mq-5](/images/mq-5.png)
|
||||
![mq-5](./images/mq-5.png)
|
||||
|
||||
如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。
|
||||
|
||||
![mq-6](/images/mq-6.png)
|
||||
![mq-6](./images/mq-6.png)
|
||||
|
||||
这个短暂的高峰期积压是 ok 的,因为高峰期过了之后,每秒钟就 50 个请求进 MQ,但是 A 系统依然会按照每秒 2k 个请求的速度在处理。所以说,只要高峰期一过,A 系统就会快速将积压的消息给解决掉。
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
下图展示的是这种架构:
|
||||
|
||||
![deployment-strategy-1](/images/deployment-strategy-1.png)
|
||||
![deployment-strategy-1](./images/deployment-strategy-1.png)
|
||||
|
||||
这种模式有一些参数,一个参数代表每个服务实例由多少进程构成。例如,需要在 Apache Tomcat Server 上部署一个 Java 服务实例作为 web 应用。一个 Node.js 服务实例可能有一个父进程和若干个子进程构成。
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
|
||||
但是用单虚拟机单实例模式,一般将服务打包成虚拟机映像(image),例如一个 Amazon EC2 AMI。每个服务实例是一个使用此映像启动的 VM(例如,EC2 实例)。下图展示了此架构:
|
||||
|
||||
![deployment-strategy-2](/images/deployment-strategy-2.png)
|
||||
![deployment-strategy-2](./images/deployment-strategy-2.png)
|
||||
|
||||
Netfix 采用这种架构部署 video streaming service。Netfix 使用 Aminator 将每个服务打包成一个 EC2 AMI。每个运行服务实例就是一个 EC2 实例。
|
||||
|
||||
@ -72,7 +72,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用
|
||||
|
||||
下图展示了这种模式:
|
||||
|
||||
![deployment-strategy-3](/images/deployment-strategy-3.png)
|
||||
![deployment-strategy-3](./images/deployment-strategy-3.png)
|
||||
|
||||
使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。
|
||||
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
相反的,微服务架构下,订单和客户表分别是相对应服务的私有表,如下图所示:
|
||||
|
||||
![service table](/images/Private-table-of-the-corresponding-service.png)
|
||||
![service table](./images/Private-table-of-the-corresponding-service.png)
|
||||
|
||||
订单服务不能直接访问客户表,只能通过客户服务发布的 API 来访问。订单服务也可以使用 distributed transactions, 也就是周知的两阶段提交 (2PC)。然而,2PC 在现在应用中不是可选性。根据 CAP 理论,必须在可用性(availability)和 ACID 一致性(consistency)之间做出选择,availability 一般是更好的选择。但是,许多现代科技,例如许多 NoSQL 数据库,并不支持 2PC。在服务和数据库之间维护数据一致性是非常根本的需求,因此我们需要找其他的方案。
|
||||
|
||||
@ -35,15 +35,15 @@
|
||||
|
||||
1. 订单服务创建一个带有 NEW 状态的 Order (订单),发布了一个 “Order Created Event(创建订单)” 的事件。
|
||||
|
||||
![Order-Created-Event](/images/Order-Created-Event.png)
|
||||
![Order-Created-Event](./images/Order-Created-Event.png)
|
||||
|
||||
2. 客户服务消费 Order Created Event 事件,为此订单预留信用,发布 “Credit Reserved Event(信用预留)” 事件。
|
||||
|
||||
![Credit-Reserved-Event](/images/Credit-Reserved-Event.png)
|
||||
![Credit-Reserved-Event](./images/Credit-Reserved-Event.png)
|
||||
|
||||
3. 订单服务消费 Credit Reserved Event ,改变订单的状态为 OPEN。
|
||||
|
||||
![Status-is-OPEN](/images/Status-is-OPEN.png)
|
||||
![Status-is-OPEN](./images/Status-is-OPEN.png)
|
||||
|
||||
更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
|
||||
亦可以使用事件来维护不同微服务拥有数据预连接(pre-join)的实现视图。维护此视图的服务订阅相关事件并且更新视图。例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件。
|
||||
|
||||
![pre-join](/images/pre-join.png)
|
||||
![pre-join](./images/pre-join.png)
|
||||
|
||||
当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。可以使用文档数据库(例如 MongoDB)来实现客户订单视图,为每个用户存储一个文档。客户订单视图查询服务负责响应对客户以及最近订单(通过查询客户订单视图数据集)的查询。
|
||||
|
||||
@ -65,7 +65,7 @@
|
||||
|
||||
获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions,技巧在于一个 EVENT 表,此表在存储业务实体数据库中起到消息列表功能。应用发起一个(本地)数据库交易,更新业务实体状态,向 EVENT 表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此 EVENT 表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示:
|
||||
|
||||
![multi-step process](/images/multi-step-process.png)
|
||||
![multi-step process](./images/multi-step-process.png)
|
||||
|
||||
订单服务向 ORDER 表插入一行,然后向 EVENT 表中插入 Order Created event,事件发布线程或者进程查询 EVENT 表,请求未发布事件,发布他们,然后更新 EVENT 表标志此事件为已发布。
|
||||
|
||||
@ -77,7 +77,7 @@
|
||||
|
||||
另外一种不需要 2PC 而获得线程或者进程发布事件原子性的方式就是挖掘数据库交易或者提交日志。应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见:
|
||||
|
||||
![No-2PC-required](/images/No-2PC-required.png)
|
||||
![No-2PC-required](./images/No-2PC-required.png)
|
||||
|
||||
此方法的例子如 LinkedIn Databus 项目,Databus 挖掘 Oracle 交易日志,根据变化发布事件,LinkedIn 使用 Databus 来保证系统内各记录之间的一致性。
|
||||
|
||||
@ -93,7 +93,7 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来
|
||||
|
||||
为了理解事件源工作方式,考虑事件实体作为一个例子。传统方式中,每个订单映射为 ORDER 表中一行,例如在 ORDER_LINE_ITEM 表中。但是对于事件源方式,订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够数据来重建订单状态。
|
||||
|
||||
![Event-sourcing](/images/Event-sourcing.png)
|
||||
![Event-sourcing](./images/Event-sourcing.png)
|
||||
|
||||
事件是长期保存在事件数据库中,提供 API 添加和获取实体事件。事件存储跟之前描述的消息代理类似,提供 API 来订阅事件。事件存储将事件递送到所有感兴趣的订阅者,事件存储是事件驱动微服务架构的基干。
|
||||
|
||||
|
@ -20,11 +20,11 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
## 服务注册中心(Eureka Server)
|
||||
我们在项目中引入 `Eureka Server` 的相关依赖,然后在启动类加上注解 `@EnableEurekaServer`,就可以将其作为注册中心,启动服务后访问页面如下:
|
||||
|
||||
![eureka-server-homepage.png](/images/eureka-server-homepage.png)
|
||||
![eureka-server-homepage.png](./images/eureka-server-homepage.png)
|
||||
|
||||
我们继续添加两个模块 `service-provider`,`service-consumer`,然后在启动类加上注解 `@EnableEurekaClient` 并指定注册中心地址为我们刚刚启动的 `Eureka Server`,再次访问可以看到两个服务都已经注册进来了。
|
||||
|
||||
![eureka-instance-registered-currently.png](/images/eureka-instance-registered-currently.png)
|
||||
![eureka-instance-registered-currently.png](./images/eureka-instance-registered-currently.png)
|
||||
|
||||
`Demo` 仓库地址:https://github.com/mghio/depth-in-springcloud
|
||||
|
||||
@ -33,7 +33,7 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
### 服务注册(Register)
|
||||
注册中心提供了服务注册接口,用于当有新的服务启动后进行调用来实现服务注册,或者心跳检测到服务状态异常时,变更对应服务的状态。服务注册就是发送一个 `POST` 请求带上当前实例信息到类 `ApplicationResource` 的 `addInstance` 方法进行服务注册。
|
||||
|
||||
![eureka-server-applicationresource-addinstance.png](/images/eureka-server-applicationresource-addinstance.png)
|
||||
![eureka-server-applicationresource-addinstance.png](./images/eureka-server-applicationresource-addinstance.png)
|
||||
|
||||
可以看到方法调用了类 `PeerAwareInstanceRegistryImpl` 的 `register` 方法,该方法主要分为两步:
|
||||
1. 调用父类 `AbstractInstanceRegistry` 的 `register` 方法把当前服务注册到注册中心
|
||||
@ -41,34 +41,34 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
|
||||
服务注册信息保存在一个嵌套的 `map` 中,它的结构如下:
|
||||
|
||||
![eureka-server-registry-structure.png](/images/eureka-server-registry-structure.png)
|
||||
![eureka-server-registry-structure.png](./images/eureka-server-registry-structure.png)
|
||||
|
||||
第一层 `map` 的 `key` 是应用名称(对应 `Demo` 里的 `SERVICE-PROVIDER`),第二层 `map` 的 `key` 是应用对应的实例名称(对应 `Demo` 里的 `mghio-mbp:service-provider:9999`),一个应用可以有多个实例,主要调用流程如下图所示:
|
||||
|
||||
![eureka-server-register-sequence-chart.png](/images/eureka-server-register-sequence-chart.png)
|
||||
![eureka-server-register-sequence-chart.png](./images/eureka-server-register-sequence-chart.png)
|
||||
|
||||
### 服务续约(Renew)
|
||||
服务续约会由服务提供者(比如 `Demo` 中的 `service-provider`)定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务时效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。
|
||||
|
||||
![eureka-server-instanceresource-renew.png](/images/eureka-server-instanceresource-renew.png)
|
||||
![eureka-server-instanceresource-renew.png](./images/eureka-server-instanceresource-renew.png)
|
||||
|
||||
进入到 `PeerAwareInstanceRegistryImpl` 的 `renew` 方法可以看到,服务续约步骤大体上和服务注册一致,先更新当前 `Eureka Server` 节点的状态,服务续约成功后再用异步的方式同步状态到其它 `Eureka Server` 节上,主要调用流程如下图所示:
|
||||
|
||||
![eureka-server-renew-sequence-chart.png](/images/eureka-server-renew-sequence-chart.png)
|
||||
![eureka-server-renew-sequence-chart.png](./images/eureka-server-renew-sequence-chart.png)
|
||||
|
||||
### 服务下线(Cancel)
|
||||
当服务提供者(比如 `Demo` 中的 `service-provider`)停止服务时,会发送请求告知注册中心 `Eureka Server` 进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个 `DELETE` 请求带上当前实例信息到类 `InstanceResource` 的 `cancelLease` 方法进行服务剔除下线操作。
|
||||
|
||||
![eureka-server-instanceresource-cancellease.png](/images/eureka-server-instanceresource-cancellease.png)
|
||||
![eureka-server-instanceresource-cancellease.png](./images/eureka-server-instanceresource-cancellease.png)
|
||||
|
||||
进入到 `PeerAwareInstanceRegistryImpl` 的 `cancel` 方法可以看到,服务续约步骤大体上和服务注册一致,先在当前 `Eureka Server` 节点剔除下线该服务,服务下线成功后再用异步的方式同步状态到其它 `Eureka Server` 节上,主要调用流程如下图所示:
|
||||
|
||||
![eureka-server-cancellease-sequence-chart.png](/images/eureka-server-cancellease-sequence-chart.png)
|
||||
![eureka-server-cancellease-sequence-chart.png](./images/eureka-server-cancellease-sequence-chart.png)
|
||||
|
||||
### 服务剔除(Eviction)
|
||||
服务剔除是注册中心 `Eureka Server` 在启动时就启动一个守护线程 `evictionTimer` 来定期(默认为 `60` 秒)执行检测服务的,判断标准就是超过一定时间没有进行 `Renew` 的服务,默认的失效时间是 `90` 秒,也就是说当一个已注册的服务在 `90` 秒内没有向注册中心 `Eureka Server` 进行服务续约(Renew),就会被从注册中心剔除下线。失效时间可以通过配置 `eureka.instance.leaseExpirationDurationInSeconds` 进行修改,定期执行检测服务可以通过配置 `eureka.server.evictionIntervalTimerInMs` 进行修改,主要调用流程如下图所示:
|
||||
|
||||
![eureka-server-evict-sequence-chart.png](/images/eureka-server-evict-sequence-chart.png)
|
||||
![eureka-server-evict-sequence-chart.png](./images/eureka-server-evict-sequence-chart.png)
|
||||
|
||||
|
||||
## 服务提供者(Service Provider)
|
||||
@ -77,17 +77,17 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
### 服务注册(Register)
|
||||
一个服务要对外提供服务,首先要在注册中心 `Eureka Server` 进行服务相关信息注册,能进行这一步的前提是你要配置 `eureka.client.register-with-eureka=true`,这个默认值为 `true`,注册中心不需要把自己注册到注册中心去,把这个配置设为 `false`,这个调用比较简单,主要调用流程如下图所示:
|
||||
|
||||
![eureka-service-provider-register-sequence-chart.png](/images/eureka-server-register-sequence-chart.png)
|
||||
![eureka-service-provider-register-sequence-chart.png](./images/eureka-server-register-sequence-chart.png)
|
||||
|
||||
### 服务续约(Renew)
|
||||
服务续约是由服务提供者方定期(默认为 `30` 秒)发起心跳的,主要是用来告知注册中心 `Eureka Server` 自己状态是正常的还活着,可以通过配置 `eureka.instance.lease-renewal-interval-in-seconds` 来修改,当然服务续约的前提是要配置 `eureka.client.register-with-eureka=true`,将该服务注册到注册中心中去,主要调用流程如下图所示:
|
||||
|
||||
![eureka-service-provider-renew-sequence-chart.png](/images/eureka-service-provider-renew-sequence-chart.png)
|
||||
![eureka-service-provider-renew-sequence-chart.png](./images/eureka-service-provider-renew-sequence-chart.png)
|
||||
|
||||
### 服务下线(Cancel)
|
||||
当服务提供者方服务停止时,要发送 `DELETE` 请求告知注册中心 `Eureka Server` 自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类 `DiscoveryClient` 的 `shutdown` 方法加上注解 `@PreDestroy`,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示:
|
||||
|
||||
![eureka-service-provider-cancel-sequence-chart.png](/images/eureka-service-provider-cancel-sequence-chart.png)
|
||||
![eureka-service-provider-cancel-sequence-chart.png](./images/eureka-service-provider-cancel-sequence-chart.png)
|
||||
|
||||
|
||||
## 服务消费者(Service Consumer)
|
||||
@ -96,16 +96,16 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
|
||||
### 获取服务列表(Fetch)
|
||||
服务消费者方启动之后首先肯定是要先从注册中心 `Eureka Server` 获取到可用的服务列表同时本地也会缓存一份。这个获取服务列表的操作是在服务启动后 `DiscoverClient` 类实例化的时候执行的。
|
||||
|
||||
![eureka-service-consumer-fetchregistry.png](/images/eureka-service-consumer-fetchregistry.png)
|
||||
![eureka-service-consumer-fetchregistry.png](./images/eureka-service-consumer-fetchregistry.png)
|
||||
|
||||
可以看出,能发生这个获取服务列表的操作前提是要保证配置了 `eureka.client.fetch-registry=true`,该配置的默认值为 `true`,主要调用流程如下图所示:
|
||||
|
||||
![eureka-service-consumer-fetch-sequence-chart.png](/images/eureka-service-consumer-fetch-sequence-chart.png)
|
||||
![eureka-service-consumer-fetch-sequence-chart.png](./images/eureka-service-consumer-fetch-sequence-chart.png)
|
||||
|
||||
### 更新服务列表(Update)
|
||||
由上面的 `获取服务列表(Fetch)` 操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心 `Eureka Server` 获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置 `eureka.client.registry-fetch-interval-seconds` 修改,默认为 `30` 秒,能进行这一步更新服务列表的前提是你要配置 `eureka.client.register-with-eureka=true`,这个默认值为 `true`。主要调用流程如下图所示:
|
||||
|
||||
![eureka-service-consumer-update-sequence-chart.png](/images/eureka-service-consumer-update-sequence-chart.png)
|
||||
![eureka-service-consumer-update-sequence-chart.png](./images/eureka-service-consumer-update-sequence-chart.png)
|
||||
|
||||
|
||||
## 总结
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
单体应用程序可以取得成功,但越来越多的人对它们感到不满——尤其是在将更多应用程序部署到云的时候。变更周期被捆绑在一起——即使只是对应用程序的一小部分进行了更改,也需要重建和部署整个单体应用。随着时间的推移,通常很难保持良好的模块化结构,也更难以保持应该只影响该模块中的一个模块的更改。对系统进行扩展时,不得不扩展整个应用系统,而不能仅扩展该系统中需要更多资源的那些部分。
|
||||
|
||||
![sketch](/images/sketch.png)
|
||||
![sketch](./images/sketch.png)
|
||||
|
||||
这些不满催生了微服务架构风格:将应用程序构建为服务套件。除了服务可独立部署、独立扩展的事实之外,每个服务还提供了一个牢固的模块边界,甚至允许以不同的编程语言编写不同的服务。他们也可以由不同的团队管理。
|
||||
|
||||
@ -42,11 +42,11 @@
|
||||
|
||||
> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。<br> —— 梅尔文•康威,1967年
|
||||
|
||||
![conways-law](/images/conways-law.png)
|
||||
![conways-law](./images/conways-law.png)
|
||||
|
||||
微服务采用不同的划分方式,它是围绕业务功能将系统拆分为多个服务 。这些服务为该业务领域采用广泛的软件实现,包括用户界面、持久化存储和任何外部协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验、数据库和项目管理。
|
||||
|
||||
![PreferFunctionalStaffOrganization](/images/PreferFunctionalStaffOrganization.png)
|
||||
![PreferFunctionalStaffOrganization](./images/PreferFunctionalStaffOrganization.png)
|
||||
|
||||
以这种方式组建的一家公司是 [www.comparethemarket.com](http://www.comparethemarket.com/)。跨职能团队负责构建和运营每个产品,每个产品拆分为多个独立的服务,彼此通过消息总线来通信。
|
||||
|
||||
@ -98,7 +98,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
|
||||
|
||||
和概念模型的去中心化决策一样,微服务也去中心化数据存储决策。虽然单体应用程序更喜欢单一的逻辑数据库做持久化存储,但企业往往倾向于一系列应用程序共用一个单一的数据库——这些决定是供应商授权许可的商业模式驱动的。微服务更倾向于让每个服务管理自己的数据库,或者同一数据库技术的不同实例,或完全不同的数据库系统 - 这就是所谓的[混合持久化](https://martinfowler.com/bliki/PolyglotPersistence.html)(Polyglot Persistence)。你可以在单体应用程序中使用混合持久化,但它更常出现在为服务里。
|
||||
|
||||
![decentralised-data](/images/decentralised-data.png)
|
||||
![decentralised-data](./images/decentralised-data.png)
|
||||
|
||||
对跨微服务的数据来说,去中心化责任对管理升级有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这个方法通常用在单体中。
|
||||
|
||||
@ -111,7 +111,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
|
||||
|
||||
许多使用微服务构建的产品或系统都是由具有丰富的持续交付和持续集成经验的团队构建的。以这种方式构建软件的团队广泛使用基础设施自动化技术。如下面显示的构建管道所示。
|
||||
|
||||
![basic-pipeline](/images/basic-pipeline.png)
|
||||
![basic-pipeline](./images/basic-pipeline.png)
|
||||
|
||||
由于这并不是一篇关于持续交付的文章,我们在这里只关注持续交付的几个关键特性。我们希望有尽可能多的信心确保我们的软件正常运行,因此我们进行了大量的**自动化测试**。想让软件达到“晋级”(Promotion)状态从而“推上”流水线,就意味着要在每一个新的环境中,对软件进行**自动化部署**。
|
||||
|
||||
@ -119,7 +119,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
|
||||
|
||||
我们看到团队大量的基础设施自动化的另一个领域是在管理生产环境中的微服务。与我们上面的断言(只要部署很无聊)相比,单体和微服务之间没有太大的区别,但是每个部署的运行环境可能会截然不同。
|
||||
|
||||
![micro-deployment](/images/micro-deployment.png)
|
||||
![micro-deployment](./images/micro-deployment.png)
|
||||
|
||||
### 设计时为故障做好准备
|
||||
使用服务作为组件的结果是,需要设计应用程序以便它们能够容忍服务的失败。如果服务提供者商不可用,任何服务呼叫都可能失败,客户必须尽可能优雅地对此做出响应。与单体设计相比,这是一个缺点,因为它这会引入额外的复杂性来处理它。结果是微服务团队不断反思服务失败是如何影响用户体验的。Netflix 的 [Simian Army](https://github.com/Netflix/SimianArmy) 能够引发服务甚至数据中心的故障在工作日发生故障,从而来测试应用程序的弹性和监控能力。
|
||||
|
@ -17,7 +17,7 @@ Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字
|
||||
|
||||
Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用不可管理时这是最佳建议。换句话说,应该停止让单体式应用继续变大,也就是说当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。如下图所示:
|
||||
|
||||
![1](/images/Law-of-Holes.png)
|
||||
![1](./images/Law-of-Holes.png)
|
||||
|
||||
除了新服务和传统应用,还有两个模块,其一是请求路由器,负责处理入口(http)请求,有点像之前提到的 API 网关。路由器将新功能请求发送给新开发的服务,而将传统请求还发给单体式应用。
|
||||
|
||||
@ -46,7 +46,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
|
||||
3. 数据访问层——访问基础元素,例如数据库和消息代理。
|
||||
|
||||
在表现层与业务数据访问层之间有清晰的隔离。业务层有由若干方面组成的粗粒度(coarse-grained)的 API,内部包含了业务逻辑元素。API 是可以将单体业务分割成两个更小应用的天然边界,其中一个应用是表现层,另外一个是业务和数据访问逻辑。分割后,表现逻辑应用远程调用业务逻辑应用,下图表示迁移前后架构不同:
|
||||
![2](/images/Before-and-after-migration.png)
|
||||
![2](./images/Before-and-after-migration.png)
|
||||
|
||||
单体应用这么分割有两个好处,其一使得应用两部分开发、部署和扩展各自独立,特别地,允许表现层开发者在用户界面上快速选择,进行 A/B 测试;其二,使得一些远程 API 可以被微服务调用。
|
||||
|
||||
@ -72,7 +72,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
|
||||
|
||||
一旦完成粗粒度接口,也就将此模块转换成独立微服务。为了实现,必须写代码使得单体应用和微服务之间通过使用进程间通信(IPC)机制的 API 来交换信息。如图所示迁移前后对比:
|
||||
|
||||
![3](/images/30103116_ZCcM.png)
|
||||
![3](./images/30103116_ZCcM.png)
|
||||
|
||||
此例中,正在使用 Y 模块的 Z 模块是备选抽取模块,其元素正在被 X 模块使用,迁移第一步就是定义一套粗粒度 APIs,第一个接口应该是被 X 模块使用的内部接口,用于激活 Z 模块;第二个接口是被 Z 模块使用的外部接口,用于激活 Y 模块。
|
||||
|
||||
|
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 249 KiB |
BIN
images/BRP.jpg
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 380 KiB |
Before Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 40 KiB |
BIN
images/doocs.png
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 50 KiB |