Redis

什么是 Redis

Redis 是一个开源(BSD 许可)的内存数据结构存储,用作数据库,缓存和消息代理。它支持数据结构,如字符串,散列,列表,集合,带有范围查询的排序集,位图,超级日志,具有半径查询和流的地理空间索引。Redis 具有内置复制,Lua 脚本,LRU 驱逐,事务和不同级别的磁盘持久性,并通过 Redis Sentinel 提供高可用性并使用 Redis Cluster 自动分区。

  • redis 是由 C 语言写成
  • 开源 key-value 型数据库

特点

  • 速度快,因为数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是 O(1)

  • 支持丰富数据类型,支持 string,list,set,sorted set,hash

  • 支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行

  • 丰富的特性:可用于缓存,消息,按 key 设置过期时间,过期后将会自动删除

应用场景

缓存

缓存是 redis 最常见的应用场景,主要是因为 redis 读写性能优异,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value DB。其次 redis 支持保存多种数据结构,此外单个 value 的最大限制是 1GB,而 memcached 只能保存 1MB 的数据。

分布式锁

因为 redis 单线程的这个特性,其中有个很重要的应用场景就是分布式锁。对于高并发的系统,都是用多服务器部署,其中程序进行逻辑处理时就可以用到分布式锁来限制程序的并发。

自动过期

redis 针对数据都可以设置过期时间,可以自动的去清理过期的数据。常见的应用场景有:短信验证码,活动开始和截止日期等具有时间性的商品展示等

计数器和排行榜

redis 在内存中对数字进行递增或递减的操作支持很好。集合(Set)和有序集合(Sorted Set)也使得我们在执行这些排行操作的时的非常简单。

秒杀的缓冲层

秒杀是现在互联网系统中常见的营销模式,但是通常由于并发太高导致、程序阻塞等原因给开发带来很大困难。可以利用 redis 单线程的特性来控制并发,可将 redis 作为缓存平台,由于 redis 的读写能力很强,所以不易产生程序阻塞现象。

处理签到和其他状态(大数据处理)

当用户量庞大时,如何去在极短的时间里去统计用户的状态,签到情况等,如 QQ 打卡、 查看好友状态等。redis 中的位图可以很好的解决这个问题。

发布/订阅

可以使用 redis 的发布订阅去实现的社交聊天系统。

实战

Redis 发布/订阅实现聊天室

1
2
3
4
5
6
7
8
9
const rclient = redis.createClient(6379, "localhost");
rclient.on("ready", err => {
console.log("client ready ....");
});

const publish = redis.createClient(6379, "localhost");
publish.on("ready", err => {
console.log("publish ready ...");
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
io.on("connection", socket => {
// 订阅
rclient.subscribe("chat");
// 收到消息后将推送消息
rclient.on("message", (channel, msg) => {
io.emit("chat message", msg);
});
socket.on("chat message", msg => {
console.log("receive message:" + msg);
publish.publish("chat", msg);
});
socket.on("disconnect", () => {
console.log("user disconnected");
});
});

Redis 队列实现秒杀 秒杀实例

https://www.jianshu.com/p/31f59426c779

痛点解决

  • 短时间内大量并发

  • 保证不会超卖

    使用 Redi 提供缓冲层

    • 内存型数据库性能远优于关系型数据库

    • Redis 单进程方式保证操作原子性

使用 Redis 队列投放秒杀商品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
};
});

秒杀从商品队列中取出商品

使用哈希表作为临时订单

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

ctx.body = { ok: 1 };
});

打印订单列表

1
2
3
4
5
6
7
8
9
10
11
12
13
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
};
});

清空订单

1
2
3
4
5
6
7
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 };
});

压力测试验证

1
2
3
4
5
6
7
8
9
10
(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("秒杀完成");
})();

Redis API

通用命令

  • keys: 计算所有的键 O(n)
  • dbsize: 数据库大小
  • exists keys: key 是否存在
  • del:删除 key
  • expire key seconds: 设置过期时间
  • type key: 获取 key 的数据类型
  • ttl key: 查看 key 的剩余过期时间
  • persist key: 去掉 key 的过期时间

列表类型

  • rpush key value1 value2 valueN O(1-n)
  • lpush key value1 value2 valueN O(1-n)
  • linsert key before|after value newValue O(n)
  • lpop key
  • rpop key
  • lrem key count value (删除 count 个 value 元素) 0(n)
  • ltrim key start end (按照索引范围修剪列表) o(n)
  • lrange key start end (包含 end) (获取列表制定索引范围)
  • lindex key index o(n)
  • llen key
  • lset key index newValue
  • blpop key timeout (lpop 的阻塞版本)
  • brpop key timeout (rpop 的阻塞版本)
  • lpush + lpop = stack
  • lpush + rpop = queue
  • lpush + ltrim = Capped Collection
  • lpush + brpop = Message Queue

字符串类型

  • 场景:缓存、计数器、分布式锁
  • get key
  • set key value
  • incr key
  • decr key
  • incrby key k
  • decrby key k
  • setnx key value 值不存在才设置
  • set key value xx 存在才设置
  • mget 批量获取 O(n)
  • mset 批量设置 O(n)
  • getset key newvalue 设置新值返回旧值
  • append key value 将新值追加到旧值
  • strlen 获取值的长度
  • incrbyfloat key 3.5 增加对应 key 3.5
  • getrange key start end
  • setrange key start value

集合类型

  • 无序 无重复 支持集合间操作
  • sadd key element (添加)
  • srem key element (删除)
  • scard key
  • sismember key element
  • srandmember key count (随机选出 count 个元素)
  • spop key (随机弹出一个元素)
  • smembers key (取出所有元素 小心使用)
  • sscan (遍历集合)
  • sdiff 差集
  • sinter 交集
  • sunion 并集
  • sadd = 打标签
  • spop/srandmember = 随机
  • sadd + sinter = Social Graph

有序集合类型

  • zadd key score element O(logN)
  • zrem key element (删除)
  • zscore key element
  • zincrby key increScore element (增加分数)
  • zcard key (返回个数)
  • zrange key start end withscores (获取元素) (O(logN + m))
  • zrangebyscore key minScore maxScore
  • zcount key minScore maxScore (O(logN + m))
  • zremrangebyrank key start end (删除指定排名内的升序元素)
  • zremrangebyscore key start end (删除指定分数内的升序元素)
  • zrevrank
  • zrevrange
  • zrevrangebyscore
  • zinterstore
  • zunionstore

哈希类型

  • hget key filed
  • hset key field value
  • hdel key field
  • hgetall key O(n)
  • hexists key field
  • hlen key 获取字段数量
  • hmget key field1 field2 O(n)
  • hmset key field1 value1 field2 value2 O(n)
  • hincrby key field value
  • hvals key 返回 hash key 对应所有 field 的 value O(n)
  • hkeys key 返回 hash key 对应的所有 field O(n)
  • hsetnx key field value
  • hincrby key field intCounter
  • hincrbyfloat key field floatCounter