kafka告警简单方案

一、前言

  为什么要设计kafka告警方案?现成的监控项目百度一下一大堆,KafkaOffsetMonitor、KafkaManager、 Burrow等,具体参考:kafka的消息挤压监控。由于本小组的项目使用的kafka集群并没有被公司的kafka-manager管理,所以只能自己简单做一个告警。

二、告警方案

  

  首先需要两个定时任务,之间的通信依靠延迟队列。

  左边的定时任务按周期扫面配置Topic-Consumer列表,通过kafka api获取消费详情并判断消息积压量是否已经大于阈值,如果阈值校验失败则放入延迟队里。

  右边的定时任务按照周期从延迟队列对应的真实队列中取出一个Topic-Consumer关系,再次进行一下阈值的校验,如果检验失败才发送告警短信。

三、准备工作

  1、依赖配置中心

  配置中心是实现告警方案的一个关键点,通过配置中心可以动态获取告警相关的属性配置,并刷新对应的 java bean。如下是告警对应的配置bean。

@ConfigCenterBean
@ConfigurationProperties(prefix = "wmhcontrol.alarm")
@Component
public class AlarmConstants extends BaseConfigCenterBean {private static Logger LOGGER = LoggerFactory.getLogger(AlarmConstants.class);//告警电话号码
    @ConfigFieldprivate String[] phones;//短信模板ID
    @ConfigFieldprivate String templateId;//延迟时间
    @ConfigFieldprivate Integer delay = 600;//轮训时间
    @ConfigFieldprivate Integer period = 60;//获取topic-consumer消费详情地址
    @ConfigFieldprivate String tcsr;//查看topic-consumer消费详情地址
    @ConfigFieldprivate String tclr;//全局统一阈值
    @ConfigFieldprivate Integer threshold = 1000;//topic和consumer关系列表
    @ConfigFieldprivate List<TCR> tcrs;@ToInitialprivate void refreshProperties() {try {super.doBind();LOGGER.info(String.format("%s 刷新成功..., 当前配置=%s...", this.getModuleName(), this));} catch (Exception e) {LOGGER.error("AlarmConstants 对象属性绑定失败...", e);}}private void toRefresh() {try {if (isCfgCenterEffect()) {ZookeeperPropertySource propertySource = ConfigHelper.getZookeeperPropertySource();if (ConfigCenterUtils.propertySourceShouldRefresh(this.getModuleName(), propertySource)) {this.refreshProperties();}}} catch (Exception e) {LOGGER.error("AlarmConstants 对象属性刷新失败", e);}}@ToRefreshpublic Integer getThreshold() {return threshold;}public void setThreshold(Integer threshold) {this.threshold = threshold;}@ToRefreshpublic List<TCR> getTcrs() {return tcrs;}public void setTcrs(List<TCR> tcrs) {this.tcrs = tcrs;}@ToRefreshpublic String getTcsr() {return tcsr;}public void setTcsr(String tcsr) {this.tcsr = tcsr;}@ToRefreshpublic Integer getPeriod() {return period;}public void setPeriod(Integer period) {this.period = period;}@ToRefreshpublic Integer getDelay() {return delay;}public void setDelay(Integer delay) {this.delay = delay;}@ToRefreshpublic String[] getPhones() {return phones;}public void setPhones(String[] phones) {this.phones = phones;}@ToRefreshpublic String getTemplateId() {return templateId;}public void setTemplateId(String templateId) {this.templateId = templateId;}@ToRefreshpublic String getTclr() {return tclr;}public void setTclr(String tclr) {this.tclr = tclr;}@Overridepublic String toString() {return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE, false, false, AlarmConstants.class);}@Overridepublic String getDefaultResourcePath() {return "config/alarm.properties";}@Overridepublic String getConfigPrefix() {return "wmhcontrol.alarm";}@Overridepublic String getModuleName() {return "告警配置";}@Overridepublic void refreshForEvent() {this.refreshProperties();}/*** topic 和 consumer之间的关系实体*/public static class TCR {private String topic;private String consumer;private String channel;private Integer threshold;public String getTopic() {return topic;}public void setTopic(String topic) {this.topic = topic;}public String getConsumer() {return consumer;}public void setConsumer(String consumer) {this.consumer = consumer;}public String getChannel() {return channel;}public void setChannel(String channel) {this.channel = channel;}public Integer getThreshold() {return threshold;}public void setThreshold(Integer threshold) {this.threshold = threshold;}@Overridepublic String toString() {return "TCR{" +"topic='" + topic + '\'' +", consumer='" + consumer + '\'' +", channel='" + channel + '\'' +", threshold=" + threshold +'}';}}public static class TopicConsumerDetail {private String group;private String topic;private Integer pid;private Long offset;private Long logsize;@Overridepublic String toString() {return "TopicConsumerDetail{" +"group='" + group + '\'' +", topic='" + topic + '\'' +", pid=" + pid +", offset=" + offset +", logsize=" + logsize +", lag=" + lag +", owner='" + owner + '\'' +'}';}private Long lag;private String owner;public String getGroup() {return group;}public void setGroup(String group) {this.group = group;}public String getTopic() {return topic;}public void setTopic(String topic) {this.topic = topic;}public Integer getPid() {return pid;}public void setPid(Integer pid) {this.pid = pid;}public Long getOffset() {return offset;}public void setOffset(Long offset) {this.offset = offset;}public Long getLogsize() {return logsize;}public void setLogsize(Long logsize) {this.logsize = logsize;}public Long getLag() {return lag;}public void setLag(Long lag) {this.lag = lag;}public String getOwner() {return owner;}public void setOwner(String owner) {this.owner = owner;}}
}

  告警有个全局统一的阈值,每一个topic可以指定不同的阈值。

  配置中心 和 java bean建立关联请参考:依赖配置中心实现注有@ConfigurationProperties的bean相关属性刷新。

  2、定时任务的周期性可动态配置

  借助 org.springframework.scheduling.annotation.SchedulingConfigurer。

  由@EnableScheduling注释的@Configuration类实现的可选接口。通常用于设置在执行计划任务时使用的特定TaskScheduler bean,或者用于以编程方式注册计划任务,而不是使用@Scheduled注释的声明性方法。例如,在实现基于触发器的任务时可能需要这样做,而@Scheduled注释不支持这些任务。

  基本的代码轮廓如下。

