Memcached读取命令 #

一、读取命令概述 #

1.1 读取命令列表 #

text
Memcached提供以下读取命令:

get      - 获取数据
gets     - 获取数据和CAS ID

1.2 命令格式 #

text
get命令格式:
get <key>*\r\n

gets命令格式:
gets <key>*\r\n

参数说明:
key - 键名,可以指定多个

响应格式:
VALUE <key> <flags> <bytes> [<cas unique>]\r\n
<data block>\r\n
END\r\n

二、get命令 #

2.1 命令说明 #

text
get命令:
- 获取指定键的数据
- 可以同时获取多个键
- 键不存在时不返回数据

语法:
get <key>*\r\n

响应:
VALUE <key> <flags> <bytes>\r\n
<data block>\r\n
END\r\n

2.2 获取单个数据 #

bash
# 存储数据
set user:1001 0 3600 45
{"id":1001,"name":"John","email":"john@example.com"}
STORED

# 获取数据
get user:1001
VALUE user:1001 0 45
{"id":1001,"name":"John","email":"john@example.com"}
END

# 获取不存在的数据
get user:9999
END

2.3 获取多个数据 #

bash
# 存储多个数据
set user:1001 0 3600 45
{"id":1001,"name":"John","email":"john@example.com"}
STORED

set user:1002 0 3600 45
{"id":1002,"name":"Jane","email":"jane@example.com"}
STORED

set user:1003 0 3600 45
{"id":1003,"name":"Bob","email":"bob@example.com"}
STORED

# 获取多个数据
get user:1001 user:1002 user:1003
VALUE user:1001 0 45
{"id":1001,"name":"John","email":"john@example.com"}
VALUE user:1002 0 45
{"id":1002,"name":"Jane","email":"jane@example.com"}
VALUE user:1003 0 45
{"id":1003,"name":"Bob","email":"bob@example.com"}
END

# 部分键不存在
get user:1001 user:9999 user:1002
VALUE user:1001 0 45
{"id":1001,"name":"John","email":"john@example.com"}
VALUE user:1002 0 45
{"id":1002,"name":"Jane","email":"jane@example.com"}
END

2.4 响应格式解析 #

text
响应格式:
VALUE <key> <flags> <bytes>\r\n
<data block>\r\n
END\r\n

字段说明:
key    - 键名
flags  - 标志位(存储时设置的值)
bytes  - 数据字节数
data   - 实际数据内容

示例解析:
VALUE user:1001 0 45
{"id":1001,"name":"John","email":"john@example.com"}
END

key    = user:1001
flags  = 0
bytes  = 45
data   = {"id":1001,"name":"John","email":"john@example.com"}

三、gets命令 #

3.1 命令说明 #

text
gets命令:
- 获取数据和CAS ID
- 用于实现乐观锁
- 可以同时获取多个键

语法:
gets <key>*\r\n

响应:
VALUE <key> <flags> <bytes> <cas unique>\r\n
<data block>\r\n
END\r\n

3.2 获取单个数据 #

bash
# 存储数据
set user:1001 0 3600 45
{"id":1001,"name":"John","email":"john@example.com"}
STORED

# 获取数据和CAS ID
gets user:1001
VALUE user:1001 0 45 123
{"id":1001,"name":"John","email":"john@example.com"}
END

# CAS ID = 123

3.3 获取多个数据 #

bash
# 存储多个数据
set user:1001 0 3600 45
{"id":1001,"name":"John","email":"john@example.com"}
STORED

set user:1002 0 3600 45
{"id":1002,"name":"Jane","email":"jane@example.com"}
STORED

# 获取多个数据和CAS ID
gets user:1001 user:1002
VALUE user:1001 0 45 123
{"id":1001,"name":"John","email":"john@example.com"}
VALUE user:1002 0 45 124
{"id":1002,"name":"Jane","email":"jane@example.com"}
END

3.4 CAS ID的作用 #

text
CAS ID(Compare And Swap ID):
- 每次数据更新时递增
- 用于检测数据是否被修改
- 实现乐观锁机制

示例:
1. 客户端A获取数据,CAS ID = 100
2. 客户端B获取数据,CAS ID = 100
3. 客户端A更新数据,CAS ID变为101
4. 客户端B使用CAS ID = 100更新,失败(ID不匹配)

四、批量读取优化 #

4.1 批量读取优势 #

text
批量读取优势:

1. 减少网络往返
   - 一次请求获取多个数据
   - 降低延迟

2. 提高吞吐量
   - 减少连接开销
   - 提高效率

3. 简化代码
   - 一次调用获取所有数据
   - 减少代码复杂度

4.2 批量读取示例 #

