【基础架构】刚果商城(congomall)

news2024/11/28 14:39:40

刚果商城(congomall)

image-20230716181118698

整体架构

img

公共规约组件

congomall-base-spring-boot-starter

META-INF/spring.factories 自动装配

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.opengoofy.congomall.springboot.starter.base.config.ApplicationBaseAutoConfiguration
/**
 * 应用基础自动装配
 *
 * @author chen.ma
 * @github <a href="https://github.com/opengoofy" />
 * @公众号 马丁玩编程,关注回复:资料,领取后端技术专家成长手册
 */
public class ApplicationBaseAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public ApplicationContextHolder congoApplicationContextHolder() {
        return new ApplicationContextHolder();
    }
    
    @Bean
    @ConditionalOnMissingBean
    public ApplicationContentPostProcessor congoApplicationContentPostProcessor() {
        return new ApplicationContentPostProcessor();
    }
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(value = "congomall.fastjson.safa-mode", havingValue = "true")
    public FastJsonSafeMode congoFastJsonSafeMode() {
        return new FastJsonSafeMode();
    }
}

首先,这个类实现了 ApplicationListener<ApplicationReadyEvent> 接口,表示它是一个监听应用启动事件的监听器。

在类的开头,引入了一些必要的包,并对版权进行了声明。

接下来是类的定义和成员变量的声明,其中 @Resource 注解用于注入 ApplicationContext 对象。

代码中的 executeOnlyOnce 是一个标志位,用于确保事件只执行一次。默认情况下,它被设置为 true

onApplicationEvent 方法中,通过加锁的方式保证在多线程环境下只执行一次。在方法内部,首先判断 executeOnlyOnce 是否为 true,如果是,则发布一个 ApplicationInitializingEvent 事件,表示应用正在初始化。然后将 executeOnlyOnce 设置为 false,确保事件只被执行一次。

总的来说,这段代码的作用是在应用启动完成后,通过发布 ApplicationInitializingEvent 事件来触发一些初始化操作。通过 executeOnlyOnce 的设置,确保事件只执行一次,避免重复执行。

请注意,我的解读是基于代码的语法和结构,无法获取代码的具体上下文和业务逻辑。如果你需要更详细的解读或有其他问题,请提供更多的信息。

确保事件只执行一次是为了避免重复执行相同的操作,从而避免可能引发的重复资源消耗或潜在的错误。

在某些情况下,特定的初始化操作只需要在应用启动时执行一次。如果这个事件被多次触发,那么这些初始化操作就会被重复执行,可能导致不必要的资源浪费或产生意外的行为。

通过设置一个标志位 executeOnlyOnce,可以在多线程环境下保证事件只执行一次。当事件第一次被触发时,将标志位设置为 false,后续的事件触发时会检查这个标志位,并不再执行相应的操作。

这种设计模式通常被称为 “单次执行” 或 “只执行一次” 模式,它常用于需要确保某些代码只被执行一次的场景,如初始化操作、注册回调函数等。

总而言之,确保事件只执行一次可以避免重复执行操作,提高代码执行效率,并减少可能出现的问题和错误。

确保事件只执行一次的场景有很多,以下是其中几个常见的例子:

  1. 应用初始化:某些应用启动后需要进行初始化操作,如加载配置、创建数据库连接等。这些初始化操作只需在应用启动时执行一次。
  2. 资源加载:在应用启动时加载一些资源,如读取配置文件、加载字典数据等。这些操作可以通过确保只执行一次来避免资源的重复加载和浪费。
  3. 缓存预热:在应用启动时,将某些数据加载到缓存中,以提高后续请求的响应速度。这个操作通常只需要在应用启动时执行一次,避免重复加载和缓存数据不一致的问题。
  4. 注册回调函数:有时需要在特定事件发生时触发回调函数,但只需在第一次事件发生时注册回调函数即可。后续的事件发生时,可以通过确保只执行一次来避免重复触发回调。
  5. 数据库迁移或升级:在应用升级或迁移时,可能需要执行一些数据库变更操作。通过确保只执行一次,可以避免重复执行数据库变更脚本并保持数据的一致性。

这些场景都需要确保事件只执行一次,避免重复操作和可能出现的问题。通过设置标志位或其他方式,可以在多线程环境下实现事件只执行一次的效果。

FastJson安全模式,开启后关闭类型隐式传递

/**
 * FastJson安全模式,开启后关闭类型隐式传递
 *
 * @author chen.ma
 * @github <a href="https://github.com/opengoofy" />
 * @公众号 马丁玩编程,关注回复:资料,领取后端技术专家成长手册
 */
public class FastJsonSafeMode implements InitializingBean {
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.setProperty("fastjson2.parser.safeMode", "true");
    }
}

方便非spring类获取bean

public class ApplicationContextHolder implements ApplicationContextAware {
    
