mirror of
https://github.com/doocs/advanced-java.git
synced 2026-08-18 10:43:29 +08:00
chore: build site with vitepress
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
contents: ['summary.md'],
|
||||
pathToPublic: 'pdf/advanced-java.pdf',
|
||||
pdfOptions: '<options for puppeteer.pdf()>',
|
||||
removeTemp: true,
|
||||
emulateMedia: 'screen',
|
||||
};
|
||||
79
.github/workflows/deploy.yml
vendored
Normal file
79
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程
|
||||
#
|
||||
name: Deploy VitePress site to Pages
|
||||
|
||||
on:
|
||||
# 在针对 `main` 分支的推送上运行。如果你
|
||||
# 使用 `master` 分支作为默认分支,请将其更改为 `master`
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
# 允许你从 Actions 选项卡手动运行此工作流程
|
||||
workflow_dispatch:
|
||||
|
||||
# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列
|
||||
# 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
# 构建工作
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # 如果未启用 lastUpdated,则不需要
|
||||
# - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消此区域注释
|
||||
# with:
|
||||
# version: 9
|
||||
# - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm # 或 pnpm / yarn
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
- name: Install dependencies
|
||||
run: npm ci # 或 pnpm install / yarn install / bun install
|
||||
- name: Build with VitePress
|
||||
run: npm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/.vitepress/dist
|
||||
|
||||
# 部署工作
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
# 同步到 Gitee
|
||||
sync:
|
||||
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
|
||||
26
.github/workflows/sync.yml
vendored
26
.github/workflows/sync.yml
vendored
@@ -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
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -41,3 +41,6 @@ yarn-error.log*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
docs/.vitepress/dist
|
||||
docs/.vitepress/cache
|
||||
@@ -1,3 +0,0 @@
|
||||
.idea/
|
||||
.github/
|
||||
node_modules/
|
||||
10
.prettierrc
10
.prettierrc
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
482
docs/.vitepress/config.mts
Normal file
482
docs/.vitepress/config.mts
Normal file
@@ -0,0 +1,482 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
|
||||
export default defineConfig({
|
||||
title: "advanced-java",
|
||||
themeConfig: {
|
||||
search: {
|
||||
provider: 'local'
|
||||
},
|
||||
footer: {
|
||||
message: 'Released under the CC-BY-SA-4.0 license.',
|
||||
copyright: `Copyright © 2018-${new Date().getFullYear()} <a href="https://github.com/doocs">Doocs</a>`
|
||||
},
|
||||
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'
|
||||
}
|
||||
});
|
||||
30
docs/.vitepress/theme/index.js
Normal file
30
docs/.vitepress/theme/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import giscusTalk from 'vitepress-plugin-comment-with-giscus';
|
||||
import { useData, useRoute } from 'vitepress';
|
||||
import { toRefs } from "vue";
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
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);
|
||||
}
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
## 如何统计不同电话号码的个数?
|
||||
# 如何统计不同电话号码的个数?
|
||||
|
||||
### 题目描述
|
||||
## 题目描述
|
||||
|
||||
已知某个文件内包含一些电话号码,每个号码为 8 位数字,统计不同号码的个数。
|
||||
|
||||
### 解答思路
|
||||
## 解答思路
|
||||
|
||||
这道题本质还是求解**数据重复**的问题,对于这类问题,一般首先考虑位图法。
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
|
||||
申请一个位图数组,长度为 1 亿,初始化为 0。然后遍历所有电话号码,把号码对应的位图中的位置置为 1。遍历完成后,如果 bit 为 1,则表示这个电话号码在文件中存在,否则不存在。bit 值为 1 的数量即为 不同电话号码的个数。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
求解数据重复问题,记得考虑位图法。
|
||||
|
||||
@@ -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 表示不存在。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
**判断数字是否存在、判断数字是否重复的问题**,位图法是一种非常高效的方法。
|
||||
|
||||
@@ -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,7 +20,7 @@
|
||||
|
||||
接着遍历 a<sub>i</sub>( `i∈[0,999]` ),把 URL 存储到一个 HashSet 集合中。然后遍历 b<sub>i</sub> 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
|
||||
|
||||
#### 2. 前缀树是否可行?
|
||||
### 2. 前缀树是否可行?
|
||||
|
||||
> 一般而言,URL 的长度差距不会不大,而且前面几个字符,绝大部分相同。这种情况下,非常适合使用**字典树**(trie tree) 这种数据结构来进行存储,降低存储成本的同时,提高查询效率。
|
||||
>
|
||||
@@ -60,13 +60,13 @@ Background:4G 的机器,解析 320G 的数据。
|
||||
|
||||
- 如果 CPU 不充足比如单核,可以考虑增加切分的文件大小,原先的 1000 个文件,降为 200 个也就是文件大小扩大两倍,之前 300MB,现在 300\*5 = 1.5G,在 4G 的机器上可以打满内存,减少 IO 次数,减少内核态到用户态的拷贝,也可以提一嘴 DMA。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
#### 分治策略
|
||||
### 分治策略
|
||||
|
||||
1. 分而治之,进行哈希取余;
|
||||
1. 对每个子文件进行 HashSet 统计。
|
||||
|
||||
#### 前缀树
|
||||
### 前缀树
|
||||
|
||||
1. 利用字符串的公共前缀牺牲速度,来降低内存占用。
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
最后依然使用小顶堆来对字符串的出现次数进行排序。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
前缀树经常被用来统计字符串的出现次数。它的另外一个大的用途是字符串查找,判断是否有重复的字符串等。
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
## 如何从 5 亿个数中找出中位数?
|
||||
# 如何从 5 亿个数中找出中位数?
|
||||
|
||||
### 题目描述
|
||||
## 题目描述
|
||||
|
||||
从 5 亿个数中找出中位数。数据排序后,位置在最中间的数就是中位数。当样本数为奇数时,中位数为 第 `(N+1)/2` 个数;当样本数为偶数时,中位数为 第 `N/2` 个数与第 `1+N/2` 个数的均值。
|
||||
|
||||
### 解答思路
|
||||
## 解答思路
|
||||
|
||||
如果这道题没有内存大小限制,则可以把所有数读到内存中排序后找出中位数。但是最好的排序算法的时间复杂度都为 `O(NlogN)` 。这里使用其他方法。
|
||||
|
||||
#### 方法一:双堆法
|
||||
### 方法一:双堆法
|
||||
|
||||
维护两个堆,一个大顶堆,一个小顶堆。大顶堆中最大的数**小于等于**小顶堆中最小的数;保证这两个堆中的元素个数的差不超过 1。
|
||||
|
||||
@@ -57,7 +57,7 @@ class MedianFinder {
|
||||
|
||||
以上这种方法,需要把所有数据都加载到内存中。当数据量很大时,就不能这样了,因此,这种方法**适用于数据量较小的情况**。5 亿个数,每个数字占用 4B,总共需要 2G 内存。如果可用内存不足 2G,就不能使用这种方法了,下面介绍另一种方法。
|
||||
|
||||
#### 方法二:分治法
|
||||
### 方法二:分治法
|
||||
|
||||
分治法的思想是把一个大的问题逐渐转换为规模较小的问题来求解。
|
||||
|
||||
@@ -71,6 +71,6 @@ class MedianFinder {
|
||||
|
||||
> **注意**,当数据总数为偶数,如果划分后两个文件中的数据有相同个数,那么中位数就是数据较小的文件中的最大值与数据较大的文件中的最小值的平均值。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
分治法,真香!
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
## 如何在大量的数据中找出不重复的整数?
|
||||
# 如何在大量的数据中找出不重复的整数?
|
||||
|
||||
### 题目描述
|
||||
## 题目描述
|
||||
|
||||
在 2.5 亿个整数中找出不重复的整数。注意:内存不足以容纳这 2.5 亿个整数。
|
||||
|
||||
### 解答思路
|
||||
## 解答思路
|
||||
|
||||
#### 方法一:分治法
|
||||
### 方法一:分治法
|
||||
|
||||
与前面的题目方法类似,先将 2.5 亿个数划分到多个小文件,用 HashSet/HashMap 找出每个小文件中不重复的整数,再合并每个子结果,即为最终结果。
|
||||
|
||||
#### 方法二:位图法
|
||||
### 方法二:位图法
|
||||
|
||||
**位图**,就是用一个或多个 bit 来标记某个元素对应的值,而键就是该元素。采用位作为单位来存储数据,可以大大节省存储空间。
|
||||
|
||||
@@ -58,6 +58,6 @@ for i in range(8):
|
||||
|
||||
当然,本题中特别说明:**内存不足以容纳这 2.5 亿个整数**,2.5 亿个整数的内存大小为:2.5e8/1024/1024/1024 \* 4=3.72GB, 如果内存大于 1GB,是可以通过位图法解决的。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
**判断数字是否重复的问题**,位图法是一种非常高效的方法,当然前提是:内存要满足位图法所需要的存储空间。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## 如何找出排名前 500 的数?
|
||||
# 如何找出排名前 500 的数?
|
||||
|
||||
### 题目描述
|
||||
## 题目描述
|
||||
|
||||
有 20 个数组,每个数组有 500 个元素,并且有序排列。如何在这 20\*500 个数中找出前 500 的数?
|
||||
|
||||
### 解答思路
|
||||
## 解答思路
|
||||
|
||||
对于 TopK 问题,最常用的方法是使用堆排序。对本题而言,假设数组降序排列,可以采用以下方法:
|
||||
|
||||
@@ -104,6 +104,6 @@ class Test {
|
||||
}
|
||||
```
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
求 TopK,不妨考虑一下堆排序?
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
## 如何找出某一天访问百度网站最多的 IP?
|
||||
# 如何找出某一天访问百度网站最多的 IP?
|
||||
|
||||
### 题目描述
|
||||
## 题目描述
|
||||
|
||||
现有海量日志数据保存在一个超大文件中,该文件无法直接读入内存,要求从中提取某天访问百度次数最多的那个 IP。
|
||||
|
||||
### 解答思路
|
||||
## 解答思路
|
||||
|
||||
这道题只关心某一天访问百度最多的 IP,因此,可以首先对文件进行一次遍历,把这一天访问百度 IP 的相关信息记录到一个单独的大文件中。接下来采用的方法与上一题一样,大致就是先对 IP 进行哈希映射,接着使用 HashMap 统计重复 IP 的次数,最后计算出重复次数最多的 IP。
|
||||
|
||||
> 注:这里只需要找出出现次数最多的 IP,可以不必使用堆,直接用一个变量 max 即可。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
1. 分而治之,进行哈希取余;
|
||||
2. 使用 HashMap 统计频数;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
## 如何从大量数据中找出高频词?
|
||||
# 如何从大量数据中找出高频词?
|
||||
|
||||
### 题目描述
|
||||
## 题目描述
|
||||
|
||||
有一个 1GB 大小的文件,文件里每一行是一个词,每个词的大小不超过 16B,内存大小限制是 1MB,要求返回频数最高的 100 个词(Top 100)。
|
||||
|
||||
### 解答思路
|
||||
## 解答思路
|
||||
|
||||
由于内存限制,我们依然无法直接将大文件的所有词一次读到内存中。因此,同样可以采用**分治策略**,把一个大文件分解成多个小文件,保证每个文件的大小小于 1MB,进而直接将单个小文件读取到内存中进行处理。
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
上面我们统计了每个小文件单词出现的频数。接下来,我们可以通过维护一个**小顶堆**来找出所有词中出现频数最高的 100 个。具体方法是:依次遍历每个小文件,构建一个**小顶堆**,堆大小为 100。如果遍历到的词的出现次数大于堆顶词的出现次数,则用新词替换堆顶的词,然后重新调整为**小顶堆**,遍历结束后,小顶堆上的词就是出现频数最高的 100 个词。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
1. 分而治之,进行哈希取余;
|
||||
2. 使用 HashMap 统计频数;
|
||||
|
||||
@@ -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 出现次数,根据次数排序并写入到另外一个单独文件中。
|
||||
|
||||
接着对所有文件按照 query 的次数进行排序,这里可以使用归并排序(由于无法把所有 query 都读入内存,因此需要使用外排序)。
|
||||
|
||||
### 方法总结
|
||||
## 方法总结
|
||||
|
||||
- 内存若够,直接读入进行排序;
|
||||
- 内存不够,先划分为小文件,小文件排好序后,整理使用外排序进行归并。
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
## 大数据中 TopK 问题的常用套路
|
||||
|
||||
> **作者 Chunel Feng,编程爱好者,阿里巴巴搜索引擎开发工程师。**<br><br>个人微信:ChunelFeng <br>个人博客:[一面之猿网](http://www.chunel.cn) <br>开源项目:[Caiss 智能相似搜索引擎](https://github.com/ChunelFeng/caiss)
|
||||
|
||||
Doocs 社区的朋友们,大家好。我是你们的新朋友 [Chunel Feng](https://github.com/ChunelFeng)。今天想跟大家聊一些**常见的 topK 问题**。
|
||||
# 大数据中 TopK 问题的常用套路
|
||||
|
||||
对于海量数据到处理经常会涉及到 topK 问题。在设计数据结构和算法的时候,主要需要考虑的应该是当前算法(包括数据结构)跟给定情境(比如数据量级、数据类型)的适配程度,和当前问题最核心的瓶颈(如降低时间复杂度,还是降低空间复杂度)是什么。
|
||||
|
||||
@@ -19,7 +15,7 @@ Doocs 社区的朋友们,大家好。我是你们的新朋友 [Chunel Feng](ht
|
||||
上面这些问题看起来很相似,但是解决的方式却千差万别。稍有不慎,就可能使得 topK 问题成为系统的瓶颈。不过也不用太担心,接下来我会总结几种常见的解决思路,遇到问题的时候,大家把这些基础思路融会贯通并且杂糅组合,即可做到见招拆招。
|
||||
<br>
|
||||
|
||||
### 1. 堆排序法
|
||||
## 1. 堆排序法
|
||||
|
||||
这里说的是堆排序法,而不是快排或者希尔排序。虽然理论时间复杂度都是 `O(nlogn)`,但是堆排在做 topK 的时候有一个优势,就是可以维护一个仅包含 k 个数字的小顶堆(想清楚,为啥是小顶堆哦),当新加入的数字大于堆顶数字的时候,将堆顶元素剔除,并加入新的数字。
|
||||
|
||||
@@ -49,7 +45,7 @@ int main() {
|
||||
|
||||
> Java 中同样提供了 PriorityQueue 的数据结构。
|
||||
|
||||
### 2. 类似快排法
|
||||
## 2. 类似快排法
|
||||
|
||||
快排大家都知道,针对 topK 问题,可以对快排进行改进。仅对部分数据进行递归计算。比如,在 100 个数字中,找最大的 10 个,第一次循环的时候,povit 被移动到了 80 的位置,则接下来仅需要在后面的 20 个数字中找最大的 10 个即可。
|
||||
|
||||
@@ -107,7 +103,7 @@ int main() {
|
||||
|
||||
<br>
|
||||
|
||||
### 3. 使用 bitmap
|
||||
## 3. 使用 bitmap
|
||||
|
||||
有时候 topK 问题会遇到数据量过大,内存无法全部加载。这个时候,可以考虑将数据存放至 bitmap 中,方便查询。
|
||||
|
||||
@@ -118,7 +114,7 @@ int main() {
|
||||
这种做法的优势,当然是降低了空间复杂度。不过需要注意一点,bitmap 比较适合不重复且有范围(比如,数据均在 0 ~ 10 亿之间)的数据的查询。至于有重复数据的情况,可以考虑与 hash 等结构的混用。
|
||||
<br>
|
||||
|
||||
### 4. 使用 hash
|
||||
## 4. 使用 hash
|
||||
|
||||
如果遇到了查询 string 类型数据的大小,可以考虑 hash 方法。
|
||||
|
||||
@@ -127,7 +123,7 @@ int main() {
|
||||
这种方法比较适合网址或者电话号码的查询。缺点就是如果需要多次查询的话,需要多次计算 hash,并且需要根据实际情况设计多个 hash 函数。
|
||||
<br>
|
||||
|
||||
### 5. 字典树
|
||||
## 5. 字典树
|
||||
|
||||
字典树(trie)的具体结构和查询方式,不在这里赘述了,自行百度一下就有很多。这里主要说一下优缺点。
|
||||
|
||||
@@ -138,13 +134,13 @@ int main() {
|
||||
比如,反复多次查询字符序(例如:z>y>...>b>a)最大的 k 个 url 这种,使用字典树把数据存储一遍,就非常适合。既减少了空间复杂度,也加速了查询效率。
|
||||
<br>
|
||||
|
||||
### 6. 混合查询
|
||||
## 6. 混合查询
|
||||
|
||||
以上几种方法,都是比较独立的方法。其实,在实际工作中,遇到更多的问题还是混合问题,这就需要我们对相关的内容,融会贯通并且做到活学活用。
|
||||
|
||||
我举个例子:我们的分布式服务跑在 10 台不同机器上,每台机器上部署的服务均被请求 10000 次,并且记录了个这 10000 次请求的耗时(耗时值为 int 数据),找出这 10\*10000 次请求中,从高到低的找出耗时最大的 50 个。看看这个问题,很现实吧。我们试着用上面介绍的方法,组合一下来求解。
|
||||
|
||||
#### 方法一
|
||||
### 方法一
|
||||
|
||||
首先,对每台机器上的 10000 个做类似快排,找出每台机器上 top50 的耗时信息。此时,单机上的这 50 条数据是无序的。
|
||||
|
||||
@@ -152,7 +148,7 @@ int main() {
|
||||
|
||||
最后,对这 50 个数据做快排,从而得到最终结果。
|
||||
|
||||
#### 方法二
|
||||
### 方法二
|
||||
|
||||
首先通过堆排,分别找出 10 台机器上耗时最高的 50 个数据,此时的这 50 个数据,已经是从大到小有序的了。
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis vs Zookeeper 实现分布式锁
|
||||
|
||||
## 面试题
|
||||
|
||||
一般实现分布式锁都有哪些方式?使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何实现分布式 Session?
|
||||
|
||||
## 面试题
|
||||
|
||||
集群部署时的分布式 Session 如何实现?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 分布式系统中的 CAP 定理
|
||||
|
||||
## 分布式系统 CAP 定理 P 代表什么含义
|
||||
|
||||
作者之前在看 CAP 定理时抱有很大的疑惑,CAP 定理的定义是指在分布式系统中三者只能满足其二,也就是存在分布式 CA 系统的。作者在网络上查阅了很多关于 CAP 文章,虽然这些文章对于 P 的解释五花八门,但总结下来这些观点大多都是指 P 是不可缺少的,也就是说在分布式系统只能是 AP 或者 CP,这种理论与我之前所认识的理论(存在分布式 CA 系统)是冲突的,所以才有了疑惑。
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 分布式服务接口的幂等性如何设计?
|
||||
|
||||
## 面试题
|
||||
|
||||
分布式服务接口的幂等性如何设计(比如不能重复扣款)?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 分布式系统面试连环炮
|
||||
# 分布式系统面试连环炮
|
||||
|
||||
有一些同学,之前呢主要是做传统行业,或者外包项目,一直是在那种小的公司,技术一直都搞的比较简单。他们有共同的一个问题,就是都没怎么搞过分布式系统,现在互联网公司,一般都是做分布式的系统,大家都不是做底层的分布式系统、分布式存储系统 Hadoop HDFS、分布式计算系统 Hadoop MapReduce / Spark、分布式流式计算系统 Storm。
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
|
||||
面试官可能会问你以下问题。
|
||||
|
||||
### 为什么要进行系统拆分?
|
||||
## 为什么要进行系统拆分?
|
||||
|
||||
- 为什么要进行系统拆分?如何进行系统拆分?拆分后不用 Dubbo 可以吗?Dubbo 和 thrift 有什么区别呢?
|
||||
|
||||
### 分布式服务框架
|
||||
## 分布式服务框架
|
||||
|
||||
- 说一下的 Dubbo 的工作原理?注册中心挂了可以继续通信吗?
|
||||
- Dubbo 支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
|
||||
@@ -25,14 +25,14 @@
|
||||
- 分布式服务接口请求的顺序性如何保证?
|
||||
- 如何自己设计一个类似 Dubbo 的 RPC 框架?
|
||||
|
||||
### 分布式锁
|
||||
## 分布式锁
|
||||
|
||||
- 使用 Redis 如何设计分布式锁?使用 zk 来设计分布式锁可以吗?这两种分布式锁的实现方式哪种效率比较高?
|
||||
|
||||
### 分布式事务
|
||||
## 分布式事务
|
||||
|
||||
- 分布式事务了解吗?你们如何解决分布式事务问题的?TCC 如果出现网络连不通怎么办?XA 的一致性如何保证?
|
||||
|
||||
### 分布式会话
|
||||
## 分布式会话
|
||||
|
||||
- 集群部署时的分布式 Session 如何实现?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 分布式服务接口请求的顺序性如何保证?
|
||||
|
||||
## 面试题
|
||||
|
||||
分布式服务接口请求的顺序性如何保证?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 分布式事务原理
|
||||
|
||||
## 面试题
|
||||
|
||||
分布式事务了解吗?你们是如何解决分布式事务问题的?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Dubbo 负载均衡策略和集群容错策略
|
||||
|
||||
## 面试题
|
||||
|
||||
dubbo 负载均衡策略和集群容错策略都有哪些?动态代理策略呢?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Dubbo 的工作原理
|
||||
|
||||
## 面试题
|
||||
|
||||
说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 设计一个类似 Dubbo 的 RPC 框架
|
||||
|
||||
## 面试题
|
||||
|
||||
如何自己设计一个类似 Dubbo 的 RPC 框架?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Dubbo 的序列化协议
|
||||
|
||||
## 面试题
|
||||
|
||||
dubbo 支持哪些通信协议?支持哪些序列化协议?说一下 Hessian 的数据结构?PB 知道吗?为什么 PB 的效率是最高的?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何基于 Dubbo 进行服务治理
|
||||
|
||||
## 面试题
|
||||
|
||||
如何基于 dubbo 进行服务治理、服务降级、失败重试以及超时重试?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Dubbo 的 SPI 机制
|
||||
|
||||
## 面试题
|
||||
|
||||
dubbo 的 spi 思想是什么?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 为什么要拆分系统?
|
||||
|
||||
## 面试题
|
||||
|
||||
为什么要进行系统拆分?如何进行系统拆分?拆分后不用 dubbo 可以吗?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Zookeeper 的使用场景
|
||||
|
||||
## 面试题
|
||||
|
||||
zookeeper 都有哪些使用场景?
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## 电商网站的商品详情页系统架构
|
||||
# 电商网站的商品详情页系统架构
|
||||
|
||||
### 小型电商网站的商品详情页系统架构
|
||||
## 小型电商网站的商品详情页系统架构
|
||||
|
||||
小型电商网站的页面展示采用页面全量静态化的思想。数据库中存放了所有的商品信息,页面静态化系统,将数据填充进静态模板中,形成静态化页面,推入 Nginx 服务器。用户浏览网站页面时,取用一个已经静态化好的 html 页面,直接返回回去,不涉及任何的业务逻辑处理。
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
**坏处**在于,仅仅适用于一些小型的网站,比如页面的规模在几十到几万不等。对于一些大型的电商网站,亿级数量的页面,你说你每次页面模板修改了,都需要将这么多页面全量静态化,靠谱吗?每次渲染花个好几天时间,那你整个网站就废掉了。
|
||||
|
||||
### 大型电商网站的商品详情页系统架构
|
||||
## 大型电商网站的商品详情页系统架构
|
||||
|
||||
大型电商网站商品详情页的系统设计中,当商品数据发生变更时,会将变更消息压入 MQ 消息队列中。**缓存服务**从消息队列中消费这条消息时,感知到有数据发生变更,便通过调用数据服务接口,获取变更后的数据,然后将整合好的数据推送至 redis 中。Nginx 本地缓存的数据是有一定的时间期限的,比如说 10 分钟,当数据过期之后,它就会从 redis 获取到最新的缓存数据,并且缓存到自己本地。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 深入 Hystrix 断路器执行原理
|
||||
# 深入 Hystrix 断路器执行原理
|
||||
|
||||
### 状态机
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## Hystrix 隔离策略细粒度控制
|
||||
# Hystrix 隔离策略细粒度控制
|
||||
|
||||
Hystrix 实现资源隔离,有两种策略:
|
||||
|
||||
@@ -7,7 +7,7 @@ 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。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 基于本地缓存的 fallback 降级机制
|
||||
# 基于本地缓存的 fallback 降级机制
|
||||
|
||||
Hystrix 出现以下四种情况,都会去调用 fallback 降级机制:
|
||||
|
||||
@@ -7,7 +7,7 @@ Hystrix 出现以下四种情况,都会去调用 fallback 降级机制:
|
||||
- Hystrix 调用各种接口,或者访问外部依赖,比如 MySQL、Redis、Zookeeper、Kafka 等等,出现了任何异常的情况。
|
||||
- 访问外部依赖的时候,访问时间过长,报了 TimeoutException 异常。
|
||||
|
||||
### 两种最经典的降级机制
|
||||
## 两种最经典的降级机制
|
||||
|
||||
- 纯内存数据<br>
|
||||
在降级逻辑中,你可以在内存中维护一个 ehcache,作为一个纯内存的基于 LRU 自动清理的缓存,让数据放在缓存内。如果说外部依赖有异常,fallback 这里直接尝试从 ehcache 中获取数据。
|
||||
@@ -23,7 +23,7 @@ Hystrix 出现以下四种情况,都会去调用 fallback 降级机制:
|
||||
|
||||
假如说,品牌服务接口挂掉了,那么我们可以尝试从本地内存中,获取一份稍过期的数据,先凑合着用。
|
||||
|
||||
### 步骤一:本地缓存获取数据
|
||||
## 步骤一:本地缓存获取数据
|
||||
|
||||
本地获取品牌名称的代码大致如下。
|
||||
|
||||
@@ -51,7 +51,7 @@ public class BrandCache {
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤二:实现 GetBrandNameCommand
|
||||
## 步骤二:实现 GetBrandNameCommand
|
||||
|
||||
在 GetBrandNameCommand 中,run() 方法的正常逻辑是去调用品牌服务的接口获取到品牌名称,如果调用失败,报错了,那么就会去调用 fallback 降级机制。
|
||||
|
||||
@@ -95,7 +95,7 @@ public class GetBrandNameCommand extends HystrixCommand<String> {
|
||||
|
||||
`FallbackIsolationSemaphoreMaxConcurrentRequests` 用于设置 fallback 最大允许的并发请求量,默认值是 10,是通过 semaphore 信号量的机制去限流的。如果超出了这个最大值,那么直接 reject。
|
||||
|
||||
### 步骤三:CacheController 调用接口
|
||||
## 步骤三:CacheController 调用接口
|
||||
|
||||
在 CacheController 中,我们通过 productInfo 获取 brandId,然后创建 GetBrandNameCommand 并执行,去尝试获取 brandName。这里执行会报错,因为我们在 run() 方法中直接抛出异常,Hystrix 就会去调用 getFallback() 方法走降级逻辑。
|
||||
|
||||
|
||||
@@ -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,7 +22,7 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外
|
||||
|
||||
[2018 年 11 月,Hystrix 在其 Github 主页宣布,不再开放新功能,推荐开发者使用其他仍然活跃的开源项目](https://github.com/Netflix/Hystrix/blob/master/README.md#hystrix-status)。维护模式的转变绝不意味着 Hystrix 不再有价值。相反,Hystrix 激发了很多伟大的想法和项目,我们高可用的这一块知识还是会针对 Hystrix 进行讲解。
|
||||
|
||||
### Hystrix 的设计原则
|
||||
## Hystrix 的设计原则
|
||||
|
||||
- 对依赖服务调用时出现的调用延迟和调用失败进行**控制和容错保护**。
|
||||
- 在复杂的分布式系统中,阻止某一个依赖服务的故障在整个系统中蔓延。比如某一个服务故障了,导致其它服务也跟着故障。
|
||||
@@ -40,7 +40,7 @@ Hystrix 是高可用性保障的一个框架。Netflix(可以认为是国外
|
||||
|
||||
Hystrix 可以对其进行资源隔离,比如限制服务 B 只有 40 个线程调用服务 C。当此 40 个线程被 hang 住时,其它 60 个线程依然能正常调用工作。从而确保整个系统不会被拖垮。
|
||||
|
||||
### Hystrix 更加细节的设计原则
|
||||
## Hystrix 更加细节的设计原则
|
||||
|
||||
- 阻止任何一个依赖服务耗尽所有的资源,比如 tomcat 中的所有线程资源。
|
||||
- 避免请求排队和积压,采用限流和 `fail fast` 来控制故障。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 深入 Hystrix 执行时内部原理
|
||||
# 深入 Hystrix 执行时内部原理
|
||||
|
||||
前面我们了解了 Hystrix 最基本的支持高可用的技术:**资源隔离** + **限流**。
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||

|
||||
|
||||
### 步骤一:创建 command
|
||||
## 步骤一:创建 command
|
||||
|
||||
一个 HystrixCommand 或 HystrixObservableCommand 对象,代表了对某个依赖服务发起的一次请求或者调用。创建的时候,可以在构造函数中传入任何需要的参数。
|
||||
|
||||
@@ -29,7 +29,7 @@ HystrixCommand hystrixCommand = new HystrixCommand(arg1, arg2);
|
||||
HystrixObservableCommand hystrixObservableCommand = new HystrixObservableCommand(arg1, arg2);
|
||||
```
|
||||
|
||||
### 步骤二:调用 command 执行方法
|
||||
## 步骤二:调用 command 执行方法
|
||||
|
||||
执行 command,就可以发起一次对依赖服务的调用。
|
||||
|
||||
@@ -69,21 +69,21 @@ final Future<R> 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。
|
||||
|
||||
@@ -129,13 +129,13 @@ observable.subscribe(new Observer<ProductInfo>() {
|
||||
|
||||
如果没有 timeout,也正常执行的话,那么调用线程就会拿到一些调用依赖服务获取到的结果,然后 Hystrix 也会做一些 logging 记录和 metric 度量统计。
|
||||
|
||||
### 步骤七:断路健康检查
|
||||
## 步骤七:断路健康检查
|
||||
|
||||
Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout 等事件发送给 circuit breaker 断路器。断路器就会对这些事件的次数进行统计,根据异常事件发生的比例来决定是否要进行断路(熔断)。如果打开了断路器,那么在接下来一段时间内,会直接断路,返回降级结果。
|
||||
|
||||
如果在之后,断路器尝试执行 command,调用没有出错,返回了正常结果,那么 Hystrix 就会把断路器关闭。
|
||||
|
||||
### 步骤八:调用 fallback 降级机制
|
||||
## 步骤八:调用 fallback 降级机制
|
||||
|
||||
在以下几种情况中,Hystrix 会调用 fallback 降级机制。
|
||||
|
||||
@@ -160,7 +160,7 @@ Hystrix 会把每一个依赖服务的调用成功、失败、Reject、Timeout
|
||||
- 对于 observe(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
|
||||
- 对于 toObservable(),返回一个 Observable 对象,但是调用 subscribe() 方法订阅它时,立即抛出调用者的 onError() 方法。
|
||||
|
||||
### 不同的执行方式
|
||||
## 不同的执行方式
|
||||
|
||||
- execute(),获取一个 Future.get(),然后拿到单个结果。
|
||||
- queue(),返回一个 Future。
|
||||
|
||||
@@ -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<ProductInfo> {
|
||||
|
||||
这里写了一个 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(),手动将缓存删除。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 基于 Hystrix 信号量机制实现资源隔离
|
||||
# 基于 Hystrix 信号量机制实现资源隔离
|
||||
|
||||
Hystrix 里面核心的一项功能,其实就是所谓的**资源隔离**,要解决的最最核心的问题,就是将多个依赖服务的调用分别隔离到各自的资源池内。避免说对某一个依赖服务的调用,因为依赖服务的接口调用的延迟或者失败,导致服务所有的线程资源全部耗费在这个服务的接口调用上。一旦说某个服务的线程资源全部耗尽的话,就可能导致服务崩溃,甚至说这种故障会不断蔓延。
|
||||
|
||||
@@ -11,13 +11,13 @@ Hystrix 实现资源隔离,主要有两种技术:
|
||||
|
||||
前面已经说过线程池技术了,这一小节就来说说信号量机制实现资源隔离,以及这两种技术的区别与具体应用场景。
|
||||
|
||||
### 信号量机制
|
||||
## 信号量机制
|
||||
|
||||
信号量的资源隔离只是起到一个开关的作用,比如,服务 A 的信号量大小为 10,那么就是说它同时只允许有 10 个 tomcat 线程来访问服务 A,其它的请求都会被拒绝,从而达到资源隔离和限流保护的作用。
|
||||
|
||||

|
||||
|
||||
### 线程池与信号量区别
|
||||
## 线程池与信号量区别
|
||||
|
||||
线程池隔离技术,并不是说去控制类似 tomcat 这种 web 容器的线程。更加严格的意义上来说,Hystrix 的线程池隔离技术,控制的是 tomcat 线程的执行。Hystrix 线程池满后,会确保说,tomcat 的线程不会因为依赖服务的接口调用延迟或故障而被 hang 住,tomcat 其它的线程不会卡死,可以快速返回,然后支撑其它的事情。
|
||||
|
||||
@@ -30,7 +30,7 @@ Hystrix 实现资源隔离,主要有两种技术:
|
||||
- **线程池技术**,适合绝大多数场景,比如说我们对依赖服务的网络请求的调用和访问、需要对调用的 timeout 进行控制(捕捉 timeout 超时异常)。
|
||||
- **信号量技术**,适合说你的访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,并且系统内部的代码,其实不涉及任何的网络请求,那么只要做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题。
|
||||
|
||||
### 信号量简单 Demo
|
||||
## 信号量简单 Demo
|
||||
|
||||
业务背景里,比较适合信号量的是什么场景呢?
|
||||
|
||||
|
||||
@@ -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,7 +18,7 @@ Hystrix 采用了 Bulkhead Partition 舱壁隔离技术,来将外部依赖进
|
||||
|
||||
Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果对那个外部依赖调用延迟很严重,最多就是耗尽那个依赖自己的线程池而已,不会影响其他的依赖调用。
|
||||
|
||||
### Hystrix 应用线程池机制的场景
|
||||
## Hystrix 应用线程池机制的场景
|
||||
|
||||
- 每个服务都会调用几十个后端依赖服务,那些后端依赖服务通常是由很多不同的团队开发的。
|
||||
- 每个后端依赖服务都会提供它自己的 client 调用库,比如说用 thrift 的话,就会提供对应的 thrift 依赖。
|
||||
@@ -34,7 +34,7 @@ Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果
|
||||
|
||||
简单来说,就是你必须默认 client 调用库很不靠谱,而且随时可能发生各种变化,所以就要用强制隔离的方式来确保任何服务的故障不会影响当前服务。
|
||||
|
||||
### 线程池机制的优点
|
||||
## 线程池机制的优点
|
||||
|
||||
- 任何一个依赖服务都可以被隔离在自己的线程池内,即使自己的线程池资源填满了,也不会影响任何其他的服务调用。
|
||||
- 服务可以随时引入一个新的依赖服务,因为即使这个新的依赖服务有问题,也不会影响其他任何服务的调用。
|
||||
@@ -44,7 +44,7 @@ Hystrix 对每个外部依赖用一个单独的线程池,这样的话,如果
|
||||
|
||||
简单来说,最大的好处,就是资源隔离,确保说任何一个依赖服务故障,不会拖垮当前的这个服务。
|
||||
|
||||
### 线程池机制的缺点
|
||||
## 线程池机制的缺点
|
||||
|
||||
- 线程池机制最大的缺点就是增加了 CPU 的开销。<br>
|
||||
除了 tomcat 本身的调用线程之外,还有 Hystrix 自己管理的线程池。
|
||||
@@ -58,7 +58,7 @@ semaphore 技术可以用来限流和削峰,但是不能用来对调用延迟
|
||||
|
||||
`execution.isolation.strategy` 设置为 `SEMAPHORE`,那么 Hystrix 就会用 semaphore 机制来替代线程池机制,来对依赖服务的访问进行限流。如果通过 semaphore 调用的时候,底层的网络调用延迟很严重,那么是无法 timeout 的,只能一直 block 住。一旦请求数量超过了 semaphore 限定的数量之后,就会立即开启限流。
|
||||
|
||||
### 接口限流 Demo
|
||||
## 接口限流 Demo
|
||||
|
||||
假设一个线程池大小为 8,等待队列的大小为 10。timeout 时长我们设置长一些,20s。
|
||||
|
||||
|
||||
@@ -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 拉回来。
|
||||
|
||||
|
||||
@@ -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。
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 动态扩缩容方案
|
||||
|
||||
## 面试题
|
||||
|
||||
如何设计可以动态扩容缩容的分库分表方案?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 主键 ID 如何处理?
|
||||
|
||||
## 面试题
|
||||
|
||||
分库分表之后,id 主键如何处理?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 分库分表如何平滑过渡?
|
||||
|
||||
## 面试题
|
||||
|
||||
现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表**动态切换**到分库分表上?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 为什么要分库分表?
|
||||
|
||||
## 面试题
|
||||
|
||||
为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# ES 的分布式架构原理
|
||||
|
||||
## 面试题
|
||||
|
||||
ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 搜索引擎介绍
|
||||
|
||||
## Lucene 和 ES 的前世今生
|
||||
|
||||
Lucene 是最先进、功能最强大的搜索库。如果直接基于 Lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# ES 查询性能优化
|
||||
|
||||
## 面试题
|
||||
|
||||
ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# ES 生产集群架构
|
||||
|
||||
## 面试题
|
||||
|
||||
ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# ES 写入数据原理
|
||||
|
||||
## 面试题
|
||||
|
||||
ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何设计一个高并发系统
|
||||
|
||||
## 面试题
|
||||
|
||||
如何设计一个高并发系统?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何保证消息队列的高可用?
|
||||
|
||||
## 面试题
|
||||
|
||||
如何保证消息队列的高可用?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何保证 redis 的高并发和高可用?
|
||||
|
||||
## 面试题
|
||||
|
||||
如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何保证消息不被重复消费?
|
||||
|
||||
## 面试题
|
||||
|
||||
如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何保证消息的顺序性?
|
||||
|
||||
## 面试题
|
||||
|
||||
如何保证消息的顺序性?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何保证消息的可靠性传输?
|
||||
|
||||
## 面试题
|
||||
|
||||
如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何设计一个消息队列?
|
||||
|
||||
## 面试题
|
||||
|
||||
如果让你写一个消息队列,该如何进行架构设计?说一下你的思路。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
## 消息队列面试场景
|
||||
# 消息队列面试场景
|
||||
|
||||
**面试官**:你好。
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何解决消息队列的延时以及过期失效问题?
|
||||
|
||||
## 面试题
|
||||
|
||||
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 如何实现读写分离?
|
||||
|
||||
## 面试题
|
||||
|
||||
你们有没有做 MySQL 读写分离?如何实现 MySQL 的读写分离?MySQL 主从复制原理的是啥?如何解决 MySQL 主从同步的延时问题?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 缓存雪崩、穿透和击穿
|
||||
|
||||
## 面试题
|
||||
|
||||
了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis 的并发竞争问题
|
||||
|
||||
## 面试题
|
||||
|
||||
Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis 集群模式原理
|
||||
|
||||
## 面试题
|
||||
|
||||
Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 缓存与数据库一致性问题
|
||||
|
||||
## 面试题
|
||||
|
||||
如何保证缓存与数据库的双写一致性?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis 数据类型和使用场景
|
||||
|
||||
## 面试题
|
||||
|
||||
Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis 的过期策略和 LRU 算法
|
||||
|
||||
## 面试题
|
||||
|
||||
Redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis 持久化机制
|
||||
|
||||
## 面试题
|
||||
|
||||
Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis 生产部署方案
|
||||
|
||||
## 面试题
|
||||
|
||||
生产环境中的 Redis 是怎么部署的?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis rehash 过程
|
||||
|
||||
## 面试题
|
||||
|
||||
有了解过 Redis rehash 的过程吗?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Redis 和 Memcached 的区别
|
||||
|
||||
## 面试题
|
||||
|
||||
Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么 Redis 单线程却能支撑高并发?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 缓存的使用方式
|
||||
|
||||
## 面试题
|
||||
|
||||
项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果?
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# 为什么使用消息队列?
|
||||
|
||||
## 面试题
|
||||
|
||||
- 为什么使用消息队列?
|
||||
|
||||
35
docs/index.md
Normal file
35
docs/index.md
Normal file
@@ -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
|
||||
---
|
||||
|
||||
@@ -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 统 计和监控。
|
||||
|
||||
# 总结
|
||||
## 总结
|
||||
|
||||
构建复杂的应用真的是非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。
|
||||
|
||||
@@ -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 也有限制。但是大家都不需要担心服务器,虚拟机或者容器内的任何方面绝对吸引人。
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# 1.1 微服务和分布式数据管理问题
|
||||
# 微服务的事件驱动数据管理
|
||||
|
||||
## 1.1 微服务和分布式数据管理问题
|
||||
|
||||
单体式应用一般都会有一个关系型数据库,由此带来的好处是应用可以使用 ACID transactions,可以带来一些重要的操作特性:
|
||||
|
||||
@@ -27,7 +29,7 @@
|
||||
|
||||
第二个挑战是如何完成从多个服务中搜索数据。例如,设想应用需要显示客户和他的订单。如果订单服务提供 API 来接受用户订单信息,那么用户可以使用类应用型的 join 操作接收数据。应用从用户服务接受用户信息,从订单服务接受此用户订单。假设,订单服务只支持通过私有键(key)来查询订单(也许是在使用只支持基于主键接受的 NoSQL 数据库),此时,没有合适的方法来接收所需数据。
|
||||
|
||||
# 1.2 事件驱动架构
|
||||
## 1.2 事件驱动架构
|
||||
|
||||
对许多应用来说,这个解决方案就是使用事件驱动架构(event-driven architecture)。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,也可能会引发更多的事件发布。
|
||||
|
||||
@@ -57,11 +59,11 @@
|
||||
|
||||
事件驱动架构也是既有优点也有缺点,此架构可以使得事务跨多个服务且提供最终一致性,并且可以使应用维护最终视图;而缺点在于编程模式比 ACID 事务模式更加复杂:为了从应用层级失效中恢复,还需要完成补偿性事务,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)事务造成的改变是可见的,另外当应用读取未更新的最终视图时也会遇见数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事件。
|
||||
|
||||
# 1.3 原子操作 Achieving Atomicity
|
||||
## 1.3 原子操作 Achieving Atomicity
|
||||
|
||||
事件驱动架构还会碰到数据库更新和发布事件原子性问题。例如,订单服务必须向 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,7 +105,7 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来
|
||||
|
||||
事件源方法也有不少缺点,因为采用不同或者不太熟悉的变成模式,使得重新学习不太容易;事件存储只支持主键查询业务实体,必须使用 Command Query Responsibility Segregation (CQRS) 来完成查询业务,因此,应用必须处理最终一致数据。
|
||||
|
||||
# 1.4 总结
|
||||
## 1.4 总结
|
||||
|
||||
在微服务架构中,每个微服务都有自己私有的数据集。不同微服务可能使用不同的 SQL 或者 NoSQL 数据库。尽管数据库架构有很强的优势,但是也面对数据分布式管理的挑战。第一个挑战就是如何在多服务之间维护业务事务一致性;第二个挑战是如何从多服务环境中获取一致性数据。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 微服务
|
||||
# 关于微服务架构的描述
|
||||
|
||||
> 翻译自 [Martin Fowler](https://martinfowler.com/) 网站 [Microservices](https://martinfowler.com/articles/microservices.html) 一文。文章篇幅较长,阅读需要一点耐心。<br> 本人水平有限,若有不妥之处,还请各位帮忙指正,谢谢。
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Martin Fowler 将这种现代化策略成为绞杀(Strangler)应用,名字
|
||||
|
||||
我们来看看其他可行策略。
|
||||
|
||||
# 策略 1——停止挖掘
|
||||
## 策略 1——停止挖掘
|
||||
|
||||
Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用不可管理时这是最佳建议。换句话说,应该停止让单体式应用继续变大,也就是说当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。如下图所示:
|
||||
|
||||
@@ -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 很有挑战性,尤其对使用域模型模式的业务逻辑层来说更具有挑战,因此经常需要改变代码来解决依赖性问题,如图所示:
|
||||
|
||||
|
||||
BIN
docs/public/favicon-16x16.png
Normal file
BIN
docs/public/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
docs/public/favicon-32x32.png
Normal file
BIN
docs/public/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
BIN
docs/public/icon.png
Normal file
BIN
docs/public/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
139
main.js
139
main.js
@@ -1,139 +0,0 @@
|
||||
const giscusTheme = () =>
|
||||
localStorage.getItem('DARK_LIGHT_THEME') === 'dark' ? 'noborder_dark' : 'light';
|
||||
|
||||
window.$docsify = {
|
||||
name: 'advanced-java',
|
||||
repo: 'doocs/advanced-java',
|
||||
maxLevel: 3,
|
||||
auto2top: true,
|
||||
coverpage: true,
|
||||
coverpage: 'docs/extra-page/cover.md',
|
||||
loadSidebar: 'summary.md',
|
||||
alias: {
|
||||
'/.*/.*/summary': 'summary.md',
|
||||
'/.*/summary.md': 'summary.md',
|
||||
},
|
||||
lastModifiedText: '最近更新时间:',
|
||||
pagination: {
|
||||
previousText: '上一篇',
|
||||
nextText: '下一篇',
|
||||
crossChapter: true,
|
||||
crossChapterText: true,
|
||||
},
|
||||
contributors: {
|
||||
repo: 'doocs/advanced-java',
|
||||
ignores: ['/README.md'],
|
||||
image: {
|
||||
margin: '0.2em',
|
||||
isRound: true,
|
||||
},
|
||||
},
|
||||
search: {
|
||||
maxAge: 1800000,
|
||||
paths: [
|
||||
'/docs/high-concurrency/',
|
||||
'/docs/distributed-system/',
|
||||
'/docs/high-availability/',
|
||||
'/docs/micro-services/',
|
||||
'/docs/big-data/',
|
||||
],
|
||||
depth: 3,
|
||||
},
|
||||
darklightTheme: {
|
||||
defaultTheme: 'light',
|
||||
siteFont: 'Source Sans Pro,Helvetica Neue,Arial,sans-serif',
|
||||
codeFontFamily: 'Roboto Mono, Monaco, courier, monospace',
|
||||
bodyFontSize: '15px',
|
||||
dark: {
|
||||
background: 'rgb(28,32,34)',
|
||||
highlightColor: '#e96900',
|
||||
codeBackgroundColor: 'rgb(34,39,46)',
|
||||
codeTextColor: '#b4b4b4',
|
||||
},
|
||||
light: {
|
||||
highlightColor: '#e96900',
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
function (hook, vm) {
|
||||
hook.beforeEach(function (content) {
|
||||
const { file, path } = vm.route;
|
||||
const en = file.indexOf('README_EN') > -1;
|
||||
if (/githubusercontent\.com/.test(file)) {
|
||||
url = file
|
||||
.replace(
|
||||
'raw.githubusercontent.com',
|
||||
'github.com',
|
||||
)
|
||||
.replace(/\/main/, '/blob/main');
|
||||
} else {
|
||||
url = `https://github.com/doocs/advanced-java/blob/main/${file}`;
|
||||
}
|
||||
|
||||
const github = `[GitHub](${url})`;
|
||||
const gitee = `[Gitee](${url.replace('github', 'gitee')})`;
|
||||
|
||||
const editHtml = en
|
||||
? `:memo: Edit on ${github} / ${gitee}\n`
|
||||
: `:memo: 在 ${github} / ${gitee} 编辑\n`;
|
||||
|
||||
if (path === '/') {
|
||||
return editHtml + content;
|
||||
}
|
||||
const subscription = `---
|
||||
## 公众号
|
||||
|
||||
[Doocs](https://github.com/doocs) 技术社区旗下唯一公众号「**Doocs**」,欢迎扫码关注,**专注分享技术领域相关知识及业内最新资讯**。当然,也可以加我个人微信(备注:GitHub),拉你进技术交流群。
|
||||
|
||||
关注「**Doocs**」公众号,回复 **PDF**,即可获取本项目离线 PDF 文档,学习更加方便!
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" style="width: 260px;">
|
||||
<img src="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/images/qrcode-for-doocs.png" style="width: 400px;"><br>
|
||||
</td>
|
||||
<td align="center" style="width: 260px;">
|
||||
<img src="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/images/qrcode-for-yanglbme.png" style="width: 400px;"><br>
|
||||
</td>
|
||||
</tr>
|
||||
</table>`;
|
||||
return editHtml + content + `\n` + subscription;
|
||||
});
|
||||
|
||||
hook.afterEach(function (html) {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const footer = `<footer><span>Copyright © 2018-${currentYear} <a href="https://github.com/doocs" target="_blank">Doocs</a>. All Rights Reserved.</footer>`;
|
||||
return html + footer;
|
||||
});
|
||||
hook.doneEach(() => {
|
||||
const giscusScript = document.createElement('script');
|
||||
giscusScript.type = 'text/javascript';
|
||||
giscusScript.async = true;
|
||||
giscusScript.setAttribute('src', 'https://giscus.app/client.js');
|
||||
giscusScript.setAttribute('data-repo', 'doocs/advanced-java');
|
||||
giscusScript.setAttribute('data-repo-id', 'MDEwOlJlcG9zaXRvcnkxNTE4MzQwNjI=');
|
||||
giscusScript.setAttribute('data-mapping', 'number');
|
||||
giscusScript.setAttribute('data-reactions-enabled', '1');
|
||||
giscusScript.setAttribute('data-strict', '1');
|
||||
giscusScript.setAttribute('data-emit-metadata', '0');
|
||||
giscusScript.setAttribute('data-input-position', 'top');
|
||||
giscusScript.setAttribute('crossorigin', 'anonymous');
|
||||
giscusScript.setAttribute('data-term', '9');
|
||||
giscusScript.setAttribute('data-lang', 'zh-CN');
|
||||
giscusScript.setAttribute('data-theme', giscusTheme());
|
||||
|
||||
document
|
||||
.getElementById('main')
|
||||
.insertBefore(giscusScript, document.getElementById('main').lastChild);
|
||||
|
||||
document.getElementById('docsify-darklight-theme').addEventListener('click', () => {
|
||||
const frame = document.querySelector('.giscus-frame');
|
||||
frame.contentWindow.postMessage(
|
||||
{ giscus: { setConfig: { theme: giscusTheme() } } },
|
||||
'https://giscus.app',
|
||||
);
|
||||
});
|
||||
})
|
||||
},
|
||||
],
|
||||
};
|
||||
4722
package-lock.json
generated
4722
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
32
package.json
32
package.json
@@ -1,27 +1,13 @@
|
||||
{
|
||||
"scripts": {
|
||||
"start": "docsify serve --open",
|
||||
"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"
|
||||
},
|
||||
"author": "yanglbme",
|
||||
"license": "CC-BY-SA-4.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/doocs/advanced-java/issues"
|
||||
},
|
||||
"homepage": "https://github.com/doocs/advanced-java#readme",
|
||||
"devDependencies": {
|
||||
"docsify-pdf-converter": "^2.0.7",
|
||||
"prettier": "2.8.8"
|
||||
"vitepress": "^1.6.3"
|
||||
},
|
||||
"scripts": {
|
||||
"docs:dev": "vitepress dev docs",
|
||||
"docs:build": "vitepress build docs",
|
||||
"docs:preview": "vitepress preview docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"vitepress-plugin-comment-with-giscus": "^1.1.15"
|
||||
}
|
||||
}
|
||||
|
||||
145
summary.md
145
summary.md
@@ -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/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/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)
|
||||
|
||||
* 海量数据处理
|
||||
- 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)
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"github": {
|
||||
"silent": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user