bash
# 场景:获取用户列表

# 存储用户数据
set user:1001 0 3600 45
{"id":1001,"name":"John","email":"john@example.com"}
STORED
set user:1002 0 3600 45
{"id":1002,"name":"Jane","email":"jane@example.com"}
STORED
set user:1003 0 3600 45
{"id":1003,"name":"Bob","email":"bob@example.com"}
STORED
set user:1004 0 3600 45
{"id":1004,"name":"Alice","email":"alice@example.com"}
STORED
set user:1005 0 3600 45
{"id":1005,"name":"Charlie","email":"charlie@example.com"}
STORED

# 批量获取
get user:1001 user:1002 user:1003 user:1004 user:1005
VALUE user:1001 0 45
{"id":1001,"name":"John","email":"john@example.com"}
VALUE user:1002 0 45
{"id":1002,"name":"Jane","email":"jane@example.com"}
VALUE user:1003 0 45
{"id":1003,"name":"Bob","email":"bob@example.com"}
VALUE user:1004 0 45
{"id":1004,"name":"Alice","email":"alice@example.com"}
VALUE user:1005 0 45
{"id":1005,"name":"Charlie","email":"charlie@example.com"}
END

4.3 批量读取最佳实践 #

text
批量读取建议:

1. 控制批量大小
   - 建议每次不超过100个键
   - 避免响应过大

2. 处理部分命中
   - 检查返回的数据
   - 处理未命中的键

3. 使用Pipeline
   - 客户端支持Pipeline
   - 进一步提高性能

五、读取性能优化 #

5.1 使用UDP读取 #

text
UDP读取优势:
- 无连接开销
- 更快的响应

限制:
- 不保证可靠性
- 仅适合读取操作

启用UDP:
memcached -U 11211 -p 11211

5.2 客户端优化 #

text
客户端优化策略:

1. 连接池
   - 复用连接
   - 减少连接开销

2. Pipeline
   - 批量发送命令
   - 减少网络往返

3. 本地缓存
   - 缓存热点数据
   - 减少远程访问

4. 异步读取
   - 并发读取多个键
   - 提高吞吐量

5.3 读取示例代码 #

python
# Python示例
import pymemcache.client.base

# 创建客户端
client = pymemcache.client.base.Client(('localhost', 11211))

# 单个读取
user = client.get('user:1001')

# 批量读取
users = client.get_many(['user:1001', 'user:1002', 'user:1003'])

# 使用连接池
from pymemcache.client.base import PooledClient
client = PooledClient(('localhost', 11211), max_pool_size=10)

六、缓存穿透处理 #

6.1 缓存穿透问题 #

text
缓存穿透:
- 查询不存在的数据
- 每次都穿透到数据库
- 可能导致数据库压力过大

示例:
1. 查询 user:9999(不存在)
2. 缓存未命中
3. 查询数据库
4. 数据库也没有
5. 返回空
6. 下次查询继续穿透

6.2 解决方案 #

bash
# 方案1:缓存空值
set user:9999 0 300 0

# 空值过期时间较短
get user:9999
VALUE user:9999 0 0

END

# 方案2:使用特殊标记
set user:9991 0 300 4
NULL
STORED

get user:9991
VALUE user:9991 0 4
NULL
END

# 方案3:布隆过滤器(客户端实现)
# 先检查布隆过滤器,不存在直接返回

6.3 代码示例 #

python
def get_user(user_id):
    key = f"user:{user_id}"
    
    # 1. 查询缓存
    data = client.get(key)
    
    if data is not None:
        if data == b"NULL":
            # 缓存的空值
            return None
        return json.loads(data)
    
    # 2. 查询数据库
    user = db.query_user(user_id)
    
    if user:
        # 缓存数据
        client.set(key, json.dumps(user), expire=3600)
    else:
        # 缓存空值
        client.set(key, "NULL", expire=300)
    
    return user

七、缓存雪崩处理 #

7.1 缓存雪崩问题 #

text
缓存雪崩:
- 大量缓存同时过期
- 大量请求同时穿透到数据库
- 数据库压力瞬间增大

示例:
1. 系统启动时加载大量缓存
2. 所有缓存设置相同的过期时间(1小时)
3. 1小时后所有缓存同时过期
4. 大量请求同时查询数据库
5. 数据库崩溃

7.2 解决方案 #

python
import random

def set_cache_with_jitter(key, value, base_expire=3600):
    # 添加随机过期时间
    jitter = random.randint(0, 300)  # 0-5分钟随机值
    expire = base_expire + jitter
    
    client.set(key, value, expire=expire)

# 示例
for user in users:
    key = f"user:{user.id}"
    value = json.dumps(user)
    set_cache_with_jitter(key, value)

