|
@@ -3,11 +3,13 @@ package com.fs.feishu.util;
|
|
|
import com.fs.course.config.CourseConfig;
|
|
import com.fs.course.config.CourseConfig;
|
|
|
|
|
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
|
+import java.util.Set;
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * SOP群发/自动执行场景,飞书直链文档分片分配器,控制单个分片承载的客户数量,实现租户维度文档分片负载均衡
|
|
|
|
|
|
|
+ * SOP群发/自动执行场景,飞书直链文档分片分配器。
|
|
|
|
|
+ * 控制单个分片承载客户数量,实现租户维度文档分片负载均衡,规避飞书文档访问限流。
|
|
|
*/
|
|
*/
|
|
|
public class FeishuDirectDocShardAllocator {
|
|
public class FeishuDirectDocShardAllocator {
|
|
|
|
|
|
|
@@ -15,20 +17,27 @@ public class FeishuDirectDocShardAllocator {
|
|
|
private static final int DEFAULT_MAX_CUSTOMERS_PER_SHARD_AUTO = 50;
|
|
private static final int DEFAULT_MAX_CUSTOMERS_PER_SHARD_AUTO = 50;
|
|
|
|
|
|
|
|
private final int maxCustomersPerShard;
|
|
private final int maxCustomersPerShard;
|
|
|
|
|
+ private final boolean refreshCacheBeforeUse;
|
|
|
private final Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
|
|
private final Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
|
|
|
|
|
+ private final Set<String> cacheRefreshedKeys = ConcurrentHashMap.newKeySet();
|
|
|
|
|
|
|
|
- private FeishuDirectDocShardAllocator(int maxCustomersPerShard) {
|
|
|
|
|
|
|
+ private FeishuDirectDocShardAllocator(int maxCustomersPerShard, boolean refreshCacheBeforeUse) {
|
|
|
this.maxCustomersPerShard = maxCustomersPerShard > 0 ? maxCustomersPerShard : DEFAULT_MAX_CUSTOMERS_PER_SHARD_MASS_SEND;
|
|
this.maxCustomersPerShard = maxCustomersPerShard > 0 ? maxCustomersPerShard : DEFAULT_MAX_CUSTOMERS_PER_SHARD_MASS_SEND;
|
|
|
|
|
+ this.refreshCacheBeforeUse = refreshCacheBeforeUse;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 群发场景分配器 */
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 群发场景分片分配器:消息推送前需要刷新文档缓存
|
|
|
|
|
+ */
|
|
|
public static FeishuDirectDocShardAllocator forMassSend(CourseConfig config) {
|
|
public static FeishuDirectDocShardAllocator forMassSend(CourseConfig config) {
|
|
|
- return new FeishuDirectDocShardAllocator(resolveMaxCustomersPerShardForMassSend(config));
|
|
|
|
|
|
|
+ return new FeishuDirectDocShardAllocator(resolveMaxCustomersPerShardForMassSend(config), true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** SOP自动执行场景分配器 */
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * SOP自动执行场景分片分配器:平缓流量,无需前置刷新文档缓存
|
|
|
|
|
+ */
|
|
|
public static FeishuDirectDocShardAllocator forAutoSop(CourseConfig config) {
|
|
public static FeishuDirectDocShardAllocator forAutoSop(CourseConfig config) {
|
|
|
- return new FeishuDirectDocShardAllocator(resolveMaxCustomersPerShardForAuto(config));
|
|
|
|
|
|
|
+ return new FeishuDirectDocShardAllocator(resolveMaxCustomersPerShardForAuto(config), false);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public static int resolveMaxCustomersPerShardForMassSend(CourseConfig config) {
|
|
public static int resolveMaxCustomersPerShardForMassSend(CourseConfig config) {
|
|
@@ -52,7 +61,19 @@ public class FeishuDirectDocShardAllocator {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 获取当前客户应当使用的分片编号,从 0 开始
|
|
|
|
|
|
|
+ * 群发场景下,每组维度首次分配分片前刷新Redis文档缓存,避免重复扫描
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return true 代表当前key首次标记,需要执行缓存刷新逻辑
|
|
|
|
|
+ */
|
|
|
|
|
+ public boolean markDirectDocCacheForRefresh(Long companyUserId, Long videoId, Long courseId, Long feishuAccountId) {
|
|
|
|
|
+ if (!refreshCacheBeforeUse) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ return cacheRefreshedKeys.add(buildKey(companyUserId, videoId, courseId, feishuAccountId));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取当前客户分配到的分片编号,分片从0开始
|
|
|
*/
|
|
*/
|
|
|
public int nextShard(Long companyUserId, Long videoId, Long courseId, Long feishuAccountId) {
|
|
public int nextShard(Long companyUserId, Long videoId, Long courseId, Long feishuAccountId) {
|
|
|
String key = buildKey(companyUserId, videoId, courseId, feishuAccountId);
|
|
String key = buildKey(companyUserId, videoId, courseId, feishuAccountId);
|