Merge pull request #117 from po-lan/main

对缓存进一步优化
This commit is contained in:
手瓜一十雪 2024-07-12 09:52:17 +08:00 committed by GitHub
commit 8be5b977bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 98 additions and 75 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ dist/
# Build # Build
*.db *.db
checkVersion.sh checkVersion.sh
bun.lockb

View File

@ -21,7 +21,8 @@ class cacheNode<T> {
} }
} }
type cache<T> = { [key: group_id]: { [key: user_id]: cacheNode<T> } }; type cache<T, K = { [key: user_id]: cacheNode<T> }> = { [key: group_id]: K };
type removeObject<T> = cache<T, { userId: user_id, value: T }[]>
class LRU<T> { class LRU<T> {
private maxAge: number; private maxAge: number;
private maxSize: number; private maxSize: number;
@ -29,9 +30,9 @@ class LRU<T> {
private cache: cache<T>; private cache: cache<T>;
private head: cacheNode<T> | null = null; private head: cacheNode<T> | null = null;
private tail: cacheNode<T> | null = null; private tail: cacheNode<T> | null = null;
private onFuncs: ((node: cacheNode<T>) => void)[] = []; private onFuncs: ((node: removeObject<T>) => void)[] = [];
constructor(maxAge: number = 6e4, maxSize: number = 5e3) { constructor(maxAge: number = 6e4 * 3, maxSize: number = 1e4) {
this.maxAge = maxAge; this.maxAge = maxAge;
this.maxSize = maxSize; this.maxSize = maxSize;
this.cache = Object.create(null); this.cache = Object.create(null);
@ -53,46 +54,39 @@ class LRU<T> {
node.prev = node.next = null; node.prev = node.next = null;
delete this.cache[node.groupId][node.userId]; delete this.cache[node.groupId][node.userId];
this.removeNode(node); this.removeNode(node);
this.onFuncs.forEach((func) => func(node)); this.onFuncs.forEach((func) => func({ [node.groupId]: [node] }));
this.currentSize--; this.currentSize--;
} }
public on(func: (node: cacheNode<T>) => void) { public on(func: (node: removeObject<T>) => void) {
this.onFuncs.push(func); this.onFuncs.push(func);
} }
private removeExpired() { private removeExpired() {
const now = Date.now(); const now = Date.now();
let current = this.tail; let current = this.tail;
const nodesToRemove: cacheNode<T>[] = []; let totalNodeNum = 0;
let removedCount = 0;
const removeObject: cache<T, { userId: user_id, value: T }[]> = {};
// 收集需要删除的节点
while (current && now - current.timestamp > this.maxAge) { while (current && now - current.timestamp > this.maxAge) {
nodesToRemove.push(current); // 收集节点
if (!removeObject[current.groupId]) removeObject[current.groupId] = [];
removeObject[current.groupId].push({ userId: current.userId, value: current.value });
// 删除LRU节点
delete this.cache[current.groupId][current.userId]
current = current.prev; current = current.prev;
removedCount++; totalNodeNum++;
if (removedCount >= 100) break; this.currentSize--
} }
// 更新链表指向 if (!totalNodeNum) return;
if (nodesToRemove.length > 0) {
const newTail = nodesToRemove[nodesToRemove.length - 1].prev;
if (newTail) {
newTail.next = null;
} else {
this.head = null;
}
this.tail = newTail;
}
nodesToRemove.forEach((node) => { // 跟新链表指向
node.prev = node.next = null; if (current) { current.next = null } else { this.head = null }
delete this.cache[node.groupId][node.userId]; this.tail = current
this.currentSize--; this.onFuncs.forEach(func => func(removeObject))
this.onFuncs.forEach((func) => func(node));
});
} }
private addNode(node: cacheNode<T>) { private addNode(node: cacheNode<T>) {
@ -144,7 +138,7 @@ class LRU<T> {
public get(groupId: group_id, userId: user_id): null | { userId: user_id; value: T }; public get(groupId: group_id, userId: user_id): null | { userId: user_id; value: T };
public get(groupId: group_id, userId?: user_id): any { public get(groupId: group_id, userId?: user_id): any {
const groupObject = this.cache[groupId]; const groupObject = this.cache[groupId];
if(!groupObject) return userId === undefined ? [] : null; if (!groupObject) return userId === undefined ? [] : null;
if (userId === undefined) { if (userId === undefined) {
return Object.entries(groupObject).map(([userId, { value }]) => ({ return Object.entries(groupObject).map(([userId, { value }]) => ({
@ -162,4 +156,6 @@ class LRU<T> {
} }
} }
export default LRU; export default LRU;

View File

@ -72,7 +72,7 @@ class DBUtil extends DBUtilBase {
private cache: { gid: number; uid: number }[] = []; private cache: { gid: number; uid: number }[] = [];
private maxSize: number; private maxSize: number;
constructor(maxSize: number = 5000) { constructor(maxSize: number = 50000) {
this.maxSize = maxSize; this.maxSize = maxSize;
} }
@ -120,57 +120,83 @@ class DBUtil extends DBUtilBase {
}); });
this.LURCache.on(async (node) => { this.LURCache.on(async (nodeObject) => {
const { value: time, groupId, userId } = node;
Object.entries(nodeObject).forEach(async ([_groupId, datas]) => {
const userIds = datas.map(v => v.userId);
const groupId = Number(_groupId)
logDebug('插入发言时间', _groupId);
logDebug('插入发言时间', userId, groupId);
await this.createGroupInfoTimeTableIfNotExist(groupId); await this.createGroupInfoTimeTableIfNotExist(groupId);
const method = await this.getDataSetMethod(groupId, userId); const needCreatUsers = await this.getNeedCreatList(groupId, userIds);
logDebug('插入发言时间方法判断', userId, groupId, method); const updateList = needCreatUsers.length > 0 ? datas.filter(user => !needCreatUsers.includes(user.userId)) : datas;
const insertList = needCreatUsers.map(userId => datas.find(e => userId == e.userId)!)
const sql = logDebug(`updateList`, updateList);
method == 'update' logDebug(`insertList`, insertList)
? `UPDATE "${groupId}" SET last_sent_time = ? WHERE user_id = ?`
: `INSERT INTO "${groupId}" (last_sent_time, user_id) VALUES (?, ?)`;
this.db!.all(sql, [time, userId], (err) => { if (insertList.length) {
const insertSql = `INSERT INTO "${groupId}" (last_sent_time, user_id) VALUES ${insertList.map(() => '(?, ?)').join(', ')};`
this.db!.all(insertSql, insertList.map(v => [v.value, v.userId]).flat(), err => {
if (err) { if (err) {
return logError('插入/更新发言时间失败', userId, groupId); logError(`${groupId} 插入失败`)
logError(`更新Sql : ${insertSql}`)
} }
logDebug('插入/更新发言时间成功', userId, groupId); })
});
});
}
async getDataSetMethod(groupId: number, userId: number) {
// 缓存记录
if (this.LastSentCache.get(groupId, userId)) {
logDebug('缓存命中', userId, groupId);
return 'update';
} }
// 数据库判断 if (updateList.length) {
return new Promise<'insert' | 'update'>((resolve, reject) => { const updateSql =
this.db!.all( `UPDATE "${groupId}" SET last_sent_time = CASE ` +
`SELECT * FROM "${groupId}" WHERE user_id = ?`, updateList.map(v => `WHEN user_id = ${v.userId} THEN ${v.value}`).join(" ") +
[userId], " ELSE last_sent_time END WHERE user_id IN " +
(err, rows) => { `(${updateList.map(v => v.userId).join(", ")});`
this.db!.all(updateSql, [], err => {
if (err) { if (err) {
logError('查询发言时间存在失败', userId, groupId, err); logError(`${groupId} 跟新失败`)
return logError('插入发言时间失败', userId, groupId, err); logError(`更新Sql : ${updateSql}`)
}
})
} }
if (rows.length === 0) { })
logDebug('查询发言时间不存在', userId, groupId);
return resolve('insert');
}
logDebug('查询发言时间存在', userId, groupId);
resolve('update');
}
);
}); });
}
async getNeedCreatList(groupId: number, userIds: number[]) {
// 获取缓存中没有的
const unhas = userIds.filter(userId => !this.LastSentCache.get(groupId, userId));
if (unhas.length == 0) {
logDebug('缓存全部命中');
return [];
}
logDebug('缓存未全部命中');
const sql = `SELECT * FROM "${groupId}" WHERE user_id IN (${unhas.map(() => '?').join(',')})`;
return new Promise<number[]>((resolve) => {
this.db!.all(sql, unhas, (err, rows: { user_id: number }[]) => {
const has = rows.map(v => v.user_id);
const needCreatUsers = unhas.filter(userId => !has.includes(userId));
if (needCreatUsers.length == 0) {
logDebug('数据库全部命中')
} else {
logDebug('数据库未全部命中')
}
resolve(needCreatUsers)
})
})
} }
async createGroupInfoTimeTableIfNotExist(groupId: number) { async createGroupInfoTimeTableIfNotExist(groupId: number) {
const createTableSQL = (groupId: number) => const createTableSQL = (groupId: number) =>
@ -408,10 +434,10 @@ class DBUtil extends DBUtilBase {
logDebug('读取发言时间', groupId); logDebug('读取发言时间', groupId);
return new Promise<IRember[]>((resolve, reject) => { return new Promise<IRember[]>((resolve, reject) => {
this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => { this.db!.all(`SELECT * FROM "${groupId}" `, (err, rows: IRember[]) => {
const cache = this.LURCache.get(groupId).map(e=>({user_id:e.userId, last_sent_time:e.value})); const cache = this.LURCache.get(groupId).map(e => ({ user_id: e.userId, last_sent_time: e.value }));
if (err) { if (err) {
logError('查询发言时间失败', groupId); logError('查询发言时间失败', groupId);
return resolve(cache.map(e=>({...e, join_time:0}))); return resolve(cache.map(e => ({ ...e, join_time: 0 })));
} }
Object.assign(rows, cache) Object.assign(rows, cache)
logDebug('查询发言时间成功', groupId, rows); logDebug('查询发言时间成功', groupId, rows);