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

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

软件开发者路线路读后感继软技能之后,这是我读的第二本非技术性质的关于开发者的书籍,据说被奉为开发者圣经的级别。但是小气的我是在Kindle上读的扫描本,加之本土翻译,阅读效果不太好。读完之后,有的概念不掌握这几条命令,你就会用git了一般做开发的都知道版本控制器的作用非比寻常,相当于千金难买的后悔药。而对于大多程序员使用较多的还是SVN,毕竟要方便很多。不过,git有两个优势是SVN无法匹敌的,那就是git将所Laraveladmin自定义拓展及常见问题(下)书接上回,当提交表单后不跳转页面也不报错时咋处理呢。六提交表单不跳转页面且不报错前面也说了,其实提交后发生了数据传送。但是由于验证未通过又返回了编辑页面,所以我们要做的是找到验证的餐见新闻逮虾记新工厂开业,农夫山泉钟睒睒成中国首富山东举办农产品食品检验职业技能大赛近日,由山东省食品科学技术学会主办的山东省技能兴鲁职业技能大赛欧倍尔杯山东省农产品食品检验职业技能竞赛在山东威海荣成举办,来自山东省各高校职业院校档口模式一店双铺不仅能反哺门店,还能成为品牌精准引流抓手疫情后,有一种餐厅火了。它们模式轻投入少店型灵活场景丰富,兼顾外带外卖和一点点堂食,疫情期间不但没有受到严重冲击,反而趁势发展得更快了。一些小区和办公楼的周边商圈,冒出了一批以外带餐见新闻新餐见门店自流量品牌强爆破沙龙举办,肯德基上新新餐见门店自流量,品牌强爆破沙龙举办9月14日,门店自流量品牌强连锁新餐饮连锁品牌创新增长研修沙龙在山东科创大厦圆满落幕。本次研修沙龙主讲导师有海右咨询集团营销总监周浩,合盛咨询营品类定位细化到何种程度?答案以品类来思考,以品牌来表达01hr品类定位细化到何种程度?自2014年巴奴更名,聚焦巴奴毛肚火锅之后,餐饮江湖兴起更名潮,纷纷聚焦单品爆款。在巴奴总部所在地郑州,更是刮起一股更名潮。百宴拉面更名为百宴菌菇拉餐见新闻喜姐炸串完成A轮融资,百胜中国三季度净利润1。04亿美元前三季度山东惠民惠农补贴同比增长30。4据山东省财政厅最新统计,1至9月,全省通过一本通发放惠民惠农财政补贴238。46亿元,同比增长30。4,涉及补贴项目82项,6496。37万门店自流量品牌强连锁新餐饮连锁品牌创新增长研修沙龙圆满闭幕9月14日,门店自流量品牌强连锁新餐饮连锁品牌创新增长研修沙龙在山东科创大厦圆满落幕。本次研修沙龙邀请了新餐见品牌中心总监新餐见联合发起单位海右咨询集团营销总监周浩,合盛咨询营销主餐见新闻健太拉面获种子轮融资,轻食品牌Sweetgreen即将上市山东财政助推科技引领产业绿色低碳高质量发展近日,山东出台科技引领产业绿色低碳高质量发展的实施意见,重点围绕绿色技术成果产出转化和产业化体系形成,进一步强化财政政策集成和供给保障,助火锅品牌的心智占位,源于品类味型爆品上的差异化定位01hr沸腾火锅大数据2021年火锅市场继续爆发。有不少业内人士认为,火锅品类的火热带动了火锅食材超市的发展。据相关数据显示,2020年火锅食材超市注册出现了爆发式增长,一年新增近
什么路由器穿墙效果好?什么路由器穿墙效果好?毕竟一分钱一分货,路由器品牌太多,每个人都要根据自己的房屋面积和经济条件来进行选择。路由器体验以稳为先,稳健架构驾驭任意宽带,多连不卡,易展Mesh即可。一台IOS14。5系统推出仅一周,为何IOS14。6就来了?众所周知,自从iOS14。5曝光以来大家对其的关注度极高,也非常期待该系统的更新,但是系统功能一直在更新,直到近日才有正式版iOS14。5在下周推送的消息,让不少人非常期待。相信有今年深圳有1700多万人,人口增加了不少,为何说深圳人少了?深圳长期以来实际居住人口是高于1700万的,现在深户好入,很多人只是在深圳挂了个名头,在深圳高消费压力下,以前那些所谓黑户不交社保不找工作的自由职业者也少了,要证据其实很简单,工厂美团市值从3万跌至现在的1。48万亿,为何?1。48万亿还太高,就一个中间商,用几款垄断软件。根本没有价值可言。如果法制健全并执行到位。首先成本就做不下来。市值本来就是资本家们玩的游戏!美团2019年的市值才3千亿而已,为什有什么可以替代notepad的软件?微软已经决定将记事本作为一个可卸载应用了,根据微软披露的Windows1020H1Build19041版本计划,微软将一些软件列入了可选功能,允许用户关闭或者打开某些功能。IE浏览这些手机好评度高,骁龙888遇冷,老旗舰受宠,小米未进入前十参考最新的安卓手机好评榜,笔者发现,新旗舰集体遇冷,老旗舰越来越香。我们知道,2021年有多款骁龙888新机问世。这些旗舰手机,做工出色,配置强悍。可是,在好评榜上,骁龙888集体高通骁龙778G处理器发布采用6nm工艺,荣耀50系列或首发IT之家5月18日消息高通公司今天推出了基于6纳米工艺技术的骁龙778G5G移动平台,扩展其7系列产品组合。骁龙778G5G芯片组采用高通Kryo670CPU,可实现40的性能提升网约车系统定制开发滴滴打车随着现代社会的高速发展,网约车早已融入了大众的日常生活。但是面对市面上层出不穷良莠不齐的各类网约车平台,只有让网约车真正实现合规化规范化运营,才能让居民群众享受到高品质的服务,名正4个小众却强大的黑科技app,让你的手机无所不能手机里的app,很大程度上决定了手机好不好用。每个人的手机里都有几款私藏已久,虽然不是很多人知道,但却非常实用的app。这里就来分享给你,我手机里的4个小众却强大的黑科技app,让中芯国际700亿造28nm芯片,却暴露一大短板,台积电的决定没有错文科技在前方前段时间,台积电曾召开过一次临时董事会,紧急官宣投资28。87亿美元,合计人民币187亿,在南京建立28nm生产线。对于台积电紧急官宣的内容,其实有两个关键点备受外界关易葳录发力供应链透明度来源经济日报中国在全球供应链中占据着至关重要的位置。全球有大量商品产自中国消费在中国,我们尤其关注中国市场。近日,易葳录首席运营官兼亚洲区主管克里斯泰勒在接受记者采访时说。易葳录是