在电商、零售类业务场景中,实时感知数据库新增的商品信息并同步到前端页面,是提升用户交互体验的重要功能。传统的页面刷新方式不仅操作繁琐,还会造成不必要的资源消耗,因此需要采用更高效的实时数据同步方案。
常见实现方案对比
目前主流的实现实时获取数据库新增商品的方案有以下几种,各自的特性如下:
| 方案名称 | 实时性 | 服务器压力 | 实现难度 |
|---|---|---|---|
| 前端短轮询 | 低 | 高 | 低 |
| 前端长轮询 | 中 | 中 | 中 |
| WebSocket推送 | 高 | 低 | 中 |
| 数据库变更监听+消息队列 | 高 | 低 | 高 |
方案一:前端短轮询实现
短轮询是最简单的实现方式,前端按照固定时间间隔向服务端发送请求,查询是否有新增商品。这种方式实现门槛低,但是实时性依赖轮询间隔,间隔太短会增加服务器压力,间隔太长又无法满足实时需求。
前端代码示例
使用原生JavaScript实现定时请求:
// 定义查询新增商品的接口地址
const queryUrl = 'http://ipipp.com/api/product/new';
// 定义上次查询到的最新商品ID,初始为0
let lastProductId = 0;
// 封装查询函数
function queryNewProduct() {
// 拼接请求参数,携带上次最新商品ID,避免重复查询
fetch(`${queryUrl}?last_id=${lastProductId}`)
.then(response => response.json())
.then(data => {
if (data.code === 0 && data.data.length > 0) {
// 处理新增商品数据,渲染到页面
renderProductList(data.data);
// 更新最新商品ID
lastProductId = data.data[data.data.length - 1].id;
}
})
.catch(err => {
console.error('查询新增商品失败', err);
});
}
// 初始查询一次
queryNewProduct();
// 每3秒查询一次
setInterval(queryNewProduct, 3000);
// 渲染商品列表的函数
function renderProductList(productList) {
const container = document.getElementById('product-container');
productList.forEach(item => {
const div = document.createElement('div');
div.className = 'product-item';
div.innerHTML = `
<h3>${item.name}</h3>
<p>价格:${item.price}元</p>
<p>上架时间:${item.create_time}</p>
`;
container.prepend(div);
});
}
后端接口示例(Node.js + Express)
后端需要提供查询新增商品的接口,根据传入的last_id返回大于该ID的商品数据:
const express = require('express');
const app = express();
const mysql = require('mysql2/promise');
// 创建数据库连接池
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'product_db',
waitForConnections: true,
connectionLimit: 10
});
// 查询新增商品接口
app.get('/api/product/new', async (req, res) => {
try {
const lastId = parseInt(req.query.last_id) || 0;
// 查询ID大于lastId的商品,按ID升序排列
const [rows] = await pool.query(
'SELECT id, name, price, create_time FROM product WHERE id > ? ORDER BY id ASC',
[lastId]
);
res.json({
code: 0,
data: rows
});
} catch (err) {
console.error('查询商品失败', err);
res.json({
code: 1,
msg: '查询失败'
});
}
});
app.listen(3000, () => {
console.log('服务启动在3000端口');
});
方案二:WebSocket实时推送
WebSocket可以在客户端和服务端之间建立持久连接,服务端可以主动往客户端推送数据,实时性远高于轮询,也减少了不必要的请求消耗。适合对实时性要求较高的场景。
服务端实现(Node.js + ws模块)
服务端需要监听数据库的新增商品事件,当有新商品插入时,推送给所有连接的客户端:
const WebSocket = require('ws');
const mysql = require('mysql2/promise');
const express = require('express');
const app = express();
// 创建WebSocket服务
const wss = new WebSocket.Server({ port: 8080 });
// 数据库连接池
const pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'product_db'
});
// 监听WebSocket连接
wss.on('connection', (ws) => {
console.log('有客户端连接');
ws.on('close', () => {
console.log('客户端断开连接');
});
});
// 模拟数据库新增商品的监听,实际场景可以结合数据库的binlog监听或者消息队列实现
// 这里用定时模拟新增商品,实际业务中替换为真实的数据库变更监听逻辑
setInterval(async () => {
try {
// 模拟插入一条新商品
const [result] = await pool.query(
'INSERT INTO product (name, price, create_time) VALUES (?, ?, NOW())',
[`测试商品${Date.now()}`, Math.floor(Math.random() * 100) + 10]
);
const newProductId = result.insertId;
// 查询新增的商品详情
const [rows] = await pool.query(
'SELECT id, name, price, create_time FROM product WHERE id = ?',
[newProductId]
);
// 推送给所有连接的客户端
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: 'new_product',
data: rows[0]
}));
}
});
} catch (err) {
console.error('新增商品失败', err);
}
}, 5000);
app.listen(3000, () => {
console.log('HTTP服务启动在3000端口');
});
前端WebSocket接收示例
前端建立WebSocket连接,接收服务端推送的新增商品数据并渲染:
// 建立WebSocket连接
const ws = new WebSocket('ws://127.0.0.1:8080');
ws.onopen = () => {
console.log('WebSocket连接成功');
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'new_product') {
// 处理新增商品数据
renderNewProduct(message.data);
}
};
ws.onerror = (err) => {
console.error('WebSocket连接出错', err);
};
ws.onclose = () => {
console.log('WebSocket连接关闭');
};
// 渲染单个新增商品
function renderNewProduct(product) {
const container = document.getElementById('product-container');
const div = document.createElement('div');
div.className = 'product-item new-product';
div.innerHTML = `
<h3>${product.name}</h3>
<p>价格:${product.price}元</p>
<p>上架时间:${product.create_time}</p>
<span class="new-tag">新品</span>
`;
container.prepend(div);
}
方案三:数据库变更监听结合消息队列
对于大型项目,可以采用监听数据库变更事件(比如MySQL的binlog、PostgreSQL的NOTIFY)结合消息队列的方案,当数据库有新增商品操作时,触发事件发送到消息队列,服务端消费消息后推送给前端,这种方式解耦了业务和推送逻辑,扩展性更强。
核心流程如下:
- 配置数据库开启变更日志,比如MySQL开启binlog
- 使用Canal等中间件监听数据库binlog,过滤出product表的插入操作
- 将新增商品的信息发送到消息队列(如RabbitMQ、Kafka)
- 后端服务消费消息队列中的消息,通过WebSocket推送给前端
方案选择建议
如果是小型项目或者实时性要求不高,优先选择前端短轮询,实现成本最低;如果是中型项目,对实时性有一定要求,选择WebSocket推送方案即可;如果是大型分布式项目,建议采用数据库变更监听结合消息队列的方案,方便后续扩展和维护。
在实际开发中,还需要考虑断线重连、消息去重、推送鉴权等细节问题,保障功能的稳定性和可靠性。