Redis
基于内存的键值数据结构,用于数据库,缓存,消息代理。
1.特点
- 保存在内存速度快
- Redis支持持久化,断电不丢数据,对数据的更新异步保存到磁盘上
- 支持类型多
- 支持事务,所有操作都是原子性。
- 场景丰富,(1)消息处理,按key设置过期事件,(2)过期自动删除
2.场景
- 缓存
- 分布式锁:支持多台服务器横向部署,共用一份数据
- 自动过期
- 计数器和排行榜
- 秒杀的缓冲层
- 处理签到和其他状态(大数据处理)
- 发布/订阅
3.例子
1.聊天室实现
#注意该代码只使用的订阅发布,不能实现多服务器部署
const Koa = require('koa');
const app = new Koa();
const Router = require('koa-router');
const fs = require('fs');
const server = require('http').createServer(app.callback());
const io = require('socket.io')(server);
const redis = require('redis')
// 首页路由
const router = new Router();
router.get('/', ctx => {
ctx.response.type = 'html';
ctx.response.body = fs.createReadStream('./index.html');
});
app.use(router.routes());
const rclient = redis.createClient(6379, '127.0.0.1')
rclient.on("ready", err => {
console.log('client ready ....');
})
const publish = redis.createClient(6379, 'localhost')
publish.on('ready', err => {
console.log('publish ready ...')
})
// socket连接
io.on('connection', (socket) => {
// redis模式
// 订阅
rclient.subscribe('chat')
// 收到redis的发布,开始处理消息给客户端
rclient.on('message', (channel, msg) => {
io.emit('chat message', msg)//通过socket发送消息给客户端
})
socket.on('chat message', msg => {
console.log('receive message:' + msg)
publish.publish('chat', msg)//这里通过redis发布消息
})
socket.on('disconnect', () => {
console.log('user disconnected');
})
});
// 监听端口
server.listen(3000, () => {
console.log('listening on *:3000');
});
测试地址:
- 初始化商品数据 http://localhost:3000
2.实现秒杀
index.js
//该代码可以用于多服务器部署
const Koa = require("koa");
const app = new Koa();
const Router = require("koa-router");
const redis = require("redis");
const redisClient = redis.createClient(6379, "localhost");
const wrapper = require("co-redis");
const client = wrapper(redisClient);
client.on("ready", () => {
console.log("redis ready ...");
});
// 首页路由
const router = new Router();
router.get("/", (ctx) => {
ctx.response.type = "html";
ctx.response.body = fs.createReadStream("./index.html");
});
router.get("/create", async (ctx) => {
// 清空商品
await client.ltrim("goods", -1, 0);
// 添加30个商品
new Array(30).fill().forEach(async (v, i) => {
await client.rpush("goods", i);
console.log("添加商品:", i);
});
// redis llen
const num = await client.llen("goods");
console.log("抢购商品数量:", num);
ctx.body = {
ok: 1,
};
});
/**
* 秒杀
*/
router.get("/buy", async (ctx) => {
// 产生一个随机数当做用户id
const uid = (Math.random() * 9999999).toFixed();
let pid = await client.lpop("goods");
// 判断库存
if (pid) {
await client.hset("orders", pid, uid);
console.log("订单生成", pid, uid);
}else {
console.log('已抢光')
}
ctx.body = { ok: 1 };
});
router.get("/order", async (ctx) => {
const keys = await client.hkeys("orders");
console.log('订单列表')
console.log('===========')
const orders = await client.hgetall('orders')
for (k of keys) {
console.log(`${k} => ${await client.hget("orders", k)}`);
}
ctx.body = {
orders
};
});
router.get('/order/clear',async ctx => {
const keys = await client.hkeys("orders");
for (k of keys) {
console.log(`删除订单: ${k} => ${await client.hdel("orders", k)}`);
}
ctx.body = {ok : 1}
})
app.use(router.routes());
// 监听端口
app.listen(3000, () => {
console.log("listening on *:3000");
});
// node index.js //启动服务
test.js 测试代码 ,使用autocannon创建100个buy请求
(async () => {
const autocannon = require("autocannon");
const result = await autocannon({
url: "http://localhost:3000/buy",
connections: 100, //default
pipelining: 1, // default
duration: 1, // default
});
console.log('秒杀完成');
})()
// node test.js //启动测试代码
测试地址:
- 初始化商品数据 http://localhost:3000/create
- 测试购买 http://localhost:3000/buy
- 测试查看购买的订单 http://localhost:3000/order
- 清空所有订单 http://localhost:3000/order/clear