范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

基于SpringBoot的轻量非侵入式数据库数据告警器

  基于SpringBoot的轻量、非侵入式数据库数据告警器
  「 傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰老下去。在我看来,这是比死亡更可怕的事。--------王小波」  我的需求:
  需要写一个数据库数据监控的告警小工具,要求:  非侵入式的,对监控的数据只有查询权限,没有写权限  可以对数据表的部分数据状态,数据数量进行监控告警  监控数据,告警条件等是可配置的  我需要解决的问题:抽象告警行为,解耦告警流程构建过程  告警命中之后如何避免重复告警  可配置的部分如何从流程代码中解耦为配置  如何动态配置告警扫描计划  我是这样做的:
  整体来讲,逻辑很简单,没啥技术难点,属于重复造轮子,考虑到需要解析配置文件、多数据源配置,定时任务等,所以使用SpringBoot,利用其自动化配置,类型安全配置属性,集成简单的任务调度等优点,可以方便地的配置不同的数据源,同时将复杂配置文件中的数据注入Bean中,动态配置定时计划
  关于多数据源配置和类型安全配置属性等不是本文重点,这里不多讲。  编码思路:一是解耦告警器类的构建和构建步骤  二是解耦告警流程,涉及的单个行为从流程解耦,对于行为可变的部分从代码解耦为配置文件。  三是对于告警缓存的处理,非侵入式需要解决重复告警,当前集成了H2,但是没有使用,感觉有点重,所以利用 WeakHashMap 构建了一个弱键的缓存工具类来实现。 解耦告警器类的构建和构建步骤
  对于告警器类的构建,涉及 初始化 和告警规则生成 两部分,初始化负责告警配置文件加载解析校验,告警规则生成负责告警流程的建立。
  这里可以使用默认的初始化规则,和告警解析规程,也可以使用自定义的规则。整体上编码基于 构建者设计模式 ,类似于Spring Security配置对象 的构建
  可以使用默认的告警解析流程,调用方式  alarms.alarmStart()
  或者  alarms.alarmsInit(null).alarmsRun(null);
  也可以通过自定义告警解析流程,这里采用函数式编程的思想,通过行为参数化的方式,可以动态编写告警解析流程。  // 告警器初始化  alarms.alarmsInit(alarmsInit -> {                 logger.info("告警器扫描时间周期cron:" + alarmsInit.getMinute());                 alarmsInit.getAlarms().forEach((alarm -> {                     logger.info("加载的告警器名称:" + alarm.getItemsName());                     logger.info("触发器:" + alarm.getTrigger());                     logger.info("动作:" + Arrays.toString(alarm.getActions()));                     logger.info("告警媒介:" + Arrays.toString(alarm.getMediaType()));                     logger.info("告警内容:" + alarm.getMedia());                     logger.info("告警短信插表SQL:" + alarm.getMediaSql());                 }));                 return alarmsInit;             // 告警规则生成             }).alarmsRun(alarmsRun -> {                 logger.info("告警器扫描......");                 alarmsRun.getAlarms().forEach(alarm -> {                     Boolean boo = Long.class.cast(jdbcTemplateOne.queryForList(alarm.getTrigger()).get(0).get("isAlarms")) == 1L ? Boolean.TRUE : Boolean.FALSE;                     if (boo) {                         logger.info("告警规则命中......" + alarm.getTrigger());                          Arrays.stream(alarm.getActions()).forEach(sql -> {                             List> list = jdbcTemplateOne.queryForList(sql);                             Object[] codes = list.stream().map((code) -> code.get("code").toString()).toArray();                             Arrays.stream(alarm.getMediaType()).forEach(phone -> {                                  String msg = String.format(alarm.getMedia(), Arrays.toString(codes));                                  logger.info("告警消息生产......》》》》 content:" + msg + " phone:" + phone);                                  Object oldTime = cache.get(msg + phone);                                  if (Objects.isNull(oldTime)) {                                     cache.put(msg + phone, System.currentTimeMillis());                                     jdbcTemplateTow.execute(String.format(alarm.getMediaSql(), msg, phone, phone));                                 } else {                                     if (System.currentTimeMillis() - Long.class.cast(oldTime) > 7200000L) {                                         jdbcTemplateTow.execute(String.format(alarm.getMediaSql(), msg, phone, phone));                                         cache.put(msg + phone, System.currentTimeMillis());                                     } else {                                         logger.info("2小时内重复告警消息....不发送");                                     }                                 }                             });                         });                     }                 });                 return alarmsRun;             }); 解耦告警行为和流程
  关于告警流程,这里结合 zabbix 监控告警的配置方式,抽象出触发器,动作,告警媒介,告警消息模板,插表sql等行为,整个告警流程行为通过配置文件配置,在上面告警器构建中告警规则生成中整合为完整流程。 触发器(trigger):这里的触发器是一个返回0/1布尔值的SQL,当为true时人为告警被触发,会执行动作。  动作(actions[]):动作在这里是一组返回触发告警唯一标识内容的SQL,用于描述告警触发后的行为,返回触发告警的数据标识  告警媒介(mediaType[]): 当前告警通过短信的方式,所以这里是一组电话号码,要给哪些用户发生告警消息  告警消息模板(media):不多讲,结合上面动作获取的告警数据,生成完整告警消息  插表sql(mediaSql): 当前发送短信的方式通过插表的方式,如过通过邮件或则短信发送调API的方式,就需要自定义告警规则
  我们通配置文件看几个具体的场景
  活动监控场景 :适用一些批量处理任务的数据,通过where条件判断是否有不符合预期状态的数据,有则获取这部分数据的唯一标识,生成告警消息发送。
  空表校验场景 : 适用一些账期表,在某些时间会数据落表,通过where条件判断是否存在数据,没有则通过select "XXX 表数据为空" as code  的方式构建告警消息,发生告警讯息
  大表监控场景 : 适用部分大表在数据量达到某个峰值的时候,会影响系统性能、SQL超时甚至部分持久化数据丢失,需要对冗余数据进行备份清理。需要提前告警. alarms:   minute: "*/5 * * * * ?"   alarms:       # 告警策略名称     - itemsName: "活动监控 "       #  SQL return isAlarms is Boolean 触发器       trigger: "select count(*) > 0 as isAlarms from 活动表  a WHERE a.CREATED_DATE >  SUBDATE(NOW(),interval 1 day)  AND a.CREATED_DATE < SUBDATE(NOW(), interval 1 hour)  AND a.STATE ="A" AND a.SP_ID=0"       # SQL return code is String[], 动作       actions: [ "select  ACTIVITY_CODE as code from 活动表  a WHERE a.CREATED_DATE >  SUBDATE(NOW(),interval 1 day)  AND a.CREATED_DATE < SUBDATE(NOW(), interval 1 hour)  AND a.STATE ="A" AND a.SP_ID=0" ]       #  MMS is String[] 告警媒介       mediaType: [ "18147405370","13147405370","12147405370"]       # MMS content 告警消息       media: "%s 活动发生异常,请排查"       #  MMS install SQL 插表sql       mediaSql: "INSERT INTO 短信表 (MT_SEQ,  MT_SERV_TYPE, SEND_PRIORITY,  MSG_CONTENT, DEST_TERMID, SRC_TERMID, FEE_USER_TERMID,  FEE_TYPE,   MAKE_TIME,  REQUIRE_APPLY ) VALUES (sms.ACCT_SENDSMS_SEQ.NEXTVAL,"123",2,"%s", "%s","1183111", "%s", "00",SYSDATE,0)"    - itemsName: "XXX 空表校验"      trigger: "select count(*) = 0 as isAlarms from XXX"      actions: ["select "XXX 表数据为空" as code "]      mediaType: ["123123","3123"]      media: "%s 异常信息,请排查"      mediaSql: "insert into sms(phone,content) values("%s","%s")"    - itemsName: "XXX 大表监控"      trigger: "select count(*) > 40000000 as isAlarms from XXX"      actions: ["select "XXX 表数据量超过峰值" as code "]      mediaType: ["123123","3123"]      media: "%s 异常信息,请排查"      mediaSql: "insert into sms(phone,content) values("%s","%s")"   重复告警的问题
  关于重复告警的问题,集成了H2,但是目前告警数据量小,所以没有使用,对于重复告警使用了WeakHashMap构建了一个弱键的缓存工具类实现。
  第一告警触发后,存到缓存里,之后2小时内触发告警不发送告警消息,2小时候在发送一次    Object oldTime = cache.get(msg + phone);    if (Objects.isNull(oldTime)) {       cache.put(msg + phone, System.currentTimeMillis());       jdbcTemplateTow.execute(String.format(alarm.getMediaSql(), msg, phone,phone));   } else {       if (System.currentTimeMillis() -Long.class.cast(oldTime) > 7200000L){           jdbcTemplateTow.execute(String.format(alarm.getMediaSql(), msg, phone,phone));           cache.put(msg + phone, System.currentTimeMillis());       }else {           logger.info("2小时内重复告警消息....不发送");       }   } 动态定时任务配置
  通过 SchedulingConfigurer 配置类实现动态配置,重配置文件获取cron表达式
  DynamicCronSchedule.java  package com.example.alarms.alert;  import com.example.alarms.alert.dto.Alarms; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component;  import java.util.*; import java.util.logging.Logger;  /**  * @author LiRuilong  * @Classname DynamicCronSchedule  * @Description TODO  * @Date 2022/5/16 11:31  */ @Component @EnableScheduling public class DynamicCronSchedule implements SchedulingConfigurer {     private static Logger logger = Logger.getLogger("com.example.alarms.alert.DynamicCronSchedule");      @Autowired     private Alarms alarms;       @Override     public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {          alarms.alarmsInit(null);          taskRegistrar.addTriggerTask(() -> {              // 自定义逻辑:             //alarms.alarmsRun(alarmsRun -> alarmsRun);              // 默认逻辑:             alarms.alarmsRun(null);          }, (triggerContext) -> {             String cron = alarms.getMinute();              logger.fine("cron expression is " + cron);             logger.fine("trigger list size is " + taskRegistrar.getTriggerTaskList().size());               CronTrigger cronTrigger = new CronTrigger(cron);             Date nextExecTime = cronTrigger.nextExecutionTime(triggerContext);             return nextExecTime;         });       } }   全部代码配置相关--- server:   port: 30036   tomcat:     uri-encoding: utf-8  spring:   datasource:     one:       type: com.alibaba.druid.pool.DruidDataSource       driver-class-name: com.mysql.jdbc.Driver       username:        password:        url:        test-while-idle: true      tow:       driver-class-name: oracle.jdbc.OracleDriver       username:        password:        url:        test-while-idle: true     h2db:       type: com.alibaba.druid.pool.DruidDataSource       driver-class-name: org.h2.Driver       schema: classpath:db/schema.sql       username: sa       password: sa       url: jdbc:h2:mem:alarms     #http://本地端口/h2-console 通过项目来进行访问数据库   h2:     console:       enabled: true   alarms:   minute: "*/5 * * * * ?"   alarms:       # 告警策略名称     - itemsName: "活动监控 "       #  SQL return isAlarms is Boolean 触发器       trigger: "select count(*) > 0 as isAlarms from 活动表  a WHERE a.CREATED_DATE >  SUBDATE(NOW(),interval 1 day)  AND a.CREATED_DATE < SUBDATE(NOW(), interval 1 hour)  AND a.STATE ="A" AND a.SP_ID=0"       # SQL return code is String[], 动作       actions: [ "select  ACTIVITY_CODE as code from 活动表  a WHERE a.CREATED_DATE >  SUBDATE(NOW(),interval 1 day)  AND a.CREATED_DATE < SUBDATE(NOW(), interval 1 hour)  AND a.STATE ="A" AND a.SP_ID=0" ]       #  MMS is String[] 告警媒介       mediaType: [ "18147405370","13147405370","12147405370"]       # MMS content 告警消息       media: "%s 活动发生异常,请排查"       #  MMS install SQL 插表sql       mediaSql: "INSERT INTO 短信表 (MT_SEQ,  MT_SERV_TYPE, SEND_PRIORITY,  MSG_CONTENT, DEST_TERMID, SRC_TERMID, FEE_USER_TERMID,  FEE_TYPE,   MAKE_TIME,  REQUIRE_APPLY ) VALUES (sms.ACCT_SENDSMS_SEQ.NEXTVAL,"123",2,"%s", "%s","1183111", "%s", "00",SYSDATE,0)"    - itemsName: "XXX 空表校验"      trigger: "select count(*) = 0 as isAlarms from XXX"      actions: ["select "XXX 表数据为空" as code "]      mediaType: ["123123","3123"]      media: "%s 异常信息,请排查"      mediaSql: "insert into sms(phone,content) values("%s","%s")"    - itemsName: "XXX 大表监控"      trigger: "select count(*) > 40000000 as isAlarms from XXX"      actions: ["select "XXX 表数据量超过峰值" as code "]      mediaType: ["123123","3123"]      media: "%s 异常信息,请排查"      mediaSql: "insert into sms(phone,content) values("%s","%s")"      package com.example.alarms.alert.config;  import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;  import javax.sql.DataSource;  /**  * @author LiRuilong  * @Classname DataSourceConfig  * @Description TODO  * @Date 2022/5/10 9:59  */  @Configuration public class DataSourceConfig {       @Bean     @ConfigurationProperties("spring.datasource.one")     DataSource dsOne() {         return DruidDataSourceBuilder.create().build();     }      @Bean     @ConfigurationProperties("spring.datasource.tow")     DataSource dsTow() {         return DruidDataSourceBuilder.create().build();     }      @Bean     @ConfigurationProperties("spring.datasource.h2db")     DataSource dsH2db() {         return DruidDataSourceBuilder.create().build();     }  }  package com.example.alarms.alert.config;  import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate;  import javax.sql.DataSource;  /**  * @author LiRuilong  * @Classname JdbcTemplateConfig  * @Description TODO  * @Date 2022/5/10 10:01  */  @Configuration public class JdbcTemplateConfig {       @Bean     JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne") DataSource ds) {         return new JdbcTemplate(ds);     }      @Bean     JdbcTemplate jdbcTemplateTow(@Qualifier("dsTow") DataSource ds) {         return new JdbcTemplate(ds);     }      @Bean     JdbcTemplate jdbcTemplateH2db(@Qualifier("dsH2db") DataSource ds) {         return new JdbcTemplate(ds);     }   }  package com.example.alarms.alert;  import com.example.alarms.alert.dto.Alarms; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component;  import java.util.*; import java.util.logging.Logger;  /**  * @author LiRuilong  * @Classname DynamicCronSchedule  * @Description TODO  * @Date 2022/5/16 11:31  */ @Component @EnableScheduling public class DynamicCronSchedule implements SchedulingConfigurer {     private static Logger logger = Logger.getLogger("com.example.alarms.alert.DynamicCronSchedule");      @Autowired     private Alarms alarms;       @Override     public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {          alarms.alarmsInit(null);          taskRegistrar.addTriggerTask(() -> {              // 自定义逻辑:             //alarms.alarmsRun(alarmsRun -> alarmsRun);              // 默认逻辑:             alarms.alarmsRun(null);          }, (triggerContext) -> {             String cron = alarms.getMinute();              logger.fine("cron expression is " + cron);             logger.fine("trigger list size is " + taskRegistrar.getTriggerTaskList().size());               CronTrigger cronTrigger = new CronTrigger(cron);             Date nextExecTime = cronTrigger.nextExecutionTime(triggerContext);             return nextExecTime;         });       } }   告警bean相关package com.example.alarms.alert.dto;  import java.io.Serializable; import java.util.Arrays;  /**  * @Classname Alarm  * @Description TODO  * @Date 2022/5/13 18:12  * @Created LiRuilong  */ public class Alarm  implements Serializable {       private  String itemsName;      private  String trigger;      private  String[] actions;      private  String[] mediaType;      private  String media;      private  String mediaSql;       public String getItemsName() {         return itemsName;     }      public Alarm setItemsName(String itemsName) {         this.itemsName = itemsName;         return this;     }      public String[] getActions() {         return actions;     }      public Alarm setActions(String[] actions) {         this.actions = actions;         return this;     }      public String getTrigger() {         return trigger;     }      public Alarm setTrigger(String trigger) {         this.trigger = trigger;         return this;     }      public String[] getMediaType() {         return mediaType;     }      public Alarm setMediaType(String[] mediaType) {         this.mediaType = mediaType;         return this;     }      public String getMedia() {         return media;     }      public Alarm setMedia(String media) {         this.media = media;         return this;     }      public String getMediaSql() {         return mediaSql;     }      public Alarm setMediaSql(String mediaSql) {         this.mediaSql = mediaSql;         return this;     }      @Override     public String toString() {         return "Alarm{" +                 "itemsName="" + itemsName + """ +                 ", actions=" + Arrays.toString(actions) +                 ", trigger="" + trigger + """ +                 ", mediaType=" + Arrays.toString(mediaType) +                 ", media="" + media + """ +                 ", mediaSql="" + mediaSql + """ +                 "}";     } } package com.example.alarms.alert.dto;  import com.example.alarms.alert.WeakHashMapCache; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component;  import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Function; import java.util.logging.Logger;  /**  * @author LiRuilong  * @Classname Alarms  * @Description TODO  * @Date 2022/5/13 17:23  */  @Component @ConfigurationProperties(prefix = "alarms") public class Alarms {      Logger logger = Logger.getLogger("com.example.alarms.alert.dto.Alarms");       WeakHashMapCache cache = new WeakHashMapCache.Builder(1000).build();      private String minute;       private List alarms;       @Autowired     @Qualifier("jdbcTemplateOne")     JdbcTemplate jdbcTemplateOne;       @Autowired     @Qualifier("jdbcTemplateTow")     JdbcTemplate jdbcTemplateTow;       public String getMinute() {         return minute;     }      public Alarms setMinute(String minute) {         this.minute = minute;         return this;     }      public List getAlarms() {         return alarms;     }      public Alarms setAlarms(List alarms) {         this.alarms = alarms;         return this;     }      /**      * @param function:      * @return: com.example.alarms.alert.dto.Alarms      * @Description 告警数据加载,默认逻辑      * @author LiRuilong      * @date 2022/5/16  11:12      **/     public Alarms alarmsInit(Function function) {          if (Objects.nonNull(function)) {             return function.apply(this);         } else {             logger.info("告警器扫描时间周期cron:" + this.getMinute());             alarms.forEach((alarm -> {                 logger.info("加载的告警器名称:" + alarm.getItemsName());                 logger.info("触发器:" + alarm.getTrigger());                 logger.info("动作:" + Arrays.toString(alarm.getActions()));                 logger.info("告警媒介:" + Arrays.toString(alarm.getMediaType()));                 logger.info("告警内容:" + alarm.getMedia());                 logger.info("告警短信插表SQL:" + alarm.getMediaSql());             }));             return this;         }     }      /**      * @param function:      * @return: com.example.alarms.alert.dto.Alarms      * @Description 告警器执行,默认逻辑      * @author LiRuilong      * @date 2022/5/16  11:30      **/     public Alarms alarmsRun(Function function) {          if (Objects.nonNull(function)) {             return function.apply(this);         } else {             logger.info("告警器扫描......");             alarms.forEach(alarm -> {                 Boolean boo = Long.class.cast(jdbcTemplateOne.queryForList(alarm.getTrigger()).get(0).get("isAlarms")) == 1L ? Boolean.TRUE : Boolean.FALSE;                 if (boo) {                     logger.info("告警规则命中......" + alarm.getTrigger());                      Arrays.stream(alarm.getActions()).forEach(sql -> {                         List> list = jdbcTemplateOne.queryForList(sql);                         Object[] codes = list.stream().map((code) -> code.get("code").toString()).toArray();                         Arrays.stream(alarm.getMediaType()).forEach(phone -> {                              String msg = String.format(alarm.getMedia(), Arrays.toString(codes));                              logger.info("告警消息生产......》》》》 content:" + msg + " phone:" + phone);                              Object oldTime = cache.get(msg + phone);                              if (Objects.isNull(oldTime)) {                                 cache.put(msg + phone, System.currentTimeMillis());                                 jdbcTemplateTow.execute(String.format(alarm.getMediaSql(), msg, phone,phone));                             } else {                                 if (System.currentTimeMillis() -Long.class.cast(oldTime) > 7200000L){                                     jdbcTemplateTow.execute(String.format(alarm.getMediaSql(), msg, phone,phone));                                     cache.put(msg + phone, System.currentTimeMillis());                                 }else {                                     logger.info("2小时内重复告警消息....不发送");                                 }                             }                          });                      });                 }              });             return this;         }     }       public void alarmStart() {         alarmsInit(null).alarmsRun(null);     }      public static void main(String[] args) {      }  }  动态定时任务配置package com.example.alarms.alert;  import com.example.alarms.alert.dto.Alarms; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component;  import java.util.*; import java.util.logging.Logger;  /**  * @author LiRuilong  * @Classname DynamicCronSchedule  * @Description TODO  * @Date 2022/5/16 11:31  */ @Component @EnableScheduling public class DynamicCronSchedule implements SchedulingConfigurer {     private static Logger logger = Logger.getLogger("com.example.alarms.alert.DynamicCronSchedule");      @Autowired     private Alarms alarms;       @Override     public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {          alarms.alarmsInit(null);          taskRegistrar.addTriggerTask(() -> {              // 自定义逻辑:             //alarms.alarmsRun(alarmsRun -> alarmsRun);              // 默认逻辑:             alarms.alarmsRun(null);          }, (triggerContext) -> {             String cron = alarms.getMinute();              logger.fine("cron expression is " + cron);             logger.fine("trigger list size is " + taskRegistrar.getTriggerTaskList().size());               CronTrigger cronTrigger = new CronTrigger(cron);             Date nextExecTime = cronTrigger.nextExecutionTime(triggerContext);             return nextExecTime;         });       } }   启动类package com.example.alarms;  import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling;   /**  * @Description  * @author LiRuilong  * @date  2022/5/13  17:22  **/ @EnableScheduling @SpringBootApplication public class AlarmsApplication {       public static void main(String[] args) {         SpringApplication.run(AlarmsApplication.class, args);      }  }  弱键缓存package com.example.alarms.alert;  import java.util.Map; import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap;  /**  * @Classname ConcurrentCache  * @Description TODO 一个基于WeakHashMap本地缓存(LRU)类  * @Date 2022/5/16 17:18  * @author  LiRuilong  */  public class WeakHashMapCache {     private final int size;      private final Map eden;      private final Map longterm;      private WeakHashMapCache(Builder builder){         this.size = builder.size;         this.eden = builder.eden;         this.longterm = builder.longterm;     }      public  static class Builder{         private volatile int size;          private volatile  Map eden;          private volatile Map longterm;          public  Builder(int size){             this.size = rangeCheck(size,Integer.MAX_VALUE,"缓存容器初始化容量异常");             this.eden = new ConcurrentHashMap<>(size);             this.longterm = new WeakHashMap<>(size);         }          private static int rangeCheck(int val, int i, String arg) {             if (val < 0 || val > i) {                 throw new IllegalArgumentException(arg + ":" + val);             }             return  val;         }         public WeakHashMapCache build(){             return new WeakHashMapCache(this);         }      }      public V get(K k){         V v = this.eden.get(k);         if (Objects.isNull(v)){             v = this.longterm.get(k);             if (Objects.nonNull(v)){                 this.eden.put(k,v);             }         }         return v;     }      public void put(K k,V v){         if (this.eden.size() >= size){             this.longterm.putAll(this.eden);             this.eden.clear();         }         this.eden.put(k,v);     }      public static void main(String[] args) {         WeakHashMapCache cache = new WeakHashMapCache.Builder(4).build();          for (int i = 0; i < 5; i++) {             cache.put(i+"",i+"");         }         System.gc();         for (int i = 0; i < 5; i++) {             System.out.println(cache.get(i + ""));         }     }    }

雷军放阿童木真芯英雄短片RedmiNote11T系列真牛手机中国新闻5月25日,雷军在微博上放出了阿童木首支真芯英雄视频短片。24日晚Redmi发布的Note11T系列手机与阿童木联动,这则短片展示了被称为小金刚的Note系列手机与铁壁华为GTRunner长测,跑者想知道的都在这里了去年GTRunner刚发布的时候,笔者还戴着GT2Pro,抱着对GT3Pro的幻想,笔者忍耐了半年,没想到3pro发布后很是失望,遂入手GTRunner。经过半个多月的使用,笔者对套走董明珠26亿,拉王健林刘强东下水,银隆新能源到底有何高人骗走格力女强人董明珠26亿元,逃往美国逍遥法外,这种一般人想都不敢想的事,已经被骗子企业家魏银仓成功做到了。1。魏银仓与银隆新能源故事的主人公名叫魏银仓,最初只是一个汽修厂的小老板教育公司开课吧超8700万元存款已被冻结,回应裁员30左右,拖欠工资会尽快补上5月25日,据山东商报援引新浪科技报道,开课吧在经过一年的激进扩张后,陷入了困难。裁员的同时,员工已被欠薪23个月,返现课程学员也被拖欠返现。开课吧回应称,裁员规模控制在30左右,巴西的编程语言都占领全世界了,中国怎么就不行呢?如果看一看主流的编程语言,都是由发达国家的程序员创造的。CCCJavaJavaScriptGoRustPHP诞生于美国Python诞生于欧洲。但是Lua是个例外,它出生在遥远南半球基于SpringBoot的轻量非侵入式数据库数据告警器基于SpringBoot的轻量非侵入式数据库数据告警器傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰OPPOReno8真机亮相,Reno7价比百元机刷新感人纪录在介绍了OPPOReno8系列的形象配置后,该机外观设计终于出现在了用户面前。机身采用流云双镜面设计,航空铝材直边金属中框,背板上精准打造75度弧面,手感极佳。值得一提的是,由于O打印出来的照片才更有温度!汉印照片打印机CP4000L体验自从有了智能手机,拍照留念变得简单快捷了许多但也正是这种简单,让照片本身少了很多回不去的体验。手机相册里的照片越来越多,翻阅起来也越来越繁琐,有时候开始怀念那些一边翻阅照片一边回忆618买什么键盘好?这些有个性的键盘值得一看大家好,这里是波导终结者。618又快到了,很多人都攥着票子,想更新外设。特别是现在的键盘,还是有不少很有个性的产品出现的,今天我们就来看一下哪些有个性的机械键盘值得选购吧。杜伽K3小米发布RedmiNote11TPro系列,1699元起5月24日,小米正式发布RedmiNote11TProRedmiNote11TPro两款新机,两款新机均搭载天玑8100处理器,首销特惠价1699元起。RedmiNote11TPr买对不买贵,目前没有短板的3款手机,最值得购买,覆盖低中高端您在阅读前请点击上面的关注二字,后续会第一时间为您提供更多有价值的相关内容,感谢您的支持。很多人在购买手机的时候,担心自己购买的手机不够使用的,多花了钱,而没有买到自己满意的机型,
雷军放阿童木真芯英雄短片RedmiNote11T系列真牛手机中国新闻5月25日,雷军在微博上放出了阿童木首支真芯英雄视频短片。24日晚Redmi发布的Note11T系列手机与阿童木联动,这则短片展示了被称为小金刚的Note系列手机与铁壁华为GTRunner长测,跑者想知道的都在这里了去年GTRunner刚发布的时候,笔者还戴着GT2Pro,抱着对GT3Pro的幻想,笔者忍耐了半年,没想到3pro发布后很是失望,遂入手GTRunner。经过半个多月的使用,笔者对套走董明珠26亿,拉王健林刘强东下水,银隆新能源到底有何高人骗走格力女强人董明珠26亿元,逃往美国逍遥法外,这种一般人想都不敢想的事,已经被骗子企业家魏银仓成功做到了。1。魏银仓与银隆新能源故事的主人公名叫魏银仓,最初只是一个汽修厂的小老板教育公司开课吧超8700万元存款已被冻结,回应裁员30左右,拖欠工资会尽快补上5月25日,据山东商报援引新浪科技报道,开课吧在经过一年的激进扩张后,陷入了困难。裁员的同时,员工已被欠薪23个月,返现课程学员也被拖欠返现。开课吧回应称,裁员规模控制在30左右,巴西的编程语言都占领全世界了,中国怎么就不行呢?如果看一看主流的编程语言,都是由发达国家的程序员创造的。CCCJavaJavaScriptGoRustPHP诞生于美国Python诞生于欧洲。但是Lua是个例外,它出生在遥远南半球基于SpringBoot的轻量非侵入式数据库数据告警器基于SpringBoot的轻量非侵入式数据库数据告警器傍晚时分,你坐在屋檐下,看着天慢慢地黑下去,心里寂寞而凄凉,感到自己的生命被剥夺了。当时我是个年轻人,但我害怕这样生活下去,衰OPPOReno8真机亮相,Reno7价比百元机刷新感人纪录在介绍了OPPOReno8系列的形象配置后,该机外观设计终于出现在了用户面前。机身采用流云双镜面设计,航空铝材直边金属中框,背板上精准打造75度弧面,手感极佳。值得一提的是,由于O打印出来的照片才更有温度!汉印照片打印机CP4000L体验自从有了智能手机,拍照留念变得简单快捷了许多但也正是这种简单,让照片本身少了很多回不去的体验。手机相册里的照片越来越多,翻阅起来也越来越繁琐,有时候开始怀念那些一边翻阅照片一边回忆618买什么键盘好?这些有个性的键盘值得一看大家好,这里是波导终结者。618又快到了,很多人都攥着票子,想更新外设。特别是现在的键盘,还是有不少很有个性的产品出现的,今天我们就来看一下哪些有个性的机械键盘值得选购吧。杜伽K3小米发布RedmiNote11TPro系列,1699元起5月24日,小米正式发布RedmiNote11TProRedmiNote11TPro两款新机,两款新机均搭载天玑8100处理器,首销特惠价1699元起。RedmiNote11TPr买对不买贵,目前没有短板的3款手机,最值得购买,覆盖低中高端您在阅读前请点击上面的关注二字,后续会第一时间为您提供更多有价值的相关内容,感谢您的支持。很多人在购买手机的时候,担心自己购买的手机不够使用的,多花了钱,而没有买到自己满意的机型,