diff --git a/.docsifytopdfrc.js b/.docsifytopdfrc.js deleted file mode 100644 index 840480c97..000000000 --- a/.docsifytopdfrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - contents: ["summary.md"], - pathToPublic: "pdf/advanced-java.pdf", - pdfOptions: "", - removeTemp: true, - emulateMedia: "screen", -}; 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/prettier.yml b/.github/workflows/prettier.yml deleted file mode 100644 index a433c20d6..000000000 --- a/.github/workflows/prettier.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Prettier - -on: - push: - branches: [main] - -jobs: - prettier: - runs-on: ubuntu-latest - if: github.repository == 'doocs/advanced-java' - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - - - name: Prettify - uses: creyD/prettier_action@v3.3 - with: - prettier_options: --write **/*.{md} - commit_message: "style: prettify code or document" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml deleted file mode 100644 index 97d4d8985..000000000 --- a/.github/workflows/rebase.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Automatic Rebase - -on: - issue_comment: - types: [created] - -jobs: - rebase: - name: Rebase - if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') - runs-on: ubuntu-latest - steps: - - name: Checkout the latest code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Automatic Rebase - uses: cirrus-actions/rebase@1.4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 bc25d9428..000000000 --- a/.github/workflows/sync.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Sync - -on: - push: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - if: github.repository == 'doocs/advanced-java' - 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@main - with: - gitee-username: yanglbme - gitee-password: ${{ secrets.GITEE_PASSWORD }} - gitee-repo: doocs/advanced-java - branch: main diff --git a/.gitignore b/.gitignore index 38c281081..58b09838b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ yarn-debug.log* yarn-error.log* dist -package-lock.json lib node_modules @@ -41,4 +40,7 @@ yarn-error.log* *.ntvs* *.njsproj *.sln -*.sw? \ No newline at end of file +*.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 c373f98b3..99266dba3 100644 --- a/README.md +++ b/README.md @@ -1,176 +1,173 @@ # 互联网 Java 工程师进阶知识完全扫盲 -[![stars](https://img.shields.io/github/stars/doocs/advanced-java?color=42b883&logo=github&style=flat-square)](https://github.com/doocs/advanced-java/stargazers) -[![forks](https://img.shields.io/github/forks/doocs/advanced-java?color=42b883&logo=github&style=flat-square)](https://github.com/doocs/advanced-java/network/members) -[![actions status](https://img.shields.io/github/workflow/status/doocs/advanced-java/Sync?color=42b883&label=sync&logo=github&style=flat-square)](https://github.com/doocs/advanced-java/actions?query=workflow%3ASync) -[![actions status](https://img.shields.io/github/workflow/status/doocs/advanced-java/Prettier?color=42b883&label=prettier&logo=github&style=flat-square)](https://github.com/doocs/advanced-java/actions?query=workflow%3APrettier) -[![license](https://img.shields.io/github/license/doocs/advanced-java?color=42b883&style=flat-square)](./LICENSE) -[![doocs](https://img.shields.io/badge/organization-join%20us-42b883?style=flat-square)](https://doocs.github.io/#/?id=how-to-join) -[![original](https://img.shields.io/badge/original-%E4%B8%AD%E5%8D%8E%E7%9F%B3%E6%9D%89-42b883?style=flat-square)](https://github.com/doocs/advanced-java) -[![notice](https://img.shields.io/badge/notice-%E7%BB%B4%E6%9D%83%E8%A1%8C%E5%8A%A8-42b883?style=flat-square)](./docs/extra-page/rights-defending-action.md) -[![wechat-group](https://img.shields.io/badge/chat-%E5%BE%AE%E4%BF%A1%E4%BA%A4%E6%B5%81-42b883?style=flat-square)](#公众号) -[![coding](https://img.shields.io/badge/leetcode-%E5%88%B7%E9%A2%98%E5%B0%8F%E9%98%9F-42b883?style=flat-square)](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) -本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)、[海量数据处理](#海量数据处理)等领域知识。我对这部分知识做了一个系统的整理,方便学习查阅。 +本项目大部分内容来自中华石杉,版权归作者所有,内容涵盖[高并发](#高并发架构)、[分布式](#分布式系统)、[高可用](#高可用架构)、[微服务](#微服务架构)、[海量数据处理](#海量数据处理)等领域知识。我们对这部分知识做了一个系统的整理,方便读者们学习查阅。 -本项目已开通 Discussions 功能。学习之前,先来看看 [Discussions 讨论区](https://github.com/doocs/advanced-java/discussions/9)的技术面试官是怎么说的吧。本项目欢迎各位开发者朋友到 Discussions 讨论区分享自己的一些想法和实践经验。也不妨 Star 关注 [doocs/advanced-java](https://github.com/doocs/advanced-java),随时追踪项目最新动态。 +我们也在全力更新算法项目!如果你在准备笔面试算法,或者想进一步提升 coding 能力,欢迎 Star 关注 [doocs/leetcode](https://github.com/doocs/leetcode) -本项目基于 Docsify 进行构建,并使用开源小工具 [Gitee Pages Action](https://github.com/yanglbme/gitee-pages-action) 实现站点的自动部署更新。目前支持以下三个站点访问: - -- 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-master-slave.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/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) -- [讲讲大数据中 TopK 问题的常用套路?](./docs/big-data/topk-problems-and-solutions.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) --- @@ -200,25 +197,19 @@ Doocs 技术社区,致力于打造一个内容完整、持续成长的互联 ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - -
- -
- 公众平台 -

+
+
- -
- 个人微信 -

+
+
-关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! +关注「**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 fb8e8f7d1..9abc72aa1 100644 --- a/docs/big-data/README.md +++ b/docs/big-data/README.md @@ -1,39 +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) --- ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - -
- -
- 公众平台 -

+
+
- -
- 个人微信 -

+
+
-关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! -
\ No newline at end of file +
diff --git a/docs/big-data/count-different-phone-numbers.md b/docs/big-data/count-different-phone-numbers.md index 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 e136167d1..7e159833e 100644 --- a/docs/big-data/find-a-number-if-exists.md +++ b/docs/big-data/find-a-number-if-exists.md @@ -1,21 +1,21 @@ -## 如何在大量的数据中判断一个数是否存在? +# 如何在大量的数据中判断一个数是否存在? -### 题目描述 +## 题目描述 给定 40 亿个不重复的没排过序的 unsigned int 型整数,然后再给定一个数,如何快速判断这个数是否在这 40 亿个整数当中? -### 解答思路 +## 解答思路 -#### 方法一:分治法 +### 方法一:分治法 依然可以用分治法解决,方法与前面类似,就不再次赘述了。 -#### 方法二:位图法 +### 方法二:位图法 由于 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 7c877d83a..699191ecd 100644 --- a/docs/big-data/find-common-urls.md +++ b/docs/big-data/find-common-urls.md @@ -1,12 +1,12 @@ -## 如何从大量的 URL 中找出相同的 URL? +# 如何从大量的 URL 中找出相同的 URL? -### 题目描述 +## 题目描述 给定 a、b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G。请找出 a、b 两个文件共同的 URL。 -### 解答思路 +## 解答思路 -#### 1. 分治策略 +### 1. 分治策略 每个 URL 占 64B,那么 50 亿个 URL 占用的空间大小约为 320GB。 @@ -20,19 +20,53 @@ 接着遍历 ai( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 bi 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。 -#### 2. 前缀树 - -一般而言,URL 的长度差距不会不大,而且前面几个字符,绝大部分相同。这种情况下,非常适合使用**字典树**(trie tree) 这种数据结构来进行存储,降低存储成本的同时,提高查询效率。 +### 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. 分而治之,进行哈希取余; 1. 对每个子文件进行 HashSet 统计。 -#### 前缀树 +### 前缀树 -1. 利用字符串的公共前缀来降低存储成本,提高查询效率。 +1. 利用字符串的公共前缀牺牲速度,来降低内存占用。 diff --git a/docs/big-data/find-hotest-query-string.md b/docs/big-data/find-hotest-query-string.md index 3e3d42a7d..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,7 +18,7 @@ 方法可行,但不是最好,下面介绍其他方法。 -#### 方法二:HashMap 法 +### 方法二:HashMap 法 虽然字符串总数比较多,但去重后不超过 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 2d240e174..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,14 +1,14 @@ -## 如何从 5 亿个数中找出中位数? +# 如何从 5 亿个数中找出中位数? -### 题目描述 +## 题目描述 从 5 亿个数中找出中位数。数据排序后,位置在最中间的数就是中位数。当样本数为奇数时,中位数为 第 `(N+1)/2` 个数;当样本数为偶数时,中位数为 第 `N/2` 个数与第 `1+N/2` 个数的均值。 -### 解答思路 +## 解答思路 如果这道题没有内存大小限制,则可以把所有数读到内存中排序后找出中位数。但是最好的排序算法的时间复杂度都为 `O(NlogN)` 。这里使用其他方法。 -#### 方法一:双堆法 +### 方法一:双堆法 维护两个堆,一个大顶堆,一个小顶堆。大顶堆中最大的数**小于等于**小顶堆中最小的数;保证这两个堆中的元素个数的差不超过 1。 @@ -53,11 +53,11 @@ class MedianFinder { } ``` -> 见 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 511054929..0fc3b7b23 100644 --- a/docs/big-data/find-no-repeat-number.md +++ b/docs/big-data/find-no-repeat-number.md @@ -1,16 +1,16 @@ -## 如何在大量的数据中找出不重复的整数? +# 如何在大量的数据中找出不重复的整数? -### 题目描述 +## 题目描述 在 2.5 亿个整数中找出不重复的整数。注意:内存不足以容纳这 2.5 亿个整数。 -### 解答思路 +## 解答思路 -#### 方法一:分治法 +### 方法一:分治法 与前面的题目方法类似,先将 2.5 亿个数划分到多个小文件,用 HashSet/HashMap 找出每个小文件中不重复的整数,再合并每个子结果,即为最终结果。 -#### 方法二:位图法 +### 方法二:位图法 **位图**,就是用一个或多个 bit 来标记某个元素对应的值,而键就是该元素。采用位作为单位来存储数据,可以大大节省存储空间。 @@ -48,16 +48,16 @@ for i in range(8): **那么对于这道题**,我们用 2 个 bit 来表示各个数字的状态: -- 00 表示这个数字没出现过; -- 01 表示这个数字出现过一次(即为题目所找的不重复整数); -- 10 表示这个数字出现了多次。 +- 00 表示这个数字没出现过; +- 01 表示这个数字出现过一次(即为题目所找的不重复整数); +- 10 表示这个数字出现了多次。 那么这 232 个整数,总共所需内存为 232\*2b=1GB。因此,当可用内存超过 1GB 时,可以采用位图法。假设内存满足位图法需求,进行下面的操作: 遍历 2.5 亿个整数,查看位图中对应的位,如果是 00,则变为 01,如果是 01 则变为 10,如果是 10 则保持不变。遍历结束后,查看位图,把对应位是 01 的整数输出即可。 -当然,本题中特别说明:**内存不足以容纳这 2.5 亿个整数**,2.5 亿个整数的内存大小为:2.5e8/1024/1024/1024=0.93G,也即是说内存不足 1G,而位图法所需要的内存大小为 1G,因此,本题并不适合用位图法解决。 +当然,本题中特别说明:**内存不足以容纳这 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 6133f6790..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 的数? -### 解答思路 +## 解答思路 对于 TopK 问题,最常用的方法是使用堆排序。对本题而言,假设数组降序排列,可以采用以下方法: @@ -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 34261baaf..6489768ee 100644 --- a/docs/big-data/find-top-100-words.md +++ b/docs/big-data/find-top-100-words.md @@ -1,10 +1,10 @@ -## 如何从大量数据中找出高频词? +# 如何从大量数据中找出高频词? -### 题目描述 +## 题目描述 有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。 -### 解答思路 +## 解答思路 由于内存限制,我们依然无法直接将大文件的所有词一次读到内存中。因此,同样可以采用**分治策略**,把一个大文件分解成多个小文件,保证每个文件的大小小于 1MB,进而直接将单个小文件读取到内存中进行处理。 @@ -16,7 +16,7 @@ 上面我们统计了每个小文件单词出现的频数。接下来,我们可以通过维护一个**小顶堆**来找出所有词中出现频数最高的 100 个。具体方法是:依次遍历每个小文件,构建一个**小顶堆**,堆大小为 100。如果遍历到的词的出现次数大于堆顶词的出现次数,则用新词替换堆顶的词,然后重新调整为**小顶堆**,遍历结束后,小顶堆上的词就是出现频数最高的 100 个词。 -### 方法总结 +## 方法总结 1. 分而治之,进行哈希取余; 2. 使用 HashMap 统计频数; diff --git a/docs/big-data/images/topk-trie.png b/docs/big-data/images/topk-trie.png index cb2cbb8d2..b14235a98 100644 Binary files a/docs/big-data/images/topk-trie.png 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 d8705a08e..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 index 6be7b5992..def80ee6f 100644 --- a/docs/big-data/topk-problems-and-solutions.md +++ b/docs/big-data/topk-problems-and-solutions.md @@ -1,16 +1,9 @@ -## 大数据中 TopK 问题的常用套路 - -> **作者 Chunel Feng,编程爱好者,阿里巴巴搜索引擎开发工程师。**

个人微信:ChunelFeng
个人博客:[一面之猿网](http://www.chunel.cn)
开源项目:[Caiss 智能相似搜索引擎](https://github.com/ChunelFeng/caiss) - - - -Doocs 社区的朋友们,大家好。我是你们的新朋友 [Chunel Feng](https://github.com/ChunelFeng)。今天想跟大家聊一些**常见的 topK 问题**。 +# 大数据中 TopK 问题的常用套路 对于海量数据到处理经常会涉及到 topK 问题。在设计数据结构和算法的时候,主要需要考虑的应该是当前算法(包括数据结构)跟给定情境(比如数据量级、数据类型)的适配程度,和当前问题最核心的瓶颈(如降低时间复杂度,还是降低空间复杂度)是什么。 首先,我们来举几个常见的 topK 问题的例子: - 1. 给定 100 个 int 数字,在其中找出最大的 10 个; 1. 给定 10 亿个 int 数字,在其中找出最大的 10 个(这 10 个数字可以无序); 1. 给定 10 亿个 int 数字,在其中找出最大的 10 个(这 10 个数字依次排序); @@ -19,11 +12,10 @@ Doocs 社区的朋友们,大家好。我是你们的新朋友 [Chunel Feng](ht 1. 给定 10 亿个 string 类型的数字,在其中找出最大的 10 个(仅需要查 1 次); 1. 给定 10 亿个 string 类型的数字,在其中找出最大的 k 个(需要反复多次查询,其中 k 是一个随机数字)。 - 上面这些问题看起来很相似,但是解决的方式却千差万别。稍有不慎,就可能使得 topK 问题成为系统的瓶颈。不过也不用太担心,接下来我会总结几种常见的解决思路,遇到问题的时候,大家把这些基础思路融会贯通并且杂糅组合,即可做到见招拆招。
-### 1. 堆排序法 +## 1. 堆排序法 这里说的是堆排序法,而不是快排或者希尔排序。虽然理论时间复杂度都是 `O(nlogn)`,但是堆排在做 topK 的时候有一个优势,就是可以维护一个仅包含 k 个数字的小顶堆(想清楚,为啥是小顶堆哦),当新加入的数字大于堆顶数字的时候,将堆顶元素剔除,并加入新的数字。 @@ -53,7 +45,7 @@ int main() { > Java 中同样提供了 PriorityQueue 的数据结构。 -### 2. 类似快排法 +## 2. 类似快排法 快排大家都知道,针对 topK 问题,可以对快排进行改进。仅对部分数据进行递归计算。比如,在 100 个数字中,找最大的 10 个,第一次循环的时候,povit 被移动到了 80 的位置,则接下来仅需要在后面的 20 个数字中找最大的 10 个即可。 @@ -111,7 +103,7 @@ int main() {
-### 3. 使用 bitmap +## 3. 使用 bitmap 有时候 topK 问题会遇到数据量过大,内存无法全部加载。这个时候,可以考虑将数据存放至 bitmap 中,方便查询。 @@ -122,7 +114,7 @@ int main() { 这种做法的优势,当然是降低了空间复杂度。不过需要注意一点,bitmap 比较适合不重复且有范围(比如,数据均在 0 ~ 10 亿之间)的数据的查询。至于有重复数据的情况,可以考虑与 hash 等结构的混用。
-### 4. 使用 hash +## 4. 使用 hash 如果遇到了查询 string 类型数据的大小,可以考虑 hash 方法。 @@ -131,25 +123,24 @@ int main() { 这种方法比较适合网址或者电话号码的查询。缺点就是如果需要多次查询的话,需要多次计算 hash,并且需要根据实际情况设计多个 hash 函数。
-### 5. 字典树 +## 5. 字典树 字典树(trie)的具体结构和查询方式,不在这里赘述了,自行百度一下就有很多。这里主要说一下优缺点。 ![](./images/topk-trie.png) - 字典树的思想,还是通过前期建立索引信息,后期可以反复多次查询,并且后期增删数据也很方便。比较适合于需要反复多次查询的情况。 比如,反复多次查询字符序(例如:z>y>...>b>a)最大的 k 个 url 这种,使用字典树把数据存储一遍,就非常适合。既减少了空间复杂度,也加速了查询效率。
-### 6. 混合查询 +## 6. 混合查询 以上几种方法,都是比较独立的方法。其实,在实际工作中,遇到更多的问题还是混合问题,这就需要我们对相关的内容,融会贯通并且做到活学活用。 我举个例子:我们的分布式服务跑在 10 台不同机器上,每台机器上部署的服务均被请求 10000 次,并且记录了个这 10000 次请求的耗时(耗时值为 int 数据),找出这 10\*10000 次请求中,从高到低的找出耗时最大的 50 个。看看这个问题,很现实吧。我们试着用上面介绍的方法,组合一下来求解。 -#### 方法一 +### 方法一 首先,对每台机器上的 10000 个做类似快排,找出每台机器上 top50 的耗时信息。此时,单机上的这 50 条数据是无序的。 @@ -157,7 +148,7 @@ int main() { 最后,对这 50 个数据做快排,从而得到最终结果。 -#### 方法二 +### 方法二 首先通过堆排,分别找出 10 台机器上耗时最高的 50 个数据,此时的这 50 个数据,已经是从大到小有序的了。 diff --git a/docs/distributed-system/README.md b/docs/distributed-system/README.md index 0b5f67225..13d3c17b5 100644 --- a/docs/distributed-system/README.md +++ b/docs/distributed-system/README.md @@ -1,59 +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) --- ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - -
- -
- 公众平台 -

+
+
- -
- 个人微信 -

+
+
-关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! +关注「**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 4679ca366..d4093fba4 100644 --- a/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md +++ b/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md @@ -1,3 +1,5 @@ +# Redis vs Zookeeper 实现分布式锁 + ## 面试题 一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高? @@ -14,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` 的过期时间,精确到毫秒级。 比如执行以下命令: @@ -253,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; - } - } + // 如果不是最小的节点,找到比自己小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); + this.waitNode = locks.get(previousLockIndex); } catch (KeeperException e) { throw new LockException(e); } catch (InterruptedException e) { @@ -332,8 +334,8 @@ public class ZooKeeperDistributedLock implements Watcher { ### 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 babf23b7f..a78c3e4db 100644 --- a/docs/distributed-system/distributed-session.md +++ b/docs/distributed-system/distributed-session.md @@ -1,3 +1,5 @@ +# 如何实现分布式 Session? + ## 面试题 集群部署时的分布式 Session 如何实现? diff --git a/docs/distributed-system/distributed-system-cap.md b/docs/distributed-system/distributed-system-cap.md index 0f9b3100e..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,17 +10,17 @@ 在理论计算机科学中,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) @@ -43,7 +45,7 @@ Eureka client 使用内置轮询负载均衡器去注册,有一个检测间隔 > 强一致性 -Zookeeper 在选举 leader 时会停止服务,只有成功选举 leader 成功后才能提供服务,选举时间较长;内部使用 paxos 选举投票机制,只有获取半数以上的投票才能成为 leader,否则重新投票,所以部署的时候最好集群节点不小于 3 的奇数个(但是谁能保证跪掉后节点也是基数个呢);Zookeeper 健康检查一般是使用 tcp 长链接,在内部网络抖动时或者对应节点阻塞时候都会变成不可用,这里还是比较危险的; +Zookeeper 在选举 leader 时会停止服务,只有成功选举 leader 成功后才能提供服务,选举时间较长;内部使用 paxos 选举投票机制,只有获取半数以上的投票才能成为 leader,否则重新投票,所以部署的时候最好集群节点不小于 3 的奇数个(但是谁能保证跪掉后节点也是奇数个呢);Zookeeper 健康检查一般是使用 tcp 长链接,在内部网络抖动时或者对应节点阻塞时候都会变成不可用,这里还是比较危险的; ### Consul diff --git a/docs/distributed-system/distributed-system-idempotency.md b/docs/distributed-system/distributed-system-idempotency.md index 4d012a586..dc6453cf4 100644 --- a/docs/distributed-system/distributed-system-idempotency.md +++ b/docs/distributed-system/distributed-system-idempotency.md @@ -1,3 +1,5 @@ +# 分布式服务接口的幂等性如何设计? + ## 面试题 分布式服务接口的幂等性如何设计(比如不能重复扣款)? @@ -22,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 adec6cd18..e69cb887f 100644 --- a/docs/distributed-system/distributed-system-interview.md +++ b/docs/distributed-system/distributed-system-interview.md @@ -1,4 +1,4 @@ -## 分布式系统面试连环炮 +# 分布式系统面试连环炮 有一些同学,之前呢主要是做传统行业,或者外包项目,一直是在那种小的公司,技术一直都搞的比较简单。他们有共同的一个问题,就是都没怎么搞过分布式系统,现在互联网公司,一般都是做分布式的系统,大家都不是做底层的分布式系统、分布式存储系统 Hadoop HDFS、分布式计算系统 Hadoop MapReduce / Spark、分布式流式计算系统 Storm。 @@ -10,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 ac0cd1ef6..45187f8c0 100644 --- a/docs/distributed-system/distributed-system-request-sequence.md +++ b/docs/distributed-system/distributed-system-request-sequence.md @@ -1,3 +1,5 @@ +# 分布式服务接口请求的顺序性如何保证? + ## 面试题 分布式服务接口请求的顺序性如何保证? diff --git a/docs/distributed-system/distributed-transaction.md b/docs/distributed-system/distributed-transaction.md index a0702d392..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 de98c560a..1d13be9e0 100644 --- a/docs/distributed-system/dubbo-load-balancing.md +++ b/docs/distributed-system/dubbo-load-balancing.md @@ -1,3 +1,5 @@ +# Dubbo 负载均衡策略和集群容错策略 + ## 面试题 dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢? @@ -8,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 进行扩展? ## 面试题剖析 @@ -47,7 +49,7 @@ dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略 一致性 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 集群容错策略 @@ -107,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 d9d06c7ed..9530f140a 100644 --- a/docs/distributed-system/dubbo-operating-principle.md +++ b/docs/distributed-system/dubbo-operating-principle.md @@ -1,3 +1,5 @@ +# Dubbo 的工作原理 + ## 面试题 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程? @@ -16,23 +18,23 @@ MQ、ES、Redis、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 c35fa93f9..ad00f7373 100644 --- a/docs/distributed-system/dubbo-rpc-design.md +++ b/docs/distributed-system/dubbo-rpc-design.md @@ -1,3 +1,5 @@ +# 设计一个类似 Dubbo 的 RPC 框架 + ## 面试题 如何自己设计一个类似 Dubbo 的 RPC 框架? @@ -6,8 +8,8 @@ 说实话,就这问题,其实就跟问你如何自己设计一个 MQ 一样的道理,就考两个: -- 你有没有对某个 rpc 框架原理有非常深入的理解。 -- 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。 +- 你有没有对某个 rpc 框架原理有非常深入的理解。 +- 你能不能从整体上来思考一下,如何设计一个 rpc 框架,考考你的系统设计能力。 ## 面试题剖析 @@ -17,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 7e5ad5fd6..b9989fe6f 100644 --- a/docs/distributed-system/dubbo-serialization-protocol.md +++ b/docs/distributed-system/dubbo-serialization-protocol.md @@ -1,3 +1,5 @@ +# Dubbo 的序列化协议 + ## 面试题 dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的? @@ -16,7 +18,7 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian ### dubbo 支持不同的通信协议 -- dubbo 协议 `dubbo://` +- dubbo 协议 `dubbo://` **默认**就是走 dubbo 协议,单一长连接,进行的是 NIO 异步通信,基于 hessian 作为序列化协议。使用的场景是:传输数据量小(每次请求在 100kb 以内),但是并发量很高,以及服务消费者机器数远大于服务提供者机器数的情况。 @@ -30,39 +32,39 @@ dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian ![dubbo-not-keep-connection](./images/dubbo-not-keep-connection.png) -- rmi 协议 `rmi://` +- rmi 协议 `rmi://` RMI 协议采用 JDK 标准的 java.rmi.\* 实现,采用阻塞式短连接和 JDK 标准序列化方式。多个短连接,适合消费者和提供者数量差不多的情况,适用于文件的传输,一般较少用。 -- hessian 协议 `hessian://` +- hessian 协议 `hessian://` Hessian 1 协议用于集成 Hessian 的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。走 hessian 序列化协议,多个短连接,适用于提供者数量比消费者数量还多的情况,适用于文件的传输,一般较少用。 -- http 协议 `http://` +- http 协议 `http://` 基于 HTTP 表单的远程调用协议,采用 Spring 的 HttpInvoker 实现。走表单序列化。 -- thrift 协议 `thrift://` +- thrift 协议 `thrift://` 当前 dubbo 支持的 thrift 协议是对 thrift 原生协议的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等。 -- webservice `webservice://` +- webservice `webservice://` 基于 WebService 的远程调用协议,基于 Apache CXF 的 frontend-simple 和 transports-http 实现。走 SOAP 文本序列化。 -- memcached 协议 `memcached://` +- memcached 协议 `memcached://` 基于 memcached 实现的 RPC 协议。 -- redis 协议 `redis://` +- redis 协议 `redis://` 基于 Redis 实现的 RPC 协议。 -- rest 协议 `rest://` +- rest 协议 `rest://` 基于标准的 Java REST API——JAX-RS 2.0(Java API for RESTful Web Services 的简写)实现的 REST 调用支持。 -- gPRC 协议 `grpc://` +- gPRC 协议 `grpc://` Dubbo 自 2.7.5 版本开始支持 gRPC 协议,对于计划使用 HTTP/2 通信,或者想利用 gRPC 带来的 Stream、反压、Reactive 编程等能力的开发者来说, 都可以考虑启用 gRPC 协议。 @@ -74,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 a84f505c2..242d21b7b 100644 --- a/docs/distributed-system/dubbo-service-management.md +++ b/docs/distributed-system/dubbo-service-management.md @@ -1,3 +1,5 @@ +# 如何基于 Dubbo 进行服务治理 + ## 面试题 如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试? @@ -28,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%) ### 服务降级 @@ -115,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 16b8d2246..8ca6d7145 100644 --- a/docs/distributed-system/dubbo-spi.md +++ b/docs/distributed-system/dubbo-spi.md @@ -1,3 +1,5 @@ +# Dubbo 的 SPI 机制 + ## 面试题 dubbo 的 spi 思想是什么? diff --git a/docs/distributed-system/why-dubbo.md b/docs/distributed-system/why-dubbo.md index d86fca3a4..0c98a54f0 100644 --- a/docs/distributed-system/why-dubbo.md +++ b/docs/distributed-system/why-dubbo.md @@ -1,3 +1,5 @@ +# 为什么要拆分系统? + ## 面试题 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗? diff --git a/docs/distributed-system/zookeeper-application-scenarios.md b/docs/distributed-system/zookeeper-application-scenarios.md index 17a411161..248a0c1f7 100644 --- a/docs/distributed-system/zookeeper-application-scenarios.md +++ b/docs/distributed-system/zookeeper-application-scenarios.md @@ -1,3 +1,5 @@ +# Zookeeper 的使用场景 + ## 面试题 zookeeper 都有哪些使用场景? @@ -14,10 +16,10 @@ zookeeper 都有哪些使用场景? 大致来说,zookeeper 的使用场景如下,我就举几个简单的,大家能说几个就好了: -- 分布式协调 -- 分布式锁 -- 元数据/配置信息管理 -- HA 高可用性 +- 分布式协调 +- 分布式锁 +- 元数据/配置信息管理 +- HA 高可用性 ### 分布式协调 diff --git a/docs/extra-page/README.md b/docs/extra-page/README.md index db8627a14..fd7d840ab 100644 --- a/docs/extra-page/README.md +++ b/docs/extra-page/README.md @@ -1,38 +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) --- ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - -
- -
- 公众平台 -

+
+
- -
- 个人微信 -

+
+
-关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! +关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! -
\ 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 e579005ec..000000000 --- a/docs/extra-page/cover.md +++ /dev/null @@ -1,8 +0,0 @@ -[![logo](https://cdn.jsdelivr.net/gh/doocs/advanced-java@main/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/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/rights-defending-action.md b/docs/extra-page/rights-defending-action.md index f89e851a7..e22eb11a5 100644 --- a/docs/extra-page/rights-defending-action.md +++ b/docs/extra-page/rights-defending-action.md @@ -32,19 +32,16 @@ | # | 文章 | 抄袭者 | | --- | ---- | ------ | - ### 头条号 | # | 文章 | 抄袭者 | | --- | ---- | ------ | - ### 掘金 | # | 文章 | 抄袭者 | | --- | ---- | ------ | - ### 知乎 | # | 文章 | 抄袭者 | 备注 | diff --git a/docs/extra-page/subscriptions-for-doocs.md b/docs/extra-page/subscriptions-for-doocs.md index 7b8439ad7..d4a058056 100644 --- a/docs/extra-page/subscriptions-for-doocs.md +++ b/docs/extra-page/subscriptions-for-doocs.md @@ -1,9 +1,9 @@ -# Doocs 开源社区的公众号来了 +# Doocs 的公众号来了 -GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs 开源社区**”,专注于挖掘 IT 技术知识,助力开发者成长。 +GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs**”,专注于挖掘 IT 技术知识,助力开发者成长。
-
+
来成为公众号的首批粉丝吗?一定不会辜负你们的期待。 @@ -12,12 +12,12 @@ GitHub 开源社区 Doocs 旗下唯一公众号“**Doocs 开源社区**”, 微信公众号的**原创保护功能**做得比较好,内容阅读也比较方便。 -后续我的所有原创文章,将第一时间通过微信公众号“Doocs 开源社区”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。 +后续我的所有原创文章,将第一时间通过微信公众号“Doocs”发布。如果其他公众号号主希望转载内容,请在公众号聊天窗口处发消息与我说明,或者直接添加我的个人微信(YLB0109)进行交流。 ## 公众号的定位是怎样的? -- **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。 -- **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。 +- **内容高质**:不随意从网上复制粘贴一些文章,即便是转载的技术文章,也要确保质量。 +- **定期更新**:尽量在每周输出 1-2 篇文章,保证一定的更新频率。
@@ -27,21 +27,21 @@ 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) 后续将推出一系列原创干货文章,敬请期待。 diff --git a/docs/high-availability/README.md b/docs/high-availability/README.md index 96fbbd059..296b2dfec 100644 --- a/docs/high-availability/README.md +++ b/docs/high-availability/README.md @@ -1,58 +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) ## 降级 -- 如何进行降级? +- 如何进行降级? --- ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - -
- -
- 公众平台 -

+
+
- -
- 个人微信 -

+
+
-关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! +关注「**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 39753a995..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,6 @@ -## 电商网站的商品详情页系统架构 +# 电商网站的商品详情页系统架构 -### 小型电商网站的商品详情页系统架构 +## 小型电商网站的商品详情页系统架构 小型电商网站的页面展示采用页面全量静态化的思想。数据库中存放了所有的商品信息,页面静态化系统,将数据填充进静态模板中,形成静态化页面,推入 Nginx 服务器。用户浏览网站页面时,取用一个已经静态化好的 html 页面,直接返回回去,不涉及任何的业务逻辑处理。 @@ -10,11 +10,11 @@ ```html - - 商品名称:#{productName}
- 商品价格:#{productPrice}
- 商品描述:#{productDesc} - + + 商品名称:#{productName}
+ 商品价格:#{productPrice}
+ 商品描述:#{productDesc} + ``` @@ -24,7 +24,7 @@ **坏处**在于,仅仅适用于一些小型的网站,比如页面的规模在几十到几万不等。对于一些大型的电商网站,亿级数量的页面,你说你每次页面模板修改了,都需要将这么多页面全量静态化,靠谱吗?每次渲染花个好几天时间,那你整个网站就废掉了。 -### 大型电商网站的商品详情页系统架构 +## 大型电商网站的商品详情页系统架构 大型电商网站商品详情页的系统设计中,当商品数据发生变更时,会将变更消息压入 MQ 消息队列中。**缓存服务**从消息队列中消费这条消息时,感知到有数据发生变更,便通过调用数据服务接口,获取变更后的数据,然后将整合好的数据推送至 redis 中。Nginx 本地缓存的数据是有一定的时间期限的,比如说 10 分钟,当数据过期之后,它就会从 redis 获取到最新的缓存数据,并且缓存到自己本地。 @@ -38,4 +38,4 @@ 如果系统访问量很高,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 db338625f..593b67a66 100644 --- a/docs/high-availability/hystrix-circuit-breaker.md +++ b/docs/high-availability/hystrix-circuit-breaker.md @@ -1,4 +1,4 @@ -## 深入 Hystrix 断路器执行原理 +# 深入 Hystrix 断路器执行原理 ### 状态机 @@ -80,9 +80,9 @@ Hystrix 并不是只要有一条请求经过就去统计,而是将整个滑动 在 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,**模拟服务执行异常**了。 diff --git a/docs/high-availability/hystrix-execution-isolation.md b/docs/high-availability/hystrix-execution-isolation.md index 68f839489..e96da1c02 100644 --- a/docs/high-availability/hystrix-execution-isolation.md +++ b/docs/high-availability/hystrix-execution-isolation.md @@ -1,13 +1,13 @@ -## Hystrix 隔离策略细粒度控制 +# Hystrix 隔离策略细粒度控制 Hystrix 实现资源隔离,有两种策略: -- 线程池隔离 -- 信号量隔离 +- 线程池隔离 +- 信号量隔离 对资源隔离这一块东西,其实可以做一定细粒度的一些控制。 -### execution.isolation.strategy +## execution.isolation.strategy 指定了 HystrixCommand.run() 的资源隔离策略:`THREAD` or `SEMAPHORE`,一种基于线程池,一种基于信号量。 @@ -29,7 +29,7 @@ HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolat 而使用信号量的场景,通常是针对超大并发量的场景下,每个服务实例每秒都几百的 `QPS`,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护。一般用信号量常见于那种基于纯内存的一些业务逻辑服务,而不涉及到任何网络访问请求。 -### command key & command group +## command key & command group 我们使用线程池隔离,要怎么对**依赖服务**、**依赖服务接口**、**线程池**三者做划分呢? @@ -47,7 +47,7 @@ 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 绑定在一起。 @@ -64,7 +64,7 @@ public CommandHelloWorld(String name) { } ``` -### command key & command group & command thread pool +## command key & command group & command thread pool **command key** ,代表了一类 command,一般来说,代表了下游依赖服务的某个接口。 @@ -84,7 +84,7 @@ command key -> 自己的 thread pool key 说白点,就是说如果你的 command key 要用自己的线程池,可以定义自己的 thread pool key,就 ok 了。 -### coreSize +## coreSize 设置线程池的大小,默认是 10。一般来说,用这个默认的 10 个线程大小就够了。 @@ -92,7 +92,7 @@ command key -> 自己的 thread pool key HystrixThreadPoolProperties.Setter().withCoreSize(int value); ``` -### queueSizeRejectionThreshold +## queueSizeRejectionThreshold 如果说线程池中的 10 个线程都在工作中,没有空闲的线程来做其它的事情,此时再有请求过来,会先进入队列积压。如果说队列积压满了,再有请求过来,就直接 reject,拒绝请求,执行 fallback 降级的逻辑,快速返回。 @@ -104,7 +104,7 @@ HystrixThreadPoolProperties.Setter().withCoreSize(int value); HystrixThreadPoolProperties.Setter().withQueueSizeRejectionThreshold(int value); ``` -### execution.isolation.semaphore.maxConcurrentRequests +## execution.isolation.semaphore.maxConcurrentRequests 设置使用 SEMAPHORE 隔离策略的时候允许访问的最大并发量,超过这个最大并发量,请求直接被 reject。 diff --git a/docs/high-availability/hystrix-fallback.md b/docs/high-availability/hystrix-fallback.md index fa6860212..52bbe1061 100644 --- a/docs/high-availability/hystrix-fallback.md +++ b/docs/high-availability/hystrix-fallback.md @@ -1,19 +1,19 @@ -## 基于本地缓存的 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() 方法。 @@ -23,7 +23,7 @@ Hystrix 出现以下四种情况,都会去调用 fallback 降级机制: 假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。 -### 步骤一:本地缓存获取数据 +## 步骤一:本地缓存获取数据 本地获取品牌名称的代码大致如下。 @@ -32,7 +32,6 @@ Hystrix 出现以下四种情况,都会去调用 fallback 降级机制: * 品牌名称本地缓存 * */ - public class BrandCache { private static Map brandMap = new HashMap<>(); @@ -52,7 +51,7 @@ public class BrandCache { } ``` -### 步骤二:实现 GetBrandNameCommand +## 步骤二:实现 GetBrandNameCommand 在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。 @@ -96,7 +95,7 @@ public class GetBrandNameCommand extends HystrixCommand { `FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。 -### 步骤三:CacheController 调用接口 +## 步骤三:CacheController 调用接口 在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。 diff --git a/docs/high-availability/hystrix-introduction.md b/docs/high-availability/hystrix-introduction.md index 5123527fe..483f4226a 100644 --- a/docs/high-availability/hystrix-introduction.md +++ b/docs/high-availability/hystrix-introduction.md @@ -1,8 +1,8 @@ -## 用 Hystrix 构建高可用服务架构 +# 用 Hystrix 构建高可用服务架构 参考 [Hystrix Home](https://github.com/Netflix/Hystrix/wiki#what)。 -### Hystrix 是什么? +## Hystrix 是什么? 在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是**依赖服务**,有的时候某些依赖服务出现故障也是很正常的。 @@ -12,7 +12,7 @@ Hystrix 通过将依赖服务进行**资源隔离**,进而阻止某个依赖 **总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。** -### Hystrix 的历史 +## Hystrix 的历史 Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外的优酷或者爱奇艺之类的视频网站)的 API 团队从 2011 年开始做一些提升系统可用性和稳定性的工作,Hystrix 就是从那时候开始发展出来的。 @@ -22,13 +22,13 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外 [2018 年 11 月,Hystrix 在其 Github 主页宣布,不再开放新功能,推荐开发者使用其他仍然活跃的开源项目](https://github.com/Netflix/Hystrix/blob/master/README.md#hystrix-status)。维护模式的转变绝不意味着 Hystrix 不再有价值。相反,Hystrix 激发了很多伟大的想法和项目,我们高可用的这一块知识还是会针对 Hystrix 进行讲解。 -### Hystrix 的设计原则 +## Hystrix 的设计原则 -- 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。 -- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。 -- 提供 `fail-fast`(快速失败)和快速恢复的支持。 -- 提供 fallback 优雅降级的支持。 -- 支持近实时的监控、报警以及运维操作。 +- 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。 +- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。 +- 提供 `fail-fast`(快速失败)和快速恢复的支持。 +- 提供 fallback 优雅降级的支持。 +- 支持近实时的监控、报警以及运维操作。 举个栗子。 @@ -40,12 +40,12 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外 Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线程调用服务 C。当此 40 个线程被 hang 住时,其它 60 个线程依然能正常调用工作。从而确保整个系统不会被拖垮。 -### Hystrix 更加细节的设计原则 +## Hystrix 更加细节的设计原则 -- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。 -- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。 -- 提供 fallback 降级机制来应对故障。 -- 使用资源隔离技术,比如 `bulkhead`(舱壁隔离技术)、`swimlane`(泳道技术)、`circuit breaker`(断路技术)来限制任何一个依赖服务的故障的影响。 -- 通过近实时的统计/监控/报警功能,来提高故障发现的速度。 -- 通过近实时的属性和配置**热修改**功能,来提高故障处理和恢复的速度。 -- 保护依赖服务调用的所有故障情况,而不仅仅只是网络故障情况。 +- 阻止任何一个依赖服务耗尽所有的资源,比如 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 22d200040..984fbf586 100644 --- a/docs/high-availability/hystrix-process.md +++ b/docs/high-availability/hystrix-process.md @@ -1,10 +1,10 @@ -## 深入 Hystrix 执行时内部原理 +# 深入 Hystrix 执行时内部原理 前面我们了解了 Hystrix 最基本的支持高可用的技术:**资源隔离** + **限流**。 -- 创建 command; -- 执行这个 command; -- 配置这个 command 对应的 group 和线程池。 +- 创建 command; +- 执行这个 command; +- 配置这个 command 对应的 group 和线程池。 这里,我们要讲一下,你开始执行这个 command,调用了这个 command 的 execute() 方法之后,Hystrix 底层的执行流程和步骤以及原理是什么。 @@ -14,12 +14,12 @@ ![hystrix-process](./images/new-hystrix-process.jpg) -### 步骤一:创建 command +## 步骤一:创建 command 一个 HystrixCommand 或 HystrixObservableCommand 对象,代表了对某个依赖服务发起的一次请求或者调用。创建的时候,可以在构造函数中传入任何需要的参数。 -- HystrixCommand 主要用于仅仅会返回一个结果的调用。 -- HystrixObservableCommand 主要用于可能会返回多条结果的调用。 +- HystrixCommand 主要用于仅仅会返回一个结果的调用。 +- HystrixObservableCommand 主要用于可能会返回多条结果的调用。 ```java // 创建 HystrixCommand @@ -29,7 +29,7 @@ HystrixCommand hystrixCommand = new HystrixCommand(arg1, arg2); HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand(arg1, arg2); ``` -### 步骤二:调用 command 执行方法 +## 步骤二:调用 command 执行方法 执行 command,就可以发起一次对依赖服务的调用。 @@ -37,10 +37,10 @@ HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand 其中 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(); @@ -69,32 +69,32 @@ 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(); @@ -129,40 +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 大步骤。 +- 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 bd5e963be..2c196022f 100644 --- a/docs/high-availability/hystrix-request-cache.md +++ b/docs/high-availability/hystrix-request-cache.md @@ -1,4 +1,4 @@ -## 基于 request cache 请求缓存技术优化批量商品数据查询接口 +# 基于 request cache 请求缓存技术优化批量商品数据查询接口 Hystrix command 执行时 8 大步骤第三步,就是检查 Request cache 是否有缓存。 @@ -20,7 +20,7 @@ HystrixCommand 和 HystrixObservableCommand 都可以指定一个缓存 key, 我们对批量查询商品数据的接口,可以用 request cache 做一个优化,就是说一次请求,就是一次 request context,对相同的商品查询只执行一次,其余重复的都走 request cache。 -### 实现 Hystrix 请求上下文过滤器并注册 +## 实现 Hystrix 请求上下文过滤器并注册 定义 HystrixRequestContextFilter 类,实现 Filter 接口。 @@ -73,7 +73,7 @@ public class EshopApplication { } ``` -### command 重写 getCacheKey() 方法 +## command 重写 getCacheKey() 方法 在 GetProductInfoCommand 中,重写 getCacheKey() 方法,这样的话,每一次请求的结果,都会放在 Hystrix 请求上下文中。下一次同一个 productId 的数据请求,直接取缓存,无须再调用 run() 方法。 @@ -122,7 +122,7 @@ public class GetProductInfoCommand extends HystrixCommand { 这里写了一个 flushCache() 方法,用于我们开发手动删除缓存。 -### controller 调用 command 查询商品信息 +## controller 调用 command 查询商品信息 在一次 web 请求上下文中,传入商品 id 列表,查询多条商品数据信息。对于每个 productId,都创建一个 command。 @@ -153,7 +153,7 @@ public class CacheController { } ``` -### 发起请求 +## 发起请求 调用接口,查询多个商品的信息。 @@ -177,7 +177,7 @@ http://localhost:8080/getProductInfos?productIds=1,1,1,2,2,5 第一次查询 productId=1 的数据,会调用接口进行查询,不是从缓存中取结果。而随后再出现查询 productId=1 的请求,就直接取缓存了,这样的话,效率明显高很多。 -### 删除缓存 +## 删除缓存 我们写一个 UpdateProductInfoCommand,在更新商品信息之后,手动调用之前写的 flushCache(),手动将缓存删除。 diff --git a/docs/high-availability/hystrix-semphore-isolation.md b/docs/high-availability/hystrix-semphore-isolation.md index 23be97894..13d5cfb0a 100644 --- a/docs/high-availability/hystrix-semphore-isolation.md +++ b/docs/high-availability/hystrix-semphore-isolation.md @@ -1,23 +1,23 @@ -## 基于 Hystrix 信号量机制实现资源隔离 +# 基于 Hystrix 信号量机制实现资源隔离 Hystrix 里面核心的一项功能,其实就是所谓的**资源隔离**,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话,就可能导致服务崩溃,甚至说这种故障会不断蔓延。 Hystrix 实现资源隔离,主要有两种技术: -- 线程池 -- 信号量 +- 线程池 +- 信号量 默认情况下,Hystrix 使用线程池模式。 前面已经说过线程池技术了,这一小节就来说说信号量机制实现资源隔离,以及这两种技术的区别与具体应用场景。 -### 信号量机制 +## 信号量机制 信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。 ![hystrix-semphore](./images/hystrix-semphore.png) -### 线程池与信号量区别 +## 线程池与信号量区别 线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。 @@ -27,10 +27,10 @@ Hystrix 实现资源隔离,主要有两种技术: **适用场景**: -- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。 -- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。 +- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。 +- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。 -### 信号量简单 Demo +## 信号量简单 Demo 业务背景里,比较适合信号量的是什么场景呢? diff --git a/docs/high-availability/hystrix-thread-pool-current-limiting.md b/docs/high-availability/hystrix-thread-pool-current-limiting.md index 760c1967a..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,4 @@ -## 深入 Hystrix 线程池隔离与接口限流 +# 深入 Hystrix 线程池隔离与接口限流 前面讲了 Hystrix 的 request cache 请求缓存、fallback 优雅降级、circuit breaker 断路器快速熔断,这一讲,我们来详细说说 Hystrix 的线程池隔离与接口限流。 @@ -8,7 +8,7 @@ Hystrix 通过判断线程池或者信号量是否已满,超出容量的请求 限流是限制对后端的服务的访问量,比如说你对 MySQL、Redis、Zookeeper 以及其它各种后端中间件的资源的访问的限制,其实是为了避免过大的流量直接打死后端的服务。 -### 线程池隔离技术的设计 +## 线程池隔离技术的设计 Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进行资源隔离,进而避免任何外部依赖的故障导致本服务崩溃。 @@ -18,39 +18,39 @@ Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进 Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。 -### Hystrix 应用线程池机制的场景 +## Hystrix 应用线程池机制的场景 -- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。 -- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。 -- client 调用库随时会变更。 -- client 调用库随时可能会增加新的网络请求的逻辑。 -- client 调用库可能会包含诸如自动重试、数据解析、内存中缓存等逻辑。 -- client 调用库一般都对调用者来说是个黑盒,包括实现细节、网络访问、默认配置等等。 -- 在真实的生产环境中,经常会出现调用者,突然间惊讶的发现,client 调用库发生了某些变化。 -- 即使 client 调用库没有改变,依赖服务本身可能有会发生逻辑上的变化。 -- 有些依赖的 client 调用库可能还会拉取其他的依赖库,而且可能那些依赖库配置的不正确。 -- 大多数网络请求都是同步调用的。 -- 调用失败和延迟,也有可能会发生在 client 调用库本身的代码中,不一定就是发生在网络请求中。 +- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。 +- 每个后端依赖服务都会提供它自己的 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 技术来实现对某个依赖服务的并发访问量的限制,而不是通过线程池/队列的大小来限制流量。 @@ -58,15 +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 相关参数没有设置的话,等待队列是处于关闭的状态。 @@ -165,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 6862ec370..735e741f4 100644 --- a/docs/high-availability/hystrix-thread-pool-isolation.md +++ b/docs/high-availability/hystrix-thread-pool-isolation.md @@ -1,4 +1,4 @@ -## 基于 Hystrix 线程池技术实现资源隔离 +# 基于 Hystrix 线程池技术实现资源隔离 [上一讲](./e-commerce-website-detail-page-architecture.md)提到,如果从 Nginx 开始,缓存都失效了,Nginx 会直接通过缓存服务调用商品服务获取最新商品数据(我们基于电商项目做个讨论),有可能出现调用延时而把缓存服务资源耗尽的情况。这里,我们就来说说,怎么通过 Hystrix 线程池技术实现资源隔离。 @@ -6,7 +6,7 @@ Hystrix 进行资源隔离,其实是提供了一个抽象,叫做 Command。这也是 Hystrix 最最基本的资源隔离技术。 -### 利用 HystrixCommand 获取单条数据 +## 利用 HystrixCommand 获取单条数据 我们通过将调用商品服务的操作封装在 HystrixCommand 中,限定一个 key,比如下面的 `GetProductInfoCommandGroup`,在这里我们可以简单认为这是一个线程池,每次调用商品服务,就只会用该线程池中的资源,不会再去用其它线程资源了。 @@ -47,7 +47,7 @@ public String getProductInfo(Long productId) { 上面执行的是 execute() 方法,其实是同步的。也可以对 command 调用 queue() 方法,它仅仅是将 command 放入线程池的一个等待队列,就立即返回,拿到一个 Future 对象,后面可以继续做其它一些事情,然后过一段时间对 Future 调用 get() 方法获取数据。这是异步的。 -### 利用 HystrixObservableCommand 批量获取数据 +## 利用 HystrixObservableCommand 批量获取数据 只要是获取商品数据,全部都绑定到同一个线程池里面去,我们通过 HystrixObservableCommand 的一个线程去执行,而在这个线程里面,批量把多个 productId 的 productInfo 拉回来。 diff --git a/docs/high-availability/hystrix-timeout.md b/docs/high-availability/hystrix-timeout.md index 91a367c1a..237897612 100644 --- a/docs/high-availability/hystrix-timeout.md +++ b/docs/high-availability/hystrix-timeout.md @@ -1,4 +1,4 @@ -## 基于 timeout 机制为服务接口调用超时提供安全保护 +# 基于 timeout 机制为服务接口调用超时提供安全保护 一般来说,在调用依赖服务的接口的时候,比较常见的一个问题就是**超时**。超时是在一个复杂的分布式系统中,导致系统不稳定,或者系统抖动。出现大量超时,线程资源会被 hang 死,从而导致吞吐量大幅度下降,甚至服务崩溃。 @@ -12,7 +12,7 @@ Peter Steiner 说过,"[On the Internet, nobody knows you're a dog](https://en. 如果你不对各种依赖服务接口的调用做超时控制,来给你的服务提供安全保护措施,那么很可能你的服务就被各种垃圾的依赖服务的性能给拖死了。大量的接口调用很慢,大量的线程被卡死。如果你做了资源的隔离,那么也就是线程池的线程被卡死,但其实我们可以做超时控制,没必要让它们全卡死。 -### TimeoutMilliseconds +## TimeoutMilliseconds 在 Hystrix 中,我们可以手动设置 timeout 时长,如果一个 command 运行时间超过了设定的时长,那么就被认为是 timeout,然后 Hystrix command 标识为 timeout,同时执行 fallback 降级逻辑。 @@ -23,7 +23,7 @@ HystrixCommandProperties.Setter() ..withExecutionTimeoutInMilliseconds(int) ``` -### TimeoutEnabled +## TimeoutEnabled 这个参数用于控制是否要打开 timeout 机制,默认值是 true。 diff --git a/docs/high-availability/images/BRP.jpg b/docs/high-availability/images/BRP.jpg index a3c4602a0..148bea3db 100644 Binary files a/docs/high-availability/images/BRP.jpg and b/docs/high-availability/images/BRP.jpg differ diff --git a/docs/high-availability/images/Homogenizer-mode.jpg b/docs/high-availability/images/Homogenizer-mode.jpg index ecf35bf92..90aff0267 100644 Binary files a/docs/high-availability/images/Homogenizer-mode.jpg and b/docs/high-availability/images/Homogenizer-mode.jpg differ diff --git a/docs/high-availability/images/Slow-Start-Preheating-Mode.jpg b/docs/high-availability/images/Slow-Start-Preheating-Mode.jpg index 9a3af1a40..29825ce18 100644 Binary files a/docs/high-availability/images/Slow-Start-Preheating-Mode.jpg and b/docs/high-availability/images/Slow-Start-Preheating-Mode.jpg differ diff --git a/docs/high-availability/images/hystrix-circuit-breaker-state-machine.png b/docs/high-availability/images/hystrix-circuit-breaker-state-machine.png index 9f544b86b..f821bad9e 100644 Binary files a/docs/high-availability/images/hystrix-circuit-breaker-state-machine.png and b/docs/high-availability/images/hystrix-circuit-breaker-state-machine.png differ diff --git a/docs/high-availability/images/new-hystrix-process.jpg b/docs/high-availability/images/new-hystrix-process.jpg index 1236691ba..0a4e59afc 100644 Binary files a/docs/high-availability/images/new-hystrix-process.jpg and b/docs/high-availability/images/new-hystrix-process.jpg differ diff --git a/docs/high-availability/sentinel-vs-hystrix.md b/docs/high-availability/sentinel-vs-hystrix.md index b27c8aa04..931ce3f3d 100644 --- a/docs/high-availability/sentinel-vs-hystrix.md +++ b/docs/high-availability/sentinel-vs-hystrix.md @@ -14,10 +14,10 @@ Sentinel 项目地址:https://github.com/alibaba/Sentinel 而 Sentinel 的侧重点在于: -- 多样化的流量控制 -- 熔断降级 -- 系统负载保护 -- 实时监控和控制台 +- 多样化的流量控制 +- 熔断降级 +- 系统负载保护 +- 实时监控和控制台 两者解决的问题还是有比较大的不同的,下面我们来具体对比一下。 @@ -27,11 +27,11 @@ Sentinel 项目地址:https://github.com/alibaba/Sentinel 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 并即时生效。 @@ -71,12 +71,12 @@ Sentinel 可以针对不同的调用关系,以不同的运行指标(如 QPS Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自动将流量调整成合适的形状。常用的有: -- **直接拒绝模式**:即超出的请求直接拒绝。 -- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。 - ![Slow-Start-Preheating-Mode](./images/Slow-Start-Preheating-Mode.jpg) +- **直接拒绝模式**:即超出的请求直接拒绝。 +- **慢启动预热模式**:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。 + ![Slow-Start-Preheating-Mode](./images/Slow-Start-Preheating-Mode.jpg) -- **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。 - ![Homogenizer-mode](./images/Homogenizer-mode.jpg) +- **匀速器模式**:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。 + ![Homogenizer-mode](./images/Homogenizer-mode.jpg) 目前 Sentinel 对异步调用链路的支持还不是很好,后续版本会着重改善支持异步调用。 diff --git a/docs/high-concurrency/README.md b/docs/high-concurrency/README.md index 8581dc50e..b80d8922d 100644 --- a/docs/high-concurrency/README.md +++ b/docs/high-concurrency/README.md @@ -1,75 +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) -- [有了解过 Redis rehash 的过程吗?](/docs/high-concurrency/redis-rehash.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 页精华),学习更加方便! +关注「**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 f6dc6f0f1..51d9bbedc 100644 --- a/docs/high-concurrency/database-shard-dynamic-expand.md +++ b/docs/high-concurrency/database-shard-dynamic-expand.md @@ -1,3 +1,5 @@ +# 动态扩缩容方案 + ## 面试题 如何设计可以动态扩容缩容的分库分表方案? @@ -6,12 +8,12 @@ 对于分库分表来说,主要是面对以下问题: -- 选择一个数据库中间件,调研、学习、测试; -- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表; -- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写; -- 完成单库单表到分库分表的**迁移**,双写方案; -- 线上系统开始基于分库分表对外提供服务; -- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢? +- 选择一个数据库中间件,调研、学习、测试; +- 设计你的分库分表的一个方案,你要分成多少个库,每个库分成多少个表,比如 3 个库,每个库 4 个表; +- 基于选择好的数据库中间件,以及在测试环境建立好的分库分表的环境,然后测试一下能否正常进行分库分表的读写; +- 完成单库单表到分库分表的**迁移**,双写方案; +- 线上系统开始基于分库分表对外提供服务; +- 扩容了,扩容成 6 个库,每个库需要 12 个表,你怎么来增加更多库和表呢? 这个是你必须面对的一个事儿,就是你已经弄好分库分表方案了,然后一堆库和表都建好了,基于分库分表中间件的代码开发啥的都好了,测试都 ok 了,数据能均匀分布到各个库和各个表里去,而且接着你还通过双写的方案咔嚓一下上了系统,已经直接基于分库分表方案在搞了。 @@ -35,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 个库。 @@ -45,7 +47,7 @@ 谈分库分表的扩容,**第一次分库分表,就一次性给他分个够**,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 (表) | | ------- | ------------ | ----------------- | @@ -56,7 +58,7 @@ 刚开始的时候,这个库可能就是逻辑库,建在一个数据库上的,就是一个 MySQL 服务器可能建了 n 个库,比如 32 个库。后面如果要拆分,就是不断在库和 MySQL 服务器之间做迁移就可以了。然后系统配合改一下配置即可。 -比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表。因为最多是 1024 个表。 +比如说最多可以扩展到 32 个数据库服务器,每个数据库服务器是一个库。如果还是不够?最多可以扩展到 1024 个数据库服务器,每个数据库服务器上面一个库一个表,那么最多是 1024 个表。 这么搞,是不用自己写代码做数据迁移的,都交给 DBA 来搞好了,但是 DBA 确实是需要做一些库表迁移的工作,但是总比你自己写代码,然后抽数据导数据来的效率高得多吧。 @@ -64,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 76a4a48f2..ffba27349 100644 --- a/docs/high-concurrency/database-shard-global-id-generate.md +++ b/docs/high-concurrency/database-shard-global-id-generate.md @@ -1,3 +1,5 @@ +# 主键 ID 如何处理? + ## 面试题 分库分表之后,id 主键如何处理? @@ -48,10 +50,10 @@ 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 diff --git a/docs/high-concurrency/database-shard-method.md b/docs/high-concurrency/database-shard-method.md index 4e1283716..1430be928 100644 --- a/docs/high-concurrency/database-shard-method.md +++ b/docs/high-concurrency/database-shard-method.md @@ -1,3 +1,5 @@ +# 分库分表如何平滑过渡? + ## 面试题 现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上? diff --git a/docs/high-concurrency/database-shard.md b/docs/high-concurrency/database-shard.md index d523dff67..94e274764 100644 --- a/docs/high-concurrency/database-shard.md +++ b/docs/high-concurrency/database-shard.md @@ -1,3 +1,5 @@ +# 为什么要分库分表? + ## 面试题 为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的? @@ -50,11 +52,11 @@ 比较常见的包括: -- Cobar -- TDDL -- Atlas -- Sharding-jdbc -- Mycat +- Cobar +- TDDL +- Atlas +- Sharding-jdbc +- Mycat #### Cobar @@ -106,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 2482dbfe4..60f6c5b5c 100644 --- a/docs/high-concurrency/es-architecture.md +++ b/docs/high-concurrency/es-architecture.md @@ -1,3 +1,5 @@ +# ES 的分布式架构原理 + ## 面试题 ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)? @@ -16,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 大部分字段是一样的,少部分字段是不一样的。 diff --git a/docs/high-concurrency/es-introduction.md b/docs/high-concurrency/es-introduction.md index e887c08d9..0fc115864 100644 --- a/docs/high-concurrency/es-introduction.md +++ b/docs/high-concurrency/es-introduction.md @@ -1,12 +1,14 @@ +# 搜索引擎介绍 + ## Lucene 和 ES 的前世今生 Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。 ElasticSearch 基于 Lucene,隐藏了 lucene 的复杂性,提供了简单易用的 RESTful api / Java api 接口(另外还有其他语言的 api 接口)。 -- 分布式的文档存储引擎 -- 分布式的搜索引擎和分析引擎 -- 分布式,支持 PB 级数据 +- 分布式的文档存储引擎 +- 分布式的搜索引擎和分析引擎 +- 分布式,支持 PB 级数据 ## ES 的核心概念 @@ -14,8 +16,8 @@ ElasticSearch 基于 Lucene,隐藏了 lucene 的复杂性,提供了简单易 近实时,有两层意思: -- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s) -- 基于 ES 执行搜索和分析可以达到秒级 +- 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s) +- 基于 ES 执行搜索和分析可以达到秒级 ### Cluster 集群 @@ -31,17 +33,17 @@ Node 是集群中的一个节点,节点也有一个名称,默认是随机分 ```json { - "product_id": "1", - "product_name": "iPhone X", - "product_desc": "苹果手机", - "category_id": "2", - "category_name": "电子产品" + "product_id": "1", + "product_name": "iPhone X", + "product_desc": "苹果手机", + "category_id": "2", + "category_name": "电子产品" } ``` ### Index -索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 ducument。 +索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 document。 ### Type diff --git a/docs/high-concurrency/es-optimizing-query-performance.md b/docs/high-concurrency/es-optimizing-query-performance.md index d5f6e0a87..b59a21049 100644 --- a/docs/high-concurrency/es-optimizing-query-performance.md +++ b/docs/high-concurrency/es-optimizing-query-performance.md @@ -1,3 +1,5 @@ +# ES 查询性能优化 + ## 面试题 ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊? diff --git a/docs/high-concurrency/es-production-cluster.md b/docs/high-concurrency/es-production-cluster.md index 9733854b3..0134cc725 100644 --- a/docs/high-concurrency/es-production-cluster.md +++ b/docs/high-concurrency/es-production-cluster.md @@ -1,3 +1,5 @@ +# ES 生产集群架构 + ## 面试题 ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片? @@ -16,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 3cdbfd5a0..600a2a8b5 100644 --- a/docs/high-concurrency/es-write-query-search.md +++ b/docs/high-concurrency/es-write-query-search.md @@ -1,3 +1,5 @@ +# ES 写入数据原理 + ## 面试题 ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗? @@ -12,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) @@ -23,10 +25,10 @@ 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 搜索数据过程 @@ -40,10 +42,10 @@ j2ee特别牛 你根据 `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 读取,采用的是随机轮询算法。 @@ -73,8 +75,8 @@ 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 一次)。 +- `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 秒的**数据丢失**。 @@ -136,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 f45ee495d..9af4256a2 100644 --- a/docs/high-concurrency/high-concurrency-design.md +++ b/docs/high-concurrency/high-concurrency-design.md @@ -1,10 +1,12 @@ +# 如何设计一个高并发系统 + ## 面试题 如何设计一个高并发系统? ## 面试官心理分析 -说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先。 +说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了。为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发经验者优先。 如果你确实有真才实学,在互联网公司里干过高并发系统,那你确实拿 offer 基本如探囊取物,没啥问题。面试官也绝对不会这样来问你,否则他就是蠢。 @@ -32,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 048662652..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,10 +1,12 @@ +# 如何保证消息队列的高可用? + ## 面试题 如何保证消息队列的高可用? ## 面试官心理分析 -如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](/docs/high-concurrency/why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。 +如果有人问到你 MQ 的知识,**高可用是必问的**。[上一讲](./why-mq.md)提到,MQ 会导致**系统可用性降低**。所以只要你用了 MQ,接下来问的一些要点肯定就是围绕着 MQ 的那些缺点怎么来解决了。 要是你傻乎乎的就干用了一个 MQ,各种问题从来没考虑过,那你就杯具了,面试官对你的感觉就是,只会简单使用一些技术,没任何思考,马上对你的印象就不太好了。这样的同学招进来要是做个 20k 薪资以内的普通小弟还凑合,要是做薪资 20k+ 的高工,那就惨了,让你设计个系统,里面肯定一堆坑,出了事故公司受损失,团队一起背锅。 @@ -26,7 +28,7 @@ RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模 #### 普通集群模式(无高可用性) -普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。 +普通集群模式,意思就是在多台机器上启动多个 RabbitMQ 实例,每台机器启动一个。你**创建的 queue,只会放在一个 RabbitMQ 实例上**,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。 ![mq-7](./images/mq-7.png) @@ -38,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) @@ -52,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 就废了,没法写也没法读,没有什么高可用性可言。 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 3dcb7f297..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,3 +1,5 @@ +# 如何保证 redis 的高并发和高可用? + ## 面试题 如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么? @@ -14,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 474ae2376..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,3 +1,5 @@ +# 如何保证消息不被重复消费? + ## 面试题 如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性? @@ -38,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 574601280..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,3 +1,5 @@ +# 如何保证消息的顺序性? + ## 面试题 如何保证消息的顺序性? @@ -16,11 +18,11 @@ 先看看顺序会错乱的俩场景: -- **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) @@ -28,13 +30,17 @@ #### 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 0240ab100..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,10 +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 传递过程中**绝对不会把计费消息给弄丢**。 @@ -20,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 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 事务机制(同步)一搞,基本上**吞吐量会下来,因为太耗性能**。 @@ -45,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 里的数据的。 - -- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2
+- 创建 queue 的时候将其设置为持久化。这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是它是不会持久化 queue 里的数据的。 -就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。 +- 第二个是发送消息的时候将消息的 `deliveryMode` 设置为 2。就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去。 必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。 @@ -71,6 +135,8 @@ 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 @@ -91,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 78% rename from docs/high-concurrency/huifer-how-to-limit-current.md rename to docs/high-concurrency/how-to-limit-current.md index 2a601d95a..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: 该文简单介绍限流相关技术以及实现 - ## 什么是限流 > 限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的。一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的。比如:延迟处理,拒绝处理,或者部分拒绝处理等等。 @@ -276,7 +273,7 @@ public class TokenBucket { ### spring cloud gateway -- spring cloud gateway 默认使用 redis 进行限流,笔者一般只是修改修改参数属于拿来即用,并没有去从头实现上述那些算法。 +- spring cloud gateway 默认使用 redis 进行限流,笔者一般只是修改修改参数属于拿来即用,并没有去从头实现上述那些算法。 ```xml @@ -291,25 +288,25 @@ public class TokenBucket { ```yaml spring: - cloud: - gateway: - routes: - - id: requestratelimiter_route + cloud: + gateway: + routes: + - id: requestratelimiter_route - uri: lb://pigx-upms - order: 10000 - predicates: - - Path=/admin/** + uri: lb://pigx-upms + order: 10000 + predicates: + - Path=/admin/** - filters: - - name: RequestRateLimiter + filters: + - name: RequestRateLimiter - args: - redis-rate-limiter.replenishRate: 1 # 令牌桶的容积 - redis-rate-limiter.burstCapacity: 3 # 流速 每秒 - key-resolver: "#{@remoteAddrKeyResolver}" #SPEL表达式去的对应的bean + args: + redis-rate-limiter.replenishRate: 1 # 令牌桶的容积 + redis-rate-limiter.burstCapacity: 3 # 流速 每秒 + key-resolver: '#{@remoteAddrKeyResolver}' #SPEL表达式去的对应的bean - - StripPrefix=1 + - StripPrefix=1 ``` ```java @@ -321,7 +318,7 @@ KeyResolver remoteAddrKeyResolver() { ### sentinel -- 通过配置来控制每个 url 的流量 +- 通过配置来控制每个 url 的流量 ```xml @@ -332,47 +329,47 @@ KeyResolver remoteAddrKeyResolver() { ```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 [ - { - "resource": "/hello", - "limitApp": "default", - "grade": 1, - "count": 1, - "strategy": 0, - "controlBehavior": 0, - "clusterMode": false - } + { + "resource": "/hello", + "limitApp": "default", + "grade": 1, + "count": 1, + "strategy": 0, + "controlBehavior": 0, + "clusterMode": false + } ] ``` -- 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:是否为集群模式 ### 总结 diff --git a/docs/high-concurrency/images/lru-cache.png b/docs/high-concurrency/images/lru-cache.png index a6280ad8a..bf54c10b8 100644 Binary files a/docs/high-concurrency/images/lru-cache.png and b/docs/high-concurrency/images/lru-cache.png differ diff --git a/docs/high-concurrency/images/redis-caching-avoid-penetration.png b/docs/high-concurrency/images/redis-caching-avoid-penetration.png index 13d02478f..b978620aa 100644 Binary files a/docs/high-concurrency/images/redis-caching-avoid-penetration.png 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 d7093a670..c81f85eb1 100644 --- a/docs/high-concurrency/mq-design.md +++ b/docs/high-concurrency/mq-design.md @@ -1,3 +1,5 @@ +# 如何设计一个消息队列? + ## 面试题 如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。 @@ -6,8 +8,8 @@ 其实聊到这个问题,一般面试官要考察两块: -- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。 -- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。 +- 你有没有对某一个消息队列做过较为深入的原理的了解,或者从整体了解把握住一个消息队列的架构原理。 +- 看看你的设计能力,给你一个常见的系统,就是消息队列系统,看看你能不能从全局把握一下整体架构设计,给出一些关键点出来。 说实话,问类似问题的时候,大部分人基本都会蒙,因为平时从来没有思考过类似的问题,**大多数人就是平时埋头用,从来不去思考背后的一些东西**。类似的问题,比如,如果让你来设计一个 Spring 框架你会怎么做?如果让你来设计一个 Dubbo 框架你会怎么做?如果让你来设计一个 MyBatis 框架你会怎么做? @@ -17,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 e76c8d9b1..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,3 +1,5 @@ +# 如何解决消息队列的延时以及过期失效问题? + ## 面试题 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决? @@ -20,11 +22,11 @@ 一般这个时候,只能临时紧急扩容了,具体操作步骤和思路如下: -- 先修复 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 中的消息过期失效了 @@ -78,10 +80,10 @@ public ConsumeConcurrentlyStatus consumeMessage( 举例如下,某条消息的消费过程如下: -- 根据消息从 DB 查询【数据 1】 -- 根据消息从 DB 查询【数据 2】 -- 复杂的业务计算 -- 向 DB 插入【数据 3】 -- 向 DB 插入【数据 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 7bef02603..b35b083dd 100644 --- a/docs/high-concurrency/mysql-read-write-separation.md +++ b/docs/high-concurrency/mysql-read-write-separation.md @@ -1,3 +1,5 @@ +# 如何实现读写分离? + ## 面试题 你们有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决 MySQL 主从同步的延时问题? @@ -44,7 +46,7 @@ show slave status 一般来说,如果主从延迟较为严重,有以下解决方案: -- 分库,将一个主库拆分为多个主库,每个主库的写并发就减少了几倍,此时主从延迟可以忽略不计。 -- 打开 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 ed54d3c41..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,3 +1,5 @@ +# 缓存雪崩、穿透和击穿 + ## 面试题 了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透? @@ -8,7 +10,7 @@ ## 面试题剖析 -### 缓存雪崩 +### 缓存雪崩(Cache Avalanche) 对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。 @@ -20,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) @@ -32,11 +34,11 @@ 好处: -- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 -- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 -- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。 +- 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。 +- 只要数据库不死,就是说,对用户来说,2/5 的请求都是可以被处理的。 +- 只要有 2/5 的请求可以被处理,就意味着你的系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来了。 -### 缓存穿透 +### 缓存穿透(Cache Penetration) 对于系统 A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。 @@ -50,19 +52,19 @@ 当然,如果黑客如果每次使用不同的负数 id 来攻击,写空值的方法可能就不奏效了。更为经常的做法是在缓存之前增加布隆过滤器,将数据库中所有可能的数据哈希映射到布隆过滤器中。然后对每个请求进行如下判断: -- 请求数据的 key 不存在于布隆过滤器中,可以确定数据就一定不会存在于数据库中,系统可以立即返回不存在。 -- 请求数据的 key 存在于布隆过滤器中,则继续再向缓存中查询。 +- 请求数据的 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 e43d27925..c1852bf8e 100644 --- a/docs/high-concurrency/redis-cas.md +++ b/docs/high-concurrency/redis-cas.md @@ -1,3 +1,5 @@ +# Redis 的并发竞争问题 + ## 面试题 Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗? diff --git a/docs/high-concurrency/redis-cluster.md b/docs/high-concurrency/redis-cluster.md index 2bc7ff6c2..69c3d4978 100644 --- a/docs/high-concurrency/redis-cluster.md +++ b/docs/high-concurrency/redis-cluster.md @@ -1,3 +1,5 @@ +# Redis 集群模式原理 + ## 面试题 Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗? @@ -18,8 +20,8 @@ Redis cluster,主要是针对**海量数据+高并发+高可用**的场景。R ### Redis cluster 介绍 -- 自动将数据进行分片,每个 master 上放一部分数据 -- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 +- 自动将数据进行分片,每个 master 上放一部分数据 +- 提供内置的高可用支持,部分 master 不可用时,还是可以继续工作的 在 Redis cluster 架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是 加 1w 的端口号,比如 16379。 @@ -43,15 +45,15 @@ 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 Redis-trib.rb add-node @@ -59,9 +61,9 @@ 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 消息深入 @@ -73,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 23146e1d4..150ba6704 100644 --- a/docs/high-concurrency/redis-consistence.md +++ b/docs/high-concurrency/redis-consistence.md @@ -1,3 +1,5 @@ +# 缓存与数据库一致性问题 + ## 面试题 如何保证缓存与数据库的双写一致性? @@ -16,8 +18,8 @@ 最经典的缓存+数据库读写的模式,就是 Cache Aside Pattern。 -- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 -- 更新的时候,**先更新数据库,然后再删除缓存**。 +- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。 +- 更新的时候,**先更新数据库,然后再删除缓存**。 **为什么是删除缓存,而不是更新缓存?** @@ -75,7 +77,7 @@ public void set(key, value) { 高并发的场景下,该解决方案要注意的问题: -- 读请求长时阻塞 +- 读请求长时阻塞 由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。 @@ -95,19 +97,19 @@ public void set(key, value) { 经过刚才简单的测算,我们知道,单机支撑的写 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 bb312436f..1681ea2af 100644 --- a/docs/high-concurrency/redis-data-types.md +++ b/docs/high-concurrency/redis-data-types.md @@ -1,3 +1,5 @@ +# Redis 数据类型和使用场景 + ## 面试题 Redis 都有哪些数据类型?分别在哪些场景下使用比较合适? @@ -8,8 +10,8 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适? 其实问这个问题,主要有两个原因: -- 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作; -- 看看你在实际项目里都怎么玩儿过 Redis。 +- 看看你到底有没有全面的了解 Redis 有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的 KV 操作; +- 看看你在实际项目里都怎么玩儿过 Redis。 要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的 set 和 get。 @@ -17,11 +19,11 @@ Redis 都有哪些数据类型?分别在哪些场景下使用比较合适? Redis 主要有以下几种数据类型: -- Strings -- Hashes -- Lists -- Sets -- Sorted Sets +- Strings +- Hashes +- Lists +- Sets +- Sorted Sets > Redis 除了这 5 种数据类型之外,还有 Bitmaps、HyperLogLogs、Streams 等。 diff --git a/docs/high-concurrency/redis-expiration-policies-and-lru.md b/docs/high-concurrency/redis-expiration-policies-and-lru.md index 8425e5649..54da3fe22 100644 --- a/docs/high-concurrency/redis-expiration-policies-and-lru.md +++ b/docs/high-concurrency/redis-expiration-policies-and-lru.md @@ -1,3 +1,5 @@ +# Redis 的过期策略和 LRU 算法 + ## 面试题 Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? @@ -8,7 +10,7 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一 常见的有两个问题: -- 往 Redis 写入的数据怎么没了? +- 往 Redis 写入的数据怎么没了? 可能有同学会遇到,在生产环境的 Redis 经常会丢掉一些数据,写进去了,过一会儿可能就没了。我的天,同学,你问这个问题就说明 Redis 你就没用对啊。Redis 是缓存,你给当存储了是吧? @@ -16,7 +18,7 @@ Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一 那既然内存是有限的,比如 Redis 就只能用 10G,你要是往里面写了 20G 的数据,会咋办?当然会干掉 10G 的数据,然后就保留 10G 的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。 -- 数据明明过期了,怎么还占用着内存? +- 数据明明过期了,怎么还占用着内存? 这是由 Redis 的过期策略来决定。 @@ -42,12 +44,12 @@ 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 算法 diff --git a/docs/high-concurrency/redis-master-slave.md b/docs/high-concurrency/redis-master-slave.md index 457f33d1b..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 主从复制的核心原理 @@ -60,23 +60,23 @@ 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 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 @@ -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 d64f0533b..20605e2df 100644 --- a/docs/high-concurrency/redis-persistence.md +++ b/docs/high-concurrency/redis-persistence.md @@ -1,3 +1,5 @@ +# Redis 持久化机制 + ## 面试题 Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的? @@ -22,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 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。 @@ -33,24 +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 5d78123ed..74aab390e 100644 --- a/docs/high-concurrency/redis-production-environment.md +++ b/docs/high-concurrency/redis-production-environment.md @@ -1,3 +1,5 @@ +# Redis 生产部署方案 + ## 面试题 生产环境中的 Redis 是怎么部署的? diff --git a/docs/high-concurrency/redis-rehash.md b/docs/high-concurrency/redis-rehash.md index de3cac24d..52deb3e6e 100644 --- a/docs/high-concurrency/redis-rehash.md +++ b/docs/high-concurrency/redis-rehash.md @@ -1,3 +1,5 @@ +# Redis rehash 过程 + ## 面试题 有了解过 Redis rehash 的过程吗? @@ -40,4 +42,4 @@ rehash 过程在数据量非常大(几千万、亿)的情况下并不是一 渐进式 rehash 的本质: 1. 借助 rehashidx,将 rehash 键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上,从而避免了集中式 rehash 而带来的庞大计算量。 -2. 在 rehash 进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将原哈希表在 rehashidx 索引上的所有键值对 rehash 到备用哈希表,当 rehash 工作完成之后,程序将 rehashidx 属性的值加 1。 \ No newline at end of file +2. 在 rehash 进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将原哈希表在 rehashidx 索引上的所有键值对 rehash 到备用哈希表,当 rehash 工作完成之后,程序将 rehashidx 属性的值加 1。 diff --git a/docs/high-concurrency/redis-sentinel.md b/docs/high-concurrency/redis-sentinel.md index 5ae5b56ad..d6aa8dff9 100644 --- a/docs/high-concurrency/redis-sentinel.md +++ b/docs/high-concurrency/redis-sentinel.md @@ -4,21 +4,21 @@ 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。 @@ -63,13 +63,13 @@ sentinel,中文名是哨兵。哨兵是 Redis 集群架构中非常重要的 主备切换的过程,可能会导致数据丢失: -- 异步复制导致的数据丢失 +- 异步复制导致的数据丢失 因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这部分数据就丢失了。 ![async-replication-data-lose-case](./images/async-replication-data-lose-case.png) -- 脑裂导致的数据丢失 +- 脑裂导致的数据丢失 脑裂,也就是说,某个 master 所在机器突然**脱离了正常的网络**,跟其他 slave 机器不能连接,但是实际上 master 还运行着。此时哨兵可能就会**认为** master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会有两个 master ,也就是所谓的**脑裂**。 @@ -90,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 了。 @@ -123,10 +123,10 @@ 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。 @@ -136,9 +136,9 @@ sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过 接下来会对 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 2c2e5364e..17a427406 100644 --- a/docs/high-concurrency/redis-single-thread-model.md +++ b/docs/high-concurrency/redis-single-thread-model.md @@ -1,3 +1,5 @@ +# Redis 和 Memcached 的区别 + ## 面试题 Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么 Redis 单线程却能支撑高并发? @@ -14,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 原生支持集群模式 @@ -30,10 +32,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事 文件事件处理器的结构包含 4 个部分: -- 多个 socket -- IO 多路复用程序 -- 文件事件分派器 -- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) +- 多个 socket +- IO 多路复用程序 +- 文件事件分派器 +- 事件处理器(连接应答处理器、命令请求处理器、命令回复处理器) 多个 socket 可能会并发产生不同的操作,每个操作对应不同的文件事件,但是 IO 多路复用程序会监听多个 socket,会将产生事件的 socket 放入队列中排队,事件分派器每次从队列中取出一个 socket,根据 socket 的事件类型交给对应的事件处理器进行处理。 @@ -55,10 +57,10 @@ Redis 内部使用文件事件处理器 `file event handler` ,这个文件事 ### 为啥 Redis 单线程模型也能效率这么高? -- 纯内存操作。 -- 核心是基于非阻塞的 IO 多路复用机制。 -- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 -- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 +- 纯内存操作。 +- 核心是基于非阻塞的 IO 多路复用机制。 +- C 语言实现,一般来说,C 语言实现的程序“距离”操作系统更近,执行速度相对会更快。 +- 单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 ### Redis 6.0 开始引入多线程 @@ -70,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 860c744d9..27eb0f94d 100644 --- a/docs/high-concurrency/why-cache.md +++ b/docs/high-concurrency/why-cache.md @@ -1,3 +1,5 @@ +# 缓存的使用方式 + ## 面试题 项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果? @@ -40,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 1f68e2422..47e54b2f1 100644 --- a/docs/high-concurrency/why-mq.md +++ b/docs/high-concurrency/why-mq.md @@ -1,26 +1,30 @@ +# 为什么使用消息队列? + ## 面试题 -- 为什么使用消息队列? -- 消息队列有什么优点和缺点? -- 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 **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。 -你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个 MQ,比如 Kafka,甚至都从没调研过业界流行的 MQ 到底有哪几种。每一个 MQ 的优点和缺点是什么。每一个 MQ **没有绝对的好坏**,但是就是看用在哪个场景可以**扬长避短,利用其优势,规避其劣势**。
-如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。 + 如果是一个不考虑技术选型的候选人招进了团队,leader 交给他一个任务,去设计个什么系统,他在里面用一些技术,可能都没考虑过选型,最后选的技术可能并不一定合适,一样是留坑。 ## 面试题剖析 @@ -34,7 +38,7 @@ #### 解耦 -看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃...... +看这么个场景。A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 D 系统现在不需要了呢?A 系统负责人几乎崩溃...... ![mq-1](./images/mq-1.png) @@ -82,19 +86,19 @@ 缺点有以下几个: -- 系统可用性降低
+- 系统可用性降低 -系统引入的外部依赖越多,越容易挂掉。本来你就是 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 有什么优缺点? @@ -109,9 +113,9 @@ A 系统处理完了直接返回成功了,人都以为你这个请求就成功 综上,各种对比之后,有如下建议: -一般的业务系统要引入 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 1cafa6b9c..ebbbcb3ab 100644 --- a/docs/micro-services/README.md +++ b/docs/micro-services/README.md @@ -1,48 +1,42 @@ # 微服务架构 -- [微服务架构整个章节内容属额外新增,后续抽空更新,也欢迎读者们参与补充完善](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) +- ...... --- ## 公众号 -[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs 开源社区**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 +[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」​,欢迎扫码关注,**专注分享技术领域相关知识及行业最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。 - -
- -
- 公众平台 -

+
+
- -
- 个人微信 -

+
+
-关注「**Doocs 开源社区**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档(283 页精华),学习更加方便! +关注「**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 a3967b702..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 统 计和监控。 -# 总结 +## 总结 构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。 diff --git a/docs/micro-services/choose-microservice-deployment-strategy.md b/docs/micro-services/choose-microservice-deployment-strategy.md index 85409764a..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。 @@ -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 8af73641a..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 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。 @@ -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) 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/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/huifer-micro-service-governance.md b/docs/micro-services/micro-service-governance.md similarity index 83% rename from docs/micro-services/huifer-micro-service-governance.md rename to docs/micro-services/micro-service-governance.md index b3cac6ef3..ba7363748 100644 --- a/docs/micro-services/huifer-micro-service-governance.md +++ b/docs/micro-services/micro-service-governance.md @@ -1,16 +1,13 @@ # 微服务治理策略 -- Author:[HuiFer](https://github.com/huifer) -- Description:该文简单介绍微服务的治理策略以及应用技术 - ## 服务的注册和发现 解决问题:集中管理服务 解决方法: -- Eureka -- Zookeeper +- Eureka +- Zookeeper ## 负载均衡 @@ -18,8 +15,8 @@ 解决方法: -- Nginx -- Ribbon +- Nginx +- Ribbon ## 通讯 @@ -27,9 +24,9 @@ 解决方法: -- REST(同步) -- RPC(同步) -- MQ(异步) +- REST(同步) +- RPC(同步) +- MQ(异步) ## 配置管理 @@ -37,9 +34,9 @@ 解决方法: -- Nacos -- Spring Cloud Config -- Apollo +- Nacos +- Spring Cloud Config +- Apollo ## 容错和服务降级 @@ -47,7 +44,7 @@ 解决方法: -- Hystrix +- Hystrix ## 服务依赖关系 @@ -61,8 +58,8 @@ 解决方法: -- Swagger -- Java doc +- Swagger +- Java doc ## 服务安全问题 @@ -70,9 +67,9 @@ 解决方法: -- Oauth -- Shiro -- Spring Security +- Oauth +- Shiro +- Spring Security ## 流量控制 @@ -80,7 +77,7 @@ 解决方法: -- Hystrix +- Hystrix ## 自动化测试 @@ -88,7 +85,7 @@ 解决方法: -- junit +- junit ## 服务上线,下线的流程 @@ -108,8 +105,8 @@ 解决方法: -- Docker -- K8s +- Docker +- K8s ## 资源调度 @@ -117,9 +114,9 @@ 解决方法: -- JVM 隔离 -- Classload 隔离 -- 硬件隔离 +- JVM 隔离 +- Classload 隔离 +- 硬件隔离 ## 容量规划 diff --git a/docs/micro-services/huifer-micro-services-technology-stack.md b/docs/micro-services/micro-services-technology-stack.md similarity index 86% rename from docs/micro-services/huifer-micro-services-technology-stack.md rename to docs/micro-services/micro-services-technology-stack.md index 3a9b7c835..c91a3177f 100644 --- a/docs/micro-services/huifer-micro-services-technology-stack.md +++ b/docs/micro-services/micro-services-technology-stack.md @@ -1,17 +1,14 @@ # 微服务技术栈 -- Author: [HuiFer](https://github.com/huifer) -- Description: 该文简单介绍微服务技术栈有哪些分别用来做什么。 - ## 技术栈 ### 微服务开发 作用:快速开发服务。 -- Spring -- Spring MVC -- Spring Boot +- Spring +- Spring MVC +- Spring Boot [Spring](https://spring.io/) 目前是 JavaWeb 开发人员必不可少的一个框架,SpringBoot 简化了 Spring 开发的配置目前也是业内主流开发框架。 @@ -21,9 +18,9 @@ #### 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/) +- 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 @@ -35,9 +32,9 @@ Zookeeper 保证 CP,Eureka 保证 AP: -- C:数据一致性; -- A:服务可用性; -- P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。 +- C:数据一致性; +- A:服务可用性; +- P:服务对网络分区故障的容错性,这三个特性在任何分布式系统中不能同时满足,最多同时满足两个。 ### 微服务配置管理 @@ -81,12 +78,12 @@ Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能 #### Rest -- 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互 +- 通过 HTTP/HTTPS 发送 Rest 请求进行数据交互 #### RPC -- Remote Procedure Call -- 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。 +- Remote Procedure Call +- 它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 不依赖于具体的网络传输协议,tcp、udp 等都可以。 #### [gRPC](https://www.grpc.io/) @@ -96,8 +93,8 @@ Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能 #### RMI -- Remote Method Invocation -- 纯 Java 调用 +- Remote Method Invocation +- 纯 Java 调用 ### 服务接口调用 diff --git a/docs/micro-services/microservices-introduction.md b/docs/micro-services/microservices-introduction.md index 18c0a6060..27c488b11 100644 --- a/docs/micro-services/microservices-introduction.md +++ b/docs/micro-services/microservices-introduction.md @@ -1,4 +1,4 @@ -# 微服务 +# 关于微服务架构的描述 > 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。
本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。 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 6b34e5ff8..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,9 +24,9 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 微服务有三种方式访问单体应用数据: -- 换气单体应用提供的远程 API -- 直接访问单体应用数据库 -- 自己维护一份从单体应用中同步的数据 +- 换气单体应用提供的远程 API +- 直接访问单体应用数据库 +- 自己维护一份从单体应用中同步的数据 胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。术语 anti-corruption layer 第一次出现在 Eric Evans 撰写的必读书 _Domain Driven Design_,随后就被提炼为一篇白皮书。开发容灾层可能有点不是很重要,但却是避免单体式泥潭的必要部分。 @@ -34,7 +34,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用 然而,这方法并不解决任何单体式本身问题,为了解决单体式本身问题必须深入单体应用 ​ 做出改变。我们来看看这么做的策略。 -# 策略 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 94% 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 37f283f99..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,12 +1,9 @@ # 什么是微服务?微服务之间是如何独立通讯的? -- Author:[HuiFer](https://github.com/huifer) -- Description:介绍微服务的定义以及服务间的通信。 - ## 什么是微服务 -- 微服务架构是一个分布式系统,按照业务进行划分成为不同的服务单元,解决单体系统性能等不足。 -- 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。 +- 微服务架构是一个分布式系统,按照业务进行划分成为不同的服务单元,解决单体系统性能等不足。 +- 微服务是一种架构风格,一个大型软件应用由多个服务单元组成。系统中的服务单元可以单独部署,各个服务单元之间是松耦合的。 > 微服务概念起源:[Microservices](https://martinfowler.com/articles/microservices.html) @@ -76,7 +73,6 @@ RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请 首先需要一个服务端: ```java - import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; @@ -183,7 +179,6 @@ public class RPCServer { 其次需要一个客户端: ```java - import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationHandler; @@ -257,10 +252,6 @@ public class TinterfaceImpl implements Tinterface { 测试代码如下: ```java - -import com.huifer.admin.rpc.Tinterface; -import com.huifer.admin.rpc.TinterfaceImpl; - import java.net.InetSocketAddress; 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 index 3ed6d14ab..1d4f2926e 100644 Binary files a/images/pdf.png 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 864859ff3..000000000 --- a/index.html +++ /dev/null @@ -1,170 +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 index 51bbb26c5..71733933c 100644 --- a/package.json +++ b/package.json @@ -1,25 +1,13 @@ { - "scripts": { - "convert": "docsify-pdf-converter" - }, - "name": "advanced-java", - "description": "互联网 Java 工程师进阶知识完全扫盲@doocs,https://github.com/doocs/advanced-java", - "version": "1.0.0", - "main": ".docsifytopdfrc.js", - "directories": { - "doc": "docs" - }, - "repository": { - "type": "git", - "url": "https://github.com/doocs/advanced-java.git" + "devDependencies": { + "vitepress": "^1.6.3" }, - "author": "yanglbme", - "license": "CC-BY-SA-4.0", - "bugs": { - "url": "https://github.com/doocs/advanced-java/issues" + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" }, - "homepage": "https://github.com/doocs/advanced-java#readme", - "devDependencies": { - "docsify-pdf-converter": "^2.0.7" + "dependencies": { + "vitepress-plugin-comment-with-giscus": "^1.1.15" } -} \ No newline at end of file +} diff --git a/summary.md b/summary.md deleted file mode 100644 index 089d777c8..000000000 --- a/summary.md +++ /dev/null @@ -1,145 +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-master-slave.md) - - [Redis 的持久化有哪几种方式?](./docs/high-concurrency/redis-persistence.md) - - [Redis 如何基于哨兵集群实现高可用?](./docs/high-concurrency/redis-sentinel.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)