    private static ApplicationContext CONTEXT;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextHolder.CONTEXT = applicationContext;
    }
    
    /**
     * Get ioc container bean by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return CONTEXT.getBean(clazz);
    }
    
    /**
     * Get ioc container bean by name and type.
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return CONTEXT.getBean(name, clazz);
    }
    
    /**
     * Get a set of ioc container beans by type.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
        return CONTEXT.getBeansOfType(clazz);
    }
    
    /**
     * Find whether the bean has annotations.
     *
     * @param beanName
     * @param annotationType
     * @param <A>
     * @return
     */
    public static <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType) {
        return CONTEXT.findAnnotationOnBean(beanName, annotationType);
    }
    
    /**
     * Get ApplicationContext.
     *
     * @return
     */
    public static ApplicationContext getInstance() {
        return CONTEXT;
    }
}

congomall-common-spring-boot-starter

标识枚举

public enum FlagEnum {
    
    /**
     * 正常状态
     */
    FALSE(0),
    
    /**
     * 删除状态
     */
    TRUE(1);
    
    private final Integer flag;
    
    FlagEnum(Integer flag) {
        this.flag = flag;
    }
    
    public Integer code() {
        return this.flag;
    }
    
    public String strCode() {
        return String.valueOf(this.flag);
    }
    
    @Override
    public String toString() {
        return strCode();
    }
}

使用:

productCommentDO.setCommentFlag(FlagEnum.FALSE.code());

封装spring环境工具类

import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.Collection;
import java.util.Map;

/**
 * Assert.
 */
public class Assert {
    
