Files
test-repo/docs/.vitepress/theme/components/appendix/cache-design/CachePatternsDemo.vue
T
sanbuphy 0eba9e87e9 fix(eslint): reduce warnings in GitHub Actions deployment
- Disable formatting rules (handled by Prettier)
- Relaxed strict Vue/JS rules for demo code compatibility
- Fix syntax errors in ApiPlayground and VoiceCloningDemo
- Fix duplicate else-if condition in ApiPlayground
- Fix Promise executor async pattern in AutoregressiveAudioDemo
- Add TypeScript file support to ESLint config

Warnings reduced from 295 to 251 problems.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 17:38:10 +08:00

927 lines
21 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.
<!--
CachePatternsDemo.vue
缓存模式演示 - Cache-Aside, Read-Through, Write-Behind
-->
<template>
<div class="cache-patterns-demo">
<div class="header">
<div class="title">
缓存模式 (Caching Patterns)
</div>
<div class="subtitle">
理解不同缓存读写模式的工作原理
</div>
</div>
<div class="pattern-selector">
<button
v-for="pattern in patterns"
:key="pattern.id"
class="pattern-btn"
:class="{ active: activePattern === pattern.id }"
@click="activePattern = pattern.id"
>
{{ pattern.name }}
</button>
</div>
<div class="pattern-content">
<!-- Cache-Aside -->
<div
v-if="activePattern === 'cache-aside'"
class="pattern-detail"
>
<div class="description">
<div class="pattern-title">
Cache-Aside (旁路缓存)
</div>
<div class="pattern-subtitle">
最常用的模式由应用代码控制缓存
</div>
<div class="pattern-points">
<div class="point">
<span class="icon">📖</span>
<div>
<strong>读取</strong>先查缓存没命中再查数据库然后写入缓存
</div>
</div>
<div class="point">
<span class="icon"></span>
<div>
<strong>更新</strong>先更新数据库然后<strong>删除</strong>缓存不是更新
</div>
</div>
</div>
</div>
<div class="diagram">
<div class="diagram-title">
读取流程
</div>
<div class="flow-chart">
<div
class="flow-step"
:class="{ active: flowStep >= 1 }"
>
<div class="step-number">
1
</div>
<div class="step-text">
查询缓存
</div>
</div>
<div class="flow-arrow">
</div>
<div class="flow-decision">
<div class="decision-label">
命中?
</div>
<div class="decision-branches">
<div
class="branch yes"
:class="{ active: flowStep >= 2 && cacheHit }"
>
<div class="branch-label">
</div>
<div class="branch-result">
返回数据
</div>
</div>
<div
class="branch no"
:class="{ active: flowStep >= 2 && !cacheHit }"
>
<div class="branch-label">
</div>
<div class="branch-steps">
<div
class="flow-step"
:class="{ active: flowStep >= 3 }"
>
<div class="step-number">
2
</div>
<div class="step-text">
查询数据库
</div>
</div>
<div class="flow-arrow">
</div>
<div
class="flow-step"
:class="{ active: flowStep >= 4 }"
>
<div class="step-number">
3
</div>
<div class="step-text">
写入缓存
</div>
</div>
<div class="flow-arrow">
</div>
<div
class="flow-step"
:class="{ active: flowStep >= 5 }"
>
<div class="step-number">
4
</div>
<div class="step-text">
返回数据
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="demo-controls">
<button
class="demo-btn"
:disabled="simulating"
@click="simulateCacheAside"
>
{{ simulating ? '模拟中...' : '模拟读取' }}
</button>
<label class="checkbox">
<input
v-model="cacheHit"
type="checkbox"
>
缓存命中
</label>
</div>
</div>
<div class="code-example">
<div class="code-title">
代码示例
</div>
<pre class="code-block"><code>// Cache-Aside 模式
def get_user(user_id):
# 1. 查缓存
user = cache.get(f'user:{user_id}')
if user:
return user # 命中直接返回
# 2. 查数据库
user = db.query(f'SELECT * FROM users WHERE id = {user_id}')
# 3. 写入缓存
cache.set(f'user:{user_id}', user, ttl=600)
return user
def update_user(user_id, data):
# 1. 更新数据库
db.update('users', data)
# 2. 删除缓存不是更新
cache.delete(f'user:{user_id}')</code></pre>
</div>
</div>
<!-- Read-Through -->
<div
v-if="activePattern === 'read-through'"
class="pattern-detail"
>
<div class="description">
<div class="pattern-title">
Read-Through / Write-Through
</div>
<div class="pattern-subtitle">
由缓存库负责与数据库交互应用只和缓存打交道
</div>
<div class="pattern-points">
<div class="point">
<span class="icon">📖</span>
<div>
<strong>Read-Through</strong>缓存库自动从数据库加载数据
</div>
</div>
<div class="point">
<span class="icon"></span>
<div>
<strong>Write-Through</strong>写入缓存时同步写入数据库
</div>
</div>
</div>
</div>
<div class="diagram">
<div class="diagram-title">
架构对比
</div>
<div class="architecture-comparison">
<div class="arch-block">
<div class="arch-title">
Cache-Aside
</div>
<div class="arch-flow">
<div class="flow-box app">
应用
</div>
<div class="flow-arrows">
<div> 缓存</div>
<div> 数据库</div>
</div>
</div>
</div>
<div class="arch-block">
<div class="arch-title">
Read-Through
</div>
<div class="arch-flow">
<div class="flow-box app">
应用
</div>
<div class="flow-arrows">
<div> 缓存库</div>
</div>
<div class="flow-box cache">
缓存库 数据库
</div>
</div>
</div>
</div>
</div>
<div class="code-example">
<div class="code-title">
代码示例
</div>
<pre class="code-block"><code>// Read-Through 模式(代码更简洁)
def get_user(user_id):
# 缓存库自动处理数据库查询
user = cache.get_or_load(user_id, lambda: db.get_user(user_id))
return user
// Write-Through 模式
def update_user(user_id, data):
# 缓存库自动同步到数据库
cache.set(user_id, data) # 自动写入数据库</code></pre>
</div>
</div>
<!-- Write-Behind -->
<div
v-if="activePattern === 'write-behind'"
class="pattern-detail"
>
<div class="description">
<div class="pattern-title">
Write-Behind (异步写回)
</div>
<div class="pattern-subtitle">
写入时只写缓存异步批量写数据库
</div>
<div class="pattern-points">
<div class="point">
<span class="icon"></span>
<div><strong>优点</strong>写入极快适合写多的场景</div>
</div>
<div class="point">
<span class="icon"></span>
<div>
<strong>缺点</strong>数据可能丢失缓存崩了数据就没了
</div>
</div>
<div class="point">
<span class="icon">🎯</span>
<div>
<strong>适用</strong>秒杀系统点赞数浏览量可接受少量丢失
</div>
</div>
</div>
</div>
<div class="diagram">
<div class="diagram-title">
写入流程
</div>
<div class="flow-chart">
<div class="flow-step">
<div class="step-number">
1
</div>
<div class="step-text">
写入缓存
</div>
<div class="step-time">
~1ms
</div>
</div>
<div class="flow-arrow">
</div>
<div class="flow-step">
<div class="step-number">
2
</div>
<div class="step-text">
立即返回
</div>
</div>
<div class="flow-arrow">
</div>
<div class="flow-step pending">
<div class="step-number">
3
</div>
<div class="step-text">
异步批量写数据库
</div>
<div class="step-time">
🕐 后台执行
</div>
</div>
</div>
<div class="demo-controls">
<button
class="demo-btn"
@click="simulateWriteBehind"
>
模拟批量写入
</button>
</div>
<div
v-if="writeQueue.length > 0"
class="write-queue"
>
<div class="queue-title">
待写入队列
</div>
<div class="queue-items">
<div
v-for="(item, index) in writeQueue"
:key="index"
class="queue-item"
:class="{ writing: item.writing, written: item.written }"
>
<span class="item-key">{{ item.key }}</span>
<span class="item-status">{{ item.status }}</span>
</div>
</div>
</div>
</div>
<div class="code-example">
<div class="code-title">
代码示例
</div>
<pre class="code-block"><code>// Write-Behind 模式
def update_counter(post_id):
# 1. 立即更新缓存极快
cache.incr(f'views:{post_id}')
# 立即返回不等待数据库
# 2. 后台异步批量写入数据库
async def flush_to_db():
while True:
await asyncio.sleep(5) # 每5秒批量写入
batch = cache.get_many('views:*')
db.batch_update(batch)
asyncio.create_task(flush_to_db())</code></pre>
</div>
</div>
</div>
<div class="pattern-comparison">
<div class="comparison-title">
模式对比
</div>
<table class="comparison-table">
<thead>
<tr>
<th>模式</th>
<th>复杂度</th>
<th>性能</th>
<th>一致性</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr :class="{ highlight: activePattern === 'cache-aside' }">
<td>Cache-Aside</td>
<td></td>
<td></td>
<td></td>
<td>大多数场景</td>
</tr>
<tr :class="{ highlight: activePattern === 'read-through' }">
<td>Read-Through</td>
<td></td>
<td></td>
<td></td>
<td>简单场景</td>
</tr>
<tr :class="{ highlight: activePattern === 'write-behind' }">
<td>Write-Behind</td>
<td></td>
<td>极高</td>
<td></td>
<td>写多可丢失</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const activePattern = ref('cache-aside')
const flowStep = ref(0)
const cacheHit = ref(false)
const simulating = ref(false)
const writeQueue = ref([])
const patterns = [
{ id: 'cache-aside', name: 'Cache-Aside' },
{ id: 'read-through', name: 'Read-Through' },
{ id: 'write-behind', name: 'Write-Behind' }
]
const simulateCacheAside = async () => {
simulating.value = true
flowStep.value = 0
const steps = cacheHit.value ? [1, 2] : [1, 2, 3, 4, 5]
for (let i = 0; i < steps.length; i++) {
await new Promise((resolve) => setTimeout(resolve, 600))
flowStep.value = steps[i]
}
setTimeout(() => {
flowStep.value = 0
simulating.value = false
}, 1000)
}
const simulateWriteBehind = async () => {
writeQueue.value = [
{
key: 'views:post:1',
value: 100,
status: '待写入',
writing: false,
written: false
},
{
key: 'views:post:2',
value: 200,
status: '待写入',
writing: false,
written: false
},
{
key: 'views:post:3',
value: 150,
status: '待写入',
writing: false,
written: false
}
]
for (let i = 0; i < writeQueue.value.length; i++) {
await new Promise((resolve) => setTimeout(resolve, 800))
writeQueue.value[i].writing = true
writeQueue.value[i].status = '写入中...'
await new Promise((resolve) => setTimeout(resolve, 700))
writeQueue.value[i].writing = false
writeQueue.value[i].written = true
writeQueue.value[i].status = '✅ 已写入'
}
}
</script>
<style scoped>
.cache-patterns-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;
}
.title {
font-weight: 700;
font-size: 1.1rem;
}
.subtitle {
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.pattern-selector {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.pattern-btn {
padding: 0.75rem 1.5rem;
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-divider);
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.2s;
}
.pattern-btn.active {
background: var(--vp-c-brand);
color: white;
border-color: var(--vp-c-brand);
}
.pattern-content {
min-height: 400px;
}
.pattern-detail {
display: flex;
flex-direction: column;
gap: 2rem;
}
.description {
background: var(--vp-c-bg);
padding: 1.5rem;
border-radius: 10px;
border: 1px solid var(--vp-c-divider);
}
.pattern-title {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 0.5rem;
}
.pattern-subtitle {
color: var(--vp-c-text-2);
margin-bottom: 1rem;
}
.pattern-points {
display: flex;
flex-direction: column;
gap: 1rem;
}
.point {
display: flex;
gap: 1rem;
align-items: flex-start;
}
.icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.diagram {
background: var(--vp-c-bg);
padding: 1.5rem;
border-radius: 10px;
border: 1px solid var(--vp-c-divider);
}
.diagram-title {
font-weight: 600;
margin-bottom: 1rem;
font-size: 0.95rem;
}
.flow-chart {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.flow-step {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1.5rem;
background: var(--vp-c-bg-soft);
border-radius: 6px;
border: 2px solid var(--vp-c-divider);
transition: all 0.3s;
}
.flow-step.active {
border-color: var(--vp-c-brand);
background: #eff6ff;
}
.flow-step.pending {
border-color: #f59e0b;
background: #fef3c7;
}
.step-number {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
background: var(--vp-c-brand);
color: white;
border-radius: 50%;
font-weight: 700;
font-size: 0.85rem;
}
.step-text {
font-weight: 600;
font-size: 0.9rem;
}
.step-time {
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
.flow-arrow {
font-size: 1.5rem;
color: var(--vp-c-text-2);
}
.flow-decision {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.decision-label {
font-weight: 600;
padding: 0.5rem 1rem;
background: #fef3c7;
border-radius: 6px;
border: 1px solid #f59e0b;
}
.decision-branches {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.branch {
padding: 0.75rem;
border-radius: 6px;
border: 2px solid var(--vp-c-divider);
transition: all 0.3s;
}
.branch.active {
border-color: var(--vp-c-brand);
background: #eff6ff;
}
.branch-label {
font-weight: 600;
margin-bottom: 0.5rem;
text-align: center;
}
.branch-result {
text-align: center;
padding: 0.5rem;
background: #f0fdf4;
border-radius: 6px;
color: #166534;
font-weight: 600;
}
.branch-steps {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.demo-controls {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.demo-btn {
padding: 0.75rem 1.5rem;
background: var(--vp-c-brand);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: all 0.2s;
}
.demo-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.demo-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.checkbox {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
cursor: pointer;
}
.architecture-comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
}
@media (max-width: 640px) {
.architecture-comparison {
grid-template-columns: 1fr;
}
}
.arch-block {
padding: 0.75rem;
background: var(--vp-c-bg-soft);
border-radius: 6px;
}
.arch-title {
font-weight: 600;
margin-bottom: 1rem;
text-align: center;
}
.arch-flow {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.flow-box {
padding: 0.75rem 1.5rem;
background: white;
border-radius: 6px;
border: 2px solid var(--vp-c-divider);
font-weight: 600;
}
.flow-box.cache {
font-size: 0.85rem;
}
.flow-arrows {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
font-size: 0.85rem;
color: var(--vp-c-text-2);
}
.write-queue {
margin-top: 1rem;
}
.queue-title {
font-weight: 600;
margin-bottom: 0.75rem;
font-size: 0.9rem;
}
.queue-items {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.queue-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.75rem;
background: var(--vp-c-bg-soft);
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
transition: all 0.3s;
}
.queue-item.writing {
border-color: #f59e0b;
background: #fef3c7;
}
.queue-item.written {
border-color: #22c55e;
background: #f0fdf4;
}
.item-key {
font-weight: 600;
font-size: 0.85rem;
}
.item-status {
font-size: 0.8rem;
}
.code-example {
background: var(--vp-c-bg);
padding: 1.5rem;
border-radius: 10px;
border: 1px solid var(--vp-c-divider);
}
.code-title {
font-weight: 600;
margin-bottom: 1rem;
font-size: 0.95rem;
}
.code-block {
background: #1e293b;
color: #e2e8f0;
padding: 0.75rem;
border-radius: 6px;
overflow-x: auto;
font-size: 0.85rem;
line-height: 1.6;
}
.pattern-comparison {
background: var(--vp-c-bg);
padding: 1.5rem;
border-radius: 10px;
border: 1px solid var(--vp-c-divider);
}
.comparison-title {
font-weight: 600;
margin-bottom: 1rem;
font-size: 0.95rem;
}
.comparison-table {
width: 100%;
border-collapse: collapse;
}
.comparison-table th,
.comparison-table td {
padding: 0.75rem;
text-align: center;
border: 1px solid var(--vp-c-divider);
}
.comparison-table th {
background: var(--vp-c-bg-soft);
font-weight: 600;
font-size: 0.85rem;
}
.comparison-table td {
font-size: 0.85rem;
}
.comparison-table tr.highlight {
background: #eff6ff;
border-left: 3px solid var(--vp-c-brand);
}
</style>