ThinkPHP6 业务分表之三:订单

已经完成的用户的基本实现,那接下来我们要进行下订单方面的。

订单也是要等我要做的时候,才发现挺复杂的,我初期的设想是不管我是通过 UID 或者订单 ID 来进行查询,都能达到 O(1),但设计的时候就开始困扰了。用户表我们知道 UID 可以直接解析出数据存在哪个表,但 UID 和订单 ID 两个都要能直接解析的时候,要怎么处理呢?

雪花算法也很难直接处理,群友推荐我可以把解析分成两块,这样子从全部表查降低成了几个表查,不过这块的实现我也没有验证过。

后面我的想法还是按照用户的 ID 生成方式,但是提取则不是按照顺序了,取最前面 余数 = UID % 订单表数 的数,但这里有存在判断是挺麻烦的,我不清楚能不能通过 Lua 来循环处理,但预生成的数量要多少合适呢?会不会影响到表与表之间数量差距较大然后无法取到值呢?

本来已经放弃了的,无意看到腾讯技术工程新发的 分布式唯一 ID 生成方案浅谈,让我想到可以在获取的时候才进行自增,因为 Redis 是原子性的,所以不会存在 ID 重复的情况。

特意查了下,Redis 的 int 可以存储 2^63 - 1, 9223372036854775807 这么多。 – What happens when Int64 maxvalue is exceded with Redis INCR

当然,这块也可以用 MySQL 来实现,修改每个表的 AUTO_INCREMENT 起始值,还有每次自增的量。

流程图

首先我们要先写个脚本来初始化我们的订单发号器,我这里分表后面的值是转换成了十六进制的,这个要注意下,我就把自己坑了。

app/command/OrderIdInit.php

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
62
63
64
65
66
67
68
69
70
71
72
73
<?php
declare (strict_types = 1);

namespace app\command;

use think\console\Command;
use think\console\Input;
use think\console\input\Argument;
use think\console\input\Option;
use think\console\Output;

class OrderIdInit extends Command
{
protected $redis;

protected $cache_key = 'order:generate_id';

protected $lock_key = 'order:init_lock';

public function __construct()
{
$this->redis = get_redis();

parent::__construct();
}

protected function configure()
{
// 指令配置
$this->setName('order:id_init')
->addOption('number', null, Option::VALUE_REQUIRED, '表数', 32)
->setDescription('订单 ID 生成初始化');
}

protected function execute(Input $input, Output $output)
{
$number = (int) $input->getOption('number');

// 加锁
$lock = $this->redis->executeRaw([
'SET',
$this->lock_key,
1,
'EX',
10 * 60,
'NX',
]);

if($lock !== 'OK') {
$output->writeln('获取锁失败');

return;
}

try {
for($i = 0; $i < $number; $i++) {
$v = sprintf('%x', $i);

$this->redis->executeRaw([
'HSETNX',
$this->cache_key,
$v,
$i,
]);
}
} catch (\Exception $e) {
$output->error($e->getMessage());
} finally {
// 释放锁
$this->redis->del($this->lock_key);
}
}
}

初始化完成

开始插入订单表

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
public function store(Request $request)
{
// 获取当前访问的 UID,自行实现鉴权功能
$uid = $request->uid;

// 获取订单表表名
$table_name = table_name('order', $uid);

// 获取订单表索引值?
$table_index = explode('_', $table_name)[1];

// 获取 Redis 自增 ID
$id = get_redis()->executeRaw([
'HINCRBY',
'order:generate_id',
$table_index,
32, // 分表数
]);

if(!$id) {
return '获取 ID 异常';
}

// 插入数据
Db::table($table_name)->insert([
'id' => $id,
'uid' => $uid,
'amount' => random_int(1, 99999),
]);
}

根据订单号查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function show(Request $request)
{
$uid = $request->uid;

$id = $request->id;

// 获取订单表表名
$table_name = table_name('order', $uid);

$data = Db::table($table_name)
->where('id', $id)
->where('uid', $uid)
->find();
}

查询当前用户全部订单

1
2
3
4
5
6
7
8
9
10
11
public function index(Request $request)
{
$uid = $request->uid;

// 获取订单表表名
$table_name = table_name('order', $uid);

$data = Db::table($table_name)
->where('uid', $uid)
->select();
}
往上