完善提取处理流程,解决提取并发问题
This commit is contained in:
@@ -25,7 +25,7 @@ var Channel = &channelServer{
|
||||
|
||||
type ChannelServiceProvider interface {
|
||||
CreateChannels(source netip.Addr, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter *EdgeFilter) ([]*m.Channel, error)
|
||||
RemoveChannels(batch string, proxyId *int32) error
|
||||
RemoveChannels(batch string) error
|
||||
ClearExpiredChannels(proxyId int32) (int, error)
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ func (s *channelServer) CreateChannels(source netip.Addr, resourceId int32, auth
|
||||
return s.provider.CreateChannels(source, resourceId, authWhitelist, authPassword, count, edgeFilter)
|
||||
}
|
||||
|
||||
func (s *channelServer) RemoveChannels(batch string, proxyId *int32) error {
|
||||
return s.provider.RemoveChannels(batch, proxyId)
|
||||
func (s *channelServer) RemoveChannels(batch string) error {
|
||||
return s.provider.RemoveChannels(batch)
|
||||
}
|
||||
|
||||
func (s *channelServer) ClearExpiredChannels(proxyId int32) (int, error) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"gorm.io/gen"
|
||||
"gorm.io/gen/field"
|
||||
)
|
||||
|
||||
@@ -29,7 +30,7 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
batch := ID.GenReadable("bat")
|
||||
batchNo := ID.GenReadable("bat")
|
||||
|
||||
// 检查并获取套餐与白名单
|
||||
resource, whitelists, err := ensure(now, source, resourceId, authWhitelist, count)
|
||||
@@ -40,210 +41,168 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
user := resource.User
|
||||
expire := now.Add(resource.Live)
|
||||
|
||||
// 注册异步关闭任务
|
||||
_, err = g.Asynq.Enqueue(
|
||||
e.NewRemoveChannel(batch),
|
||||
asynq.ProcessAt(expire),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("注册异步关闭通道任务失败", err)
|
||||
}
|
||||
|
||||
// 选择代理
|
||||
proxyResult := struct {
|
||||
m.Proxy
|
||||
Count int
|
||||
}{}
|
||||
err = q.Proxy.
|
||||
LeftJoin(q.Channel, q.Channel.ProxyID.EqCol(q.Proxy.ID), q.Channel.ExpiredAt.Gt(now)).
|
||||
Select(q.Proxy.ALL, field.NewUnsafeFieldRaw("10000 - count(*)").As("count")).
|
||||
Where(
|
||||
q.Proxy.Type.Eq(int(m.ProxyTypeBaiYin)),
|
||||
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
|
||||
).
|
||||
Group(q.Proxy.ID).
|
||||
Order(field.NewField("", "count")).
|
||||
Limit(1).Scan(&proxyResult)
|
||||
proxy, gateway, err := selectProxy(count)
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("获取可用代理失败", err)
|
||||
return nil, err
|
||||
}
|
||||
if proxyResult.Count < count {
|
||||
return nil, core.NewBizErr("无可用主机,请稍后再试")
|
||||
}
|
||||
proxy := proxyResult.Proxy
|
||||
|
||||
// 取用端口
|
||||
var chans []netip.AddrPort
|
||||
err = g.Redsync.WithLock(proxyStatusLockKey(proxy.ID), func() error {
|
||||
lockedProxy, err := q.Proxy.Where(q.Proxy.ID.Eq(proxy.ID)).Take()
|
||||
chans, err := selectPorts(proxy.ID, batchNo, count, expire)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 节点查询到提交,需要锁定防止并发取用
|
||||
channels := make([]*m.Channel, count)
|
||||
err = g.Redsync.WithLock(lockChannelCreateKey(), func() error {
|
||||
// 取用节点
|
||||
edges, err := selectEdges(gateway, filter, count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if lockedProxy.Status != m.ProxyStatusOnline {
|
||||
return core.NewBizErr("无可用主机,请稍后再试")
|
||||
|
||||
// 绑定节点端口
|
||||
chanConfigs := make([]*g.PortConfigsReq, count)
|
||||
edgeConfigs := make([]string, 0, count)
|
||||
for i := range count {
|
||||
ch := chans[i]
|
||||
edge := edges[i]
|
||||
|
||||
// 通道数据
|
||||
channels[i] = &m.Channel{
|
||||
UserID: user.ID,
|
||||
ResourceID: resourceId,
|
||||
BatchNo: batchNo,
|
||||
ProxyID: proxy.ID,
|
||||
Host: u.Else(proxy.Host, proxy.IP.String()),
|
||||
Port: ch.Port(),
|
||||
EdgeRef: u.P(edge.EdgeID),
|
||||
FilterISP: filter.Isp,
|
||||
FilterProv: filter.Prov,
|
||||
FilterCity: filter.City,
|
||||
ExpiredAt: expire,
|
||||
Proxy: proxy,
|
||||
}
|
||||
|
||||
// 通道配置数据
|
||||
chanConfigs[i] = &g.PortConfigsReq{
|
||||
Port: int(ch.Port()),
|
||||
Status: true,
|
||||
Edge: &[]string{edge.EdgeID},
|
||||
}
|
||||
|
||||
// 白名单模式
|
||||
if authWhitelist {
|
||||
channels[i].Whitelists = u.P(strings.Join(whitelists, ","))
|
||||
chanConfigs[i].Whitelist = &whitelists
|
||||
}
|
||||
|
||||
// 密码模式
|
||||
if authPassword {
|
||||
username, password := genPassPair()
|
||||
channels[i].Username = &username
|
||||
channels[i].Password = &password
|
||||
chanConfigs[i].Userpass = u.P(username + ":" + password)
|
||||
}
|
||||
|
||||
// 连接配置数据
|
||||
if edge.Type == EdgeInfoCloud {
|
||||
edgeConfigs = append(edgeConfigs, edge.EdgeID)
|
||||
}
|
||||
}
|
||||
|
||||
chans, err = lockChans(proxy.ID, batch, count)
|
||||
if err != nil {
|
||||
return core.NewBizErr("无可用通道,请稍后再试", err)
|
||||
// 提交配置
|
||||
slog.Debug("提交代理端口配置", "proxy", proxy.IP.String(), "total_count", len(chanConfigs), "remote_count", len(edgeConfigs))
|
||||
if env.RunMode == env.RunModeProd {
|
||||
|
||||
// 连接节点到网关
|
||||
if err := g.Cloud.CloudConnect(&g.CloudConnectReq{Uuid: proxy.Mac, Edge: &edgeConfigs}); err != nil {
|
||||
return core.NewServErr("连接云平台失败", err)
|
||||
}
|
||||
|
||||
// 启用网关代理通道
|
||||
if err := gateway.GatewayPortConfigs(chanConfigs); err != nil {
|
||||
slog.Warn("提交代理端口配置失败", "error", err.Error())
|
||||
return core.NewServErr(fmt.Sprintf("配置代理 %s 端口失败", proxy.IP.String()), err)
|
||||
}
|
||||
} else {
|
||||
for _, item := range chanConfigs {
|
||||
str, _ := json.Marshal(item)
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
}
|
||||
|
||||
proxy = *lockedProxy
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 保存数据
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
// 更新使用记录
|
||||
var result gen.ResultInfo
|
||||
var err error
|
||||
switch resource.Type {
|
||||
case m.ResourceTypeShort:
|
||||
result, err = q.ResourceShort.
|
||||
Where(
|
||||
q.ResourceShort.ID.Eq(*resource.ShortId),
|
||||
q.ResourceShort.Used.Eq(resource.Used),
|
||||
q.ResourceShort.Daily.Eq(resource.Daily),
|
||||
).
|
||||
UpdateSimple(
|
||||
q.ResourceShort.Used.Add(int32(count)),
|
||||
q.ResourceShort.Daily.Value(int32(resource.Today+count)),
|
||||
q.ResourceShort.LastAt.Value(now),
|
||||
)
|
||||
|
||||
// 取用节点
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
if len(secret) != 2 {
|
||||
return nil, core.NewServErr(fmt.Sprintf("代理 %s 密钥格式错误", proxy.IP.String()), nil)
|
||||
}
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
case m.ResourceTypeLong:
|
||||
result, err = q.ResourceLong.
|
||||
Where(
|
||||
q.ResourceLong.ID.Eq(*resource.LongId),
|
||||
q.ResourceLong.Used.Eq(resource.Used),
|
||||
q.ResourceLong.Daily.Eq(resource.Daily),
|
||||
).
|
||||
UpdateSimple(
|
||||
q.ResourceLong.Used.Add(int32(count)),
|
||||
q.ResourceLong.Daily.Value(int32(resource.Today+count)),
|
||||
q.ResourceLong.LastAt.Value(now),
|
||||
)
|
||||
|
||||
edges, err := getAvailableEdges(gateway, filter, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return core.NewBizErr("套餐类型不正确,无法更新", nil)
|
||||
}
|
||||
if err != nil {
|
||||
return core.NewServErr("更新套餐使用记录失败", err)
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return core.NewBizErr("提取太频繁,请稍后再试", nil)
|
||||
}
|
||||
|
||||
// 绑定节点到端口
|
||||
channels := make([]*m.Channel, count)
|
||||
chanConfigs := make([]*g.PortConfigsReq, count)
|
||||
edgeConfigs := make([]string, 0, count)
|
||||
for i := range count {
|
||||
ch := chans[i]
|
||||
edge := edges[i]
|
||||
// 保存通道
|
||||
err = q.Channel.
|
||||
Omit(field.AssociationFields).
|
||||
Create(channels...)
|
||||
if err != nil {
|
||||
return core.NewServErr("保存通道失败", err)
|
||||
}
|
||||
|
||||
// 通道数据
|
||||
channels[i] = &m.Channel{
|
||||
UserID: user.ID,
|
||||
ResourceID: resourceId,
|
||||
BatchNo: batch,
|
||||
ProxyID: proxy.ID,
|
||||
Host: u.Else(proxy.Host, proxy.IP.String()),
|
||||
Port: ch.Port(),
|
||||
EdgeRef: u.P(edge.EdgeID),
|
||||
FilterISP: filter.Isp,
|
||||
FilterProv: filter.Prov,
|
||||
FilterCity: filter.City,
|
||||
ExpiredAt: expire,
|
||||
Proxy: &proxy,
|
||||
}
|
||||
// 保存提取记录
|
||||
err = q.LogsUserUsage.Create(&m.LogsUserUsage{
|
||||
UserID: user.ID,
|
||||
ResourceID: resourceId,
|
||||
BatchNo: batchNo,
|
||||
Count: int32(count),
|
||||
ISP: u.X(filter.Isp.String()),
|
||||
Prov: filter.Prov,
|
||||
City: filter.City,
|
||||
IP: orm.Inet{Addr: source},
|
||||
Time: now,
|
||||
})
|
||||
if err != nil {
|
||||
return core.NewServErr("保存用户使用记录失败", err)
|
||||
}
|
||||
|
||||
// 通道配置数据
|
||||
chanConfigs[i] = &g.PortConfigsReq{
|
||||
Port: int(ch.Port()),
|
||||
Status: true,
|
||||
Edge: &[]string{edge.EdgeID},
|
||||
}
|
||||
|
||||
// 白名单模式
|
||||
if authWhitelist {
|
||||
channels[i].Whitelists = u.P(strings.Join(whitelists, ","))
|
||||
chanConfigs[i].Whitelist = &whitelists
|
||||
}
|
||||
|
||||
// 密码模式
|
||||
if authPassword {
|
||||
username, password := genPassPair()
|
||||
channels[i].Username = &username
|
||||
channels[i].Password = &password
|
||||
chanConfigs[i].Userpass = u.P(username + ":" + password)
|
||||
}
|
||||
|
||||
// 连接配置数据
|
||||
if edge.Type == EdgeInfoCloud {
|
||||
edgeConfigs = append(edgeConfigs, edge.EdgeID)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交配置
|
||||
slog.Debug("提交代理端口配置", "proxy", proxy.IP.String(), "total_count", len(chanConfigs), "remote_count", len(edgeConfigs))
|
||||
if env.RunMode == env.RunModeProd {
|
||||
|
||||
// 连接节点到网关
|
||||
err = g.Cloud.CloudConnect(&g.CloudConnectReq{
|
||||
Uuid: proxy.Mac,
|
||||
Edge: &edgeConfigs,
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("连接云平台失败", err)
|
||||
}
|
||||
|
||||
// 启用网关代理通道
|
||||
err = gateway.GatewayPortConfigs(chanConfigs)
|
||||
if err != nil {
|
||||
slog.Warn("提交代理端口配置失败", "error", err.Error())
|
||||
return nil, core.NewServErr(fmt.Sprintf("配置代理 %s 端口失败", proxy.IP.String()), err)
|
||||
}
|
||||
} else {
|
||||
for _, item := range chanConfigs {
|
||||
str, _ := json.Marshal(item)
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
}
|
||||
|
||||
// 保存数据
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
// 更新使用记录
|
||||
var err error
|
||||
switch resource.Type {
|
||||
case m.ResourceTypeShort:
|
||||
_, err = q.ResourceShort.
|
||||
Where(
|
||||
q.ResourceShort.ID.Eq(*resource.ShortId),
|
||||
q.ResourceShort.Used.Eq(resource.Used),
|
||||
q.ResourceShort.Daily.Eq(resource.Daily),
|
||||
).
|
||||
UpdateSimple(
|
||||
q.ResourceShort.Used.Add(int32(count)),
|
||||
q.ResourceShort.Daily.Value(int32(resource.Today+count)),
|
||||
q.ResourceShort.LastAt.Value(now),
|
||||
)
|
||||
|
||||
case m.ResourceTypeLong:
|
||||
_, err = q.ResourceLong.
|
||||
Where(
|
||||
q.ResourceLong.ID.Eq(*resource.LongId),
|
||||
q.ResourceLong.Used.Eq(resource.Used),
|
||||
q.ResourceLong.Daily.Eq(resource.Daily),
|
||||
).
|
||||
UpdateSimple(
|
||||
q.ResourceLong.Used.Add(int32(count)),
|
||||
q.ResourceLong.Daily.Value(int32(resource.Today+count)),
|
||||
q.ResourceLong.LastAt.Value(now),
|
||||
)
|
||||
|
||||
default:
|
||||
return core.NewServErr("套餐类型不正确,无法更新", nil)
|
||||
}
|
||||
if err != nil {
|
||||
return core.NewServErr("更新套餐使用记录失败", err)
|
||||
}
|
||||
|
||||
// 保存通道
|
||||
err = q.Channel.
|
||||
Omit(field.AssociationFields).
|
||||
Create(channels...)
|
||||
if err != nil {
|
||||
return core.NewServErr("保存通道失败", err)
|
||||
}
|
||||
|
||||
// 保存提取记录
|
||||
err = q.LogsUserUsage.Create(&m.LogsUserUsage{
|
||||
UserID: user.ID,
|
||||
ResourceID: resourceId,
|
||||
BatchNo: batch,
|
||||
Count: int32(count),
|
||||
ISP: u.P(filter.Isp.String()),
|
||||
Prov: filter.Prov,
|
||||
City: filter.City,
|
||||
IP: orm.Inet{Addr: source},
|
||||
Time: now,
|
||||
})
|
||||
if err != nil {
|
||||
return core.NewServErr("保存用户使用记录失败", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -255,110 +214,106 @@ func (s *channelBaiyinProvider) CreateChannels(source netip.Addr, resourceId int
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func (s *channelBaiyinProvider) RemoveChannels(batch string, proxyId *int32) error {
|
||||
return g.Redsync.WithLock(batchRemoveExpiredKey(batch), func() error {
|
||||
func (s *channelBaiyinProvider) RemoveChannels(batch string) error {
|
||||
return g.Redsync.WithLock(lockChannelRemoveKey(batch), func() error {
|
||||
start := time.Now()
|
||||
|
||||
pid := int32(0)
|
||||
if proxyId == nil {
|
||||
// 获取连接数据
|
||||
channels, err := q.Channel.Where(q.Channel.BatchNo.Eq(batch)).Find()
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("获取通道数据失败,batch:%s", batch), err)
|
||||
}
|
||||
if len(channels) == 0 {
|
||||
slog.Warn(fmt.Sprintf("未找到通道数据,batch:%s", batch))
|
||||
return nil
|
||||
}
|
||||
// 获取连接数据
|
||||
channels, err := q.Channel.Where(q.Channel.BatchNo.Eq(batch)).Find()
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("获取通道数据失败,batch:%s", batch), err)
|
||||
}
|
||||
if len(channels) == 0 {
|
||||
slog.Warn(fmt.Sprintf("未找到通道数据,batch:%s", batch))
|
||||
return nil
|
||||
}
|
||||
|
||||
proxy, err := q.Proxy.Where(q.Proxy.ID.Eq(channels[0].ProxyID)).Take()
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("获取代理数据失败,batch:%s", batch), err)
|
||||
}
|
||||
proxy, err := q.Proxy.Where(q.Proxy.ID.Eq(channels[0].ProxyID)).Take()
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("获取代理数据失败,batch:%s", batch), err)
|
||||
}
|
||||
|
||||
// 检查通道是否存在
|
||||
exist, err := g.Redis.Exists(context.Background(), usedChansKey(proxy.ID, batch)).Result()
|
||||
if err != nil {
|
||||
return core.NewServErr("查询使用中通道失败", err)
|
||||
}
|
||||
if exist == 0 {
|
||||
return nil // 没有使用中通道,已经被清理过了
|
||||
}
|
||||
|
||||
// 准备配置数据
|
||||
edgeConfigs := make([]string, len(channels))
|
||||
configs := make([]*g.PortConfigsReq, len(channels))
|
||||
for i, channel := range channels {
|
||||
if channel.EdgeRef != nil {
|
||||
edgeConfigs[i] = *channel.EdgeRef
|
||||
} else {
|
||||
slog.Warn(fmt.Sprintf("通道 %d 没有保存节点引用", channel.ID))
|
||||
}
|
||||
|
||||
configs[i] = &g.PortConfigsReq{
|
||||
Status: false,
|
||||
Port: int(channel.Port),
|
||||
Edge: &[]string{},
|
||||
}
|
||||
}
|
||||
|
||||
// 提交配置
|
||||
if env.RunMode == env.RunModeProd {
|
||||
|
||||
// 清空通道配置
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
err := gateway.GatewayPortConfigs(configs)
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("清空代理 %s 端口配置失败", proxy.IP.String()), err)
|
||||
}
|
||||
|
||||
// 断开节点连接
|
||||
_, err = g.Cloud.CloudDisconnect(&g.CloudDisconnectReq{
|
||||
Uuid: proxy.Mac,
|
||||
Edge: &edgeConfigs,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Warn("断开云平台连接失败", "error", err.Error())
|
||||
return core.NewServErr("断开云平台连接失败", err)
|
||||
}
|
||||
// 检查通道是否存在
|
||||
exist, err := g.Redis.Exists(context.Background(), usedChansKey(proxy.ID, batch)).Result()
|
||||
if err != nil {
|
||||
return core.NewServErr("查询使用中通道失败", err)
|
||||
}
|
||||
if exist == 0 {
|
||||
return nil // 没有使用中通道,已经被清理过了
|
||||
}
|
||||
|
||||
// 准备配置数据
|
||||
edgeConfigs := make([]string, len(channels))
|
||||
configs := make([]*g.PortConfigsReq, len(channels))
|
||||
for i, channel := range channels {
|
||||
if channel.EdgeRef != nil {
|
||||
edgeConfigs[i] = *channel.EdgeRef
|
||||
} else {
|
||||
for _, item := range configs {
|
||||
str, _ := json.Marshal(item)
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
slog.Warn(fmt.Sprintf("通道 %d 没有保存节点引用", channel.ID))
|
||||
}
|
||||
|
||||
configs[i] = &g.PortConfigsReq{
|
||||
Status: false,
|
||||
Port: int(channel.Port),
|
||||
Edge: &[]string{},
|
||||
}
|
||||
}
|
||||
|
||||
// 提交配置
|
||||
if env.RunMode == env.RunModeProd {
|
||||
|
||||
// 清空通道配置
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
if len(secret) != 2 {
|
||||
return core.NewServErr(fmt.Sprintf("代理 %s 密钥格式错误", proxy.IP.String()), nil)
|
||||
}
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
err := gateway.GatewayPortConfigs(configs)
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("清空代理 %s 端口配置失败", proxy.IP.String()), err)
|
||||
}
|
||||
|
||||
// 断开节点连接
|
||||
_, err = g.Cloud.CloudDisconnect(&g.CloudDisconnectReq{
|
||||
Uuid: proxy.Mac,
|
||||
Edge: &edgeConfigs,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Warn("断开云平台连接失败", "error", err.Error())
|
||||
return core.NewServErr("断开云平台连接失败", err)
|
||||
}
|
||||
|
||||
pid = proxy.ID
|
||||
} else {
|
||||
pid = *proxyId
|
||||
for _, item := range configs {
|
||||
str, _ := json.Marshal(item)
|
||||
fmt.Println(string(str))
|
||||
}
|
||||
}
|
||||
|
||||
// 释放端口
|
||||
err := freeChans(pid, batch)
|
||||
err = freeChans(proxy.ID, batch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
slog.Debug("清除代理端口配置", "proxy", pid, "batch", batch, "duration", time.Since(start).String())
|
||||
slog.Debug("清除代理端口配置", "proxy", proxy.ID, "batch", batch, "duration", time.Since(start).String())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ClearExpiredChannels 定期清理过期通道,返回清理数量
|
||||
// 通道有三种情况:
|
||||
// - 过期等待清理,过期时间在一小时内,可以等待异步任务回收通道
|
||||
// - 过期未清理,过期时间超过一小时,说明异步任务可能失败了,需要强制清理
|
||||
// - 异常通道,取用后任务失败,导致通道悬空,需要强制清理
|
||||
// ClearExpiredChannels 清理指定代理的过期通道,并返回清理数量(现在理论上不会有需要手动批量清理的通道,未来可以废弃)
|
||||
func (s *channelBaiyinProvider) ClearExpiredChannels(proxyId int32) (int, error) {
|
||||
now := time.Now()
|
||||
|
||||
// 获取使用中通道批次
|
||||
// 获取未清理通道
|
||||
keys, err := g.Redis.Keys(context.Background(), usedChansKey(proxyId, "*")).Result()
|
||||
if err != nil {
|
||||
return 0, core.NewServErr("查询使用中通道失败", err)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
batchList := make([]string, len(keys))
|
||||
batchSet := make(map[string]struct{}, len(keys))
|
||||
for i, key := range keys {
|
||||
@@ -390,7 +345,7 @@ func (s *channelBaiyinProvider) ClearExpiredChannels(proxyId int32) (int, error)
|
||||
// 清理过期通道
|
||||
slog.Info("批量清理过期通道", "count", len(batchSet))
|
||||
for batchNo, _ := range batchSet {
|
||||
err := s.RemoveChannels(batchNo, &proxyId)
|
||||
err := s.RemoveChannels(batchNo)
|
||||
if err != nil {
|
||||
slog.Error("清理过期通道失败", "batch", batchNo, "error", err)
|
||||
}
|
||||
@@ -399,11 +354,83 @@ func (s *channelBaiyinProvider) ClearExpiredChannels(proxyId int32) (int, error)
|
||||
return len(batchSet), nil
|
||||
}
|
||||
|
||||
func batchRemoveExpiredKey(bid string) string {
|
||||
func lockChannelCreateKey() string {
|
||||
return "platform:channel:create"
|
||||
}
|
||||
|
||||
func lockChannelRemoveKey(bid string) string {
|
||||
return fmt.Sprintf("platform:batch:remove_expired:%s", bid)
|
||||
}
|
||||
|
||||
func getAvailableEdges(gateway g.GatewayClient, filter *EdgeFilter, count int) ([]EdgeInfo, error) {
|
||||
func selectProxy(count int) (*m.Proxy, g.GatewayClient, error) {
|
||||
// 获取在线节点
|
||||
proxies, err := q.Proxy.Where(
|
||||
q.Proxy.Type.Eq(int(m.ProxyTypeBaiYin)),
|
||||
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
|
||||
).Find()
|
||||
if err != nil {
|
||||
return nil, nil, core.NewBizErr("获取可用代理失败", err)
|
||||
}
|
||||
if len(proxies) == 0 {
|
||||
return nil, nil, core.NewBizErr("无可用代理")
|
||||
}
|
||||
|
||||
proxyIDs := make([]int32, 0, len(proxies))
|
||||
proxyMap := make(map[int32]*m.Proxy, len(proxies))
|
||||
for _, item := range proxies {
|
||||
proxyIDs = append(proxyIDs, item.ID)
|
||||
proxyMap[item.ID] = item
|
||||
}
|
||||
|
||||
// 获取最空闲节点
|
||||
maxId := int32(0)
|
||||
maxCount := -1
|
||||
for _, id := range proxyIDs {
|
||||
idCount, err := g.Redis.SCard(context.Background(), freeChansKey(id)).Result()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("查询可用通道数量失败: %w", err)
|
||||
}
|
||||
|
||||
if idCount > int64(maxCount) {
|
||||
maxCount = int(idCount)
|
||||
maxId = id
|
||||
}
|
||||
}
|
||||
if maxCount < count {
|
||||
return nil, nil, core.NewBizErr("无可用代理")
|
||||
}
|
||||
proxy := proxyMap[maxId]
|
||||
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
if len(secret) != 2 {
|
||||
return nil, nil, core.NewServErr(fmt.Sprintf("代理 %s 密钥格式错误", proxy.IP.String()), nil)
|
||||
}
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
|
||||
return proxy, gateway, nil
|
||||
}
|
||||
|
||||
func selectPorts(proxyId int32, batchNo string, count int, expire time.Time) ([]netip.AddrPort, error) {
|
||||
chans, err := lockChans(proxyId, batchNo, count)
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("无可用通道,请稍后再试", err)
|
||||
}
|
||||
|
||||
_, err = g.Asynq.Enqueue(
|
||||
e.NewRemoveChannel(batchNo),
|
||||
asynq.ProcessAt(expire),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("注册异步关闭通道任务失败", err)
|
||||
}
|
||||
|
||||
return chans, nil
|
||||
}
|
||||
|
||||
// selectEdges 选择节点,优先本地节点,失败重试,直到达到重试次数限制
|
||||
// 本地节点通过 Assigned = false 排除已分配节点
|
||||
// 云端节点通过 NoRepeat = true 排除已分配节点
|
||||
func selectEdges(gateway g.GatewayClient, filter *EdgeFilter, count int) ([]EdgeInfo, error) {
|
||||
edges := make([]EdgeInfo, 0, count)
|
||||
|
||||
// 先查本地
|
||||
@@ -428,7 +455,7 @@ func getAvailableEdges(gateway g.GatewayClient, filter *EdgeFilter, count int) (
|
||||
return edges, nil
|
||||
}
|
||||
|
||||
// 再查云端无重复
|
||||
// 再查云端
|
||||
remaining := count - len(edges)
|
||||
cloudEdgesResp, err := g.Cloud.CloudEdges(&g.CloudEdgesReq{
|
||||
Province: filter.Prov,
|
||||
@@ -449,13 +476,11 @@ func getAvailableEdges(gateway g.GatewayClient, filter *EdgeFilter, count int) (
|
||||
EdgeID: edge.EdgeID,
|
||||
})
|
||||
}
|
||||
if len(edges) >= count {
|
||||
return edges, nil
|
||||
if len(edges) < count {
|
||||
return nil, core.NewBizErr("地区可用节点数量不足")
|
||||
}
|
||||
|
||||
// 不能和已有的重复,如果有重复则再次查询云端补足,二次提取还有重复则放弃
|
||||
|
||||
return nil, core.NewBizErr("地区可用节点数量不足")
|
||||
return edges, nil
|
||||
}
|
||||
|
||||
type EdgeInfo struct {
|
||||
|
||||
@@ -2,7 +2,6 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
@@ -19,10 +18,6 @@ var Proxy = &proxyService{}
|
||||
|
||||
type proxyService struct{}
|
||||
|
||||
func proxyStatusLockKey(id int32) string {
|
||||
return fmt.Sprintf("platform:proxy:status:%d", id)
|
||||
}
|
||||
|
||||
func hasUsedChans(proxyID int32) (bool, error) {
|
||||
ctx := context.Background()
|
||||
pattern := usedChansKey(proxyID, "*")
|
||||
@@ -191,7 +186,7 @@ type UpdateProxyStatus struct {
|
||||
}
|
||||
|
||||
func (s *proxyService) UpdateStatus(update *UpdateProxyStatus) error {
|
||||
return g.Redsync.WithLock(proxyStatusLockKey(update.ID), func() error {
|
||||
return q.Q.Transaction(func(tx *q.Query) error {
|
||||
proxy, err := q.Proxy.Where(q.Proxy.ID.Eq(update.ID)).Take()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user