244 lines
7.0 KiB
Markdown
244 lines
7.0 KiB
Markdown
# 快速匹配实现原理分析
|
||
|
||
## 为什么其他机构能"秒级"返回匹配结果?
|
||
|
||
### 核心原理:数据库层面快速过滤 + 只计算符合条件的岗位
|
||
|
||
## 实现方式对比
|
||
|
||
### ❌ 当前方案(慢的原因)
|
||
|
||
```
|
||
1. 接收1万个岗位的完整数据(JSON数组)
|
||
2. 在内存中遍历所有岗位
|
||
3. 对每个岗位进行完整匹配计算
|
||
4. 排序返回
|
||
```
|
||
|
||
**问题**:
|
||
- 需要传输1万个岗位的完整数据(几十MB)
|
||
- 需要计算所有岗位(即使明显不符合)
|
||
- 无法利用数据库索引优化
|
||
|
||
### ✅ 优化方案(快速实现)
|
||
|
||
```
|
||
1. 岗位数据存储在数据库中
|
||
2. 使用SQL WHERE条件快速过滤
|
||
3. 只对通过初步筛选的岗位进行详细计算
|
||
4. 排序返回
|
||
```
|
||
|
||
**优势**:
|
||
- 利用数据库索引,查询速度极快(毫秒级)
|
||
- 大幅减少需要计算的岗位数量(从1万降到几百)
|
||
- 只传输少量数据
|
||
|
||
## 具体实现步骤
|
||
|
||
### 第一步:数据库表结构设计
|
||
|
||
```sql
|
||
-- 岗位表
|
||
CREATE TABLE positions (
|
||
id INT PRIMARY KEY,
|
||
-- 基础信息
|
||
position_name VARCHAR(255),
|
||
company_name VARCHAR(255),
|
||
|
||
-- 硬性条件(建立索引)
|
||
education_require VARCHAR(50), -- 学历要求:本科、硕士等
|
||
degree_require VARCHAR(50), -- 学位要求:学士、硕士等
|
||
age_min INT, -- 最小年龄
|
||
age_max INT, -- 最大年龄
|
||
gender_require VARCHAR(10), -- 性别要求:男、女、不限制
|
||
major_require TEXT, -- 专业要求
|
||
|
||
-- 其他信息
|
||
position_require JSON, -- 完整要求(JSON格式)
|
||
created_at TIMESTAMP
|
||
);
|
||
|
||
-- 建立索引
|
||
CREATE INDEX idx_education ON positions(education_require);
|
||
CREATE INDEX idx_age ON positions(age_min, age_max);
|
||
CREATE INDEX idx_gender ON positions(gender_require);
|
||
```
|
||
|
||
### 第二步:快速过滤SQL查询
|
||
|
||
```php
|
||
// 根据简历信息,快速过滤岗位
|
||
$resume = [
|
||
'birth_date' => '1995-03-01', // 计算年龄:30岁
|
||
'gender' => '男',
|
||
'education_level' => '硕士研究生',
|
||
'degree' => '硕士',
|
||
'major' => '计算机科学与技术'
|
||
];
|
||
|
||
// 计算年龄
|
||
$age = calculateAge($resume['birth_date']); // 30岁
|
||
|
||
// SQL快速过滤(利用索引,毫秒级响应)
|
||
$positions = DB::table('positions')
|
||
->where(function($query) use ($resume, $age) {
|
||
// 学历要求:简历学历 >= 岗位要求
|
||
$query->whereIn('education_require', [
|
||
'本科', '本科及以上', '硕士', '硕士及以上'
|
||
]);
|
||
|
||
// 年龄要求:在范围内
|
||
$query->where('age_min', '<=', $age)
|
||
->where('age_max', '>=', $age);
|
||
|
||
// 性别要求:不限制 或 匹配
|
||
$query->where(function($q) use ($resume) {
|
||
$q->where('gender_require', '不限制')
|
||
->orWhere('gender_require', $resume['gender']);
|
||
});
|
||
|
||
// 专业要求:模糊匹配(或使用专业分类表)
|
||
$query->where('major_require', 'like', '%计算机%')
|
||
->orWhere('major_require', 'like', '%软件%');
|
||
})
|
||
->get(); // 可能从1万个筛选到200个
|
||
|
||
// 只对这200个岗位进行详细匹配计算
|
||
foreach ($positions as $position) {
|
||
$score = calculateMatchScore($position, $resume);
|
||
}
|
||
```
|
||
|
||
### 第三步:性能对比
|
||
|
||
| 方案 | 需要计算的岗位数 | 计算时间 | 数据库查询时间 |
|
||
|------|----------------|---------|---------------|
|
||
| **当前方案** | 10,000个 | 10-50秒 | 0秒(无数据库) |
|
||
| **优化方案** | 200个(过滤后) | 0.2-1秒 | 0.01-0.1秒 |
|
||
|
||
**总时间对比**:
|
||
- 当前方案:10-50秒
|
||
- 优化方案:0.21-1.1秒(**快50-200倍**)
|
||
|
||
## 关键技术点
|
||
|
||
### 1. 数据库索引优化
|
||
|
||
```sql
|
||
-- 对常用查询字段建立索引
|
||
CREATE INDEX idx_education_age ON positions(education_require, age_min, age_max);
|
||
CREATE INDEX idx_gender ON positions(gender_require);
|
||
```
|
||
|
||
### 2. 数据预处理
|
||
|
||
```php
|
||
// 岗位入库时,解析并存储结构化数据
|
||
$position = [
|
||
'position_require' => [
|
||
'学历要求' => '本科及以上',
|
||
'年龄要求' => '18周岁以上、35周岁以下',
|
||
// ...
|
||
]
|
||
];
|
||
|
||
// 解析并存储到独立字段
|
||
$position['education_require'] = '本科'; // 标准化
|
||
$position['age_min'] = 18;
|
||
$position['age_max'] = 35;
|
||
```
|
||
|
||
### 3. 专业匹配优化
|
||
|
||
```sql
|
||
-- 方案A:使用专业分类表
|
||
CREATE TABLE major_categories (
|
||
major_name VARCHAR(100),
|
||
category VARCHAR(100),
|
||
INDEX idx_category(category)
|
||
);
|
||
|
||
-- 方案B:使用全文索引
|
||
ALTER TABLE positions ADD FULLTEXT INDEX idx_major(major_require);
|
||
```
|
||
|
||
### 4. 缓存机制
|
||
|
||
```php
|
||
// 缓存常见简历的匹配结果
|
||
$cacheKey = "match:resume:{$resumeId}";
|
||
$result = Redis::get($cacheKey);
|
||
|
||
if (!$result) {
|
||
// 计算并缓存
|
||
$result = calculateMatch($positions, $resume);
|
||
Redis::setex($cacheKey, 3600, $result); // 缓存1小时
|
||
}
|
||
```
|
||
|
||
## 完整实现流程
|
||
|
||
```php
|
||
public function fastBatchMatch($resume, $page = 1, $pageSize = 20) {
|
||
// 1. 解析简历信息
|
||
$age = calculateAge($resume['birth_date']);
|
||
$education = parseEducation($resume['education']);
|
||
|
||
// 2. 数据库快速过滤(毫秒级)
|
||
$filteredPositions = DB::table('positions')
|
||
->where('education_require', '<=', $education['level'])
|
||
->where('age_min', '<=', $age)
|
||
->where('age_max', '>=', $age)
|
||
->where(function($q) use ($resume) {
|
||
$q->where('gender_require', '不限制')
|
||
->orWhere('gender_require', $resume['gender']);
|
||
})
|
||
->get(); // 从1万筛选到几百个
|
||
|
||
// 3. 只计算通过初步筛选的岗位(秒级)
|
||
$results = [];
|
||
foreach ($filteredPositions as $position) {
|
||
$score = $this->calculateMatchScore($position, $resume);
|
||
$results[] = [
|
||
'position_id' => $position->id,
|
||
'match_score' => $score,
|
||
'position' => $position
|
||
];
|
||
}
|
||
|
||
// 4. 排序+分页
|
||
usort($results, fn($a, $b) => $b['match_score'] - $a['match_score']);
|
||
$paginated = array_slice($results, ($page - 1) * $pageSize, $pageSize);
|
||
|
||
return [
|
||
'list' => $paginated,
|
||
'total' => count($results),
|
||
'page' => $page,
|
||
'page_size' => $pageSize
|
||
];
|
||
}
|
||
```
|
||
|
||
## 总结
|
||
|
||
**其他机构能快速返回的关键**:
|
||
1. ✅ **岗位存储在数据库**(不是JSON数组)
|
||
2. ✅ **使用SQL WHERE快速过滤**(利用索引,毫秒级)
|
||
3. ✅ **只计算通过初步筛选的岗位**(从1万降到几百)
|
||
4. ✅ **数据预处理**(结构化存储硬性条件)
|
||
5. ✅ **索引优化**(对常用查询字段建索引)
|
||
|
||
**性能提升**:
|
||
- 数据库过滤:1万个 → 200个(减少98%)
|
||
- 计算时间:50秒 → 1秒(快50倍)
|
||
- **总响应时间:从50秒降到1秒以内**
|
||
|
||
## 实施建议
|
||
|
||
1. **如果岗位数据在数据库**:直接使用SQL过滤
|
||
2. **如果岗位数据是JSON**:考虑导入数据库或建立索引
|
||
3. **如果无法改数据库**:使用内存索引(如Elasticsearch)或预计算
|
||
|
||
|