    public static void isTrue(boolean expression, String message) {
        if (!expression) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void isTrue(boolean expression) {
        isTrue(expression, "[Assertion failed] - this expression must be true");
    }
    
    public static void isNull(Object object, String message) {
        if (object != null) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void isNull(Object object) {
        isNull(object, "[Assertion failed] - the object argument must be null");
    }
    
    public static void notNull(Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void notNull(Object object) {
        notNull(object, "[Assertion failed] - this argument is required; it must not be null");
    }
    
    public static void notEmpty(Collection<?> collection, String message) {
        if (CollectionUtils.isEmpty(collection)) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void notEmpty(Collection<?> collection) {
        notEmpty(collection,
                "[Assertion failed] - this collection must not be empty: it must contain at least 1 element");
    }
    
    public static void notEmpty(Map<?, ?> map, String message) {
        if (CollectionUtils.isEmpty(map)) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void notEmpty(Map<?, ?> map) {
        notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
    }
    
    public static void notEmpty(String str, String message) {
        if (StringUtils.isEmpty(str)) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void notEmpty(String str) {
        if (StringUtils.isEmpty(str)) {
            notEmpty(str, "[Assertion failed] - this string must not be empty");
        }
    }
    
    public static void notBlank(String str, String message) {
        if (org.apache.commons.lang3.StringUtils.isBlank(str)) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void notBlank(String str) {
        notBlank(str, "[Assertion failed] - this string must not be blank");
    }
    
    public static void hasText(String text, String message) {
        if (!StringUtils.hasText(text)) {
            throw new IllegalArgumentException(message);
        }
    }
    
    public static void hasText(String text) {
        hasText(text,
                "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");
    }
}

当前环境判断

/**
 * 环境工具类
 *
 * @author chen.ma
 * @github <a href="https://github.com/opengoofy" />
 * @公众号 马丁玩编程,关注回复:资料,领取后端技术专家成长手册
 */
public class EnvironmentUtil {
    
    private static List<String> ENVIRONMENT_LIST = new ArrayList<>();
    
    static {
        ENVIRONMENT_LIST.add("dev");
        ENVIRONMENT_LIST.add("test");
    }
    
    /**
     * 判断当前是否为正式环境
     *
     * @return
     */
    public static boolean isProdEnvironment() {
        ConfigurableEnvironment configurableEnvironment = ApplicationContextHolder.getBean(ConfigurableEnvironment.class);
        String propertyActive = configurableEnvironment.getProperty("spring.profiles.active", "dev");
        return !ENVIRONMENT_LIST.stream().filter(each -> propertyActive.contains(each)).findFirst().isPresent();
    }
}

应用:验证码开关

    public void checkoutValidCode(String verifyCode) {
        if (EnvironmentUtil.isProdEnvironment()) {
            if (StrUtil.isBlank(verifyCode)) {
                throw new ClientException("验证码已失效");
            }
            verifyCode = StrUtil.trim(verifyCode);
            this.verifyCode = StrUtil.trim(this.verifyCode);
            if (!StrUtil.equals(verifyCode, this.verifyCode)) {
                throw new ClientException("验证码错误");
            }
        }
    }

sleep方法异常统一捕获

/**
 * 线程池工具类
 *
 * @author chen.ma
 * @github <a href="https://github.com/opengoofy" />
 * @公众号 马丁玩编程,关注回复:资料,领取后端技术专家成长手册
 */
public final class ThreadUtil {
    
    /**
     * 睡眠当前线程指定时间 {@param millis}
     *
     * @param millis 睡眠时间,单位毫秒
     */
    @SneakyThrows(value = InterruptedException.class)
    public static void sleep(long millis) {
        Thread.sleep(millis);
    }
}

@SneakyThrows(value = InterruptedException.class) 是 Lombok 注解之一,它的作用是在方法中自动处理 InterruptedException 异常。

在 Java 中,Thread.sleep() 方法会抛出 InterruptedException 异常,当一个线程在睡眠期间被中断时,即另一个线程调用该线程的 interrupt() 方法时,就会抛出 InterruptedException。

通常情况下,我们需要显式地在方法中捕获并处理 InterruptedException 异常。但是使用 @SneakyThrows 注解可以简化这一过程,使我们不需要在方法中编写 try-catch 语句来处理异常。

具体来说,@SneakyThrows(value = InterruptedException.class) 的作用是在编译时自动生成捕获和重新抛出 InterruptedException 异常的代码块。这样,我们就不需要在方法体中显式捕获 InterruptedException,而是将其交给 @SneakyThrows 注解来自动处理。

这样做的好处是减少了代码的冗余,提高了代码的可读性和简洁性。但需要注意的是,在使用 @SneakyThrows 注解时,方法声明必须包含对应的异常类型,否则编译器会报错。

总结来说,@SneakyThrows(value = InterruptedException.class) 的作用是在方法中自动处理 InterruptedException 异常,简化了代码编写,提高了代码的可读性和简洁性。

构建者模式

/**
 * 线程池 {@link ThreadPoolExecutor} 构建器, 构建者模式
 *
 * @author chen.ma
 * @github <a href="https://github.com/opengoofy" />
 * @公众号 马丁玩编程,关注回复:资料,领取后端技术专家成长手册
 */
public final class ThreadPoolBuilder implements Builder<ThreadPoolExecutor> {
    
    private int corePoolSize = calculateCoreNum();
    
    // 等价1.5倍corepoolsize
    private int maximumPoolSize = corePoolSize + (corePoolSize >> 1);
    
    private long keepAliveTime = 30000L;
    
    private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
    
    private BlockingQueue workQueue = new LinkedBlockingQueue(4096);
    
    private RejectedExecutionHandler rejectedExecutionHandler = new ThreadPoolExecutor.AbortPolicy();
    
    private boolean isDaemon = false;
    
    private String threadNamePrefix;
    
    private ThreadFactory threadFactory;
    
    private Integer calculateCoreNum() {
        int cpuCoreNum = Runtime.getRuntime().availableProcessors();
        return new BigDecimal(cpuCoreNum).divide(new BigDecimal("0.2")).intValue();
    }
    
    public ThreadPoolBuilder threadFactory(ThreadFactory threadFactory) {
        this.threadFactory = threadFactory;
        return this;
    }
    
    public ThreadPoolBuilder corePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
        return this;
    }
    
    public ThreadPoolBuilder maximumPoolSize(int maximumPoolSize) {
        this.maximumPoolSize = maximumPoolSize;
        if (maximumPoolSize < this.corePoolSize) {
            this.corePoolSize = maximumPoolSize;
        }
        return this;
    }
    
    public ThreadPoolBuilder threadFactory(String threadNamePrefix, Boolean isDaemon) {
        this.threadNamePrefix = threadNamePrefix;
        this.isDaemon = isDaemon;
        return this;
    }
    
    public ThreadPoolBuilder keepAliveTime(long keepAliveTime) {
        this.keepAliveTime = keepAliveTime;
        return this;
    }
    
    public ThreadPoolBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
        this.keepAliveTime = keepAliveTime;
        this.timeUnit = timeUnit;
        return this;
    }
    
    public ThreadPoolBuilder rejected(RejectedExecutionHandler rejectedExecutionHandler) {
        this.rejectedExecutionHandler = rejectedExecutionHandler;
        return this;
    }
    
    public ThreadPoolBuilder workQueue(BlockingQueue workQueue) {
        this.workQueue = workQueue;
        return this;
    }
    
    public static ThreadPoolBuilder builder() {
        return new ThreadPoolBuilder();
    }
    
    @Override
    public ThreadPoolExecutor build() {
        if (threadFactory == null) {
            Assert.notEmpty(threadNamePrefix, "The thread name prefix cannot be empty or an empty string.");
            threadFactory = ThreadFactoryBuilder.builder().prefix(threadNamePrefix).daemon(isDaemon).build();
        }
        ThreadPoolExecutor executorService;
        try {
            executorService = new ThreadPoolExecutor(corePoolSize,
                    maximumPoolSize,
                    keepAliveTime,
                    timeUnit,
                    workQueue,
                    threadFactory,
                    rejectedExecutionHandler);
        } catch (IllegalArgumentException ex) {
            throw new IllegalArgumentException("Error creating thread pool parameter.", ex);
        }
        return executorService;
    }
}

最好根据本机CPU设置核心线程池数

    private Integer calculateCoreNum() {
        int cpuCoreNum = Runtime.getRuntime().availableProcessors();
        return new BigDecimal(cpuCoreNum).divide(new BigDecimal("0.2")).intValue();
    }

拒绝策略

image-20230716210556612

AbortPolicy:抛出异常,终止任务

**CallerRunsPolicy:**使用调用线程执行任务

**DiscardPolicy:**直接丢弃

**DiscardPolicy:**丢弃队列最老任务,添加新任务

动态代理模式:增强线程池拒绝策略

    public static RejectedExecutionHandler createProxy(RejectedExecutionHandler rejectedExecutionHandler, AtomicLong rejectedNum) {
        // 动态代理模式: 增强线程池拒绝策略,比如:拒绝任务报警或加入延迟队列重复放入等逻辑
        return (RejectedExecutionHandler) Proxy
                .newProxyInstance(
                        rejectedExecutionHandler.getClass().getClassLoader(),
                        new Class[]{RejectedExecutionHandler.class},
                        new RejectedProxyInvocationHandler(rejectedExecutionHandler, rejectedNum));
    }

这段代码实现了一个动态代理模式,用于增强线程池的拒绝策略。具体来说,它创建了一个代理对象,将原始的拒绝执行处理器(RejectedExecutionHandler)替换为增强后的处理器。

createProxy 方法中,使用了 Java 的动态代理机制。通过调用 Proxy.newProxyInstance 方法,可以创建一个代理对象,该代理对象实现了 RejectedExecutionHandler 接口,并且通过一个自定义的代理处理器(RejectedProxyInvocationHandler)来处理方法调用。

方法的参数包括原始的拒绝执行处理器(rejectedExecutionHandler)和一个原子长整型对象(rejectedNum)。代理处理器会将所有方法调用委托给原始的拒绝执行处理器,并在适当的时候进行增强处理。

通过使用动态代理模式,可以在不修改原始拒绝执行处理器代码的情况下,对其功能进行扩展。比如可以在任务被拒绝时触发报警、将任务加入延迟队列以便稍后再次尝试执行等额外逻辑。这样可以灵活地定制拒绝策略,并增强线程池的容错能力和业务逻辑。

public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(1, 3, 1024, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1));
        ThreadPoolExecutor.AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
        AtomicLong rejectedNum = new AtomicLong();
        RejectedExecutionHandler proxyRejectedExecutionHandler = RejectedProxyUtil.createProxy(abortPolicy, rejectedNum);
        threadPoolExecutor.setRejectedExecutionHandler(proxyRejectedExecutionHandler);
        for (int i = 0; i < 5; i++) {
            try {
                threadPoolExecutor.execute(() -> ThreadUtil.sleep(100000L));
            } catch (Exception ignored) {
                ignored.printStackTrace();
            }
        }
        System.out.println("================ 线程池拒绝策略执行次数: " + rejectedNum.get());
    }

这段代码实现了一个简单的线程池示例,使用了之前提到的动态代理增强了线程池的拒绝策略。下面是代码的执行流程:

  1. 创建一个带有边界和容量的 ThreadPoolExecutor 对象,其中核心线程数为 1,最大线程数为 3,队列容量为 1。
  2. 使用 ThreadPoolExecutor.AbortPolicy 创建一个拒绝策略对象 abortPolicy,它会在任务被拒绝时抛出异常。
  3. 创建一个原子长整型对象 rejectedNum,用于记录被拒绝的任务数量。
  4. 使用之前提到的 RejectedProxyUtil.createProxy 方法创建代理的拒绝执行处理器 proxyRejectedExecutionHandler,将原始的 abortPolicyrejectedNum 作为参数传入。
  5. 将代理的拒绝执行处理器设置给线程池执行器,以替换默认的拒绝策略。
  6. 进行一个循环,共执行 5 次。在每次循环中,通过调用 threadPoolExecutor.execute 方法提交一个新的任务,该任务会暂停执行 100000 毫秒(100 秒)。
  7. 如果任务被拒绝,即超出了线程池的容量和队列容量限制,会进入 catch 块,并打印异常信息。
  8. 最后输出被拒绝的任务数量 rejectedNum.get()

这段代码展示了如何创建线程池、设置拒绝策略,并使用动态代理对拒绝策略进行增强。通过输出被拒绝的任务数量,可以观察线程池拒绝策略执行的次数。

AtomicLong

AtomicLong 是一个原子长整型类,用于在并发场景下进行线程安全的长整型操作。在这段代码中,rejectedNum 创建了一个新的 AtomicLong 对象,用于记录被拒绝的任务数量。

由于线程池的拒绝策略会在任务被拒绝时执行,为了线程安全地更新计数器,使用了 AtomicLong。通过调用 rejectedNum.get() 方法可以获取当前的计数值,而通过调用 rejectedNum.incrementAndGet() 方法可以对计数进行原子性的自增操作。

decrementAndGet

快速消费线程池

/**
 * 快速消费线程池
 *
 * @author chen.ma
 * @github <a href="https://github.com/opengoofy" />
 * @公众号 马丁玩编程,关注回复:资料,领取后端技术专家成长手册
 */
public class EagerThreadPoolExecutor extends ThreadPoolExecutor {
    
    public EagerThreadPoolExecutor(int corePoolSize,
                                   int maximumPoolSize,
                                   long keepAliveTime,
                                   TimeUnit unit,
                                   TaskQueue<Runnable> workQueue,
                                   ThreadFactory threadFactory,
                                   RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }
    
    private final AtomicInteger submittedTaskCount = new AtomicInteger(0);
    
    public int getSubmittedTaskCount() {
        return submittedTaskCount.get();
    }
    
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        submittedTaskCount.decrementAndGet();
    }
    
    @Override
    public void execute(Runnable command) {
        submittedTaskCount.incrementAndGet();
        try {
            super.execute(command);
        } catch (RejectedExecutionException ex) {
            TaskQueue taskQueue = (TaskQueue) super.getQueue();
            try {
                if (!taskQueue.retryOffer(command, 0, TimeUnit.MILLISECONDS)) {
                    submittedTaskCount.decrementAndGet();
                    throw new RejectedExecutionException("Queue capacity is full.", ex);
                }
            } catch (InterruptedException iex) {
                submittedTaskCount.decrementAndGet();
                throw new RejectedExecutionException(iex);
            }
        } catch (Exception ex) {
            submittedTaskCount.decrementAndGet();
            throw ex;
        }
    }
}
/**
 * 快速消费任务队列
 *
 * @author chen.ma
 * @github <a href="https://github.com/opengoofy" />
 * @公众号 马丁玩编程,关注回复:资料,领取后端技术专家成长手册
 */
public class TaskQueue<R extends Runnable> extends LinkedBlockingQueue<Runnable> {
    
    @Setter
    private EagerThreadPoolExecutor executor;
    
    public TaskQueue(int capacity) {
        super(capacity);
    }
    
    @Override
    public boolean offer(Runnable runnable) {
        int currentPoolThreadSize = executor.getPoolSize();
        // 如果有核心线程正在空闲,将任务加入阻塞队列,由核心线程进行处理任务
        if (executor.getSubmittedTaskCount() < currentPoolThreadSize) {
            return super.offer(runnable);
        }
        // 当前线程池线程数量小于最大线程数,返回 False,根据线程池源码,会创建非核心线程
        if (currentPoolThreadSize < executor.getMaximumPoolSize()) {
            return false;
        }
        // 如果当前线程池数量大于最大线程数,任务加入阻塞队列
        return super.offer(runnable);
    }
    
    public boolean retryOffer(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
        if (executor.isShutdown()) {
            throw new RejectedExecutionException("Executor is shutdown!");
        }
        return super.offer(o, timeout, unit);
    }
}

这样写的目的是为了实现一种特定的任务处理策略。在这个策略中,首先会尽可能地将任务分配给空闲的核心线程来处理,以保证快速消费和处理任务。

为什么要优先将任务分配给空闲的核心线程呢?这是因为核心线程是线程池的基础,它们始终存在并且可以立即执行任务,避免了线程创建和销毁的开销。通过利用核心线程处理任务,可以最大限度地利用线程池的资源,提高任务处理的效率。

如果核心线程都已经被占用,那么会进一步判断当前线程池的线程数量是否已达到最大限制。如果还未达到最大限制,则返回 false,让线程池创建一个非核心线程来处理任务。这样可以控制线程池的线程数量,避免无限制地创建线程,从而保护系统资源的稳定性。

如果线程池的线程数量已经达到或超过最大限制,那么新任务将被添加到阻塞队列中等待处理。这样,即使线程池已满,任务也不会被丢弃,而是会在阻塞队列中等待有可用线程来处理。

综上所述,这样的设计可以根据线程池的状态和任务数量,采取不同的策略来处理任务,以实现快速消费和高效利用线程池资源的目标。

congomall-cache-spring-boot-starter

缓存穿透布隆过滤器

    /**
     * 防止缓存穿透的布隆过滤器
     */
    @Bean
    @ConditionalOnProperty(prefix = BloomFilterPenetrateProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
    public RBloomFilter<String> cachePenetrationBloomFilter(RedissonClient redissonClient, BloomFilterPenetrateProperties bloomFilterPenetrateProperties) {
        RBloomFilter<String> cachePenetrationBloomFilter = redissonClient.getBloomFilter(bloomFilterPenetrateProperties.getName());
        cachePenetrationBloomFilter.tryInit(bloomFilterPenetrateProperties.getExpectedInsertions(), bloomFilterPenetrateProperties.getFalseProbability());
        return cachePenetrationBloomFilter;
    }
    @Override
    public void safePut(String key, Object value, long timeout, TimeUnit timeUnit, RBloomFilter<String> bloomFilter) {
        put(key, value, timeout, timeUnit);
        bloomFilter.add(key);
    }

get

    @Override
    public <T> T safeGet(String key, Class<T> clazz, CacheLoader<T> cacheLoader, long timeout, TimeUnit timeUnit,
                         RBloomFilter<String> bloomFilter, CacheGetFilter<String> cacheGetFilter, CacheGetIfAbsent<String> cacheGetIfAbsent) {
        T result = get(key, clazz);
        // 缓存结果不等于空或空字符串直接返回;通过函数判断是否返回空,为了适配布隆过滤器无法删除的场景;两者都不成立,判断布隆过滤器是否存在,不存在返回空
        if (!CacheUtil.isNullOrBlank(result)
                || Optional.ofNullable(cacheGetFilter).map(each -> each.filter(key)).orElse(false)
                || Optional.ofNullable(bloomFilter).map(each -> !each.contains(key)).orElse(false)) {
            return result;
        }
        RLock lock = redissonClient.getLock(SAFE_GET_DISTRIBUTED_LOCK_KEY_PREFIX + key);
        lock.lock();
        try {
            // 双重判定锁,减轻获得分布式锁后线程访问数据库压力
            if (CacheUtil.isNullOrBlank(result = get(key, clazz))) {
                // 如果访问 cacheLoader 加载数据为空,执行后置函数操作
                if (CacheUtil.isNullOrBlank(result = loadAndSet(key, cacheLoader, timeout, timeUnit, true, bloomFilter))) {
                    Optional.ofNullable(cacheGetIfAbsent).ifPresent(each -> each.execute(key));
                }
            }
        } finally {
            lock.unlock();
        }
        return result;
    }

缓存穿透、缓存击穿和缓存雪崩是常见的与缓存相关的问题和现象,它们可以对系统性能和可用性造成影响。

  1. 缓存穿透(Cache Penetration): 缓存穿透指的是在缓存中无法命中所需数据,并且每次请求都会导致数据库查询。这通常发生在恶意攻击或者非法用户请求不存在的数据时。由于缓存中不存在该数据,每次请求都直接访问数据库,从而导致数据库负载过高,甚至可能引起系统崩溃。

    解决方案:可以通过在缓存中预先设置一个“空值”来避免缓存穿透。当查询到结果为空时,将空值存入缓存,并设置较短的过期时间,以防止攻击者频繁请求。

  2. 缓存击穿(Cache Breakdown): 缓存击穿指的是某个热点数据过期或被移除,而此时正好有大量并发请求同时访问该数据,导致请求直接访问数据库,增加了数据库负载。相比于缓存穿透,缓存击穿通常发生在存在合法数据的情况下。

    解决方案:一种解决方案是使用互斥锁(例如分布式锁)来保护缓存数据的访问,只允许一个线程去重新加载缓存,并在加载期间屏蔽其他请求对该缓存的访问。另一种方式是使用缓存预热,提前主动加载热点数据到缓存中,避免在关键时刻过期。

  3. 缓存雪崩(Cache Avalanche): 缓存雪崩指的是在某个时间段内,大量缓存数据同时失效或过期,导致大量请求直接访问数据库,造成数据库负载剧增。通常这是由于缓存服务器宕机、网络故障或者大规模缓存数据同时失效等情况导致的。

    解决方案:为了避免缓存雪崩,可以采用以下几种策略:

    • 设置不同的缓存过期时间,避免同时大量缓存失效。
    • 使用分布式缓存,将缓存数据部署在多个节点上,提高缓存的可用性和容错能力。
    • 实施限流和熔断机制,控制缓存失效时的请求量,避免对数据库造成过大压力。

以上是对缓存穿透、缓存击穿和缓存雪崩的简要介绍及解决方案。在实际开发中,可根据具体情况选择适合的缓存策略和技术来应对这些问题。

静态代理模式

    @Bean
    // 静态代理模式: Redis 客户端代理类增强
    public StringRedisTemplateProxy stringRedisTemplateProxy(RedisKeySerializer redisKeySerializer,
                                                             StringRedisTemplate stringRedisTemplate,
                                                             RedissonClient redissonClient) {
        stringRedisTemplate.setKeySerializer(redisKeySerializer);
        return new StringRedisTemplateProxy(stringRedisTemplate, redisDistributedProperties, redissonClient);
    }

静态代理模式是一种设计模式,它通过创建一个代理对象来间接访问原始对象,并在代理对象中提供额外的功能或控制访问。在静态代理模式中,代理类和原始类是在编译时就确定的,并且代理类和原始类实现相同的接口或继承相同的父类。

静态代理模式通常由以下几个角色组成:

  1. 抽象角色(接口或抽象类):定义了代理类和原始类之间的公共方法,是代理类和原始类的共同接口。
  2. 原始角色:实际执行业务逻辑的类,也称为被代理类或目标类。
  3. 代理角色:代理类,持有对原始类的引用,并在其方法中调用原始类的方法,在调用前后可以执行额外的操作。
  4. 客户端:使用代理对象来访问原始对象的类。

静态代理模式的主要优点是可以在不修改原始类的情况下,通过代理类来增强原始类的功能,可以实现对原始类的访问控制、增加日志记录、性能监控等功能。缺点是当原始类的接口发生变化时,代理类也需要相应修改。

单例模式

// 饿汉式
public class singleton{
    private static finnal singleton instance = new singleton();
    
    private singleton(){
        
    }
    
    public static singleton get(){
        return instance;
    }
}
// 懒汉式
public class singleton{
    private static singleton instance;
    
    private singleton(){
        
    }
    
    public static singleton get(){
        if(instance == null){
            instance = = new singleton();
        }
        return instance;
    }
    
    // 双重判定锁
     public static singleton get(){
        if(instance == null){
           synchronized(singleton.class){
            if(instance == null){
                instance = = new singleton();
            }   
           }
        }
        return instance;
    }
}
// 静态内部类
public class singleton{
    
    private singleton(){
        
    }
    
    private static class s{
        private static finnal singleton instance = new singleton(); 
    }
    
    public static singleton get(){
      
        return s.instance;
    }
}
// 枚举类
public enum Singleton {
    INSTANCE;

    // 添加其他方法和属性

    public void doSomething() {
        // 单例对象的操作
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/763833.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Webpack原理与实战 --- Webpack 核心特性

如何使用 Webpack 实现模块化打包&#xff1f; 对模块化打包方案或工具的设想或者说是诉求&#xff1a; 能够将散落的模块打包到一起&#xff1b;能够编译代码中的新特性&#xff1b;能够支持不同种类的前端资源模块。 其中最为主流的就是 Webpack、Parcel 和 Rollup 以 We…

Lottie源代码解析

Lottie-iOS Lottie动画的原理&#xff1a; 一个完整动画View&#xff0c;是由很多个子Layer 组成&#xff0c;而每个子Layer主要通过shapes&#xff08;形状&#xff09;&#xff0c;masks&#xff08;蒙版&#xff09;&#xff0c;transform三大部分进行动画。Lottie框架通过…

jdk9以上反射报错 , jib 镜像打包添加配置

错误信息&#xff1a; unable to make protected final java.lang.class java.lang.classloader.defineclass 在IDEA中添加&#xff1a; --add-opens java.base/java.langALL-UNNAMED 即可启动 如果用了jib-maven-plugin 发布镜像&#xff0c; 怎么配置这个参数进去呢&…

多表查询进阶

首先两表如下所示 两表结构如下 查询要求 1 所有有门派的人员信息 select *from t_emp right join t_dept on t_emp.deptIdt_dept.id; 2 列出所有用户&#xff0c;并显示其机构信息 select t_emp.name,t_dept.id,t_dept.deptName,t_dept.address,t_dept.CEO from t_emp l…

深蓝学院C++基础与深度解析笔记 第 12 章 类进阶

深蓝学院C基础与深度解析笔记 第 12 章 类进阶 1. 运算符重载 ● 使用 operator 关键字引入重载函数&#xff1a; – 重载不能发明新的运算&#xff0c;不能改变运算的优先级与结合性&#xff0c;通常不改变运算含义 – 函数参数个数与运算操作数个数相同&#xff0c;至少一…

C++模拟实现unordered_map和unordered_set(哈希)

目录 一、unordered系列关联式容器 1.1 unordered_map 1.1.1 unordered_map 1.1.2 unordered_map接口说明 1. unordered_map的容量 2. unordered_map的迭代器 3.unordered_map的元素访问 4. unordered_map的查询 5. unordered_map的修改操作 6. unordered_map的桶操作…

ros::catkin_create_pkg

用下面的命令即可 catkin_create_pkg first_pkg rospy roscpp std_msg -m ur-email-name

HBase(一)HBase v2.2 高可用多节点搭建

最近刚刚完成了HBase相关的一个项目,作为项目的技术负责人,完成了大部分的项目部署,特性调研工作,以此系列文章作为上一阶段工作的总结. 前言 其实目前就大多数做应用的情况来讲,我们并不需要去自己搭建一套HBase的集群,现有的很多云厂商提供的服务已经极大的方便日常的应用使…

接口测试工具——Postman使用详解

目录 Postman简介 Postman主界面 菜单栏 工具栏 请求管理区 环境管理区 请求设计区 发送请求 发送GET请求 Postman发送GET请求 发送表单格式POST请求 发送JSON格式POST请求 发送XML格式POST请求 发送文件上传类型的请求 响应 环境和变量 环境变量设置 环境变量…

【Ceph的介绍】

目录 1、存储基础1、单机存储设备2、单机存储的问题3、商业存储解决方案4、分布式存储&#xff08;软件定义的存储 SDS&#xff09;1、分布式存储的类型 2、Ceph 简介3、Ceph 优势4、Ceph 架构5、Ceph 核心组件1、Pool中数据保存方式支持两种类型2、Pool、PG 和 OSD 的关系 6、…

测试用例设计方法-场景法详解

01、定义 场景法是通过运用场景来对系统的功能点或业务流程的描述&#xff0c;从而提高测试效果的一种方法。 场景法一般包含基本流和备用流&#xff0c;从一个流程开始&#xff0c;通过描述经过的路径来确定的过程&#xff0c;经过遍历所有的基本流和备用流来完成整个场景。…

SOPC之NiosⅡ系统(四)

NIOS Ⅱ系统实例&#xff0c;参考自特权同学《勇敢的芯-伴你玩转NIOS Ⅱ》 一些基础操作就不再赘述 目录 1.创建Quartus项目 1.2 进入Platform Designer添加组件并设置 1.2.1 设置时钟频率50MHz&#xff1b; 1.2.2 添加Nios Ⅱ组件 1.2.3 添加RAM组件 1.2.4 设置Nios Ⅱ…

【每日随笔】摩托车安全驾驶 ① ( 摩托车骑行准备 | 买好保险 | 摩托车必要改装 - 护杠 + 行车记录仪 | 骑行护具 )

文章目录 一、摩托车骑行准备1、买好保险2、摩托车必要改装 - 护杠 行车记录仪3、骑行护具 德州考驾照归来 , 提了一辆 铃木 UY125 , 注意安全驾驶 , 以后上班就骑摩托车了 ; 由于居住证上的地址是海淀区 , 目前住在学院路 , 导致无法把车落户到自己名下 , 只能上公户了 ; 车…

G1垃圾收集器-JVM(十三)

上篇文章说了CMS垃圾收集器使用以及三色标记如何解决cms的一些问题。分别有初始标记&#xff0c;并发标记&#xff0c;重新标记&#xff0c;并发清理&#xff0c;并发重置。 CMS垃圾收集器&三色标记-JVM&#xff08;十二&#xff09; G1收集器&#xff08;Garbage-First&a…

浅析缓存一致性的解析方案

各位同学们平时开发的时候除了使用到数据库&#xff08;这里以mysql为例&#xff09;还会用到相关的缓存&#xff08;这里以redis为例&#xff09;操作。 举一个常用的场景当我们写的接口性能相对比较慢的时候&#xff08;高并发场景需要响应速度很快&#xff09;为了保证性能的…

LeetCode144. 二叉树的前序遍历

144. 二叉树的前序遍历 文章目录 [144. 二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/)一、题目二、思路及代码&#xff08;1&#xff09;递归&#xff08;2&#xff09;迭代&#xff08;两种方法&#xff09; 一、题目 给你二叉树的根节点…

AlienSwap 首期 Launchpad — 偶像女团 NFT+RWA 的创新探索

NFT 是整个加密市场一致看好&#xff0c;并认为会继续爆发的领域。随着更多的 NFT 平台和 NFT 项目的推出&#xff0c;NFT 市场的格局也在不断变化。从开始的 OpenSea 占据绝对领先地位&#xff0c;到 Blur 的横空出世风头无两&#xff0c;在加密领域&#xff0c;局势更迭总是在…

【Java面试丨并发编程】线程中并发安全

一、Synchronized关键字的底层原理 1. Synchronized的作用 Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】&#xff0c;其他线程再想获取这个【对象锁】时就会阻塞住 2. Monitor Synchronized【对象锁】底层是由Monitor实现&#xff0c;…

泰裤辣!这是什么操作,自动埋点,还能传参?

目录 前言 参数放在注释中 准备入口文件 编写插件 运行代码 完整代码 参数放在局部作用域中 准备源代码 编写插件 运行代码 完整代码 总结 前言 在上篇文章讲了如何通过手写babel插件自动给函数埋点之后&#xff0c;就有同学问我&#xff0c;自动插入埋点的函数怎么…

基于IMX6ULL的AP3216C的QT动态数据曲线图显示

前言&#xff1a;本文为手把手教学 LinuxQT 的典型基础项目 AP3216C 的数据折线图显示&#xff0c;项目使用正点原子的 IMX6ULL 阿尔法( Cortex-A7 系列)开发板。项目需要实现 AP3216C 在 Linux 系统下的驱动&#xff0c;使用 QT 设计 AP3216C 的数据显示页面作为项目的应用层。…