用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/personamanagmentlayer/pcl --skill redis-expert命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Expert in Persona Control Language (PCL) - language design, compiler architecture, runtime systems, and ecosystem development
Expert system for designing, creating, and validating PCL skills with comprehensive domain knowledge extraction
Expert-level Docker containerization, image optimization, and container orchestration. Use this skill for building efficient Docker images, managing containers, and implementing Docker best practices.
基于 SOC 职业分类
正在显示 SKILL.md
| name | redis-expert |
| version | 1.0.0 |
| description | Expert-level Redis for caching, pub/sub, data structures, and high-performance applications |
| category | data |
| tags | ["redis","cache","pubsub","inmemory","keyvalue","nosql"] |
| allowed-tools | ["Read","Write","Edit","Bash(redis-cli:*, docker:*)"] |
Expert guidance for Redis - the in-memory data structure store used as cache, message broker, and database with microsecond latency.
# Development
docker run --name redis -p 6379:6379 -d redis:7-alpine
# Production with persistence
docker run --name redis \
-p 6379:6379 \
-v redis-data:/data \
-d redis:7-alpine \
redis-server --appendonly yes --requirepass strongpassword
# Redis with config file
docker run --name redis \
-p 6379:6379 \
-v ./redis.conf:/usr/local/etc/redis/redis.conf \
-d redis:7-alpine \
redis-server /usr/local/etc/redis/redis.conf
# Network
bind 0.0.0.0
port 6379
protected-mode yes
# Security
requirepass strongpassword
# Memory
maxmemory 2gb
maxmemory-policy allkeys-lru
# Persistence
save 900 1 # Save after 900s if 1 key changed
save 300 10 # Save after 300s if 10 keys changed
save 60 10000 # Save after 60s if 10000 keys changed
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
# Replication
replica-read-only yes
repl-diskless-sync yes
# Performance
tcp-backlog 511
timeout 0
tcp-keepalive 300
import Redis from 'ioredis';
const redis = new Redis({
host: 'localhost',
port: 6379,
password: 'strongpassword',
db: 0,
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
});
// Strings
await redis.set('user:1000:name', 'Alice');
await redis.set('counter', 42);
await redis.get('user:1000:name'); // 'Alice'
// Expiration (TTL)
await redis.setex('session:abc123', 3600, JSON.stringify({ userId: 1000 }));
await redis.expire('user:1000:name', 300); // 5 minutes
await redis.ttl('user:1000:name');
redis.();
redis.();
redis.(, );
redis.();
redis.(, {
: ,
: ,
: ,
});
redis.(, );
redis.();
redis.(, , );
redis.(, , , );
redis.(, );
redis.();
redis.();
redis.(, , -);
redis.(, , , );
redis.();
redis.(, );
redis.();
redis.(, , , );
redis.(, );
redis.(, );
redis.(, );
redis.(, , , , , , );
redis.(, , -, );
redis.(, , );
redis.(, , );
redis.(, );
redis.(, );
// Cache helper
class CacheService {
constructor(private redis: Redis) {}
async get<T>(key: string): Promise<T | null> {
const data = await this.redis.get(key);
return data ? JSON.parse(data) : null;
}
async set(key: string, value: any, ttl: number = 3600): Promise<void> {
await this.redis.setex(key, ttl, JSON.stringify(value));
}
async delete(key: string): Promise<void> {
await this.redis.del(key);
}
async getOrSet<T>(
key: string,
factory: () => <T>,
: =
): <T> {
cached = .<T>(key);
(cached) cached;
fresh = ();
.(key, fresh, ttl);
fresh;
}
}
cache = (redis);
user = cache.(
,
() => db..(),
);
class RateLimiter {
constructor(private redis: Redis) {}
async checkRateLimit(
key: string,
limit: number,
window: number
): Promise<{ allowed: boolean; remaining: number }> {
const current = await this.redis.incr(key);
if (current === 1) {
await this.redis.expire(key, window);
}
return {
allowed: current <= limit,
remaining: Math.max(0, limit - current),
};
}
}
// Usage: 100 requests per hour per IP
const limiter = new RateLimiter(redis);
const result = await limiter.checkRateLimit(`ratelimit:${ip}`, 100, 3600);
if (!result.allowed) {
return res.().({ : });
}
async function slidingWindowRateLimit(
redis: Redis,
key: string,
limit: number,
window: number
): Promise<boolean> {
const now = Date.now();
const windowStart = now - window * 1000;
// Remove old entries
await redis.zremrangebyscore(key, 0, windowStart);
// Count requests in window
const count = await redis.zcard(key);
if (count < limit) {
// Add current request
await redis.zadd(key, now, `${now}-${Math.random()}`);
await redis.expire(key, window);
return true;
}
return false;
}
class RedisLock {
constructor(private redis: Redis) {}
async acquire(
resource: string,
ttl: number = 10000,
retryDelay: number = 50,
retryCount: number = 100
): Promise<string | null> {
const lockKey = `lock:${resource}`;
const lockValue = crypto.randomUUID();
for (let i = 0; i < retryCount; i++) {
const acquired = await this.redis.set(
lockKey,
lockValue,
'PX',
ttl,
'NX'
);
if (acquired === 'OK') {
return lockValue;
}
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
return null;
}
async release(resource: , : ): <> {
lockKey = ;
script = ;
result = ..(script, , lockKey, lockValue);
result === ;
}
withLock<T>(
: ,
: <T>,
: =
): <T> {
lockValue = .(resource, ttl);
(!lockValue) {
();
}
{
();
} {
.(resource, lockValue);
}
}
}
lock = (redis);
lock.(, () => {
data = ();
(data);
});
// Publisher
const publisher = new Redis();
await publisher.publish('notifications', JSON.stringify({
type: 'new_message',
userId: 1000,
message: 'Hello!',
}));
// Subscriber
const subscriber = new Redis();
subscriber.subscribe('notifications', (err, count) => {
console.log(`Subscribed to ${count} channels`);
});
subscriber.on('message', (channel, message) => {
const data = JSON.parse(message);
console.log(`Received from ${channel}:`, data);
});
// Pattern subscription
subscriber.psubscribe('user:*:notifications', (err, count) => {
console.log(`Subscribed to ${count} patterns`);
});
subscriber.on('pmessage', (pattern, channel, message) => {
.(, message);
});
subscriber.();
subscriber.();
// Add to stream
await redis.xadd(
'events',
'*', // Auto-generate ID
'type', 'user_registered',
'userId', '1000',
'email', 'alice@example.com'
);
// Read from stream
const messages = await redis.xread('COUNT', 10, 'STREAMS', 'events', '0');
/*
[
['events', [
['1609459200000-0', ['type', 'user_registered', 'userId', '1000']],
['1609459201000-0', ['type', 'order_placed', 'orderId', '500']]
]]
]
*/
// Consumer Groups
await redis.xgroup('CREATE', 'events', 'worker-group', '0', 'MKSTREAM');
// Read as consumer
const messages = await redis.xreadgroup(
'GROUP', 'worker-group', 'consumer-1',
'COUNT', 10,
'STREAMS', 'events', '>'
);
// Acknowledge message
await redis.xack('events', 'worker-group', '1609459200000-0');
// Pending messages
const pending = redis.(, );
// Multi/Exec (transaction)
const pipeline = redis.multi();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.incr('counter');
const results = await pipeline.exec();
// Watch (optimistic locking)
await redis.watch('balance:1000');
const balance = parseInt(await redis.get('balance:1000') || '0');
if (balance >= amount) {
const multi = redis.multi();
multi.decrby('balance:1000', amount);
multi.incrby('balance:2000', amount);
await multi.exec(); // Executes only if balance:1000 wasn't modified
} else {
await redis.unwatch();
}
// Pipeline multiple commands
const pipeline = redis.pipeline();
pipeline.set('key1', 'value1');
pipeline.set('key2', 'value2');
pipeline.get('key1');
pipeline.get('key2');
const results = await pipeline.exec();
// [[null, 'OK'], [null, 'OK'], [null, 'value1'], [null, 'value2']]
// Batch operations
async function batchSet(items: Record<string, string>) {
const pipeline = redis.pipeline();
for (const [key, value] of Object.entries(items)) {
pipeline.set(key, value);
}
await pipeline.exec();
}
// Atomic increment with max
const script = `
local current = redis.call('GET', KEYS[1])
local max = tonumber(ARGV[1])
if current and tonumber(current) >= max then
return tonumber(current)
else
return redis.call('INCR', KEYS[1])
end
`;
const result = await redis.eval(script, 1, 'counter', 100);
// Load script once, execute many times
const sha = await redis.script('LOAD', script);
const result = await redis.evalsha(sha, 1, 'counter', 100);
# Create 6 nodes (3 masters, 3 replicas)
for port in {7000..7005}; do
mkdir -p cluster/${port}
cat > cluster/${port}/redis.conf <<EOF
port ${port}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOF
redis-server cluster/${port}/redis.conf &
done
# Create cluster
redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
import Redis from 'ioredis';
const cluster = new Redis.Cluster([
{ host: '127.0.0.1', port: 7000 },
{ host: '127.0.0.1', port: 7001 },
{ host: '127.0.0.1', port: 7002 },
]);
// Operations work transparently
await cluster.set('key', 'value');
await cluster.get('key');
allkeys-lru: Remove least recently used keysallkeys-lfu: Remove least frequently used keysvolatile-lru: Remove LRU keys with expire setvolatile-ttl: Remove keys with shortest TTLINFO memory// Good: hierarchical, descriptive
'user:1000:profile'
'session:abc123'
'cache:api:users:page:1'
'ratelimit:ip:192.168.1.1:2024-01-19'
// Use consistent separators
const key = ['user', userId, 'profile'].join(':');
redis-cli --bigkeys# Monitor commands in real-time
redis-cli MONITOR
# Stats
redis-cli INFO
# Slow queries
redis-cli SLOWLOG GET 10
# Memory analysis
redis-cli --bigkeys
# Latency
redis-cli --latency
const redis = new Redis({
host: 'localhost',
port: 6379,
maxRetriesPerRequest: 3,
enableReadyCheck: true,
lazyConnect: true,
});
// ❌ Bad: Blocks entire server
const keys = await redis.keys('user:*');
// ✅ Good: Use SCAN for large datasets
async function* scanKeys(pattern: string) {
let cursor = '0';
do {
const [newCursor, keys] = await redis.scan(
cursor,
'MATCH',
pattern,
'COUNT',
100
);
cursor = newCursor;
yield* keys;
} while (cursor !== '0');
}
for await (const key of scanKeys('user:*')) {
console.log(key);
}
// Use hashes for objects instead of multiple keys
// ❌ Bad: 3 keys
await redis.set('user:1000:name', 'Alice');
await redis.set('user:1000:email', 'alice@example.com');
await redis.set('user:1000:age', '30');
// ✅ Good: 1 key
await redis.hset('user:1000', {
name: 'Alice',
email: 'alice@example.com',
age: '30',
});
❌ Using Redis as primary database: Use for caching/sessions ❌ Not setting TTL on cache keys: Causes memory bloat ❌ Using KEYS in production: Use SCAN instead ❌ Large values in keys: Keep values small (<1MB) ❌ No monitoring: Track memory, latency, hit rate ❌ Synchronous blocking operations: Use async operations ❌ Not handling connection failures: Implement retry logic ❌ Storing large collections in single key: Split into multiple keys
import session from 'express-session';
import RedisStore from 'connect-redis';
app.use(
session({
store: new RedisStore({ client: redis }),
secret: 'secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24, // 24 hours
},
})
);
import { Queue, Worker } from 'bullmq';
const queue = new Queue('emails', { connection: redis });
// Add job
await queue.add('send-email', {
to: 'user@example.com',
subject: 'Welcome',
body: 'Hello!',
});
// Process jobs
const worker = new Worker('emails', async (job) => {
await sendEmail(job.data);
}, { connection: redis });