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

基于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 + ""));         }     }    }

航天爱好者的福音!华硕a豆发布两款航天联名新品,这科技感绝了对于每一个航天爱好者来说,能够见证这些年中国航天事业的蓬勃发展,无疑是令人振奋和激动的。特别是这两年,中国航天事业迎来重大突破,例如向月球成功发射了嫦娥五号天问1号火星探测器和祝融综合实力秒杀特斯拉Model3,小鹏P5正式上市,15。79万元起小鹏P5是旗下的第三款量产车型,定位紧凑型轿车,与特斯拉Model3处于同一级别。9月15日,小鹏P5正式上市,补贴后售价15。7922。39万元。新车的售价相比特斯拉Model3淘菜菜美团优选多多买菜,社区电商上演三国杀!9月14日,阿里宣布将盒马集市和淘宝买菜整合升级为社区电商淘菜菜。今年年初我接了美团买菜多多买菜兴盛优选十荟团橙心优选的南京地推订单,短短半年多时间,社区团购的大局悄然改变。互联网A15处理器全新发布性能领先竞品50iPhone13系列如期而至,新一代A15处理器也如期而至,意外的是第六代iPadmini用的也直接就是A15,要是到上一代还是A12。A15处理器集成多达150亿个晶体管,对比上对抗黑客零日攻击苹果发布紧急安全更新稿源3DMGame如果您用的是苹果生态设备,并且一直不喜欢更新软件,您可能处在被黑客攻击的危险当中。一家网络安全研究公司最近披露了一个大规模的安全漏洞,随后苹果发布了iOS和mac失去滴滴供血的橙心优选,败局已定大家都知道,目前滴滴生死未卜,有关部门已经介入调查,滴滴的命运已经无法掌握在自己手里了。不过落得如此田地也是滴滴咎由自取,一个靠出卖利益换取资源的企业是不会得到消费者和国家支持的,联想少帅往事能干的孙宏斌救火的郭为沉默的杨元庆作者阿政柴狗夫斯基专题网事哈喽大家好,我是宇宙第二反套路防忽悠揭秘商业和资本真相的镰刀粉碎机小柴(点击下方关注)锁定小柴网事专题,每周一期互联网激荡三十年,他们的故事足够精彩,小柴为什么抓了那么多,做电信诈骗的还是那么多?为什么抓了那么多,做电信诈骗的还是那么多?的确,电信诈骗犯,抓了很多。据公安部刑事侦查局副局长姜国利介绍,今年1至5月,全国共破获电信网络诈骗案件11。4万起,打掉犯罪团伙1。4万数字服务专区首次亮相服贸会数字开启美好生活据人民网,未来生活,数字将无处不在。2021年服贸会首次设置的数字服务专区成为一大亮点。让传统文化瑰宝活起来的数字故宫刷新世界天际线的智慧建造超高层建筑守护全球自然生态的AI智能系雷军谈小米梦想让全球每个人都能享受科技带来的美好生活作者龚进辉日前,小米掌门人雷军在微博上表示,小米的梦想是让全球每个人都能享受科技带来的美好生活。不得不说,小米的梦想很伟大,是一家非常有追求有情怀的科技公司。事实上,成立11年来,身为穷人,你自卑吗?身为穷人我认为,完全不该自卑,自卑者在于站在原地,躺倒不干仼人宰割,脚踏西瓜皮,滑到哪里是那里没志气的人,才感到悲哀而自卑。凡是在面临穷的面前,只要能有坚强的自信和毅力,去努力拚搏
轻装户外运动鞋,斯立德大地系列S204深度体验俗话说的好,鞋合不合适,只有脚知道。你想挑一双合适你的户外运动鞋?我的脚说了不算,钱包说了算。玩笑归玩笑,接下来我想跟各位分享这段时间我试穿斯立德大地系列S204的感受,以及挑选户30秒过安检是一种怎样的体验?地平线8号商务登机箱评测一前言经常出差坐飞机的人应该都知道笔记本电脑和充电宝需要和行李箱分开过安检,大部分人都会选择额外带一个电脑包,或是从行李箱内取出电脑再置入,当麻烦变成习惯依然还是个麻烦。想当年我在佳能索尼通用,一充四座,XTARVN2屏显相机充电器评测座充几乎是每位摄影人外出拍摄的必备配件,如果你还在直连充电线或者使用劣质底座,充电效率低不说,还有可能损伤电池寿命。最近刚好有这个机会体验这款XTAR屏显相机电池充电器,使用两周半螺丝刀一支就够了,WOWSTICK手动螺丝刀套装分享一前言普通的螺丝刀用完了随便一扔,找不到了花十几块钱再买一套,是最不起眼的工具。如果想做点有仪式感的案头工作,比如像手工耿那样的,没有一套趁手的工具是不行的。我最近收到的这套WOW冲牙,小蛮腰!欧卡尔琳P40冲牙器体验记牙,该从何说起呢?从十年前啃鸡爪子把牙咬崩了,去医院补牙那天开始吧,之后我就非常在意牙齿清洁,坚持饭后漱口,定期更换牙刷,与牙齿健康相关的文章我都会格外关注。好巧不巧,亿智蘑菇最近长了PS4的脸,流着Xbox的血,北通蝙蝠4手柄评测Xbox和PS4手柄究竟哪个更好用?您是否曾经这两者之间犹豫过呢?花开两朵,咱各表一枝,不论您选哪位,现如今要挑一款PS4布局的手柄可真不太容易。高仿的看上去很美,兼容性摇杆按键如来给你的冬天加把火,FOOXMET风谜智能温控电加热服讨厌冬天!不知您各位是否有这种体会,天儿一转凉,甭管穿多少衣服,手脚都是冰凉的,我不止一次羡慕那些大冬天只穿一件T恤羽绒服还能暖手暖脚的人。倘若一件衣服既能暖身,还能暖手,岂不乐哉会用光用好光,洋葱工厂布朗尼摄影灯套装体验摄影是用光的艺术,想必你已经听过这句话了。光是影响照片氛围感的重要因素,充足的光线能够最大程度发挥相机性能,好的摄影师总是在第一时间找到合适的光线。如果光线不足,或者需要特殊光,那你的背包,背到现在还没烂,多特DEUTER户外通勤电脑包评测多特是欧洲大牌户外运动装备厂商,户外探险人应该或多或少都听说过多特,我身边用FUTURA系列的朋友对它的评价是多特的背负系统永远不会让人失望。回过头来,这位欧洲老炮做起日用通勤包会2020年最后一次毫无保留的JEETONE无线蓝牙耳机评测2020年只剩下最后一个月了,如果你还没有用过真无线蓝牙耳机,那真应该反思一下了。也许是选择太多,也许是等等党的诱惑实在太大,可以确定的是,无线耳机已经是比较成熟的产品了,无论是运好熨来,居家小技能1,舒乐氏手持挂烫机体验一前言曾几何时,熨斗也算是家中的必备电器,现在会用的年轻人好像不太多了,习惯了穿旧即换,哪知衣服只要熨得好,新旧自然了无痕。传统的电熨斗笨重占地方,吃灰几率极高,因此选对合适的熨烫