@Configuration
public class MessageCenterAlarmTask implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {try {//每5s检测一下队列taskRegistrar.addFixedRateTask(() -> {}, 5 * 1000L);//动态修改定时任务周期taskRegistrar.addTriggerTask(() -> {}, triggerContext -> new PeriodicTrigger(alarmConstants.getPeriod(), TimeUnit.SECONDS).nextExecutionTime(triggerContext));} catch (Exception e) {LOGGER.error("消息中心topic-consumer定时任务初始化失败...", e);}}
}

  上面的代码中的定时任务分别表示了告警方案中右边和左边的定时任务。

  3、延迟队列的实现

  借助redisson分布式延迟队列 或者 java delayqueue + redistemplate 实现分布式延迟队列。

  参考:Redisson分布式延迟队列官方文档

  参考:Redisson DelayedQueue实现原理

  Redisson的集群模式配置如下。

public class RedissonBuilder {private static Logger LOGGER = LoggerFactory.getLogger(RedissonBuilder.class);public static RedissonClient getRedisson(String cluster) {String[] nodes = cluster.split(",");for (int i = 0; i < nodes.length; i++) {nodes[i] = "redis://" + nodes[i];}Config config = new Config();config.useClusterServers() //这是用的集群server.setScanInterval(2000) //设置集群状态扫描时间.setConnectTimeout(2000).addNodeAddress(nodes);try {RedissonClient rc = Redisson.create(config);return rc;} catch (Exception e) {LOGGER.error("RedissonClient getRedisson errors...", e);return null;}}
}@Configuration
public class RedissonConfig {private static Logger LOGGER = LoggerFactory.getLogger(RedissonConfig.class);@Beanpublic RedissonClient redissonClient(@Value("${redisAddress}") String cacheAddress) {RedissonClient rc = RedissonBuilder.getRedisson(cacheAddress);try {if (!Objects.isNull(rc)) {LOGGER.info(rc.getConfig().toJSON());}} catch (IOException e) {LOGGER.error("RedissonConfig redissonClient errors... ", e);}return rc;}}

