perf: 提升后端高并发承载能力
This commit is contained in:
332
backend/internal/db/cache.go
Normal file
332
backend/internal/db/cache.go
Normal file
@@ -0,0 +1,332 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"hightube/internal/model"
|
||||
)
|
||||
|
||||
type userCache struct {
|
||||
mutex sync.RWMutex
|
||||
byID map[uint]model.User
|
||||
byUsername map[string]uint
|
||||
}
|
||||
|
||||
type roomCache struct {
|
||||
mutex sync.RWMutex
|
||||
byID map[uint]model.Room
|
||||
byUserID map[uint]uint
|
||||
byStreamKey map[string]uint
|
||||
activeRoomIDs map[uint]struct{}
|
||||
activeRoomsLoaded bool
|
||||
}
|
||||
|
||||
var users = &userCache{
|
||||
byID: make(map[uint]model.User),
|
||||
byUsername: make(map[string]uint),
|
||||
}
|
||||
|
||||
var rooms = &roomCache{
|
||||
byID: make(map[uint]model.Room),
|
||||
byUserID: make(map[uint]uint),
|
||||
byStreamKey: make(map[string]uint),
|
||||
activeRoomIDs: make(map[uint]struct{}),
|
||||
}
|
||||
|
||||
func cacheUser(user model.User) {
|
||||
usernameKey := strings.ToLower(strings.TrimSpace(user.Username))
|
||||
|
||||
users.mutex.Lock()
|
||||
users.byID[user.ID] = user
|
||||
if usernameKey != "" {
|
||||
users.byUsername[usernameKey] = user.ID
|
||||
}
|
||||
users.mutex.Unlock()
|
||||
}
|
||||
|
||||
func removeUserFromCache(user model.User) {
|
||||
usernameKey := strings.ToLower(strings.TrimSpace(user.Username))
|
||||
|
||||
users.mutex.Lock()
|
||||
delete(users.byID, user.ID)
|
||||
if usernameKey != "" {
|
||||
delete(users.byUsername, usernameKey)
|
||||
}
|
||||
users.mutex.Unlock()
|
||||
}
|
||||
|
||||
func cacheRoom(room model.Room) {
|
||||
rooms.mutex.Lock()
|
||||
rooms.byID[room.ID] = room
|
||||
rooms.byUserID[room.UserID] = room.ID
|
||||
if room.StreamKey != "" {
|
||||
rooms.byStreamKey[room.StreamKey] = room.ID
|
||||
}
|
||||
if room.IsActive {
|
||||
rooms.activeRoomIDs[room.ID] = struct{}{}
|
||||
} else {
|
||||
delete(rooms.activeRoomIDs, room.ID)
|
||||
}
|
||||
rooms.mutex.Unlock()
|
||||
}
|
||||
|
||||
func removeRoomFromCache(room model.Room) {
|
||||
rooms.mutex.Lock()
|
||||
delete(rooms.byID, room.ID)
|
||||
delete(rooms.byUserID, room.UserID)
|
||||
if room.StreamKey != "" {
|
||||
delete(rooms.byStreamKey, room.StreamKey)
|
||||
}
|
||||
delete(rooms.activeRoomIDs, room.ID)
|
||||
rooms.mutex.Unlock()
|
||||
}
|
||||
|
||||
func LoadUserByID(id uint) (model.User, error) {
|
||||
users.mutex.RLock()
|
||||
if user, ok := users.byID[id]; ok {
|
||||
users.mutex.RUnlock()
|
||||
return user, nil
|
||||
}
|
||||
users.mutex.RUnlock()
|
||||
|
||||
var user model.User
|
||||
if err := DB.First(&user, id).Error; err != nil {
|
||||
return model.User{}, err
|
||||
}
|
||||
cacheUser(user)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func LoadUserByUsername(username string) (model.User, error) {
|
||||
key := strings.ToLower(strings.TrimSpace(username))
|
||||
if key == "" {
|
||||
return model.User{}, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
users.mutex.RLock()
|
||||
if id, ok := users.byUsername[key]; ok {
|
||||
if user, found := users.byID[id]; found {
|
||||
users.mutex.RUnlock()
|
||||
return user, nil
|
||||
}
|
||||
}
|
||||
users.mutex.RUnlock()
|
||||
|
||||
var user model.User
|
||||
if err := DB.Where("username = ?", strings.TrimSpace(username)).First(&user).Error; err != nil {
|
||||
return model.User{}, err
|
||||
}
|
||||
cacheUser(user)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func LoadRoomByUserID(userID uint) (model.Room, error) {
|
||||
rooms.mutex.RLock()
|
||||
if roomID, ok := rooms.byUserID[userID]; ok {
|
||||
if room, found := rooms.byID[roomID]; found {
|
||||
rooms.mutex.RUnlock()
|
||||
return room, nil
|
||||
}
|
||||
}
|
||||
rooms.mutex.RUnlock()
|
||||
|
||||
var room model.Room
|
||||
if err := DB.Where("user_id = ?", userID).First(&room).Error; err != nil {
|
||||
return model.Room{}, err
|
||||
}
|
||||
cacheRoom(room)
|
||||
return room, nil
|
||||
}
|
||||
|
||||
func LoadRoomByStreamKey(streamKey string) (model.Room, error) {
|
||||
rooms.mutex.RLock()
|
||||
if roomID, ok := rooms.byStreamKey[streamKey]; ok {
|
||||
if room, found := rooms.byID[roomID]; found {
|
||||
rooms.mutex.RUnlock()
|
||||
return room, nil
|
||||
}
|
||||
}
|
||||
rooms.mutex.RUnlock()
|
||||
|
||||
var room model.Room
|
||||
if err := DB.Where("stream_key = ?", streamKey).First(&room).Error; err != nil {
|
||||
return model.Room{}, err
|
||||
}
|
||||
cacheRoom(room)
|
||||
return room, nil
|
||||
}
|
||||
|
||||
func ListActiveRooms() ([]model.Room, error) {
|
||||
rooms.mutex.RLock()
|
||||
loaded := rooms.activeRoomsLoaded
|
||||
if loaded {
|
||||
result := make([]model.Room, 0, len(rooms.activeRoomIDs))
|
||||
for roomID := range rooms.activeRoomIDs {
|
||||
if room, ok := rooms.byID[roomID]; ok {
|
||||
result = append(result, room)
|
||||
}
|
||||
}
|
||||
complete := len(result) == len(rooms.activeRoomIDs)
|
||||
rooms.mutex.RUnlock()
|
||||
if complete {
|
||||
return result, nil
|
||||
}
|
||||
} else {
|
||||
rooms.mutex.RUnlock()
|
||||
}
|
||||
|
||||
var result []model.Room
|
||||
if err := DB.Where("is_active = ?", true).Find(&result).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rooms.mutex.Lock()
|
||||
for _, room := range result {
|
||||
rooms.byID[room.ID] = room
|
||||
rooms.byUserID[room.UserID] = room.ID
|
||||
if room.StreamKey != "" {
|
||||
rooms.byStreamKey[room.StreamKey] = room.ID
|
||||
}
|
||||
rooms.activeRoomIDs[room.ID] = struct{}{}
|
||||
}
|
||||
rooms.activeRoomsLoaded = true
|
||||
rooms.mutex.Unlock()
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func CreateUserAndRoom(user *model.User, room *model.Room) error {
|
||||
if user == nil || room == nil {
|
||||
return errors.New("user and room are required")
|
||||
}
|
||||
|
||||
if err := DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Create(user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
room.UserID = user.ID
|
||||
if err := tx.Create(room).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cacheUser(*user)
|
||||
cacheRoom(*room)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateUserRole(userID uint, role string) error {
|
||||
if err := DB.Model(&model.User{}).Where("id = ?", userID).Update("role", role).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := LoadUserByID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Role = role
|
||||
cacheUser(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateUserEnabled(userID uint, enabled bool) error {
|
||||
if err := DB.Model(&model.User{}).Where("id = ?", userID).Update("enabled", enabled).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := LoadUserByID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Enabled = enabled
|
||||
cacheUser(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateUserPassword(userID uint, hash string) error {
|
||||
if err := DB.Model(&model.User{}).Where("id = ?", userID).Update("password", hash).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := LoadUserByID(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user.Password = hash
|
||||
cacheUser(user)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetRoomActive(roomID uint, active bool) error {
|
||||
if err := DB.Model(&model.Room{}).Where("id = ?", roomID).Update("is_active", active).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rooms.mutex.Lock()
|
||||
room, ok := rooms.byID[roomID]
|
||||
if ok {
|
||||
room.IsActive = active
|
||||
rooms.byID[roomID] = room
|
||||
if active {
|
||||
rooms.activeRoomIDs[roomID] = struct{}{}
|
||||
} else {
|
||||
delete(rooms.activeRoomIDs, roomID)
|
||||
}
|
||||
rooms.activeRoomsLoaded = true
|
||||
rooms.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
rooms.mutex.Unlock()
|
||||
|
||||
if !active {
|
||||
rooms.mutex.Lock()
|
||||
delete(rooms.activeRoomIDs, roomID)
|
||||
rooms.activeRoomsLoaded = true
|
||||
rooms.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
var roomFromDB model.Room
|
||||
if err := DB.First(&roomFromDB, roomID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
roomFromDB.IsActive = active
|
||||
cacheRoom(roomFromDB)
|
||||
|
||||
rooms.mutex.Lock()
|
||||
rooms.activeRoomsLoaded = true
|
||||
rooms.mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteUserCascade(userID uint) error {
|
||||
user, userErr := LoadUserByID(userID)
|
||||
room, roomErr := LoadRoomByUserID(userID)
|
||||
|
||||
if err := DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("user_id = ?", userID).Delete(&model.Room{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&model.User{}, userID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if roomErr == nil {
|
||||
removeRoomFromCache(room)
|
||||
}
|
||||
if userErr == nil {
|
||||
removeUserFromCache(user)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package db
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
@@ -37,15 +38,32 @@ func InitDB() {
|
||||
log.Fatalf("Failed to connect database: %v", err)
|
||||
}
|
||||
|
||||
// Configure connection pool settings
|
||||
sqlDB, err := DB.DB()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get database instance: %v", err)
|
||||
}
|
||||
sqlDB.SetMaxOpenConns(10)
|
||||
sqlDB.SetMaxIdleConns(5)
|
||||
maxOpen := runtime.NumCPU()*2 + 1
|
||||
if maxOpen < 4 {
|
||||
maxOpen = 4
|
||||
}
|
||||
if maxOpen > 32 {
|
||||
maxOpen = 32
|
||||
}
|
||||
sqlDB.SetMaxOpenConns(maxOpen)
|
||||
sqlDB.SetMaxIdleConns(maxOpen)
|
||||
sqlDB.SetConnMaxIdleTime(10 * time.Minute)
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
for _, pragma := range []string{
|
||||
"PRAGMA synchronous=NORMAL",
|
||||
"PRAGMA temp_store=MEMORY",
|
||||
"PRAGMA foreign_keys=ON",
|
||||
} {
|
||||
if execErr := DB.Exec(pragma).Error; execErr != nil {
|
||||
log.Fatalf("Failed to apply %s: %v", pragma, execErr)
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-migrate the schema
|
||||
err = DB.AutoMigrate(&model.User{}, &model.Room{})
|
||||
if err != nil {
|
||||
@@ -57,7 +75,7 @@ func InitDB() {
|
||||
|
||||
ensureAdminUser()
|
||||
|
||||
monitor.Infof("Database initialized successfully with WAL mode and connection pooling")
|
||||
monitor.Infof("Database initialized successfully with WAL mode and tuned SQLite pragmas")
|
||||
}
|
||||
|
||||
func ensureAdminUser() {
|
||||
@@ -77,14 +95,17 @@ func ensureAdminUser() {
|
||||
updates := map[string]interface{}{}
|
||||
if user.Role != "admin" {
|
||||
updates["role"] = "admin"
|
||||
user.Role = "admin"
|
||||
}
|
||||
if !user.Enabled {
|
||||
updates["enabled"] = true
|
||||
user.Enabled = true
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
DB.Model(&user).Updates(updates)
|
||||
monitor.Warnf("Admin account normalized for username=%s", adminUsername)
|
||||
}
|
||||
cacheUser(user)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -115,5 +136,10 @@ func ensureAdminUser() {
|
||||
monitor.Warnf("Failed to create default admin room: %v", roomErr)
|
||||
}
|
||||
|
||||
cacheUser(newAdmin)
|
||||
if room.ID != 0 {
|
||||
cacheRoom(room)
|
||||
}
|
||||
|
||||
monitor.Warnf("Default admin created for username=%s; change the password after first login", adminUsername)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user