如何使用ThreadLocal上下文解決查詢性能問題
問題背景:
安全事件列表中數(shù)據(jù)右鍵操作需要控制工單處置記錄和SOAR處置記錄是否置灰,置灰的邏輯是判斷當(dāng)前用戶是否有相應(yīng)的菜單權(quán)限以及當(dāng)前租戶是否有相應(yīng)的授權(quán)權(quán)限。這個(gè)判斷邏輯在每次刷新安全事件列表時(shí)都需要調(diào)用grpc接口判斷租戶是否授權(quán),為了提高性能,所以禁止每次都去請求調(diào)用grpc接口,有兩種解決方案,一種是使用ThreadLocal上下文,另一種是使用Redis緩存。簡單介紹下如何使用ThreadLocal上下文解決性能問題。
01. 定義實(shí)體 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 接口
進(jìn)行g(shù)rpc接口調(diào)用獲取 List<TenantManagement.Subscription> 信息
public interface SubscribeInfoService { /** * 獲取授權(quán)信息 * * @return 授權(quán)信息 */ List<TenantManagement.Subscription> getSubscribeInfoList(); }
1. 定義 SubscribeInfoServiceImpl 實(shí)現(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é)束時(shí)清除上下文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é)束時(shí)清除上下文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ù)擴(kuò)展和使用
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( // 加載所有實(shí)現(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àng)目中出現(xiàn)同名bean異常報(bào)錯的解決方法
這篇文章給大家聊聊springboot項(xiàng)目出現(xiàn)同名bean異常報(bào)錯如何修復(fù),文中通過代碼示例給大家介紹解決方法非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-01-01Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式
這篇文章主要介紹了Spring如何根據(jù)條件創(chuàng)建bean,@Conditional注解使用方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06spring Boot打包部署到遠(yuǎn)程服務(wù)器的tomcat中
這篇文章主要給大家介紹了關(guān)于spring Boot打包部署到遠(yuǎn)程服務(wù)器的tomcat中的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12mybatis-plus指定字段模糊查詢的實(shí)現(xiàn)方法
最近項(xiàng)目中使用springboot+mybatis-plus來實(shí)現(xiàn),所以下面這篇文章主要給大家介紹了關(guān)于mybatis-plus實(shí)現(xiàn)指定字段模糊查詢的相關(guān)資料,需要的朋友可以參考下2022-04-04淺談java如何實(shí)現(xiàn)Redis的LRU緩存機(jī)制
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著java如何實(shí)現(xiàn)Redis的LRU緩存機(jī)制展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06idea如何關(guān)閉頁面顯示的瀏覽器圖標(biāo)
這篇文章主要介紹了idea如何關(guān)閉頁面顯示的瀏覽器圖標(biāo)問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07