  Redisson创建延迟队列方式

RQueue<AlarmConstants.TCR> topicConsumerQueue = redissonClient.getQueue(commonRedisKey + "message_center_tcrs");
RDelayedQueue<AlarmConstants.TCR> topicConsumerDelayedQueue = redissonClient.getDelayedQueue(topicConsumerQueue);

  首先创建目标队列,然后通过目标队列拿到延迟队列。

  4、kafka api返回数据处理

  参考:简单封装kafka相关的api

  更具topic和consumer可以拿到如下数据。其中Lag对应的这一列表示未消费的消息数量。

  

  需要做的是把如上数据映射到 AlarmConstants.TopicConsumerDetail 这个java bean中,借助Spring BeanWrapperImpl,如下。

private static List<AlarmConstants.TopicConsumerDetail> retrieveDetail(String detailResponse) {List<AlarmConstants.TopicConsumerDetail> result = new ArrayList<>();try {Scanner scanner = new Scanner(detailResponse.replace("<pre>", StringUtils.EMPTY).replace("</pre>", StringUtils.EMPTY));String[] propertyNames = null;
//第一行对应java bean的field name
if (scanner.hasNextLine()) {String nameLine = scanner.nextLine();if (StringUtils.isBlank(nameLine)) {return result;}propertyNames = Arrays.stream(nameLine.split("\\s+")).map(propertyName -> propertyName.toLowerCase()).toArray(String[]::new);}
//剩余行对应java bean的field value
while (scanner.hasNextLine()) {AlarmConstants.TopicConsumerDetail tcd = new AlarmConstants.TopicConsumerDetail();BeanWrapper br = new BeanWrapperImpl(tcd);String valueLine = scanner.nextLine();if (StringUtils.isBlank(valueLine)) {continue;}String[] propertyValues = valueLine.split("\\s+");for (int i = 0; i < propertyValues.length; i++) { br.setPropertyValue(propertyNames[i], propertyValues[i]);}result.add(tcd);}LOGGER.info("消息中心提取topic-consumer详情信息:" + result);} catch (Exception e) {LOGGER.error("消息中心提取topic-consumer信息异常..." + detailResponse, e);}return result; }

  处理之后的效果如下。

[TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=0, offset=10956087, logsize=10956091, lag=4, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818094331-835bc5f7-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=1, offset=10950487, logsize=10950502, lag=15, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818094331-835bc5f7-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=2, offset=10958523, logsize=10958529, lag=6, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818094331-835bc5f7-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=3, offset=10955709, logsize=10955717, lag=8, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818094331-835bc5f7-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=4, offset=10956550, logsize=10956563, lag=13, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818094331-835bc5f7-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=5, offset=10956343, logsize=10956347, lag=4, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818202772-32905832-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=6, offset=10954124, logsize=10954128, lag=4, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818202772-32905832-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=7, offset=10949075, logsize=10949082, lag=7, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818202772-32905832-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=8, offset=10963839, logsize=10963843, lag=4, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818202772-32905832-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=9, offset=10958536, logsize=10958540, lag=4, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0495-DMZ-A620R-1543818202772-32905832-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=10, offset=10955316, logsize=10955327, lag=11, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0496-DMZ-A620R-1543818798625-b7a38283-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=11, offset=10957850, logsize=10957856, lag=6, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0496-DMZ-A620R-1543818798625-b7a38283-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=12, offset=10954508, logsize=10954515, lag=7, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0496-DMZ-A620R-1543818798625-b7a38283-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=13, offset=10960468, logsize=10960477, lag=9, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0496-DMZ-A620R-1543818798625-b7a38283-0'}, TopicConsumerDetail{group='group-message-center-consumer-TELEPHONE_HOTLINE', topic='message-center-telephone-hotline-topic', pid=14, offset=10955540, logsize=10955544, lag=4, owner='group-message-center-consumer-TELEPHONE_HOTLINE_N03-NFJD-BB-SV0496-DMZ-A620R-1543818798625-b7a38283-0'}]

四、告警逻辑

  1、短息发送

private String toShortMessage(AlarmConstants.TCR tcr, Long lag) {JSONObject info = new JSONObject();StringBuilder text = new StringBuilder();String messageDate = LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME);text.append("【Topic-Consumer阈值告警 " + messageDate + "】\r\n");text.append("\t渠道: " + tcr.getChannel() + "\r\n");text.append("\t主题: " + tcr.getTopic() + "\r\n");text.append("\t消费: " + tcr.getConsumer() + "\r\n");text.append("\t阈值: " + (Objects.isNull(tcr.getThreshold()) ? alarmConstants.getThreshold() : tcr.getThreshold()) + "\r\n");text.append("\t堆积: " + lag + "\r\n");try {String refUrl = alarmConstants.getTclr() + "?topic=" + tcr.getTopic() + "&group=" + tcr.getConsumer();JSONObject params = new JSONObject();params.put("url", refUrl);String shortUrlResponse = HttpClient.post("https://dwz.cn/admin/create", params.toJSONString(), "application/json");LOGGER.info("获取短链接返回内容:" + shortUrlResponse);if (StringUtils.isNotBlank(shortUrlResponse)) {JSONObject shortUrlJson = JSON.parseObject(shortUrlResponse);Integer code = (Integer) FastJsonUtils.search(shortUrlJson, "Code");if (Integer.valueOf(0).equals(code)) {String shortUrl = (String) FastJsonUtils.search(shortUrlJson, "ShortUrl");if (StringUtils.isNotBlank(shortUrl)) {text.append("\t查看: " + shortUrl + "\r\n");}}}} catch (Exception e) {LOGGER.error("短链接生成异常...", e);}info.put("txt_name", "消息中心");info.put("txt_result", text.toString());return info.toJSONString();
}

