如何使用ThreadLocal上下文解決查詢性能問題
問題背景:
安全事件列表中數(shù)據(jù)右鍵操作需要控制工單處置記錄和SOAR處置記錄是否置灰,置灰的邏輯是判斷當(dāng)前用戶是否有相應(yīng)的菜單權(quán)限以及當(dāng)前租戶是否有相應(yīng)的授權(quán)權(quán)限。這個判斷邏輯在每次刷新安全事件列表時都需要調(diào)用grpc接口判斷租戶是否授權(quán),為了提高性能,所以禁止每次都去請求調(diào)用grpc接口,有兩種解決方案,一種是使用ThreadLocal上下文,另一種是使用Redis緩存。簡單介紹下如何使用ThreadLocal上下文解決性能問題。
01. 定義實體 Subscription
@Generated
public final class TenantManagement {
@Generated
public static final class Subscription {
private volatile Object id;
private volatile Object name;
private volatile Object productId;
private long startTime;
private long expiryTime;
private long createTime;
private long renewTime;
private volatile Object info;
private volatile Object status;
private volatile Object subscribeType;
}
}02. 定義 SubscribeInfoListContext 上下文
利用ThreadLocal上下文存儲需要緩存的數(shù)據(jù) List<TenantManagement.Subscription>
public class SubscribeInfoListContext {
private static final ThreadLocal<List<TenantManagement.Subscription>> SUBSCRIBE_INFO_LIST_THREAD_LOCAL = new ThreadLocal<>();
public static void set(List<TenantManagement.Subscription> subscribeInfoList) {
SUBSCRIBE_INFO_LIST_THREAD_LOCAL.set(subscribeInfoList);
}
public static List<TenantManagement.Subscription> get() {
return SUBSCRIBE_INFO_LIST_THREAD_LOCAL.get();
}
public static void remove() {
SUBSCRIBE_INFO_LIST_THREAD_LOCAL.remove();
}
private SubscribeInfoListContext() {
}
}03. 定義SubscribeInfoService 接口
進行g(shù)rpc接口調(diào)用獲取 List<TenantManagement.Subscription> 信息
public interface SubscribeInfoService {
/**
* 獲取授權(quán)信息
*
* @return 授權(quán)信息
*/
List<TenantManagement.Subscription> getSubscribeInfoList();
}1. 定義 SubscribeInfoServiceImpl 實現(xiàn)類
@CustomLog
@Service
public class SubscribeInfoServiceImpl implements SubscribeInfoService {
@Setter(onMethod_ = @Autowired)
private TenantManageClient tenantManageClient;
@NotNull
private static final List<String> SUBSCRIPTION_NAME_LIST = List.of(
SubscriptionConstant.ORDER_SUBSCRIPTION,
SubscriptionConstant.SOAR_SUBSCRIPTION
);
@Override
public List<TenantManagement.Subscription> getSubscribeInfoList() {
// 先從上下文SubscribeInfoListContext中獲取List<TenantManagement.Subscription>
List<TenantManagement.Subscription> subscriptions = SubscribeInfoListContext.get();
// 如果上下文SubscribeInfoListContext中獲取不到,再去調(diào)用grpc接口獲取
if (!CollectionUtil.isEmpty(subscriptions)) {
return subscriptions;
}
String tenantId = Objects.requireNonNull(TenantInfoContext.getTenantInfo()).getTenantId();
List<TenantManagement.Subscription> subscribeInfos = tenantManageClient.getSubscribeInfoByIds(tenantId, SUBSCRIPTION_NAME_LIST);
// 獲取到List<TenantManagement.Subscription> 后存入上下文SubscribeInfoListContext中
SubscribeInfoListContext.set(subscribeInfos);
return subscribeInfos;
}
}public interface SubscriptionInfoConstant {
// 工單
String ORDER_SUBSCRIPTION = "XDR_ORDER_MANAGEMENT";
// SOAR
String SOAR_SUBSCRIPTION = "XDR_SOAR_SECURITY_MANAGEMENT";
}2. 判斷是否授權(quán)SOAR和是否有SOAR菜單權(quán)限 GenerateSoarDisableFlagConverter
@NoArgsConstructor
@AllArgsConstructor
public class GenerateSoarDisableFlagConverter implements ResultConverter {
@Getter
@Setter
private Supplier<LicenseInfoService> licenseInfoServiceSupplier;
@Getter
@Setter
private Supplier<SessionNoRelatedService> sessionNoRelatedService;
@Getter
@Setter
private Supplier<SubscribeInfoService> subscribeInfoServiceSupplier;
private static final String SUPER_ADMIN_POLICY = "superAdmin";
private static final String SOAR_QUERY_POLICY = "soarQuery";
private static final String SOAR_SUBSCRIPTION = "XDR_SOAR_SECURITY_MANAGEMENT";
@Nullable
@Override
public Map<?, ?> convert(@NotNull SplTranslateContext<?> splTranslateContext, @Nullable Map<?, ?> map, boolean ifUsingSplReturnEntity) {
LinkedHashMap<Object, Object> result;
if (map == null) {
result = new LinkedHashMap<>();
} else {
result = new LinkedHashMap<>(map);
}
Object value = !isSoarAvailable();
result.put(
"soarDisableFlag",
ifUsingSplReturnEntity ? SplArrayReturnEntity.of(value)
: value
);
return result;
}
private Boolean isSoarAvailable() {
return hasSoarPolicy() && hasSubscription(SOAR_SUBSCRIPTION);
}
public Boolean hasSubscription(String subscriptionName) {
LicenseInfoService licenseInfoService = licenseInfoServiceSupplier.get();
if (licenseInfoService.ifLicenseInfoIsSaas()) {
List<TenantManagement.Subscription> subscribeInfos = subscribeInfoServiceSupplier.get().getSubscribeInfoList();
if (CollectionUtils.isEmpty(subscribeInfos)) {
return false;
}
List<TenantManagement.Subscription> subscriptionList = subscribeInfos.stream()
.filter(subscriptionInfo -> subscriptionInfo.getProductID().equals(subscriptionName) && checkSubscribeStatus(subscriptionInfo.getStatus()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(subscriptionList)) {
return false;
}
return true;
}
Collection<String> moduleCodes = licenseInfoServiceSupplier.get().getActiveModuleCodes();
return moduleCodes.contains(subscriptionName);
}
private boolean checkSubscribeStatus(String status) {
return SubscriptionStatusEnum.getActivateSubscriptionStatus().contains(status);
}
public Boolean hasSoarPolicy() {
SessionContextResponseVo sessionContextNoRelated = sessionNoRelatedService.get().getSessionContextNoRelated();
if (sessionContextNoRelated == null) {
return false;
}
Set<String> policies = sessionContextNoRelated.getPolicies();
if (policies.isEmpty()) {
return false;
}
return policies.contains(SOAR_QUERY_POLICY) || policies.contains(SUPER_ADMIN_POLICY);
}
}3. 判斷是否授權(quán)工單和是否有工單菜單權(quán)限 GenerateSoarDisableFlagConverter
@NoArgsConstructor
@AllArgsConstructor
public class GenerateOrderDisableFlagConverter implements ResultConverter {
@Getter
@Setter
private Supplier<LicenseInfoService> licenseInfoServiceSupplier;
@Getter
@Setter
private Supplier<SessionNoRelatedService> sessionNoRelatedServiceSupplier;
@Getter
@Setter
private Supplier<SubscribeInfoService> subscribeInfoServiceSupplier;
private static final String SUPER_ADMIN_POLICY = "superAdmin";
private static final String ORDER_QUERY_POLICY = "safeOperationQuery";
private static final String ORDER_SUBSCRIPTION = "XDR_ORDER_MANAGEMENT";
@Nullable
@Override
public Map<?, ?> convert(@NotNull SplTranslateContext<?> splTranslateContext, @Nullable Map<?, ?> map, boolean ifUsingSplReturnEntity) {
LinkedHashMap<Object, Object> result;
if (map == null) {
result = new LinkedHashMap<>();
} else {
result = new LinkedHashMap<>(map);
}
Object value = !isOrderAvailable();
result.put(
"orderDisableFlag",
ifUsingSplReturnEntity ? SplArrayReturnEntity.of(value)
: value
);
return result;
}
private Boolean isOrderAvailable() {
return hasOrderPolicy() && hasSubscription(ORDER_SUBSCRIPTION);
}
public Boolean hasSubscription(String subscriptionName) {
LicenseInfoService licenseInfoService = licenseInfoServiceSupplier.get();
if (licenseInfoService.ifLicenseInfoIsSaas()) {
List<TenantManagement.Subscription> subscribeInfos = subscribeInfoServiceSupplier.get().getSubscribeInfoList();
if (CollectionUtils.isEmpty(subscribeInfos)) {
return false;
}
List<TenantManagement.Subscription> subscriptionList = subscribeInfos.stream()
.filter(subscriptionInfo -> subscriptionInfo.getProductID().equals(subscriptionName) && checkSubscribeStatus(subscriptionInfo.getStatus()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(subscriptionList)) {
return false;
}
return true;
}
Collection<String> moduleCodes = licenseInfoService.getActiveModuleCodes();
return moduleCodes.contains(subscriptionName);
}
private boolean checkSubscribeStatus(String status) {
return SubscriptionStatusEnum.getActivateSubscriptionStatus().contains(status);
}
public Boolean hasOrderPolicy() {
SessionContextResponseVo sessionContextNoRelated = sessionNoRelatedServiceSupplier.get().getSessionContextNoRelated();
if (sessionContextNoRelated == null) {
return false;
}
Set<String> policies = sessionContextNoRelated.getPolicies();
if (policies.isEmpty()) {
return false;
}
return policies.contains(ORDER_QUERY_POLICY) || policies.contains(SUPER_ADMIN_POLICY);
}
}4. 查詢安全事件列表接口中添加 ResultConverter
@CustomLog
@Service
public class IncidentSplTableHandlingServiceImpl extends SplTableHandlingServiceImpl implements InitializingBean {
@Getter
@Setter(onMethod_ = @Autowired)
private LicenseInfoService licenseInfoService;
@Getter
@Setter(onMethod_ = @Autowired)
private SessionNoRelatedService sessionNoRelatedService;
@Getter
@Setter(onMethod_ = @Autowired)
private SubscribeInfoService subscribeInfoService;
@Override
public void afterPropertiesSet() throws Exception {
List<ResultConverter> originalResultConverters = this.getResultConverters();
List<ResultConverter> resultConverters = new ArrayList<>(originalResultConverters);
resultConverters.add(
new GenerateOrderDisableFlagConverter(
this::getLicenseInfoService,
this::getSessionNoRelatedService,
this::getSubscribeInfoService
)
);
resultConverters.add(
new GenerateSoarDisableFlagConverter(
this::getLicenseInfoService,
this::getSessionNoRelatedService,
this::getSubscribeInfoService
)
);
this.setResultConverters(resultConverters);
}
// 省略.....
}04. 添加攔截器 IncidentInterceptorConfig
在請求開始和請求結(jié)束時清除上下文SubscribeInfoListContext中的數(shù)據(jù),消除不同租戶間數(shù)據(jù)的影響。
@Data
@Configuration
@CustomLog
public class IncidentInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors( @NotNull InterceptorRegistry registry) {
registry.addInterceptor(
new HandlerInterceptorAdapter() {
// 在請求開始清除上下文SubscribeInfoListContext中的數(shù)據(jù)
@Override
public boolean preHandle(
@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull Object handler
) throws Exception {
SubscribeInfoListContext.remove();
return true;
}
// 在請求結(jié)束時清除上下文SubscribeInfoListContext中的數(shù)據(jù)
@Override
public void afterCompletion(
@NotNull HttpServletRequest request,
@NotNull HttpServletResponse response,
@NotNull Object handler,
@Nullable Exception ex
) throws Exception {
SubscribeInfoListContext.remove();
}
}
);
}
}05. 添加 SaasThreadContextDataHolderSubscription 存儲緩存信息
public interface SaasThreadContextDataHolder {
}@Data
@AllArgsConstructor
public class SaasThreadContextDataHolderSubscription implements SaasThreadContextDataHolder {
@Nullable
private final List<TenantManagement.Subscription> subscriptionList;
}06. 添加 SaasThreadContextHolderSubscriptionInfo 以便后續(xù)擴展和使用
public interface SaasThreadContextHolder<T extends SaasThreadContextDataHolder> {
/**
* 獲取data holder類
*
* @return data holder類
*/
@NotNull
Class<T> getSaasThreadContextDataHolderClass();
/**
* 嘗試加載SaasThreadContextDataHolder
*
* @param holder SaasThreadContextDataHolder
* @return 可以加載則true, 否則false
*/
default boolean tryLoad(@NotNull SaasThreadContextDataHolder holder) {
if (!getSaasThreadContextDataHolderClass().isInstance(holder)) {
return false;
}
// noinspection unchecked
this.load((T) holder);
return true;
}
/**
* 加載SaasThreadContextDataHolder
*
* @param holder SaasThreadContextDataHolder
*/
void load(@NotNull T holder);
/**
* 存檔SaasThreadContextDataHolder
*
* @return SaasThreadContextDataHolder
*/
@NotNull
T save();
/**
* 清理SaasThreadContextDataHolder
*/
void remove();
}@AutoService(SaasThreadContextHolder.class)
public class SaasThreadContextHolderSubscriptionInfo implements SaasThreadContextHolder<SaasThreadContextDataHolderSubscriptionInfo> {
@Override
public @NotNull Class<SaasThreadContextDataHolderSubscriptionInfo> getSaasThreadContextDataHolderClass() {
return SaasThreadContextDataHolderSubscriptionInfo.class;
}
@Override
public void load(@NotNull SaasThreadContextDataHolderSubscriptionInfo holder) {
List<TenantManagement.Subscription> subscriptionInfoList = holder.getSubscriptionInfoList();
if(!CollectionUtils.isEmpty(subscriptionInfoList)){
SubscriptionInfoContext.set(subscriptionInfoList);
}
}
@Override
public @NotNull SaasThreadContextDataHolderSubscriptionInfo save() {
return new SaasThreadContextDataHolderSubscriptionInfo(
SubscriptionInfoContext.get()
);
}
@Override
public void remove() {
SubscriptionInfoContext.remove();
}
}07. 添加 SaasThreadContextUtil 工具類
public class SaasThreadContextUtil {
@NotNull
static List<SaasThreadContextHolder<?>> getSaasThreadContextHolders() {
// IterableUtils.toList 方法將 ServiceLoader.load 返回的 Iterable 轉(zhuǎn)換成了 List
return (List) IterableUtils.toList(
// 加載所有實現(xiàn)了 SaasThreadContextHolder 接口的類,并將它們轉(zhuǎn)換成 List 返回
ServiceLoader.load(SaasThreadContextHolder.class)
);
}
@NotNull
public static List<SaasThreadContextDataHolder> save() {
List<SaasThreadContextHolder<?>> saasThreadContextHolders = getSaasThreadContextHolders();
List<SaasThreadContextDataHolder> saasThreadContextDataHolders = new ArrayList<>(
saasThreadContextHolders.size()
);
for (SaasThreadContextHolder<?> saasThreadContextHolder : saasThreadContextHolders) {
saasThreadContextDataHolders.add(saasThreadContextHolder.save());
}
return saasThreadContextDataHolders;
}
public static void load(
@NotNull List<SaasThreadContextDataHolder> saasThreadContextDataHolders
) {
for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {
for (SaasThreadContextDataHolder saasThreadContextDataHolder : saasThreadContextDataHolders) {
if (saasThreadContextHolder.tryLoad(saasThreadContextDataHolder)) {
break;
}
}
}
}
public static void remove() {
for (SaasThreadContextHolder<?> saasThreadContextHolder : getSaasThreadContextHolders()) {
saasThreadContextHolder.remove();
}
}
private SaasThreadContextUtil() {
}
}08. 使用 SaasThreadContextUtil
@Override
public FusionAlertVo countFusionAlerts(FusionAlertQo fusionAlertQo) {
// 調(diào)用 SaasThreadContextUtil.save()
List<SaasThreadContextDataHolder> threadContextDataHolders = SaasThreadContextUtil.save();
CompletableFuture<Long> fusionAlertCountFuture = CompletableFuture.supplyAsync(() -> {
try {
// 調(diào)用 SaasThreadContextUtil.load(threadContextDataHolders);
SaasThreadContextUtil.load(threadContextDataHolders);
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(SaasEsFactory.getTenantIndex(DatabaseConstants.ALERT));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.trackTotalHits(true);
searchSourceBuilder.query(createBoolQueryBuilder(timeRange));
return getSearchResponseTotalHits(fusionAlertQo, searchRequest);
} finally {
// 調(diào)用 SaasThreadContextUtil.remove();
SaasThreadContextUtil.remove();
}
}, THREAD_POOL_EXECUTOR);
CompletableFuture<Long> multiSourceAssociateAlertCountFuture = CompletableFuture.supplyAsync(() -> {
try {
// 調(diào)用 SaasThreadContextUtil.load(threadContextDataHolders);
SaasThreadContextUtil.load(threadContextDataHolders);
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(SaasEsFactory.getTenantIndex(DatabaseConstants.ALERT));
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchRequest.source(searchSourceBuilder);
searchSourceBuilder.trackTotalHits(true);
BoolQueryBuilder boolQueryBuilder = createBoolQueryBuilder(timeRange);
searchSourceBuilder.query(boolQueryBuilder);
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("fusionAlert", true);
boolQueryBuilder.must(termQueryBuilder);
return getSearchResponseTotalHits(fusionAlertQo, searchRequest);
} finally {
// 調(diào)用 SaasThreadContextUtil.remove();
SaasThreadContextUtil.remove();
}
}, THREAD_POOL_EXECUT
// ...
}到此這篇關(guān)于利用ThreadLocal上下文解決查詢性能問題的文章就介紹到這了,更多相關(guān)ThreadLocal查詢性能內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot項目中出現(xiàn)同名bean異常報錯的解決方法
這篇文章給大家聊聊springboot項目出現(xiàn)同名bean異常報錯如何修復(fù),文中通過代碼示例給大家介紹解決方法非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-01-01
Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式
這篇文章主要介紹了Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
spring Boot打包部署到遠程服務(wù)器的tomcat中
這篇文章主要給大家介紹了關(guān)于spring Boot打包部署到遠程服務(wù)器的tomcat中的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12
mybatis-plus指定字段模糊查詢的實現(xiàn)方法
最近項目中使用springboot+mybatis-plus來實現(xiàn),所以下面這篇文章主要給大家介紹了關(guān)于mybatis-plus實現(xiàn)指定字段模糊查詢的相關(guān)資料,需要的朋友可以參考下2022-04-04
idea如何關(guān)閉頁面顯示的瀏覽器圖標(biāo)
這篇文章主要介紹了idea如何關(guān)閉頁面顯示的瀏覽器圖標(biāo)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07

