高并发三大法宝之 缓存,消息队列,异步任务

发布时间 2023-08-01 00:02:23作者: 你就学个JVAV?

1.缓存(常用redis)

将热点数据或者经常需要进行read的数据放到redis或者其他缓存中,可以极大的降低数据库的压力,遇到流量高峰时,不至于一下子就把数据库压垮了,使用springcache配合redis继续使用,也可以很方便的对数据进行缓存。

springcache 几个常用注解

 @Cacheable(value = "groupName",key = "keyName")  // 对数据进行缓存
 @CacheEvict(value = "groupName",allEntries = true or false) // 对缓存数据进行删除

springcache使用时需要配置缓存类型

 spring.cache.type=redis

config

 @EnableConfigurationProperties(CacheProperties.class)
 @EnableCaching
 @Configuration
 public class MyCacheConfiguration {
     @Bean
     public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
         RedisCacheConfiguration config =  RedisCacheConfiguration.defaultCacheConfig();
 ​
         // 设置序列化器
         config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
         config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
 ​
         CacheProperties.Redis redisProperties =cacheProperties.getRedis();
         // 设置ttl
         if(redisProperties.getTimeToLive() != null){
             config = config = config.entryTtl(redisProperties.getTimeToLive());
         }
 ​
         if(redisProperties.getKeyPrefix() != null){
             config = config.prefixKeysWith(redisProperties.getKeyPrefix());
         }
         if(!redisProperties.isCacheNullValues()){
             config = config.disableCachingNullValues();
         }
         if(!redisProperties.isUseKeyPrefix()){
             config = config.disableKeyPrefix();
         }
         return config;
     }
 }

 

 

2.异步

使用线程池搭配异步编排可以对业务进行高效的分段处理

普通线程池配置

 @EnableConfigurationProperties(value = ThreadPoolConfigProperties.class) // 开启属性配置
 @Configuration
 public class MyThreadConfig {
 ​
     @Bean
     public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties poolConfigProperties) {
         return new ThreadPoolExecutor(
                 poolConfigProperties.getCoreSize(),
                 poolConfigProperties.getMaxSize(),
                 poolConfigProperties.getKeepAliveTime(),
                 TimeUnit.SECONDS,
                 new LinkedBlockingQueue<>(1000),
                 Executors.defaultThreadFactory(),
                 new ThreadPoolExecutor.AbortPolicy());
     }
 }
 ​

将参数动态的配置到yaml文件中

 @ConfigurationProperties(prefix = "mall.thread")
 @Component
 @Data
 @Primary
 public class ThreadPoolConfigProperties {
     private Integer coreSize;
     private Integer maxSize;
     private Integer keepAliveTime;
 }
 ​
 gulimall:
   thread:
     core-size: 20
     max-size: 200
     keep-alive-time: 10

 

将任务分多个组进行异步执行将提升代码效率,以下为示例效果

         @Resource
         ThreadPoolExecutor excutor;
         
         Result result =  new Result();
         CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {
             // ...... 业务代码 逻辑1
         }, excutor);
 ​
         CompletableFuture<Void> task2 = task1.thenRunAsync(()->{
             // ...... 业务代码 逻辑2
         },excutor);
         
         CompletableFuture<Void> task3 = task2.thenRunAsync(() -> {
             // ...... 业务代码 逻辑3
         }, excutor);
         
         try {
             // 等待所有的任务执行完成一同返回
             CompletableFuture.allOf(task1, task2,task3).get();
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
         // this is your result return
         return result;

 

 

3.消息队列

将大流量放到消息队列中,主流的像kafka,rabbitmq,activemq等,这里使用rabbitmq

例如秒杀活动,到了秒杀开始时间秒杀活动上线,用户点击立即抢购,可能会导致大量的请求涌入服务器,若是全都对数据库进行查询,那么后果是不堪设想的,例如某商品限时抢购100件,我们可以将大量的请求先放入到rabbitmq中,然后服务器监听该队列中的信息,按顺序处理,此时将有很好的效果,若是我们使用限时抢购的商品数量作为一个信号量,只有获取到信号量的用户才能真正的去数据库创建订单,而获取不到的我们则直接返回失败信息,此时又进一步缓解了高并发带来的困难。