redis基础应用
验证码
1
2
3
4
5
6
|
# 添加验证码
setex uniqueId 30 "658428"
#验证验证码
get uniqueId # 返回nil 不存在或过期
#销毁验证码
del uniqueId
|
计数器
场景
社交产品业务里有很多统计计数的功能,比如:
- 用户: 总点赞数,关注数,粉丝数
- 帖子: 点赞数,评论数,热度
- 消息: 已读,未读,红点消息数
- 话题: 阅读数,帖子数,收藏数
string实现
1
2
3
4
5
6
7
8
9
|
# 文章12的阅读数+1
incr article:readcount:{12}
# 获取文章12的阅读数
get article:readcount:{12}
# 批量设置阅读数
mset article:readcount:{12} old_val+1 article:readcount:{13} old_val+1 article:readcount:{14} old_val+1
# 批量获取阅读数
mget article:readcount:{12} article:readcount:{13} article:readcount:{14}
|
缺点:
- 会生成很多key,如:article:readcount:,article:him:
- 批量设置需要读取之前数据,在进行+1设置
hash实现
使用命令
1
2
3
4
5
6
7
8
9
10
11
|
# 给key值+1
hincrby hashkey key # hincrby user:1 followCnt
# 获取单个值
hget hashkey key # hget user:1 followCnt
# 获取多个值
hmget hashkey key1 key2
# 获取所有key
hgetall hashkey
|
设计方案1
1
2
3
4
5
6
|
# 用户
counter:user:{userID}
-> praiseCnt: 100 #点赞数
-> hostCnt: 200 #热度
-> followCnt: 332 #关注数
-> fansCnt: 123 #粉丝数
|
缺点
这样设计,如果要批量查询多个用户的数据,就比较麻烦,例如一次要查指定20个userID的计数器?只能循环执行 HGETALL counter:user:{userID}。
优点
以实体聚合数据,方便数据管理
优化:通过redis管道来优化,一次性发送多个命令给redis执行
1
2
3
4
5
6
7
8
9
|
$userIDArray = array(1001, 1002, 1003, 1009);
$pipe = $redis->multi(Redis::PIPELINE);
foreach ($userIDArray as $userID) {
$pipe->hGetAll('counter:user:' . $userID);
}
$replies = $pipe->exec();
print_r($replies);
|
设计方案2
1
2
3
4
5
6
|
# 用户点赞数
counter:user:praiseCnt
-> userID_1001: 100
-> userID_1002: 200
.....
-> userID_9999: 123
|
优点
解决了批量操作的问题
缺点
当要获取多个计数器,比如同时需要praiseCnt,hostCnt时,要读多次,不过要比第一种方案读的次数要少。一个hash里的字段将会非常宠大,HMGET也许会有性能瓶颈
店铺筛选
应用场景
根据所在地,获取周围的店铺
geospetial实现
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# 批量添加店铺经纬度
geoadd store:city1 121.47 31.23 store:1 106.50 29.53 store:2 115.05 22.52 store:3 120.15 30.28 store:4 125.14 42.92 store:5
# 获取(110,30)周围1km内的所有店铺名称
georadius store:city1 110 30 1 km
1) "store:1"
2) "store:2"
3) "store:3"
# 获取(110,30)周围1km内所有店铺经纬度列表
georadius store:city1 110 30 1 km withcoord
1) 1) "store:1"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
|
标记状态
应用场景
给文章点赞,收藏
set实现
1
2
3
4
5
6
7
8
9
10
|
# 点赞
sadd like:消息id 用户id
# 取消点赞
srem like:消息id 用户id
# 检测用户是否点赞
sismember like:消息id 用户id
# 获取点赞的用户列表
smembrs like:消息id
# 获取点赞用户数
scard like:消息id
|
打卡
1
2
3
4
5
6
7
8
9
10
11
12
|
# 记录周一到周末的打卡
setbit sign 0 1 #周一打卡
setbit sign 1 0 #周二未打卡
setbit sign 2 1
setbit sign 3 1
setbit sign 4 1
setbit sign 5 0
setbit sign 6 1
getbit sign 6 # 查看星期天是否打卡
(integer) 1
bitcount sign #统计这周打卡天数,就可以看是否全勤
(integer) 5
|
https://blog.csdn.net/qq_39455116/article/details/87629029
统计uv
1
2
3
4
5
6
7
8
9
|
# 统计10.1号0:30 之间的uv
pfadd 10:1:0:30:uv uv1 uv2 uv3 uv4 uv5 uv6
# 统计10.1号0:30 - 1:00 之间的uv
pfadd 10:1:1:00:uv uv2 uv4 uv5 uv7 uv9 uv10
# 统计10.1号uv数
pfmerge 10:1:all 10:1:0:30:uv 10:1:1:00:uv
pfcount 10:1:all
"9"
|
string
限速
限制每分钟请求次数
1
2
3
|
"user:$id:info:".$phonename;
exists
$Redis->incr($key)<=5
|
list
队列 秒杀
数据表
秒杀商品表(product_seckill):
1
2
3
4
5
6
7
|
CREATE TABLE `product_seckill` (
`id` int NOT NULL, --主键id
`product_id` int NOT NULL, --商品id
`start_at` datetime DEFAULT NULL, --活动开始时间
`stop_at` datetime DEFAULT NULL, --活动结束时间
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
|
商品表(product):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
CREATE TABLE `products` (
`id` int NOT NULL AUTO_INCREMENT, --主键id
`title` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, --商品标题
`category_id` int DEFAULT NULL, --商品分类id
`status` tinyint DEFAULT NULL, --商品状态,0:未上架,1:上架
`type` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,--商品类型
`shop_id` int DEFAULT NULL, --店铺id
`stock` int DEFAULT NULL, --库存
`rating` int DEFAULT NULL, --浏览量
`sold_count` int DEFAULT NULL, --销量
`review_count` int DEFAULT NULL, --
`price` decimal(10,2) DEFAULT NULL, --价格
`image` varchar(100) DEFAULT NULL, --图片路径
`create_at` datetime DEFAULT NULL, --新增时间
`updated_at` datetime DEFAULT NULL, --修改时间
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
|
订单表(order)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
CREATE TABLE `orders` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT, --主键id
`no` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, --订单编号
`user_id` bigint unsigned NOT NULL, --用户id
`address` text COLLATE utf8mb4_unicode_ci NOT NULL, --详细地址
`total_amount` decimal(10,2) NOT NULL, --订单总金额
`remark` text COLLATE utf8mb4_unicode_ci, --备注
`paid_at` datetime DEFAULT NULL, --支付时间
`payment_method` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --支付类型
`payment_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --支付编号
`refund_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending', --物流状态
`refund_no` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, --物流编号
`closed` tinyint(1) NOT NULL DEFAULT '0', --订单关闭
`reviewed` tinyint(1) NOT NULL DEFAULT '0', --订单评价
`ship_status` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending', --
`ship_data` text COLLATE utf8mb4_unicode_ci,
`extra` text COLLATE utf8mb4_unicode_ci,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `orders_no_unique` (`no`),
UNIQUE KEY `orders_refund_no_unique` (`refund_no`),
KEY `orders_user_id_foreign` (`user_id`),
CONSTRAINT `orders_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=420 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
订单详情表(order_items):
1
2
3
4
5
6
7
8
9
10
11
12
13
|
CREATE TABLE `order_items` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`order_id` bigint unsigned NOT NULL,
`product_id` bigint unsigned NOT NULL,
`amount` int unsigned NOT NULL,
`price` decimal(10,2) NOT NULL,
`rating` int unsigned DEFAULT NULL,
`review` text COLLATE utf8mb4_unicode_ci,
`reviewed_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `order_items_order_id_foreign` (`order_id`),
CONSTRAINT `order_items_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=419 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
业务场景
对于请求的数据进行验证以及查看商品库存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
<?php
namespace App\Http\Requests;
use App\Models\Order;
use App\Models\Product;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class SeckillOrderRequest extends FormRequest
{
public function rules()
{
return [
'product_id' => [
'required',
function ($attribute, $value, $fail) {
if (!$product = Product::find($value)) {
return $fail('该商品不存在');
}
if ($product->type !== Product::TYPE_SECKILL) {
return $fail('该商品不支持秒杀');
}
if ($product->seckill->is_before_start) {
return $fail('秒杀尚未开始');
}
if ($product->seckill->is_after_end) {
return $fail('秒杀已经结束');
}
if (!$product->status) {
return $fail('该商品未上架');
}
if ($product->stock < 1) {
return $fail('该商品已售完');
}
if ($order = Order::query()
// 筛选出当前用户的订单
->where('user_id', $this->post('userId'))
->whereHas('items', function ($query) use ($value) {
// 筛选出包含当前 SKU 的订单
$query->where('product_id', $value);
})
->where(function ($query) {
// 已支付的订单
$query->whereNotNull('paid_at')
// 或者未关闭的订单
->orWhere('closed', false);
})
->first()) {
if ($order->paid_at) {
return $fail('你已经抢购了该商品');
}
return $fail('你已经下单了该商品,请到订单页面支付');
}
},
],
];
}
}
?>
|
对于活动下单时的处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
<?php
namespace App\Services;
use App\Models\Order;
use App\Models\User;
use App\Models\UserAddress;
use App\Models\Product;
use Carbon\Carbon;
use App\Jobs\CloseOrder;
class OrderService
{
public function seckill(User $user,UserAddress $address, Product $product)
{
$order = \DB::transaction(function () use ($user,$address, $product) {
// 更新此地址的最后使用时间
$address->update(['last_used_at' => Carbon::now()]);
// 扣减对应 SKU 库存
if ($product->decreaseStock(1) <= 0) {
throw new \Exception('该商品库存不足');
}
// 创建一个订单
$order = new Order([
'address' => [ // 将地址信息放入订单中
'address' => $address->full_address,
'zip' => $address->zip,
'contact_name' => $address->contact_name,
'contact_phone' => $address->contact_phone,
],
'remark' => '',
'total_amount' => $product->price,
'type' => Order::TYPE_SECKILL,
'paid_at' => Carbon::now()
]);
// 订单关联到当前用户
$order->user()->associate($user);
// 写入数据库
$order->save();
// 创建一个新的订单项并与 SKU 关联
$item = $order->items()->make([
'amount' => 1, // 秒杀商品只能一份
'price' => $product->price,
]);
$item->product()->associate($product->id);
// $item->productSku()->associate($product);
$item->save();
return $order;
});
// 秒杀订单的自动关闭时间与普通订单不同
dispatch(new CloseOrder($order, config('app.seckill_order_ttl')));
if ($order){
return ["status" => true,"message" => "下单成功,请到订单页面支付"];
}else{
return ["status" => false,"message" => "秒杀异常"];
}
}
}
?>
|
hash
购物车
优点:
- 不用操作数据库就可以实现功能;
- 用户可以不登录就将商品加入购物车中;
缺点:
-
用户关闭浏览器再访问没有记录;
注:要给redis购物车信息加入过期时间;如果用户登录状态,可以用用户ID做标识符,就不需要给过期时间了
hash实现
使用命令
1
2
3
4
|
# 设置值
hset hashkey key val # hset cart:user:1 g1 1
# 获取单个值
hget hashkey key # hget user:1 followCnt
|
hashkey生成规则:
- 如果用户登录 user:cart:{user_id}
- 没有登录 user:cart:{session_id}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 添加/修改购物车
hget hashkey 1001 # 获取该商品1001添加到购物车数量
hset hashkey 1001 10 # 修改购物车中商品1001的数量为10
# 设置hashkey 过期时间
expire hashkey 1800
# 获取某个用户的购物车列表
hgetall hashkey
# 删除购物车
hdel hashkey 1001 # 删除购物车商品1001
hdel hashkey 1001 1002 # 批量删除购物车商品
|
zset
商品筛选/排行榜
场景
将商品按销量,评论数,价格区间筛选商品
商品的效率与评论数一般是需要统计的,即:商品表中没有该两字段的冗余字段,数据量大了,排序比价困难。
价格区间:mysql的range效率比较差,尤其是数据量庞大的时候
zset使用命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# 赋值
zadd zkey sort key # zadd orderBySale 10 1001 #将商品1001的销售数量10存放到zset中
# 增加sort
zincrby zkey sort key # zincrby orderBySale 11 1001 # 将商品1001的销量增加到11
# 删除key
zrem zkey key1 key2 # zrem orderBySale 1001 1002 # 删除商品1001,1002
# 查询zkey成员数
zcard zkey # zcard orderBySale # 有销量的商品数量,用于分页
# 按sort从小到大获取数
zrange zkey startkey endkey # zrange orderBySale 0 4 # 获取第[1,5]的销售数据
# 按sort从大到小获取数
zrevrange zkey startkey endkey # zrange orderBySale 5 9 # 获取第[6,10]的销售数据
# 正序
zrangebyscore zkey -inf sort limit startkey endKey
# 倒序:
zrevrangebyscore zkey -inf sort limit startkey endKey
|
zset实现销量与评论数排序,排行榜
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 批量添加所有商品的评论
zadd goodCommentSort 0 g1 0 g2 1 g3 3 g4 3 g5 10 g6
# 商品4 新增2条评论
zincrby goodCommentSort 2 g4
# 商品5 下架
zrem goodCommentSort g5
# 查询商品数量
zcard goodCommentSort
# 正序 查询商品第1页数据,每页30条数据
zrange goodCommentSort 0 29
# 倒序 查询商品第2页数据,每页30条数据
zrevrange goodCommentSort 30 59
|
zset实现价格区间排序
实现流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 批量添加所有商品的评论
zadd goodPriceSort 100 g1 120 g2 80 g3 95 g4 120 g5 160 g6
# 商品4 价格
zincrby goodPriceSort 15 g4
# 商品5 下架
zrem goodPriceSort g5
# 查询商品数量
zcard goodPriceSort
# 按区间查询
#获取小于100的记录,从小到大
ZRANGEBYSCORE goodPriceSort -inf 100 limit 0 3
# 按从小到大获取价格(100,200]记录
zrangebyscore goodPriceSort (100 200 limit 0 3
# 按从小到大获取价格(100,200)记录
zrangebyscore goodPriceSort (100 (200 limit 0 3
# 获取大于100的记录,从大到小
zrevrangebyscore goodPriceSort +inf 100 limit 0 4
# 按从大到小获取价格[100,200]记录
zrevrangebyscore goodPriceSort 200 100 limit 0 4
# 按从大到小获取价格小于等于100记录
zrevrangebyscore goodPriceSort 100 -inf limit 0 4
|
list实现
list类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等。
但是,并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list类型不能支持实时计算的排行榜,之后在介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。