7.3 缓存预热 #

bash
# 系统启动时预热缓存

# 1. 加载热点数据
set hot:product:1001 0 3600 128
{"id":1001,"name":"iPhone","price":999}

set hot:product:1002 0 3600 128
{"id":1002,"name":"iPad","price":799}

# 2. 设置不同的过期时间
set hot:product:1001 0 3600 128
set hot:product:1002 0 3700 128
set hot:product:1003 0 3800 128

八、缓存击穿处理 #

8.1 缓存击穿问题 #

text
缓存击穿:
- 热点数据过期
- 大量请求同时访问
- 数据库压力瞬间增大

示例:
1. 热点商品缓存过期
2. 大量用户同时访问该商品
3. 所有请求穿透到数据库
4. 数据库压力增大

8.2 解决方案 #

python
import threading
import time

# 方案1:互斥锁
def get_user_with_lock(user_id):
    key = f"user:{user_id}"
    lock_key = f"lock:{key}"
    
    # 1. 查询缓存
    data = client.get(key)
    if data is not None:
        return json.loads(data)
    
    # 2. 获取锁
    if client.add(lock_key, "1", expire=10):
        try:
            # 3. 查询数据库
            user = db.query_user(user_id)
            
            # 4. 缓存数据
            if user:
                client.set(key, json.dumps(user), expire=3600)
            
            return user
        finally:
            # 5. 释放锁
            client.delete(lock_key)
    else:
        # 等待并重试
        time.sleep(0.1)
        return get_user_with_lock(user_id)

# 方案2:永不过期
def get_user_never_expire(user_id):
    key = f"user:{user_id}"
    
    # 1. 查询缓存
    data = client.get(key)
    if data is not None:
        value = json.loads(data)
        # 检查逻辑过期时间
        if value['expire_time'] > time.time():
            return value['data']
    
    # 2. 异步更新缓存
    async_update_cache(user_id)
    
    # 3. 返回旧数据或查询数据库
    if data is not None:
        return json.loads(data)['data']
    
    return db.query_user(user_id)

九、读取统计 #

9.1 查看读取统计 #

bash
# 查看统计信息
stats
STAT cmd_get 10000        # get命令次数
STAT get_hits 8000        # 命中次数
STAT get_misses 2000      # 未命中次数
END

# 计算命中率
命中率 = get_hits / (get_hits + get_misses)
命中率 = 8000 / 10000 = 80%

9.2 监控脚本 #

bash
#!/bin/bash
# monitor_hit_rate.sh

HOST="127.0.0.1"
PORT="11211"

# 获取统计
stats=$(echo "stats" | nc $HOST $PORT)

# 解析数据
get_hits=$(echo "$stats" | grep "STAT get_hits" | awk '{print $3}')
get_misses=$(echo "$stats" | grep "STAT get_misses" | awk '{print $3}')

# 计算命中率
if [ $((get_hits + get_misses)) -gt 0 ]; then
    hit_rate=$(echo "scale=2; $get_hits * 100 / ($get_hits + $get_misses)" | bc)
else
    hit_rate=0
fi

echo "缓存命中率: ${hit_rate}%"
echo "命中次数: $get_hits"
echo "未命中次数: $get_misses"

十、最佳实践 #

10.1 读取优化建议 #

text
读取优化建议:

1. 使用批量读取
   - 减少网络往返
   - 提高性能

2. 合理设置过期时间
   - 避免缓存雪崩
   - 添加随机值

3. 处理缓存穿透
   - 缓存空值
   - 使用布隆过滤器

4. 处理缓存击穿
   - 使用互斥锁
   - 热点数据永不过期

5. 监控命中率
   - 定期检查命中率
   - 优化缓存策略

10.2 键命名建议 #

bash
# 好的键命名
get user:1001
get product:2001:detail
get order:3001:items

# 不好的键命名
get 1001
get user_data_1001
get user-data-1001

10.3 数据格式建议 #

bash
# JSON格式(推荐)
set user:1001 0 3600 45
{"id":1001,"name":"John","email":"john@example.com"}

# 序列化格式(紧凑)
set user:1001 0 3600 30
<serialized data>

# 压缩格式(大数据)
set large:data 0 3600 100
<compressed data>

十一、总结 #

读取命令要点:

命令 说明 用途
get 获取数据 简单读取
gets 获取数据和CAS ID 乐观锁

最佳实践:

  1. 使用批量读取提高性能
  2. 处理缓存穿透、雪崩、击穿
  3. 监控缓存命中率
  4. 合理设置过期时间

下一步,让我们学习Memcached的删除与更新命令!

最后更新:2026-03-27