Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 46 additions & 65 deletions model/ability.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package model
import (
"errors"
"fmt"
"sort"
"strings"
"sync"

Expand Down Expand Up @@ -60,87 +61,67 @@ func GetAllEnableAbilities() []Ability {
return abilities
}

func getPriority(group string, model string, retry int) (int, error) {

var priorities []int
err := DB.Model(&Ability{}).
Select("DISTINCT(priority)").
Where(commonGroupCol+" = ? and model = ? and enabled = ?", group, model, true).
Order("priority DESC"). // 按优先级降序排序
Pluck("priority", &priorities).Error // Pluck用于将查询的结果直接扫描到一个切片中

if err != nil {
// 处理错误
return 0, err
func abilityPriority(ability Ability) int64 {
if ability.Priority == nil {
return 0
}
return *ability.Priority
}

if len(priorities) == 0 {
// 如果没有查询到优先级,则返回错误
return 0, errors.New("数据库一致性被破坏")
func selectAbilityPriority(abilities []Ability, retry int) int64 {
priorities := make(map[int64]struct{}, len(abilities))
for _, ability := range abilities {
priorities[abilityPriority(ability)] = struct{}{}
}

// 确定要使用的优先级
var priorityToUse int
if retry >= len(priorities) {
// 如果重试次数大于优先级数,则使用最小的优先级
priorityToUse = priorities[len(priorities)-1]
} else {
priorityToUse = priorities[retry]
sortedPriorities := make([]int64, 0, len(priorities))
for priority := range priorities {
sortedPriorities = append(sortedPriorities, priority)
}
return priorityToUse, nil
}

func getChannelQuery(group string, model string, retry int) (*gorm.DB, error) {
maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(commonGroupCol+" = ? and model = ? and enabled = ?", group, model, true)
channelQuery := DB.Where(commonGroupCol+" = ? and model = ? and enabled = ? and priority = (?)", group, model, true, maxPrioritySubQuery)
if retry != 0 {
priority, err := getPriority(group, model, retry)
if err != nil {
return nil, err
} else {
channelQuery = DB.Where(commonGroupCol+" = ? and model = ? and enabled = ? and priority = ?", group, model, true, priority)
}
sort.Slice(sortedPriorities, func(i, j int) bool { return sortedPriorities[i] > sortedPriorities[j] })
if retry >= len(sortedPriorities) {
retry = len(sortedPriorities) - 1
}

return channelQuery, nil
return sortedPriorities[retry]
}

func GetChannel(group string, model string, retry int, requestPath string) (*Channel, error) {
var abilities []Ability

var err error = nil
channelQuery, err := getChannelQuery(group, model, retry)
err := DB.Where(commonGroupCol+" = ? and model = ? and enabled = ?", group, model, true).
Order("weight DESC").
Find(&abilities).Error
if err != nil {
return nil, err
}
if common.UsingMainDatabase(common.DatabaseTypeSQLite) || common.UsingMainDatabase(common.DatabaseTypePostgreSQL) {
err = channelQuery.Order("weight DESC").Find(&abilities).Error
} else {
err = channelQuery.Order("weight DESC").Find(&abilities).Error

// Path eligibility must be determined before priority selection. Otherwise a
// priority with no route for this request shifts the retry index and can cause
// the next retry to select the same eligible priority again.
abilities = filterAbilitiesByRequestPathAndModel(abilities, requestPath, model)
if len(abilities) == 0 {
return nil, nil
}
if err != nil {
return nil, err
targetPriority := selectAbilityPriority(abilities, retry)
priorityAbilities := make([]Ability, 0, len(abilities))
for _, ability := range abilities {
if abilityPriority(ability) == targetPriority {
priorityAbilities = append(priorityAbilities, ability)
}
}
abilities = filterAbilitiesByRequestPathAndModel(abilities, requestPath, model)
abilities = priorityAbilities

channel := Channel{}
if len(abilities) > 0 {
// Randomly choose one
weightSum := uint(0)
for _, ability_ := range abilities {
weightSum += ability_.Weight + 10
}
// Randomly choose one
weight := common.GetRandomInt(int(weightSum))
for _, ability_ := range abilities {
weight -= int(ability_.Weight) + 10
//log.Printf("weight: %d, ability weight: %d", weight, *ability_.Weight)
if weight <= 0 {
channel.Id = ability_.ChannelId
break
}
// Randomly choose one
weightSum := uint(0)
for _, ability := range abilities {
weightSum += ability.Weight + 10
}
weight := common.GetRandomInt(int(weightSum))
for _, ability := range abilities {
weight -= int(ability.Weight) + 10
if weight <= 0 {
channel.Id = ability.ChannelId
break
}
} else {
return nil, nil
}
err = DB.First(&channel, "id = ?", channel.Id).Error
return &channel, err
Expand Down
78 changes: 46 additions & 32 deletions model/channel_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"math/rand"
"sort"
"strings"
"sync"
"time"

Expand All @@ -17,7 +16,8 @@ import (
)

var group2model2channels map[string]map[string][]int // enabled channel
var channelsIDM map[int]*Channel // all channels include disabled
var group2model2channelPriorities map[string]map[string]map[int]int64
var channelsIDM map[int]*Channel // all channels include disabled
// channel2advancedCustomConfig caches parsed Advanced Custom (type 58) configs so
// path-aware selection avoids re-parsing JSON per request. Refreshed on full sync.
var channel2advancedCustomConfig map[int]*dto.AdvancedCustomConfig
Expand All @@ -42,42 +42,44 @@ func InitChannelCache() {
}
var abilities []*Ability
DB.Find(&abilities)
groups := make(map[string]bool)
for _, ability := range abilities {
groups[ability.Group] = true
}
newGroup2model2channels := make(map[string]map[string][]int)
for group := range groups {
newGroup2model2channels[group] = make(map[string][]int)
}
for _, channel := range channels {
if channel.Status != common.ChannelStatusEnabled {
continue // skip disabled channels
newGroup2model2channelPriorities := make(map[string]map[string]map[int]int64)
for _, ability := range abilities {
if !ability.Enabled {
continue
}
groups := strings.Split(channel.Group, ",")
for _, group := range groups {
models := strings.Split(channel.Models, ",")
for _, model := range models {
if _, ok := newGroup2model2channels[group][model]; !ok {
newGroup2model2channels[group][model] = make([]int, 0)
}
newGroup2model2channels[group][model] = append(newGroup2model2channels[group][model], channel.Id)
}
channel, ok := newChannelId2channel[ability.ChannelId]
if !ok || channel.Status != common.ChannelStatusEnabled {
continue
}
if _, ok := newGroup2model2channels[ability.Group]; !ok {
newGroup2model2channels[ability.Group] = make(map[string][]int)
newGroup2model2channelPriorities[ability.Group] = make(map[string]map[int]int64)
}
if _, ok := newGroup2model2channelPriorities[ability.Group][ability.Model]; !ok {
newGroup2model2channelPriorities[ability.Group][ability.Model] = make(map[int]int64)
}
newGroup2model2channels[ability.Group][ability.Model] = append(
newGroup2model2channels[ability.Group][ability.Model],
ability.ChannelId,
)
newGroup2model2channelPriorities[ability.Group][ability.Model][ability.ChannelId] = abilityPriority(*ability)
}

// sort by priority
for group, model2channels := range newGroup2model2channels {
for model, channels := range model2channels {
priorities := newGroup2model2channelPriorities[group][model]
sort.Slice(channels, func(i, j int) bool {
return newChannelId2channel[channels[i]].GetPriority() > newChannelId2channel[channels[j]].GetPriority()
return priorities[channels[i]] > priorities[channels[j]]
})
newGroup2model2channels[group][model] = channels
}
}

channelSyncLock.Lock()
group2model2channels = newGroup2model2channels
group2model2channelPriorities = newGroup2model2channelPriorities
//channelsIDM = newChannelId2channel
for i, channel := range newChannelId2channel {
if channel.ChannelInfo.IsMultiKey {
Expand Down Expand Up @@ -140,31 +142,43 @@ func GetRandomSatisfiedChannel(group string, model string, retry int, requestPat
return nil, fmt.Errorf("数据库一致性错误,渠道# %d 不存在,请联系管理员修复", channels[0])
}

uniquePriorities := make(map[int]bool)
channelPriorities := group2model2channelPriorities[group][model]
if len(channelPriorities) == 0 {
normalizedModel := ratio_setting.FormatMatchingModelName(model)
channelPriorities = group2model2channelPriorities[group][normalizedModel]
}
uniquePriorities := make(map[int64]struct{})
for _, channelId := range channels {
if channel, ok := channelsIDM[channelId]; ok {
uniquePriorities[int(channel.GetPriority())] = true
} else {
if _, ok := channelsIDM[channelId]; !ok {
return nil, fmt.Errorf("数据库一致性错误,渠道# %d 不存在,请联系管理员修复", channelId)
}
priority, ok := channelPriorities[channelId]
if !ok {
priority = channelsIDM[channelId].GetPriority()
}
uniquePriorities[priority] = struct{}{}
}
var sortedUniquePriorities []int
sortedUniquePriorities := make([]int64, 0, len(uniquePriorities))
for priority := range uniquePriorities {
sortedUniquePriorities = append(sortedUniquePriorities, priority)
}
sort.Sort(sort.Reverse(sort.IntSlice(sortedUniquePriorities)))
sort.Slice(sortedUniquePriorities, func(i, j int) bool { return sortedUniquePriorities[i] > sortedUniquePriorities[j] })

if retry >= len(uniquePriorities) {
retry = len(uniquePriorities) - 1
if retry >= len(sortedUniquePriorities) {
retry = len(sortedUniquePriorities) - 1
}
targetPriority := int64(sortedUniquePriorities[retry])
targetPriority := sortedUniquePriorities[retry]

// get the priority for the given retry number
var sumWeight = 0
var targetChannels []*Channel
for _, channelId := range channels {
if channel, ok := channelsIDM[channelId]; ok {
if channel.GetPriority() == targetPriority {
priority, exists := channelPriorities[channelId]
if !exists {
priority = channel.GetPriority()
}
if priority == targetPriority {
sumWeight += channel.GetWeight()
targetChannels = append(targetChannels, channel)
}
Expand Down
Loading