Files
test-repo/docs/.vitepress/theme/components/appendix/gateway-proxy/NginxArchitectureDemo.vue
T
sanbuphy 7c70c37072 feat(docs): add interactive demo components for technical appendices
Add placeholder Vue components for visualizing technical concepts across multiple domains including frontend routing, browser rendering, cache design, queue design, database principles, API design, cloud services, and backend evolution. These components provide interactive educational content for the documentation.

Update documentation structure to include new appendix sections and enhance existing content with visual components. Remove unused 'codex' dependency from package.json.
2026-02-06 03:34:50 +08:00

523 lines
11 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
NginxArchitectureDemo.vue
Nginx架构 - Master-Worker/事件驱动
-->
<template>
<div class="nginx-architecture-demo">
<div class="header">
<div class="title"> Nginx 架构揭秘为什么它能扛住百万并发</div>
<div class="subtitle">Master-Worker 进程模型 + 事件驱动 = 高性能的秘诀</div>
</div>
<div class="architecture-diagram">
<div class="diagram-title">Nginx 进程架构图</div>
<div class="process-layer master-layer">
<div class="process master">
<div class="process-icon">👑</div>
<div class="process-info">
<div class="process-name">Master 进程</div>
<div class="process-desc">管理所有 Worker负责配置加载平滑升级</div>
</div>
</div>
</div>
<div class="connections">
<div class="connection-line" v-for="n in workerCount" :key="n"></div>
</div>
<div class="process-layer worker-layer">
<div class="worker-controls">
<button class="control-btn" @click="decreaseWorker" :disabled="workerCount <= 1">-</button>
<span class="worker-count">{{ workerCount }} Worker</span>
<button class="control-btn" @click="increaseWorker" :disabled="workerCount >= 8">+</button>
</div>
<div class="workers">
<div
class="process worker"
v-for="n in workerCount"
:key="n"
:class="{ active: activeWorker === n, processing: processingWorkers.includes(n) }"
@click="activateWorker(n)"
>
<div class="process-icon"></div>
<div class="process-info">
<div class="process-name">Worker {{ n }}</div>
<div class="process-desc">处理 {{ requestCounts[n] || 0 }} 请求</div>
</div>
<div class="status-indicator"></div>
</div>
</div>
</div>
<div class="epoll-layer">
<div class="epoll-box">
<div class="epoll-title">📡 epoll (Linux) / kqueue (macOS)</div>
<div class="epoll-desc">事件驱动一个 Worker 同时处理数万个连接</div>
<div class="epoll-comparison">
<div class="compare-item old">
<div class="compare-title">传统 Apache</div>
<div class="compare-detail">一个连接 = 一个进程/线程</div>
<div class="compare-result"> C10K 问题</div>
</div>
<div class="vs">VS</div>
<div class="compare-item new">
<div class="compare-title">Nginx</div>
<div class="compare-detail">事件驱动 + 异步非阻塞</div>
<div class="compare-result"> 百万并发</div>
</div>
</div>
</div>
</div>
</div>
<div class="simulation-panel">
<div class="panel-title">🎮 模拟请求处理</div>
<div class="sim-controls">
<button class="sim-btn" @click="simulateRequests" :disabled="isSimulating">
{{ isSimulating ? '处理中...' : '发送 20 个并发请求' }}
</button>
<button class="sim-btn secondary" @click="resetSimulation">重置</button>
</div>
<div class="sim-stats" v-if="totalRequests > 0">
<div class="stat-item">
<div class="stat-value">{{ totalRequests }}</div>
<div class="stat-label">总请求数</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ mostActiveWorker }}</div>
<div class="stat-label">最忙 Worker</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ avgRequests.toFixed(1) }}</div>
<div class="stat-label">平均/Worker</div>
</div>
</div>
</div>
<div class="config-tip">
<div class="tip-title">💡 生产环境建议</div>
<div class="tip-content">
<strong>Worker 数量 = CPU 核心数</strong>通常设置为 auto Nginx 自动检测
<br>
太多了上下文切换开销大太少了无法利用多核性能
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const workerCount = ref(4)
const activeWorker = ref(1)
const requestCounts = ref({})
const isSimulating = ref(false)
const processingWorkers = ref([])
const totalRequests = computed(() => {
return Object.values(requestCounts.value).reduce((a, b) => a + b, 0)
})
const mostActiveWorker = computed(() => {
let max = 0
let worker = '-'
Object.entries(requestCounts.value).forEach(([k, v]) => {
if (v > max) {
max = v
worker = k
}
})
return worker
})
const avgRequests = computed(() => {
if (workerCount.value === 0) return 0
return totalRequests.value / workerCount.value
})
const increaseWorker = () => {
if (workerCount.value < 8) {
workerCount.value++
}
}
const decreaseWorker = () => {
if (workerCount.value > 1) {
workerCount.value--
}
}
const activateWorker = (n) => {
activeWorker.value = n
}
const simulateRequests = async () => {
isSimulating.value = true
const requests = 20
for (let i = 0; i < requests; i++) {
const worker = Math.floor(Math.random() * workerCount.value) + 1
processingWorkers.value.push(worker)
await new Promise(resolve => setTimeout(resolve, 100))
requestCounts.value[worker] = (requestCounts.value[worker] || 0) + 1
processingWorkers.value = processingWorkers.value.filter(w => w !== worker)
}
isSimulating.value = false
}
const resetSimulation = () => {
requestCounts.value = {}
processingWorkers.value = []
isSimulating.value = false
}
</script>
<style scoped>
.nginx-architecture-demo {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 12px;
padding: 1.5rem;
margin: 1.5rem 0;
font-family: var(--vp-font-family-base);
}
.header {
margin-bottom: 1.5rem;
text-align: center;
}
.title {
font-weight: 700;
font-size: 1.2rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.subtitle {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.architecture-diagram {
background: var(--vp-c-bg);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid var(--vp-c-divider);
}
.diagram-title {
font-weight: 700;
font-size: 1.1rem;
text-align: center;
margin-bottom: 1.5rem;
color: var(--vp-c-text-1);
}
.process-layer {
margin-bottom: 1.5rem;
}
.process {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.5rem;
border-radius: 12px;
transition: all 0.3s;
}
.process.master {
background: linear-gradient(135deg, #fef3c7, #fde68a);
border: 2px solid #f59e0b;
}
.process.worker {
background: var(--vp-c-bg-soft);
border: 2px solid var(--vp-c-divider);
cursor: pointer;
}
.process.worker:hover {
border-color: var(--vp-c-brand);
transform: translateY(-2px);
}
.process.worker.active {
border-color: var(--vp-c-brand);
background: rgba(var(--vp-c-brand-rgb), 0.1);
}
.process.worker.processing {
animation: pulse 0.5s ease-in-out;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.process-icon {
font-size: 2rem;
}
.process-info {
flex: 1;
}
.process-name {
font-weight: 700;
font-size: 1rem;
margin-bottom: 0.25rem;
}
.process-desc {
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background: #22c55e;
}
.worker-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1rem;
}
.control-btn {
width: 36px;
height: 36px;
border-radius: 50%;
border: 2px solid var(--vp-c-divider);
background: var(--vp-c-bg);
font-weight: 700;
font-size: 1.2rem;
cursor: pointer;
transition: all 0.2s;
}
.control-btn:hover:not(:disabled) {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
}
.control-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.worker-count {
font-weight: 600;
font-size: 1rem;
color: var(--vp-c-text-1);
}
.workers {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 0.75rem;
}
.epoll-layer {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 2px solid var(--vp-c-divider);
}
.epoll-box {
background: linear-gradient(135deg, rgba(var(--vp-c-brand-rgb), 0.1), rgba(var(--vp-c-brand-rgb), 0.05));
border: 2px solid var(--vp-c-brand);
border-radius: 12px;
padding: 1.5rem;
}
.epoll-title {
font-weight: 700;
font-size: 1.1rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.epoll-desc {
color: var(--vp-c-text-2);
margin-bottom: 1.5rem;
}
.epoll-comparison {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 1rem;
align-items: center;
}
.compare-item {
background: var(--vp-c-bg);
border-radius: 10px;
padding: 1rem;
text-align: center;
}
.compare-item.old {
border: 2px solid #ef4444;
}
.compare-item.new {
border: 2px solid #22c55e;
}
.compare-title {
font-weight: 700;
margin-bottom: 0.5rem;
}
.compare-detail {
font-size: 0.85rem;
color: var(--vp-c-text-2);
margin-bottom: 0.5rem;
}
.compare-result {
font-weight: 700;
font-size: 1.1rem;
}
.old .compare-result {
color: #ef4444;
}
.new .compare-result {
color: #22c55e;
}
.vs {
font-weight: 700;
font-size: 1.2rem;
color: var(--vp-c-text-2);
}
.simulation-panel {
background: var(--vp-c-bg);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
border: 1px solid var(--vp-c-divider);
}
.panel-title {
font-weight: 700;
font-size: 1.1rem;
margin-bottom: 1rem;
text-align: center;
}
.sim-controls {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 1rem;
}
.sim-btn {
padding: 0.75rem 1.5rem;
background: var(--vp-c-brand);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.sim-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(var(--vp-c-brand-rgb), 0.3);
}
.sim-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.sim-btn.secondary {
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
border: 1px solid var(--vp-c-divider);
}
.sim-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
margin-top: 1rem;
}
.stat-item {
text-align: center;
padding: 1rem;
background: var(--vp-c-bg-soft);
border-radius: 8px;
}
.stat-value {
font-weight: 700;
font-size: 1.5rem;
color: var(--vp-c-brand);
margin-bottom: 0.25rem;
}
.stat-label {
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.config-tip {
background: linear-gradient(135deg, rgba(34, 197, 94, 0.1), rgba(34, 197, 94, 0.05));
border: 2px solid #22c55e;
border-radius: 12px;
padding: 1.25rem;
}
.tip-title {
font-weight: 700;
font-size: 1.1rem;
margin-bottom: 0.75rem;
color: #15803d;
}
.tip-content {
color: var(--vp-c-text-1);
line-height: 1.7;
}
@media (max-width: 768px) {
.epoll-comparison {
grid-template-columns: 1fr;
}
.vs {
text-align: center;
}
.sim-stats {
grid-template-columns: 1fr;
}
.workers {
grid-template-columns: 1fr;
}
}
</style>