  2、阈值校验

private Long thresholdCheck(AlarmConstants.TCR tcr) {String detailUrl = alarmConstants.getTcsr() + "?topic=" + tcr.getTopic() + "&group=" + tcr.getConsumer();String detailResponseStr = HttpClient.get(detailUrl);LOGGER.info(detailUrl + " " + detailResponseStr);List<AlarmConstants.TopicConsumerDetail> detailResponse = retrieveDetail(detailResponseStr);if (CollectionUtils.isEmpty(detailResponse)) {return -1L;}Long lag = detailResponse.stream().mapToLong(AlarmConstants.TopicConsumerDetail::getLag).sum();Long threshold = Long.valueOf(Objects.isNull(tcr.getThreshold()) ? alarmConstants.getThreshold() : tcr.getThreshold());if (lag.compareTo(threshold) > 0) {return lag;}return -1L;
}

  3、两个定时任务逻辑补充

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {try {RQueue<AlarmConstants.TCR> topicConsumerQueue = redissonClient.getQueue(commonRedisKey + "message_center_tcrs");RDelayedQueue<AlarmConstants.TCR> topicConsumerDelayedQueue = redissonClient.getDelayedQueue(topicConsumerQueue);//每5s检测一下队列taskRegistrar.addFixedRateTask(() -> {AlarmConstants.TCR tcr = topicConsumerQueue.poll();if (!Objects.isNull(tcr)) {//发送告警信息Long lag = thresholdCheck(tcr);if (lag > 0) {if (ArrayUtils.isNotEmpty(alarmConstants.getPhones())) {String message = toShortMessage(tcr, lag);String tmplateId = alarmConstants.getTemplateId();LOGGER.info("消息中心告警短信内容:" + message);for (String phone : alarmConstants.getPhones()) {try {MessageUtils.sendMessage(phone, messageUrl, message, tmplateId);} catch (Exception e) {LOGGER.error(String.format("消息中心告警短信发送异常...%s %s %s", phone, messageUrl, message), e);}}}}}}, 5 * 1000L);taskRegistrar.addTriggerTask(() -> {RLock lock = null;try {lock = redissonClient.getLock(commonRedisKey + "TopicConsumerForEach");// 尝试加锁,最多等待5秒,上锁以后5秒自动解锁if (!lock.tryLock(5, 5, TimeUnit.SECONDS)) {return;}if (!CollectionUtils.isEmpty(alarmConstants.getTcrs())) {alarmConstants.getTcrs().stream().filter(tcr -> !topicConsumerDelayedQueue.contains(tcr) && (thresholdCheck(tcr) > 0)).forEach(tcr -> topicConsumerDelayedQueue.offer(tcr, alarmConstants.getDelay(), TimeUnit.SECONDS));}} catch (Exception e) {LOGGER.error("消息中心topic-consumer定时任务执行失败...", e);} finally {if (!Objects.isNull(lock)) {lock.unlock();}}
     //动态周期性检测Topic-Consumer阈值}, triggerContext -> new PeriodicTrigger(alarmConstants.getPeriod(), TimeUnit.SECONDS).nextExecutionTime(triggerContext));} catch (Exception e) {LOGGER.error("消息中心topic-consumer定时任务初始化失败...", e);}
}

五、告警定时任务源码

  请关注订阅号(算法和技术SHARING),回复:kafka告警, 便可查看。

转载于:https://www.cnblogs.com/hujunzheng/p/10063449.html

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

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

相关文章

RedisCacheManager设置Value序列化器技巧

CacheManager基本配置 请参考博文&#xff1a;springboot2.0 redis EnableCaching的配置和使用 RedisCacheManager构造函数 /*** Construct a {link RedisCacheManager}.* * param redisOperations*/ SuppressWarnings("rawtypes") public RedisCacheManager(RedisOp…

HashMap 源码阅读

前言 之前读过一些类的源码&#xff0c;近来发现都忘了&#xff0c;再读一遍整理记录一下。这次读的是 JDK 11 的代码&#xff0c;贴上来的源码会去掉大部分的注释, 也会加上一些自己的理解。 Map 接口 这里提一下 Map 接口与1.8相比 Map接口又新增了几个方法&#xff1a;   …

SpringMvc接口中转设计(策略+模板方法)

一、前言 最近带着两个兄弟做支付宝小程序后端相关的开发&#xff0c;小程序首页涉及到很多查询的服务。小程序后端服务在我司属于互联网域&#xff0c;相关的查询服务已经在核心域存在了&#xff0c;查询这块所要做的工作就是做接口中转。参考了微信小程序的代码&#xff0c;发…

SpringSecurity整合JWT

一、前言 最近负责支付宝小程序后端项目设计&#xff0c;这里主要分享一下用户会话、接口鉴权的设计。参考过微信小程序后端的设计&#xff0c;会话需要依靠redis。相关的开发人员和我说依靠Redis并不是很靠谱&#xff0c;redis在业务高峰期不稳定&#xff0c;容易出现问题&…

Springboot定时任务原理及如何动态创建定时任务

一、前言 上周工作遇到了一个需求&#xff0c;同步多个省份销号数据&#xff0c;解绑微信粉丝。分省定时将销号数据放到SFTP服务器上&#xff0c;我需要开发定时任务去解析文件。因为是多省份&#xff0c;服务器、文件名规则、数据规则都不一定&#xff0c;所以要做成可配置是有…

转载:ThreadPoolExecutor 源码阅读

前言 之前研究了一下如何使用ScheduledThreadPoolExecutor动态创建定时任务(Springboot定时任务原理及如何动态创建定时任务)&#xff0c;简单了解了ScheduledThreadPoolExecutor相关源码。今天看了同学写的ThreadPoolExecutor 的源码解读&#xff0c;甚是NB&#xff0c;必须转…

使用pdfBox实现pdf转图片,解决中文方块乱码等问题

一、引入依赖 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.13</version> </dependency> <dependency><groupId>org.apache.pdfbox</groupId><artif…

Spring异步调用原理及SpringAop拦截器链原理

一、Spring异步调用底层原理 开启异步调用只需一个注解EnableAsync Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Import(AsyncConfigurationSelector.class) public interface EnableAsync {/*** Indicate the async annotation type to be detec…

Spring MVC源码——Root WebApplicationContext

Spring MVC源码——Root WebApplicationContext 打算开始读一些框架的源码,先拿 Spring MVC 练练手,欢迎点击这里访问我的源码注释, SpringMVC官方文档一开始就给出了这样的两段示例: WebApplicationInitializer示例: public class MyWebApplicationInitializer implements Web…

Spring MVC源码——Servlet WebApplicationContext

上一篇笔记(Spring MVC源码——Root WebApplicationContext)中记录了下 Root WebApplicationContext 的初始化代码.这一篇来看 Servlet WebApplicationContext 的初始化代码 DispatcherServlet 是另一个需要在 web.xml 中配置的类, Servlet WebApplicationContext 就由它来创建…

Springboot源码——应用程序上下文分析

前两篇(Spring MVC源码——Root WebApplicationContext 和 Spring MVC源码——Servlet WebApplicationContext)讲述了springmvc项目创建上下文的过程&#xff0c;这一篇带大家了解一下springboot项目创建上下文的过程。 SpringApplication引导类 SpringApplication类用于启动或…

基于zookeeper实现分布式配置中心(一)

最近在学习zookeeper&#xff0c;发现zk真的是一个优秀的中间件。在分布式环境下&#xff0c;可以高效解决数据管理问题。在学习的过程中&#xff0c;要深入zk的工作原理&#xff0c;并根据其特性做一些简单的分布式环境下数据管理工具。本文首先对zk的工作原理和相关概念做一下…

基于zookeeper实现分布式配置中心(二)

上一篇&#xff08;基于zookeeper实现分布式配置中心&#xff08;一&#xff09;&#xff09;讲述了zookeeper相关概念和工作原理。接下来根据zookeeper的特性&#xff0c;简单实现一个分布式配置中心。 配置中心的优势 1、各环境配置集中管理。 2、配置更改&#xff0c;实时推…

Redis分布式锁实战

背景 目前开发过程中&#xff0c;按照公司规范&#xff0c;需要依赖框架中的缓存组件。不得不说&#xff0c;做组件的大牛对CRUD操作的封装&#xff0c;连接池、缓存路由、缓存安全性的管控都处理的无可挑剔。但是有一个小问题&#xff0c;该组件没有对分布式锁做实现&#xff…

基于RobotFramework实现自动化测试

Java robotframework seleniumlibrary 使用Robot Framework Maven Plugin&#xff08;http://robotframework.org/MavenPlugin/&#xff09;执行自动化测试chromedriver下载&#xff1a; http://chromedriver.storage.googleapis.com/index.htmlchromedriver和chrome版本对应…

Springboot国际化信息(i18n)解析

国际化信息理解 国际化信息也称为本地化信息 。 Java 通过 java.util.Locale 类来表示本地化对象&#xff0c;它通过 “语言类型” 和 “国家/地区” 来创建一个确定的本地化对象 。举个例子吧&#xff0c;比如在发送一个具体的请求的时候&#xff0c;在header中设置一个键值对…

C语言一看就能上手的干货!你确定你不来看吗?

本地环境设置 如果您想要设置 C 语言环境&#xff0c;您需要确保电脑上有以下两款可用的软件&#xff0c;文本编辑器和 C 编译器。 文本编辑器 这将用于输入您的程序。文本编辑器包括 Windows Notepad、OS Edit command、Brief、Epsilon、EMACS 和 vim/vi。文本编辑器的名称…

10万码农五年的C语言笔记!你现在知道别人为什么这么优秀了吗?

c语言对许多同学来说确实是一门比较难学的课程&#xff0c;不仅抽象&#xff0c;而且繁琐&#xff0c;但这又是一门不得不学的课程。前两节可能还有兴致听一听&#xff0c;然而&#xff0c;再过几节课就是一脸蒙比。凭空要想出一道题的算法和程序&#xff0c;根本无从下手。 所…

C语言/C++编程学习:C语言环境设置!

C语言是面向过程的&#xff0c;而C&#xff0b;&#xff0b;是面向对象的 C和C的区别&#xff1a; C是一个结构化语言&#xff0c;它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程&#xff0c;对输入&#xff08;或环境条件&#xff09;进行运算处理得…

C语言指针原来也可以这么的通俗易懂!

C语言是面向过程的&#xff0c;而C&#xff0b;&#xff0b;是面向对象的 C和C的区别&#xff1a; C是一个结构化语言&#xff0c;它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程&#xff0c;对输入&#xff08;或环境条件&#xff09;进行运算处理得…