diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 8c2f118c5..000000000 --- a/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -*.html linguist-language=java \ No newline at end of file diff --git a/.github/workflows/compress.yml b/.github/workflows/compress.yml new file mode 100644 index 000000000..c7da47c47 --- /dev/null +++ b/.github/workflows/compress.yml @@ -0,0 +1,36 @@ +name: Compress + +on: + schedule: + - cron: "0 0 * * 3" + workflow_dispatch: + +jobs: + compress: + runs-on: ubuntu-latest + if: github.repository == 'doocs/advanced-java' + steps: + - name: Checkout Branch + uses: actions/checkout@v3 + + - name: Compress Images + id: calibre + uses: calibreapp/image-actions@main + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + compressOnly: true + + - name: Commit Files + if: | + steps.calibre.outputs.markdown != '' + run: | + git config --local user.email "szuyanglb@outlook.com" + git config --local user.name "yanglbme" + git commit -m "chore: auto compress images" -a + + - name: Push Changes + if: | + steps.calibre.outputs.markdown != '' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..b1e38166b --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,48 @@ +name: Build and deploy + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build with VitePress + run: npm run docs:build + + - name: Generate CNAME + run: echo "java.doocs.org" > docs/.vitepress/dist/CNAME + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + needs: build + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github_pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/starcharts.yml b/.github/workflows/starcharts.yml new file mode 100644 index 000000000..83d69772e --- /dev/null +++ b/.github/workflows/starcharts.yml @@ -0,0 +1,18 @@ +name: Starcharts + +on: + schedule: + - cron: "0 0/12 * * *" + workflow_dispatch: + +jobs: + update-readme: + name: Generate starcharts + runs-on: ubuntu-latest + steps: + - uses: MaoLongLong/actions-starcharts@main + with: + github_token: ${{ secrets.ACTION_TOKEN }} + svg_path: images/starcharts.svg + commit_message: "chore: auto update starcharts" + stars_change: ${{ secrets.STARS_CHANGE }} diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml deleted file mode 100644 index 7097cc9d6..000000000 --- a/.github/workflows/sync.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Sync - -on: - push: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Sync to Gitee - uses: wearerequired/git-mirror-action@master - env: - SSH_PRIVATE_KEY: ${{ secrets.GITEE_RSA_PRIVATE_KEY }} - with: - source-repo: "git@github.com:doocs/advanced-java.git" - destination-repo: "git@gitee.com:Doocs/advanced-java.git" - - - name: Build Gitee Pages - uses: yanglbme/gitee-pages-action@master - with: - gitee-username: yanglbme - gitee-password: ${{ secrets.GITEE_PASSWORD }} - gitee-repo: doocs/advanced-java diff --git a/.gitignore b/.gitignore index 2608ec266..58b09838b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,46 @@ + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc .DS_Store -.vscode \ No newline at end of file +.env.local +.env.development.local +.env.test.local +.env.production.local +.vscode + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +dist +lib + +node_modules + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Editor directories and files +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +docs/.vitepress/dist +docs/.vitepress/cache \ No newline at end of file diff --git a/Main.java b/Main.java new file mode 100644 index 000000000..2a610bf99 --- /dev/null +++ b/Main.java @@ -0,0 +1,8 @@ +/** + * @author yanglbme + */ +public class Main { + public static void main(String[] args) { + System.out.println("互联网 Java 工程师进阶知识完全扫盲"); + } +} \ No newline at end of file diff --git a/README.md b/README.md index 6a7f5b555..99266dba3 100644 --- a/README.md +++ b/README.md @@ -1,174 +1,173 @@ -# 互联网 Java 工程师进阶知识完全扫盲[©](https://github.com/yanglbme) +# 互联网 Java 工程师进阶知识完全扫盲 -[![actions status](https://github.com/doocs/advanced-java/workflows/Sync/badge.svg)](https://github.com/doocs/advanced-java/actions) -[![license](https://badgen.net/github/license/doocs/advanced-java?color=green)](https://github.com/doocs/advanced-java/blob/master/LICENSE) -[![PRs Welcome](https://badgen.net/badge/PRs/welcome/green)](http://makeapullrequest.com) -[![doocs](https://badgen.net/badge/organization/join%20us/green)](https://doocs.github.io/#/?id=how-to-join) -[![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) -[![issues](https://badgen.net/github/open-issues/doocs/advanced-java)](https://github.com/doocs/advanced-java/issues) -[![original](https://badgen.net/badge/original/%E4%B8%AD%E5%8D%8E%E7%9F%B3%E6%9D%89/red)](https://github.com/doocs/advanced-java) -[![notice](https://badgen.net/badge/notice/%E7%BB%B4%E6%9D%83%E8%A1%8C%E5%8A%A8/red)](/docs/extra-page/rights-defending-action.md) -[![wechat-group](https://badgen.net/badge/chat/%E5%BE%AE%E4%BF%A1%E4%BA%A4%E6%B5%81/cyan)](#公众号) -[![coding](https://badgen.net/badge/leetcode/%e4%b8%80%e8%b5%b7%e5%88%b7%e9%a2%98/cyan)](https://github.com/doocs/leetcode) +[![stars](https://img.shields.io/github/stars/doocs/advanced-java?color=42b883&logo=github&style=flat-square&logoColor=ffffff)](https://github.com/doocs/advanced-java/stargazers) +[![forks](https://img.shields.io/github/forks/doocs/advanced-java?color=42b883&logo=github&style=flat-square&logoColor=ffffff)](https://github.com/doocs/advanced-java/network/members) +[![license](https://img.shields.io/github/license/doocs/advanced-java?color=42b883&style=flat-square&logo=homeassistantcommunitystore&logoColor=ffffff)](./LICENSE) +[![doocs](https://img.shields.io/badge/org-join%20us-42b883?style=flat-square&logo=homeassistantcommunitystore&logoColor=ffffff)](https://doocs.github.io/#/?id=how-to-join) +本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)、[海量数据处理](#海量数据处理)等领域知识。我们对这部分知识做了一个系统的整理,方便读者们学习查阅。 -本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)、[海量数据处理](#海量数据处理)等领域知识。[我](https://github.com/yanglbme)对这部分知识做了一个系统的整理,方便学习查阅。 +我们也在全力更新算法项目!如果你在准备笔面试算法,或者想进一步提升 coding 能力,欢迎 Star 关注 [doocs/leetcode](https://github.com/doocs/leetcode) -学习之前,先来看看 [Issues 讨论区](https://github.com/doocs/advanced-java/issues/9#issue-394275038)的技术面试官是怎么说的吧。本项目也欢迎各位开发者朋友到 Issues 讨论区分享自己的一些想法和实践经验。 - -* Netlify: https://adjava.netlify.app -* Gitee Pages: https://doocs.gitee.io/advanced-java -* GitHub Pages: https://doocs.github.io/advanced-java +学习本项目之前,先来看看 [Discussions 讨论区](https://github.com/doocs/advanced-java/discussions/9)的技术面试官是怎么说的吧。本项目欢迎各位开发者朋友到 Discussions 讨论区分享自己的一些想法和实践经验。也不妨 Star 关注 [doocs/advanced-java](https://github.com/doocs/advanced-java),随时追踪项目最新动态。 ## 高并发架构 -### [消息队列](./docs/high-concurrency/mq-interview.md) +### [消息队列](/docs/high-concurrency/mq-interview.md) -* [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](./docs/high-concurrency/why-mq.md) -* [如何保证消息队列的高可用?](./docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md) -* [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](./docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md) -* [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](./docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md) -* [如何保证消息的顺序性?](./docs/high-concurrency/how-to-ensure-the-order-of-messages.md) -* [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](./docs/high-concurrency/mq-time-delay-and-expired-failure.md) -* [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](./docs/high-concurrency/mq-design.md) +- [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](/docs/high-concurrency/why-mq.md) +- [如何保证消息队列的高可用?](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md) +- [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md) +- [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md) +- [如何保证消息的顺序性?](/docs/high-concurrency/how-to-ensure-the-order-of-messages.md) +- [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](/docs/high-concurrency/mq-time-delay-and-expired-failure.md) +- [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](/docs/high-concurrency/mq-design.md) -### [搜索引擎](./docs/high-concurrency/es-introduction.md) +### [搜索引擎](/docs/high-concurrency/es-introduction.md) -* [ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?](./docs/high-concurrency/es-architecture.md) -* [ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?](./docs/high-concurrency/es-write-query-search.md) -* [ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](./docs/high-concurrency/es-optimizing-query-performance.md) -* [ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](./docs/high-concurrency/es-production-cluster.md) +- [ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?](/docs/high-concurrency/es-architecture.md) +- [ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?](/docs/high-concurrency/es-write-query-search.md) +- [ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](/docs/high-concurrency/es-optimizing-query-performance.md) +- [ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](/docs/high-concurrency/es-production-cluster.md) ### 缓存 -* [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](./docs/high-concurrency/why-cache.md) -* [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](./docs/high-concurrency/redis-single-thread-model.md) -* [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](./docs/high-concurrency/redis-data-types.md) -* [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](./docs/high-concurrency/redis-expiration-policies-and-lru.md) -* [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](./docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md) -* [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](./docs/high-concurrency/redis-persistence.md) -* [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](./docs/high-concurrency/redis-cluster.md) -* [了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](./docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) -* [如何保证缓存与数据库的双写一致性?](./docs/high-concurrency/redis-consistence.md) -* [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](./docs/high-concurrency/redis-cas.md) -* [生产环境中的 Redis 是怎么部署的?](./docs/high-concurrency/redis-production-environment.md) +- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](/docs/high-concurrency/why-cache.md) +- [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](/docs/high-concurrency/redis-single-thread-model.md) +- [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](/docs/high-concurrency/redis-data-types.md) +- [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](/docs/high-concurrency/redis-expiration-policies-and-lru.md) +- [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md) +- [Redis 主从架构是怎样的?](/docs/high-concurrency/redis-master-slave.md) +- [Redis 哨兵集群如何实现高可用?](/docs/high-concurrency/redis-sentinel.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) +- [有了解过 Redis rehash 的过程吗?](/docs/high-concurrency/redis-rehash.md) ### 分库分表 -* [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](./docs/high-concurrency/database-shard.md) -* [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](./docs/high-concurrency/database-shard-method.md) -* [如何设计可以动态扩容缩容的分库分表方案?](./docs/high-concurrency/database-shard-dynamic-expand.md) -* [分库分表之后,id 主键如何处理?](./docs/high-concurrency/database-shard-global-id-generate.md) +- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](/docs/high-concurrency/database-shard.md) +- [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](/docs/high-concurrency/database-shard-method.md) +- [如何设计可以动态扩容缩容的分库分表方案?](/docs/high-concurrency/database-shard-dynamic-expand.md) +- [分库分表之后,id 主键如何处理?](/docs/high-concurrency/database-shard-global-id-generate.md) ### 读写分离 -* [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](./docs/high-concurrency/mysql-read-write-separation.md) +- [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md) ### 高并发系统 -* [如何设计一个高并发系统?](./docs/high-concurrency/high-concurrency-design.md) +- [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md) ## 分布式系统 -### [面试连环炮](./docs/distributed-system/distributed-system-interview.md) +### [面试连环炮](/docs/distributed-system/distributed-system-interview.md) ### 系统拆分 -* [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](./docs/distributed-system/why-dubbo.md) +- [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md) ### 分布式服务框架 -* [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](./docs/distributed-system/dubbo-operating-principle.md) -* [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](./docs/distributed-system/dubbo-serialization-protocol.md) -* [Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](./docs/distributed-system/dubbo-load-balancing.md) -* [Dubbo 的 spi 思想是什么?](./docs/distributed-system/dubbo-spi.md) -* [如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](./docs/distributed-system/dubbo-service-management.md) -* [分布式服务接口的幂等性如何设计(比如不能重复扣款)?](./docs/distributed-system/distributed-system-idempotency.md) -* [分布式服务接口请求的顺序性如何保证?](./docs/distributed-system/distributed-system-request-sequence.md) -* [如何自己设计一个类似 Dubbo 的 RPC 框架?](./docs/distributed-system/dubbo-rpc-design.md) -* [CAP 定理的 P 是什么?](./docs/distributed-system/distributed-system-cap.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) +- [CAP 定理的 P 是什么?](/docs/distributed-system/distributed-system-cap.md) ### 分布式锁 -* [Zookeeper 都有哪些应用场景?](./docs/distributed-system/zookeeper-application-scenarios.md) -* [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](./docs/distributed-system/distributed-lock-redis-vs-zookeeper.md) +- [Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md) +- [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md) ### 分布式事务 -* [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](./docs/distributed-system/distributed-transaction.md) +- [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md) ### 分布式会话 -* [集群部署时的分布式 Session 如何实现?](./docs/distributed-system/distributed-session.md) +- [集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.md) ## 高可用架构 -* [Hystrix 介绍](./docs/high-availability/hystrix-introduction.md) -* [电商网站详情页系统架构](./docs/high-availability/e-commerce-website-detail-page-architecture.md) -* [Hystrix 线程池技术实现资源隔离](./docs/high-availability/hystrix-thread-pool-isolation.md) -* [Hystrix 信号量机制实现资源隔离](./docs/high-availability/hystrix-semphore-isolation.md) -* [Hystrix 隔离策略细粒度控制](./docs/high-availability/hystrix-execution-isolation.md) -* [深入 Hystrix 执行时内部原理](./docs/high-availability/hystrix-process.md) -* [基于 request cache 请求缓存技术优化批量商品数据查询接口](./docs/high-availability/hystrix-request-cache.md) -* [基于本地缓存的 fallback 降级机制](./docs/high-availability/hystrix-fallback.md) -* [深入 Hystrix 断路器执行原理](./docs/high-availability/hystrix-circuit-breaker.md) -* [深入 Hystrix 线程池隔离与接口限流](./docs/high-availability/hystrix-thread-pool-current-limiting.md) -* [基于 timeout 机制为服务接口调用超时提供安全保护](./docs/high-availability/hystrix-timeout.md) +- [Hystrix 介绍](/docs/high-availability/hystrix-introduction.md) +- [电商网站详情页系统架构](/docs/high-availability/e-commerce-website-detail-page-architecture.md) +- [Hystrix 线程池技术实现资源隔离](/docs/high-availability/hystrix-thread-pool-isolation.md) +- [Hystrix 信号量机制实现资源隔离](/docs/high-availability/hystrix-semphore-isolation.md) +- [Hystrix 隔离策略细粒度控制](/docs/high-availability/hystrix-execution-isolation.md) +- [深入 Hystrix 执行时内部原理](/docs/high-availability/hystrix-process.md) +- [基于 request cache 请求缓存技术优化批量商品数据查询接口](/docs/high-availability/hystrix-request-cache.md) +- [基于本地缓存的 fallback 降级机制](/docs/high-availability/hystrix-fallback.md) +- [深入 Hystrix 断路器执行原理](/docs/high-availability/hystrix-circuit-breaker.md) +- [深入 Hystrix 线程池隔离与接口限流](/docs/high-availability/hystrix-thread-pool-current-limiting.md) +- [基于 timeout 机制为服务接口调用超时提供安全保护](/docs/high-availability/hystrix-timeout.md) ### 高可用系统 -* 如何设计一个高可用系统? +- 如何设计一个高可用系统? ### 限流 -* [如何限流?在工作中是怎么做的?说一下具体的实现?](/docs/high-concurrency/huifer-how-to-limit-current.md) +- [如何限流?在工作中是怎么做的?说一下具体的实现?](/docs/high-concurrency/how-to-limit-current.md) ### 熔断 -* 如何进行熔断? -* 熔断框架都有哪些?具体实现原理知道吗? -* [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](./docs/high-availability/sentinel-vs-hystrix.md) +- 如何进行熔断? +- 熔断框架都有哪些?具体实现原理知道吗? +- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](/docs/high-availability/sentinel-vs-hystrix.md) ### 降级 -* 如何进行降级? +- 如何进行降级? ## 微服务架构 -* [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java) -* [关于微服务架构的描述](./docs/micro-services/microservices-introduction.md) -* [从单体式架构迁移到微服务架构](./docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md) -* [微服务的事件驱动数据管理](./docs/micro-services/event-driven-data-management-for-microservices.md) -* [选择微服务部署策略](./docs/micro-services/choose-microservice-deployment-strategy.md) -* [微服务架构的优势与不足](./docs/micro-services/advantages-and-disadvantages-of-microservice.md) +- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java) +- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md) +- [从单体式架构迁移到微服务架构](/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md) +- [微服务的事件驱动数据管理](/docs/micro-services/event-driven-data-management-for-microservices.md) +- [选择微服务部署策略](/docs/micro-services/choose-microservice-deployment-strategy.md) +- [微服务架构的优势与不足](/docs/micro-services/advantages-and-disadvantages-of-microservice.md) ### Spring Cloud 微服务架构 -* [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/huifer-what's-microservice-how-to-communicate.md) -* Spring Cloud 和 Dubbo 有哪些区别? -* Spring Boot 和 Spring Cloud,谈谈你对它们的理解? -* 什么是服务熔断?什么是服务降级? -* 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑? -* [你所知道的微服务技术栈都有哪些?](/docs/micro-services/huifer-micro-services-technology-stack.md) -* [微服务治理策略](/docs/micro-services/huifer-micro-service-governance.md) -* Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? -* [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md) -* ...... +- [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/what's-microservice-how-to-communicate.md) +- Spring Cloud 和 Dubbo 有哪些区别? +- Spring Boot 和 Spring Cloud,谈谈你对它们的理解? +- 什么是服务熔断?什么是服务降级? +- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑? +- [你所知道的微服务技术栈都有哪些?](/docs/micro-services/micro-services-technology-stack.md) +- [微服务治理策略](/docs/micro-services/micro-service-governance.md) +- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? +- [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md) +- ...... ## 海量数据处理 -* [如何从大量的 URL 中找出相同的 URL?](./docs/big-data/find-common-urls.md) -* [如何从大量数据中找出高频词?](./docs/big-data/find-top-100-words.md) -* [如何找出某一天访问百度网站最多的 IP?](./docs/big-data/find-top-1-ip.md) -* [如何在大量的数据中找出不重复的整数?](./docs/big-data/find-no-repeat-number.md) -* [如何在大量的数据中判断一个数是否存在?](./docs/big-data/find-a-number-if-exists.md) -* [如何查询最热门的查询串?](./docs/big-data/find-hotest-query-string.md) -* [如何统计不同电话号码的个数?](./docs/big-data/count-different-phone-numbers.md) -* [如何从 5 亿个数中找出中位数?](./docs/big-data/find-mid-value-in-500-millions.md) -* [如何按照 query 的频度排序?](./docs/big-data/sort-the-query-strings-by-counts.md) -* [如何找出排名前 500 的数?](./docs/big-data/find-rank-top-500-numbers.md) +- [如何从大量的 URL 中找出相同的 URL?](/docs/big-data/find-common-urls.md) +- [如何从大量数据中找出高频词?](/docs/big-data/find-top-100-words.md) +- [如何找出某一天访问百度网站最多的 IP?](/docs/big-data/find-top-1-ip.md) +- [如何在大量的数据中找出不重复的整数?](/docs/big-data/find-no-repeat-number.md) +- [如何在大量的数据中判断一个数是否存在?](/docs/big-data/find-a-number-if-exists.md) +- [如何查询最热门的查询串?](/docs/big-data/find-hotest-query-string.md) +- [如何统计不同电话号码的个数?](/docs/big-data/count-different-phone-numbers.md) +- [如何从 5 亿个数中找出中位数?](/docs/big-data/find-mid-value-in-500-millions.md) +- [如何按照 query 的频度排序?](/docs/big-data/sort-the-query-strings-by-counts.md) +- [如何找出排名前 500 的数?](/docs/big-data/find-rank-top-500-numbers.md) +- [讲讲大数据中 TopK 问题的常用套路?](/docs/big-data/topk-problems-and-solutions.md) + +## Stars 趋势 + +Stargazers over time + +注:本趋势图由 [actions-starcharts](https://github.com/MaoLongLong/actions-starcharts) 自动定时刷新,作者 [@MaoLongLong](https://github.com/maolonglong) --- @@ -176,15 +175,15 @@ Doocs 技术社区,致力于打造一个内容完整、持续成长的互联网开发者学习生态圈!以下是 Doocs 旗下的一些优秀项目,欢迎各位开发者朋友持续保持关注。 -| # | 项目 | 描述 | 热度 | -|---|---|---|---| -| 1 | [advanced-java](https://github.com/doocs/advanced-java) | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 | ![](https://badgen.net/github/stars/doocs/advanced-java)
![](https://badgen.net/github/forks/doocs/advanced-java) | -| 2 | [leetcode](https://github.com/doocs/leetcode) | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。 | ![](https://badgen.net/github/stars/doocs/leetcode)
![](https://badgen.net/github/forks/doocs/leetcode) | -| 3 | [source-code-hunter](https://github.com/doocs/source-code-hunter) | 互联网常用组件框架源码分析。 | ![](https://badgen.net/github/stars/doocs/source-code-hunter)
![](https://badgen.net/github/forks/doocs/source-code-hunter) | -| 4 | [jvm](https://github.com/doocs/jvm) | Java 虚拟机底层原理知识总结。 | ![](https://badgen.net/github/stars/doocs/jvm)
![](https://badgen.net/github/forks/doocs/jvm) | -| 5 | [coding-interview](https://github.com/doocs/coding-interview) | 代码面试题集,包括《剑指 Offer》、《编程之美》等。 | ![](https://badgen.net/github/stars/doocs/coding-interview)
![](https://badgen.net/github/forks/doocs/coding-interview) | -| 6 | [md](https://github.com/doocs/md) | 一款高度简洁的微信 Markdown 编辑器。 | ![](https://badgen.net/github/stars/doocs/md)
![](https://badgen.net/github/forks/doocs/md) | -| 7 | [technical-books](https://github.com/doocs/technical-books) | 值得一看的技术书籍列表。 | ![](https://badgen.net/github/stars/doocs/technical-books)
![](https://badgen.net/github/forks/doocs/technical-books) | +| # | 项目 | 描述 | 热度 | +| --- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | +| 1 | [advanced-java](https://github.com/doocs/advanced-java) | 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务、海量数据处理等领域知识。 | ![](https://badgen.net/github/stars/doocs/advanced-java)
![](https://badgen.net/github/forks/doocs/advanced-java) | +| 2 | [leetcode](https://github.com/doocs/leetcode) | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解。 | ![](https://badgen.net/github/stars/doocs/leetcode)
![](https://badgen.net/github/forks/doocs/leetcode) | +| 3 | [source-code-hunter](https://github.com/doocs/source-code-hunter) | 互联网常用组件框架源码分析。 | ![](https://badgen.net/github/stars/doocs/source-code-hunter)
![](https://badgen.net/github/forks/doocs/source-code-hunter) | +| 4 | [jvm](https://github.com/doocs/jvm) | Java 虚拟机底层原理知识总结。 | ![](https://badgen.net/github/stars/doocs/jvm)
![](https://badgen.net/github/forks/doocs/jvm) | +| 5 | [coding-interview](https://github.com/doocs/coding-interview) | 代码面试题集,包括《剑指 Offer》、《编程之美》等。 | ![](https://badgen.net/github/stars/doocs/coding-interview)
![](https://badgen.net/github/forks/doocs/coding-interview) | +| 6 | [md](https://github.com/doocs/md) | 一款高度简洁的微信 Markdown 编辑器。 | ![](https://badgen.net/github/stars/doocs/md)
![](https://badgen.net/github/forks/doocs/md) | +| 7 | [technical-books](https://github.com/doocs/technical-books) | 值得一看的技术书籍列表。 | ![](https://badgen.net/github/stars/doocs/technical-books)
![](https://badgen.net/github/forks/doocs/technical-books) | ## 贡献者 @@ -198,21 +197,19 @@ Doocs 技术社区,致力于打造一个内容完整、持续成长的互联 ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - - - - + + + +
- -
- 公众平台 -

-
- -
- 个人微信 -

-
+
+
+
+
+ +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! + +
diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 000000000..7b5abc4dc --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,483 @@ +import { defineConfig } from "vitepress"; + +export default defineConfig({ + title: "advanced-java", + ignoreDeadLinks: true, + themeConfig: { + search: { + provider: 'local' + }, + footer: { + message: 'Released under the CC-BY-SA-4.0 license.', + copyright: `Copyright © 2018-${new Date().getFullYear()} Doocs` + }, + logo: '/icon.png', + docFooter: { + prev: '上一篇', + next: '下一篇' + }, + editLink: { + pattern: 'https://github.com/doocs/advanced-java/edit/main/docs/:path', + text: '在 GitHub 编辑' + }, + nav: [ + { text: "首页", link: "/" }, + { text: "高并发架构", link: "/high-concurrency/mq-interview.md" }, + { + text: "分布式系统", + link: "/distributed-system/distributed-system-interview.md", + }, + { + text: "高可用架构", + link: "/high-availability/hystrix-introduction.md", + }, + { + text: "微服务架构", + link: "/micro-services/microservices-introduction.md", + }, + { text: "海量数据处理", link: "/big-data/find-common-urls.md" }, + ], + + sidebar: [ + { + text: "高并发架构", + collapsed: true, + items: [ + { + text: "消息队列", + link: "/high-concurrency/mq-interview.md", + collapsed: true, + items: [ + { + text: "为什么使用消息队列?", + link: "/high-concurrency/why-mq.md", + }, + { + text: "如何保证消息队列的高可用?", + link: "/high-concurrency/how-to-ensure-high-availability-of-message-queues.md", + }, + { + text: "如何保证消息不被重复消费?", + link: "/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md", + }, + { + text: "如何保证消息的可靠性传输?", + link: "/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md", + }, + { + text: "如何保证消息的顺序性?", + link: "/high-concurrency/how-to-ensure-the-order-of-messages.md", + }, + { + text: "如何解决消息队列的延时及过期问题?", + link: "/high-concurrency/mq-time-delay-and-expired-failure.md", + }, + { + text: "如何设计一个消息队列?", + link: "/high-concurrency/mq-design.md", + }, + ], + }, + { + text: "搜索引擎", + collapsed: true, + link: "/high-concurrency/es-introduction.md", + items: [ + { + text: "ES 的分布式架构原理", + link: "/high-concurrency/es-architecture.md", + }, + { + text: "ES 写入数据原理", + link: "/high-concurrency/es-write-query-search.md", + }, + { + text: "ES 查询性能优化", + link: "/high-concurrency/es-optimizing-query-performance.md", + }, + { + text: "ES 生产集群架构", + link: "/high-concurrency/es-production-cluster.md", + }, + ], + }, + { + text: "缓存", + collapsed: true, + items: [ + { + text: "缓存的使用方式", + link: "/high-concurrency/why-cache.md", + }, + { + text: "Redis 和 Memcached 区别", + link: "/high-concurrency/redis-single-thread-model.md", + }, + { + text: "Redis 数据类型和场景", + link: "/high-concurrency/redis-data-types.md", + }, + { + text: "Redis 过期策略", + link: "/high-concurrency/redis-expiration-policies-and-lru.md", + }, + { + text: "Redis 高并发与高可用", + link: "/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md", + }, + { + text: "Redis 主从架构", + link: "/high-concurrency/redis-master-slave.md", + }, + { + text: "Redis 持久化机制", + link: "/high-concurrency/redis-persistence.md", + }, + { + text: "哨兵集群实现高可用", + link: "/high-concurrency/redis-sentinel.md", + }, + { + text: "Redis 集群模式原理", + link: "/high-concurrency/redis-cluster.md", + }, + { + text: "缓存雪崩穿透击穿", + link: "/high-concurrency/redis-caching-avalanche-and-caching-penetration.md", + }, + { + text: "缓存与数据库一致性", + link: "/high-concurrency/redis-consistence.md", + }, + { + text: "Redis 并发竞争问题", + link: "/high-concurrency/redis-cas.md", + }, + { + text: "Redis 生产部署方案", + link: "/high-concurrency/redis-production-environment.md", + }, + ], + }, + { + text: "分库分表", + collapsed: true, + items: [ + { + text: "为什么要分库分表?", + link: "/high-concurrency/database-shard.md", + }, + { + text: "分库分表如何平滑过渡?", + link: "/high-concurrency/database-shard-method.md", + }, + { + text: "动态扩缩容方案", + link: "/high-concurrency/database-shard-dynamic-expand.md", + }, + { + text: "主键 ID 如何处理?", + link: "/high-concurrency/database-shard-global-id-generate.md", + }, + ], + }, + { + text: "读写分离", + items: [ + { + text: "如何实现读写分离?", + link: "/high-concurrency/mysql-read-write-separation.md", + }, + ], + }, + { + text: "高并发系统", + items: [ + { + text: "如何设计一个高并发系统?", + link: "/high-concurrency/high-concurrency-design.md", + }, + ], + }, + { + text: "限流", + items: [ + { + text: "限流实现方式", + link: "/high-concurrency/how-to-limit-current.md", + }, + ], + }, + ], + }, + { + text: "分布式系统", + collapsed: true, + items: [ + { + text: "面试连环炮", + link: "/distributed-system/distributed-system-interview.md", + }, + { + text: "系统拆分", + items: [ + { + text: "为什么要拆分系统?", + link: "/distributed-system/why-dubbo.md", + }, + ], + }, + { + text: "分布式服务框架", + collapsed: true, + items: [ + { + text: "Dubbo 工作原理", + link: "/distributed-system/dubbo-operating-principle.md", + }, + { + text: "Dubbo 序列化协议", + link: "/distributed-system/dubbo-serialization-protocol.md", + }, + { + text: "Dubbo 负载与容错策略", + link: "/distributed-system/dubbo-load-balancing.md", + }, + { + text: "Dubbo 的 SPI 思想", + link: "/distributed-system/dubbo-spi.md", + }, + { + text: "服务治理方案", + link: "/distributed-system/dubbo-service-management.md", + }, + { + text: "接口幂等性设计", + link: "/distributed-system/distributed-system-idempotency.md", + }, + { + text: "接口顺序性设计", + link: "/distributed-system/distributed-system-request-sequence.md", + }, + { + text: "设计 Dubbo 类似的 RPC 框架", + link: "/distributed-system/dubbo-rpc-design.md", + }, + { + text: "CAP 定理中的 P 是什么?", + link: "/distributed-system/distributed-system-cap.md", + }, + ], + }, + { + text: "分布式锁", + collapsed: true, + items: [ + { + text: "Zookeeper 应用场景", + link: "/distributed-system/zookeeper-application-scenarios.md", + }, + { + text: "Redis vs Zookeeper 实现分布式锁", + link: "/distributed-system/distributed-lock-redis-vs-zookeeper.md", + }, + ], + }, + { + text: "分布式事务", + items: [ + { + text: "分布式事务原理", + link: "/distributed-system/distributed-transaction.md", + }, + ], + }, + { + text: "分布式会话", + items: [ + { + text: "如何实现分布式 Session?", + link: "/distributed-system/distributed-session.md", + }, + ], + }, + ], + }, + { + text: "高可用架构", + collapsed: true, + items: [ + { + text: "Hystrix 高可用", + collapsed: true, + items: [ + { + text: "Hystrix 介绍", + link: "/high-availability/hystrix-introduction.md", + }, + { + text: "详情页架构设计", + link: "/high-availability/e-commerce-website-detail-page-architecture.md", + }, + { + text: "线程池隔离", + link: "/high-availability/hystrix-thread-pool-isolation.md", + }, + { + text: "信号量隔离", + link: "/high-availability/hystrix-semphore-isolation.md", + }, + { + text: "细粒度隔离策略", + link: "/high-availability/hystrix-execution-isolation.md", + }, + { + text: "执行原理", + link: "/high-availability/hystrix-process.md", + }, + { + text: "Request Cache 缓存", + link: "/high-availability/hystrix-request-cache.md", + }, + { + text: "本地降级缓存", + link: "/high-availability/hystrix-fallback.md", + }, + { + text: "断路器原理", + link: "/high-availability/hystrix-circuit-breaker.md", + }, + { + text: "限流与线程池隔离", + link: "/high-availability/hystrix-thread-pool-current-limiting.md", + }, + { + text: "Timeout 保护机制", + link: "/high-availability/hystrix-timeout.md", + }, + ], + }, + { + text: "熔断与降级", + items: [ + { + text: "Sentinel vs Hystrix", + link: "/high-availability/sentinel-vs-hystrix.md", + }, + ], + }, + ], + }, + { + text: "微服务架构", + collapsed: true, + items: [ + { + text: "微服务概念", + collapsed: true, + items: [ + { + text: "微服务架构描述", + link: "/micro-services/microservices-introduction.md", + }, + { + text: "从单体到微服务", + link: "/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md", + }, + { + text: "事件驱动数据管理", + link: "/micro-services/event-driven-data-management-for-microservices.md", + }, + { + text: "选择部署策略", + link: "/micro-services/choose-microservice-deployment-strategy.md", + }, + ], + }, + { + text: "Spring Cloud 架构", + collapsed: true, + items: [ + { + text: "微服务间通信机制", + link: "/micro-services/what's-microservice-how-to-communicate.md", + }, + { + text: "微服务技术栈", + link: "/micro-services/micro-services-technology-stack.md", + }, + { + text: "微服务治理策略", + link: "/micro-services/micro-service-governance.md", + }, + { + text: "Eureka 服务注册发现", + link: "/micro-services/how-eureka-enable-service-discovery-and-service-registration.md", + }, + ], + }, + ], + }, + { + text: "海量数据处理", + collapsed: true, + items: [ + { text: "查找相同 URL", link: "/big-data/find-common-urls.md" }, + { text: "查找高频词", link: "/big-data/find-top-100-words.md" }, + { text: "找出最多访问 IP", link: "/big-data/find-top-1-ip.md" }, + { + text: "找出不重复整数", + link: "/big-data/find-no-repeat-number.md", + }, + { + text: "判断数是否存在", + link: "/big-data/find-a-number-if-exists.md", + }, + { + text: "查询最热门查询串", + link: "/big-data/find-hotest-query-string.md", + }, + { + text: "统计不同手机号", + link: "/big-data/count-different-phone-numbers.md", + }, + { + text: "找出中位数", + link: "/big-data/find-mid-value-in-500-millions.md", + }, + { + text: "根据频率排序查询串", + link: "/big-data/sort-the-query-strings-by-counts.md", + }, + { + text: "找出前 500 个数", + link: "/big-data/find-rank-top-500-numbers.md", + }, + ], + }, + ], + + socialLinks: [ + { icon: "github", link: "https://github.com/doocs/advanced-java" }, + ], + }, + head: [ + ['link', { rel: 'icon', type: 'image/png', href: '/favicon-32x32.png' }], + [ + 'script', + { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-GWYHFTEDNE' } + ], + [ + 'script', + {}, + `window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'G-GWYHFTEDNE');` + ] + ], + cleanUrls: true, + sitemap: { + hostname: 'https://java.doocs.org' + } +}); diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue new file mode 100644 index 000000000..0a0afe7ec --- /dev/null +++ b/docs/.vitepress/theme/Layout.vue @@ -0,0 +1,38 @@ + + + diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js new file mode 100644 index 000000000..419b58502 --- /dev/null +++ b/docs/.vitepress/theme/index.js @@ -0,0 +1,36 @@ +import DefaultTheme from "vitepress/theme"; +import giscusTalk from "vitepress-plugin-comment-with-giscus"; +import { useData, useRoute } from "vitepress"; +import { toRefs } from "vue"; +import Layout from "./Layout.vue"; + +export default { + extends: DefaultTheme, + Layout: Layout, + enhanceApp(ctx) { + DefaultTheme.enhanceApp(ctx); + }, + setup() { + const { frontmatter } = toRefs(useData()); + const route = useRoute(); + + giscusTalk( + { + repo: "doocs/advanced-java", + repoId: "MDEwOlJlcG9zaXRvcnkxNTE4MzQwNjI=", + mapping: "number", + inputPosition: "top", + lang: "zh-CN", + homePageShowComment: true, + term: "9", + lightTheme: "light", + darkTheme: "transparent_dark", + }, + { + frontmatter, + route, + }, + true + ); + }, +}; diff --git a/docs/big-data/README.md b/docs/big-data/README.md index 39dee6fee..9abc72aa1 100644 --- a/docs/big-data/README.md +++ b/docs/big-data/README.md @@ -1,35 +1,33 @@ # 海量数据处理 -* [如何从大量的 URL 中找出相同的 URL?](/docs/big-data/find-common-urls.md) -* [如何从大量数据中找出高频词?](/docs/big-data/find-top-100-words.md) -* [如何找出某一天访问百度网站最多的 IP?](/docs/big-data/find-top-1-ip.md) -* [如何在大量的数据中找出不重复的整数?](/docs/big-data/find-no-repeat-number.md) -* [如何在大量的数据中判断一个数是否存在?](/docs/big-data/find-a-number-if-exists.md) -* [如何查询最热门的查询串?](/docs/big-data/find-hotest-query-string.md) -* [如何统计不同电话号码的个数?](/docs/big-data/count-different-phone-numbers.md) -* [如何从 5 亿个数中找出中位数?](/docs/big-data/find-mid-value-in-500-millions.md) -* [如何按照 query 的频度排序?](/docs/big-data/sort-the-query-strings-by-counts.md) -* [如何找出排名前 500 的数?](/docs/big-data/find-rank-top-500-numbers.md) +- [如何从大量的 URL 中找出相同的 URL?](/docs/big-data/find-common-urls.md) +- [如何从大量数据中找出高频词?](/docs/big-data/find-top-100-words.md) +- [如何找出某一天访问百度网站最多的 IP?](/docs/big-data/find-top-1-ip.md) +- [如何在大量的数据中找出不重复的整数?](/docs/big-data/find-no-repeat-number.md) +- [如何在大量的数据中判断一个数是否存在?](/docs/big-data/find-a-number-if-exists.md) +- [如何查询最热门的查询串?](/docs/big-data/find-hotest-query-string.md) +- [如何统计不同电话号码的个数?](/docs/big-data/count-different-phone-numbers.md) +- [如何从 5 亿个数中找出中位数?](/docs/big-data/find-mid-value-in-500-millions.md) +- [如何按照 query 的频度排序?](/docs/big-data/sort-the-query-strings-by-counts.md) +- [如何找出排名前 500 的数?](/docs/big-data/find-rank-top-500-numbers.md) --- ## 公众号 -GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - - - - + + + +
- -
- 公众平台 -

-
- -
- 个人微信 -

-
+
+
+
+
+ +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! + +
diff --git a/docs/big-data/count-different-phone-numbers.md b/docs/big-data/count-different-phone-numbers.md index 2d7a4c1cc..b799f8184 100644 --- a/docs/big-data/count-different-phone-numbers.md +++ b/docs/big-data/count-different-phone-numbers.md @@ -1,19 +1,19 @@ -## 如何统计不同电话号码的个数? +# 如何统计不同电话号码的个数? -### 题目描述 +## 题目描述 已知某个文件内包含一些电话号码,每个号码为 8 位数字,统计不同号码的个数。 -### 解答思路 +## 解答思路 这道题本质还是求解**数据重复**的问题,对于这类问题,一般首先考虑位图法。 -对于本题,8 位电话号码可以表示的号码个数为 108 个,即 1 亿个。我们每个号码用一个 bit 来表示,则总共需要 1 亿个 bit,内存占用约 100M。 +对于本题,8 位电话号码可以表示的号码个数为 108 个,即 1 亿个。我们每个号码用一个 bit 来表示,则总共需要 1 亿个 bit,内存占用约 12M。 **思路如下**: 申请一个位图数组,长度为 1 亿,初始化为 0。然后遍历所有电话号码,把号码对应的位图中的位置置为 1。遍历完成后,如果 bit 为 1,则表示这个电话号码在文件中存在,否则不存在。bit 值为 1 的数量即为 不同电话号码的个数。 -### 方法总结 +## 方法总结 求解数据重复问题,记得考虑位图法。 diff --git a/docs/big-data/find-a-number-if-exists.md b/docs/big-data/find-a-number-if-exists.md index 2862db661..7e159833e 100644 --- a/docs/big-data/find-a-number-if-exists.md +++ b/docs/big-data/find-a-number-if-exists.md @@ -1,20 +1,21 @@ -## 如何在大量的数据中判断一个数是否存在? +# 如何在大量的数据中判断一个数是否存在? -### 题目描述 +## 题目描述 给定 40 亿个不重复的没排过序的 unsigned int 型整数,然后再给定一个数,如何快速判断这个数是否在这 40 亿个整数当中? -### 解答思路 +## 解答思路 + +### 方法一:分治法 -#### 方法一:分治法 依然可以用分治法解决,方法与前面类似,就不再次赘述了。 -#### 方法二:位图法 +### 方法二:位图法 -40 亿个不重复整数,我们用 40 亿个 bit 来表示,初始位均为 0,那么总共需要内存:4, 000, 000, 000b≈512M。 +由于 unsigned int 数字的范围是 `[0, 1 << 32)`,我们用 `1<<32=4,294,967,296` 个 bit 来表示每个数字。初始位均为 0,那么总共需要内存:4,294,967,296b≈512M。 我们读取这 40 亿个整数,将对应的 bit 设置为 1。接着读取要查询的数,查看相应位是否为 1,如果为 1 表示存在,如果为 0 表示不存在。 -### 方法总结 +## 方法总结 **判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。 diff --git a/docs/big-data/find-common-urls.md b/docs/big-data/find-common-urls.md index bb4227145..699191ecd 100644 --- a/docs/big-data/find-common-urls.md +++ b/docs/big-data/find-common-urls.md @@ -1,14 +1,16 @@ -## 如何从大量的 URL 中找出相同的 URL? +# 如何从大量的 URL 中找出相同的 URL? -### 题目描述 +## 题目描述 给定 a、b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G。请找出 a、b 两个文件共同的 URL。 -### 解答思路 +## 解答思路 -每个 URL 占 64B,那么 50 亿个 URL占用的空间大小约为 320GB。 +### 1. 分治策略 -> 5, 000, 000, 000 * 64B ≈ 5GB * 64 = 320GB +每个 URL 占 64B,那么 50 亿个 URL 占用的空间大小约为 320GB。 + +> 5, 000, 000, 000 _ 64B ≈ 5GB _ 64 = 320GB 由于内存大小只有 4G,因此,我们不可能一次性把所有 URL 加载到内存中处理。对于这种类型的题目,一般采用**分治策略**,即:把一个文件中的 URL 按照某个特征划分为多个小文件,使得每个小文件大小不超过 4G,这样就可以把这个小文件读到内存中进行处理了。 @@ -16,9 +18,55 @@ 首先遍历文件 a,对遍历到的 URL 求 `hash(URL) % 1000` ,根据计算结果把遍历到的 URL 存储到 a0, a1, a2, ..., a999,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b0, b1, b2, ..., b999 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a0 对应 b0, ..., a999 对应 b999,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。 -接着遍历 ai( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 bi 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。 +接着遍历 ai( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 bi 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。 + +### 2. 前缀树是否可行? + +> 一般而言,URL 的长度差距不会不大,而且前面几个字符,绝大部分相同。这种情况下,非常适合使用**字典树**(trie tree) 这种数据结构来进行存储,降低存储成本的同时,提高查询效率。 +> +> 由 [@ChunelFeng](https://github.com/ChunelFeng) 反馈。[#212](https://github.com/doocs/advanced-java/issues/212) + +有 issue 提到是否可以直接使用 trie 树,我们来推算一下方案是否可行。 + +Background:4G 的机器,解析 320G 的数据。 + +首先题目提到 64B 大小,按一个字符一个字节来算的话就是 64 位,也就是 64 个字符组成(这里考虑极端情况)。Trie 树的定义这里就不提了,见[字典树](https://doocs.github.io/leetcode/tags/#%E5%A4%9A%E7%BA%BF%E7%A8%8B)。 + +- **树高**:URL 的长度就是最后的树的能达到的高度,及最高 64 层树。 + +- **节点下的子节点数量**:因为是 URL,除了数字和字符之外还有`%`, `/`, `:`etc。这里为了简化计算和方便理解只使用英文字符+数字总数=62+10=72。也就是说一个结点下的子节点最多有 72 个,及占 72B。 + +- **节点总大小**:因为样本 320G 足够大,我们考虑坏情况下,最后的树是 64 层高的满 72 叉树。最后的内存占用为节点数量\*节点大小: + + - 节点数量:满 N = 64 层,K = 72 叉,树节点数量计算公式(等比数列求和)为:`1 * (1-72^64)/ (1 - 72) = (72^64 - 1)/ (71)`,估算为`71^63`个节点。 + + - 节点大小:每个节点占用大小为 1B + - 当前字符 1B + - 当前位置是否存在值 1b(常见 Bool 值大小),这里按最小大小来。 -### 方法总结 +所以总大小为:`71^63 * 1B/ 1000 ≈ 71^60 KB ≈ 71 ^ 63 GB`,这个值已经很大了,况且在方案推算过程中按照最小原则考虑。 + +--- + +写到这里有些读者可能就被吓到了,怎么出来这么多数据,trie 树不是有压缩的作用嘛? + +这里得到的结果是不正确的,因为 320G 的数据,生成的节点数也最多只会是 320G(路径完全不重复),因为无法构成上面讨论的满 N 叉树的情况,填满了其中一点而已。但是对于单机来说此部分数据单机已无法解析,并且方案不具有扩展性和稳定性 。所以单纯的 trie 树太依赖数据分布,如果你的大部分 URL 是相同的压缩度很高,单机 4G 解析 320G 有可能,但是对于面试官想听到的应用场景来说这个概率太小了。所以还是得借助分治法的方式降低内存。 + +在方案一上进行优化:在上一个方法中使用 HaseSet 来判重,hash 效率比访问 trie 树,hash 效率更高,trie 可以降低内存。 + +所以在面试中可以提到 trie 方案,在这个场景中解析 320G 离线数据为时间不敏感类型,所以可以牺牲速度来换取空间。 + +- 方案一提到单文件大小为 300MB+,使用 trie 树之后这 300MB+会有所压缩比如到 200MB+,考虑到机器性能利用率,在一些 CPU 充足的场景中可以考虑引入并发,因为压缩率 1/3,其余空间可以腾出来加载更多文件做并发提升处理速度。 + +- 如果 CPU 不充足比如单核,可以考虑增加切分的文件大小,原先的 1000 个文件,降为 200 个也就是文件大小扩大两倍,之前 300MB,现在 300\*5 = 1.5G,在 4G 的机器上可以打满内存,减少 IO 次数,减少内核态到用户态的拷贝,也可以提一嘴 DMA。 + +## 方法总结 + +### 分治策略 1. 分而治之,进行哈希取余; -2. 对每个子文件进行 HashSet 统计。 +1. 对每个子文件进行 HashSet 统计。 + +### 前缀树 + +1. 利用字符串的公共前缀牺牲速度,来降低内存占用。 diff --git a/docs/big-data/find-hotest-query-string.md b/docs/big-data/find-hotest-query-string.md index 6ef41d114..ea92712e6 100644 --- a/docs/big-data/find-hotest-query-string.md +++ b/docs/big-data/find-hotest-query-string.md @@ -1,16 +1,16 @@ -## 如何查询最热门的查询串? +# 如何查询最热门的查询串? -### 题目描述 +## 题目描述 搜索引擎会通过日志文件把用户每次检索使用的所有查询串都记录下来,每个查询串的长度不超过 255 字节。 假设目前有 1000w 个记录(这些查询串的重复度比较高,虽然总数是 1000w,但如果除去重复后,则不超过 300w 个)。请统计最热门的 10 个查询串,要求使用的内存不能超过 1G。(一个查询串的重复度越高,说明查询它的用户越多,也就越热门。) -### 解答思路 +## 解答思路 每个查询串最长为 255B,1000w 个串需要占用 约 2.55G 内存,因此,我们无法将所有字符串全部读入到内存中处理。 -#### 方法一:分治法 +### 方法一:分治法 分治法依然是一个非常实用的方法。 @@ -18,9 +18,9 @@ 方法可行,但不是最好,下面介绍其他方法。 -#### 方法二:HashMap 法 +### 方法二:HashMap 法 -虽然字符串总数比较多,但去重后不超过 300w,因此,可以考虑把所有字符串及出现次数保存在一个 HashMap 中,所占用的空间为 300w*(255+4)≈777M(其中,4表示整数占用的4个字节)。由此可见,1G 的内存空间完全够用。 +虽然字符串总数比较多,但去重后不超过 300w,因此,可以考虑把所有字符串及出现次数保存在一个 HashMap 中,所占用的空间为 300w\*(255+4)≈777M(其中,4 表示整数占用的 4 个字节)。由此可见,1G 的内存空间完全够用。 **思路如下**: @@ -30,7 +30,7 @@ 遍历结束后,堆中 10 个字符串就是出现次数最多的字符串。这一步时间复杂度 `O(Nlog10)` 。 -#### 方法三:前缀树法 +### 方法三:前缀树法 方法二使用了 HashMap 来统计次数,当这些字符串有大量相同前缀时,可以考虑使用前缀树来统计字符串出现的次数,树的结点保存字符串出现次数,0 表示没有出现。 @@ -40,6 +40,6 @@ 最后依然使用小顶堆来对字符串的出现次数进行排序。 -### 方法总结 +## 方法总结 前缀树经常被用来统计字符串的出现次数。它的另外一个大的用途是字符串查找,判断是否有重复的字符串等。 diff --git a/docs/big-data/find-mid-value-in-500-millions.md b/docs/big-data/find-mid-value-in-500-millions.md index 213dd2a51..8421801ce 100644 --- a/docs/big-data/find-mid-value-in-500-millions.md +++ b/docs/big-data/find-mid-value-in-500-millions.md @@ -1,22 +1,22 @@ -## 如何从 5 亿个数中找出中位数? +# 如何从 5 亿个数中找出中位数? -### 题目描述 +## 题目描述 从 5 亿个数中找出中位数。数据排序后,位置在最中间的数就是中位数。当样本数为奇数时,中位数为 第 `(N+1)/2` 个数;当样本数为偶数时,中位数为 第 `N/2` 个数与第 `1+N/2` 个数的均值。 -### 解答思路 +## 解答思路 如果这道题没有内存大小限制,则可以把所有数读到内存中排序后找出中位数。但是最好的排序算法的时间复杂度都为 `O(NlogN)` 。这里使用其他方法。 -#### 方法一:双堆法 +### 方法一:双堆法 维护两个堆,一个大顶堆,一个小顶堆。大顶堆中最大的数**小于等于**小顶堆中最小的数;保证这两个堆中的元素个数的差不超过 1。 若数据总数为**偶数**,当这两个堆建好之后,**中位数就是这两个堆顶元素的平均值**。当数据总数为**奇数**时,根据两个堆的大小,**中位数一定在数据多的堆的堆顶**。 -``` java +```java class MedianFinder { - + private PriorityQueue maxHeap; private PriorityQueue minHeap; @@ -25,14 +25,14 @@ class MedianFinder { maxHeap = new PriorityQueue<>(Comparator.reverseOrder()); minHeap = new PriorityQueue<>(Integer::compareTo); } - + public void addNum(int num) { if (maxHeap.isEmpty() || maxHeap.peek() > num) { maxHeap.offer(num); } else { minHeap.offer(num); } - + int size1 = maxHeap.size(); int size2 = minHeap.size(); if (size1 - size2 > 1) { @@ -41,23 +41,23 @@ class MedianFinder { maxHeap.offer(minHeap.poll()); } } - + public double findMedian() { int size1 = maxHeap.size(); int size2 = minHeap.size(); - - return size1 == size2 + + return size1 == size2 ? (maxHeap.peek() + minHeap.peek()) * 1.0 / 2 : (size1 > size2 ? maxHeap.peek() : minHeap.peek()); } } ``` -> 见 LeetCode No.295:https://leetcode.com/problems/find-median-from-data-stream/ +> 见 [LeetCode No.295](https://leetcode.com/problems/find-median-from-data-stream/) 以上这种方法,需要把所有数据都加载到内存中。当数据量很大时,就不能这样了,因此,这种方法**适用于数据量较小的情况**。5 亿个数,每个数字占用 4B,总共需要 2G 内存。如果可用内存不足 2G,就不能使用这种方法了,下面介绍另一种方法。 -#### 方法二:分治法 +### 方法二:分治法 分治法的思想是把一个大的问题逐渐转换为规模较小的问题来求解。 @@ -71,6 +71,6 @@ class MedianFinder { > **注意**,当数据总数为偶数,如果划分后两个文件中的数据有相同个数,那么中位数就是数据较小的文件中的最大值与数据较大的文件中的最小值的平均值。 -### 方法总结 +## 方法总结 分治法,真香! diff --git a/docs/big-data/find-no-repeat-number.md b/docs/big-data/find-no-repeat-number.md index 9a265d8f2..0fc3b7b23 100644 --- a/docs/big-data/find-no-repeat-number.md +++ b/docs/big-data/find-no-repeat-number.md @@ -1,15 +1,16 @@ -## 如何在大量的数据中找出不重复的整数? +# 如何在大量的数据中找出不重复的整数? -### 题目描述 +## 题目描述 在 2.5 亿个整数中找出不重复的整数。注意:内存不足以容纳这 2.5 亿个整数。 -### 解答思路 +## 解答思路 + +### 方法一:分治法 -#### 方法一:分治法 与前面的题目方法类似,先将 2.5 亿个数划分到多个小文件,用 HashSet/HashMap 找出每个小文件中不重复的整数,再合并每个子结果,即为最终结果。 -#### 方法二:位图法 +### 方法二:位图法 **位图**,就是用一个或多个 bit 来标记某个元素对应的值,而键就是该元素。采用位作为单位来存储数据,可以大大节省存储空间。 @@ -17,25 +18,25 @@ 假设我们要对 `[0,7]` 中的 5 个元素 (6, 4, 2, 1, 5) 进行排序,可以采用位图法。0~7 范围总共有 8 个数,只需要 8bit,即 1 个字节。首先将每个位都置 0: -``` +``` 0 0 0 0 0 0 0 0 ``` 然后遍历 5 个元素,首先遇到 6,那么将下标为 6 的位的 0 置为 1;接着遇到 4,把下标为 4 的位 的 0 置为 1: -``` +``` 0 0 0 0 1 0 1 0 ``` 依次遍历,结束后,位数组是这样的: -``` +``` 0 1 1 0 1 1 1 0 ``` 每个为 1 的位,它的下标都表示了一个数: -``` +``` for i in range(8): if bits[i] == 1: print(i) @@ -47,14 +48,16 @@ for i in range(8): **那么对于这道题**,我们用 2 个 bit 来表示各个数字的状态: -* 00 表示这个数字没出现过; -* 01 表示这个数字出现过一次(即为题目所找的不重复整数); -* 10 表示这个数字出现了多次。 +- 00 表示这个数字没出现过; +- 01 表示这个数字出现过一次(即为题目所找的不重复整数); +- 10 表示这个数字出现了多次。 -那么这 232 个整数,总共所需内存为 232*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作: +那么这 232 个整数,总共所需内存为 232\*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作: 遍历 2.5 亿个整数,查看位图中对应的位,如果是 00,则变为 01,如果是 01 则变为 10,如果是 10 则保持不变。遍历结束后,查看位图,把对应位是 01 的整数输出即可。 -### 方法总结 +当然,本题中特别说明:**内存不足以容纳这 2.5 亿个整数**,2.5 亿个整数的内存大小为:2.5e8/1024/1024/1024 \* 4=3.72GB, 如果内存大于 1GB,是可以通过位图法解决的。 + +## 方法总结 -**判断数字是否重复的问题**,位图法是一种非常高效的方法。 +**判断数字是否重复的问题**,位图法是一种非常高效的方法,当然前提是:内存要满足位图法所需要的存储空间。 diff --git a/docs/big-data/find-rank-top-500-numbers.md b/docs/big-data/find-rank-top-500-numbers.md index 9ba055924..8b840c36d 100644 --- a/docs/big-data/find-rank-top-500-numbers.md +++ b/docs/big-data/find-rank-top-500-numbers.md @@ -1,10 +1,10 @@ -## 如何找出排名前 500 的数? +# 如何找出排名前 500 的数? -### 题目描述 +## 题目描述 -有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20*500 个数中找出前 500 的数? +有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20\*500 个数中找出前 500 的数? -### 解答思路 +## 解答思路 对于 TopK 问题,最常用的方法是使用堆排序。对本题而言,假设数组降序排列,可以采用以下方法: @@ -16,7 +16,7 @@ > 为了在堆中取出一个数据后,能知道它是从哪个数组中取出的,从而可以从这个数组中取下一个值,可以把数组的指针存放到堆中,对这个指针提供比较大小的方法。 -``` java +```java import lombok.Data; import java.util.Arrays; @@ -104,6 +104,6 @@ class Test { } ``` -### 方法总结 +## 方法总结 求 TopK,不妨考虑一下堆排序? diff --git a/docs/big-data/find-top-1-ip.md b/docs/big-data/find-top-1-ip.md index 8289b6e9f..2f361dad6 100644 --- a/docs/big-data/find-top-1-ip.md +++ b/docs/big-data/find-top-1-ip.md @@ -1,16 +1,16 @@ -## 如何找出某一天访问百度网站最多的 IP? +# 如何找出某一天访问百度网站最多的 IP? -### 题目描述 +## 题目描述 现有海量日志数据保存在一个超大文件中,该文件无法直接读入内存,要求从中提取某天访问百度次数最多的那个 IP。 -### 解答思路 +## 解答思路 这道题只关心某一天访问百度最多的 IP,因此,可以首先对文件进行一次遍历,把这一天访问百度 IP 的相关信息记录到一个单独的大文件中。接下来采用的方法与上一题一样,大致就是先对 IP 进行哈希映射,接着使用 HashMap 统计重复 IP 的次数,最后计算出重复次数最多的 IP。 > 注:这里只需要找出出现次数最多的 IP,可以不必使用堆,直接用一个变量 max 即可。 -### 方法总结 +## 方法总结 1. 分而治之,进行哈希取余; 2. 使用 HashMap 统计频数; diff --git a/docs/big-data/find-top-100-words.md b/docs/big-data/find-top-100-words.md index 391c7a0bf..6489768ee 100644 --- a/docs/big-data/find-top-100-words.md +++ b/docs/big-data/find-top-100-words.md @@ -1,22 +1,22 @@ -## 如何从大量数据中找出高频词? +# 如何从大量数据中找出高频词? -### 题目描述 +## 题目描述 有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。 -### 解答思路 +## 解答思路 由于内存限制,我们依然无法直接将大文件的所有词一次读到内存中。因此,同样可以采用**分治策略**,把一个大文件分解成多个小文件,保证每个文件的大小小于 1MB,进而直接将单个小文件读取到内存中进行处理。 **思路如下**: -首先遍历大文件,对遍历到的每个词x,执行 `hash(x) % 5000` ,将结果为 i 的词存放到文件 ai 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。 +首先遍历大文件,对遍历到的每个词 x,执行 `hash(x) % 5000` ,将结果为 i 的词存放到文件 ai 中。遍历结束后,我们可以得到 5000 个小文件。每个小文件的大小为 200KB 左右。如果有的小文件大小仍然超过 1MB,则采用同样的方式继续进行分解。 接着统计每个小文件中出现频数最高的 100 个词。最简单的方式是使用 HashMap 来实现。其中 key 为词,value 为该词出现的频率。具体方法是:对于遍历到的词 x,如果在 map 中不存在,则执行 `map.put(x, 1)` ;若存在,则执行 `map.put(x, map.get(x)+1)` ,将该词频数加 1。 上面我们统计了每个小文件单词出现的频数。接下来,我们可以通过维护一个**小顶堆**来找出所有词中出现频数最高的 100 个。具体方法是:依次遍历每个小文件,构建一个**小顶堆**,堆大小为 100。如果遍历到的词的出现次数大于堆顶词的出现次数,则用新词替换堆顶的词,然后重新调整为**小顶堆**,遍历结束后,小顶堆上的词就是出现频数最高的 100 个词。 -### 方法总结 +## 方法总结 1. 分而治之,进行哈希取余; 2. 使用 HashMap 统计频数; diff --git a/docs/big-data/images/qrcode-for-doocs.jpg b/docs/big-data/images/qrcode-for-doocs.jpg deleted file mode 100644 index bd1db5d11..000000000 Binary files a/docs/big-data/images/qrcode-for-doocs.jpg and /dev/null differ diff --git a/docs/big-data/images/qrcode-for-yanglbme.jpg b/docs/big-data/images/qrcode-for-yanglbme.jpg deleted file mode 100644 index 5bd385bca..000000000 Binary files a/docs/big-data/images/qrcode-for-yanglbme.jpg and /dev/null differ diff --git a/docs/big-data/images/topk-trie.png b/docs/big-data/images/topk-trie.png new file mode 100644 index 000000000..b14235a98 Binary files /dev/null and b/docs/big-data/images/topk-trie.png differ diff --git a/docs/big-data/sort-the-query-strings-by-counts.md b/docs/big-data/sort-the-query-strings-by-counts.md index 1f386f2a3..2fceb83b4 100644 --- a/docs/big-data/sort-the-query-strings-by-counts.md +++ b/docs/big-data/sort-the-query-strings-by-counts.md @@ -1,24 +1,24 @@ -## 如何按照 query 的频度排序? +# 如何按照 query 的频度排序? -### 题目描述 +## 题目描述 有 10 个文件,每个文件大小为 1G,每个文件的每一行存放的都是用户的 query,每个文件的 query 都可能重复。要求按照 query 的频度排序。 -### 解答思路 +## 解答思路 如果 query 的重复度比较大,可以考虑一次性把所有 query 读入内存中处理;如果 query 的重复率不高,那么可用内存不足以容纳所有的 query,这时候就需要采用分治法或其他的方法来解决。 -#### 方法一:HashMap 法 +### 方法一:HashMap 法 如果 query 重复率高,说明不同 query 总数比较小,可以考虑把所有的 query 都加载到内存中的 HashMap 中。接着就可以按照 query 出现的次数进行排序。 -#### 方法二:分治法 +### 方法二:分治法 -分治法需要根据数据量大小以及可用内存的大小来确定问题划分的规模。对于这道题,可以顺序遍历 10 个文件中的 query,通过 Hash 函数 `hash(query) % 10` 把这些 query 划分到 10 个小文件中。之后对每个小文件使用 HashMap 统计 query 出现次数,根据次数排序并写入到零外一个单独文件中。 +分治法需要根据数据量大小以及可用内存的大小来确定问题划分的规模。对于这道题,可以顺序遍历 10 个文件中的 query,通过 Hash 函数 `hash(query) % 10` 把这些 query 划分到 10 个小文件中。之后对每个小文件使用 HashMap 统计 query 出现次数,根据次数排序并写入到另外一个单独文件中。 接着对所有文件按照 query 的次数进行排序,这里可以使用归并排序(由于无法把所有 query 都读入内存,因此需要使用外排序)。 -### 方法总结 +## 方法总结 -* 内存若够,直接读入进行排序; -* 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。 +- 内存若够,直接读入进行排序; +- 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。 diff --git a/docs/big-data/topk-problems-and-solutions.md b/docs/big-data/topk-problems-and-solutions.md new file mode 100644 index 000000000..def80ee6f --- /dev/null +++ b/docs/big-data/topk-problems-and-solutions.md @@ -0,0 +1,163 @@ +# 大数据中 TopK 问题的常用套路 + +对于海量数据到处理经常会涉及到 topK 问题。在设计数据结构和算法的时候,主要需要考虑的应该是当前算法(包括数据结构)跟给定情境(比如数据量级、数据类型)的适配程度,和当前问题最核心的瓶颈(如降低时间复杂度,还是降低空间复杂度)是什么。 + +首先,我们来举几个常见的 topK 问题的例子: + +1. 给定 100 个 int 数字,在其中找出最大的 10 个; +1. 给定 10 亿个 int 数字,在其中找出最大的 10 个(这 10 个数字可以无序); +1. 给定 10 亿个 int 数字,在其中找出最大的 10 个(这 10 个数字依次排序); +1. 给定 10 亿个不重复的 int 数字,在其中找出最大的 10 个; +1. 给定 10 个数组,每个数组中有 1 亿个 int 数字,在其中找出最大的 10 个; +1. 给定 10 亿个 string 类型的数字,在其中找出最大的 10 个(仅需要查 1 次); +1. 给定 10 亿个 string 类型的数字,在其中找出最大的 k 个(需要反复多次查询,其中 k 是一个随机数字)。 + +上面这些问题看起来很相似,但是解决的方式却千差万别。稍有不慎,就可能使得 topK 问题成为系统的瓶颈。不过也不用太担心,接下来我会总结几种常见的解决思路,遇到问题的时候,大家把这些基础思路融会贯通并且杂糅组合,即可做到见招拆招。 +
+ +## 1. 堆排序法 + +这里说的是堆排序法,而不是快排或者希尔排序。虽然理论时间复杂度都是 `O(nlogn)`,但是堆排在做 topK 的时候有一个优势,就是可以维护一个仅包含 k 个数字的小顶堆(想清楚,为啥是小顶堆哦),当新加入的数字大于堆顶数字的时候,将堆顶元素剔除,并加入新的数字。 + +用 C++ 来说明,堆在 stl 中是 priority_queue(不是 set)。 + +```cpp +int main() { + const int topK = 3; + vector vec = {4,1,5,8,7,2,3,0,6,9}; + priority_queue, greater<>> pq; // 小顶堆 + for (const auto& x : vec) { + pq.push(x); + if (pq.size() > topK) { + // 如果超出个数,则弹出堆顶(最小的)数据 + pq.pop(); + } + } + + while (!pq.empty()) { + cout << pq.top() << endl; // 输出依次为7,8,9 + pq.pop(); + } + + return 0; +} +``` + +> Java 中同样提供了 PriorityQueue 的数据结构。 + +## 2. 类似快排法 + +快排大家都知道,针对 topK 问题,可以对快排进行改进。仅对部分数据进行递归计算。比如,在 100 个数字中,找最大的 10 个,第一次循环的时候,povit 被移动到了 80 的位置,则接下来仅需要在后面的 20 个数字中找最大的 10 个即可。 + +这样做的优势是,理论最优时间复杂度可以达到 `O(n)`,不过平均时间复杂度还是 `O(nlogn)`。需要说明的是,通过这种方式,找出来的最大的 k 个数字之间,是无序的。 + +```cpp +int partition(vector& arr, int begin, int end) { + int left = begin; + int right = end; + int povit = arr[begin]; + + while (left < right) { + while (left < right && arr[right] >= povit) {right--;} + while (left < right && arr[left] <= povit) {left++;} + if (left < right) {swap(arr[left], arr[right]);} + } + + swap(arr[begin], arr[left]); + return left; +} + +void partSort(vector& arr, int begin, int end, int target) { + if (begin >= end) { + return; + } + + int povit = partition(arr, begin, end); + if (target < povit) { + partSort(arr, begin, povit - 1, target); + } else if (target > povit) { + partSort(arr, povit + 1, end, target); + } +} + +vector getMaxNumbers(vector& arr, int k) { + int size = (int)arr.size(); + // 把求最大的k个数,转换成求最小的size-k个数字 + int target = size - k; + partSort(arr, 0, size - 1, target); + vector ret(arr.end() - k, arr.end()); + return ret; +} + +int main() { + vector vec = {4,1,5,8,7,2,3,0,6,9}; + auto ret = getMaxNumbers(vec, 3); + + for (auto x : ret) { + cout << x << endl; // 输出7,8,9(理论上无序) + } + + return 0; +} +``` + +
+ +## 3. 使用 bitmap + +有时候 topK 问题会遇到数据量过大,内存无法全部加载。这个时候,可以考虑将数据存放至 bitmap 中,方便查询。 + +比如,给出 10 个 int 类型的数据,分别是【13,12,11,1,2,3,4,5,6,7】,int 类型的数据每个占据 4 个字节,那这个数组就占据了 40 个字节。现在,把它们放到一个 16 个长度 bool 的 bitmap 中,结果就是【0,1,1,1,1,1,1,1,0,0,0,1,1,1,0,0】,在将空间占用降低至 4 字节的同时,也可以很方便的看出,最大的 3 个数字,分别是 11,12 和 13。 + +需要说明的是,bitmap 结合跳表一起使用往往有奇效。比如以上数据还可以记录成:从第 1 位开始,有连续 7 个 1;从第 11 位开始,有连续 3 个 1。这样做,空间复杂度又得到了进一步的降低。 + +这种做法的优势,当然是降低了空间复杂度。不过需要注意一点,bitmap 比较适合不重复且有范围(比如,数据均在 0 ~ 10 亿之间)的数据的查询。至于有重复数据的情况,可以考虑与 hash 等结构的混用。 +
+ +## 4. 使用 hash + +如果遇到了查询 string 类型数据的大小,可以考虑 hash 方法。 + +举个例子,10 个 string 数字【"1001","23","1002","3003","2001","1111","65","834","5","987"】找最大的 3 个。我们先通过长度进行 hash,得到长度最大为 4,且有 5 个长度为 4 的 string。接下来再通过最高位值做 hash,发现有 1 个最高位为"3"的,1 个为"2"的,3 个为"1"的。接下来,可以通过再设计 hash 函数,或者是循环的方式,在 3 个最高位为"1"的 string 中找到最大的一个,即可找到 3 个最值大的数据。 + +这种方法比较适合网址或者电话号码的查询。缺点就是如果需要多次查询的话,需要多次计算 hash,并且需要根据实际情况设计多个 hash 函数。 +
+ +## 5. 字典树 + +字典树(trie)的具体结构和查询方式,不在这里赘述了,自行百度一下就有很多。这里主要说一下优缺点。 + +![](./images/topk-trie.png) + +字典树的思想,还是通过前期建立索引信息,后期可以反复多次查询,并且后期增删数据也很方便。比较适合于需要反复多次查询的情况。 + +比如,反复多次查询字符序(例如:z>y>...>b>a)最大的 k 个 url 这种,使用字典树把数据存储一遍,就非常适合。既减少了空间复杂度,也加速了查询效率。 +
+ +## 6. 混合查询 + +以上几种方法,都是比较独立的方法。其实,在实际工作中,遇到更多的问题还是混合问题,这就需要我们对相关的内容,融会贯通并且做到活学活用。 + +我举个例子:我们的分布式服务跑在 10 台不同机器上,每台机器上部署的服务均被请求 10000 次,并且记录了个这 10000 次请求的耗时(耗时值为 int 数据),找出这 10\*10000 次请求中,从高到低的找出耗时最大的 50 个。看看这个问题,很现实吧。我们试着用上面介绍的方法,组合一下来求解。 + +### 方法一 + +首先,对每台机器上的 10000 个做类似快排,找出每台机器上 top50 的耗时信息。此时,单机上的这 50 条数据是无序的。 + +然后,再将 10 台机器上的 50 条数据(共 500 条)放到一起,再做一次类似快排,找到最大的 50 个(此时应该这 50 个应该是无序的)。 + +最后,对这 50 个数据做快排,从而得到最终结果。 + +### 方法二 + +首先通过堆排,分别找出 10 台机器上耗时最高的 50 个数据,此时的这 50 个数据,已经是从大到小有序的了。 + +然后,我们依次取出 10 台机器中,耗时最高的 5 条放入小顶堆中。 + +最后,遍历 10 台机器上的数据,每台机器从第 6 个数据开始往下循环,如果这个值比堆顶的数据大,则抛掉堆顶数据并且把它加入,继续用下一个值进行同样比较。如果这个值比堆顶的值小,则结束当前循环,并且在下一台机器上做同样操作。 + +以上我介绍了两种方法,并不是为了说明哪种方法更好,或者时间复杂度更低。而是想说同样的事情有多种不同的解决方法,而且随着数据量的增加,可能会需要更多组合形式。在这个领域,数据决定了数据结构,数据结构决定了算法。 + +**没有最好的方法,只有不断找寻更好的方法的程序员。适合的,才会是最好的。** + +嗯,加油,你可以找到更好的!!! diff --git a/docs/distributed-system/README.md b/docs/distributed-system/README.md index 6d0599ec7..13d3c17b5 100644 --- a/docs/distributed-system/README.md +++ b/docs/distributed-system/README.md @@ -1,55 +1,53 @@ # 分布式系统 -## [面试连环炮](/docs/distributed-system/distributed-system-interview.md) +## [面试连环炮](./distributed-system-interview.md) ## 系统拆分 -* [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](/docs/distributed-system/why-dubbo.md) +- [为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?](./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) -* [CAP定理的P是什么](/docs/distributed-system/distributed-system-cap.md) +- [说一下 Dubbo 的工作原理?注册中心挂了可以继续通信吗?](./dubbo-operating-principle.md) +- [Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?](./dubbo-serialization-protocol.md) +- [Dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?](./dubbo-load-balancing.md) +- [Dubbo 的 SPI 思想是什么?](./dubbo-spi.md) +- [如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试?](./dubbo-service-management.md) +- [分布式服务接口的幂等性如何设计(比如不能重复扣款)?](./distributed-system-idempotency.md) +- [分布式服务接口请求的顺序性如何保证?](./distributed-system-request-sequence.md) +- [如何自己设计一个类似 Dubbo 的 RPC 框架?](./dubbo-rpc-design.md) +- [CAP 定理的 P 是什么](./distributed-system-cap.md) ## 分布式锁 -* [Zookeeper 都有哪些应用场景?](/docs/distributed-system/zookeeper-application-scenarios.md) -* [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md) +- [Zookeeper 都有哪些应用场景?](./zookeeper-application-scenarios.md) +- [使用 Redis 如何设计分布式锁?使用 Zookeeper 来设计分布式锁可以吗?以上两种分布式锁的实现方式哪种效率比较高?](./distributed-lock-redis-vs-zookeeper.md) ## 分布式事务 -* [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](/docs/distributed-system/distributed-transaction.md) +- [分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?](./distributed-transaction.md) ## 分布式会话 -* [集群部署时的分布式 Session 如何实现?](/docs/distributed-system/distributed-session.md) +- [集群部署时的分布式 Session 如何实现?](./distributed-session.md) --- ## 公众号 -GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - - - - + + + +
- -
- 公众平台 -

-
- -
- 个人微信 -

-
+
+
+
+
+ +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! + +
diff --git a/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md b/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md index 3f6f4f993..d4093fba4 100644 --- a/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md +++ b/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md @@ -1,4 +1,7 @@ +# Redis vs Zookeeper 实现分布式锁 + ## 面试题 + 一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? ## 面试官心理分析 @@ -13,17 +16,17 @@ 这个分布式锁有 3 个重要的考量点: -* 互斥(只能有一个客户端获取锁) -* 不能死锁 -* 容错(只要大部分 Redis 节点创建了这把锁就可以) +- 互斥(只能有一个客户端获取锁) +- 不能死锁 +- 容错(只要大部分 Redis 节点创建了这把锁就可以) #### Redis 最普通的分布式锁 第一个最普通的实现方式,就是在 Redis 里使用 `SET key value [EX seconds] [PX milliseconds] NX` 创建一个 key,这样就算加锁。其中: -- `NX`:表示只有 `key` 不存在的时候才会设置成功,如果此时 redis 中存在这个 `key`,那么设置失败,返回 `nil`。 -- `EX seconds`:设置 `key` 的过期时间,精确到秒级。意思是 `seconds` 秒后锁自动释放,别人创建的时候如果发现已经有了就不能加锁了。 -- `PX milliseconds`:同样是设置 `key` 的过期时间,精确到毫秒级。 +- `NX`:表示只有 `key` 不存在的时候才会设置成功,如果此时 redis 中存在这个 `key`,那么设置失败,返回 `nil`。 +- `EX seconds`:设置 `key` 的过期时间,精确到秒级。意思是 `seconds` 秒后锁自动释放,别人创建的时候如果发现已经有了就不能加锁了。 +- `PX milliseconds`:同样是设置 `key` 的过期时间,精确到毫秒级。 比如执行以下命令: @@ -33,7 +36,7 @@ SET resource_name my_random_value PX 30000 NX 释放锁就是删除 key ,但是一般可以用 `lua` 脚本删除,判断 value 一样才删除: -``` lua +```lua -- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。 if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) @@ -51,7 +54,7 @@ end 这个场景是假设有一个 Redis cluster,有 5 个 Redis master 实例。然后执行如下步骤获取一把锁: 1. 获取当前时间戳,单位是毫秒; -2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,过期时间较短,一般就几十毫秒; +2. 跟上面类似,轮流尝试在每个 master 节点上创建锁,超时时间较短,一般就几十毫秒(客户端为了获取锁而使用的超时时间比自动释放锁的总时间要小。例如,如果自动释放时间是 10 秒,那么超时时间可能在 `5~50` 毫秒范围内); 3. 尝试在**大多数节点**上建立一个锁,比如 5 个节点就要求是 3 个节点 `n / 2 + 1` ; 4. 客户端计算建立好锁的时间,如果建立锁的时间小于超时时间,就算建立成功了; 5. 要是锁建立失败了,那么就依次之前建立过的锁删除; @@ -65,7 +68,7 @@ end zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能**注册个监听器**监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。 -``` java +```java /** * ZooKeeperSession */ @@ -93,7 +96,7 @@ public class ZooKeeperSession { /** * 获取分布式锁 - * + * * @param productId */ public Boolean acquireDistributedLock(Long productId) { @@ -126,7 +129,7 @@ public class ZooKeeperSession { /** * 释放掉一个分布式锁 - * + * * @param productId */ public void releaseDistributedLock(Long productId) { @@ -177,7 +180,7 @@ public class ZooKeeperSession { /** * 获取单例 - * + * * @return */ public static ZooKeeperSession getInstance() { @@ -198,7 +201,7 @@ public class ZooKeeperSession { 如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听**排在自己前面**的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 ZooKeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。 -``` java +```java public class ZooKeeperDistributedLock implements Watcher { private ZooKeeper zk; @@ -252,32 +255,32 @@ public class ZooKeeperDistributedLock implements Watcher { public boolean tryLock() { try { - // 传入进去的locksRoot + “/” + productId - // 假设productId代表了一个商品id,比如说1 - // locksRoot = locks - // /locks/10000000000,/locks/10000000001,/locks/10000000002 + // 传入进去的locksRoot + “/” + productId + // 假设productId代表了一个商品id,比如说1 + // locksRoot = locks + // /locks/10000000000,/locks/10000000001,/locks/10000000002 lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); - + // 看看刚创建的节点是不是最小的节点 - // locks:10000000000,10000000001,10000000002 + // locks:10000000000,10000000001,10000000002 List locks = zk.getChildren(locksRoot, false); Collections.sort(locks); - - if(lockNode.equals(locksRoot+"/"+ locks.get(0))){ - //如果是最小的节点,则表示取得锁 + + if (lockNode.equals(locksRoot + "/" + locks.get(0))) { + // 如果是最小的节点,则表示取得锁 return true; } - - //如果不是最小的节点,找到比自己小1的节点 - int previousLockIndex = -1; - for(int i = 0; i < locks.size(); i++) { - if(lockNode.equals(locksRoot + “/” + locks.get(i))) { - previousLockIndex = i - 1; - break; - } - } - - this.waitNode = locks.get(previousLockIndex); + + // 如果不是最小的节点,找到比自己小1的节点 + int previousLockIndex = -1; + for (int i = 0; i < locks.size(); i++) { + if (lockNode.equals(locksRoot + "/" +locks.get(i))){ + previousLockIndex = i - 1; + break; + } + } + + this.waitNode = locks.get(previousLockIndex); } catch (KeeperException e) { throw new LockException(e); } catch (InterruptedException e) { @@ -325,10 +328,14 @@ public class ZooKeeperDistributedLock implements Watcher { } ``` +但是,使用 zk 临时节点会存在另一个问题:由于 zk 依靠 session 定期的心跳来维持客户端,如果客户端进入长时间的 GC,可能会导致 zk 认为客户端宕机而释放锁,让其他的客户端获取锁,但是客户端在 GC 恢复后,会认为自己还持有锁,从而可能出现多个客户端同时获取到锁的情形。[#209](https://github.com/doocs/advanced-java/issues/209) + +针对这种情况,可以通过 JVM 调优,尽量避免长时间 GC 的情况发生。 + ### redis 分布式锁和 zk 分布式锁的对比 -* redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。 -* zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 +- redis 分布式锁,其实**需要自己不断去尝试获取锁**,比较消耗性能。 +- zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。 另外一点就是,如果是 Redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。 diff --git a/docs/distributed-system/distributed-session.md b/docs/distributed-system/distributed-session.md index fd3d87ebb..a78c3e4db 100644 --- a/docs/distributed-system/distributed-session.md +++ b/docs/distributed-system/distributed-session.md @@ -1,4 +1,7 @@ +# 如何实现分布式 Session? + ## 面试题 + 集群部署时的分布式 Session 如何实现? ## 面试官心理分析 @@ -25,11 +28,11 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存 ### Tomcat + Redis -这个其实还挺方便的,就是使用 Session 的代码,跟以前一样,还是基于 Tomcat 原生的 Session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 Tomcat 都将 Session 数据存储到 Redis 即可。 +这个其实还挺方便的,就是使用 Session 的代码,跟以前一样,还是基于 Tomcat 原生的 Session 支持即可,然后就是用一个叫做 `Tomcat RedisSessionManager` 的东西,让所有我们部署的 Tomcat 都将 Session 数据存储到 Redis 即可。 在 Tomcat 的配置文件中配置: -``` xml +```xml -``` +``` 然后指定 Redis 的 host 和 port 就 ok 了。 @@ -61,7 +64,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存 在 pom.xml 中配置: -``` xml +```xml org.springframework.session spring-session-data-redis @@ -76,7 +79,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存 在 Spring 配置文件中配置: -``` xml +```xml @@ -100,7 +103,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存 在 web.xml 中配置: -``` xml +```xml springSessionRepositoryFilter org.springframework.web.filter.DelegatingFilterProxy @@ -113,7 +116,7 @@ Session 是啥?浏览器有个 Cookie,在一段时间内这个 Cookie 都存 示例代码: -``` java +```java @RestController @RequestMapping("/test") public class TestController { diff --git a/docs/distributed-system/distributed-system-cap.md b/docs/distributed-system/distributed-system-cap.md index a2ca84c68..96c491553 100644 --- a/docs/distributed-system/distributed-system-cap.md +++ b/docs/distributed-system/distributed-system-cap.md @@ -1,3 +1,5 @@ +# 分布式系统中的 CAP 定理 + ## 分布式系统 CAP 定理 P 代表什么含义 作者之前在看 CAP 定理时抱有很大的疑惑,CAP 定理的定义是指在分布式系统中三者只能满足其二,也就是存在分布式 CA 系统的。作者在网络上查阅了很多关于 CAP 文章,虽然这些文章对于 P 的解释五花八门,但总结下来这些观点大多都是指 P 是不可缺少的,也就是说在分布式系统只能是 AP 或者 CP,这种理论与我之前所认识的理论(存在分布式 CA 系统)是冲突的,所以才有了疑惑。 @@ -8,16 +10,46 @@ 在理论计算机科学中,CAP 定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点: -- 一致性(Consistency) (等同于所有节点访问同一份最新的数据副本) -- 可用性(Availability)(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据) -- 分区容错性(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。) +- 一致性(Consistency) (等同于所有节点访问同一份最新的数据副本) +- 可用性(Availability)(每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据) +- 分区容错性(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。) ### 分区容错性(Partition tolerance) 理解 CAP 理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致,即丧失了 C 性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了 A 性质。除非两个节点可以互相通信,才能既保证 C 又保证 A,这又会导致丧失 P 性质。 -- P 指的是分区容错性,分区现象产生后需要容错,容错是指在 A 与 C 之间选择。如果分布式系统没有分区现象(没有出现不一致不可用情况) 本身就没有分区 ,既然没有分区则就更没有分区容错性 P。 -- 无论我设计的系统是 AP 还是 CP 系统如果没有出现不一致不可用。 则该系统就处于 CA 状态 -- P 的体现前提是得有分区情况存在 +- P 指的是分区容错性,分区现象产生后需要容错,容错是指在 A 与 C 之间选择。如果分布式系统没有分区现象(没有出现不一致不可用情况) 本身就没有分区 ,既然没有分区则就更没有分区容错性 P。 +- 无论我设计的系统是 AP 还是 CP 系统如果没有出现不一致不可用。 则该系统就处于 CA 状态 +- P 的体现前提是得有分区情况存在 > 文章来源:[维基百科 CAP 定理](https://zh.wikipedia.org/wiki/CAP%E5%AE%9A%E7%90%86) + +## 几个常用的 CAP 框架对比 + +| 框架 | 所属 | +| --------- | ---- | +| Eureka | AP | +| Zookeeper | CP | +| Consul | CP | + +### Eureka + +> Eureka 保证了可用性,实现最终一致性。 + +Eureka 所有节点都是平等的所有数据都是相同的,且 Eureka 可以相互交叉注册。 +Eureka client 使用内置轮询负载均衡器去注册,有一个检测间隔时间,如果在一定时间内没有收到心跳,才会移除该节点注册信息;如果客户端发现当前 Eureka 不可用,会切换到其他的节点,如果所有的 Eureka 都跪了,Eureka client 会使用最后一次数据作为本地缓存;所以以上的每种设计都是他不具备`一致性`的特性。 + +注意:因为 EurekaAP 的特性和请求间隔同步机制,在服务更新时候一般会手动通过 Eureka 的 api 把当前服务状态设置为`offline`,并等待 2 个同步间隔后重新启动,这样就能保证服务更新节点对整体系统的影响 + +### Zookeeper + +> 强一致性 + +Zookeeper 在选举 leader 时会停止服务,只有成功选举 leader 成功后才能提供服务,选举时间较长;内部使用 paxos 选举投票机制,只有获取半数以上的投票才能成为 leader,否则重新投票,所以部署的时候最好集群节点不小于 3 的奇数个(但是谁能保证跪掉后节点也是奇数个呢);Zookeeper 健康检查一般是使用 tcp 长链接,在内部网络抖动时或者对应节点阻塞时候都会变成不可用,这里还是比较危险的; + +### Consul + +和 Zookeeper 一样数据 CP + +Consul 注册时候只有过半的节点都写入成功才认为注册成功;leader 挂掉时,重新选举期间整个 Consul 不可用,保证了强一致性但牺牲了可用性 +有很多 blog 说 Consul 属于 ap,官方已经确认他为 CP 机制 原文地址:https://www.consul.io/docs/intro/vs/serf diff --git a/docs/distributed-system/distributed-system-idempotency.md b/docs/distributed-system/distributed-system-idempotency.md index 4b5a78c6b..dc6453cf4 100644 --- a/docs/distributed-system/distributed-system-idempotency.md +++ b/docs/distributed-system/distributed-system-idempotency.md @@ -1,4 +1,7 @@ +# 分布式服务接口的幂等性如何设计? + ## 面试题 + 分布式服务接口的幂等性如何设计(比如不能重复扣款)? ## 面试官心理分析 @@ -21,9 +24,9 @@ 其实保证幂等性主要是三点: -* 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。 -* 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。 -* 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。 +- 对于每个请求必须有一个唯一的标识,举个栗子:订单支付请求,肯定得包含订单 id,一个订单 id 最多支付一次,对吧。 +- 每次处理完请求之后,必须有一个记录标识这个请求处理过了。常见的方案是在 mysql 中记录个状态啥的,比如支付之前记录一条这个订单的支付流水。 +- 每次接收请求需要进行判断,判断之前是否处理过。比如说,如果有一个订单已经支付了,就已经有了一条支付流水,那么如果重复发送这个请求,则此时先插入支付流水,orderId 已经存在了,唯一键约束生效,报错插入不进去的。然后你就不用再扣款了。 实际运作过程中,你要结合自己的业务来,比如说利用 Redis,用 orderId 作为唯一键。只有成功插入这个支付流水,才可以执行实际的支付扣款。 diff --git a/docs/distributed-system/distributed-system-interview.md b/docs/distributed-system/distributed-system-interview.md index 2c2f32a2b..e69cb887f 100644 --- a/docs/distributed-system/distributed-system-interview.md +++ b/docs/distributed-system/distributed-system-interview.md @@ -1,4 +1,5 @@ -## 分布式系统面试连环炮 +# 分布式系统面试连环炮 + 有一些同学,之前呢主要是做传统行业,或者外包项目,一直是在那种小的公司,技术一直都搞的比较简单。他们有共同的一个问题,就是都没怎么搞过分布式系统,现在互联网公司,一般都是做分布式的系统,大家都不是做底层的分布式系统、分布式存储系统 Hadoop HDFS、分布式计算系统 Hadoop MapReduce / Spark、分布式流式计算系统 Storm。 分布式业务系统,就是把原来用 Java 开发的一个大块系统,给拆分成**多个子系统**,多个子系统之间互相调用,形成一个大系统的整体。假设原来你做了一个 OA 系统,里面包含了权限模块、员工模块、请假模块、财务模块,一个工程,里面包含了一堆模块,模块与模块之间会互相去调用,1 台机器部署。现在如果你把这个系统给拆开,权限系统、员工系统、请假系统、财务系统 4 个系统,4 个工程,分别在 4 台机器上部署。一个请求过来,完成这个请求,这个员工系统,调用权限系统,调用请假系统,调用财务系统,4 个系统分别完成了一部分的事情,最后 4 个系统都干完了以后,才认为是这个请求已经完成了。 @@ -9,29 +10,29 @@ 面试官可能会问你以下问题。 -### 为什么要进行系统拆分? +## 为什么要进行系统拆分? -* 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?Dubbo 和 thrift 有什么区别呢? +- 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?Dubbo 和 thrift 有什么区别呢? -### 分布式服务框架 +## 分布式服务框架 -* 说一下的 Dubbo 的工作原理?注册中心挂了可以继续通信吗? -* Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的? -* Dubbo 负载均衡策略和高可用策略都有哪些?动态代理策略呢? -* Dubbo 的 SPI 思想是什么? -* 如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试? -* 分布式服务接口的幂等性如何设计(比如不能重复扣款)? -* 分布式服务接口请求的顺序性如何保证? -* 如何自己设计一个类似 Dubbo 的 RPC 框架? +- 说一下的 Dubbo 的工作原理?注册中心挂了可以继续通信吗? +- Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的? +- Dubbo 负载均衡策略和高可用策略都有哪些?动态代理策略呢? +- Dubbo 的 SPI 思想是什么? +- 如何基于 Dubbo 进行服务治理、服务降级、失败重试以及超时重试? +- 分布式服务接口的幂等性如何设计(比如不能重复扣款)? +- 分布式服务接口请求的顺序性如何保证? +- 如何自己设计一个类似 Dubbo 的 RPC 框架? -### 分布式锁 +## 分布式锁 -* 使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? +- 使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? -### 分布式事务 +## 分布式事务 -* 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证? +- 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证? -### 分布式会话 +## 分布式会话 -* 集群部署时的分布式 Session 如何实现? +- 集群部署时的分布式 Session 如何实现? diff --git a/docs/distributed-system/distributed-system-request-sequence.md b/docs/distributed-system/distributed-system-request-sequence.md index 3c8476fbf..45187f8c0 100644 --- a/docs/distributed-system/distributed-system-request-sequence.md +++ b/docs/distributed-system/distributed-system-request-sequence.md @@ -1,4 +1,7 @@ +# 分布式服务接口请求的顺序性如何保证? + ## 面试题 + 分布式服务接口请求的顺序性如何保证? ## 面试官心理分析 diff --git a/docs/distributed-system/distributed-transaction.md b/docs/distributed-system/distributed-transaction.md index 8f100e46b..5b55aa148 100644 --- a/docs/distributed-system/distributed-transaction.md +++ b/docs/distributed-system/distributed-transaction.md @@ -1,3 +1,5 @@ +# 分布式事务原理 + ## 面试题 分布式事务了解吗?你们是如何解决分布式事务问题的? @@ -12,12 +14,12 @@ 分布式事务的实现主要有以下 6 种方案: -- XA 方案 -- TCC 方案 -- SAGA 方案 -- 本地消息表 -- 可靠消息最终一致性方案 -- 最大努力通知方案 +- XA 方案 +- TCC 方案 +- SAGA 方案 +- 本地消息表 +- 可靠消息最终一致性方案 +- 最大努力通知方案 ### 两阶段提交方案/XA 方案 @@ -37,9 +39,9 @@ TCC 的全称是: `Try` 、 `Confirm` 、 `Cancel` 。 -* Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 -* Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 -* Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) +- Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行**锁定或者预留**。 +- Confirm 阶段:这个阶段说的是在各个服务中**执行实际的操作**。 +- Cancel 阶段:如果任何一个服务的业务方法执行出错,那么这里就需要**进行补偿**,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚) 这种方案说实话几乎很少人使用,我们用的也比较少,但是也有使用的场景。因为这个**事务回滚**实际上是**严重依赖于你自己写代码来回滚和补偿**了,会造成补偿代码巨大,非常之恶心。 @@ -69,18 +71,18 @@ TCC 的全称是: `Try` 、 `Confirm` 、 `Cancel` 。 所以 Saga 模式的适用场景是: -- 业务流程长、业务流程多; -- 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口。 +- 业务流程长、业务流程多; +- 参与者包含其它公司或遗留系统服务,无法提供 TCC 模式要求的三个接口。 #### 优势 -- 一阶段提交本地事务,无锁,高性能; -- 参与者可异步执行,高吞吐; -- 补偿服务易于实现,因为一个更新操作的反向操作是比较容易理解的。 +- 一阶段提交本地事务,无锁,高性能; +- 参与者可异步执行,高吞吐; +- 补偿服务易于实现,因为一个更新操作的反向操作是比较容易理解的。 #### 缺点 -- 不保证事务的隔离性。 +- 不保证事务的隔离性。 ### 本地消息表 diff --git a/docs/distributed-system/dubbo-load-balancing.md b/docs/distributed-system/dubbo-load-balancing.md index 62130bb09..1d13be9e0 100644 --- a/docs/distributed-system/dubbo-load-balancing.md +++ b/docs/distributed-system/dubbo-load-balancing.md @@ -1,4 +1,7 @@ +# Dubbo 负载均衡策略和集群容错策略 + ## 面试题 + dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢? ## 面试官心理分析 @@ -7,10 +10,10 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略 说白了,就是看你对 dubbo 熟悉不熟悉: -* dubbo 工作原理:服务注册、注册中心、消费者、代理通信、负载均衡; -* 网络通信、序列化:dubbo 协议、长连接、NIO、hessian 序列化协议; -* 负载均衡策略、集群容错策略、动态代理策略:dubbo 跑起来的时候一些功能是如何运转的?怎么做负载均衡?怎么做集群容错?怎么生成动态代理? -* dubbo SPI 机制:你了解不了解 dubbo 的 SPI 机制?如何基于 SPI 机制对 dubbo 进行扩展? +- dubbo 工作原理:服务注册、注册中心、消费者、代理通信、负载均衡; +- 网络通信、序列化:dubbo 协议、长连接、NIO、hessian 序列化协议; +- 负载均衡策略、集群容错策略、动态代理策略:dubbo 跑起来的时候一些功能是如何运转的?怎么做负载均衡?怎么做集群容错?怎么生成动态代理? +- dubbo SPI 机制:你了解不了解 dubbo 的 SPI 机制?如何基于 SPI 机制对 dubbo 进行扩展? ## 面试题剖析 @@ -20,6 +23,8 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略 默认情况下,dubbo 是 RandomLoadBalance ,即**随机**调用实现负载均衡,可以对 provider 不同实例**设置不同的权重**,会按照权重来负载均衡,权重越大分配流量越高,一般就用这个默认的就可以了。 +算法思想很简单。假设有一组服务器 servers = `[A, B, C]`,他们对应的权重为 weights = `[5, 3, 2]`,权重总和为 10。现在把这些权重值平铺在一维坐标值上,`[0, 5)` 区间属于服务器 A,`[5, 8)` 区间属于服务器 B,`[8, 10)` 区间属于服务器 C。接下来通过随机数生成器生成一个范围在 `[0, 10)` 之间的随机数,然后计算这个随机数会落到哪个区间上。比如数字 3 会落到服务器 A 对应的区间上,此时返回服务器 A 即可。权重越大的机器,在坐标轴上对应的区间范围就越大,因此随机数生成器生成的数字就会有更大的概率落到此区间内。只要随机数生成器产生的随机数分布性很好,在经过多次选择后,每个服务器被选中的次数比例接近其权重比例。比如,经过一万次选择后,服务器 A 被选中的次数大约为 5000 次,服务器 B 被选中的次数约为 3000 次,服务器 C 被选中的次数约为 2000 次。 + #### RoundRobinLoadBalance 这个的话默认就是均匀地将流量打到各个机器上去,但是如果各个机器的性能不一样,容易导致性能差的机器负载过高。所以此时需要调整权重,让性能差的机器承载权重小一些,流量少一些。 @@ -32,34 +37,41 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略 #### LeastActiveLoadBalance -这个就是自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给**不活跃的性能差的机器更少的请求**。 +官网对 `LeastActiveLoadBalance` 的解释是“**最小活跃数负载均衡**”,活跃调用数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求,那么此时请求会优先分配给该服务提供者。 + +最小活跃数负载均衡算法的基本思想是这样的: + +每个服务提供者会对应着一个活跃数 `active`。初始情况下,所有服务提供者的 `active` 均为 0。每当收到一个请求,对应的服务提供者的 `active` 会加 1,处理完请求后,`active` 会减 1。所以,如果服务提供者性能较好,处理请求的效率就越高,那么 `active` 也会下降的越快。因此可以给这样的服务提供者优先分配请求。 + +当然,除了最小活跃数,`LeastActiveLoadBalance` 在实现上还引入了权重值。所以准确的来说,`LeastActiveLoadBalance` 是基于加权最小活跃数算法实现的。 #### ConsistentHashLoadBalance 一致性 Hash 算法,相同参数的请求一定分发到一个 provider 上去,provider 挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。**如果你需要的不是随机负载均衡**,是要一类请求都到一个节点,那就走这个一致性 Hash 策略。 -> 关于 dubbo 负载均衡策略更加详细的描述,可以查看官网 http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html 。 +> 关于 dubbo 负载均衡策略更加详细的描述,可以查看官网 https://dubbo.apache.org/zh/docs/advanced/loadbalance 。 ### dubbo 集群容错策略 #### Failover Cluster 模式 + 失败自动切换,自动重试其他机器,**默认**就是这个,常见于读操作。(失败重试其它机器) 可以通过以下几种方式配置重试次数: -``` xml +```xml ``` 或者 -``` xml +```xml ``` 或者 -``` xml +```xml @@ -75,13 +87,13 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略 配置示例如下: -``` xml +```xml ``` 或者 -``` xml +```xml ``` @@ -97,7 +109,7 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略 逐个调用所有的 provider。任何一个 provider 出错则报错(从 `2.1.0` 版本开始支持)。通常用于通知所有提供者更新缓存或日志等本地资源信息。 -> 关于 dubbo 集群容错策略更加详细的描述,可以查看官网 http://dubbo.apache.org/zh-cn/docs/source_code_guide/cluster.html 。 +> 关于 dubbo 集群容错策略更加详细的描述,可以查看官网 https://dubbo.apache.org/zh/docs/advanced/fault-tolerent-strategy 。 ### dubbo 动态代理策略 diff --git a/docs/distributed-system/dubbo-operating-principle.md b/docs/distributed-system/dubbo-operating-principle.md index b5c04cb7f..9530f140a 100644 --- a/docs/distributed-system/dubbo-operating-principle.md +++ b/docs/distributed-system/dubbo-operating-principle.md @@ -1,4 +1,7 @@ +# Dubbo 的工作原理 + ## 面试题 + 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程? ## 面试官心理分析 @@ -9,29 +12,29 @@ MQ、ES、Redis、Dubbo,上来先问你一些**思考性的问题**、**原理 当然去年开始 spring cloud 非常火,现在大量的公司开始转向 spring cloud 了,spring cloud 人家毕竟是微服务架构的全家桶式的这么一个东西。但是因为很多公司还在用 dubbo,所以 dubbo 肯定会是目前面试的重点,何况人家 dubbo 现在重启开源社区维护了,捐献给了 apache,未来应该也还是有一定市场和地位的。 -既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc 分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。 +既然聊 dubbo,那肯定是先从 dubbo 原理开始聊了,你先说说 dubbo 支撑 rpc 分布式调用的架构啥的,然后说说一次 rpc 请求 dubbo 是怎么给你完成的,对吧。 ## 面试题剖析 ### dubbo 工作原理 -* 第一层:service 层,接口层,给服务提供者和消费者来实现的 -* 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的 -* 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信 -* 第四层:registry 层,服务注册层,负责服务的注册与发现 -* 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务 -* 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控 -* 第七层:protocal 层,远程调用层,封装 rpc 调用 -* 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步 -* 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口 -* 第十层:serialize 层,数据序列化层 +- 第一层:service 层,接口层,给服务提供者和消费者来实现的 +- 第二层:config 层,配置层,主要是对 dubbo 进行各种配置的 +- 第三层:proxy 层,服务代理层,无论是 consumer 还是 provider,dubbo 都会给你生成代理,代理之间进行网络通信 +- 第四层:registry 层,服务注册层,负责服务的注册与发现 +- 第五层:cluster 层,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务 +- 第六层:monitor 层,监控层,对 rpc 接口的调用次数和调用时间进行监控 +- 第七层:protocal 层,远程调用层,封装 rpc 调用 +- 第八层:exchange 层,信息交换层,封装请求响应模式,同步转异步 +- 第九层:transport 层,网络传输层,抽象 mina 和 netty 为统一接口 +- 第十层:serialize 层,数据序列化层 ### 工作流程 -* 第一步:provider 向注册中心去注册 -* 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务 -* 第三步:consumer 调用 provider -* 第四步:consumer 和 provider 都异步通知监控中心 +- 第一步:provider 向注册中心去注册 +- 第二步:consumer 从注册中心订阅服务,注册中心会通知 consumer 注册好的服务 +- 第三步:consumer 调用 provider +- 第四步:consumer 和 provider 都异步通知监控中心 ![dubbo-operating-principle](./images/dubbo-operating-principle.png) diff --git a/docs/distributed-system/dubbo-rpc-design.md b/docs/distributed-system/dubbo-rpc-design.md index 98926cda6..ad00f7373 100644 --- a/docs/distributed-system/dubbo-rpc-design.md +++ b/docs/distributed-system/dubbo-rpc-design.md @@ -1,12 +1,15 @@ +# 设计一个类似 Dubbo 的 RPC 框架 + ## 面试题 + 如何自己设计一个类似 Dubbo 的 RPC 框架? ## 面试官心理分析 说实话,就这问题,其实就跟问你如何自己设计一个 MQ 一样的道理,就考两个: -* 你有没有对某个 rpc 框架原理有非常深入的理解。 -* 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。 +- 你有没有对某个 rpc 框架原理有非常深入的理解。 +- 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。 ## 面试题剖析 @@ -16,11 +19,11 @@ 举个栗子,我给大家说个最简单的回答思路: -* 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。 -* 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。 -* 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。 -* 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。 -* 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。 -* 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。 +- 上来你的服务就得去注册中心注册吧,你是不是得有个注册中心,保留各个服务的信息,可以用 zookeeper 来做,对吧。 +- 然后你的消费者需要去注册中心拿对应的服务信息吧,对吧,而且每个服务可能会存在于多台机器上。 +- 接着你就该发起一次请求了,咋发起?当然是基于动态代理了,你面向接口获取到一个动态代理,这个动态代理就是接口在本地的一个代理,然后这个代理会找到服务对应的机器地址。 +- 然后找哪个机器发送请求?那肯定得有个负载均衡算法了,比如最简单的可以随机轮询是不是。 +- 接着找到一台机器,就可以跟它发送请求了,第一个问题咋发送?你可以说用 netty 了,nio 方式;第二个问题发送啥格式数据?你可以说用 hessian 序列化协议了,或者是别的,对吧。然后请求过去了。 +- 服务器那边一样的,需要针对你自己的服务生成一个动态代理,监听某个网络端口了,然后代理你本地的服务代码。接收到请求的时候,就调用对应的服务代码,对吧。 这就是一个最最基本的 rpc 框架的思路,先不说你有多牛逼的技术功底,哪怕这个最简单的思路你先给出来行不行? diff --git a/docs/distributed-system/dubbo-serialization-protocol.md b/docs/distributed-system/dubbo-serialization-protocol.md index ebf99a50b..b9989fe6f 100644 --- a/docs/distributed-system/dubbo-serialization-protocol.md +++ b/docs/distributed-system/dubbo-serialization-protocol.md @@ -1,4 +1,7 @@ +# Dubbo 的序列化协议 + ## 面试题 + dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的? ## 面试官心理分析 @@ -15,9 +18,9 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian ### dubbo 支持不同的通信协议 -* dubbo 协议 +- dubbo 协议 `dubbo://` -**默认**就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高。 +**默认**就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高,以及服务消费者机器数远大于服务提供者机器数的情况。 为了要支持高并发场景,一般是服务提供者就几台机器,但是服务消费者有上百台,可能每天调用量达到上亿次!此时用长连接是最合适的,就是跟每个服务消费者维持一个长连接就可以,可能总共就 100 个连接。然后后面直接基于长连接 NIO 异步通信,可以支撑高并发请求。 @@ -29,21 +32,41 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian ![dubbo-not-keep-connection](./images/dubbo-not-keep-connection.png) -* rmi 协议 +- rmi 协议 `rmi://` + +RMI 协议采用 JDK 标准的 java.rmi.\* 实现,采用阻塞式短连接和 JDK 标准序列化方式。多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用。 + +- hessian 协议 `hessian://` + +Hessian 1 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用。 + +- http 协议 `http://` + +基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现。走表单序列化。 + +- thrift 协议 `thrift://` + +当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等。 + +- webservice `webservice://` + +基于 WebService 的远程调用协议,基于 Apache CXF 的 frontend-simple 和 transports-http 实现。走 SOAP 文本序列化。 + +- memcached 协议 `memcached://` -走 Java 二进制序列化,多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用。 +基于 memcached 实现的 RPC 协议。 -* hessian 协议 +- redis 协议 `redis://` -走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用。 +基于 Redis 实现的 RPC 协议。 -* http 协议 +- rest 协议 `rest://` -走表单序列化。 +基于标准的 Java REST API——JAX-RS 2.0(Java API for RESTful Web Services 的简写)实现的 REST 调用支持。 -* webservice +- gPRC 协议 `grpc://` -走 SOAP 文本序列化。 +Dubbo 自 2.7.5 版本开始支持 gRPC 协议,对于计划使用 HTTP/2 通信,或者想利用 gRPC 带来的 Stream、反压、Reactive 编程等能力的开发者来说, 都可以考虑启用 gRPC 协议。 ### dubbo 支持的序列化协议 @@ -53,24 +76,24 @@ dubbo 支持 hession、Java 二进制序列化、json、SOAP 文本序列化多 Hessian 的对象序列化机制有 8 种原始类型: -* 原始二进制数据 -* boolean -* 64-bit date(64 位毫秒值的日期) -* 64-bit double -* 32-bit int -* 64-bit long -* null -* UTF-8 编码的 string +- 原始二进制数据 +- boolean +- 64-bit date(64 位毫秒值的日期) +- 64-bit double +- 32-bit int +- 64-bit long +- null +- UTF-8 编码的 string 另外还包括 3 种递归类型: -* list for lists and arrays -* map for maps and dictionaries -* object for objects +- list for lists and arrays +- map for maps and dictionaries +- object for objects 还有一种特殊的类型: -* ref:用来表示对共享对象的引用。 +- ref:用来表示对共享对象的引用。 ### 为什么 PB 的效率是最高的? diff --git a/docs/distributed-system/dubbo-service-management.md b/docs/distributed-system/dubbo-service-management.md index 126e5bb24..242d21b7b 100644 --- a/docs/distributed-system/dubbo-service-management.md +++ b/docs/distributed-system/dubbo-service-management.md @@ -1,4 +1,7 @@ +# 如何基于 Dubbo 进行服务治理 + ## 面试题 + 如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试? ## 面试官心理分析 @@ -27,17 +30,17 @@ 需要自动统计**各个接口和服务之间的调用次数以及访问延时**,而且要分成两个级别。 -* 一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少; -* 第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的 TP50/TP90/TP99,分别是多少。 +- 一个级别是接口粒度,就是每个服务的每个接口每天被调用多少次,TP50/TP90/TP99,三个档次的请求延时分别是多少; +- 第二个级别是从源头入口开始,一个完整的请求链路经过几十个服务之后,完成一次请求,每天全链路走多少次,全链路请求延时的 TP50/TP90/TP99,分别是多少。 这些东西都搞定了之后,后面才可以来看当前系统的压力主要在哪里,如何来扩容和优化啊。 #### 3. 其它 -* 服务分层(避免循环依赖) -* 调用链路失败监控和报警 -* 服务鉴权 -* 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%) +- 服务分层(避免循环依赖) +- 调用链路失败监控和报警 +- 服务鉴权 +- 每个服务的可用性的监控(接口调用成功率?几个 9?99.99%,99.9%,99%) ### 服务降级 @@ -45,7 +48,7 @@ 举个栗子,我们有接口 `HelloService` 。 `HelloServiceImpl` 有该接口的具体实现。 -``` java +```java public interface HelloService { void sayHello(); } @@ -57,7 +60,7 @@ public class HelloServiceImpl implements HelloService { } ``` -``` xml +```xml ``` @@ -114,5 +117,5 @@ public class HelloServiceMock implements HelloService { 可以结合你们公司具体的场景来说说你是怎么设置这些参数的: -* `timeout` :一般设置为 `200ms` ,我们认为不能超过 `200ms` 还没返回。 -* `retries` :设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。 +- `timeout` :一般设置为 `200ms` ,我们认为不能超过 `200ms` 还没返回。 +- `retries` :设置 retries,一般是在读请求的时候,比如你要查询个数据,你可以设置个 retries,如果第一次没读到,报错,重试指定的次数,尝试再次读取。 diff --git a/docs/distributed-system/dubbo-spi.md b/docs/distributed-system/dubbo-spi.md index b825e3b17..8ca6d7145 100644 --- a/docs/distributed-system/dubbo-spi.md +++ b/docs/distributed-system/dubbo-spi.md @@ -1,4 +1,7 @@ +# Dubbo 的 SPI 机制 + ## 面试题 + dubbo 的 spi 思想是什么? ## 面试官心理分析 @@ -10,11 +13,12 @@ dubbo 的 spi 思想是什么? ## 面试题剖析 ### spi 是啥? + spi,简单来说,就是 `service provider interface` ,说白了是什么意思呢,比如你有个接口,现在这个接口有 3 个实现类,那么在系统运行的时候对这个接口到底选择哪个实现类呢?这就需要 spi 了,需要**根据指定的配置**或者是**默认的配置**,去**找到对应的实现类**加载进来,然后用这个实现类的实例对象。 举个栗子。 -你有一个接口 A。A1/A2/A3 分别是接口A的不同实现。你通过配置 `接口 A = 实现 A2` ,那么在系统实际运行的时候,会加载你的配置,用实现 A2 实例化一个对象来提供服务。 +你有一个接口 A。A1/A2/A3 分别是接口 A 的不同实现。你通过配置 `接口 A = 实现 A2` ,那么在系统实际运行的时候,会加载你的配置,用实现 A2 实例化一个对象来提供服务。 spi 机制一般用在哪儿?**插件扩展的场景**,比如说你开发了一个给别人使用的开源框架,如果你想让别人自己写个插件,插到你的开源框架里面,从而扩展某个功能,这个时候 spi 思想就用上了。 @@ -32,7 +36,7 @@ Java 定义了一套 jdbc 的接口,但是 Java 并没有提供 jdbc 的实现 dubbo 也用了 spi 思想,不过没有用 jdk 的 spi 机制,是自己实现的一套 spi 机制。 -``` java +```java Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); ``` @@ -42,26 +46,26 @@ Protocol 接口,在系统运行的时候,,dubbo 会判断一下应该选 上面那行代码就是 dubbo 里大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置,那就走默认的实现好了,没问题。 -``` java -@SPI("dubbo") -public interface Protocol { - - int getDefaultPort(); - - @Adaptive - Exporter export(Invoker invoker) throws RpcException; - - @Adaptive - Invoker refer(Class type, URL url) throws RpcException; - - void destroy(); - -} +```java +@SPI("dubbo") +public interface Protocol { + + int getDefaultPort(); + + @Adaptive + Exporter export(Invoker invoker) throws RpcException; + + @Adaptive + Invoker refer(Class type, URL url) throws RpcException; + + void destroy(); + +} ``` 在 dubbo 自己的 jar 里,在 `/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol` 文件中: -``` xml +```xml dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol @@ -83,7 +87,7 @@ hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol 然后自己搞一个 `dubbo provider` 工程,在这个工程里面依赖你自己搞的那个 jar,然后在 spring 配置文件里给个配置: -``` xml +```xml ``` @@ -93,4 +97,4 @@ provider 启动的时候,就会加载到我们 jar 包里的 `my=com.bingo.MyP dubbo 里面提供了大量的类似上面的扩展点,就是说,你如果要扩展一个东西,只要自己写个 jar,让你的 consumer 或者是 provider 工程,依赖你的那个 jar,在你的 jar 里指定目录下配置好接口名称对应的文件,里面通过 `key=实现类` 。 -然后对于对应的组件,类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。 +然后对于对应的组件,类似 `` 用你的那个 key 对应的实现类来实现某个接口,你可以自己去扩展 dubbo 的各种功能,提供你自己的实现。 diff --git a/docs/distributed-system/images/favicon-16x16.png b/docs/distributed-system/images/favicon-16x16.png deleted file mode 100644 index a2c7968cf..000000000 Binary files a/docs/distributed-system/images/favicon-16x16.png and /dev/null differ diff --git a/docs/distributed-system/images/favicon-32x32.png b/docs/distributed-system/images/favicon-32x32.png deleted file mode 100644 index 473eec893..000000000 Binary files a/docs/distributed-system/images/favicon-32x32.png and /dev/null differ diff --git a/docs/distributed-system/images/qrcode-for-doocs.jpg b/docs/distributed-system/images/qrcode-for-doocs.jpg deleted file mode 100644 index bd1db5d11..000000000 Binary files a/docs/distributed-system/images/qrcode-for-doocs.jpg and /dev/null differ diff --git a/docs/distributed-system/images/qrcode-for-yanglbme.jpg b/docs/distributed-system/images/qrcode-for-yanglbme.jpg deleted file mode 100644 index 5bd385bca..000000000 Binary files a/docs/distributed-system/images/qrcode-for-yanglbme.jpg and /dev/null differ diff --git a/docs/distributed-system/why-dubbo.md b/docs/distributed-system/why-dubbo.md index 89e8ba6bf..0c98a54f0 100644 --- a/docs/distributed-system/why-dubbo.md +++ b/docs/distributed-system/why-dubbo.md @@ -1,4 +1,7 @@ +# 为什么要拆分系统? + ## 面试题 + 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗? ## 面试官心理分析 @@ -20,6 +23,7 @@ ## 面试题剖析 ### 为什么要将系统进行拆分? + 网上查查,答案极度零散和复杂,很琐碎,原因一大坨。但是我这里给大家直观的感受: 要是**不拆分**,一个大系统几十万行代码,20 个人维护一份代码,简直是悲剧啊。代码经常改着改着就冲突了,各种代码冲突和合并要处理,非常耗费时间;经常我改动了我的代码,你调用了我的,导致你的代码也得重新测试,麻烦的要死;然后每次发布都是几十万行代码的系统一起发布,大家得一起提心吊胆准备上线,几十万行代码的上线,可能每次上线都要做很多的检查,很多异常问题的处理,简直是又麻烦又痛苦;而且如果我现在打算把技术升级到最新的 spring 版本,还不行,因为这可能导致你的代码报错,我不敢随意乱改技术。 @@ -42,7 +46,7 @@ A 就检查了自己负责的 1 万行代码对应的功能,确保 ok 就闪 系统拆分为分布式系统,拆成多个服务,拆成微服务的架构,是需要拆很多轮的。并不是说上来一个架构师一次就给拆好了,而以后都不用拆。 -第一轮;团队继续扩大,拆好的某个服务,刚开始是 1 个人维护 1 万行代码,后来业务系统越来越复杂,这个服务是 10 万行代码,5 个人;第二轮,1个服务 -> 5个服务,每个服务 2 万行代码,每人负责一个服务。 +第一轮;团队继续扩大,拆好的某个服务,刚开始是 1 个人维护 1 万行代码,后来业务系统越来越复杂,这个服务是 10 万行代码,5 个人;第二轮,1 个服务 -> 5 个服务,每个服务 2 万行代码,每人负责一个服务。 如果是多人维护一个服务,最理想的情况下,几十个人,1 个人负责 1 个或 2~3 个服务;某个服务工作量变大了,代码量越来越多,某个同学,负责一个服务,代码量变成了 10 万行了,他自己不堪重负,他现在一个人拆开,5 个服务,1 个人顶着,负责 5 个人,接着招人,2 个人,给那个同学带着,3 个人负责 5 个服务,其中 2 个人每个人负责 2 个服务,1 个人负责 1 个服务。 diff --git a/docs/distributed-system/zookeeper-application-scenarios.md b/docs/distributed-system/zookeeper-application-scenarios.md index 37c58a5e1..248a0c1f7 100644 --- a/docs/distributed-system/zookeeper-application-scenarios.md +++ b/docs/distributed-system/zookeeper-application-scenarios.md @@ -1,9 +1,12 @@ +# Zookeeper 的使用场景 + ## 面试题 + zookeeper 都有哪些使用场景? ## 面试官心理分析 -现在聊的 topic 是分布式系统,面试官跟你聊完了 dubbo 相关的一些问题之后,已经确认你对分布式服务框架/RPC框架基本都有一些认知了。那么他可能开始要跟你聊分布式相关的其它问题了。 +现在聊的 topic 是分布式系统,面试官跟你聊完了 dubbo 相关的一些问题之后,已经确认你对分布式服务框架/RPC 框架基本都有一些认知了。那么他可能开始要跟你聊分布式相关的其它问题了。 分布式锁这个东西,很常用的,你做 Java 系统开发,分布式系统,可能会有一些场景会用到。最常用的分布式锁就是基于 zookeeper 来实现的。 @@ -13,10 +16,10 @@ zookeeper 都有哪些使用场景? 大致来说,zookeeper 的使用场景如下,我就举几个简单的,大家能说几个就好了: -* 分布式协调 -* 分布式锁 -* 元数据/配置信息管理 -* HA高可用性 +- 分布式协调 +- 分布式锁 +- 元数据/配置信息管理 +- HA 高可用性 ### 分布式协调 @@ -36,7 +39,7 @@ zookeeper 可以用作很多系统的配置信息的管理,比如 kafka、stor ![zookeeper-meta-data-manage](./images/zookeeper-meta-data-manage.png) -### HA高可用性 +### HA 高可用性 这个应该是很常见的,比如 hadoop、hdfs、yarn 等很多大数据系统,都选择基于 zookeeper 来开发 HA 高可用机制,就是一个**重要进程一般会做主备**两个,主进程挂了立马通过 zookeeper 感知到切换到备用进程。 diff --git a/docs/extra-page/README.md b/docs/extra-page/README.md index 43faee50e..fd7d840ab 100644 --- a/docs/extra-page/README.md +++ b/docs/extra-page/README.md @@ -1,34 +1,35 @@ -# 项目额外页面 +# 项目补充 ## Offer 与进阶 -* [我的 Offer 在哪里?](https://doocs.gitee.io/advanced-java/#/docs/extra-page/offer) -* [让我们同步进阶!](https://doocs.gitee.io/advanced-java/#/docs/extra-page/advanced) +- [我的 Offer 在哪里?](https://doocs.gitee.io/advanced-java/#/docs/extra-page/offer) +- [让我们同步进阶!](https://doocs.gitee.io/advanced-java/#/docs/extra-page/advanced) -## 项目 Page 页 +## 项目 Pages 站点 -* [GitHub Page](https://doocs.github.io/advanced-java/#/) -* [Gitee Page](https://doocs.gitee.io/advanced-java/#/) +https://java.doocs.org + +## 维权行动 + +- [维护他人知识产权,尊重他人劳动成果](./rights-defending-action.md) --- ## 公众号 -GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - - - - + + + +
- -
- 公众平台 -

-
- -
- 个人微信 -

-
+
+
+
+
+ +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! + +
diff --git a/docs/extra-page/advanced.md b/docs/extra-page/advanced.md index 877cae1b3..f75a3cf2c 100644 --- a/docs/extra-page/advanced.md +++ b/docs/extra-page/advanced.md @@ -1,9 +1,7 @@

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

- -> 受伤的得到疗愈,挣扎的得到出口
-*Let those who hurt heal, let those who struggle find hope*. +> 受伤的得到疗愈,挣扎的得到出口
> _Let those who hurt heal, let those who struggle find hope_. ``` 告别的时刻已到了 @@ -69,4 +67,4 @@ 跨越了浩劫 曙光渐亮 oh~ -``` \ No newline at end of file +``` diff --git a/docs/extra-page/cover.md b/docs/extra-page/cover.md deleted file mode 100644 index 84d488a72..000000000 --- a/docs/extra-page/cover.md +++ /dev/null @@ -1,8 +0,0 @@ -[![logo](./images/icon.png)](https://github.com/doocs/advanced-java) - -# 互联网 Java 工程师进阶知识完全扫盲 - -> 本系列知识由 Doocs 技术社区总结发布,内容涵盖高并发、分布式、高可用、微服务、海量数据处理等 - -[社区首页](https://doocs.github.io) -[开始学习](#互联网-java-工程师进阶知识完全扫盲©) diff --git a/docs/extra-page/images/advanced-java-doocs-shishan.png b/docs/extra-page/images/advanced-java-doocs-shishan.png index 6d3e1388a..a2b233143 100644 Binary files a/docs/extra-page/images/advanced-java-doocs-shishan.png and b/docs/extra-page/images/advanced-java-doocs-shishan.png differ diff --git a/docs/extra-page/images/icon.png b/docs/extra-page/images/icon.png deleted file mode 100644 index a39d36b6f..000000000 Binary files a/docs/extra-page/images/icon.png and /dev/null differ diff --git a/docs/extra-page/images/qrcode-for-doocs.jpg b/docs/extra-page/images/qrcode-for-doocs.jpg deleted file mode 100644 index bd1db5d11..000000000 Binary files a/docs/extra-page/images/qrcode-for-doocs.jpg and /dev/null differ diff --git a/docs/extra-page/images/qrcode-for-yanglbme.jpg b/docs/extra-page/images/qrcode-for-yanglbme.jpg deleted file mode 100644 index 5bd385bca..000000000 Binary files a/docs/extra-page/images/qrcode-for-yanglbme.jpg and /dev/null differ diff --git a/docs/extra-page/images/qrcode_for_doocs.jpg b/docs/extra-page/images/qrcode_for_doocs.jpg deleted file mode 100644 index 70d24158b..000000000 Binary files a/docs/extra-page/images/qrcode_for_doocs.jpg and /dev/null differ diff --git a/docs/extra-page/images/where-is-my-offer.png b/docs/extra-page/images/where-is-my-offer.png index 8d2f84485..0a41f8e8c 100644 Binary files a/docs/extra-page/images/where-is-my-offer.png and b/docs/extra-page/images/where-is-my-offer.png differ diff --git a/docs/extra-page/offer.md b/docs/extra-page/offer.md index 32f201026..566134b2e 100644 --- a/docs/extra-page/offer.md +++ b/docs/extra-page/offer.md @@ -119,7 +119,7 @@ HR : / |"- ' ' | / ` | - + ``` -[![get-up-and-study](./images/get-up-and-study.png)](https://doocs.github.io/advanced-java) \ No newline at end of file +[![get-up-and-study](./images/get-up-and-study.png)](https://doocs.github.io/advanced-java) diff --git a/docs/extra-page/rights-defending-action.md b/docs/extra-page/rights-defending-action.md index a3cf72a75..e22eb11a5 100644 --- a/docs/extra-page/rights-defending-action.md +++ b/docs/extra-page/rights-defending-action.md @@ -16,34 +16,34 @@ ### 博客 -| # | 文章 | 抄袭者 | -|---|---|---| -| 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 | [如何保证缓存与数据库的双写一致性](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高级架构解析 | 严重抄袭 | +| # | 文章 | 抄袭者 | 备注 | +| --- | ---------------------------------------------------------------------- | ----------------- | -------- | +| 1 | [Java 消息队列三道面试题详解!](https://zhuanlan.zhihu.com/p/62739616) | Java 高级架构解析 | 严重抄袭 | diff --git a/docs/extra-page/subscriptions-for-doocs.md b/docs/extra-page/subscriptions-for-doocs.md index 5d5fc8b1b..d4a058056 100644 --- a/docs/extra-page/subscriptions-for-doocs.md +++ b/docs/extra-page/subscriptions-for-doocs.md @@ -1,8 +1,9 @@ -# Doocs 开源社区的公众号来了 -GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专注于挖掘 IT 技术知识,助力开发者成长。 +# Doocs 的公众号来了 + +GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs**”,专注于挖掘 IT 技术知识,助力开发者成长。
- +
来成为公众号的首批粉丝吗?一定不会辜负你们的期待。 @@ -11,12 +12,12 @@ GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专 微信公众号的**原创保护功能**做得比较好,内容阅读也比较方便。 -后续我的所有原创文章,将第一时间通过微信公众号“Doocs开源社区”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。 +后续我的所有原创文章,将第一时间通过微信公众号“Doocs”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。 ## 公众号的定位是怎样的? -* **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。 -* **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。 +- **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。 +- **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。
@@ -26,27 +27,27 @@ GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs开源社区**”,专 因为刚刚推出公众号,目前有以下几篇文章,来先睹为快吧: -* [硬核!亿级流量秒杀系统设计](https://mp.weixin.qq.com/s/Mo_knIRBQQL2s-D2aieZLg) -* [技术面试是否要看面经?面试官/面试者有话说!](https://mp.weixin.qq.com/s/fNiUmbY395rsPdEC0QDIrw) -* [如何破解极验滑动验证码?成功率 100%!](https://mp.weixin.qq.com/s/Fsl6qYN5Dw4s6Du893MkFQ) -* [免费且好用的图床,就你了,「图壳」!](https://mp.weixin.qq.com/s/0HhgHLo_tTRFZcC-CVjDbw) -* [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson!](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow) -* [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw) -* [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA) -* [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12!](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw) -* [Google 搜索的即时自动补全功能究竟是如何“工作”的?](https://mp.weixin.qq.com/s/YlMISSc3Sn890BzTLytcLA) -* [厉害了,原来 Redisson 这么好用!](https://mp.weixin.qq.com/s/lpZ7eRdImy0MyTEVH68HYw) -* [一文带你搞懂 “缓存策略”](https://mp.weixin.qq.com/s/47A_iXY_nArURwUTPHr2IQ) -* [Java Getter/Setter “防坑指南”](https://mp.weixin.qq.com/s/TZqcAw7NTlcvU-p930-eHA) -* [太棒了,GitHub Review 代码能力小升级](https://mp.weixin.qq.com/s/Lok0epqn91Q51ygZo_FLkg) -* [巧用 Redis Hyperloglog,轻松统计 UV 数据](https://mp.weixin.qq.com/s/w1r-M6YVvQSfUtzO_xe44Q) -* [如何开启「GitHub+码云」双工作流模式?](https://mp.weixin.qq.com/s/byxAjr3-ifWfDYQcR7YA8Q) +- [硬核!亿级流量秒杀系统设计](https://mp.weixin.qq.com/s/Mo_knIRBQQL2s-D2aieZLg) +- [技术面试是否要看面经?面试官/面试者有话说!](https://mp.weixin.qq.com/s/fNiUmbY395rsPdEC0QDIrw) +- [如何破解极验滑动验证码?成功率 100%!](https://mp.weixin.qq.com/s/Fsl6qYN5Dw4s6Du893MkFQ) +- [免费且好用的图床,就你了,「图壳」!](https://mp.weixin.qq.com/s/0HhgHLo_tTRFZcC-CVjDbw) +- [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson!](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow) +- [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw) +- [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA) +- [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12!](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw) +- [Google 搜索的即时自动补全功能究竟是如何“工作”的?](https://mp.weixin.qq.com/s/YlMISSc3Sn890BzTLytcLA) +- [厉害了,原来 Redisson 这么好用!](https://mp.weixin.qq.com/s/lpZ7eRdImy0MyTEVH68HYw) +- [一文带你搞懂 “缓存策略”](https://mp.weixin.qq.com/s/47A_iXY_nArURwUTPHr2IQ) +- [Java Getter/Setter “防坑指南”](https://mp.weixin.qq.com/s/TZqcAw7NTlcvU-p930-eHA) +- [太棒了,GitHub Review 代码能力小升级](https://mp.weixin.qq.com/s/Lok0epqn91Q51ygZo_FLkg) +- [巧用 Redis Hyperloglog,轻松统计 UV 数据](https://mp.weixin.qq.com/s/w1r-M6YVvQSfUtzO_xe44Q) +- [如何开启「GitHub+码云」双工作流模式?](https://mp.weixin.qq.com/s/byxAjr3-ifWfDYQcR7YA8Q) 后续将推出一系列原创干货文章,敬请期待。 ## 是否有交流群? -有的,目前微信群“**Doocs的技术朋友们**”已经接近 200 号人,如果你希望加入,请通过微信与我联系。 +有的,目前微信群“**Doocs 的技术朋友们**”已经接近 200 号人,如果你希望加入,请通过微信与我联系。 **注意**,群内禁止一切垃圾广告信息,包括小程序助力信息、小游戏、社群推广、公众号推广、支付宝推广码等;交流 GitHub、开发相关,可以自由分享一些开发相关知识,但不提倡整天水群,建议还是多花点时间提升自己。 diff --git a/docs/high-availability/README.md b/docs/high-availability/README.md index 0c10a2d40..296b2dfec 100644 --- a/docs/high-availability/README.md +++ b/docs/high-availability/README.md @@ -1,48 +1,52 @@ # 高可用架构 -- [Hystrix 介绍](./hystrix-introduction.md) -- [电商网站详情页系统架构](./e-commerce-website-detail-page-architecture.md) -- [Hystrix 线程池技术实现资源隔离](./hystrix-thread-pool-isolation.md) -- [Hystrix 信号量机制实现资源隔离](./hystrix-semphore-isolation.md) -- [Hystrix 隔离策略细粒度控制](./hystrix-execution-isolation.md) -- [深入 Hystrix 执行时内部原理](./hystrix-process.md) -- [基于 request cache 请求缓存技术优化批量商品数据查询接口](./hystrix-request-cache.md) -- [基于本地缓存的 fallback 降级机制](./hystrix-fallback.md) -- [深入 Hystrix 断路器执行原理](./hystrix-circuit-breaker.md) -- [深入 Hystrix 线程池隔离与接口限流](./hystrix-thread-pool-current-limiting.md) -- [基于 timeout 机制为服务接口调用超时提供安全保护](./hystrix-timeout.md) + +- [Hystrix 介绍](./hystrix-introduction.md) +- [电商网站详情页系统架构](./e-commerce-website-detail-page-architecture.md) +- [Hystrix 线程池技术实现资源隔离](./hystrix-thread-pool-isolation.md) +- [Hystrix 信号量机制实现资源隔离](./hystrix-semphore-isolation.md) +- [Hystrix 隔离策略细粒度控制](./hystrix-execution-isolation.md) +- [深入 Hystrix 执行时内部原理](./hystrix-process.md) +- [基于 request cache 请求缓存技术优化批量商品数据查询接口](./hystrix-request-cache.md) +- [基于本地缓存的 fallback 降级机制](./hystrix-fallback.md) +- [深入 Hystrix 断路器执行原理](./hystrix-circuit-breaker.md) +- [深入 Hystrix 线程池隔离与接口限流](./hystrix-thread-pool-current-limiting.md) +- [基于 timeout 机制为服务接口调用超时提供安全保护](./hystrix-timeout.md) ## 高可用系统 -- 如何设计一个高可用系统? + +- 如何设计一个高可用系统? ## 限流 -- 如何限流?在工作中是怎么做的?说一下具体的实现? + +- 如何限流?在工作中是怎么做的?说一下具体的实现? ## 熔断 -- 如何进行熔断? -- 熔断框架都有哪些?具体实现原理知道吗? -- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](/docs/high-availability/sentinel-vs-hystrix.md) + +- 如何进行熔断? +- 熔断框架都有哪些?具体实现原理知道吗? +- [熔断框架如何做技术选型?选用 Sentinel 还是 Hystrix?](./sentinel-vs-hystrix.md) ## 降级 -- 如何进行降级? + +- 如何进行降级? --- ## 公众号 -GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 + +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - - - - -
- -
- 公众平台 -

-
- -
- 个人微信 -

-
\ No newline at end of file + + +
+ + +
+ + + + +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! + +
diff --git a/docs/high-availability/e-commerce-website-detail-page-architecture.md b/docs/high-availability/e-commerce-website-detail-page-architecture.md index 0978f5f46..b9a27bc8f 100644 --- a/docs/high-availability/e-commerce-website-detail-page-architecture.md +++ b/docs/high-availability/e-commerce-website-detail-page-architecture.md @@ -1,6 +1,7 @@ -## 电商网站的商品详情页系统架构 +# 电商网站的商品详情页系统架构 + +## 小型电商网站的商品详情页系统架构 -### 小型电商网站的商品详情页系统架构 小型电商网站的页面展示采用页面全量静态化的思想。数据库中存放了所有的商品信息,页面静态化系统,将数据填充进静态模板中,形成静态化页面,推入 Nginx 服务器。用户浏览网站页面时,取用一个已经静态化好的 html 页面,直接返回回去,不涉及任何的业务逻辑处理。 ![e-commerce-website-detail-page-architecture-1](./images/e-commerce-website-detail-page-architecture-1.png) @@ -10,8 +11,8 @@ ```html - 商品名称:#{productName}
- 商品价格:#{productPrice}
+ 商品名称:#{productName}
+ 商品价格:#{productPrice}
商品描述:#{productDesc} @@ -23,18 +24,18 @@ **坏处**在于,仅仅适用于一些小型的网站,比如页面的规模在几十到几万不等。对于一些大型的电商网站,亿级数量的页面,你说你每次页面模板修改了,都需要将这么多页面全量静态化,靠谱吗?每次渲染花个好几天时间,那你整个网站就废掉了。 -### 大型电商网站的商品详情页系统架构 +## 大型电商网站的商品详情页系统架构 + 大型电商网站商品详情页的系统设计中,当商品数据发生变更时,会将变更消息压入 MQ 消息队列中。**缓存服务**从消息队列中消费这条消息时,感知到有数据发生变更,便通过调用数据服务接口,获取变更后的数据,然后将整合好的数据推送至 redis 中。Nginx 本地缓存的数据是有一定的时间期限的,比如说 10 分钟,当数据过期之后,它就会从 redis 获取到最新的缓存数据,并且缓存到自己本地。 用户浏览网页时,动态将 Nginx 本地数据渲染到本地 html 模板并返回给用户。 ![e-commerce-website-detail-page-architecture-2](./images/e-commerce-website-detail-page-architecture-2.png) - 虽然没有直接返回 html 页面那么快,但是因为数据在本地缓存,所以也很快,其实耗费的也就是动态渲染一个 html 页面的性能。如果 html 模板发生了变更,不需要将所有的页面重新静态化,也不需要发送请求,没有网络请求的开销,直接将数据渲染进最新的 html 页面模板后响应即可。 在这种架构下,我们需要**保证系统的高可用性**。 如果系统访问量很高,Nginx 本地缓存过期失效了,redis 中的缓存也被 LRU 算法给清理掉了,那么会有较高的访问量,从缓存服务调用商品服务。但如果此时商品服务的接口发生故障,调用出现了延时,缓存服务全部的线程都被这个调用商品服务接口给耗尽了,每个线程去调用商品服务接口的时候,都会卡住很长时间,后面大量的请求过来都会卡在那儿,此时缓存服务没有足够的线程去调用其它一些服务的接口,从而导致整个大量的商品详情页无法正常显示。 -这其实就是一个商品接口服务故障导致缓存服务资源耗尽的现象。 \ No newline at end of file +这其实就是一个商品接口服务故障导致缓存服务资源耗尽的现象。 diff --git a/docs/high-availability/hystrix-circuit-breaker.md b/docs/high-availability/hystrix-circuit-breaker.md index 9ca3f1b07..593b67a66 100644 --- a/docs/high-availability/hystrix-circuit-breaker.md +++ b/docs/high-availability/hystrix-circuit-breaker.md @@ -1,4 +1,4 @@ -## 深入 Hystrix 断路器执行原理 +# 深入 Hystrix 断路器执行原理 ### 状态机 @@ -7,8 +7,8 @@ Hystrix 断路器有三种状态,分别是关闭(Closed)、打开(Open ![image-20191104211642271](./images/hystrix-circuit-breaker-state-machine.png) 1. `Closed` 断路器关闭:调用下游的请求正常通过 -2. `Open` 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑 -3. `Half-Open` 断路器处于半开状态:[SleepWindowInMilliseconds](#circuitBreaker.sleepWindowInMilliseconds) +1. `Open` 断路器打开:阻断对下游服务的调用,直接走 Fallback 逻辑 +1. `Half-Open` 断路器处于半开状态:[SleepWindowInMilliseconds](#circuitBreaker.sleepWindowInMilliseconds) ### [Enabled](https://github.com/Netflix/Hystrix/wiki/Configuration#circuitbreakerenabled) @@ -77,11 +77,12 @@ Hystrix 并不是只要有一条请求经过就去统计,而是将整个滑动 ## 实例 Demo ### HystrixCommand 配置参数 + 在 GetProductInfoCommand 中配置 Setter 断路器相关参数。 -- 滑动窗口中,最少 20 个请求,才可能触发断路。 -- 异常比例达到 40% 时,才触发断路。 -- 断路后 3000ms 内,所有请求都被 reject,直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。 +- 滑动窗口中,最少 20 个请求,才可能触发断路。 +- 异常比例达到 40% 时,才触发断路。 +- 断路后 3000ms 内,所有请求都被 reject,直接走 fallback 降级,不会调用 run() 方法。3000ms 过后,变为 half-open 状态。 run() 方法中,我们判断一下 productId 是否为 -1,是的话,直接抛出异常。这么写,我们之后测试的时候就可以传入 productId=-1,**模拟服务执行异常**了。 @@ -132,6 +133,7 @@ public class GetProductInfoCommand extends HystrixCommand { ``` ### 断路测试类 + 我们在测试类中,前 30 次请求,传入 productId=-1,然后休眠 3s,之后 70 次请求,传入 productId=1。 ```java @@ -163,7 +165,7 @@ public class CircuitBreakerTest { 测试结果,我们可以明显看出系统断路与恢复的整个过程。 -```c +```java 调用接口查询商品数据,productId=-1 ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) // ... @@ -184,7 +186,7 @@ ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, spe 前 30 次请求,我们传入的 productId 为 -1,所以服务执行过程中会抛出异常。我们设置了最少 20 次请求通过断路器并且异常比例超出 40% 就触发断路。因此执行了 21 次接口调用,每次都抛异常并且走降级,21 次过后,断路器就被打开了。 -之后的 9 次请求,都不会执行 run() 方法,也就不会打印以下信息。 +之后的 9 次请求,都不会执行 `run()` 方法,也就不会打印以下信息。 ```c 调用接口查询商品数据,productId=-1 @@ -197,4 +199,4 @@ ProductInfo(id=1, name=iphone7手机, price=5599.0, pictureList=a.jpg,b.jpg, spe ### 参考内容 1. [Hystrix issue 1459](https://github.com/Netflix/Hystrix/issues/1459) -2. [Hystrix Metrics](https://github.com/Netflix/Hystrix/wiki/Configuration#metrics) \ No newline at end of file +1. [Hystrix Metrics](https://github.com/Netflix/Hystrix/wiki/Configuration#metrics) diff --git a/docs/high-availability/hystrix-execution-isolation.md b/docs/high-availability/hystrix-execution-isolation.md index 80b8f1e75..e96da1c02 100644 --- a/docs/high-availability/hystrix-execution-isolation.md +++ b/docs/high-availability/hystrix-execution-isolation.md @@ -1,12 +1,14 @@ -## Hystrix 隔离策略细粒度控制 +# Hystrix 隔离策略细粒度控制 + Hystrix 实现资源隔离,有两种策略: -- 线程池隔离 -- 信号量隔离 +- 线程池隔离 +- 信号量隔离 对资源隔离这一块东西,其实可以做一定细粒度的一些控制。 -### execution.isolation.strategy +## execution.isolation.strategy + 指定了 HystrixCommand.run() 的资源隔离策略:`THREAD` or `SEMAPHORE`,一种基于线程池,一种基于信号量。 ```java @@ -27,13 +29,15 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat 而使用信号量的场景,通常是针对超大并发量的场景下,每个服务实例每秒都几百的 `QPS`,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护。一般用信号量常见于那种基于纯内存的一些业务逻辑服务,而不涉及到任何网络访问请求。 -### command key & command group +## command key & command group + 我们使用线程池隔离,要怎么对**依赖服务**、**依赖服务接口**、**线程池**三者做划分呢? 每一个 command,都可以设置一个自己的名称 command key,同时可以设置一个自己的组 command group。 + ```java private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) - .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")); + .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")); public CommandHelloWorld(String name) { super(cachedSetter); @@ -43,10 +47,12 @@ public CommandHelloWorld(String name) { command group 是一个非常重要的概念,默认情况下,就是通过 command group 来定义一个线程池的,而且还会通过 command group 来聚合一些监控和报警信息。同一个 command group 中的请求,都会进入同一个线程池中。 -### command thread pool +## command thread pool + ThreadPoolKey 代表了一个 HystrixThreadPool,用来进行统一监控、统计、缓存。默认的 ThreadPoolKey 就是 command group 的名称。每个 command 都会跟它的 ThreadPoolKey 对应的 ThreadPool 绑定在一起。 如果不想直接用 command group,也可以手动设置 ThreadPool 的名称。 + ```java private static final Setter cachedSetter = Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorld")) @@ -58,7 +64,8 @@ public CommandHelloWorld(String name) { } ``` -### command key & command group & command thread pool +## command key & command group & command thread pool + **command key** ,代表了一类 command,一般来说,代表了下游依赖服务的某个接口。 **command group** ,代表了某一个下游依赖服务,这是很合理的,一个依赖服务可能会暴露出来多个接口,每个接口就是一个 command key。command group 在逻辑上对一堆 command key 的调用次数、成功次数、timeout 次数、失败次数等进行统计,可以看到某一个服务整体的一些访问情况。**一般来说,推荐根据一个服务区划分出一个线程池,command key 默认都是属于同一个线程池的。** @@ -73,17 +80,20 @@ command key -> command group command key -> 自己的 thread pool key ``` -逻辑上来说,多个 command key 属于一个command group,在做统计的时候,会放在一起统计。每个 command key 有自己的线程池,每个接口有自己的线程池,去做资源隔离和限流。 +逻辑上来说,多个 command key 属于一个 command group,在做统计的时候,会放在一起统计。每个 command key 有自己的线程池,每个接口有自己的线程池,去做资源隔离和限流。 说白点,就是说如果你的 command key 要用自己的线程池,可以定义自己的 thread pool key,就 ok 了。 -### coreSize +## coreSize + 设置线程池的大小,默认是 10。一般来说,用这个默认的 10 个线程大小就够了。 + ```java HystrixThreadPoolProperties.Setter().withCoreSize(int value); ``` -### queueSizeRejectionThreshold +## queueSizeRejectionThreshold + 如果说线程池中的 10 个线程都在工作中,没有空闲的线程来做其它的事情,此时再有请求过来,会先进入队列积压。如果说队列积压满了,再有请求过来,就直接 reject,拒绝请求,执行 fallback 降级的逻辑,快速返回。 ![hystrix-thread-pool-queue](./images/hystrix-thread-pool-queue.png) @@ -94,7 +104,8 @@ HystrixThreadPoolProperties.Setter().withCoreSize(int value); HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value); ``` -### execution.isolation.semaphore.maxConcurrentRequests +## execution.isolation.semaphore.maxConcurrentRequests + 设置使用 SEMAPHORE 隔离策略的时候允许访问的最大并发量,超过这个最大并发量,请求直接被 reject。 这个并发量的设置,跟线程池大小的设置,应该是类似的,但是基于信号量的话,性能会好很多,而且 Hystrix 框架本身的开销会小很多。 @@ -103,4 +114,4 @@ HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value); ```java HystrixCommandProperties.Setter().withExecutionIsolationSemaphoreMaxConcurrentRequests(int value); -``` \ No newline at end of file +``` diff --git a/docs/high-availability/hystrix-fallback.md b/docs/high-availability/hystrix-fallback.md index cb9e06c6c..52bbe1061 100644 --- a/docs/high-availability/hystrix-fallback.md +++ b/docs/high-availability/hystrix-fallback.md @@ -1,29 +1,30 @@ -## 基于本地缓存的 fallback 降级机制 +# 基于本地缓存的 fallback 降级机制 + Hystrix 出现以下四种情况,都会去调用 fallback 降级机制: -- 断路器处于打开的状态。 -- 资源池已满(线程池+队列 / 信号量)。 -- Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。 -- 访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。 +- 断路器处于打开的状态。 +- 资源池已满(线程池+队列 / 信号量)。 +- Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。 +- 访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。 -### 两种最经典的降级机制 +## 两种最经典的降级机制 -- 纯内存数据
-在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。 +- 纯内存数据
+ 在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。 -- 默认值
-fallback 降级逻辑中,也可以直接返回一个默认值。 +- 默认值
+ fallback 降级逻辑中,也可以直接返回一个默认值。 在 `HystrixCommand`,降级逻辑的书写,是通过实现 getFallback() 接口;而在 `HystrixObservableCommand` 中,则是实现 resumeWithFallback() 方法。 - 现在,我们用一个简单的栗子,来演示 fallback 降级是怎么做的。 比如,有这么个**场景**。我们现在有个包含 brandId 的商品数据,假设正常的逻辑是这样:拿到一个商品数据,根据 brandId 去调用品牌服务的接口,获取品牌的最新名称 brandName。 假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。 -### 步骤一:本地缓存获取数据 +## 步骤一:本地缓存获取数据 + 本地获取品牌名称的代码大致如下。 ```java @@ -31,7 +32,6 @@ fallback 降级逻辑中,也可以直接返回一个默认值。 * 品牌名称本地缓存 * */ - public class BrandCache { private static Map brandMap = new HashMap<>(); @@ -51,7 +51,8 @@ public class BrandCache { } ``` -### 步骤二:实现 GetBrandNameCommand +## 步骤二:实现 GetBrandNameCommand + 在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。 这里,我们直接**模拟接口调用报错**,给它抛出个异常。 @@ -63,7 +64,6 @@ public class BrandCache { * 获取品牌名称的command * */ - public class GetBrandNameCommand extends HystrixCommand { private Long brandId; @@ -95,7 +95,8 @@ public class GetBrandNameCommand extends HystrixCommand { `FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。 -### 步骤三:CacheController 调用接口 +## 步骤三:CacheController 调用接口 + 在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。 ```java @@ -122,4 +123,4 @@ public class CacheController { } ``` -关于降级逻辑的演示,基本上就结束了。 \ No newline at end of file +关于降级逻辑的演示,基本上就结束了。 diff --git a/docs/high-availability/hystrix-introduction.md b/docs/high-availability/hystrix-introduction.md index d7eb44c40..483f4226a 100644 --- a/docs/high-availability/hystrix-introduction.md +++ b/docs/high-availability/hystrix-introduction.md @@ -1,7 +1,9 @@ -## 用 Hystrix 构建高可用服务架构 +# 用 Hystrix 构建高可用服务架构 + 参考 [Hystrix Home](https://github.com/Netflix/Hystrix/wiki#what)。 -### Hystrix 是什么? +## Hystrix 是什么? + 在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是**依赖服务**,有的时候某些依赖服务出现故障也是很正常的。 Hystrix 可以让我们在分布式系统中对服务间的调用进行控制,加入一些**调用延迟**或者**依赖故障**的**容错机制**。 @@ -10,7 +12,8 @@ Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖 **总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。** -### Hystrix 的历史 +## Hystrix 的历史 + Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外的优酷或者爱奇艺之类的视频网站)的 API 团队从 2011 年开始做一些提升系统可用性和稳定性的工作,Hystrix 就是从那时候开始发展出来的。 在 2012 年的时候,Hystrix 就变得比较成熟和稳定了,Netflix 中,除了 API 团队以外,很多其他的团队都开始使用 Hystrix。 @@ -19,13 +22,13 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外 [2018 年 11 月,Hystrix 在其 Github 主页宣布,不再开放新功能,推荐开发者使用其他仍然活跃的开源项目](https://github.com/Netflix/Hystrix/blob/master/README.md#hystrix-status)。维护模式的转变绝不意味着 Hystrix 不再有价值。相反,Hystrix 激发了很多伟大的想法和项目,我们高可用的这一块知识还是会针对 Hystrix 进行讲解。 -### Hystrix 的设计原则 -- 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。 -- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。 -- 提供 `fail-fast`(快速失败)和快速恢复的支持。 -- 提供 fallback 优雅降级的支持。 -- 支持近实时的监控、报警以及运维操作。 +## Hystrix 的设计原则 +- 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。 +- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。 +- 提供 `fail-fast`(快速失败)和快速恢复的支持。 +- 提供 fallback 优雅降级的支持。 +- 支持近实时的监控、报警以及运维操作。 举个栗子。 @@ -37,12 +40,12 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外 Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线程调用服务 C。当此 40 个线程被 hang 住时,其它 60 个线程依然能正常调用工作。从而确保整个系统不会被拖垮。 +## Hystrix 更加细节的设计原则 -### Hystrix 更加细节的设计原则 -- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。 -- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。 -- 提供 fallback 降级机制来应对故障。 -- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(断路技术)来限制任何一个依赖服务的故障的影响。 -- 通过近实时的统计/监控/报警功能,来提高故障发现的速度。 -- 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。 -- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。 \ No newline at end of file +- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。 +- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。 +- 提供 fallback 降级机制来应对故障。 +- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(断路技术)来限制任何一个依赖服务的故障的影响。 +- 通过近实时的统计、监控、报警功能,来提高故障发现的速度。 +- 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。 +- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。 diff --git a/docs/high-availability/hystrix-process.md b/docs/high-availability/hystrix-process.md index c0ffedfdd..984fbf586 100644 --- a/docs/high-availability/hystrix-process.md +++ b/docs/high-availability/hystrix-process.md @@ -1,9 +1,10 @@ -## 深入 Hystrix 执行时内部原理 +# 深入 Hystrix 执行时内部原理 + 前面我们了解了 Hystrix 最基本的支持高可用的技术:**资源隔离** + **限流**。 -- 创建 command; -- 执行这个 command; -- 配置这个 command 对应的 group 和线程池。 +- 创建 command; +- 执行这个 command; +- 配置这个 command 对应的 group 和线程池。 这里,我们要讲一下,你开始执行这个 command,调用了这个 command 的 execute() 方法之后,Hystrix 底层的执行流程和步骤以及原理是什么。 @@ -13,11 +14,12 @@ ![hystrix-process](./images/new-hystrix-process.jpg) -### 步骤一:创建 command +## 步骤一:创建 command + 一个 HystrixCommand 或 HystrixObservableCommand 对象,代表了对某个依赖服务发起的一次请求或者调用。创建的时候,可以在构造函数中传入任何需要的参数。 -- HystrixCommand 主要用于仅仅会返回一个结果的调用。 -- HystrixObservableCommand 主要用于可能会返回多条结果的调用。 +- HystrixCommand 主要用于仅仅会返回一个结果的调用。 +- HystrixObservableCommand 主要用于可能会返回多条结果的调用。 ```java // 创建 HystrixCommand @@ -27,17 +29,18 @@ HystrixCommand hystrixCommand = new HystrixCommand(arg1, arg2); HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand(arg1, arg2); ``` -### 步骤二:调用 command 执行方法 +## 步骤二:调用 command 执行方法 + 执行 command,就可以发起一次对依赖服务的调用。 要执行 command,可以在 4 个方法中选择其中的一个:execute()、queue()、observe()、toObservable()。 其中 execute() 和 queue() 方法仅仅对 HystrixCommand 适用。 -- execute():调用后直接 block 住,属于同步调用,直到依赖服务返回单条结果,或者抛出异常。 -- queue():返回一个 Future,属于异步调用,后面可以通过 Future 获取单条结果。 -- observe():订阅一个 Observable 对象,Observable 代表的是依赖服务返回的结果,获取到一个那个代表结果的 Observable 对象的拷贝对象。 -- toObservable():返回一个 Observable 对象,如果我们订阅这个对象,就会执行 command 并且获取返回结果。 +- execute():调用后直接 block 住,属于同步调用,直到依赖服务返回单条结果,或者抛出异常。 +- queue():返回一个 Future,属于异步调用,后面可以通过 Future 获取单条结果。 +- observe():订阅一个 Observable 对象,Observable 代表的是依赖服务返回的结果,获取到一个那个代表结果的 Observable 对象的拷贝对象。 +- toObservable():返回一个 Observable 对象,如果我们订阅这个对象,就会执行 command 并且获取返回结果。 ```java K value = hystrixCommand.execute(); @@ -47,6 +50,7 @@ Observable toOValue = hystrixObservableCommand.toObservable(); ``` execute() 实际上会调用 queue().get() 方法,可以看一下 Hystrix 源码。 + ```java public R execute() { try { @@ -58,34 +62,39 @@ public R execute() { ``` 而在 queue() 方法中,会调用 toObservable().toBlocking().toFuture()。 + ```java final Future delegate = toObservable().toBlocking().toFuture(); ``` 也就是说,先通过 toObservable() 获得 Future 对象,然后调用 Future 的 get() 方法。那么,其实无论是哪种方式执行 command,最终都是依赖于 toObservable() 去执行的。 -### 步骤三:检查是否开启缓存(不太常用) +## 步骤三:检查是否开启缓存(不太常用) + 从这一步开始,就进入到 Hystrix 底层运行原理啦,看一下 Hystrix 一些更高级的功能和特性。 如果这个 command 开启了请求缓存 Request Cache,而且这个调用的结果在缓存中存在,那么直接从缓存中返回结果。否则,继续往后的步骤。 -### 步骤四:检查是否开启了断路器 +## 步骤四:检查是否开启了断路器 + 检查这个 command 对应的依赖服务是否开启了断路器。如果断路器被打开了,那么 Hystrix 就不会执行这个 command,而是直接去执行 fallback 降级机制,返回降级结果。 -### 步骤五:检查线程池/队列/信号量是否已满 +## 步骤五:检查线程池/队列/信号量是否已满 + 如果这个 command 线程池和队列已满,或者 semaphore 信号量已满,那么也不会执行 command,而是直接去调用 fallback 降级机制,同时发送 reject 信息给断路器统计。 -### 步骤六:执行 command +## 步骤六:执行 command + 调用 HystrixObservableCommand 对象的 construct() 方法,或者 HystrixCommand 的 run() 方法来实际执行这个 command。 -- HystrixCommand.run() 返回单条结果,或者抛出异常。 +- HystrixCommand.run() 返回单条结果,或者抛出异常。 ```java // 通过command执行,获取最新一条商品数据 ProductInfo productInfo = getProductInfoCommand.execute(); ``` -- HystrixObservableCommand.construct() 返回一个 Observable 对象,可以获取多条结果。 +- HystrixObservableCommand.construct() 返回一个 Observable 对象,可以获取多条结果。 ```java Observable observable = getProductInfosCommand.observe(); @@ -104,7 +113,7 @@ observable.subscribe(new Observer() { /** * 获取完一条数据,就回调一次这个方法 - * + * * @param productInfo 商品信息 */ @Override @@ -120,37 +129,40 @@ observable.subscribe(new Observer() { 如果没有 timeout,也正常执行的话,那么调用线程就会拿到一些调用依赖服务获取到的结果,然后 Hystrix 也会做一些 logging 记录和 metric 度量统计。 -### 步骤七:断路健康检查 +## 步骤七:断路健康检查 + Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout 等事件发送给 circuit breaker 断路器。断路器就会对这些事件的次数进行统计,根据异常事件发生的比例来决定是否要进行断路(熔断)。如果打开了断路器,那么在接下来一段时间内,会直接断路,返回降级结果。 如果在之后,断路器尝试执行 command,调用没有出错,返回了正常结果,那么 Hystrix 就会把断路器关闭。 -### 步骤八:调用 fallback 降级机制 +## 步骤八:调用 fallback 降级机制 + 在以下几种情况中,Hystrix 会调用 fallback 降级机制。 -- 断路器处于打开状态; -- 线程池/队列/semaphore满了; -- command 执行超时; -- run() 或者 construct() 抛出异常。 +- 断路器处于打开状态; +- 线程池/队列/semaphore 满了; +- command 执行超时; +- run() 或者 construct() 抛出异常。 一般在降级机制中,都建议给出一些默认的返回值,比如静态的一些代码逻辑,或者从内存中的缓存中提取一些数据,在这里尽量不要再进行网络请求了。 在降级中,如果一定要进行网络调用的话,也应该将那个调用放在一个 HystrixCommand 中进行隔离。 -- HystrixCommand 中,实现 getFallback() 方法,可以提供降级机制。 -- HystrixObservableCommand 中,实现 resumeWithFallback() 方法,返回一个 Observable 对象,可以提供降级结果。 +- HystrixCommand 中,实现 getFallback() 方法,可以提供降级机制。 +- HystrixObservableCommand 中,实现 resumeWithFallback() 方法,返回一个 Observable 对象,可以提供降级结果。 如果没有实现 fallback,或者 fallback 抛出了异常,Hystrix 会返回一个 Observable,但是不会返回任何数据。 不同的 command 执行方式,其 fallback 为空或者异常时的返回结果不同。 -- 对于 execute(),直接抛出异常。 -- 对于 queue(),返回一个 Future,调用 get() 时抛出异常。 -- 对于 observe(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。 -- 对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。 +- 对于 execute(),直接抛出异常。 +- 对于 queue(),返回一个 Future,调用 get() 时抛出异常。 +- 对于 observe(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。 +- 对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。 + +## 不同的执行方式 -### 不同的执行方式 -- execute(),获取一个 Future.get(),然后拿到单个结果。 -- queue(),返回一个 Future。 -- observe(),立即订阅 Observable,然后启动 8 大执行步骤,返回一个拷贝的 Observable,订阅时立即回调给你结果。 -- toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。 \ No newline at end of file +- execute(),获取一个 Future.get(),然后拿到单个结果。 +- queue(),返回一个 Future。 +- observe(),立即订阅 Observable,然后启动 8 大执行步骤,返回一个拷贝的 Observable,订阅时立即回调给你结果。 +- toObservable(),返回一个原始的 Observable,必须手动订阅才会去执行 8 大步骤。 diff --git a/docs/high-availability/hystrix-request-cache.md b/docs/high-availability/hystrix-request-cache.md index 1222af3e6..2c196022f 100644 --- a/docs/high-availability/hystrix-request-cache.md +++ b/docs/high-availability/hystrix-request-cache.md @@ -1,4 +1,5 @@ -## 基于 request cache 请求缓存技术优化批量商品数据查询接口 +# 基于 request cache 请求缓存技术优化批量商品数据查询接口 + Hystrix command 执行时 8 大步骤第三步,就是检查 Request cache 是否有缓存。 首先,有一个概念,叫做 Request Context 请求上下文,一般来说,在一个 web 应用中,如果我们用到了 Hystrix,我们会在一个 filter 里面,对每一个请求都施加一个请求上下文。就是说,每一次请求,就是一次请求上下文。然后在这次请求上下文中,我们会去执行 N 多代码,调用 N 多依赖服务,有的依赖服务可能还会调用好几次。 @@ -19,7 +20,8 @@ HystrixCommand 和 HystrixObservableCommand 都可以指定一个缓存 key, 我们对批量查询商品数据的接口,可以用 request cache 做一个优化,就是说一次请求,就是一次 request context,对相同的商品查询只执行一次,其余重复的都走 request cache。 -### 实现 Hystrix 请求上下文过滤器并注册 +## 实现 Hystrix 请求上下文过滤器并注册 + 定义 HystrixRequestContextFilter 类,实现 Filter 接口。 ```java @@ -71,7 +73,8 @@ public class EshopApplication { } ``` -### command 重写 getCacheKey() 方法 +## command 重写 getCacheKey() 方法 + 在 GetProductInfoCommand 中,重写 getCacheKey() 方法,这样的话,每一次请求的结果,都会放在 Hystrix 请求上下文中。下一次同一个 productId 的数据请求,直接取缓存,无须再调用 run() 方法。 ```java @@ -119,7 +122,8 @@ public class GetProductInfoCommand extends HystrixCommand { 这里写了一个 flushCache() 方法,用于我们开发手动删除缓存。 -### controller 调用 command 查询商品信息 +## controller 调用 command 查询商品信息 + 在一次 web 请求上下文中,传入商品 id 列表,查询多条商品数据信息。对于每个 productId,都创建一个 command。 如果 id 列表没有去重,那么重复的 id,第二次查询的时候就会直接走缓存。 @@ -149,7 +153,8 @@ public class CacheController { } ``` -### 发起请求 +## 发起请求 + 调用接口,查询多个商品的信息。 ``` @@ -172,7 +177,8 @@ http://localhost:8080/getProductInfos?productIds=1,1,1,2,2,5 第一次查询 productId=1 的数据,会调用接口进行查询,不是从缓存中取结果。而随后再出现查询 productId=1 的请求,就直接取缓存了,这样的话,效率明显高很多。 -### 删除缓存 +## 删除缓存 + 我们写一个 UpdateProductInfoCommand,在更新商品信息之后,手动调用之前写的 flushCache(),手动将缓存删除。 ```java @@ -197,4 +203,4 @@ public class UpdateProductInfoCommand extends HystrixCommand { } ``` -这样,以后查询该商品的请求,第一次就会走接口调用去查询最新的商品信息。 \ No newline at end of file +这样,以后查询该商品的请求,第一次就会走接口调用去查询最新的商品信息。 diff --git a/docs/high-availability/hystrix-semphore-isolation.md b/docs/high-availability/hystrix-semphore-isolation.md index cd0e1d3ae..13d5cfb0a 100644 --- a/docs/high-availability/hystrix-semphore-isolation.md +++ b/docs/high-availability/hystrix-semphore-isolation.md @@ -1,21 +1,24 @@ -## 基于 Hystrix 信号量机制实现资源隔离 +# 基于 Hystrix 信号量机制实现资源隔离 + Hystrix 里面核心的一项功能,其实就是所谓的**资源隔离**,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话,就可能导致服务崩溃,甚至说这种故障会不断蔓延。 Hystrix 实现资源隔离,主要有两种技术: -- 线程池 -- 信号量 +- 线程池 +- 信号量 默认情况下,Hystrix 使用线程池模式。 前面已经说过线程池技术了,这一小节就来说说信号量机制实现资源隔离,以及这两种技术的区别与具体应用场景。 -### 信号量机制 +## 信号量机制 + 信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。 ![hystrix-semphore](./images/hystrix-semphore.png) -### 线程池与信号量区别 +## 线程池与信号量区别 + 线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。 线程池隔离技术,是用 Hystrix 自己的线程去执行调用;而信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。 @@ -23,10 +26,12 @@ Hystrix 实现资源隔离,主要有两种技术: ![hystrix-semphore-thread-pool](./images/hystrix-semphore-thread-pool.png) **适用场景**: -- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。 -- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。 -### 信号量简单 Demo +- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。 +- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。 + +## 信号量简单 Demo + 业务背景里,比较适合信号量的是什么场景呢? 比如说,我们一般来说,缓存服务,可能会将一些量特别少、访问又特别频繁的数据,放在自己的纯内存中。 @@ -36,6 +41,7 @@ Hystrix 实现资源隔离,主要有两种技术: 优点在于,不用自己管理线程池啦,不用 care timeout 超时啦,也不需要进行线程的上下文切换啦。信号量做隔离的话,性能相对来说会高一些。 假如这是本地缓存,我们可以通过 cityId,拿到 cityName。 + ```java public class LocationCache { private static Map cityMap = new HashMap<>(); @@ -57,6 +63,7 @@ public class LocationCache { ``` 写一个 GetCityNameCommand,策略设置为**信号量**。run() 方法中获取本地缓存。我们目的就是对获取本地缓存的代码进行资源隔离。 + ```java public class GetCityNameCommand extends HystrixCommand { @@ -80,6 +87,7 @@ public class GetCityNameCommand extends HystrixCommand { ``` 在接口层,通过创建 GetCityNameCommand,传入 cityId,执行 execute() 方法,那么获取本地 cityName 缓存的代码将会进行信号量的资源隔离。 + ```java @RequestMapping("/getProductInfo") @ResponseBody @@ -100,4 +108,4 @@ public String getProductInfo(Long productId) { System.out.println(productInfo); return "success"; } -``` \ No newline at end of file +``` diff --git a/docs/high-availability/hystrix-thread-pool-current-limiting.md b/docs/high-availability/hystrix-thread-pool-current-limiting.md index a99eb1e22..116320f94 100644 --- a/docs/high-availability/hystrix-thread-pool-current-limiting.md +++ b/docs/high-availability/hystrix-thread-pool-current-limiting.md @@ -1,4 +1,5 @@ -## 深入 Hystrix 线程池隔离与接口限流 +# 深入 Hystrix 线程池隔离与接口限流 + 前面讲了 Hystrix 的 request cache 请求缓存、fallback 优雅降级、circuit breaker 断路器快速熔断,这一讲,我们来详细说说 Hystrix 的线程池隔离与接口限流。 ![hystrix-process](./images/hystrix-process.png) @@ -7,7 +8,8 @@ Hystrix 通过判断线程池或者信号量是否已满,超出容量的请求 限流是限制对后端的服务的访问量,比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制,其实是为了避免过大的流量直接打死后端的服务。 -### 线程池隔离技术的设计 +## 线程池隔离技术的设计 + Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃。 **舱壁隔离**,是说将船体内部空间区隔划分成若干个隔舱,一旦某几个隔舱发生破损进水,水流不会在其间相互流动,如此一来船舶在受损时,依然能具有足够的浮力和稳定性,进而减低立即沉船的危险。 @@ -16,36 +18,39 @@ Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进 Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。 -### Hystrix 应用线程池机制的场景 -- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。 -- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。 -- client 调用库随时会变更。 -- client 调用库随时可能会增加新的网络请求的逻辑。 -- client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。 -- client 调用库一般都对调用者来说是个黑盒,包括实现细节、网络访问、默认配置等等。 -- 在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client 调用库发生了某些变化。 -- 即使 client 调用库没有改变,依赖服务本身可能有会发生逻辑上的变化。 -- 有些依赖的 client 调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确。 -- 大多数网络请求都是同步调用的。 -- 调用失败和延迟,也有可能会发生在 client 调用库本身的代码中,不一定就是发生在网络请求中。 +## Hystrix 应用线程池机制的场景 + +- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。 +- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。 +- client 调用库随时会变更。 +- client 调用库随时可能会增加新的网络请求的逻辑。 +- client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。 +- client 调用库一般都对调用者来说是个黑盒,包括实现细节、网络访问、默认配置等等。 +- 在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client 调用库发生了某些变化。 +- 即使 client 调用库没有改变,依赖服务本身可能有会发生逻辑上的变化。 +- 有些依赖的 client 调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确。 +- 大多数网络请求都是同步调用的。 +- 调用失败和延迟,也有可能会发生在 client 调用库本身的代码中,不一定就是发生在网络请求中。 简单来说,就是你必须默认 client 调用库很不靠谱,而且随时可能发生各种变化,所以就要用强制隔离的方式来确保任何服务的故障不会影响当前服务。 -### 线程池机制的优点 -- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。 -- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。 -- 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦。 -- 如果一个 client 调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机。 -- 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层。 +## 线程池机制的优点 + +- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。 +- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。 +- 当一个故障的依赖服务重新变好的时候,可以通过清理掉线程池,瞬间恢复该服务的调用,而如果是 tomcat 线程池被占满,再恢复就很麻烦。 +- 如果一个 client 调用库配置有问题,线程池的健康状况随时会报告,比如成功/失败/拒绝/超时的次数统计,然后可以近实时热修改依赖服务的调用配置,而不用停机。 +- 基于线程池的异步本质,可以在同步的调用之上,构建一层异步调用层。 简单来说,最大的好处,就是资源隔离,确保说任何一个依赖服务故障,不会拖垮当前的这个服务。 -### 线程池机制的缺点 -- 线程池机制最大的缺点就是增加了 CPU 的开销。
-除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。 +## 线程池机制的缺点 + +- 线程池机制最大的缺点就是增加了 CPU 的开销。
+ 除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。 -- 每个 command 的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换。 -- Hystrix 官方自己做了一个多线程异步带来的额外开销统计,通过对比多线程异步调用+同步调用得出,Netflix API 每天通过 Hystrix 执行 10 亿次调用,每个服务实例有 40 个以上的线程池,每个线程池有 10 个左右的线程。)最后发现说,用 Hystrix 的额外开销,就是给请求带来了 3ms 左右的延时,最多延时在 10ms 以内,相比于可用性和稳定性的提升,这是可以接受的。 +- 每个 command 的执行都依托一个独立的线程,会进行排队,调度,还有上下文切换。 +- Hystrix 官方自己做了一个多线程异步带来的额外开销统计,通过对比多线程异步调用+同步调用得出,Netflix API 每天通过 Hystrix 执行 10 亿次调用,每个服务实例有 40 个以上的线程池,每个线程池有 10 个左右的线程。)最后发现说,用 Hystrix 的额外开销,就是给请求带来了 3ms 左右的延时,最多延时在 10ms 以内,相比于可用性和稳定性的提升,这是可以接受的。 我们可以用 Hystrix semaphore 技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量。 @@ -53,14 +58,15 @@ semaphore 技术可以用来限流和削峰,但是不能用来对调用延迟 `execution.isolation.strategy` 设置为 `SEMAPHORE`,那么 Hystrix 就会用 semaphore 机制来替代线程池机制,来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候,底层的网络调用延迟很严重,那么是无法 timeout 的,只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后,就会立即开启限流。 -### 接口限流 Demo +## 接口限流 Demo + 假设一个线程池大小为 8,等待队列的大小为 10。timeout 时长我们设置长一些,20s。 在 command 内部,写死代码,做一个 sleep,比如 sleep 3s。 -- withCoreSize:设置线程池大小。 -- withMaxQueueSize:设置等待队列大小。 -- withQueueSizeRejectionThreshold:这个与 withMaxQueueSize 配合使用,等待队列的大小,取得是这两个参数的较小值。 +- withCoreSize:设置线程池大小。 +- withMaxQueueSize:设置等待队列大小。 +- withQueueSizeRejectionThreshold:这个与 withMaxQueueSize 配合使用,等待队列的大小,取得是这两个参数的较小值。 如果只设置了线程池大小,另外两个 queue 相关参数没有设置的话,等待队列是处于关闭的状态。 @@ -159,5 +165,5 @@ ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specificat ProductInfo(id=null, name=降级商品, price=null, pictureList=null, specification=null, service=null, color=null, size=null, shopId=null, modifiedTime=null, cityId=null, cityName=null, brandId=null, brandName=null) {"id": -2, "name": "iphone7手机", "price": 5599, "pictureList":"a.jpg,b.jpg", "specification": "iphone7的规格", "service": "iphone7的售后服务", "color": "红色,白色,黑色", "size": "5.5", "shopId": 1, "modifiedTime": "2017-01-01 12:00:00", "cityId": 1, "brandId": 1} // 后面都是一些正常的商品信息,就不贴出来了 -//... +// ... ``` diff --git a/docs/high-availability/hystrix-thread-pool-isolation.md b/docs/high-availability/hystrix-thread-pool-isolation.md index 8fa440865..735e741f4 100644 --- a/docs/high-availability/hystrix-thread-pool-isolation.md +++ b/docs/high-availability/hystrix-thread-pool-isolation.md @@ -1,11 +1,13 @@ -## 基于 Hystrix 线程池技术实现资源隔离 +# 基于 Hystrix 线程池技术实现资源隔离 + [上一讲](./e-commerce-website-detail-page-architecture.md)提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。 资源隔离,就是说,你如果要把对某一个依赖服务的所有调用请求,全部隔离在同一份资源池内,不会去用其它资源了,这就叫资源隔离。哪怕对这个依赖服务,比如说商品服务,现在同时发起的调用量已经到了 1000,但是分配给商品服务线程池内就 10 个线程,最多就只会用这 10 个线程去执行。不会因为对商品服务调用的延迟,将 Tomcat 内部所有的线程资源全部耗尽。 Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 Command。这也是 Hystrix 最最基本的资源隔离技术。 -### 利用 HystrixCommand 获取单条数据 +## 利用 HystrixCommand 获取单条数据 + 我们通过将调用商品服务的操作封装在 HystrixCommand 中,限定一个 key,比如下面的 `GetProductInfoCommandGroup`,在这里我们可以简单认为这是一个线程池,每次调用商品服务,就只会用该线程池中的资源,不会再去用其它线程资源了。 ```java @@ -35,7 +37,7 @@ public class GetProductInfoCommand extends HystrixCommand { @ResponseBody public String getProductInfo(Long productId) { HystrixCommand getProductInfoCommand = new GetProductInfoCommand(productId); - + // 通过command执行,获取最新商品数据 ProductInfo productInfo = getProductInfoCommand.execute(); System.out.println(productInfo); @@ -45,7 +47,8 @@ public String getProductInfo(Long productId) { 上面执行的是 execute() 方法,其实是同步的。也可以对 command 调用 queue() 方法,它仅仅是将 command 放入线程池的一个等待队列,就立即返回,拿到一个 Future 对象,后面可以继续做其它一些事情,然后过一段时间对 Future 调用 get() 方法获取数据。这是异步的。 -### 利用 HystrixObservableCommand 批量获取数据 +## 利用 HystrixObservableCommand 批量获取数据 + 只要是获取商品数据,全部都绑定到同一个线程池里面去,我们通过 HystrixObservableCommand 的一个线程去执行,而在这个线程里面,批量把多个 productId 的 productInfo 拉回来。 ```java @@ -78,6 +81,7 @@ public class GetProductInfosCommand extends HystrixObservableCommand 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. @@ -12,26 +14,29 @@ Sentinel 项目地址:https://github.com/alibaba/Sentinel 而 Sentinel 的侧重点在于: -- 多样化的流量控制 -- 熔断降级 -- 系统负载保护 -- 实时监控和控制台 +- 多样化的流量控制 +- 熔断降级 +- 系统负载保护 +- 实时监控和控制台 两者解决的问题还是有比较大的不同的,下面我们来具体对比一下。 ## 共同特性 + ### 1. 资源模型和执行模型上的对比 + Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象 `HystrixCommand` 或 `HystrixObservableCommand`,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 `commandKey` 和 `groupKey`(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。 -**注**:关于 Hystrix 的详细介绍及代码演示,可以参考本项目[高可用架构](/docs/high-availability/README.md)-Hystrix 部分的详细说明。 +**注**:关于 Hystrix 的详细介绍及代码演示,可以参考本项目[高可用架构](./README.md)-Hystrix 部分的详细说明。 Sentinel 的设计则更为简单。相比 Hystrix Command 强依赖隔离规则,Sentinel 的资源定义与规则配置的耦合度更低。Hystrix 的 Command 强依赖于隔离规则配置的原因是隔离规则会直接影响 Command 的执行。在执行的时候 Hystrix 会解析 Command 的隔离规则来创建 RxJava Scheduler 并在其上调度执行,若是线程池模式则 Scheduler 底层的线程池为配置的线程池,若是信号量模式则简单包装成当前线程执行的 Scheduler。 -而 Sentinel 则不一样,开发的时候只需要考虑这个方法/代码是否需要保护,置于用什么来保护,可以任何时候动态实时的区修改。 +而 Sentinel 则不一样,开发的时候只需要考虑这个方法/代码是否需要保护,至于用什么来保护,可以任何时候动态实时的去修改。 从 `0.1.1` 版本开始,Sentinel 还支持基于注解的资源定义方式,可以通过注解参数指定异常处理函数和 fallback 函数。Sentinel 提供多样化的规则配置方式。除了直接通过 `loadRules` API 将规则注册到内存态之外,用户还可以注册各种外部数据源来提供动态的规则。用户可以根据系统当前的实时情况去动态地变更规则配置,数据源会将变更推送至 Sentinel 并即时生效。 ### 2. 隔离设计上的对比 + 隔离是 Hystrix 的核心功能之一。Hystrix 提供两种隔离策略:线程池隔离 `Bulkhead Pattern` 和信号量隔离,其中最推荐也是最常用的是**线程池隔离**。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。 但是,实际情况下,线程池隔离并没有带来非常多的好处。最直接的影响,就是会让机器资源碎片化。考虑这样一个常见的场景,在 Tomcat 之类的 Servlet 容器使用 Hystrix,本身 Tomcat 自身的线程数目就非常多了(可能到几十或一百多),如果加上 Hystrix 为各个资源创建的线程池,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。另外,线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。 @@ -41,62 +46,70 @@ Hystrix 的信号量隔离限制对某个资源调用的并发数。这样的隔 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) +- **直接拒绝模式**:即超出的请求直接拒绝。 +- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。 + ![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 提供 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 | + +| # | Sentinel | Hystrix | +| -------------- | ---------------------------------------------- | ----------------------------- | +| 隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 | +| 熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 | +| 实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) | +| 规则配置 | 支持多种数据源 | 支持多种数据源 | +| 扩展性 | 多个扩展点 | 插件的形式 | +| 基于注解的支持 | 支持 | 支持 | +| 限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 | +| 流量整形 | 支持慢启动、匀速器模式 | 不支持 | +| 系统负载保护 | 支持 | 不支持 | +| 控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 | +| 常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC | Servlet、Spring Cloud Netflix | diff --git a/docs/high-concurrency/README.md b/docs/high-concurrency/README.md index 20eb73e4d..b80d8922d 100644 --- a/docs/high-concurrency/README.md +++ b/docs/high-concurrency/README.md @@ -1,70 +1,69 @@ # 高并发架构 -## [消息队列](/docs/high-concurrency/mq-interview.md) +## [消息队列](./mq-interview.md) -* [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](/docs/high-concurrency/why-mq.md) -* [如何保证消息队列的高可用?](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md) -* [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md) -* [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md) -* [如何保证消息的顺序性?](/docs/high-concurrency/how-to-ensure-the-order-of-messages.md) -* [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](/docs/high-concurrency/mq-time-delay-and-expired-failure.md) -* [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](/docs/high-concurrency/mq-design.md) +- [为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?](./why-mq.md) +- [如何保证消息队列的高可用?](./how-to-ensure-high-availability-of-message-queues.md) +- [如何保证消息不被重复消费?(如何保证消息消费的幂等性)](./how-to-ensure-that-messages-are-not-repeatedly-consumed.md) +- [如何保证消息的可靠性传输?(如何处理消息丢失的问题)](./how-to-ensure-the-reliable-transmission-of-messages.md) +- [如何保证消息的顺序性?](./how-to-ensure-the-order-of-messages.md) +- [如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?](./mq-time-delay-and-expired-failure.md) +- [如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。](./mq-design.md) -## [搜索引擎](/docs/high-concurrency/es-introduction.md) +## [搜索引擎](./es-introduction.md) -* [ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?](/docs/high-concurrency/es-architecture.md) -* [ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?](/docs/high-concurrency/es-write-query-search.md) -* [ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](/docs/high-concurrency/es-optimizing-query-performance.md) -* [ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](/docs/high-concurrency/es-production-cluster.md) +- [ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?](./es-architecture.md) +- [ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?](./es-write-query-search.md) +- [ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?](./es-optimizing-query-performance.md) +- [ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?](./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) +- [在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?](./why-cache.md) +- [Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?](./redis-single-thread-model.md) +- [Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?](./redis-data-types.md) +- [Redis 的过期策略都有哪些?手写一下 LRU 代码实现?](./redis-expiration-policies-and-lru.md) +- [如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?](./how-to-ensure-high-concurrency-and-high-availability-of-redis.md) +- [Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?](./redis-persistence.md) +- [Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?](./redis-cluster.md) +- [了解什么是 redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?](./redis-caching-avalanche-and-caching-penetration.md) +- [如何保证缓存与数据库的双写一致性?](./redis-consistence.md) +- [Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?](./redis-cas.md) +- [生产环境中的 Redis 是怎么部署的?](./redis-production-environment.md) +- [有了解过 Redis rehash 的过程吗?](./redis-rehash.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) +- [为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?](./database-shard.md) +- [现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?](./database-shard-method.md) +- [如何设计可以动态扩容缩容的分库分表方案?](./database-shard-dynamic-expand.md) +- [分库分表之后,id 主键如何处理?](./database-shard-global-id-generate.md) ## 读写分离 -* [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](/docs/high-concurrency/mysql-read-write-separation.md) +- [如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?](./mysql-read-write-separation.md) ## 高并发系统 -* [如何设计一个高并发系统?](/docs/high-concurrency/high-concurrency-design.md) +- [如何设计一个高并发系统?](./high-concurrency-design.md) ---- +--- ## 公众号 -GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - - - - + + + +
- -
- 公众平台 -

-
- -
- 个人微信 -

-
+
+
+
+
+ +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! + +
diff --git a/docs/high-concurrency/database-shard-dynamic-expand.md b/docs/high-concurrency/database-shard-dynamic-expand.md index 67277dc38..51d9bbedc 100644 --- a/docs/high-concurrency/database-shard-dynamic-expand.md +++ b/docs/high-concurrency/database-shard-dynamic-expand.md @@ -1,16 +1,19 @@ +# 动态扩缩容方案 + ## 面试题 + 如何设计可以动态扩容缩容的分库分表方案? ## 面试官心理分析 对于分库分表来说,主要是面对以下问题: -* 选择一个数据库中间件,调研、学习、测试; -* 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表; -* 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写; -* 完成单库单表到分库分表的**迁移**,双写方案; -* 线上系统开始基于分库分表对外提供服务; -* 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢? +- 选择一个数据库中间件,调研、学习、测试; +- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表; +- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写; +- 完成单库单表到分库分表的**迁移**,双写方案; +- 线上系统开始基于分库分表对外提供服务; +- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢? 这个是你必须面对的一个事儿,就是你已经弄好分库分表方案了,然后一堆库和表都建好了,基于分库分表中间件的代码开发啥的都好了,测试都 ok 了,数据能均匀分布到各个库和各个表里去,而且接着你还通过双写的方案咔嚓一下上了系统,已经直接基于分库分表方案在搞了。 @@ -21,9 +24,10 @@ ## 面试题剖析 ### 停机扩容(不推荐) + 这个方案就跟停机迁移一样,步骤几乎一致,唯一的一点就是那个导数的工具,是把现有库表的数据抽出来慢慢倒入到新的库和表里去。但是最好别这么玩儿,有点不太靠谱,因为既然**分库分表**就说明数据量实在是太大了,可能多达几亿条,甚至几十亿,你这么玩儿,可能会出问题。 -从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1小时数据就导完了。这没有问题。 +从单库单表迁移到分库分表的时候,数据量并不是很大,单表最大也就两三千万。那么你写个工具,多弄几台机器并行跑,1 小时数据就导完了。这没有问题。 如果 3 个库 + 12 个表,跑了一段时间了,数据量都 1~2 亿了。光是导 2 亿数据,都要导个几个小时,6 点,刚刚导完数据,还要搞后续的修改配置,重启系统,测试验证,10 点才可以搞完。所以不能这么搞。 @@ -33,7 +37,7 @@ 我可以告诉各位同学,这个分法,第一,基本上国内的互联网肯定都是够用了,第二,无论是并发支撑还是数据量支撑都没问题。 -每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载 32 * 1000 = 32000 的写并发,如果每个库承载 1500 的写并发,32 * 1500 = 48000 的写并发,接近 5 万每秒的写入并发,前面再加一个MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。 +每个库正常承载的写入并发量是 1000,那么 32 个库就可以承载 $32 \times 1000 = 32000$ 的写并发,如果每个库承载 1500 的写并发,总共就是 $32 \times 1500 = 48000$ 的写并发,接近 5 万每秒的写入并发,前面再加一个 MQ,削峰,每秒写入 MQ 8 万条数据,每秒消费 5 万条数据。 有些除非是国内排名非常靠前的这些公司,他们的最核心的系统的数据库,可能会出现几百台数据库的这么一个规模,128 个库,256 个库,512 个库。 @@ -43,18 +47,18 @@ 谈分库分表的扩容,**第一次分库分表,就一次性给他分个够**,32 个库,1024 张表,可能对大部分的中小型互联网公司来说,已经可以支撑好几年了。 -一个实践是利用 `32 * 32` 来分库分表,即分为 32 个库,每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模路由到库里的表。 +一个实践是利用 $32 \times 32$ 来分库分表,即分为 32 个库,每个库里一个表分为 32 张表。一共就是 1024 张表。根据某个 id 先根据 32 取模路由到库,再根据 32 取模路由到库里的表。 | orderId | id % 32 (库) | id / 32 % 32 (表) | -|---|---|---| -| 259 | 3 | 8 | -| 1189 | 5 | 5 | -| 352 | 0 | 11 | -| 4593 | 17 | 15 | +| ------- | ------------ | ----------------- | +| 259 | 3 | 8 | +| 1189 | 5 | 5 | +| 352 | 0 | 11 | +| 4593 | 17 | 15 | 刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 MySQL 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 MySQL 服务器之间做迁移就可以了。然后系统配合改一下配置即可。 -比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是 1024 个表。 +比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表,那么最多是 1024 个表。 这么搞,是不用自己写代码做数据迁移的,都交给 DBA 来搞好了,但是 DBA 确实是需要做一些库表迁移的工作,但是总比你自己写代码,然后抽数据导数据来的效率高得多吧。 @@ -62,7 +66,7 @@ 这里对步骤做一个总结: -1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了。 +1. 设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 $32 库 \times 32 表$,对于大部分公司来说,可能几年都够了。 2. 路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表 3. 扩容的时候,申请增加更多的数据库服务器,装好 MySQL,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器。 4. 由 DBA 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的。 diff --git a/docs/high-concurrency/database-shard-global-id-generate.md b/docs/high-concurrency/database-shard-global-id-generate.md index d8ef35682..ffba27349 100644 --- a/docs/high-concurrency/database-shard-global-id-generate.md +++ b/docs/high-concurrency/database-shard-global-id-generate.md @@ -1,4 +1,7 @@ +# 主键 ID 如何处理? + ## 面试题 + 分库分表之后,id 主键如何处理? ## 面试官心理分析 @@ -33,7 +36,7 @@ 适合的场景:如果你是要随机生成个什么文件名、编号之类的,你可以用 UUID,但是作为主键是不能用 UUID 的。 -``` java +```java UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf ``` @@ -47,16 +50,16 @@ UUID.randomUUID().toString().replace("-", "") -> sfsdf23423rr234sfdaf snowflake 算法是 twitter 开源的分布式 id 生成算法,采用 Scala 语言实现,是把一个 64 位的 long 型的 id,1 个 bit 是不用的,用其中的 41 bits 作为毫秒数,用 10 bits 作为工作机器 id,12 bits 作为序列号。 -* 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。 -* 41 bits:表示的是时间戳,单位是毫秒。41 bits 可以表示的数字多达 `2^41 - 1` ,也就是可以标识 `2^41 - 1` 个毫秒值,换算成年就是表示69年的时间。 -* 10 bits:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。但是 10 bits 里 5 个 bits 代表机房 id,5 个 bits 代表机器 id。意思就是最多代表 `2^5` 个机房(32 个机房),每个机房里可以代表 `2^5` 个机器(32台机器)。 -* 12 bits:这个是用来记录同一个毫秒内产生的不同 id,12 bits 可以代表的最大正整数是 `2^12 - 1 = 4096` ,也就是说可以用这个 12 bits 代表的数字来区分**同一个毫秒内**的 4096 个不同的 id。 +- 1 bit:不用,为啥呢?因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。 +- 41 bits:表示的是时间戳,单位是毫秒。41 bits 可以表示的数字多达 `2^41 - 1` ,也就是可以标识 `2^41 - 1` 个毫秒值,换算成年就是表示 69 年的时间。 +- 10 bits:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。但是 10 bits 里 5 个 bits 代表机房 id,5 个 bits 代表机器 id。意思就是最多代表 `2^5` 个机房(32 个机房),每个机房里可以代表 `2^5` 个机器(32 台机器)。 +- 12 bits:这个是用来记录同一个毫秒内产生的不同 id,12 bits 可以代表的最大数字是 `2^12 - 1 = 4095` ,也就是说可以用这个 12 bits 代表的数字来区分**同一个毫秒内**的 4096 个(数字 0 到数字 4095)不同的 id。 -``` +```sh 0 | 0001100 10100010 10111110 10001001 01011100 00 | 10001 | 1 1001 | 0000 00000000 ``` -``` java +```java public class IdWorker { private long workerId; @@ -170,7 +173,7 @@ public class IdWorker { ``` -怎么说呢,大概这个意思吧,就是说 41 bit 是当前毫秒单位的一个时间戳,就这意思;然后 5 bit 是你传递进来的一个**机房** id(但是最大只能是 32 以内),另外 5 bit 是你传递进来的**机器** id(但是最大只能是 32 以内),剩下的那个 12 bit序列号,就是如果跟你上次生成 id 的时间还在一个毫秒内,那么会把顺序给你累加,最多在 4096 个序号以内。 +怎么说呢,大概这个意思吧,就是说 41 bit 是当前毫秒单位的一个时间戳,就这意思;然后 5 bit 是你传递进来的一个**机房** id(但是最大只能是 32 以内),另外 5 bit 是你传递进来的**机器** id(但是最大只能是 32 以内),剩下的那个 12 bit 序列号,就是如果跟你上次生成 id 的时间还在一个毫秒内,那么会把顺序给你累加,最多在 4096 个序号以内。 所以你自己利用这个工具类,自己搞一个服务,然后对每个机房的每个机器都初始化这么一个东西,刚开始这个机房的这个机器的序号就是 0。然后每次接收到一个请求,说这个机房的这个机器要生成一个 id,你就找到对应的 Worker 生成。 diff --git a/docs/high-concurrency/database-shard-method.md b/docs/high-concurrency/database-shard-method.md index 526420902..1430be928 100644 --- a/docs/high-concurrency/database-shard-method.md +++ b/docs/high-concurrency/database-shard-method.md @@ -1,4 +1,7 @@ +# 分库分表如何平滑过渡? + ## 面试题 + 现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上? ## 面试官心理分析 @@ -19,7 +22,7 @@ 导数完了之后,就 ok 了,修改系统的数据库连接配置啥的,包括可能代码和 SQL 也许有修改,那你就用最新的代码,然后直接启动连到新的分库分表上去。 -验证一下,ok了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。 +验证一下,ok 了,完美,大家伸个懒腰,看看看凌晨 4 点钟的北京夜景,打个滴滴回家吧。 但是这个方案比较 low,谁都能干,我们来看看高大上一点的方案。 diff --git a/docs/high-concurrency/database-shard.md b/docs/high-concurrency/database-shard.md index eaaaa3a90..94e274764 100644 --- a/docs/high-concurrency/database-shard.md +++ b/docs/high-concurrency/database-shard.md @@ -1,4 +1,7 @@ +# 为什么要分库分表? + ## 面试题 + 为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的? ## 面试官心理分析 @@ -8,6 +11,7 @@ ## 面试题剖析 ### 为什么要分库分表?(设计高并发系统的时候,数据库层面该如何设计?) + 说白了,分库分表是两回事儿,大家可别搞混了,可能是光分库不分表,也可能是光分表不分库,都有可能。 我先给大家抛出来一个场景。 @@ -36,11 +40,11 @@ 这就是所谓的**分库分表**,为啥要分库分表?你明白了吧。 -| # | 分库分表前 | 分库分表后 | -|---|---|---| -| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL从单机到多机,能承受的并发增加了多倍 | -| 磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 | -| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 | +| # | 分库分表前 | 分库分表后 | +| ------------ | ---------------------------- | -------------------------------------------- | +| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL 从单机到多机,能承受的并发增加了多倍 | +| 磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 | +| SQL 执行性能 | 单表数据量太大,SQL 越跑越慢 | 单表数据量减少,SQL 执行效率明显提升 | ### 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点? @@ -48,11 +52,11 @@ 比较常见的包括: -* Cobar -* TDDL -* Atlas -* Sharding-jdbc -* Mycat +- Cobar +- TDDL +- Atlas +- Sharding-jdbc +- Mycat #### Cobar @@ -96,7 +100,7 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套 这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。 -还有**表层面的拆分**,就是分表,将一个表变成 N 个表,就是**让每个表的数据量控制在一定范围内**,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的SQL越复杂,就最好让单表行数越少。 +还有**表层面的拆分**,就是分表,将一个表变成 N 个表,就是**让每个表的数据量控制在一定范围内**,保证 SQL 的性能。否则单表数据量越大,SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的 SQL 越复杂,就最好让单表行数越少。 好了,无论分库还是分表,上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后,**中间件可以根据你指定的某个字段值**,比如说 userid,**自动路由到对应的库上去,然后再自动路由到对应的表里去**。 @@ -104,8 +108,8 @@ Mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套 而且这儿还有两种**分库分表的方式**: -* 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。 -* 或者是按照某个字段 hash 一下均匀分散,这个较为常用。 +- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。 +- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。 range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。 diff --git a/docs/high-concurrency/es-architecture.md b/docs/high-concurrency/es-architecture.md index 6542c2a02..60f6c5b5c 100644 --- a/docs/high-concurrency/es-architecture.md +++ b/docs/high-concurrency/es-architecture.md @@ -1,4 +1,7 @@ +# ES 的分布式架构原理 + ## 面试题 + ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)? ## 面试官心理分析 @@ -15,15 +18,15 @@ ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊 ElasticSearch 设计的理念就是分布式搜索引擎,底层其实还是基于 lucene 的。核心思想就是在多台机器上启动多个 ES 进程实例,组成了一个 ES 集群。 -ES 中存储数据的**基本单位是索引**,比如说你现在要在 ES 中存储一些订单数据,你就应该在 ES 中创建一个索引 `order_idx` ,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一张表。 +ES 中存储数据的**基本单位是索引**,比如说你现在要在 ES 中存储一些订单数据,你就应该在 ES 中创建一个索引 `order_idx` ,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是 mysql 里的一个数据库。 -``` -index -> type -> mapping -> document -> field。 +``` +index -> type -> mapping -> document -> field ``` 这样吧,为了做个更直白的介绍,我在这里做个类比。但是切记,不要划等号,类比只是为了便于理解。 -index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比,一个 index 里可以有多个 type,每个 type 的字段都是差不多的,但是有一些略微的差别。假设有一个 index,是订单 index,里面专门是放订单数据的。就好比说你在 mysql 中建表,有些订单是实物商品的订单,比如一件衣服、一双鞋子;有些订单是虚拟商品的订单,比如游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字段可能有略微的一些差别。 +index 相当于 mysql 数据库。而 type 没法跟 mysql 里去对比,一个 index 里可以有多个 type,每个 type 的字段都是差不多的,但是有一些略微的差别。假设有一个 index,是订单 index,里面专门是放订单数据的。就好比说你在 mysql 中建表,有些订单是实物商品的订单,比如一件衣服、一双鞋子;有些订单是虚拟商品的订单,比如游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字段可能有略微的一些差别。 所以就会在订单 index 里,建两个 type,一个是实物商品订单 type,一个是虚拟商品订单 type,这两个 type 大部分字段是一样的,少部分字段是不一样的。 @@ -41,7 +44,7 @@ index 相当于 mysql 里的一张表。而 type 没法跟 mysql 里去对比, ES 集群多个节点,会自动选举一个节点为 master 节点,这个 master 节点其实就是干一些管理的工作的,比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了,那么会重新选举一个节点为 master 节点。 -如果是非 master节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器,重启了之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。 +如果是非 master 节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器,重启了之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。 说得更简单一点,就是说如果某个非 master 节点宕机了。那么此节点上的 primary shard 不就没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。 diff --git a/docs/high-concurrency/es-introduction.md b/docs/high-concurrency/es-introduction.md index d9d42a8a7..0fc115864 100644 --- a/docs/high-concurrency/es-introduction.md +++ b/docs/high-concurrency/es-introduction.md @@ -1,19 +1,23 @@ +# 搜索引擎介绍 + ## Lucene 和 ES 的前世今生 + Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。 ElasticSearch 基于 Lucene,隐藏了 lucene 的复杂性,提供了简单易用的 RESTful api / Java api 接口(另外还有其他语言的 api 接口)。 -* 分布式的文档存储引擎 -* 分布式的搜索引擎和分析引擎 -* 分布式,支持 PB 级数据 +- 分布式的文档存储引擎 +- 分布式的搜索引擎和分析引擎 +- 分布式,支持 PB 级数据 ## ES 的核心概念 ### Near Realtime + 近实时,有两层意思: -* 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s) -* 基于 ES 执行搜索和分析可以达到秒级 +- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s) +- 基于 ES 执行搜索和分析可以达到秒级 ### Cluster 集群 @@ -27,7 +31,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分 文档是 ES 中最小的数据单元,一个 document 可以是一条客户数据、一条商品分类数据、一条订单数据,通常用 json 数据结构来表示。每个 index 下的 type,都可以存储多条 document。一个 document 里面有多个 field,每个 field 就是一个数据字段。 -``` json +```json { "product_id": "1", "product_name": "iPhone X", @@ -39,7 +43,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分 ### Index -索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 ducument。 +索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 document。 ### Type @@ -51,7 +55,7 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分 ### replica -任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5个 replica shard,最小的高可用配置,是 2 台服务器。 +任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5 个 replica shard,最小的高可用配置,是 2 台服务器。 这么说吧,shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard,而 replica shard 一般简称为 replica。 @@ -59,10 +63,10 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分 ## ES 核心概念 vs. DB 核心概念 -| ES | DB | -|---|---| -| index | 数据库 | -| type | 数据表 | -| docuemnt | 一行数据 | +| ES | DB | +| -------- | -------- | +| index | 数据库 | +| type | 数据表 | +| document | 一行数据 | 以上是一个简单的类比。 diff --git a/docs/high-concurrency/es-optimizing-query-performance.md b/docs/high-concurrency/es-optimizing-query-performance.md index 91ec7a386..b59a21049 100644 --- a/docs/high-concurrency/es-optimizing-query-performance.md +++ b/docs/high-concurrency/es-optimizing-query-performance.md @@ -1,4 +1,7 @@ +# ES 查询性能优化 + ## 面试题 + ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊? ## 面试官心理分析 @@ -19,7 +22,7 @@ ES 在数据量很大的情况下(数十亿级别)如何提高查询效率 es 的搜索引擎严重依赖于底层的 `filesystem cache` ,你如果给 `filesystem cache` 更多的内存,尽量让内存可以容纳所有的 `idx segment file ` 索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。 -性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1秒、5秒、10秒。但如果是走 `filesystem cache` ,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。 +性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能绝对是秒级别的,1 秒、5 秒、10 秒。但如果是走 `filesystem cache` ,是走纯内存的,那么一般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。 这里有个真实的案例。某个公司 es 节点有 3 台机器,每台机器看起来内存很多,64G,总内存就是 `64 * 3 = 192G` 。每台机器给 es jvm heap 是 `32G` ,那么剩下来留给 `filesystem cache` 的就是每台机器才 `32G` ,总共集群里给 `filesystem cache` 的就是 `32 * 3 = 96G` 内存。而此时,整个磁盘上索引数据文件,在 3 台机器上一共占用了 `1T` 的磁盘容量,es 数据量是 `1T` ,那么每台机器的数据量是 `300G` 。这样性能好吗? `filesystem cache` 的内存才 100G,十分之一的数据可以放内存,其他的都在磁盘,然后你执行搜索操作,大部分操作都是走磁盘,性能肯定差。 @@ -39,7 +42,7 @@ hbase 的特点是**适用于海量数据的在线存储**,就是对 hbase 可 其实可以做**数据预热**。 -举个例子,拿微博来说,你可以把一些大V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 `filesystem cache` 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。 +举个例子,拿微博来说,你可以把一些大 V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 `filesystem cache` 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。 或者是电商,你可以将平时查看最多的一些商品,比如说 iphone 8,热数据提前后台搞个程序,每隔 1 分钟自己主动访问一次,刷到 `filesystem cache` 里去。 diff --git a/docs/high-concurrency/es-production-cluster.md b/docs/high-concurrency/es-production-cluster.md index ea64087f0..0134cc725 100644 --- a/docs/high-concurrency/es-production-cluster.md +++ b/docs/high-concurrency/es-production-cluster.md @@ -1,4 +1,7 @@ +# ES 生产集群架构 + ## 面试题 + ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片? ## 面试官心理分析 @@ -15,8 +18,8 @@ ES 生产集群的部署架构是什么?每个索引的数据量大概有多 但是如果你确实没干过,也别虚,我给你说一个基本的版本,你到时候就简单说一下就好了。 -* es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。 -* 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。 -* 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。 +- es 生产集群我们部署了 5 台机器,每台机器是 6 核 64G 的,集群总内存是 320G。 +- 我们 es 集群的日增量数据大概是 2000 万条,每天日增量数据大概是 500MB,每月增量数据大概是 6 亿,15G。目前系统已经运行了几个月,现在 es 集群里数据总量大概是 100G 左右。 +- 目前线上有 5 个索引(这个结合你们自己业务来,看看自己有哪些数据可以放 es 的),每个索引的数据量大概是 20G,所以这个数据量之内,我们每个索引分配的是 8 个 shard,比默认的 5 个 shard 多了 3 个 shard。 大概就这么说一下就行了。 diff --git a/docs/high-concurrency/es-write-query-search.md b/docs/high-concurrency/es-write-query-search.md index 90a06e5dd..600a2a8b5 100644 --- a/docs/high-concurrency/es-write-query-search.md +++ b/docs/high-concurrency/es-write-query-search.md @@ -1,4 +1,7 @@ +# ES 写入数据原理 + ## 面试题 + ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗? ## 面试官心理分析 @@ -11,10 +14,10 @@ ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是 ### es 写数据过程 -* 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node` (协调节点)。 -* `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。 -* 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node` 。 -* `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 +- 客户端选择一个 node 发送请求过去,这个 node 就是 `coordinating node` (协调节点)。 +- `coordinating node` 对 document 进行**路由**,将请求转发给对应的 node(有 primary shard)。 +- 实际的 node 上的 `primary shard` 处理请求,然后将数据同步到 `replica node` 。 +- `coordinating node` 如果发现 `primary node` 和所有 `replica node` 都搞定之后,就返回响应结果给客户端。 ![es-write](./images/es-write.png) @@ -22,27 +25,27 @@ ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是 可以通过 `doc id` 来查询,会根据 `doc id` 进行 hash,判断出来当时把 `doc id` 分配到了哪个 shard 上面去,从那个 shard 去查询。 -* 客户端发送请求到**任意**一个 node,成为 `coordinate node` 。 -* `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。 -* 接收请求的 node 返回 document 给 `coordinate node` 。 -* `coordinate node` 返回 document 给客户端。 +- 客户端发送请求到**任意**一个 node,成为 `coordinate node` 。 +- `coordinate node` 对 `doc id` 进行哈希路由,将请求转发到对应的 node,此时会使用 `round-robin` **随机轮询算法**,在 `primary shard` 以及其所有 replica 中随机选择一个,让读请求负载均衡。 +- 接收请求的 node 返回 document 给 `coordinate node` 。 +- `coordinate node` 返回 document 给客户端。 ### es 搜索数据过程 es 最强大的是做全文检索,就是比如你有三条数据: -``` +``` java真好玩儿啊 java好难学啊 j2ee特别牛 ``` -你根据 `java` 关键词来搜索,将包含 `java` 的 `document` 给搜索出来。es 就会给你返回:java真好玩儿啊,java好难学啊。 +你根据 `java` 关键词来搜索,将包含 `java` 的 `document` 给搜索出来。es 就会给你返回:java 真好玩儿啊,java 好难学啊。 -* 客户端发送请求到一个 `coordinate node` 。 -* 协调节点将搜索请求转发到**所有**的 shard 对应的 `primary shard` 或 `replica shard` ,都可以。 -* query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id` )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。 -* fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。 +- 客户端发送请求到一个 `coordinate node` 。 +- 协调节点将搜索请求转发到**所有**的 shard 对应的 `primary shard` 或 `replica shard` ,都可以。 +- query phase:每个 shard 将自己的搜索结果(其实就是一些 `doc id` )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。 +- fetch phase:接着由协调节点根据 `doc id` 去各个节点上**拉取实际**的 `document` 数据,最终返回给客户端。 > 写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。 @@ -72,6 +75,9 @@ translog 日志文件的作用是什么?你执行 commit 操作之前,数据 translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会**丢失** 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 `fsync` 到磁盘,但是性能会差很多。 +- `index.translog.sync_interval` 控制 translog 多久 fsync 到磁盘,最小为 100ms; +- `index.translog.durability` translog 是每 5 秒钟刷新一次还是每次请求都 fsync,这个参数有 2 个取值:request(每次请求都执行 fsync,es 要等 translog fsync 到磁盘后才会返回成功)和 async(默认值,translog 每隔 5 秒钟 fsync 一次)。 + 实际上你在这里,如果面试官没有问你 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 磁盘文件中。 @@ -102,29 +108,29 @@ buffer 每 refresh 一次,就会产生一个 `segment file` ,所以默认情 有以下文档: -| DocId | Doc | -|---|---| -| 1 | 谷歌地图之父跳槽 Facebook | -| 2 | 谷歌地图之父加盟 Facebook | -| 3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook | -| 4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 | -| 5 | 谷歌地图之父拉斯加盟社交网站 Facebook | +| DocId | Doc | +| ----- | ---------------------------------------------- | +| 1 | 谷歌地图之父跳槽 Facebook | +| 2 | 谷歌地图之父加盟 Facebook | +| 3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook | +| 4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 | +| 5 | 谷歌地图之父拉斯加盟社交网站 Facebook | 对文档进行分词之后,得到以下**倒排索引**。 -| WordId | Word | DocIds | -|---|---|---| -| 1 | 谷歌 | 1, 2, 3, 4, 5 | -| 2 | 地图 | 1, 2, 3, 4, 5 | -| 3 | 之父 | 1, 2, 4, 5 | -| 4 | 跳槽 | 1, 4 | -| 5 | Facebook | 1, 2, 3, 4, 5 | -| 6 | 加盟 | 2, 3, 5 | -| 7 | 创始人 | 3 | -| 8 | 拉斯 | 3, 5 | -| 9 | 离开 | 3 | -| 10 | 与 | 4 | -| .. | .. | .. | +| WordId | Word | DocIds | +| ------ | -------- | ------------- | +| 1 | 谷歌 | 1, 2, 3, 4, 5 | +| 2 | 地图 | 1, 2, 3, 4, 5 | +| 3 | 之父 | 1, 2, 4, 5 | +| 4 | 跳槽 | 1, 4 | +| 5 | Facebook | 1, 2, 3, 4, 5 | +| 6 | 加盟 | 2, 3, 5 | +| 7 | 创始人 | 3 | +| 8 | 拉斯 | 3, 5 | +| 9 | 离开 | 3 | +| 10 | 与 | 4 | +| .. | .. | .. | 另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。 @@ -132,7 +138,7 @@ buffer 每 refresh 一次,就会产生一个 `segment file` ,所以默认情 要注意倒排索引的两个重要细节: -* 倒排索引中的所有词项对应一个或多个文档; -* 倒排索引中的词项**根据字典顺序升序排列** +- 倒排索引中的所有词项对应一个或多个文档; +- 倒排索引中的词项**根据字典顺序升序排列** > 上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。 diff --git a/docs/high-concurrency/high-concurrency-design.md b/docs/high-concurrency/high-concurrency-design.md index 3e940e24c..9af4256a2 100644 --- a/docs/high-concurrency/high-concurrency-design.md +++ b/docs/high-concurrency/high-concurrency-design.md @@ -1,9 +1,12 @@ +# 如何设计一个高并发系统 + ## 面试题 + 如何设计一个高并发系统? ## 面试官心理分析 -说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先。 +说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发经验者优先。 如果你确实有真才实学,在互联网公司里干过高并发系统,那你确实拿 offer 基本如探囊取物,没啥问题。面试官也绝对不会这样来问你,否则他就是蠢。 @@ -31,12 +34,12 @@ 可以分为以下 6 点: -* 系统拆分 -* 缓存 -* MQ -* 分库分表 -* 读写分离 -* ElasticSearch +- 系统拆分 +- 缓存 +- MQ +- 分库分表 +- 读写分离 +- ElasticSearch ![high-concurrency-system-design](./images/high-concurrency-system-design.png) diff --git a/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md b/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md index 4ac2e27bd..7ad6f9921 100644 --- a/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md +++ b/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md @@ -1,9 +1,12 @@ +# 如何保证消息队列的高可用? + ## 面试题 + 如何保证消息队列的高可用? ## 面试官心理分析 -如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](/docs/high-concurrency/why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。 +如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](./why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。 要是你傻乎乎的就干用了一个 MQ,各种问题从来没考虑过,那你就杯具了,面试官对你的感觉就是,只会简单使用一些技术,没任何思考,马上对你的印象就不太好了。这样的同学招进来要是做个 20k 薪资以内的普通小弟还凑合,要是做薪资 20k+ 的高工,那就惨了,让你设计个系统,里面肯定一堆坑,出了事故公司受损失,团队一起背锅。 @@ -21,11 +24,11 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 #### 单机模式 -单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的😄,没人生产用单机模式。 +单机模式,就是 Demo 级别的,一般就是你本地启动了玩玩儿的,没人生产用单机模式。 #### 普通集群模式(无高可用性) -普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。 +普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每台机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。 ![mq-7](./images/mq-7.png) @@ -37,7 +40,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 #### 镜像集群模式(高可用性) -这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。 +这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论是元数据还是 queue 里的消息都会**存在于多个实例上**,就是说,每个 RabbitMQ 节点都有这个 queue 的一个**完整镜像**,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把**消息同步**到多个实例的 queue 上。 ![mq-8](./images/mq-8.png) @@ -51,7 +54,7 @@ Kafka 一个最基本的架构认识:由多个 broker 组成,每个 broker 这就是**天然的分布式消息队列**,就是说一个 topic 的数据,是**分散放在多个机器上的,每个机器就放一部分数据**。 -实际上 RabbitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个 queue 的完整数据。 +实际上 RabbitMQ 之类的,并不是分布式消息队列,它就是传统的消息队列,只不过提供了一些集群、HA(High Availability, 高可用性) 的机制而已,因为无论怎么玩儿,RabbitMQ 一个 queue 的数据都是放在一个节点里的,镜像集群模式下,也是每个节点都放这个 queue 的完整数据。 Kafka 0.8 以前,是没有 HA 机制的,就是任何一个 broker 宕机了,那个 broker 上的 partition 就废了,没法写也没法读,没有什么高可用性可言。 @@ -63,7 +66,7 @@ Kafka 0.8 以后,提供了 HA 机制,就是 replica(复制品) 副本机 ![kafka-after](./images/kafka-after.png) -这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 +这么搞,就有所谓的**高可用性**了,因为如果某个 broker 宕机了,没事儿,那个 broker 上面的 partition 在其他机器上都有副本的。如果这个宕机的 broker 上面有某个 partition 的 leader,那么此时会从 follower 中**重新选举**一个新的 leader 出来,大家继续读写那个新的 leader 即可。这就有所谓的高可用性了。 **写数据**的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自己主动从 leader 来 pull 数据。一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为) diff --git a/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md b/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md index 8fefa0c86..bbadee315 100644 --- a/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md +++ b/docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md @@ -1,4 +1,7 @@ +# 如何保证 redis 的高并发和高可用? + ## 面试题 + 如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么? ## 面试官心理分析 @@ -13,8 +16,8 @@ 由于此节内容较多,因此,会分为两个小节进行讲解。 -* [redis 主从架构](/docs/high-concurrency/redis-master-slave.md) -* [redis 基于哨兵实现高可用](/docs/high-concurrency/redis-sentinel.md) +- [redis 主从架构](./redis-master-slave.md) +- [redis 基于哨兵实现高可用](./redis-sentinel.md) redis 实现**高并发**主要依靠**主从架构**,一主多从,一般来说,很多项目其实就足够了,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。 diff --git a/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md b/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md index db42f806b..4e53f36f7 100644 --- a/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md +++ b/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md @@ -1,4 +1,7 @@ +# 如何保证消息不被重复消费? + ## 面试题 + 如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性? ## 面试官心理分析 @@ -17,7 +20,9 @@ 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 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。 +有这么个场景。数据 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 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。 + +注意:新版的 Kafka 已经将 offset 的存储从 Zookeeper 转移至 Kafka brokers,并使用内部位移主题 `__consumer_offsets` 进行存储。 ![mq-10](./images/mq-10.png) @@ -35,11 +40,11 @@ Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一 其实还是得结合业务来思考,我这里给几个思路: -* 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。 -* 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 -* 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 -* 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。 +- 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。 +- 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。 +- 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。 +- 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。 ![mq-11](./images/mq-11.png) -当然,如何保证 MQ 的消费是幂等性的,需要结合具体的业务来看。 +当然,如何保证 MQ 的消费是幂等性的,在实际应用中需要结合具体的业务来看。 diff --git a/docs/high-concurrency/how-to-ensure-the-order-of-messages.md b/docs/high-concurrency/how-to-ensure-the-order-of-messages.md index 69847577b..132608cdb 100644 --- a/docs/high-concurrency/how-to-ensure-the-order-of-messages.md +++ b/docs/high-concurrency/how-to-ensure-the-order-of-messages.md @@ -1,4 +1,7 @@ +# 如何保证消息的顺序性? + ## 面试题 + 如何保证消息的顺序性? ## 面试官心理分析 @@ -15,24 +18,29 @@ 先看看顺序会错乱的俩场景: -* **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者2先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。 +- **RabbitMQ**:一个 queue,多个 consumer。比如,生产者向 RabbitMQ 里发送了三条数据,顺序依次是 data1/data2/data3,压入的是 RabbitMQ 的一个内存队列。有三个消费者分别从 MQ 中消费这三条数据中的一条,结果消费者 2 先执行完操作,把 data2 存入数据库,然后是 data1/data3。这不明显乱了。 ![rabbitmq-order-01](./images/rabbitmq-order-01.png) -* **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。 +- **Kafka**:比如说我们建了一个 topic,有三个 partition。生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到同一个 partition 中去,而且这个 partition 中的数据一定是有顺序的。
消费者从 partition 中取出来数据的时候,也一定是有顺序的。到这里,顺序还是 ok 的,没有错乱。接着,我们在消费者里可能会搞**多个线程来并发处理消息**。因为如果消费者是单线程消费处理,而处理比较耗时的话,比如处理一条消息耗时几十 ms,那么 1 秒钟只能处理几十条消息,这吞吐量太低了。而多个线程并发跑的话,顺序可能就乱掉了。 ![kafka-order-01](./images/kafka-order-01.png) ### 解决方案 #### RabbitMQ -拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点;或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。 + +拆分多个 queue,每个 queue 一个 consumer,就是多一些 queue 而已,确实是麻烦点,这样也会造成吞吐量下降,可以在消费者内部采用多线程的方式取消费。 ![rabbitmq-order-02](./images/rabbitmq-order-02.png) +或者就一个 queue 但是对应一个 consumer,然后这个 consumer 内部用内存队列做排队,然后分发给底层不同的 worker 来处理。 + +注意,这里消费者不直接消费消息,而是将消息根据关键值(比如:订单 id)进行哈希,哈希值相同的消息保存到相同的内存队列里。也就是说,需要保证顺序的消息存到了相同的内存队列,然后由一个唯一的 worker 去处理。 + #### Kafka -* 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。 -* 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。 +- 一个 topic,一个 partition,一个 consumer,内部单线程消费,单线程吞吐量太低,一般不会用这个。 +- 写 N 个内存 queue,具有相同 key 的数据都到同一个内存 queue;然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。 ![kafka-order-02](./images/kafka-order-02.png) diff --git a/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md b/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md index de901f9e8..14bbd7658 100644 --- a/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md +++ b/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md @@ -1,9 +1,12 @@ +# 如何保证消息的可靠性传输? + ## 面试题 + 如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题? ## 面试官心理分析 -这个是肯定的,用 MQ 有个基本原则,就是**数据不能多一条,也不能少一条**,不能多,就是前面说的[重复消费和幂等性问题](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)。不能少,就是说这数据别搞丢了。那这个问题你必须得考虑一下。 +这个是肯定的,用 MQ 有个基本原则,就是**数据不能多一条,也不能少一条**,不能多,就是前面说的[重复消费和幂等性问题](./how-to-ensure-that-messages-are-not-repeatedly-consumed.md)。不能少,就是说这数据别搞丢了。那这个问题你必须得考虑一下。 如果说你这个是用 MQ 来传递非常核心的消息,比如说计费、扣费的一些消息,那必须确保这个 MQ 传递过程中**绝对不会把计费消息给弄丢**。 @@ -19,21 +22,29 @@ 生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。 -此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务 `channel.txSelect` ,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 `channel.txRollback` ,然后重试发送消息;如果收到了消息,那么可以提交事务 `channel.txCommit` 。 +此时可以选择用 RabbitMQ 提供的事务功能,就是生产者**发送数据之前**开启 RabbitMQ 事务 `channel.txSelect()` ,然后发送消息,如果消息没有成功被 RabbitMQ 接收到,那么生产者会收到异常报错,此时就可以回滚事务 `channel.txRollback()` ,然后重试发送消息;如果收到了消息,那么可以提交事务 `channel.txCommit()` 。 -``` java -// 开启事务 -channel.txSelect +```java try { + // 通过工厂创建连接 + connection = factory.newConnection(); + // 获取通道 + channel = connection.createChannel(); + // 开启事务 + channel.txSelect(); + // 这里发送消息 -} catch (Exception e) { - channel.txRollback + channel.basicPublish(exchange, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes()); - // 这里再次重发这条消息 -} + // 模拟出现异常 + int result = 1 / 0; -// 提交事务 -channel.txCommit + // 提交事务 + channel.txCommit(); +} catch (IOException | TimeoutException e) { + // 捕捉异常,回滚事务 + channel.txRollback(); +} ``` 但是问题是,RabbitMQ 事务机制(同步)一搞,基本上**吞吐量会下来,因为太耗性能**。 @@ -44,19 +55,73 @@ channel.txCommit 所以一般在生产者这块**避免数据丢失**,都是用 `confirm` 机制的。 +> 已经在 transaction 事务模式的 channel 是不能再设置成 confirm 模式的,即这两种模式是不能共存的。 + +客户端实现生产者 `confirm` 有 3 种方式: + +1.**普通 confirm 模式**:每发送一条消息后,调用 `waitForConfirms()` 方法,等待服务器端 confirm,如果服务端返回 false 或者在一段时间内都没返回,客户端可以进行消息重发。 + +```java +channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes()); +if (!channel.waitForConfirms()) { + // 消息发送失败 + // ... +} +``` + +2.**批量 confirm 模式**:每发送一批消息后,调用 `waitForConfirms()` 方法,等待服务端 confirm。 + +```java +channel.confirmSelect(); +for (int i = 0; i < batchCount; ++i) { + channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes()); +} +if (!channel.waitForConfirms()) { + // 消息发送失败 + // ... +} +``` + +3.**异步 confirm 模式**:提供一个回调方法,服务端 confirm 了一条或者多条消息后客户端会回调这个方法。 + +```java +SortedSet confirmSet = Collections.synchronizedSortedSet(new TreeSet()); +channel.confirmSelect(); +channel.addConfirmListener(new ConfirmListener() { + public void handleAck(long deliveryTag, boolean multiple) throws IOException { + if (multiple) { + confirmSet.headSet(deliveryTag + 1).clear(); + } else { + confirmSet.remove(deliveryTag); + } + } + + public void handleNack(long deliveryTag, boolean multiple) throws IOException { + System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple); + if (multiple) { + confirmSet.headSet(deliveryTag + 1).clear(); + } else { + confirmSet.remove(deliveryTag); + } + } +}); + +while (true) { + long nextSeqNo = channel.getNextPublishSeqNo(); + channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes()); + confirmSet.add(nextSeqNo); +} +``` + #### RabbitMQ 弄丢了数据 就是 RabbitMQ 自己弄丢了数据,这个你必须**开启 RabbitMQ 的持久化**,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,**恢复之后会自动读取之前存储的数据**,一般数据不会丢。除非极其罕见的是,RabbitMQ 还没持久化,自己就挂了,**可能导致少量数据丢失**,但是这个概率较小。 设置持久化有**两个步骤**: -* 创建 queue 的时候将其设置为持久化
- -这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。 +- 创建 queue 的时候将其设置为持久化。这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。 -* 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2
- -就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。 +- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2。就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。 必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。 @@ -70,11 +135,14 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费 这个时候得用 RabbitMQ 提供的 `ack` 机制,简单来说,就是你必须关闭 RabbitMQ 的自动 `ack` ,可以通过一个 api 来调用就行,然后每次你自己代码里确保处理完的时候,再在程序里 `ack` 一把。这样的话,如果你还没处理完,不就没有 `ack` 了?那 RabbitMQ 就认为你还没处理完,这个时候 RabbitMQ 会把这个消费分配给别的 consumer 去处理,消息是不会丢的。 +> 为了保证消息从队列中可靠地到达消费者,RabbitMQ 提供了消息确认机制。消费者在声明队列时,可以指定 noAck 参数,当 noAck=false,RabbitMQ 会等待消费者显式发回 ack 信号后,才从内存(和磁盘,如果是持久化消息)中移去消息。否则,一旦消息被消费者消费,RabbitMQ 会在队列中立即删除它。 + ![rabbitmq-message-lose-solution](./images/rabbitmq-message-lose-solution.png) ### Kafka #### 消费端弄丢了数据 + 唯一可能导致消费者弄丢数据的情况,就是说,你消费到了这个消息,然后消费者那边**自动提交了 offset**,让 Kafka 以为你已经消费好了这个消息,但其实你才刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。 这不是跟 RabbitMQ 差不多吗,大家都知道 Kafka 会自动提交 offset,那么只要**关闭自动提交** offset,在处理完之后自己手动提交 offset,就可以保证数据不会丢。但是此时确实还是**可能会有重复消费**,比如你刚处理完,还没提交 offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。 @@ -89,13 +157,54 @@ RabbitMQ 如果丢失了数据,主要是因为你消费的时候,**刚消费 所以此时一般是要求起码设置如下 4 个参数: -* 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。 -* 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。 -* 在 producer 端设置 `acks=all` :这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。 -* 在 producer 端设置 `retries=MAX` (很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。 +- 给 topic 设置 `replication.factor` 参数:这个值必须大于 1,要求每个 partition 必须有至少 2 个副本。 +- 在 Kafka 服务端设置 `min.insync.replicas` 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,没掉队,这样才能确保 leader 挂了还有一个 follower 吧。 +- 在 producer 端设置 `acks=all` :这个是要求每条数据,必须是**写入所有 replica 之后,才能认为是写成功了**。 +- 在 producer 端设置 `retries=MAX` (很大很大很大的一个值,无限次重试的意思):这个是**要求一旦写入失败,就无限重试**,卡在这里了。 我们生产环境就是按照上述要求配置的,这样配置之后,至少在 Kafka broker 端就可以保证在 leader 所在 broker 发生故障,进行 leader 切换时,数据不会丢失。 #### 生产者会不会弄丢数据? 如果按照上述的思路设置了 `acks=all` ,一定不会丢,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之后,才认为本次写成功了。如果没满足这个条件,生产者会自动不断的重试,重试无限次。 + +### RocketMQ + +#### 消息丢失的场景 + +1. 生产者发送消息到 MQ 有可能丢失消息 +2. MQ 收到消息后写入硬盘可能丢失消息 +3. 消息写入硬盘后,硬盘坏了丢失消息 +4. 消费者消费 MQ 也可能丢失消息 +5. 整个 MQ 节点挂了丢失消息 + +#### 生产者发送消息时如何保证不丢失? + +解决发送时消息丢失的问题可以采用 RocketMQ 自带的**事务消息**机制 + +事务消息原理:首先生产者会发送一个**half 消息**(对原始消息的封装),该消息对消费者不可见,MQ 通过 ACK 机制返回消息接受状态, 生产者执行本地事务并且返回给 MQ 一个状态(Commit、RollBack 等),如果是 Commit 的话 MQ 就会把消息给到下游, RollBack 的话就会丢弃该消息,状态如果为 UnKnow 的话会过一段时间回查本地事务状态,默认回查 15 次,一直是 UnKnow 状态的话就会丢弃此消息。 + +为什么先发一个 half 消息,作用就是先判断下 MQ 有没有问题,服务正不正常。 + +#### MQ 收到消息后写入硬盘如何保证不丢失? + +数据存盘绕过缓存,改为同步刷盘,这一步需要修改 Broker 的配置文件,将 flushDiskType 改为 SYNC_FLUSH 同步刷盘策略,默认的是 ASYNC_FLUSH 异步刷盘,一旦同步刷盘返回成功,那么就一定保证消息已经持久化到磁盘中了。 + +#### 消息写入硬盘后,硬盘坏了如何保证不丢失? + +为了保证磁盘损坏导致丢失数据,RocketMQ 采用主从机构,集群部署,Leader 中的数据在多个 Follower 中都存有备份,防止单点故障导致数据丢失。 + +Master 节点挂了怎么办?Master 节点挂了之后 DLedger 登场 + +- 接管 MQ 的 commitLog +- 选举从节点 +- 文件复制 uncommited 状态 多半从节点收到之后改为 commited + +#### 消费者消费 MQ 如何保证不丢失? + +1. 如果是网络问题导致的消费失败可以进行重试机制,默认每条消息重试 16 次 +2. 多线程异步消费失败,MQ 认为已经消费成功但是实际上对于业务逻辑来说消息是没有落地的,解决方案就是按照 mq 官方推荐的先执行本地事务再返回成功状态。 + +#### 整个 MQ 节点挂了如何保证不丢失? + +这种极端情况可以消息发送失败之后先存入本地,例如放到缓存中,另外启动一个线程扫描缓存的消息去重试发送。 diff --git a/docs/high-concurrency/huifer-how-to-limit-current.md b/docs/high-concurrency/how-to-limit-current.md similarity index 61% rename from docs/high-concurrency/huifer-how-to-limit-current.md rename to docs/high-concurrency/how-to-limit-current.md index cffc1b0de..d3616aea1 100644 --- a/docs/high-concurrency/huifer-how-to-limit-current.md +++ b/docs/high-concurrency/how-to-limit-current.md @@ -1,8 +1,5 @@ # 如何限流?在工作中是怎么做的?说一下具体的实现? -* Author: [HuiFer](https://github.com/huifer) -* Description: 该文简单介绍限流相关技术以及实现 - ## 什么是限流 > 限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。 @@ -13,9 +10,9 @@ #### 实现方式 -* 控制单位时间内的请求数量 +控制单位时间内的请求数量。 -``` java +```java import java.util.concurrent.atomic.AtomicInteger; @@ -54,18 +51,19 @@ public class Counter { ``` -* 劣势 - - 假设在 00:01 时发生一个请求,在 00:01-00:58 之间不在发送请求,在 00:59 时发送剩下的所有请求 `n-1` (n为限流请求数量),在下一分钟的 00:01 发送n个请求,这样在2秒钟内请求到达了 `2n - 1` 个. - - 设每分钟请求数量为60个,每秒可以处理1个请求,用户在 00:59 发送 60 个请求,在 01:00 发送 60 个请求 此时2秒钟有120个请求(每秒60个请求),远远大于了每秒钟处理数量的阈值 +劣势: + +假设在 00:01 时发生一个请求,在 00:01-00:58 之间不在发送请求,在 00:59 时发送剩下的所有请求 `n-1` (n 为限流请求数量),在下一分钟的 00:01 发送 n 个请求,这样在 2 秒钟内请求到达了 `2n - 1` 个。 + +设每分钟请求数量为 60 个,每秒可以处理 1 个请求,用户在 00:59 发送 60 个请求,在 01:00 发送 60 个请求 此时 2 秒钟有 120 个请求(每秒 60 个请求),远远大于了每秒钟处理数量的阈值。 ### 滑动窗口 #### 实现方式 -* 滑动窗口是对计数器方式的改进, 增加一个时间粒度的度量单位 - - 把一分钟分成若干等分(6份,每份10秒), 在每一份上设置独立计数器,在 00:00-00:09 之间发生请求计数器累加1.当等分数量越大限流统计就越详细 +滑动窗口是对计数器方式的改进,增加一个时间粒度的度量单位,把一分钟分成若干等分(6 份,每份 10 秒),在每一份上设置独立计数器,在 00:00-00:09 之间发生请求计数器累加 1。当等分数量越大限流统计就越详细。 -``` java +```java package com.example.demo1.service; import java.util.Iterator; @@ -86,7 +84,7 @@ public class TimeWindow { */ private int max; - public TimeWindow(int max, int seconds) { + public TimeWindow(int max, int seconds) { this.seconds = seconds; this.max = max; @@ -109,10 +107,10 @@ public class TimeWindow { public static void main(String[] args) throws Exception { - final TimeWindow timeWindow = new TimeWindow(10, 1); + final TimeWindow timeWindow = new TimeWindow(10, 1); // 测试3个线程 - IntStream.range(0, 3).forEach((i) -> { + IntStream.range(0, 3).forEach((i) -> { new Thread(() -> { while (true) { @@ -193,9 +191,9 @@ public class TimeWindow { #### 实现方式 -* 规定固定容量的桶, 有水进入, 有水流出. 对于流进的水我们无法估计进来的数量、速度, 对于流出的水我们可以控制速度. +规定固定容量的桶,有水进入,有水流出。对于流进的水我们无法估计进来的数量、速度,对于流出的水我们可以控制速度。 -``` java +```java public class LeakBucket { /** * 时间 @@ -216,7 +214,7 @@ public class LeakBucket { public boolean limit() { long now = System.currentTimeMillis(); - nowSize = Math.max(0, (nowSize - (now - time) * rate)); + nowSize = Math.max(0, (nowSize - (now - time) * rate)); time = now; if ((nowSize + 1) < total) { nowSize++; @@ -233,9 +231,9 @@ public class LeakBucket { #### 实现方式 -* 规定固定容量的桶, token 以固定速度往桶内填充, 当桶满时 token 不会被继续放入, 每过来一个请求把 token 从桶中移除, 如果桶中没有 token 不能请求 +规定固定容量的桶, token 以固定速度往桶内填充, 当桶满时 token 不会被继续放入, 每过来一个请求把 token 从桶中移除, 如果桶中没有 token 不能请求。 -``` java +```java public class TokenBucket { /** * 时间 @@ -256,7 +254,7 @@ public class TokenBucket { public boolean limit() { long now = System.currentTimeMillis(); - nowSize = Math.min(total, nowSize + (now - time) * rate); + nowSize = Math.min(total, nowSize + (now - time) * rate); time = now; if (nowSize < 1) { // 桶里没有token @@ -275,9 +273,9 @@ public class TokenBucket { ### spring cloud gateway -* spring cloud gateway 默认使用redis进行限流, 笔者一般只是修改修改参数属于拿来即用. 并没有去从头实现上述那些算法. +- spring cloud gateway 默认使用 redis 进行限流,笔者一般只是修改修改参数属于拿来即用,并没有去从头实现上述那些算法。 -``` xml +```xml org.springframework.cloud spring-cloud-starter-gateway @@ -288,34 +286,30 @@ public class TokenBucket { ``` -``` yaml +```yaml spring: - cloud: - gateway: - routes: - - - id: requestratelimiter_route - - uri: lb://pigx-upms - order: 10000 - predicates: - - - Path=/admin/** - - filters: + cloud: + gateway: + routes: + - id: requestratelimiter_route - - name: RequestRateLimiter + uri: lb://pigx-upms + order: 10000 + predicates: + - Path=/admin/** - args: - redis-rate-limiter.replenishRate: 1 # 令牌桶的容积 - redis-rate-limiter.burstCapacity: 3 # 流速 每秒 - key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean + filters: + - name: RequestRateLimiter - - StripPrefix=1 + args: + redis-rate-limiter.replenishRate: 1 # 令牌桶的容积 + redis-rate-limiter.burstCapacity: 3 # 流速 每秒 + key-resolver: '#{@remoteAddrKeyResolver}' #SPEL表达式去的对应的bean + - StripPrefix=1 ``` -``` java +```java @Bean KeyResolver remoteAddrKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); @@ -324,38 +318,38 @@ KeyResolver remoteAddrKeyResolver() { ### sentinel -* 通过配置来控制每个url的流量 +- 通过配置来控制每个 url 的流量 -``` xml +```xml com.alibaba.cloud spring-cloud-starter-alibaba-sentinel ``` -``` yaml +```yaml spring: - cloud: - nacos: - discovery: - server-addr: localhost:8848 - sentinel: - transport: - dashboard: localhost:8080 - port: 8720 - datasource: - ds: - nacos: - server-addr: localhost:8848 - dataId: spring-cloud-sentinel-nacos - groupId: DEFAULT_GROUP - rule-type: flow - namespace: xxxxxxxx + cloud: + nacos: + discovery: + server-addr: localhost:8848 + sentinel: + transport: + dashboard: localhost:8080 + port: 8720 + datasource: + ds: + nacos: + server-addr: localhost:8848 + dataId: spring-cloud-sentinel-nacos + groupId: DEFAULT_GROUP + rule-type: flow + namespace: xxxxxxxx ``` -* 配置内容在nacos上进行编辑 +- 配置内容在 nacos 上进行编辑 -``` json +```json [ { "resource": "/hello", @@ -369,14 +363,14 @@ spring: ] ``` -* resource:资源名,即限流规则的作用对象。 -* limitApp:流控针对的调用来源,若为 default 则不区分调用来源。 -* grade:限流阈值类型,QPS 或线程数模式,0代表根据并发数量来限流,1代表根据QPS来进行流量控制。 -* count:限流阈值 -* strategy:判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口 -* controlBehavior:流控效果(直接拒绝 / 排队等待 / 慢启动模式) -* clusterMode:是否为集群模式 +- resource:资源名,即限流规则的作用对象。 +- limitApp:流控针对的调用来源,若为 default 则不区分调用来源。 +- grade:限流阈值类型,QPS 或线程数模式,0 代表根据并发数量来限流,1 代表根据 QPS 来进行流量控制。 +- count:限流阈值 +- strategy:判断的根据是资源自身,还是根据其它关联资源 (refResource),还是根据链路入口 +- controlBehavior:流控效果(直接拒绝 / 排队等待 / 慢启动模式) +- clusterMode:是否为集群模式 ### 总结 -> sentinel和spring cloud gateway两个框架都是很好的限流框架, 但是在我使用中还没有将[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)接入到项目中进行使用, 所以我会选择**spring cloud gateway**, 当接入完整的或者接入Nacos项目使用setinel会有更加好的体验. +> sentinel 和 spring cloud gateway 两个框架都是很好的限流框架, 但是在我使用中还没有将[spring-cloud-alibaba](https://github.com/alibaba/spring-cloud-alibaba)接入到项目中进行使用, 所以我会选择**spring cloud gateway**, 当接入完整的或者接入 Nacos 项目使用 setinel 会有更加好的体验. diff --git a/docs/high-concurrency/images/favicon-16x16.png b/docs/high-concurrency/images/favicon-16x16.png deleted file mode 100644 index f0ee715c7..000000000 Binary files a/docs/high-concurrency/images/favicon-16x16.png and /dev/null differ diff --git a/docs/high-concurrency/images/favicon-32x32.png b/docs/high-concurrency/images/favicon-32x32.png deleted file mode 100644 index c90d7f588..000000000 Binary files a/docs/high-concurrency/images/favicon-32x32.png and /dev/null differ diff --git a/docs/high-concurrency/images/icon.png b/docs/high-concurrency/images/icon.png deleted file mode 100644 index fdcfeee2d..000000000 Binary files a/docs/high-concurrency/images/icon.png and /dev/null differ diff --git a/docs/high-concurrency/images/lru-cache.png b/docs/high-concurrency/images/lru-cache.png new file mode 100644 index 000000000..bf54c10b8 Binary files /dev/null and b/docs/high-concurrency/images/lru-cache.png differ diff --git a/docs/high-concurrency/images/lru.png b/docs/high-concurrency/images/lru.png new file mode 100644 index 000000000..bc2f084c0 Binary files /dev/null and b/docs/high-concurrency/images/lru.png differ diff --git a/docs/high-concurrency/images/qrcode-for-doocs.jpg b/docs/high-concurrency/images/qrcode-for-doocs.jpg deleted file mode 100644 index bd1db5d11..000000000 Binary files a/docs/high-concurrency/images/qrcode-for-doocs.jpg and /dev/null differ diff --git a/docs/high-concurrency/images/qrcode-for-yanglbme.jpg b/docs/high-concurrency/images/qrcode-for-yanglbme.jpg deleted file mode 100644 index 5bd385bca..000000000 Binary files a/docs/high-concurrency/images/qrcode-for-yanglbme.jpg and /dev/null differ diff --git a/docs/high-concurrency/images/redis-caching-avoid-penetration.png b/docs/high-concurrency/images/redis-caching-avoid-penetration.png new file mode 100644 index 000000000..b978620aa Binary files /dev/null and b/docs/high-concurrency/images/redis-caching-avoid-penetration.png differ diff --git a/docs/high-concurrency/mq-design.md b/docs/high-concurrency/mq-design.md index dbb746746..c81f85eb1 100644 --- a/docs/high-concurrency/mq-design.md +++ b/docs/high-concurrency/mq-design.md @@ -1,12 +1,15 @@ +# 如何设计一个消息队列? + ## 面试题 + 如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。 ## 面试官心理分析 其实聊到这个问题,一般面试官要考察两块: -* 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。 -* 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。 +- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。 +- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。 说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,**大多数人就是平时埋头用,从来不去思考背后的一些东西**。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做? @@ -16,12 +19,12 @@ 比如说这个消息队列系统,我们从以下几个角度来考虑一下: -* 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了? +- 首先这个 mq 得支持可伸缩性吧,就是需要的时候快速扩容,就可以增加吞吐量和容量,那怎么搞?设计个分布式的系统呗,参照一下 kafka 的设计理念,broker -> topic -> partition,每个 partition 放一个机器,就存一部分数据。如果现在资源不够了,简单啊,给 topic 增加 partition,然后做数据迁移,增加机器,不就可以存放更多数据,提供更高的吞吐量了? -* 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。 +- 其次你得考虑一下这个 mq 的数据要不要落地磁盘吧?那肯定要了,落磁盘才能保证别进程挂了数据就丢了。那落磁盘的时候怎么落啊?顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这就是 kafka 的思路。 -* 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。 +- 其次你考虑一下你的 mq 的可用性啊?这个事儿,具体参考之前可用性那个环节讲解的 kafka 的高可用保障机制。多副本 -> leader & follower -> broker 挂了重新选举 leader 即可对外服务。 -* 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。 +- 能不能支持数据 0 丢失啊?可以的,参考我们之前说的那个 kafka 数据零丢失方案。 mq 肯定是很复杂的,面试官问你这个问题,其实是个开放题,他就是看看你有没有从架构角度整体构思和设计的思维以及能力。确实这个问题可以刷掉一大批人,因为大部分人平时不思考这些东西。 diff --git a/docs/high-concurrency/mq-interview.md b/docs/high-concurrency/mq-interview.md index 25abde680..e89b2aa60 100644 --- a/docs/high-concurrency/mq-interview.md +++ b/docs/high-concurrency/mq-interview.md @@ -1,4 +1,4 @@ -## 消息队列面试场景 +# 消息队列面试场景 **面试官**:你好。 diff --git a/docs/high-concurrency/mq-time-delay-and-expired-failure.md b/docs/high-concurrency/mq-time-delay-and-expired-failure.md index 7561864d2..4a0ad9887 100644 --- a/docs/high-concurrency/mq-time-delay-and-expired-failure.md +++ b/docs/high-concurrency/mq-time-delay-and-expired-failure.md @@ -1,4 +1,7 @@ +# 如何解决消息队列的延时以及过期失效问题? + ## 面试题 + 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决? ## 面试官心理分析 @@ -19,20 +22,68 @@ 一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下: -* 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。 -* 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。 -* 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。 -* 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。 -* 等快速消费完积压数据之后,**得恢复原先部署的架构**,**重新**用原先的 consumer 机器来消费消息。 +- 先修复 consumer 的问题,确保其恢复消费速度,然后将现有 consumer 都停掉。 +- 新建一个 topic,partition 是原来的 10 倍,临时建立好原先 10 倍的 queue 数量。 +- 然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,**消费之后不做耗时的处理**,直接均匀轮询写入临时建立好的 10 倍数量的 queue。 +- 接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据。这种做法相当于是临时将 queue 资源和 consumer 资源扩大 10 倍,以正常的 10 倍速度来消费数据。 +- 等快速消费完积压数据之后,**得恢复原先部署的架构**,**重新**用原先的 consumer 机器来消费消息。 ### mq 中的消息过期失效了 假设你用的是 RabbitMQ,RabbtiMQ 是可以设置过期时间的,也就是 TTL。如果消息在 queue 中积压超过一定的时间就会被 RabbitMQ 给清理掉,这个数据就没了。那这就是第二个坑了。这就不是说数据会大量积压在 mq 里,而是**大量的数据会直接搞丢**。 -这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是**批量重导**,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上12点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。 +这个情况下,就不是说要增加 consumer 消费积压的消息,因为实际上没啥积压,而是丢了大量的消息。我们可以采取一个方案,就是**批量重导**,这个我们之前线上也有类似的场景干过。就是大量积压的时候,我们当时就直接丢弃数据了,然后等过了高峰期以后,比如大家一起喝咖啡熬夜到晚上 12 点以后,用户都睡觉了。这个时候我们就开始写程序,将丢失的那批数据,写个临时程序,一点一点的查出来,然后重新灌入 mq 里面去,把白天丢的数据给他补回来。也只能是这样了。 假设 1 万个订单积压在 mq 里面,没有处理,其中 1000 个订单都丢了,你只能手动写程序把那 1000 个订单给查出来,手动发到 mq 里去再补一次。 ### mq 都快写满了 如果消息积压在 mq 里,你很长时间都没有处理掉,此时导致 mq 都快写满了,咋办?这个还有别的办法吗?没有,谁让你第一个方案执行的太慢了,你临时写程序,接入数据来消费,**消费一个丢弃一个,都不要了**,快速消费掉所有的消息。然后走第二个方案,到了晚上再补数据吧。 + +--- + +对于 RocketMQ,官方针对消息积压问题,提供了解决方案。 + +### 1. 提高消费并行度 + +绝大部分消息消费行为都属于 IO 密集型,即可能是操作数据库,或者调用 RPC,这类消费行为的消费速度在于后端数据库或者外系统的吞吐量,通过增加消费并行度,可以提高总的消费吞吐量,但是并行度增加到一定程度,反而会下降。所以,应用必须要设置合理的并行度。 如下有几种修改消费并行度的方法: + +同一个 ConsumerGroup 下,通过增加 Consumer 实例数量来提高并行度(需要注意的是超过订阅队列数的 Consumer 实例无效)。可以通过加机器,或者在已有机器启动多个进程的方式。 +提高单个 Consumer 的消费并行线程,通过修改参数 consumeThreadMin、consumeThreadMax 实现。 + +### 2. 批量方式消费 + +某些业务流程如果支持批量方式消费,则可以很大程度上提高消费吞吐量,例如订单扣款类应用,一次处理一个订单耗时 1 s,一次处理 10 个订单可能也只耗时 2 s,这样即可大幅度提高消费的吞吐量,通过设置 consumer 的 consumeMessageBatchMaxSize 返个参数,默认是 1,即一次只消费一条消息,例如设置为 N,那么每次消费的消息数小于等于 N。 + +### 3. 跳过非重要消息 + +发生消息堆积时,如果消费速度一直追不上发送速度,如果业务对数据要求不高的话,可以选择丢弃不重要的消息。例如,当某个队列的消息数堆积到 100000 条以上,则尝试丢弃部分或全部消息,这样就可以快速追上发送消息的速度。示例代码如下: + +```java +public ConsumeConcurrentlyStatus consumeMessage( + List msgs, + ConsumeConcurrentlyContext context) { + long offset = msgs.get(0).getQueueOffset(); + String maxOffset = + msgs.get(0).getProperty(Message.PROPERTY_MAX_OFFSET); + long diff = Long.parseLong(maxOffset) - offset; + if (diff > 100000) { + // TODO 消息堆积情况的特殊处理 + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + // TODO 正常消费过程 + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; +} +``` + +### 4. 优化每条消息消费过程 + +举例如下,某条消息的消费过程如下: + +- 根据消息从 DB 查询【数据 1】 +- 根据消息从 DB 查询【数据 2】 +- 复杂的业务计算 +- 向 DB 插入【数据 3】 +- 向 DB 插入【数据 4】 + +这条消息的消费过程中有 4 次与 DB 的 交互,如果按照每次 5ms 计算,那么总共耗时 20ms,假设业务计算耗时 5ms,那么总过耗时 25ms,所以如果能把 4 次 DB 交互优化为 2 次,那么总耗时就可以优化到 15ms,即总体性能提高了 40%。所以应用如果对时延敏感的话,可以把 DB 部署在 SSD 硬盘,相比于 SCSI 磁盘,前者的 RT 会小很多。 diff --git a/docs/high-concurrency/mysql-read-write-separation.md b/docs/high-concurrency/mysql-read-write-separation.md index a1358bdcb..b35b083dd 100644 --- a/docs/high-concurrency/mysql-read-write-separation.md +++ b/docs/high-concurrency/mysql-read-write-separation.md @@ -1,4 +1,7 @@ +# 如何实现读写分离? + ## 面试题 + 你们有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决 MySQL 主从同步的延时问题? ## 面试官心理分析 @@ -8,6 +11,7 @@ ## 面试题剖析 ### 如何实现 MySQL 的读写分离? + 其实很简单,就是基于主从复制架构,简单来说,就搞一个主库,挂多个从库,然后我们就单单只是写主库,然后主库会自动把数据给同步到从库上去。 ### MySQL 主从复制原理的是啥? @@ -34,15 +38,15 @@ 我们通过 MySQL 命令: -``` sql -show status +```sql +show slave status ``` 查看 `Seconds_Behind_Master` ,可以看到从库复制主库的数据落后了几 ms。 一般来说,如果主从延迟较为严重,有以下解决方案: -* 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。 -* 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。 -* 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。 -* 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。 +- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。 +- 打开 MySQL 支持的并行复制,多个库并行复制。如果说某个库的写入并发就是特别高,单库写并发达到了 2000/s,并行复制还是没意义。 +- 重写代码,写代码的同学,要慎重,插入数据时立马查询可能查不到。 +- 如果确实是存在必须先插入,立马要求就查询到,然后立马就要反过来执行一些操作,对这个查询**设置直连主库**。**不推荐**这种方法,你要是这么搞,读写分离的意义就丧失了。 diff --git a/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md b/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md index 30c6a1f4d..05be3b92a 100644 --- a/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md +++ b/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md @@ -1,4 +1,7 @@ +# 缓存雪崩、穿透和击穿 + ## 面试题 + 了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透? ## 面试官心理分析 @@ -7,7 +10,8 @@ ## 面试题剖析 -### 缓存雪崩 +### 缓存雪崩(Cache Avalanche) + 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 这就是缓存雪崩。 @@ -18,9 +22,9 @@ 缓存雪崩的事前事中事后的解决方案如下: -* 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。 -* 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 -* 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 +- 事前:Redis 高可用,主从+哨兵,Redis cluster,避免全盘崩溃。 +- 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。 +- 事后:Redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。 ![redis-caching-avalanche-solution](./images/redis-caching-avalanche-solution.png) @@ -30,13 +34,13 @@ 好处: -* 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 -* 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 -* 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。 +- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 +- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 +- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。 -### 缓存穿透 +### 缓存穿透(Cache Penetration) -对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。 +对于系统 A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。 黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。 @@ -46,12 +50,21 @@ 解决方式很简单,每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 `set -999 UNKNOWN` 。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 -### 缓存击穿 +当然,如果黑客如果每次使用不同的负数 id 来攻击,写空值的方法可能就不奏效了。更为经常的做法是在缓存之前增加布隆过滤器,将数据库中所有可能的数据哈希映射到布隆过滤器中。然后对每个请求进行如下判断: + +- 请求数据的 key 不存在于布隆过滤器中,可以确定数据就一定不会存在于数据库中,系统可以立即返回不存在。 +- 请求数据的 key 存在于布隆过滤器中,则继续再向缓存中查询。 + +使用布隆过滤器能够对访问的请求起到了一定的初筛作用,避免了因数据不存在引起的查询压力。 + +![redis-caching-avoid-penetration](./images/redis-caching-avoid-penetration.png) + +### 缓存击穿(Hotspot Invalid) 缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。 不同场景下的解决方式可如下: -* 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。 -* 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。 -* 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。 +- 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。 +- 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 Redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。 +- 若缓存的数据更新频繁或者在缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动地重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。 diff --git a/docs/high-concurrency/redis-cas.md b/docs/high-concurrency/redis-cas.md index 2d96ab772..c1852bf8e 100644 --- a/docs/high-concurrency/redis-cas.md +++ b/docs/high-concurrency/redis-cas.md @@ -1,4 +1,7 @@ +# Redis 的并发竞争问题 + ## 面试题 + Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗? ## 面试官心理分析 diff --git a/docs/high-concurrency/redis-cluster.md b/docs/high-concurrency/redis-cluster.md index 54403a51a..69c3d4978 100644 --- a/docs/high-concurrency/redis-cluster.md +++ b/docs/high-concurrency/redis-cluster.md @@ -1,4 +1,7 @@ +# Redis 集群模式原理 + ## 面试题 + Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗? ## 面试官心理分析 @@ -17,19 +20,20 @@ Redis cluster,主要是针对**海量数据+高并发+高可用**的场景。R ### Redis cluster 介绍 -* 自动将数据进行分片,每个 master 上放一部分数据 -* 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 +- 自动将数据进行分片,每个 master 上放一部分数据 +- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 -在 Redis cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。 +在 Redis cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是 加 1w 的端口号,比如 16379。 16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议, `gossip` 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。 ### 节点间的内部通信机制 #### 基本通信原理 + 集群元数据的维护有两种方式:集中式、Gossip 协议。Redis cluster 节点间采用 gossip 协议进行通信。 -**集中式**是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm` 。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 +**集中式**是将集群元数据(节点信息、故障等等)集中存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的 `storm` 。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于 zookeeper(分布式协调的中间件)对所有元数据进行存储维护。 ![zookeeper-centralized-storage](./images/zookeeper-centralized-storage.png) @@ -41,25 +45,25 @@ Redis 维护集群元数据采用另一个方式, `gossip` 协议,所有节 gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。 -* 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong` 。 +- 10000 端口:每个节点都有一个专门用于节点间通信的端口,就是自己提供服务的端口号+10000,比如 7001,那么用于节点间通信的就是 17001 端口。每个节点每隔一段时间都会往另外几个节点发送 `ping` 消息,同时其它几个节点接收到 `ping` 之后返回 `pong` 。 -* 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。 +- 交换的信息:信息包括故障信息,节点的增加和删除,hash slot 信息等等。 #### gossip 协议 gossip 协议包含多种消息,包含 `ping` , `pong` , `meet` , `fail` 等等。 -* meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。 +- meet:某个节点发送 meet 给新加入的节点,让新节点加入集群中,然后新节点就会开始与其它节点进行通信。 -``` bash +```bash Redis-trib.rb add-node ``` 其实内部就是发送了一个 gossip meet 消息给新加入的节点,通知那个节点去加入我们的集群。 -* ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。 -* pong:返回 ping 和 meeet,包含自己的状态和其它信息,也用于信息广播和更新。 -* fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。 +- ping:每个节点都会频繁给其它节点发送 ping,其中包含自己的状态还有自己维护的集群元数据,互相通过 ping 交换元数据。 +- pong:返回 ping 和 meet,包含自己的状态和其它信息,也用于信息广播和更新。 +- fail:某个节点判断另一个节点 fail 之后,就发送 fail 给其它节点,通知其它节点说,某个节点宕机啦。 #### ping 消息深入 @@ -71,9 +75,9 @@ ping 时要携带一些元数据,如果很频繁,可能会加重网络负担 ### 分布式寻址算法 -* hash 算法(大量缓存重建) -* 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡) -* Redis cluster 的 hash slot 算法 +- hash 算法(大量缓存重建) +- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡) +- Redis cluster 的 hash slot 算法 #### hash 算法 diff --git a/docs/high-concurrency/redis-consistence.md b/docs/high-concurrency/redis-consistence.md index 31f2f411e..150ba6704 100644 --- a/docs/high-concurrency/redis-consistence.md +++ b/docs/high-concurrency/redis-consistence.md @@ -1,4 +1,7 @@ +# 缓存与数据库一致性问题 + ## 面试题 + 如何保证缓存与数据库的双写一致性? ## 面试官心理分析 @@ -15,8 +18,8 @@ 最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。 -* 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 -* 更新的时候,**先更新数据库,然后再删除缓存**。 +- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 +- 更新的时候,**先更新数据库,然后再删除缓存**。 **为什么是删除缓存,而不是更新缓存?** @@ -36,7 +39,21 @@ ![redis-junior-inconsistent](./images/redis-junior-inconsistent.png) -解决思路:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。 +解决思路 1:先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。 + +解决思路 2:延时双删。依旧是先更新数据库,再删除缓存,唯一不同的是,我们把这个删除的动作,在不久之后再执行一次,比如 5s 之后。 + +```java +public void set(key, value) { + putToDb(key, value); + deleteFromRedis(key); + + // ... a few seconds later + deleteFromRedis(key); +} +``` + +删除的动作,可以有多种选择,比如:1. 使用 `DelayQueue`,会随着 JVM 进程的死亡,丢失更新的风险;2. 放在 `MQ`,但编码复杂度为增加。总之,我们需要综合各种因素去做设计,选择一个最合理的解决方案。 ### 比较复杂的数据不一致问题分析 @@ -60,13 +77,13 @@ 高并发的场景下,该解决方案要注意的问题: -* 读请求长时阻塞 +- 读请求长时阻塞 由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。 该解决方案,最大的风险点在于说,**可能数据更新很频繁**,导致队列中积压了大量更新操作在里面,然后**读请求会发生大量的超时**,最后导致大量的请求直接走数据库。务必通过一些模拟真实的测试,看看更新数据的频率是怎样的。 -另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 * 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。 +另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要**部署多个服务**,每个服务分摊一些数据的更新操作。如果一个内存队列里居然会挤压 100 个商品的库存修改操作,每个库存修改操作要耗费 10ms 去完成,那么最后一个商品的读请求,可能等待 10 \* 100 = 1000ms = 1s 后,才能得到数据,这个时候就导致**读请求的长时阻塞**。 一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会 hang 多少时间,如果读请求在 200ms 返回,如果你计算过后,哪怕是最繁忙的时候,积压 10 个更新操作,最多等待 200ms,那还可以的。 @@ -80,19 +97,19 @@ 经过刚才简单的测算,我们知道,单机支撑的写 QPS 在几百是没问题的,如果写 QPS 扩大了 10 倍,那么就扩容机器,扩容 10 倍的机器,每个机器 20 个队列。 -* 读请求并发量过高 +- 读请求并发量过高 这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时 hang 在服务上,看服务能不能扛的住,需要多少机器才能扛住最大的极限情况的峰值。 但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大。 -* 多服务实例部署的请求路由 +- 多服务实例部署的请求路由 可能这个服务部署了多个实例,那么必须**保证**说,执行数据更新操作,以及执行缓存更新操作的请求,都通过 Nginx 服务器**路由到相同的服务实例上**。 比如说,对同一个商品的读写请求,全部路由到同一台机器上。可以自己去做服务间的按照某个请求参数的 hash 路由,也可以用 Nginx 的 hash 路由功能等等。 -* 热点商品的路由问题,导致请求的倾斜 +- 热点商品的路由问题,导致请求的倾斜 万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能会造成某台机器的压力过大。就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以其实要根据业务系统去看,如果更新频率不是太高的话,这个问题的影响并不是特别大,但是的确可能某些机器的负载会高一些。 diff --git a/docs/high-concurrency/redis-data-types.md b/docs/high-concurrency/redis-data-types.md index abf57e582..1681ea2af 100644 --- a/docs/high-concurrency/redis-data-types.md +++ b/docs/high-concurrency/redis-data-types.md @@ -1,4 +1,7 @@ +# Redis 数据类型和使用场景 + ## 面试题 + Redis 都有哪些数据类型?分别在哪些场景下使用比较合适? ## 面试官心理分析 @@ -7,8 +10,8 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适? 其实问这个问题,主要有两个原因: -* 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作; -* 看看你在实际项目里都怎么玩儿过 Redis。 +- 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作; +- 看看你在实际项目里都怎么玩儿过 Redis。 要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。 @@ -16,11 +19,11 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适? Redis 主要有以下几种数据类型: -* Strings -* Hashes -* Lists -* Sets -* Sorted Sets +- Strings +- Hashes +- Lists +- Sets +- Sorted Sets > Redis 除了这 5 种数据类型之外,还有 Bitmaps、HyperLogLogs、Streams 等。 @@ -28,7 +31,7 @@ Redis 主要有以下几种数据类型: 这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。 -``` bash +```bash set college szu ``` @@ -36,19 +39,19 @@ set college szu 这个是类似 map 的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是**这个对象没嵌套其他的对象**)给缓存在 Redis 里,然后每次读写缓存的时候,可以就操作 hash 里的**某个字段**。 -``` bash +```bash hset person name bingo hset person age 20 hset person id 1 hget person name ``` -``` json -person = { - "name": "bingo", - "age": 20, - "id": 1 -} +```json +(person = { + "name": "bingo", + "age": 20, + "id": 1 +}) ``` ### Lists @@ -59,14 +62,14 @@ Lists 是有序列表,这个可以玩儿出很多花样。 比如可以通过 lrange 命令,读取某个闭区间内的元素,可以基于 list 实现分页查询,这个是很棒的一个功能,基于 Redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。 -``` bash +```bash # 0开始位置,-1结束位置,结束位置为-1时,表示列表的最后一个位置,即查看所有。 lrange mylist 0 -1 ``` 比如可以搞个简单的消息队列,从 list 头怼进去,从 list 尾巴那里弄出来。 -``` bash +```bash lpush mylist 1 lpush mylist 2 lpush mylist 3 4 5 @@ -85,7 +88,7 @@ Sets 是无序集合,自动去重。 把两个大 V 的粉丝都放在两个 set 中,对两个 set 做交集。 -``` bash +```bash #-------操作一个set------- # 添加元素 sadd mySet 1 @@ -124,7 +127,7 @@ sdiff yourSet mySet Sorted Sets 是排序的 set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。 -``` bash +```bash zadd board 85 zhangsan zadd board 72 lisi zadd board 96 wangwu diff --git a/docs/high-concurrency/redis-expiration-policies-and-lru.md b/docs/high-concurrency/redis-expiration-policies-and-lru.md index 7fccd942d..54da3fe22 100644 --- a/docs/high-concurrency/redis-expiration-policies-and-lru.md +++ b/docs/high-concurrency/redis-expiration-policies-and-lru.md @@ -1,4 +1,7 @@ +# Redis 的过期策略和 LRU 算法 + ## 面试题 + Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? ## 面试官心理分析 @@ -7,7 +10,7 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一 常见的有两个问题: -* 往 Redis 写入的数据怎么没了? +- 往 Redis 写入的数据怎么没了? 可能有同学会遇到,在生产环境的 Redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 Redis 你就没用对啊。Redis 是缓存,你给当存储了是吧? @@ -15,13 +18,14 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一 那既然内存是有限的,比如 Redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。 -* 数据明明过期了,怎么还占用着内存? +- 数据明明过期了,怎么还占用着内存? 这是由 Redis 的过期策略来决定。 ## 面试题剖析 ### Redis 过期策略 + Redis 过期策略是:**定期删除+惰性删除**。 所谓**定期删除**,指的是 Redis 默认是每隔 100ms 就随机抽取一些设置了过期时间的 key,检查其是否过期,如果过期就删除。 @@ -40,42 +44,53 @@ Redis 过期策略是:**定期删除+惰性删除**。 Redis 内存淘汰机制有以下几个: -* noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。 -* **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。 -* allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。 -* volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。 -* volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。 -* volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。 +- noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。 +- **allkeys-lru**:当内存不足以容纳新写入数据时,在**键空间**中,移除最近最少使用的 key(这个是**最常用**的)。 +- allkeys-random:当内存不足以容纳新写入数据时,在**键空间**中,随机移除某个 key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。 +- volatile-lru:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,移除最近最少使用的 key(这个一般不太合适)。 +- volatile-random:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,**随机移除**某个 key。 +- volatile-ttl:当内存不足以容纳新写入数据时,在**设置了过期时间的键空间**中,有**更早过期时间**的 key 优先移除。 ### 手写一个 LRU 算法 +LRU 就是 Least Recently Used 的缩写,翻译过来就是“最近最少使用”。也就是说 LRU 算法会将最近最少用的缓存移除,让给最新使用的缓存。而往往最常读取的,也就是读取次数最多的,所以利用好 LRU 算法,我们能够提供对热点数据的缓存效率,能够提高缓存服务的内存使用率。 + +那么如何实现呢? + +其实,实现的思路非常简单,就像下面这张图种描述的一样。 + +![](./images/lru.png) + 你可以现场手写最原始的 LRU 算法,那个代码量太大了,似乎不太现实。 不求自己纯手工从底层开始打造出自己的 LRU,但是起码要知道如何利用已有的 JDK 数据结构实现一个 Java 版的 LRU。 -``` java -class LRUCache extends LinkedHashMap { - private final int CACHE_SIZE; +![](./images/lru-cache.png) + +```java +public class LRUCache extends LinkedHashMap { + private int capacity; /** * 传递进来最多能缓存多少数据 * - * @param cacheSize 缓存大小 + * @param capacity 缓存大小 */ - public LRUCache(int cacheSize) { - // true 表示让 linkedHashMap 按照访问顺序来进行排序,最近访问的放在头部,最老访问的放在尾部。 - super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); - CACHE_SIZE = cacheSize; + public LRUCache(int capacity) { + super(capacity, 0.75f, true); + this.capacity = capacity; } /** - * 钩子方法,通过put新增键值对的时候,若该方法返回true - * 便移除该map中最老的键和值 + * 如果map中的数据量大于设定的最大容量,返回true,再新加入对象时删除最老的数据 + * + * @param eldest 最老的数据项 + * @return true则移除最老的数据 */ @Override protected boolean removeEldestEntry(Map.Entry eldest) { - // 当 map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据。 - return size() > CACHE_SIZE; + // 当 map中的数据量大于指定的缓存个数的时候,自动移除最老的数据 + return size() > capacity; } } ``` diff --git a/docs/high-concurrency/redis-master-slave.md b/docs/high-concurrency/redis-master-slave.md index 47fe1b44f..bcb451e88 100644 --- a/docs/high-concurrency/redis-master-slave.md +++ b/docs/high-concurrency/redis-master-slave.md @@ -8,16 +8,16 @@ Redis replication -> 主从架构 -> 读写分离 -> 水平扩容支撑读高并 ## Redis replication 的核心机制 -* Redis 采用**异步方式**复制数据到 slave 节点,不过 Redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量; -* 一个 master node 是可以配置多个 slave node 的; -* slave node 也可以连接其他的 slave node; -* slave node 做复制的时候,不会 block master node 的正常工作; -* slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了; -* slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。 +- Redis 采用**异步方式**复制数据到 slave 节点,不过 Redis2.8 开始,slave node 会周期性地确认自己每次复制的数据量; +- 一个 master node 是可以配置多个 slave node 的; +- slave node 也可以连接其他的 slave node; +- slave node 做复制的时候,不会 block master node 的正常工作; +- slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了; +- slave node 主要用来进行横向扩容,做读写分离,扩容的 slave node 可以提高读的吞吐量。 -注意,如果采用了主从架构,那么建议必须**开启** master node 的[持久化](/docs/high-concurrency/redis-persistence.md),不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。 +注意,如果采用了主从架构,那么建议必须**开启** master node 的[持久化](./redis-persistence.md),不建议用 slave node 作为 master node 的数据热备,因为那样的话,如果你关掉 master 的持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。 -另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能**确保启动的时候,是有数据的**,即使采用了后续讲解的[高可用机制](/docs/high-concurrency/redis-sentinel.md),slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。 +另外,master 的各种备份方案,也需要做。万一本地的所有文件丢失了,从备份中挑选一份 rdb 去恢复 master,这样才能**确保启动的时候,是有数据的**,即使采用了后续讲解的[高可用机制](./redis-sentinel.md),slave node 可以自动接管 master node,但也可能 sentinel 还没检测到 master failure,master node 就自动重启了,还是可能导致上面所有的 slave node 数据被清空。 ## Redis 主从复制的核心原理 @@ -39,7 +39,7 @@ master node 会在内存中维护一个 backlog,master 和 slave 都会保存 master 在内存中直接创建 `RDB` ,然后发送给 slave,不会在自己本地落地磁盘了。只需要在配置文件中开启 `repl-diskless-sync yes` 即可。 -``` bash +```bash repl-diskless-sync yes # 等待 5s 后再开始复制,因为要等更多 slave 重新连接过来 @@ -60,29 +60,29 @@ slave node 内部有个定时任务,每秒检查是否有新的 master node ### 全量复制 -* master 执行 bgsave ,在本地生成一份 rdb 快照文件。 -* master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s) -* master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。 -* 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。 +- master 执行 bgsave ,在本地生成一份 rdb 快照文件。 +- master node 将 rdb 快照文件发送给 slave node,如果 rdb 复制时间超过 60 秒(repl-timeout),那么 slave node 就会认为复制失败,可以适当调大这个参数(对于千兆网卡的机器,一般每秒传输 100MB,6G 文件,很可能超过 60s) +- master node 在生成 rdb 时,会将所有新的写命令缓存在内存中,在 slave node 保存了 rdb 之后,再将新的写命令复制给 slave node。 +- 如果在复制期间,内存缓冲区持续消耗超过 64MB,或者一次性超过 256MB,那么停止复制,复制失败。 -``` bash +```bash client-output-buffer-limit slave 256MB 64MB 60 ``` -* slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中,同时**基于旧的数据版本**对外提供服务。 -* 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。 +- slave node 接收到 rdb 之后,清空自己的旧数据,然后重新加载 rdb 到自己的内存中。注意,在清空旧数据之前,slave node 依然会**基于旧的数据版本**对外提供服务。 +- 如果 slave node 开启了 AOF,那么会立即执行 BGREWRITEAOF,重写 AOF。 ### 增量复制 -* 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。 -* master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。 -* master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。 +- 如果全量复制过程中,master-slave 网络连接断掉,那么 slave 重新连接 master 时,会触发增量复制。 +- master 直接从自己的 backlog 中获取部分丢失的数据,发送给 slave node,默认 backlog 就是 1MB。 +- master 就是根据 slave 发送的 psync 中的 offset 来从 backlog 中获取数据的。 ### heartbeat 主从节点互相都会发送 heartbeat 信息。 -master 默认每隔 10秒 发送一次 heartbeat,slave node 每隔 1秒 发送一个 heartbeat。 +master 默认每隔 10 秒发送一次 heartbeat,slave node 每隔 1 秒发送一个 heartbeat。 ### 异步复制 @@ -100,4 +100,4 @@ Redis 的高可用架构,叫做 `failover` **故障转移**,也可以叫做 master node 在故障时,自动检测,并且将某个 slave node 自动切换为 master node 的过程,叫做主备切换。这个过程,实现了 Redis 的主从架构下的高可用。 -后面会详细说明 Redis [基于哨兵的高可用性](/docs/high-concurrency/redis-sentinel.md)。 +后面会详细说明 Redis [基于哨兵的高可用性](./redis-sentinel.md)。 diff --git a/docs/high-concurrency/redis-persistence.md b/docs/high-concurrency/redis-persistence.md index 7dba2cf3e..20605e2df 100644 --- a/docs/high-concurrency/redis-persistence.md +++ b/docs/high-concurrency/redis-persistence.md @@ -1,4 +1,7 @@ +# Redis 持久化机制 + ## 面试题 + Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的? ## 面试官心理分析 @@ -21,8 +24,8 @@ Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了 ### Redis 持久化的两种方式 -* RDB:RDB 持久化机制,是对 Redis 中的数据执行**周期性**的持久化。 -* AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。 +- RDB:RDB 持久化机制,是对 Redis 中的数据执行**周期性**的持久化。 +- AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 Redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。 通过 RDB 或 AOF,都可以将 Redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。 @@ -32,25 +35,24 @@ Redis 如果仅仅只是将数据缓存在内存里面,如果 Redis 宕机了 #### RDB 优缺点 -* RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。 -* RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis **保持高性能**,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。 -* 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。 - -* 如果想要在 Redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 Redis 进程宕机,那么会丢失最近 5 分钟的数据。 -* RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。 +- RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 Redis 的数据,这种多个数据文件的方式,**非常适合做冷备**,可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 Redis 中的数据。 +- RDB 对 Redis 对外提供的读写服务,影响非常小,可以让 Redis **保持高性能**,因为 Redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。 +- 相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 Redis 进程,更加快速。 +- 如果想要在 Redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 Redis 进程宕机,那么会丢失最近 5 分钟(甚至更长时间)的数据。 +- RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。 #### AOF 优缺点 -* AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 `fsync` 操作,最多丢失 1 秒钟的数据。 -* AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。 -* AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。 -* AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。 -* 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。 -* AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync` ,性能也还是很高的。(如果实时写入,那么 QPS 会大降,Redis 性能会大大降低) -* 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。 +- AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 `fsync` 操作,最多丢失 1 秒钟的数据。 +- AOF 日志文件以 `append-only` 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。 +- AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在 `rewrite` log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。 +- AOF 日志文件的命令通过可读较强的方式进行记录,这个特性非常**适合做灾难性的误删除的紧急恢复**。比如某人不小心用 `flushall` 命令清空了所有数据,只要这个时候后台 `rewrite` 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 `flushall` 命令给删了,然后再将该 `AOF` 文件放回去,就可以通过恢复机制,自动恢复所有数据。 +- 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。 +- AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 `fsync` 一次日志文件,当然,每秒一次 `fsync` ,性能也还是很高的。(如果实时写入,那么 QPS 会大降,Redis 性能会大大降低) +- 以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。所以说,类似 AOF 这种较为复杂的基于命令日志 `merge` 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是**基于当时内存中的数据进行指令的重新构建**,这样健壮性会好很多。 ### RDB 和 AOF 到底该如何选择 -* 不要仅仅使用 RDB,因为那样会导致你丢失很多数据; -* 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug; -* Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择; 用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。 +- 不要仅仅使用 RDB,因为那样会导致你丢失很多数据; +- 也不要仅仅使用 AOF,因为那样有两个问题:第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug; +- Redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择;用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。 diff --git a/docs/high-concurrency/redis-production-environment.md b/docs/high-concurrency/redis-production-environment.md index f743b7c1d..74aab390e 100644 --- a/docs/high-concurrency/redis-production-environment.md +++ b/docs/high-concurrency/redis-production-environment.md @@ -1,4 +1,7 @@ +# Redis 生产部署方案 + ## 面试题 + 生产环境中的 Redis 是怎么部署的? ## 面试官心理分析 @@ -9,9 +12,9 @@ ## 面试题剖析 -Redis cluster,10 台机器,5 台机器部署了 Redis 主实例,另外 5 台机器部署了 Redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。 +Redis cluster,10 台机器,5 台机器部署了 Redis 主实例,另外 5 台机器部署了 Redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰 QPS 可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求每秒。 -机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 Redis 进程的是10g内存,一般线上生产环境,Redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。 +机器是什么配置?32G 内存+ 8 核 CPU + 1T 磁盘,但是分配给 Redis 进程的是 10g 内存,一般线上生产环境,Redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。 5 台机器对外提供读写,一共有 50g 内存。 diff --git a/docs/high-concurrency/redis-rehash.md b/docs/high-concurrency/redis-rehash.md new file mode 100644 index 000000000..52deb3e6e --- /dev/null +++ b/docs/high-concurrency/redis-rehash.md @@ -0,0 +1,45 @@ +# Redis rehash 过程 + +## 面试题 + +有了解过 Redis rehash 的过程吗? + +## 面试官心理分析 + +这个知识点算 redis 中比较低频的面试点,但是当你在介绍 HashMap 的 rehash 或者 ConcurrentHashMap 的 rehash 过程中,可以主动和面试官提及你不仅了解这些,同时还了解 Redis 中的 rehash 过程。 + +Redis 是以速度快,性能好著称的,我们知道 Redis 一开始的容量是有限的,当容量不足时,需要扩容,那扩容的方式是什么?一次性全部将数据转移吗?那当数据量上千万上亿,这必定会阻塞 Redis 对命令的执行。因此就非常有必要了解一下 Redis 中的 rehash 过程。 + +## 面试题剖析 + +众所周知,Redis 主要用于存储键值对(`Key-Value Pair`),而键值对的存储方式是由字典实现,而 Redis 中字典的底层又是通过哈希表来实现的。通过哈希表中的节点保存字典中的键值对。类似 Java 中的 HashMap,将 Key 通过哈希函数映射到哈希表节点位置。 + +Redis 中字典的数据结构如下: + +```c +// 字典对应的数据结构,有关hash表的结构可以参考redis源码,再次就不进行描述 +typedef struct dict { + dictType *type; // 字典类型 + void *privdata; // 私有数据 + dictht ht[2]; // 2个哈希表,这也是进行rehash的重要数据结构,从这也看出字典的底层通过哈希表进行实现。 + long rehashidx; // rehash过程的重要标志,值为-1表示rehash未进行 + int iterators; // 当前正在迭代的迭代器数 +} dict; +``` + +在对哈希表进行扩展或者收缩操作时,程序需要将现有哈希表包含的所有键值对 rehash 到新哈希表里面,具体过程如下: + +### 1. 为字典的备用哈希表分配空间。 + +如果执行的是扩展操作,那么备用哈希表的大小为第一个大于等于需要扩容的哈希表的键值对数量*2 的 2"(2 的 n 次方幂);【`5*2=10,`所以备用哈希表的容量为第一个大于 10 的 2",即 16】 + +如果执行的是收缩操作,那么备用哈希表的大小为第一个大于等于需要扩容的哈希表的键值对数量(`ht[0] .used`)的 2"。 + +### 2. 渐进式 rehash + +rehash 过程在数据量非常大(几千万、亿)的情况下并不是一次性地完成的,而是**渐进式地**完成的。**渐进式 rehash**的好处在于避免对服务器造成影响。 + +渐进式 rehash 的本质: + +1. 借助 rehashidx,将 rehash 键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式 rehash 而带来的庞大计算量。 +2. 在 rehash 进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将原哈希表在 rehashidx 索引上的所有键值对 rehash 到备用哈希表,当 rehash 工作完成之后,程序将 rehashidx 属性的值加 1。 diff --git a/docs/high-concurrency/redis-sentinel.md b/docs/high-concurrency/redis-sentinel.md index 0564368b3..d6aa8dff9 100644 --- a/docs/high-concurrency/redis-sentinel.md +++ b/docs/high-concurrency/redis-sentinel.md @@ -4,25 +4,25 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的一个组件,主要有以下功能: -* 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。 -* 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。 -* 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。 -* 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。 +- 集群监控:负责监控 Redis master 和 slave 进程是否正常工作。 +- 消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。 +- 故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。 +- 配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。 哨兵用于实现 Redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。 -* 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。 -* 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。 +- 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。 +- 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。 ## 哨兵的核心知识 -* 哨兵至少需要 3 个实例,来保证自己的健壮性。 -* 哨兵 + Redis 主从的部署架构,是**不保证数据零丢失**的,只能保证 Redis 集群的高可用性。 -* 对于哨兵 + Redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。 +- 哨兵至少需要 3 个实例,来保证自己的健壮性。 +- 哨兵 + Redis 主从的部署架构,是**不保证数据零丢失**的,只能保证 Redis 集群的高可用性。 +- 对于哨兵 + Redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。 哨兵集群必须部署 2 个以上节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1。 -``` +``` +----+ +----+ | M1 |---------| R1 | | S1 | | S2 | @@ -31,7 +31,7 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的 配置 `quorum=1` ,如果 master 宕机, s1 和 s2 中只要有 1 个哨兵认为 master 宕机了,就可以进行切换,同时 s1 和 s2 会选举出一个哨兵来执行故障转移。但是同时这个时候,需要 majority,也就是大多数哨兵都是运行的。 -``` +``` 2 个哨兵,majority=2 3 个哨兵,majority=2 4 个哨兵,majority=2 @@ -43,7 +43,7 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的 经典的 3 节点哨兵集群是这样的: -``` +``` +----+ | M1 | | S1 | @@ -60,15 +60,16 @@ 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 ,也就是所谓的**脑裂**。 @@ -80,7 +81,7 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的 进行如下配置: -``` bash +```bash min-slaves-to-write 1 min-slaves-max-lag 10 ``` @@ -89,18 +90,18 @@ min-slaves-max-lag 10 如果说一旦所有的 slave,数据复制和同步的延迟都超过了 10 秒钟,那么这个时候,master 就不会再接收任何请求了。 -* 减少异步复制数据的丢失 +- 减少异步复制数据的丢失 有了 `min-slaves-max-lag` 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未同步到 slave 导致的数据丢失降低的可控范围内。 -* 减少脑裂的数据丢失 +- 减少脑裂的数据丢失 如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10 秒没有给自己 ack 消息,那么就直接拒绝客户端的写请求。因此在脑裂场景下,最多就丢失 10 秒的数据。 ## sdown 和 odown 转换机制 -* sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机 -* odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机 +- sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机 +- odown 是客观宕机,如果 quorum 数量的哨兵都觉得一个 master 宕机了,那么就是客观宕机 sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 `is-master-down-after-milliseconds` 指定的毫秒数之后,就主观认为 master 宕机了;如果一个哨兵在指定时间内,收到了 quorum 数量的其它哨兵也认为那个 master 是 sdown 的,那么就认为是 odown 了。 @@ -122,22 +123,22 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过 如果一个 master 被认为 odown 了,而且 majority 数量的哨兵都允许主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来,会考虑 slave 的一些信息: -* 跟 master 断开连接的时长 -* slave 优先级 -* 复制 offset -* run id +- 跟 master 断开连接的时长 +- slave 优先级 +- 复制 offset +- run id 如果一个 slave 跟 master 断开连接的时间已经超过了 `down-after-milliseconds` 的 10 倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master。 -``` +``` (down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state ``` 接下来会对 slave 进行排序: -* 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。 -* 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。 -* 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。 +- 按照 slave 优先级进行排序,slave priority 越低,优先级就越高。 +- 如果 slave priority 相同,那么看 replica offset,哪个 slave 复制了越多的数据,offset 越靠后,优先级就越高。 +- 如果上面两个条件都相同,那么选择一个 run id 比较小的那个 slave。 ## quorum 和 majority diff --git a/docs/high-concurrency/redis-single-thread-model.md b/docs/high-concurrency/redis-single-thread-model.md index dedbf630b..17a427406 100644 --- a/docs/high-concurrency/redis-single-thread-model.md +++ b/docs/high-concurrency/redis-single-thread-model.md @@ -1,4 +1,7 @@ +# Redis 和 Memcached 的区别 + ## 面试题 + Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么 Redis 单线程却能支撑高并发? ## 面试官心理分析 @@ -13,7 +16,7 @@ Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么 #### Redis 支持复杂的数据结构 -Redis 相比 Memcached 来说,拥有[更多的数据结构](/docs/high-concurrency/redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, Redis 会是不错的选择。 +Redis 相比 Memcached 来说,拥有[更多的数据结构](./redis-data-types.md),能支持更丰富的数据操作。如果需要缓存能够支持更复杂的结构和操作, Redis 会是不错的选择。 #### Redis 原生支持集群模式 @@ -29,10 +32,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事 文件事件处理器的结构包含 4 个部分: -* 多个 socket -* IO 多路复用程序 -* 文件事件分派器 -* 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) +- 多个 socket +- IO 多路复用程序 +- 文件事件分派器 +- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) 多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。 @@ -54,10 +57,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事 ### 为啥 Redis 单线程模型也能效率这么高? -* 纯内存操作。 -* 核心是基于非阻塞的 IO 多路复用机制。 -* C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 -* 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 +- 纯内存操作。 +- 核心是基于非阻塞的 IO 多路复用机制。 +- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 +- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 ### Redis 6.0 开始引入多线程 @@ -69,4 +72,4 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事 ### 总结 -Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率。 +Redis 选择使用单线程模型处理客户端的请求主要还是因为 CPU 不是 Redis 服务器的瓶颈,所以使用多线程模型带来的性能提升并不能抵消它带来的开发成本和维护成本,系统的性能瓶颈也主要在网络 I/O 操作上;而 Redis 引入多线程操作也是出于性能上的考虑,对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间(释放操作不会阻塞网络 IO 读写,因为网络 IO 读写与释放的命令执行不是同一个线程)也能减少对 Redis 主线程阻塞的时间,提高执行的效率。 diff --git a/docs/high-concurrency/why-cache.md b/docs/high-concurrency/why-cache.md index 7a9365f0d..27eb0f94d 100644 --- a/docs/high-concurrency/why-cache.md +++ b/docs/high-concurrency/why-cache.md @@ -1,4 +1,7 @@ +# 缓存的使用方式 + ## 面试题 + 项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果? ## 面试官心理分析 @@ -31,7 +34,7 @@ mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql 单机支撑到 `2000QPS` 也开始容易报警了。 -所以要是你有个系统,高峰期一秒钟过来的请求有 1万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。 +所以要是你有个系统,高峰期一秒钟过来的请求有 1 万,那一个 mysql 单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放 mysql。缓存功能简单,说白了就是 `key-value` 式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发 so easy。单机承载并发量是 mysql 单机的几十倍。 > 缓存是走内存的,内存天然就支撑高并发。 @@ -39,8 +42,8 @@ mysql 这么重的数据库,压根儿设计不是让你玩儿高并发的, 常见的缓存问题有以下几个: -* [缓存与数据库双写不一致](/docs/high-concurrency/redis-consistence.md) -* [缓存雪崩、缓存穿透、缓存击穿](/docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) -* [缓存并发竞争](/docs/high-concurrency/redis-cas.md) +- [缓存与数据库双写不一致](./redis-consistence.md) +- [缓存雪崩、缓存穿透、缓存击穿](./redis-caching-avalanche-and-caching-penetration.md) +- [缓存并发竞争](./redis-cas.md) 点击超链接,可直接查看缓存相关问题及解决方案。 diff --git a/docs/high-concurrency/why-mq.md b/docs/high-concurrency/why-mq.md index cb8fac066..47e54b2f1 100644 --- a/docs/high-concurrency/why-mq.md +++ b/docs/high-concurrency/why-mq.md @@ -1,30 +1,35 @@ +# 为什么使用消息队列? + ## 面试题 -* 为什么使用消息队列? -* 消息队列有什么优点和缺点? -* Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景? +- 为什么使用消息队列? +- 消息队列有什么优点和缺点? +- Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么区别,以及适合哪些场景? ## 面试官心理分析 其实面试官主要是想看看: -* **第一**,你知不知道你们系统里为什么要用消息队列这个东西?
+- **第一**,你知不知道你们系统里为什么要用消息队列这个东西? + + 不少候选人,说自己项目里用了 Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人设计的架构,他从头到尾都没思考过。 -不少候选人,说自己项目里用了 Redis、MQ,但是其实他并不知道自己为什么要用这个东西。其实说白了,就是为了用而用,或者是别人设计的架构,他从头到尾都没思考过。
-没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。 + 没有对自己的架构问过为什么的人,一定是平时没有思考的人,面试官对这类候选人印象通常很不好。因为面试官担心你进了团队之后只会木头木脑的干呆活儿,不会自己思考。 -* **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处?
+- **第二**,你既然用了消息队列这个东西,你知不知道用了有什么好处&坏处? -你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。 + 你要是没考虑过这个,那你盲目弄个 MQ 进系统里,后面出了问题你是不是就自己溜了给公司留坑?你要是没考虑过引入一个技术可能存在的弊端和风险,面试官把这类候选人招进来了,基本可能就是挖坑型选手。就怕你干 1 年挖一堆坑,自己跳槽了,给公司留下无穷后患。 -* **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研?
+- **第三**,既然你用了 MQ,可能是某一种 MQ,那么你当时做没做过调研? -你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。
-如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。 + 你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。 + + 如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。 ## 面试题剖析 ### 为什么使用消息队列 + 其实就是问问你消息队列都有哪些使用场景,然后你项目里具体是什么场景,说说你在这个场景里用消息队列是什么? 面试官问你这个问题,**期望的一个回答**是说,你们公司有个什么**业务场景**,这个业务场景有个什么技术挑战,如果不用 MQ 可能会很麻烦,但是你现在用了 MQ 之后带给了你很多的好处。 @@ -33,7 +38,7 @@ #### 解耦 -看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃...... +看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 D 系统现在不需要了呢?A 系统负责人几乎崩溃...... ![mq-1](./images/mq-1.png) @@ -81,36 +86,36 @@ 缺点有以下几个: -* 系统可用性降低
+- 系统可用性降低 -系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](/docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md)。 + 系统引入的外部依赖越多,越容易挂掉。本来你就是 A 系统调用 BCD 三个系统的接口就好了,ABCD 四个系统还好好的,没啥问题,你偏加个 MQ 进来,万一 MQ 挂了咋整?MQ 一挂,整套系统崩溃,你不就完了?如何保证消息队列的高可用,可以[点击这里查看](./how-to-ensure-high-availability-of-message-queues.md)。 -* 系统复杂度提高
+- 系统复杂度提高 -硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](/docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md)?怎么[处理消息丢失的情况](/docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。 + 硬生生加个 MQ 进来,你怎么[保证消息没有重复消费](./how-to-ensure-that-messages-are-not-repeatedly-consumed.md)?怎么[处理消息丢失的情况](./how-to-ensure-the-reliable-transmission-of-messages.md)?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已。 -* 一致性问题
+- 一致性问题 -A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。 + A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数据就不一致了。 -所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。 + 所以消息队列实际是一种非常复杂的架构,你引入它有很多好处,但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉,做好之后,你会发现,妈呀,系统复杂度提升了一个数量级,也许是复杂了 10 倍。但是关键时刻,用,还是得用的。 ### Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点? -| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka | -|---|---|---|---|---| -| 单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 | -| topic 数量对吞吐量的影响 | | | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | -| 时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 | -| 可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | -| 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | -| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | +| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka | +| ------------------------ | ------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| 单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 | +| topic 数量对吞吐量的影响 | | | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | +| 时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | ms 级 | 延迟在 ms 级以内 | +| 可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | +| 消息可靠性 | 有较低的概率丢失数据 | 基本不丢 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | +| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 | 综上,各种对比之后,有如下建议: -一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了; +一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了。 -后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高; +后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高。 不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐给 [Apache](https://github.com/apache/rocketmq),但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..99b7329ed --- /dev/null +++ b/docs/index.md @@ -0,0 +1,35 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "advanced-java" + text: "互联网 Java 工程师进阶知识完全扫盲" + tagline: Doocs 技术社区出品 + actions: + - theme: alt + text: GitHub 首页 + link: https://github.com/doocs/advanced-java + - theme: brand + text: 开始学习 + link: /high-concurrency/mq-interview + + +features: + - title: "⚡ 高并发架构" + details: 包含消息队列、搜索引擎、缓存、分库分表、读写分离等核心知识点 + link: /high-concurrency/mq-interview + - title: "🌐 分布式系统" + details: 涉及 Dubbo、分布式锁、事务、会话等设计与实现原理 + link: /distributed-system/distributed-system-interview + - title: "🛡️ 高可用架构" + details: 涵盖限流、熔断、降级、Hystrix 实现等高可用保障手段 + link: /high-availability/hystrix-introduction + - title: "🧩 微服务架构" + details: 深入了解微服务基础、Spring Cloud、服务治理与通信机制 + link: /micro-services/microservices-introduction + - title: "📊 海量数据处理" + details: 探索应对大数据场景下的经典算法与设计题 + link: /big-data/find-common-urls +--- + diff --git a/docs/micro-services/README.md b/docs/micro-services/README.md index 6c5d0962d..ebbbcb3ab 100644 --- a/docs/micro-services/README.md +++ b/docs/micro-services/README.md @@ -1,38 +1,42 @@ -# 微服务架构 - -* [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java) -* [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md) - -## Spring Cloud 微服务架构 - -* 什么是微服务?微服务之间是如何独立通讯的? -* Spring Cloud 和 Dubbo 有哪些区别? -* Spring Boot 和 Spring Cloud,谈谈你对它们的理解? -* 什么是服务熔断?什么是服务降级? -* 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑? -* 你所知道的微服务技术栈都有哪些? -* Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? -* ...... - ---- - -## 公众号 - -GitHub 技术社区 [Doocs](https://github.com/doocs) 旗下唯一公众号「**Doocs开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - - - - - - -
- -
- 公众平台 -

-
- -
- 个人微信 -

-
+# 微服务架构 + +- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](https://github.com/doocs/advanced-java) +- [关于微服务架构的描述](/docs/micro-services/microservices-introduction.md) +- [从单体式架构迁移到微服务架构](/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md) +- [微服务的事件驱动数据管理](/docs/micro-services/event-driven-data-management-for-microservices.md) +- [选择微服务部署策略](/docs/micro-services/choose-microservice-deployment-strategy.md) +- [微服务架构的优势与不足](/docs/micro-services/advantages-and-disadvantages-of-microservice.md) + +## Spring Cloud 微服务架构 + +- [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/what's-microservice-how-to-communicate.md) +- Spring Cloud 和 Dubbo 有哪些区别? +- Spring Boot 和 Spring Cloud,谈谈你对它们的理解? +- 什么是服务熔断?什么是服务降级? +- 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑? +- [你所知道的微服务技术栈都有哪些?](/docs/micro-services/micro-services-technology-stack.md) +- [微服务治理策略](/docs/micro-services/micro-service-governance.md) +- Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? +- [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md) +- ...... + +--- + +## 公众号 + +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 + + + + + + +
+
+
+
+
+ +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! + +
diff --git a/docs/micro-services/advantages-and-disadvantages-of-microservice.md b/docs/micro-services/advantages-and-disadvantages-of-microservice.md index a5d975d99..f3e359740 100644 --- a/docs/micro-services/advantages-and-disadvantages-of-microservice.md +++ b/docs/micro-services/advantages-and-disadvantages-of-microservice.md @@ -1,4 +1,6 @@ -# 开发单体式应用 +# 微服务架构的优势与不足 + +## 开发单体式应用 假设你正准备开发一款与 Uber 和 Hailo 竞争的出租车调度软件,经过初步会议和需求分析,你可能会手动或者使用基于 Rails、Spring Boot、Play 或者 Maven 的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下: @@ -10,7 +12,7 @@ 这种应用开发风格很常见,因为 IDE 和其它工具都擅长开发一个简单应用,这类应用也很易于调试,只需要简单运行此应用,用 Selenium 链接 UI 就可以完成端到端测试。单体式应用也易于部署,只需要把打包应用拷贝到服务器端,通过在负载均衡器后端运行多个拷贝就可以轻松实现应用扩展。在早期这 类应用运行的很好。 -# 单体式应用的不足 +## 单体式应用的不足 不幸的是,这种简单方法却有很大的局限性。一个简单的应用会随着时间推移逐渐变大。在每次的 sprint 中, 开发团队都会面对新“故事”,然后开发许多新代码。几年后,这个小而简单的应用会变成了一个巨大的怪物。这儿有一个例子,我最近和一个开发者讨论,他正在 写一个工具,用来分析他们一个拥有数百万行代码的应用中 JAR 文件之间的依赖关系。我很确信这个代码正是很多开发者经过多年努力开发出来的一个怪物。 @@ -30,7 +32,7 @@ 那么如何应对呢? -# 微处理架构——处理复杂事物 +## 微处理架构——处理复杂事物 许多公司,比如 Amazon、eBay 和 NetFlix,通过采用微处理结构模式解决了上述问题。其思路不是开发一个巨大的单体式的应用,而是将应用分解为小的、互相连接的微服务。 @@ -64,7 +66,7 @@ 表面上看来,微服务架构模式有点像 SOA,他们都由多个服务构成。但是,可以从另外一个角度看此问题,微服务架构模式是一个不包含 Web 服务 (WS-)和 ESB 服务的 SOA。微服务应用乐于采用简单轻量级协议,比如 REST,而不是 WS-,在微服务内部避免使用 ESB 以及 ESB 类似功能。微服 务架构模式也拒绝使用 canonical schema 等 SOA 概念。 -# 微服务架构的好处 +## 微服务架构的好处 微服务架构模式有很多好处。首先,通过分解巨大单体式应用为多个服务方法解决了复杂性问题。在功能不变的情况下,应用 被分解为多个可管理的分支或服务。每个服务都有一个用 RPC-或者消息驱动 API 定义清楚的边界。微服务架构模式给采用单体式编码方式很难实现的功能提供 了模块化的解决方案,由此,单个服务很容易开发、理解和维护。 @@ -74,7 +76,7 @@ 最后,微服务架构模式使得每个服务独立扩展。你可以根据每个服务的规模来部署满足需求的规模。甚至于,你可以使用更适合于服务资源需求的硬件。比 如,你可以在 EC2 Compute Optimized instances 上部署 CPU 敏感的服务,而在 EC2 memory-optimized instances 上部署内存数据库。 -# 微服务架构的不足 +## 微服务架构的不足 Fred Brooks 在 30 年前写道,“there are no silver bullets”,像任何其它科技一样,微服务架构也有不足。其中一个跟他的名字类似,『微服务』强调了服务大小,实际上,有一些开发者鼓吹建立稍微大一 些的,10-100 LOC 服务组。尽管小服务更乐于被采用,但是不要忘了这只是终端的选择而不是最终的目的。微服务的目的是有效的拆分应用,实现敏捷开发和部署。 @@ -90,6 +92,6 @@ Fred Brooks 在 30 年前写道,“there are no silver bullets”,像任何 一种自动化方法是使用 PaaS 服务,例如 Cloud Foundry。 PaaS 给开发者提供一个部署和管理微服务的简单方法,它把所有这些问题都打包内置解决了。同时,配置 PaaS 的系统和网络专家可以采用最佳实践和策略来 简化这些问题。另外一个自动部署微服务应用的方法是开发对于你来说最基础的 PaaS 系统。一个典型的开始点是使用一个集群化方案,比如配合 Docker 使 用 Mesos 或者 Kubernetes。后面的系列我们会看看如何基于软件部署方法例如 NGINX,可以方便的在微服务层面提供缓存、权限控制、API 统 计和监控。 -# 总结 +## 总结 -构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。 \ No newline at end of file +构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。 diff --git a/docs/micro-services/choose-microservice-deployment-strategy.md b/docs/micro-services/choose-microservice-deployment-strategy.md index 037843b83..0795d5d0e 100644 --- a/docs/micro-services/choose-microservice-deployment-strategy.md +++ b/docs/micro-services/choose-microservice-deployment-strategy.md @@ -1,4 +1,6 @@ -# 前言 +# 选择微服务部署策略 + +## 前言 部署一个单体式应用意味运行大型应用的多个副本,典型的提供若干个(N)服务器(物理或者虚拟),运行若干个(M)个应用实例。部署单体式应用不会很直接,但是肯定比部署微服务应用简单些。 @@ -6,7 +8,7 @@ 有一些微服务部署的模式,先讨论一下每个主机多服务实例的模式。 -# 单主机多服务实例模式 +## 单主机多服务实例模式 部署微服务的一种方法就是单主机多服务实例模式,使用这种模式,需要提供若干台物理或者虚拟机,每台机器上运行多个服务实例。很多情况下,这是传统的应用部署方法。每个服务实例运行一个或者多个主机的 well-known 端口,主机可以看做宠物。 @@ -32,11 +34,11 @@ 可以看到,尽管熟悉,但是单主机多服务实例有很多严重缺陷。下面看看是否有其他部署微服务方式能够避免这些问题。 -# 单主机单服务实例模式 +## 单主机单服务实例模式 另外一种部署微服务方式是单主机单实例模式。当使用这种模式,每个主机上服务实例都是各自独立的。有两种不同实现模式:单虚拟机单实例和单容器单实例。 -## 单虚拟机单实例模式 +### 单虚拟机单实例模式 但是用单虚拟机单实例模式,一般将服务打包成虚拟机映像(image),例如一个 Amazon EC2 AMI。每个服务实例是一个使用此映像启动的 VM(例如,EC2 实例)。下图展示了此架构: @@ -66,7 +68,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用 那么我们来看看另外一种仍然具有虚机特性,但是比较轻量的微服务部署方法。 -# 单容器单服务实例模式 +## 单容器单服务实例模式 当使用这种模式时,每个服务实例都运行在各自容器中。容器是运行在操作系统层面的虚拟化机制。一个容器包含若干运行在沙箱中的进程。从进程角度来看,他们有各自的命名空间和根文件系统;可以限制容器的内存和 CPU 资源。某些容器还具有 I/O 限制,这类容器技术包括 Docker 和 Solaris Zones。 @@ -74,7 +76,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用 ![deployment-strategy-3](./images/deployment-strategy-3.png) -使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统​。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。 +使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统 ​。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server,以及编译过的 Java 应用。 一旦将服务打包成容器映像,就需要启动若干容器。一般在一个物理机或者虚拟机上运行多个容器,可能需要集群管理系统,例如 k8s 或者 Marathon,来管理容器。集群管理系统将主机作为资源池,根据每个容器对资源的需求,决定将容器调度到那个主机上。 @@ -92,7 +94,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用,Bakery。用 除了这些之外,server-less 部署技术,避免了前述容器和 VM 技术的缺陷,吸引了越来越多的注意。下面我们来看看。 -# Serverless 部署 +## Serverless 部署 AWS Lambda 是 serverless 部署技术的例子,支持 Java,Node.js 和 Python 服务;需要将服务打包成 ZIP 文件上载到 AWS Lambda 就可以部署。可以提供元数据,提供处理服务请求函数的名字(一个事件)。AWS Lambda 自动运行处理请求足够多的微服务,然而只根据运行时间和消耗内存量来计费。当然细节决定成败,AWS Lambda 也有限制。但是大家都不需要担心服务器,虚拟机或者容器内的任何方面绝对吸引人。 @@ -100,10 +102,10 @@ Lambda 函数 是无状态服务。一般通过激活 AWS 服务处理请求。 有四种方法激活 Lambda 函数: -* 直接方式,使用 web 服务请求 -* 自动方式,回应例如 AWS S3,DynamoDB,Kinesis 或者 Simple Email Service 等产生的事件 -* 自动方式,通过 AWS API 网关来处理应用客户端发出的 HTTP 请求​ -* 定时方式,通过 cron 响应​--很像定时器方式 +- 直接方式,使用 web 服务请求 +- 自动方式,回应例如 AWS S3,DynamoDB,Kinesis 或者 Simple Email Service 等产生的事件 +- 自动方式,通过 AWS API 网关来处理应用客户端发出的 HTTP 请求 ​ +- 定时方式,通过 cron 响应 ​--很像定时器方式 可以看出,AWS Lambda 是一种很方便部署微服务的方式。基于请求计费方式意味着用户只需要承担处理自己业务那部分的负载;另外,因为不需要了解基础架构,用户只需要开发自己的应用。 diff --git a/docs/micro-services/event-driven-data-management-for-microservices.md b/docs/micro-services/event-driven-data-management-for-microservices.md index 43fbcd06c..e4edbcdaf 100644 --- a/docs/micro-services/event-driven-data-management-for-microservices.md +++ b/docs/micro-services/event-driven-data-management-for-microservices.md @@ -1,13 +1,15 @@ -# 1.1 微服务和分布式数据管理问题 +# 微服务的事件驱动数据管理 + +## 1.1 微服务和分布式数据管理问题 单体式应用一般都会有一个关系型数据库,由此带来的好处是应用可以使用 ACID transactions,可以带来一些重要的操作特性: 1. 原子性 – 任何改变都是原子性的 2. 一致性 – 数据库状态一直是一致性的 -3. 隔离性 – 即使交易并发执行,看起来也是串行的 -4. Durable – 一旦交易提交了就不可回滚 +3. 隔离性 – 即使事务并发执行,看起来也是串行的 +4. Durable – 一旦事务提交了就不可回滚 -鉴于以上特性,应用可以简化为:开始一个交易,改变(插入,删除,更新)很多行,然后提交这些交易。 +鉴于以上特性,应用可以简化为:开始一个事务,改变(插入,删除,更新)很多行,然后提交这些事务。 使用关系型数据库带来另外一个优势在于提供 SQL(功能强大,可声明的,表转化的查询语言)支持。用户可以非常容易通过查询将多个表的数据组合起来,RDBMS 查询调度器决定最佳实现方式,用户不需要担心例如如何访问数据库等底层问题。另外,因为所有应用的数据都在一个数据库中,很容易去查询。 @@ -27,9 +29,9 @@ 第二个挑战是如何完成从多个服务中搜索数据。例如,设想应用需要显示客户和他的订单。如果订单服务提供 API 来接受用户订单信息,那么用户可以使用类应用型的 join 操作接收数据。应用从用户服务接受用户信息,从订单服务接受此用户订单。假设,订单服务只支持通过私有键(key)来查询订单(也许是在使用只支持基于主键接受的 NoSQL 数据库),此时,没有合适的方法来接收所需数据。 -# 1.2 事件驱动架构 +## 1.2 事件驱动架构 -对许多应用来说,这个解决方案就是使用事件驱动架构(event-driven architecture)。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的时间发布。 +对许多应用来说,这个解决方案就是使用事件驱动架构(event-driven architecture)。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的事件发布。 可以使用事件来实现跨多服务的业务交易。交易一般由一系列步骤构成,每一步骤都由一个更新业务实体的微服务和发布激活下一步骤的事件构成。下图展现如何使用事件驱动方法,在创建订单时检查信用可用度,微服务通过消息代理(Messsage Broker)来交换事件。 @@ -47,7 +49,7 @@ 更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。 -考虑到(a)每个服务原子性更新数据库和发布事件,然后,(b)消息代理确保事件传递至少一次,然后可以跨多个服务完成业务交易(此交易不是 ACID 交易)。这种模式提供弱确定性,例如最终一致性 eventual consistency。这种交易类型被称作 BASE model。 +考虑到(a)每个服务原子性更新数据库和发布事件,然后,(b)消息代理确保事件传递至少一次,然后可以跨多个服务完成业务交易(此交易不是 ACID 事务)。这种模式提供弱确定性,例如最终一致性 eventual consistency。这种交易类型被称作 BASE model。 亦可以使用事件来维护不同微服务拥有数据预连接(pre-join)的实现视图。维护此视图的服务订阅相关事件并且更新视图。例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件。 @@ -55,13 +57,13 @@ 当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。可以使用文档数据库(例如 MongoDB)来实现客户订单视图,为每个用户存储一个文档。客户订单视图查询服务负责响应对客户以及最近订单(通过查询客户订单视图数据集)的查询。 -事件驱动架构也是既有优点也有缺点,此架构可以使得交易跨多个服务且提供最终一致性,并且可以使应用维护最终视图;而缺点在于编程模式比 ACID 交易模式更加复杂:为了从应用层级失效中恢复,还需要完成补偿性交易,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)交易造成的改变是可见的,另外当应用读取未更新的最终视图时也会遇见数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事件。 +事件驱动架构也是既有优点也有缺点,此架构可以使得事务跨多个服务且提供最终一致性,并且可以使应用维护最终视图;而缺点在于编程模式比 ACID 事务模式更加复杂:为了从应用层级失效中恢复,还需要完成补偿性事务,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)事务造成的改变是可见的,另外当应用读取未更新的最终视图时也会遇见数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事件。 -# 1.3 原子操作 Achieving Atomicity +## 1.3 原子操作 Achieving Atomicity -事件驱动架构还会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向 ORDER 表插入一行,然后发布 Order Created event,这两个操作需要原子性。如果更新数据库后,服务瘫了(crashes)造成事件未能发布,系统变成不一致状态。确保原子操作的标准方式是使用一个分布式交易,其中包括数据库和消息代理。然而,基于以上描述的 CAP 理论,这却并不是我们想要的。 +事件驱动架构还会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向 ORDER 表插入一行,然后发布 Order Created event,这两个操作需要原子性。如果更新数据库后,服务瘫了(crashes)造成事件未能发布,系统变成不一致状态。确保原子操作的标准方式是使用一个分布式事务,其中包括数据库和消息代理。然而,基于以上描述的 CAP 理论,这却并不是我们想要的。 -## 1.3.1 使用本地交易发布事件 +### 1.3.1 使用本地事务发布事件 获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions,技巧在于一个 EVENT 表,此表在存储业务实体数据库中起到消息列表功能。应用发起一个(本地)数据库交易,更新业务实体状态,向 EVENT 表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此 EVENT 表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示: @@ -73,7 +75,7 @@ 此方法因为应用采用了本地交易更新状态和发布事件而不需要 2PC,现在再看看另外一种应用简单更新状态获得原子性的方法。 -## 1.3.2 挖掘数据库交易日志 +### 1.3.2 挖掘数据库交易日志 另外一种不需要 2PC 而获得线程或者进程发布事件原子性的方式就是挖掘数据库交易或者提交日志。应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见: @@ -87,7 +89,7 @@ 交易日志挖掘方法通过应用直接更新数据库而不需要 2PC 介入。下面我们再看一种完全不同的方法:不需要更新只依赖事件的方法。 -## 1.3.3 使用事件源 +### 1.3.3 使用事件源 Event sourcing (事件源)通过使用根本不同的事件中心方式来获得不需 2PC 的原子性,保证业务实体的一致性。 这种应用保存业务实体一系列状态改变事件,而不是存储实体现在的状态。应用可以通过重放事件来重建实体现在状态。只要业务实体发生变化,新事件就会添加到时间表中。因为保存事件是单一操作,因此肯定是原子性的。 @@ -103,8 +105,8 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来 事件源方法也有不少缺点,因为采用不同或者不太熟悉的变成模式,使得重新学习不太容易;事件存储只支持主键查询业务实体,必须使用 Command Query Responsibility Segregation (CQRS) 来完成查询业务,因此,应用必须处理最终一致数据。 -# 1.4 总结 +## 1.4 总结 -在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务交易一致性;第二个挑战是如何从多服务环境中获取一致性数据。 +在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务事务一致性;第二个挑战是如何从多服务环境中获取一致性数据。 最佳解决办法是采用事件驱动架构。其中碰到的一个挑战是如何原子性的更新状态和发布事件。有几种方法可以解决此问题,包括将数据库视为消息队列、交易日志挖掘和事件源。 diff --git a/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md b/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md index d3a9f45a1..fe5b0503a 100644 --- a/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md +++ b/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md @@ -1,8 +1,5 @@ # 服务发现组件 Eureka 的几个主要调用过程 -* Author: [mghio](https://www.mghio.cn) -* Description: 该文主要讲述服务发现组件 Eureka 的几个主要调用过程 - ## 前言 现在流行的微服务体系结构正在改变我们构建应用程序的方式,从单一的单体服务转变为越来越小的可单独部署的服务(称为 `微服务` ),共同构成了我们的应用程序。当进行一个业务时不可避免就会存在多个服务之间调用,假如一个服务 A 要访问在另一台服务器部署的服务 B,那么前提是服务 A 要知道服务 B 所在机器的 IP 地址和服务对应的端口,最简单的方式就是让服务 A 自己去维护一份服务 B 的配置(包含 IP 地址和端口等信息),但是这种方式有几个明显的缺点:随着我们调用服务数量的增加,配置文件该如何维护;缺乏灵活性,如果服务 B 改变 IP 地址或者端口,服务 A 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。 @@ -18,7 +15,7 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti `Eureka` 是由 [Netflix](https://www.netflix.com) 公司开源,采用的是 Client / Server 模式进行设计,基于 http 协议和使用 Restful Api 开发的服务注册与发现组件,提供了完整的服务注册和服务发现,可以和 `Spring Cloud` 无缝集成。其中 Server 端扮演着服务注册中心的角色,主要是为 Client 端提供服务注册和发现等功能,维护着 Client 端的服务注册信息,同时定期心跳检测已注册的服务当不可用时将服务剔除下线,Client 端可以通过 Server 端获取自身所依赖服务的注册信息,从而完成服务间的调用。遗憾的是从其官方的 [github wiki](https://github.com/Netflix/eureka/wik) 可以发现,2.0 版本已经不再开源。但是不影响我们对其进行深入了解,毕竟服务注册、服务发现相对来说还是比较基础和通用的,其它开源实现框架的思想也是想通的。 -## 服务注册中心(Eureka Server) +## 服务注册中心(Eureka Server) 我们在项目中引入 `Eureka Server` 的相关依赖,然后在启动类加上注解 `@EnableEurekaServer` ,就可以将其作为注册中心,启动服务后访问页面如下: @@ -28,8 +25,6 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti ![eureka-instance-registered-currently.png](./images/eureka-instance-registered-currently.png) -`Demo` 仓库地址:https://github.com/mghio/depth-in-springcloud - 可以看到 `Eureka` 的使用非常简单,只需要添加几个注解和配置就实现了服务注册和服务发现,接下来我们看看它是如何实现这些功能的。 ### 服务注册(Register) @@ -53,7 +48,7 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti ### 服务续约(Renew) -服务续约会由服务提供者(比如 `Demo` 中的 `service-provider` )定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务时效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。 +服务续约会由服务提供者(比如 `Demo` 中的 `service-provider` )定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务失效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource` 的 `renewLease` 方法进行服务续约操作。 ![eureka-server-instanceresource-renew.png](./images/eureka-server-instanceresource-renew.png) @@ -123,7 +118,8 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti 工作中项目使用的是 `Spring Cloud` 技术栈,它有一套非常完善的开源代码来整合 `Eureka` ,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对 `Eureka` 服务发现组件有了更近一步的了解。 ---- +--- + 参考文章 [Netflix Eureka](https://github.com/Netflix/eureka) diff --git a/docs/micro-services/huifer-micro-service-governance.md b/docs/micro-services/huifer-micro-service-governance.md deleted file mode 100644 index 82960667d..000000000 --- a/docs/micro-services/huifer-micro-service-governance.md +++ /dev/null @@ -1,101 +0,0 @@ -# 微服务治理策略 - -* Author: [HuiFer](https://github.com/huifer) -* Description: 该文简单介绍微服务的治理策略以及应用技术 - -## 服务的注册和发现 - -> 解决问题: 集中管理服务 - -> 解决方法: eureka 、zookeeper - -## 负载均衡 - -> 解决问题: 降低服务器硬件压力 - -> 解决方法: nginx 、 Ribbon - -## 通讯 - -> 解决问题: 各个服务之间的沟通桥梁 - -> 解决方法 : -> - 同步消息 -> 1. rest -> 1. rpc -> - 异步消息 -> 1. MQ - -## 配置管理 - -> 解决问题: 随着服务的增加配置也在增加, 如何管理各个服务的配置 - -> 解决方法: nacos 、 spring cloud config 、 Apollo - -## 容错和服务降级 - -> 解决问题: 在微服务当中,一个请求经常会涉及到调用几个服务,如果其中某个服务不可以,没有做服务容错的话,极有可能会造成一连串的服务不可用,这就是雪崩效应. - -> 解决方法: hystrix - -## 服务依赖关系 - -> 解决问题: 多个服务之间来回依赖, 启动关系的不明确 - -> 解决方法: - -> 1. 应用分层: 数据层, 业务层 数据层不需要依赖业务层, 业务层依赖数据, 规定上下依赖关系避免循环圈 - -## 服务文档 - -> 解决问题: 降低沟通成本 - -> 解决方法: swagger 、 java doc - -## 服务安全问题 - -> 解决问题: 敏感数据的安全性 - -> 解决方法: oauth 、 shiro 、 spring security - -## 流量控制 - -> 解决问题: 避免一个服务上的流量过大拖垮整个服务体系 - -> 解决方法: Hystrix - -## 自动化测试 - -> 解决问题: 提前预知异常, 确定服务是否可用 - -> 解决方法: junit - -## 服务上线, 下线的流程 - -> 解决问题: 避免服务随意的上线下线 - -> 解决方法: 新服务上线需要经过管理人员审核. 服务下线需要告知各个调用方进行修改, 直到没有调用该服务才可以进行下线. - -## 兼容性 - -> 解决问题: 服务开发持续进行如何做到兼容 - -> 解决方法: 通过版本号的形式进行管理, 修改完成进行回归测试 - -## 服务编排 - -> 解决问题: 解决服务依赖问题的一种方式 - -> 解决方法: docker & k8s - -## 资源调度 - -> 解决问题: 每个服务的资源占用量不同, 如何分配 - -> 解决方法: JVM 隔离、classload 隔离 ; 硬件隔离 - -## 容量规划 - -> 解决问题: 随着时间增长, 调用逐步增加, 什么时候追加机器 - -> 解决方法: 统计每日调用量和响应时间, 根据机器情况设置阈值, 超过阈值就可以追加机器 diff --git a/docs/micro-services/huifer-micro-services-technology-stack.md b/docs/micro-services/huifer-micro-services-technology-stack.md deleted file mode 100644 index 36d6c1c09..000000000 --- a/docs/micro-services/huifer-micro-services-technology-stack.md +++ /dev/null @@ -1,311 +0,0 @@ -# 微服务技术栈 - -* Author: [HuiFer](https://github.com/huifer) -* Description: 该文简单介绍微服务技术栈有哪些分别用来做什么 - -## 技术栈 - -### 微服务开发 - -作用:快速开发服务。 - -* Spring -* Spring MVC -* Spring Boot - -[官网](https://spring.io/),Spring 目前是 JavaWeb 开发人员必不可少的一个框架,SpringBoot 简化了 Spring 开发的配置目前也是业内主流开发框架。 - -### 微服务注册发现 - -作用:发现服务,注册服务,集中管理服务 - -#### Eureka - -* Eureka Server : 提供服务注册服务, 各个节点启动后,会在 Eureka Server 中进行注册。 -* Eureka Client : 简化与 Eureka Server 的交互操作 -* Spring Cloud Netflix : [GitHub](https://github.com/spring-cloud/spring-cloud-netflix),[文档](https://cloud.spring.io/spring-cloud-netflix/reference/html/) - -#### Zookeeper - -> ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. - -[Zookeeper](https://github.com/apache/zookeeper) 是一个集中的服务, 用于维护配置信息、命名、提供分布式同步和提供组服务。 - -#### Zookeeper 和 Eureka 区别 - -Zookeeper 保证 CP,Eureka 保证 AP: - -* C:数据一致性; -* A:服务可用性; -* P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。 - -### 微服务配置管理 - -作用:统一管理一个或多个服务的配置信息, 集中管理。 - -#### Disconf - -Distributed Configuration Management Platform(分布式配置管理平台) , 它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务, 是一套完整的基于 zookeeper 的分布式配置统一解决方案。 - -* [GitHub](https://github.com/knightliao/disconf) - -#### SpringCloudConfig - -* [GitHub](https://github.com/spring-cloud/spring-cloud-config) - -#### Apollo - -Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,用于微服务配置管理场景。 - -* [GitHub](https://github.com/ctripcorp/apollo) - -### 权限认证 - -作用:根据系统设置的安全规则或者安全策略, 用户可以访问而且只能访问自己被授权的资源,不多不少。 - -#### Spring Security - -* [官网](https://spring.io/projects/spring-security) - -#### apache Shiro - -> Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications. - -* [官网](http://shiro.apache.org/) - -### 批处理 - -作用: 批量处理同类型数据或事物 - -#### Spring Batch - -* [官网](官网) - -### 定时任务 - -> 作用: 定时做什么. - -#### Quartz - -* [官网](http://www.quartz-scheduler.org/) - -### 微服务调用 (协议) - -> 通讯协议 - -#### Rest - -* 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互 - -#### RPC - -* Remote Procedure Call -* 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。 - -#### gRPC - -* [官网](https://www.grpc.io/) -* > A high-performance, open-source universal RPC framework - - > 所谓 RPC(remote procedure call 远程过程调用) 框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从 server/client 模型。使用的时候客户端调用 server 端提供的接口就像是调用本地的函数一样。 - -#### RMI - -* Remote Method Invocation -* 纯 Java 调用 - -### 服务接口调用 - -> 作用:多个服务之间的通讯 - -#### Feign(HTTP) - -Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。 - -* [GitHub](https://github.com/OpenFeign/feign) - -### 服务熔断 - -> 作用: 当请求到达一定阈值时不让请求继续. - -#### Hystrix - -* > Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable. - - > - -* [GitHub](https://github.com/Netflix/Hystrix) - -#### Sentinel - -* > A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库) -* [GitHub](https://github.com/alibaba/Sentinel) - -### 服务的负载均衡 - -> 作用:降低服务压力, 增加吞吐量 - -#### Ribbon - -* >Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具, 它基于 Netflix Ribbon 实现 - - > - -* [GitHub](https://github.com/Netflix/ribbon) - -#### Nginx - -Nginx (engine x) 是一个高性能的 HTTP 和反向代理 web 服务器, 同时也提供了 IMAP/POP3/SMTP 服务 - -* [GitHub](https://github.com/nginx/nginx) - -#### Nginx 与 Ribbon 区别 - -1. Nginx 属于服务端负载均衡,Ribbon 属于客户端负载均衡.Nginx 作用与 Tomcat,Ribbon 作用与各个服务之间的调用 (RPC) - -### 消息队列 - -> 作用: 解耦业务, 异步化处理数据 - -#### Kafka - -* [官网](http://kafka.apache.org/) - -#### RabbitMQ - -* [官网](https://www.rabbitmq.com/) - -#### RocketMQ - -* [官网](http://rocketmq.apache.org/) - -#### activeMQ - -* [官网](http://activemq.apache.org/) - -### 日志采集 (elk) - -> 作用: 收集各服务日志提供日志分析、用户画像等 - -#### Elasticsearch - -* [GitHub](https://github.com/elastic/elasticsearch) - -#### Logstash - -* [GitHub](https://github.com/elastic/logstash) - -#### Kibana - -* [GitHub](https://github.com/elastic/kibana) - -### API 网关 - -> 作用: 外部请求通过 API 网关进行拦截处理, 再转发到真正的服务 - -#### Zuul - -> Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more. -> - -* [GitHub](https://github.com/Netflix/zuul) - -### 服务监控 - -> 作用: 以可视化或非可视化的形式展示出各个服务的运行情况 (CPU、内存、访问量等) - -#### Zabbix - -* [GitHub](https://github.com/jjmartres/Zabbix) - -#### Nagios - -* [官网](https://www.nagios.org/) - -#### Metrics - -* [官网](https://metrics.dropwizard.io) - -### 服务链路追踪 - -> 作用: 明确服务之间的调用关系 - -#### Zipkin - -* [GitHub](https://github.com/openzipkin/zipkin) - -#### Brave - -* [GitHub](https://github.com/openzipkin/brave) - -### 数据存储 - -> 作用: 存储数据 - -#### 关系型数据库 - -##### MySql - -* [官网](https://www.mysql.com/) - -##### Oracle - -* [官网](https://www.oracle.com/index.html) - -##### MsSql - -* [官网](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15) - -##### PostgreSql - -* [官网](https://www.postgresql.org/) - -#### 非关系型数据库 - -##### Mongodb - -* [官网](https://www.mongodb.com/) - -##### Elasticsearch - -* [GitHub](https://github.com/elastic/elasticsearch) - -### 缓存 - -> 作用: 存储数据 - -#### redis - -* [官网](https://redis.io/) - -### 分库分表 - -> 作用: 数据库分库分表方案. - -#### shardingsphere - -* [官网](http://shardingsphere.apache.org/) - -#### Mycat - -* [官网](http://www.mycat.io/) - -### 服务部署 - -> 作用: 将项目快速部署、上线、持续集成. - -#### Docker - -* [官网](http://www.docker.com/) - -#### Jenkins - -* [官网](https://jenkins.io/zh/) - -#### Kubernetes(K8s) - -* [官网](https://kubernetes.io/) - -#### Mesos - -* [官网](http://mesos.apache.org/) diff --git a/docs/micro-services/images/30103116_ZCcM.png b/docs/micro-services/images/30103116_ZCcM.png index 474a8a8fc..18dd99933 100644 Binary files a/docs/micro-services/images/30103116_ZCcM.png and b/docs/micro-services/images/30103116_ZCcM.png differ diff --git a/docs/micro-services/images/Before-and-after-migration.png b/docs/micro-services/images/Before-and-after-migration.png index 3ec40a886..3e22f0016 100644 Binary files a/docs/micro-services/images/Before-and-after-migration.png and b/docs/micro-services/images/Before-and-after-migration.png differ diff --git a/docs/micro-services/images/Event-sourcing.png b/docs/micro-services/images/Event-sourcing.png index 6ea80168b..a509b1156 100644 Binary files a/docs/micro-services/images/Event-sourcing.png and b/docs/micro-services/images/Event-sourcing.png differ diff --git a/docs/micro-services/images/Law-of-Holes.png b/docs/micro-services/images/Law-of-Holes.png index 696fc2585..0b138b901 100644 Binary files a/docs/micro-services/images/Law-of-Holes.png and b/docs/micro-services/images/Law-of-Holes.png differ diff --git a/docs/micro-services/images/No-2PC-required.png b/docs/micro-services/images/No-2PC-required.png index 7be7d25a0..c3330e2d4 100644 Binary files a/docs/micro-services/images/No-2PC-required.png and b/docs/micro-services/images/No-2PC-required.png differ diff --git a/docs/micro-services/images/PreferFunctionalStaffOrganization.png b/docs/micro-services/images/PreferFunctionalStaffOrganization.png index e7f5a6f79..ca1e2c99a 100644 Binary files a/docs/micro-services/images/PreferFunctionalStaffOrganization.png and b/docs/micro-services/images/PreferFunctionalStaffOrganization.png differ diff --git a/docs/micro-services/images/basic-pipeline.png b/docs/micro-services/images/basic-pipeline.png index 4d9fa62d2..2e6041da7 100644 Binary files a/docs/micro-services/images/basic-pipeline.png and b/docs/micro-services/images/basic-pipeline.png differ diff --git a/docs/micro-services/images/conways-law.png b/docs/micro-services/images/conways-law.png index 595677355..eb85ee189 100644 Binary files a/docs/micro-services/images/conways-law.png and b/docs/micro-services/images/conways-law.png differ diff --git a/docs/micro-services/images/deal-with-complex-things-3.png b/docs/micro-services/images/deal-with-complex-things-3.png index 07ed24ccc..ec7eec45f 100644 Binary files a/docs/micro-services/images/deal-with-complex-things-3.png and b/docs/micro-services/images/deal-with-complex-things-3.png differ diff --git a/docs/micro-services/images/decentralised-data.png b/docs/micro-services/images/decentralised-data.png index c2b851901..e85ab2752 100644 Binary files a/docs/micro-services/images/decentralised-data.png and b/docs/micro-services/images/decentralised-data.png differ diff --git a/docs/micro-services/images/deployment-strategy-1.png b/docs/micro-services/images/deployment-strategy-1.png index f30401d19..728c24cef 100644 Binary files a/docs/micro-services/images/deployment-strategy-1.png and b/docs/micro-services/images/deployment-strategy-1.png differ diff --git a/docs/micro-services/images/deployment-strategy-2.png b/docs/micro-services/images/deployment-strategy-2.png index 165ec0a0c..45e8424d3 100644 Binary files a/docs/micro-services/images/deployment-strategy-2.png and b/docs/micro-services/images/deployment-strategy-2.png differ diff --git a/docs/micro-services/images/deployment-strategy-3.png b/docs/micro-services/images/deployment-strategy-3.png index beaeec50a..f18eaabaa 100644 Binary files a/docs/micro-services/images/deployment-strategy-3.png and b/docs/micro-services/images/deployment-strategy-3.png differ diff --git a/docs/micro-services/images/eureka-instance-registered-currently.png b/docs/micro-services/images/eureka-instance-registered-currently.png index 56404e60e..76d9ccefe 100644 Binary files a/docs/micro-services/images/eureka-instance-registered-currently.png and b/docs/micro-services/images/eureka-instance-registered-currently.png differ diff --git a/docs/micro-services/images/eureka-server-applicationresource-addinstance.png b/docs/micro-services/images/eureka-server-applicationresource-addinstance.png index b09c9f04a..e53cf0391 100644 Binary files a/docs/micro-services/images/eureka-server-applicationresource-addinstance.png and b/docs/micro-services/images/eureka-server-applicationresource-addinstance.png differ diff --git a/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png b/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png index 2d5704d2e..4886824aa 100644 Binary files a/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png and b/docs/micro-services/images/eureka-server-cancellease-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-server-evict-sequence-chart.png b/docs/micro-services/images/eureka-server-evict-sequence-chart.png index 49af8fb56..2dc853fd6 100644 Binary files a/docs/micro-services/images/eureka-server-evict-sequence-chart.png and b/docs/micro-services/images/eureka-server-evict-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-server-homepage.png b/docs/micro-services/images/eureka-server-homepage.png index 6152d05fa..7b7990f8e 100644 Binary files a/docs/micro-services/images/eureka-server-homepage.png and b/docs/micro-services/images/eureka-server-homepage.png differ diff --git a/docs/micro-services/images/eureka-server-instanceresource-cancellease.png b/docs/micro-services/images/eureka-server-instanceresource-cancellease.png index 478984e49..9ef461431 100644 Binary files a/docs/micro-services/images/eureka-server-instanceresource-cancellease.png and b/docs/micro-services/images/eureka-server-instanceresource-cancellease.png differ diff --git a/docs/micro-services/images/eureka-server-instanceresource-renew.png b/docs/micro-services/images/eureka-server-instanceresource-renew.png index 02424ed95..7b053417d 100644 Binary files a/docs/micro-services/images/eureka-server-instanceresource-renew.png and b/docs/micro-services/images/eureka-server-instanceresource-renew.png differ diff --git a/docs/micro-services/images/eureka-server-register-sequence-chart.png b/docs/micro-services/images/eureka-server-register-sequence-chart.png index 6bc2fd6d1..1229176fb 100644 Binary files a/docs/micro-services/images/eureka-server-register-sequence-chart.png and b/docs/micro-services/images/eureka-server-register-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-server-registry-structure.png b/docs/micro-services/images/eureka-server-registry-structure.png index e3f487016..c70cdb32e 100644 Binary files a/docs/micro-services/images/eureka-server-registry-structure.png and b/docs/micro-services/images/eureka-server-registry-structure.png differ diff --git a/docs/micro-services/images/eureka-server-renew-sequence-chart.png b/docs/micro-services/images/eureka-server-renew-sequence-chart.png index 3e8dd1005..c0cbc2dd6 100644 Binary files a/docs/micro-services/images/eureka-server-renew-sequence-chart.png and b/docs/micro-services/images/eureka-server-renew-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png b/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png index b3b89560f..37d4c3cfb 100644 Binary files a/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png and b/docs/micro-services/images/eureka-service-consumer-fetch-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-consumer-fetchregistry.png b/docs/micro-services/images/eureka-service-consumer-fetchregistry.png index 0782482a5..e99bdf270 100644 Binary files a/docs/micro-services/images/eureka-service-consumer-fetchregistry.png and b/docs/micro-services/images/eureka-service-consumer-fetchregistry.png differ diff --git a/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png index becf2ea5f..2092cf2ff 100644 Binary files a/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png and b/docs/micro-services/images/eureka-service-provider-cancel-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png index 8132d5cc8..4b1c8afc4 100644 Binary files a/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png and b/docs/micro-services/images/eureka-service-provider-register-sequence-chart.png differ diff --git a/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png b/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png index e239928bb..804051b01 100644 Binary files a/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png and b/docs/micro-services/images/eureka-service-provider-renew-sequence-chart.png differ diff --git a/docs/micro-services/images/micro-deployment.png b/docs/micro-services/images/micro-deployment.png index 55c710ec9..191e4cd39 100644 Binary files a/docs/micro-services/images/micro-deployment.png and b/docs/micro-services/images/micro-deployment.png differ diff --git a/docs/micro-services/images/pre-join.png b/docs/micro-services/images/pre-join.png index 1971edce1..27ba6d861 100644 Binary files a/docs/micro-services/images/pre-join.png and b/docs/micro-services/images/pre-join.png differ diff --git a/docs/micro-services/images/qrcode-for-doocs.jpg b/docs/micro-services/images/qrcode-for-doocs.jpg deleted file mode 100644 index bd1db5d11..000000000 Binary files a/docs/micro-services/images/qrcode-for-doocs.jpg and /dev/null differ diff --git a/docs/micro-services/images/qrcode-for-yanglbme.jpg b/docs/micro-services/images/qrcode-for-yanglbme.jpg deleted file mode 100644 index 5bd385bca..000000000 Binary files a/docs/micro-services/images/qrcode-for-yanglbme.jpg and /dev/null differ diff --git a/docs/micro-services/images/sketch.png b/docs/micro-services/images/sketch.png index ac8827dd1..f4a540288 100644 Binary files a/docs/micro-services/images/sketch.png and b/docs/micro-services/images/sketch.png differ diff --git a/docs/micro-services/micro-service-governance.md b/docs/micro-services/micro-service-governance.md new file mode 100644 index 000000000..ba7363748 --- /dev/null +++ b/docs/micro-services/micro-service-governance.md @@ -0,0 +1,125 @@ +# 微服务治理策略 + +## 服务的注册和发现 + +解决问题:集中管理服务 + +解决方法: + +- Eureka +- Zookeeper + +## 负载均衡 + +解决问题:降低服务器硬件压力 + +解决方法: + +- Nginx +- Ribbon + +## 通讯 + +解决问题:各个服务之间的沟通桥梁 + +解决方法: + +- REST(同步) +- RPC(同步) +- MQ(异步) + +## 配置管理 + +解决问题:随着服务的增加配置也在增加,如何管理各个服务的配置。 + +解决方法: + +- Nacos +- Spring Cloud Config +- Apollo + +## 容错和服务降级 + +解决问题:在微服务当中,一个请求经常会涉及到调用几个服务,如果其中某个服务不可以,没有做服务容错的话,极有可能会造成一连串的服务不可用,这就是雪崩效应。 + +解决方法: + +- Hystrix + +## 服务依赖关系 + +解决问题:多个服务之间来回依赖,启动关系的不明确。 + +解决方法:应用分层。 + +## 服务文档 + +解决问题:降低沟通成本 + +解决方法: + +- Swagger +- Java doc + +## 服务安全问题 + +解决问题:敏感数据的安全性 + +解决方法: + +- Oauth +- Shiro +- Spring Security + +## 流量控制 + +解决问题:避免一个服务上的流量过大拖垮整个服务体系 + +解决方法: + +- Hystrix + +## 自动化测试 + +解决问题:提前预知异常,确定服务是否可用 + +解决方法: + +- junit + +## 服务上线,下线的流程 + +解决问题:避免服务随意的上线下线 + +解决方法:新服务上线需要经过管理人员审核,服务下线需要告知各个调用方进行修改,直到没有调用该服务才可以进行下线。 + +## 兼容性 + +解决问题:服务开发持续进行如何做到兼容。 + +解决方法:通过版本号的形式进行管理,修改完成进行回归测试。 + +## 服务编排 + +解决问题:解决服务依赖问题的一种方式 + +解决方法: + +- Docker +- K8s + +## 资源调度 + +解决问题:每个服务的资源占用量不同,如何分配 + +解决方法: + +- JVM 隔离 +- Classload 隔离 +- 硬件隔离 + +## 容量规划 + +解决问题:随着时间增长,调用逐步增加,什么时候追加机器。 + +解决方法:统计每日调用量和响应时间,根据机器情况设置阈值,超过阈值就可以追加机器。 diff --git a/docs/micro-services/micro-services-technology-stack.md b/docs/micro-services/micro-services-technology-stack.md new file mode 100644 index 000000000..c91a3177f --- /dev/null +++ b/docs/micro-services/micro-services-technology-stack.md @@ -0,0 +1,227 @@ +# 微服务技术栈 + +## 技术栈 + +### 微服务开发 + +作用:快速开发服务。 + +- Spring +- Spring MVC +- Spring Boot + +[Spring](https://spring.io/) 目前是 JavaWeb 开发人员必不可少的一个框架,SpringBoot 简化了 Spring 开发的配置目前也是业内主流开发框架。 + +### 微服务注册发现 + +作用:发现服务,注册服务,集中管理服务。 + +#### Eureka + +- Eureka Server : 提供服务注册服务, 各个节点启动后,会在 Eureka Server 中进行注册。 +- Eureka Client : 简化与 Eureka Server 的交互操作。 +- Spring Cloud Netflix : [GitHub](https://github.com/spring-cloud/spring-cloud-netflix),[文档](https://cloud.spring.io/spring-cloud-netflix/reference/html/) + +#### Zookeeper + +> ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. + +[Zookeeper](https://github.com/apache/zookeeper) 是一个集中的服务, 用于维护配置信息、命名、提供分布式同步和提供组服务。 + +#### Zookeeper 和 Eureka 区别 + +Zookeeper 保证 CP,Eureka 保证 AP: + +- C:数据一致性; +- A:服务可用性; +- P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。 + +### 微服务配置管理 + +作用:统一管理一个或多个服务的配置信息, 集中管理。 + +#### [Disconf](https://github.com/knightliao/disconf) + +Distributed Configuration Management Platform(分布式配置管理平台) , 它是专注于各种分布式系统配置管理 的通用组件/通用平台, 提供统一的配置管理服务, 是一套完整的基于 zookeeper 的分布式配置统一解决方案。 + +#### [SpringCloudConfig](https://github.com/spring-cloud/spring-cloud-config) + +#### [Apollo](https://github.com/ctripcorp/apollo) + +Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,用于微服务配置管理场景。 + +### 权限认证 + +作用:根据系统设置的安全规则或者安全策略, 用户可以访问而且只能访问自己被授权的资源,不多不少。 + +#### [Spring Security](https://spring.io/projects/spring-security) + +#### [Apache Shiro](http://shiro.apache.org/) + +> Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications. + +### 批处理 + +作用: 批量处理同类型数据或事物 + +#### [Spring Batch](https://spring.io/projects/spring-batch) + +### 定时任务 + +> 作用: 定时做什么。 + +#### [Quartz](http://www.quartz-scheduler.org/) + +### 微服务调用 (协议) + +> 通讯协议 + +#### Rest + +- 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互 + +#### RPC + +- Remote Procedure Call +- 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。 + +#### [gRPC](https://www.grpc.io/) + +> A high-performance, open-source universal RPC framework + +所谓 RPC(remote procedure call 远程过程调用) 框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从 server/client 模型。使用的时候客户端调用 server 端提供的接口就像是调用本地的函数一样。 + +#### RMI + +- Remote Method Invocation +- 纯 Java 调用 + +### 服务接口调用 + +> 作用:多个服务之间的通讯 + +#### [Feign(HTTP)](https://github.com/OpenFeign/feign) + +Spring Cloud Netflix 的微服务都是以 HTTP 接口的形式暴露的,所以可以用 Apache 的 HttpClient 或 Spring 的 RestTemplate 去调用,而 Feign 是一个使用起来更加方便的 HTTP 客戶端,使用起来就像是调用自身工程的方法,而感觉不到是调用远程方法。 + +### 服务熔断 + +> 作用: 当请求到达一定阈值时不让请求继续. + +#### [Hystrix](https://github.com/Netflix/Hystrix) + +> Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable. + +#### [Sentinel](https://github.com/alibaba/Sentinel) + +> A lightweight powerful flow control component enabling reliability and monitoring for microservices. (轻量级的流量控制、熔断降级 Java 库) + +### 服务的负载均衡 + +> 作用:降低服务压力, 增加吞吐量 + +#### [Ribbon](https://github.com/Netflix/ribbon) + +> Spring Cloud Ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具, 它基于 Netflix Ribbon 实现 + +#### [Nginx](https://github.com/nginx/nginx) + +Nginx (engine x) 是一个高性能的 HTTP 和反向代理 web 服务器, 同时也提供了 IMAP/POP3/SMTP 服务 + +#### Nginx 与 Ribbon 区别 + +Nginx 属于服务端负载均衡,Ribbon 属于客户端负载均衡。Nginx 作用与 Tomcat,Ribbon 作用与各个服务之间的调用 (RPC)。 + +### 消息队列 + +> 作用: 解耦业务, 异步化处理数据 + +#### [Kafka](http://kafka.apache.org/) + +#### [RabbitMQ](https://www.rabbitmq.com/) + +#### [RocketMQ](http://rocketmq.apache.org/) + +#### [activeMQ](http://activemq.apache.org/) + +### 日志采集 (elk) + +> 作用: 收集各服务日志提供日志分析、用户画像等 + +#### [Elasticsearch](https://github.com/elastic/elasticsearch) + +#### [Logstash](https://github.com/elastic/logstash) + +#### [Kibana](https://github.com/elastic/kibana) + +### API 网关 + +> 作用: 外部请求通过 API 网关进行拦截处理, 再转发到真正的服务 + +#### [Zuul](https://github.com/Netflix/zuul) + +> Zuul is a gateway service that provides dynamic routing, monitoring, resiliency, security, and more. + +### 服务监控 + +> 作用: 以可视化或非可视化的形式展示出各个服务的运行情况 (CPU、内存、访问量等) + +#### [Zabbix](https://github.com/jjmartres/Zabbix) + +#### [Nagios](https://www.nagios.org/) + +#### [Metrics](https://metrics.dropwizard.io) + +### 服务链路追踪 + +> 作用: 明确服务之间的调用关系 + +#### [Zipkin](https://github.com/openzipkin/zipkin) + +#### [Brave](https://github.com/openzipkin/brave) + +### 数据存储 + +> 作用: 存储数据 + +#### 关系型数据库 + +##### [MySql](https://www.mysql.com/) + +##### [Oracle](https://www.oracle.com/index.html) + +##### [MsSQL](https://docs.microsoft.com/zh-cn/sql/?view=sql-server-ver15) + +##### [PostgreSql](https://www.postgresql.org/) + +#### 非关系型数据库 + +##### [Mongodb](https://www.mongodb.com/) + +##### [Elasticsearch](https://github.com/elastic/elasticsearch) + +### 缓存 + +> 作用: 存储数据 + +#### [redis](https://redis.io/) + +### 分库分表 + +> 作用: 数据库分库分表方案. + +#### [ShardingSphere](http://shardingsphere.apache.org/) + +#### [Mycat](http://www.mycat.io/) + +### 服务部署 + +> 作用: 将项目快速部署、上线、持续集成. + +#### [Docker](http://www.docker.com/) + +#### [Jenkins](https://jenkins.io/zh/) + +#### [Kubernetes(K8s)](https://kubernetes.io/) + +#### [Mesos](http://mesos.apache.org/) diff --git a/docs/micro-services/microservices-introduction.md b/docs/micro-services/microservices-introduction.md index 93d66eb5a..27c488b11 100644 --- a/docs/micro-services/microservices-introduction.md +++ b/docs/micro-services/microservices-introduction.md @@ -1,6 +1,6 @@ -# 微服务 +# 关于微服务架构的描述 -> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。
本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。 +> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。
本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。 过去几年中出现了“微服务架构”这一术语,它描述了将软件应用程序设计为若干个可独立部署的服务套件的特定方法。尽管这种架构风格尚未有精确的定义,但围绕业务能力、自动部署、端点智能以及语言和数据的分散控制等组织来说,它们还是存在着某些共同特征。 @@ -44,7 +44,7 @@ 在将大型应用程序拆分为多个部分时,管理层往往侧重于技术层面,从而导致 UI 团队、服务器端逻辑团队、数据库团队的划分。当团队按照这些方式分开时,即便是简单的更改也可能导致跨团队项目的时间和预算批准。一个聪明的团队将围绕这个进行优化,“两害相权取其轻”——只需将逻辑强制应用到他们可以访问的任何应用程序中。换句话说,逻辑无处不在。这是康威定律[5]的一个例子。 -> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。
—— 梅尔文•康威,1967年 +> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。
—— 梅尔文•康威,1967 年 ![conways-law](./images/conways-law.png) @@ -96,7 +96,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形 对于微服务社区来说,开销特别缺乏吸引力。这并不是说社区不重视服务合约。恰恰相反,因为他们有更多的合约。只是他们正在寻找不同的方式来管理这些合约。像 [Tolerant Reader](https://martinfowler.com/bliki/TolerantReader.html) 和 [Consumer-Driven Contracts](https://martinfowler.com/articles/consumerDrivenContracts.html) 这样的模式通常被用于微服务。这些援助服务合约在独立进化。执行消费者驱动的合约作为构建的一部分,增加了信心并对服务是否在运作提供了更快的反馈。事实上,我们知道澳大利亚的一个团队用消费者驱动的合约这种模式来驱动新业务的构建。他们使用简单的工具定义服务的合约。这已变成自动构建的一部分,即使新服务的代码还没写。服务仅在满足合约的时候才被创建出来 - 这是在构建新软件时避免 "YAGNI"[9] 困境的一个优雅的方法。围绕这些成长起来的技术和工具,通过减少服务间的临时耦合,限制了中心合约管理的需要。 -也许去中心化治理的最高境界就是亚马逊广为流传的 build it/run it 理念。团队要对他们构建的软件的各方面负责,包括 7*24 小时的运营。这一级别的责任下放绝对是不规范的,但我们看到越来越多的公司让开发团队负起更多责任。Netflix 是采用这一理念的另一家公司[11]。每天凌晨 3 点被传呼机叫醒无疑是一个强有力的激励,使你在写代码时关注质量。这是关于尽可能远离传统的集中治理模式的一些想法。 +也许去中心化治理的最高境界就是亚马逊广为流传的 build it/run it 理念。团队要对他们构建的软件的各方面负责,包括 7\*24 小时的运营。这一级别的责任下放绝对是不规范的,但我们看到越来越多的公司让开发团队负起更多责任。Netflix 是采用这一理念的另一家公司[11]。每天凌晨 3 点被传呼机叫醒无疑是一个强有力的激励,使你在写代码时关注质量。这是关于尽可能远离传统的集中治理模式的一些想法。 ### 分散数据管理 @@ -202,7 +202,7 @@ Guardian 网站是设计和构建成单体应用程序的一个很好的例子 12: We are being a little disengenuous here. Obviously deploying more services, in more complex topologies is more difficult than deploying a single monolith. Fortunately, patterns reduce this complexity - investment in tooling is still a must though. -13: In fact, Dan North refers to this style as *Replaceable Component Architecture* rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter. +13: In fact, Dan North refers to this style as _Replaceable Component Architecture_ rather than microservices. Since this seems to talk to a subset of the characteristics we prefer the latter. 14: Kent Beck highlights this as one his design principles in [Implementation Patterns](https://www.amazon.com/gp/product/0321413091?ie=UTF8&tag=martinfowlerc-20&linkCode=as2&camp=1789&creative=9325&creativeASIN=0321413091). diff --git a/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md b/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md index b11ad562a..9484ebfea 100644 --- a/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md +++ b/docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md @@ -12,7 +12,7 @@ Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字 我们来看看其他可行策略。 -# 策略 1——停止挖掘 +## 策略 1——停止挖掘 Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用不可管理时这是最佳建议。换句话说,应该停止让单体式应用继续变大,也就是说当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。如下图所示: @@ -24,17 +24,17 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 微服务有三种方式访问单体应用数据: -* 换气单体应用提供的远程 API -* 直接访问单体应用数据库 -* 自己维护一份从单体应用中同步的数据 +- 换气单体应用提供的远程 API +- 直接访问单体应用数据库 +- 自己维护一份从单体应用中同步的数据 -胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 *Domain Driven Design*,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。 +胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 _Domain Driven Design_,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。 将新功能以轻量级微服务方式实现由很多优点,例如可以阻止单体应用变的更加无法管理。微服务本身可以开发、部署和独立扩展。采用微服务架构会给开发者带来不同的切身感受。 -然而,这方法并不解决任何单体式本身问题,为了解决单体式本身问题必须深入单体应用​做出改变。我们来看看这么做的策略。 +然而,这方法并不解决任何单体式本身问题,为了解决单体式本身问题必须深入单体应用 ​ 做出改变。我们来看看这么做的策略。 -# 策略 2——将前端和后端分离 +## 策略 2——将前端和后端分离 减小单体式应用复杂度的策略是讲表现层和业务逻辑、数据访问层分开。典型的企业应用至少有三个不同元素构成: @@ -52,11 +52,11 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 然而,这种策略只是部分的解决方案。很可能应用的两部分之一或者全部都是不可管理的,因此需要使用第三种策略来消除剩余的单体架构。 -# 策略 3——抽出服务 +## 策略 3——抽出服务 第三种迁移策略就是从单体应用中抽取出某些模块成为独立微服务。每当抽取一个模块变成微服务,单体应用就变简单一些;一旦转换足够多的模块,单体应用本身已经不成为问题了,要么消失了,要么简单到成为一个服务。 -## 排序那个模块应该被转成微服务 +### 排序那个模块应该被转成微服务 一个巨大的复杂单体应用由成十上百个模块构成,每个都是被抽取对象。决定第一个被抽取模块一般都是挑战,一般最好是从最容易抽取的模块开始,这会让开发者积累足够经验,这些经验可以为后续模块化工作带来巨大好处。 @@ -66,7 +66,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 查找现有粗粒度边界来决定哪个模块应该被抽取,也是很有益的,这使得移植工作更容易和简单。例如,只与其他应用异步同步消息的模块就是一个明显边界,可以很简单容易地将其转换为微服务。 -## 如何抽取模块 +### 如何抽取模块 抽取模块第一步就是定义好模块和单体应用之间粗粒度接口,由于单体应用需要微服务的数据,反之亦然,因此更像是一个双向 API。因为必须在负责依赖关系和细粒度接口模式之间做好平衡,因此开发这种 API 很有挑战性,尤其对使用域模型模式的业务逻辑层来说更具有挑战,因此经常需要改变代码来解决依赖性问题,如图所示: diff --git a/docs/micro-services/huifer-what's-microservice-how-to-communicate.md b/docs/micro-services/what's-microservice-how-to-communicate.md similarity index 78% rename from docs/micro-services/huifer-what's-microservice-how-to-communicate.md rename to docs/micro-services/what's-microservice-how-to-communicate.md index 24442707d..7367a81c1 100644 --- a/docs/micro-services/huifer-what's-microservice-how-to-communicate.md +++ b/docs/micro-services/what's-microservice-how-to-communicate.md @@ -1,14 +1,11 @@ # 什么是微服务?微服务之间是如何独立通讯的? -* Author: [HuiFer](https://github.com/huifer) -* Description: 介绍微服务的定义以及服务间的通信。 - ## 什么是微服务 -* 微服务架构是一个分布式系统, 按照业务进行划分成为不同的服务单元, 解决单体系统性能等不足。 -* 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。 +- 微服务架构是一个分布式系统,按照业务进行划分成为不同的服务单元,解决单体系统性能等不足。 +- 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。 -> 微服务概念起源: [Microservices](https://martinfowler.com/articles/microservices.html) +> 微服务概念起源:[Microservices](https://martinfowler.com/articles/microservices.html) ## 微服务之间是如何独立通讯的 @@ -16,17 +13,17 @@ #### REST HTTP 协议 -REST 请求在微服务中是最为常用的一种通讯方式, 它依赖于 HTTP\HTTPS 协议。RESTFUL 的特点是: +REST 请求在微服务中是最为常用的一种通讯方式,它依赖于 HTTP\HTTPS 协议。RESTFUL 的特点是: -1\. 每一个 URI 代表 1 种资源 -2\. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作: GET 用来获取资源, POST 用来新建资源\(也可以用于更新资源\), PUT 用来更新资源, DELETE 用来删除资源 -3\. 通过操作资源的表现形式来操作资源 -4\. 资源的表现形式是 XML 或者 HTML -5\. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息 +1. 每一个 URI 代表 1 种资源 +2. 客户端使用 GET、POST、PUT、DELETE 4 个表示操作方式的动词对服务端资源进行操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源 +3. 通过操作资源的表现形式来操作资源 +4. 资源的表现形式是 XML 或者 HTML +5. 客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息 举个例子,有一个服务方提供了如下接口: -``` java +```java @RestController @RequestMapping("/communication") public class RestControllerDemo { @@ -39,7 +36,7 @@ public class RestControllerDemo { 另外一个服务需要去调用该接口,调用方只需要根据 API 文档发送请求即可获取返回结果。 -``` java +```java @RestController @RequestMapping("/demo") public class RestDemo{ @@ -56,27 +53,26 @@ public class RestDemo{ 通过这样的方式可以实现服务之间的通讯。 -#### RPC TCP协议 +#### RPC TCP 协议 RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务。它的工作流程是这样的: -1\. 执行客户端调用语句,传送参数 -2\. 调用本地系统发送网络消息 -3\. 消息传送到远程主机 -4\. 服务器得到消息并取得参数 -5\. 根据调用请求以及参数执行远程过程(服务) -6\. 执行过程完毕,将结果返回服务器句柄 -7\. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果 -8\. 消息传回本地主机 -9\. 客户端句柄由本地主机的网络服务接收消息 +1. 执行客户端调用语句,传送参数 +2. 调用本地系统发送网络消息 +3. 消息传送到远程主机 +4. 服务器得到消息并取得参数 +5. 根据调用请求以及参数执行远程过程(服务) +6. 执行过程完毕,将结果返回服务器句柄 +7. 服务器句柄返回结果,调用远程主机的系统网络服务发送结果 +8. 消息传回本地主机 +9. 客户端句柄由本地主机的网络服务接收消息 10. 客户端接收到调用语句返回的结果数据 举个例子。 首先需要一个服务端: -``` java - +```java import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -90,7 +86,6 @@ import java.util.concurrent.Executors; /** * RPC 服务端用来注册远程方法的接口和实现类 - * @Date: 2019-11-04 */ public class RPCServer { private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); @@ -183,8 +178,7 @@ public class RPCServer { 其次需要一个客户端: -``` java - +```java import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationHandler; @@ -195,7 +189,6 @@ import java.net.Socket; /** * RPC 客户端 - * @Date: 2019-11-04 */ public class RPCclient { /** @@ -242,7 +235,7 @@ public class RPCclient { 再来一个测试的远程方法。 -``` java +```java public interface Tinterface { String send(String msg); } @@ -258,16 +251,10 @@ public class TinterfaceImpl implements Tinterface { 测试代码如下: -``` java - -import com.huifer.admin.rpc.Tinterface; -import com.huifer.admin.rpc.TinterfaceImpl; - +```java import java.net.InetSocketAddress; -/** - * @Date: 2019-11-04 - */ + public class RunTest { public static void main(String[] args) { new Thread(new Runnable() { @@ -292,5 +279,4 @@ public class RunTest { #### 消息中间件 -> 常见的消息中间件有 Kafka、ActiveMQ、RabbitMQ、RocketMQ , 常见的协议有AMQP、MQTTP、STOMP、XMPP. 这里不对消息队列进行拓展了, 具体如何使用还是请移步官网. -> +常见的消息中间件有 Kafka、ActiveMQ、RabbitMQ、RocketMQ ,常见的协议有 AMQP、MQTTP、STOMP、XMPP。这里不对消息队列进行拓展了,具体如何使用还是请移步官网。 diff --git a/docs/public/favicon-16x16.png b/docs/public/favicon-16x16.png new file mode 100644 index 000000000..d0010b987 Binary files /dev/null and b/docs/public/favicon-16x16.png differ diff --git a/docs/public/favicon-32x32.png b/docs/public/favicon-32x32.png new file mode 100644 index 000000000..292ae8597 Binary files /dev/null and b/docs/public/favicon-32x32.png differ diff --git a/docs/public/icon.png b/docs/public/icon.png new file mode 100644 index 000000000..1de9f42e0 Binary files /dev/null and b/docs/public/icon.png differ diff --git a/images/favicon-16x16.png b/images/favicon-16x16.png index 3c1b762d6..d0010b987 100644 Binary files a/images/favicon-16x16.png and b/images/favicon-16x16.png differ diff --git a/images/favicon-32x32.png b/images/favicon-32x32.png index 7c069d2ef..292ae8597 100644 Binary files a/images/favicon-32x32.png and b/images/favicon-32x32.png differ diff --git a/images/icon.png b/images/icon.png index a39d36b6f..1de9f42e0 100644 Binary files a/images/icon.png and b/images/icon.png differ diff --git a/images/pdf.png b/images/pdf.png new file mode 100644 index 000000000..1d4f2926e Binary files /dev/null and b/images/pdf.png differ diff --git a/images/qrcode-for-doocs.jpg b/images/qrcode-for-doocs.jpg index bd1db5d11..487ce6b28 100644 Binary files a/images/qrcode-for-doocs.jpg and b/images/qrcode-for-doocs.jpg differ diff --git a/images/qrcode-for-yanglbme.jpg b/images/qrcode-for-yanglbme.jpg index 5bd385bca..cfbb34448 100644 Binary files a/images/qrcode-for-yanglbme.jpg and b/images/qrcode-for-yanglbme.jpg differ diff --git a/images/starcharts.svg b/images/starcharts.svg new file mode 100644 index 000000000..40f7b1f19 --- /dev/null +++ b/images/starcharts.svg @@ -0,0 +1,77494 @@ + +\n2018-11-232019-09-112020-06-292021-04-182022-02-042022-11-232023-09-112024-06-302025-04-18Time0980019500293003900048800585006830078000Stargazers \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index b3fe9e8f8..000000000 --- a/index.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - 互联网 Java 工程师进阶知识完全扫盲 - - - - - - - - - - - - -
本系列知识由 Doocs 开源社区总结发布
- - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..c1789e796 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2304 @@ +{ + "name": "advanced-java", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "vitepress-plugin-comment-with-giscus": "^1.1.15" + }, + "devDependencies": { + "vitepress": "^1.6.3" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmmirror.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-abtesting/-/client-abtesting-5.23.3.tgz", + "integrity": "sha512-yHI0hBwYcNPc+nJoHPTmmlP8pG6nstCEhpHaZQCDwLZhdMtNhd1hliZMCtLgNnvd1yKEgTt/ZDnTSdZLehfKdA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-analytics/-/client-analytics-5.23.3.tgz", + "integrity": "sha512-/70Ey+nZm4bRr2DcNrGU251YIn9lDu0g8xeP4jTCyunGRNFZ/d8hQAw9El34pcTpO1QDojJWAi6ywKIrUaks9w==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-common/-/client-common-5.23.3.tgz", + "integrity": "sha512-fkpbPclIvaiyw3ADKRBCxMZhrNx/8//6DClfWGxeEiTJ0HEEYtHlqE6GjAkEJubz4v1ioCQkhZwMoFfFct2/vQ==", + "dev": true, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-insights/-/client-insights-5.23.3.tgz", + "integrity": "sha512-TXc5Ve6QOCihWCTWY9N56CZxF1iovzpBWBUhQhy6JSiUfX3MXceV3saV+sXHQ1NVt2NKkyUfEspYHBsTrYzIDg==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-personalization/-/client-personalization-5.23.3.tgz", + "integrity": "sha512-JlReruxxiw9LB53jF/BmvVV+c0thiWQUHRdgtbVIEusvRaiX1IdpWJSPQExEtBQ7VFg89nP8niCzWtA34ktKSA==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.23.3.tgz", + "integrity": "sha512-GDEExFMXwx0ScE0AZUA4F6ssztdJvGcXUkdWmWyt2hbYz43ukqmlVJqPaYgGmWdjJjvTx+dNF/hcinwWuXbCug==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/client-search/-/client-search-5.23.3.tgz", + "integrity": "sha512-mwofV6tGo0oHt4BPi+S5eLC3wnhOa4A1OVgPxetTxZuetod+2W4cxKavUW2v/Ma5CABXPLooXX+g9E67umELZw==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/ingestion/-/ingestion-1.23.3.tgz", + "integrity": "sha512-Zxgmi7Hk4lI52YFphzzJekUqWxYxVjY2GrCpOxV+QiojvUi8Ru+knq6REcwLHFSwpwaDh2Th5pOefMpn4EkQCw==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/monitoring/-/monitoring-1.23.3.tgz", + "integrity": "sha512-zi/IqvsmFW4E5gMaovAE4KRbXQ+LDYpPGG1nHtfuD5u3SSuQ31fT1vX2zqb6PbPTlgJMEmMk91Mbb7fIKmbQUw==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/recommend/-/recommend-5.23.3.tgz", + "integrity": "sha512-C9TwbT1zGwULLXGSUSB+G7o/30djacPmQcsTHepvT47PVfPr2ISK/5QVtUnjMU84LEP8uNjuPUeM4ZeWVJ2iuQ==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.23.3.tgz", + "integrity": "sha512-/7oYeUhYzY0lls7WtkAURM6wy21/Wwmq9GdujW1MpoYVC0ATXXxwCiAfOpYL9xdWxLV0R3wjyD+yZEni+nboKg==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/requester-fetch/-/requester-fetch-5.23.3.tgz", + "integrity": "sha512-r/4fKz4t+bSU1KdjRq+swdNvuGfJ0spV8aFTHPtcsF+1ZaN/VqmdXrTe5NkaZLSztFeMqKwZlJIVvE7VuGlFtw==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/@algolia/requester-node-http/-/requester-node-http-5.23.3.tgz", + "integrity": "sha512-UZiTNmUBQFPl3tUKuXaDd8BxEC0t0ny86wwW6XgwfM9IQf4PrzuMpvuOGIJMcCGlrNolZDEI0mcbz/tqRdKW7A==", + "dev": true, + "dependencies": { + "@algolia/client-common": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmmirror.com/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmmirror.com/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmmirror.com/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@giscus/vue": { + "version": "2.4.0", + "resolved": "https://registry.npmmirror.com/@giscus/vue/-/vue-2.4.0.tgz", + "integrity": "sha512-QOxKHgsMT91myyQagP2v20YYAei1ByZuc3qcaYxbHx4AwOeyVrybDIuRFwG9YDv6OraC86jYnU4Ixd37ddC/0A==", + "dependencies": { + "giscus": "^1.4.0" + }, + "peerDependencies": { + "vue": ">=3.2.0" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.31", + "resolved": "https://registry.npmmirror.com/@iconify-json/simple-icons/-/simple-icons-1.2.31.tgz", + "integrity": "sha512-xBUPtvkcSAiXs9DfVtudhLddQtQYin3I3Ph/W5FNYA0oE6r2hmLB8TgOog9OjOt1Sxn3IB5+4n5+64DMf2xNmQ==", + "dev": true, + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", + "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@lit/reactive-element/-/reactive-element-2.1.0.tgz", + "integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmmirror.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", + "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dependencies": { + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.2.tgz", + "integrity": "sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==", + "dev": true, + "dependencies": { + "@vue/devtools-kit": "^7.7.2" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz", + "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==", + "dev": true, + "dependencies": { + "@vue/devtools-shared": "^7.7.2", + "birpc": "^0.2.19", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.1" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.2", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz", + "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==", + "dev": true, + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "dependencies": { + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "dependencies": { + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "dependencies": { + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "vue": "3.5.13" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.23.3", + "resolved": "https://registry.npmmirror.com/algoliasearch/-/algoliasearch-5.23.3.tgz", + "integrity": "sha512-0JlUaY/hl3LrKvbidI5FysEi2ggAlcTHM8AHV2UsrJUXnNo8/lWBfhzc1b7o8bK3YZNiU26JtLyT9exoj5VBgA==", + "dev": true, + "dependencies": { + "@algolia/client-abtesting": "5.23.3", + "@algolia/client-analytics": "5.23.3", + "@algolia/client-common": "5.23.3", + "@algolia/client-insights": "5.23.3", + "@algolia/client-personalization": "5.23.3", + "@algolia/client-query-suggestions": "5.23.3", + "@algolia/client-search": "5.23.3", + "@algolia/ingestion": "1.23.3", + "@algolia/monitoring": "1.23.3", + "@algolia/recommend": "5.23.3", + "@algolia/requester-browser-xhr": "5.23.3", + "@algolia/requester-fetch": "5.23.3", + "@algolia/requester-node-http": "5.23.3" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/birpc": { + "version": "0.2.19", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/copy-anything": { + "version": "3.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz", + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dev": true, + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/focus-trap": { + "version": "7.6.4", + "resolved": "https://registry.npmmirror.com/focus-trap/-/focus-trap-7.6.4.tgz", + "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", + "dev": true, + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/giscus": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/giscus/-/giscus-1.6.0.tgz", + "integrity": "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==", + "dependencies": { + "lit": "^3.2.1" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmmirror.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-what": { + "version": "4.1.16", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/lit": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lit/-/lit-3.3.0.tgz", + "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/lit-element/-/lit-element-4.2.0.tgz", + "integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.0", + "resolved": "https://registry.npmmirror.com/lit-html/-/lit-html-3.3.0.tgz", + "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmmirror.com/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/minisearch": { + "version": "7.1.2", + "resolved": "https://registry.npmmirror.com/minisearch/-/minisearch-7.1.2.tgz", + "integrity": "sha512-R1Pd9eF+MD5JYDDSPAp/q1ougKglm14uEkPMvQ/05RGmx6G9wvmLTrTI/Q5iPNJLYqNdsDQ7qTGIcNWR+FrHmA==", + "dev": true + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.26.5", + "resolved": "https://registry.npmmirror.com/preact/-/preact-10.26.5.tgz", + "integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/property-information": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/property-information/-/property-information-7.0.0.tgz", + "integrity": "sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "dev": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmmirror.com/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "peer": true + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmmirror.com/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/superjson": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", + "dev": true, + "dependencies": { + "copy-anything": "^3.0.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmmirror.com/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "dev": true + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.18", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.18.tgz", + "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.3", + "resolved": "https://registry.npmmirror.com/vitepress/-/vitepress-1.6.3.tgz", + "integrity": "sha512-fCkfdOk8yRZT8GD9BFqusW3+GggWYZ/rYncOfmgcDtP3ualNHCAg+Robxp2/6xfH1WwPHtGpPwv7mbA3qomtBw==", + "dev": true, + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vitepress-plugin-comment-with-giscus": { + "version": "1.1.15", + "resolved": "https://registry.npmmirror.com/vitepress-plugin-comment-with-giscus/-/vitepress-plugin-comment-with-giscus-1.1.15.tgz", + "integrity": "sha512-1DJjgN+7SYvn5ZkjuSXPmz7nlqfcrh4qCGGviiZghA2ELXnaO2m9WY7m+RisPSaqCn90xqe0JbO2T4NMq8iUBg==", + "dependencies": { + "@giscus/vue": "^2.2.8" + } + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..71733933c --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "devDependencies": { + "vitepress": "^1.6.3" + }, + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "dependencies": { + "vitepress-plugin-comment-with-giscus": "^1.1.15" + } +} diff --git a/summary.md b/summary.md deleted file mode 100644 index 8087ba649..000000000 --- a/summary.md +++ /dev/null @@ -1,126 +0,0 @@ -- 高并发架构 - - - [消息队列](./docs/high-concurrency/mq-interview.md) - - [为什么使用消息队列?](./docs/high-concurrency/why-mq.md) - - [如何保证消息队列的高可用?](./docs/high-concurrency/how-to-ensure-high-availability-of-message-queues.md) - - [如何保证消息不被重复消费?](./docs/high-concurrency/how-to-ensure-that-messages-are-not-repeatedly-consumed.md) - - [如何保证消息的可靠性传输?](./docs/high-concurrency/how-to-ensure-the-reliable-transmission-of-messages.md) - - [如何保证消息的顺序性?](./docs/high-concurrency/how-to-ensure-the-order-of-messages.md) - - [如何解决消息队列的延时以及过期失效问题?](./docs/high-concurrency/mq-time-delay-and-expired-failure.md) - - [如何设计一个消息队列?](./docs/high-concurrency/mq-design.md) - - - [搜索引擎](./docs/high-concurrency/es-introduction.md) - - [ES 的分布式架构原理是什么?](./docs/high-concurrency/es-architecture.md) - - [ES 写入数据的工作原理是什么?](./docs/high-concurrency/es-write-query-search.md) - - [ES 在数十亿级别数量下如何提高查询效率?](./docs/high-concurrency/es-optimizing-query-performance.md) - - [ES 生产集群的部署架构是什么?](./docs/high-concurrency/es-production-cluster.md) - - - 缓存 - - [在项目中缓存是如何使用的?](./docs/high-concurrency/why-cache.md) - - [Redis 和 Memcached 有什么区别?](./docs/high-concurrency/redis-single-thread-model.md) - - [Redis 都有哪些数据类型以及适用场景?](./docs/high-concurrency/redis-data-types.md) - - [Redis 的过期策略都有哪些?](./docs/high-concurrency/redis-expiration-policies-and-lru.md) - - [如何保证 Redis 高并发、高可用?](./docs/high-concurrency/how-to-ensure-high-concurrency-and-high-availability-of-redis.md) - - [Redis 的持久化有哪几种方式?](./docs/high-concurrency/redis-persistence.md) - - [Redis 集群模式的工作原理能说一下么?](./docs/high-concurrency/redis-cluster.md) - - [Redis 的雪崩、穿透和击穿,如何应对?](./docs/high-concurrency/redis-caching-avalanche-and-caching-penetration.md) - - [如何保证缓存与数据库双写一致性?](./docs/high-concurrency/redis-consistence.md) - - [如何解决 Redis 的并发竞争问题?](./docs/high-concurrency/redis-cas.md) - - [生产环境中的 Redis 是怎么部署的?](./docs/high-concurrency/redis-production-environment.md) - - - 分库分表 - - [为什么要分库分表?](./docs/high-concurrency/database-shard.md) - - [分库分表如何平滑过渡?](./docs/high-concurrency/database-shard-method.md) - - [设计一个动态扩容缩容的分库分表方案?](./docs/high-concurrency/database-shard-dynamic-expand.md) - - [分库分表之后,id 主键如何处理?](./docs/high-concurrency/database-shard-global-id-generate.md) - - - 读写分离 - - [如何实现 MySQL 的读写分离?](./docs/high-concurrency/mysql-read-write-separation.md) - - - 高并发系统 - - [如何设计一个高并发系统?](./docs/high-concurrency/high-concurrency-design.md) - -* 分布式系统 - - [面试连环炮](./docs/distributed-system/distributed-system-interview.md) - - 系统拆分 - - [为什么要进行系统拆分?](./docs/distributed-system/why-dubbo.md) - - - 分布式服务框架 - - [说一下 Dubbo 的工作原理?](./docs/distributed-system/dubbo-operating-principle.md) - - [Dubbo 支持哪些序列化协议?](./docs/distributed-system/dubbo-serialization-protocol.md) - - [Dubbo 负载均衡策略和集群容错策略?](./docs/distributed-system/dubbo-load-balancing.md) - - [Dubbo 的 SPI 思想是什么?](./docs/distributed-system/dubbo-spi.md) - - [如何基于 Dubbo 进行服务治理?](./docs/distributed-system/dubbo-service-management.md) - - [分布式服务接口的幂等性如何设计?](./docs/distributed-system/distributed-system-idempotency.md) - - [分布式服务接口请求的顺序性如何保证?](./docs/distributed-system/distributed-system-request-sequence.md) - - [如何自己设计一个类似 Dubbo 的 RPC 框架?](./docs/distributed-system/dubbo-rpc-design.md) - - [CAP 定理的 P 是什么?](./docs/distributed-system/distributed-system-cap.md) - - - 分布式锁 - - [Zookeeper 都有哪些应用场景?](./docs/distributed-system/zookeeper-application-scenarios.md) - - [分布式锁如何设计?](./docs/distributed-system/distributed-lock-redis-vs-zookeeper.md) - - - 分布式事务 - - [分布式事务了解吗?](./docs/distributed-system/distributed-transaction.md) - - - 分布式会话 - - [集群分布式 Session 如何实现?](./docs/distributed-system/distributed-session.md) - -* 高可用架构 - - 基于 Hystrix 实现高可用 - - [Hystrix 介绍](./docs/high-availability/hystrix-introduction.md) - - [电商网站详情页系统架构](./docs/high-availability/e-commerce-website-detail-page-architecture.md) - - [Hystrix 线程池技术实现资源隔离](./docs/high-availability/hystrix-thread-pool-isolation.md) - - [Hystrix 信号量机制实现资源隔离](./docs/high-availability/hystrix-semphore-isolation.md) - - [Hystrix 隔离策略细粒度控制](./docs/high-availability/hystrix-execution-isolation.md) - - [深入 Hystrix 执行时内部原理](./docs/high-availability/hystrix-process.md) - - [基于 request cache 请求缓存技术优化批量商品数据查询接口](./docs/high-availability/hystrix-request-cache.md) - - [基于本地缓存的 fallback 降级机制](./docs/high-availability/hystrix-fallback.md) - - [深入 Hystrix 断路器执行原理](./docs/high-availability/hystrix-circuit-breaker.md) - - [深入 Hystrix 线程池隔离与接口限流](./docs/high-availability/hystrix-thread-pool-current-limiting.md) - - [基于 timeout 机制为服务接口调用超时提供安全保护](./docs/high-availability/hystrix-timeout.md) - - - 高可用系统 - - 如何设计一个高可用系统? - - - 限流 - - [如何限流?说一下具体的实现?](/docs/high-concurrency/huifer-how-to-limit-current.md) - - - 熔断 - - 如何进行熔断? - - 熔断框架都有哪些?具体实现原理知道吗? - - [熔断框架,选用 Sentinel 还是 Hystrix?](./docs/high-availability/sentinel-vs-hystrix.md) - - - 降级 - - 如何进行降级? - -* 微服务架构 - - 微服务的一些概念 - - [关于微服务架构的描述](./docs/micro-services/microservices-introduction.md) - - [从单体式架构迁移到微服务架构](./docs/micro-services/migrating-from-a-monolithic-architecture-to-a-microservices-architecture.md) - - [微服务的事件驱动数据管理](./docs/micro-services/event-driven-data-management-for-microservices.md) - - [选择微服务部署策略](./docs/micro-services/choose-microservice-deployment-strategy.md) - - - Spring Cloud 微服务架构 - - [什么是微服务?微服务之间是如何独立通讯的?](/docs/micro-services/huifer-what's-microservice-how-to-communicate.md) - - Spring Cloud 和 Dubbo 有哪些区别? - - Spring Boot 和 Spring Cloud,谈谈你对它们的理解? - - 什么是服务熔断?什么是服务降级? - - 微服务的优缺点分别是什么?说一下你在项目开发中碰到的坑? - - [你所知道的微服务技术栈都有哪些?](/docs/micro-services/huifer-micro-services-technology-stack.md) - - [微服务治理策略](/docs/micro-services/huifer-micro-service-governance.md) - - Eureka 和 Zookeeper 都可以提供服务注册与发现的功能,它们有什么区别? - - [谈谈服务发现组件 Eureka 的主要调用过程?](/docs/micro-services/how-eureka-enable-service-discovery-and-service-registration.md) - -* 海量数据处理 - - 10 道经典的海量数据处理面试题 - - [如何从大量的 URL 中找出相同的 URL?](./docs/big-data/find-common-urls.md) - - [如何从大量数据中找出高频词?](./docs/big-data/find-top-100-words.md) - - [如何找出某一天访问百度网站最多的 IP?](./docs/big-data/find-top-1-ip.md) - - [如何在大量的数据中找出不重复的整数?](./docs/big-data/find-no-repeat-number.md) - - [如何在大量的数据中判断一个数是否存在?](./docs/big-data/find-a-number-if-exists.md) - - [如何查询最热门的查询串?](./docs/big-data/find-hotest-query-string.md) - - [如何统计不同电话号码的个数?](./docs/big-data/count-different-phone-numbers.md) - - [如何从 5 亿个数中找出中位数?](./docs/big-data/find-mid-value-in-500-millions.md) - - [如何按照 query 的频度排序?](./docs/big-data/sort-the-query-strings-by-counts.md) - - [如何找出排名前 500 的数?](./docs/big-data/find-rank-top-500-numbers.md)