日志打入kafka改造历程-我们到底能走多远系列

扩展logback将日志输出到Kafka实例源码 扩展logback将日志输出到Kafka实例源码,详情请参见博文:http://blog.csdn.net/l1028386804/article/details/79135948 立即下载

方案

日志收集的方案有很多,包括各种日志过滤清洗,分析,统计,而且看起来都很高大上。本文只描述一个打入kafka的功能。

流程:app->kafka->logstash->es->kibana

业务应用直接将日志打入kafka,然后由logstash消费,数据进入es。

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

另一方面,应用在服务器上会打日志文件。

如图:

 

详细

初步实现

首先,我们来初步实现这个方案,搭建elk略去不谈,其中特别注意各个版本的兼容。这里主要在代码层面讲解如何实现的历程。

要将日志数据写入kafka,我们想只要依赖官方提供的kafka client就可以了,翻看github,有现成的:链接

没多少代码,通看一遍,在此基础上进行修改即可。

以下代码在spring boot框架基础。

核心appender代码:


 
public class KafkaAppender<E> extends KafkaAppenderConfig<E> { /** * Kafka clients uses this prefix for its slf4j logging. * This appender defers appends of any Kafka logs since it could cause harmful infinite recursion/self feeding effects. */ private static final String KAFKA_LOGGER_PREFIX = "org.apache.kafka.clients"; public static final Logger logger = LoggerFactory.getLogger(KafkaAppender.class); private LazyProducer lazyProducer = null; private final AppenderAttachableImpl<E> aai = new AppenderAttachableImpl<E>(); private final ConcurrentLinkedQueue<E> queue = new ConcurrentLinkedQueue<E>(); private final FailedDeliveryCallback<E> failedDeliveryCallback = new FailedDeliveryCallback<E>() { @Override public void onFailedDelivery(E evt, Throwable throwable) { aai.appendLoopOnAppenders(evt); } }; public KafkaAppender() { // setting these as config values sidesteps an unnecessary warning (minor bug in KafkaProducer) addProducerConfigValue(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class.getName()); addProducerConfigValue(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class.getName()); } @Override public void doAppend(E e) { ensureDeferredAppends(); if (e instanceof ILoggingEvent && ((ILoggingEvent)e).getLoggerName().startsWith(KAFKA_LOGGER_PREFIX)) { deferAppend(e); } else { super.doAppend(e); } } @Override public void start() { // only error free appenders should be activated if (!checkPrerequisites()) return; lazyProducer = new LazyProducer(); super.start(); } @Override public void stop() { super.stop(); if (lazyProducer != null && lazyProducer.isInitialized()) { try { lazyProducer.get().close(); } catch (KafkaException e) { this.addWarn("Failed to shut down kafka producer: " + e.getMessage(), e); } lazyProducer = null; } } @Override public void addAppender(Appender<E> newAppender) { aai.addAppender(newAppender); } @Override public Iterator<Appender<E>> iteratorForAppenders() { return aai.iteratorForAppenders(); } @Override public Appender<E> getAppender(String name) { return aai.getAppender(name); } @Override public boolean isAttached(Appender<E> appender) { return aai.isAttached(appender); } @Override public void detachAndStopAllAppenders() { aai.detachAndStopAllAppenders(); } @Override public boolean detachAppender(Appender<E> appender) { return aai.detachAppender(appender); } @Override public boolean detachAppender(String name) { return aai.detachAppender(name); } @Override protected void append(E e) { // encode 逻辑 final byte[] payload = encoder.doEncode(e); final byte[] key = keyingStrategy.createKey(e); final ProducerRecord<byte[], byte[]> record = new ProducerRecord<byte[],byte[]>(topic, key, payload); Producer producer = lazyProducer.get(); if(producer == null){ logger.error("kafka producer is null"); return; } // 核心发送方法 deliveryStrategy.send(lazyProducer.get(), record, e, failedDeliveryCallback); } protected Producer<byte[], byte[]> createProducer() { return new KafkaProducer<byte[], byte[]>(new HashMap<String, Object>(producerConfig)); } private void deferAppend(E event) { queue.add(event); } // drains queue events to super private void ensureDeferredAppends() { E event; while ((event = queue.poll()) != null) { super.doAppend(event); } } /** * Lazy initializer for producer, patterned after commons-lang. * * @see <a href="https://commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/apache/commons/lang3/concurrent/LazyInitializer.html">LazyInitializer</a> */ private class LazyProducer { private volatile Producer<byte[], byte[]> producer; private boolean initialized; public Producer<byte[], byte[]> get() { Producer<byte[], byte[]> result = this.producer; if (result == null) { synchronized(this) { if(!initialized){ result = this.producer; if(result == null) { // 注意 这里initialize可能失败,比如传入servers为非法字符,返回producer为空,所以只用initialized标记来确保不进行重复初始化,而避免不断出错的初始化 this.producer = result = this.initialize(); initialized = true; } } } } return result; } protected Producer<byte[], byte[]> initialize() { Producer<byte[], byte[]> producer = null; try { producer = createProducer(); } catch (Exception e) { addError("error creating producer", e); } return producer; } public boolean isInitialized() { return producer != null; } } } 

以上代码对producer生产时进行initialized标记,确保在异常场景时只生产一次。

在实际场景中比如我们的servers配置非ip的字符,initialize方法会返回null,因为判断是否进行initialize()方法是判断producer是否为空,所以进入不断失败的情况,从而导致应用启动失败。

配置logback-spring.xml:


 
<springProperty scope="context" name="LOG_KAFKA_SERVERS" source="application.log.kafka.bootstrap.servers"/> <springProperty scope="context" name="LOG_KAFKA_TOPIC" source="application.log.kafka.topic"/> <appender name="KafkaAppender" class="com.framework.common.log.kafka.KafkaAppender"> <topic>${LOG_KAFKA_TOPIC}</topic> <producerConfig>bootstrap.servers=${LOG_KAFKA_SERVERS}</producerConfig> </appender> 

bootstrap.properties配置:

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。


 
application.log.kafka.bootstrap.servers=10.0.11.55:9092 application.log.kafka.topic=prod-java 

在打入kafka的json进行自定义,上面的encoder.doEncode(e)进行扩展:


 
public class FormatKafkaMessageEncoder<E> extends KafkaMessageEncoderBase<E> { protected static final int BUILDER_CAPACITY = 2048; protected static final int LENGTH_OPTION = 2048; public static final String CAUSED_BY = "Caused by: "; public static final String SUPPRESSED = "Suppressed: "; public static final char TAB = '\t'; public byte[] encode(ILoggingEvent event) { Map<String, String> formatMap = new HashMap<>(); formatMap.put("timestamp", event.getTimeStamp()!=0?String.valueOf(new Date(event.getTimeStamp())):""); formatMap.put("span", event.getMDCPropertyMap()!=null?event.getMDCPropertyMap().get("X-B3-SpanId"):""); formatMap.put("trace", event.getMDCPropertyMap()!=null?event.getMDCPropertyMap().get("X-B3-TraceId"):""); formatMap.put("class", event.getLoggerName()); formatMap.put("level", event.getLevel() != null?event.getLevel().toString():""); formatMap.put("message", event.getMessage()); formatMap.put("stacktrace", event.getThrowableProxy()!=null?convertStackTrace(event.getThrowableProxy()):""); formatMap.put("thread", event.getThreadName()); formatMap.put("ip", IpUtil.getLocalIP()); formatMap.put("application", event.getLoggerContextVO()!=null&&event.getLoggerContextVO().getPropertyMap()!=null? event.getLoggerContextVO().getPropertyMap().get("springAppName"):""); String formatJson = JSONObject.toJSONString(formatMap); return formatJson.getBytes(); } @Override public byte[] doEncode(E event) { return encode((ILoggingEvent) event); } public String convertStackTrace(IThrowableProxy tp){ StringBuilder sb = new StringBuilder(BUILDER_CAPACITY); recursiveAppend(sb, tp, null); return sb.toString(); } private void recursiveAppend(StringBuilder sb, IThrowableProxy tp, String prefix) { if(tp == null){ return; } if (prefix != null) { sb.append(prefix); } sb.append(tp.getClassName()).append(": ").append(tp.getMessage()); sb.append(CoreConstants.LINE_SEPARATOR); StackTraceElementProxy[] stepArray = tp.getStackTraceElementProxyArray(); boolean unrestrictedPrinting = LENGTH_OPTION > stepArray.length; int maxIndex = (unrestrictedPrinting) ? stepArray.length : LENGTH_OPTION; for (int i = 0; i < maxIndex; i++) { sb.append(TAB); StackTraceElementProxy element = stepArray[i]; sb.append(element); sb.append(CoreConstants.LINE_SEPARATOR); } IThrowableProxy[] suppressed = tp.getSuppressed(); if (suppressed != null) { for (IThrowableProxy current : suppressed) { recursiveAppend(sb, current, SUPPRESSED); } } recursiveAppend(sb, tp.getCause(), CAUSED_BY); } } 

其中recursiveAppend方法是模仿ch.qos.logback.classic.spi.ThrowableProxyUtil,用来答应异常的全部堆栈。

还有这个ip的获取问题,InetAddress.getLocalHost().getHostAddress()解决不了。

以下是详细代码:


 
public class IpUtil { public static final String DEFAULT_IP = "127.0.0.1"; public static String cacheLocalIp = null; private static Logger logger = LoggerFactory.getLogger(IpUtil.class); /** * 直接根据第一个网卡地址作为其内网ipv4地址,避免返回 127.0.0.1 * * @return */ private static String getLocalIpByNetworkCard() { String ip = null; try { for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) { NetworkInterface item = e.nextElement(); for (InterfaceAddress address : item.getInterfaceAddresses()) { if (item.isLoopback() || !item.isUp()) { continue; } if (address.getAddress() instanceof Inet4Address) { Inet4Address inet4Address = (Inet4Address) address.getAddress(); ip = inet4Address.getHostAddress(); } } } } catch (Exception e) { logger.error("getLocalIpByNetworkCard error", e); try { ip = InetAddress.getLocalHost().getHostAddress(); } catch (Exception e1) { logger.error("InetAddress.getLocalHost().getHostAddress() error", e1); ip = DEFAULT_IP; } } return ip == null ? DEFAULT_IP : ip; } public synchronized static String getLocalIP() { if(cacheLocalIp == null){ cacheLocalIp = getLocalIpByNetworkCard(); return cacheLocalIp; }else{ return cacheLocalIp; } } } 

另外在logback-spring.xml中配置了本地日志appender:


 
<!-- 按照每天生成日志文件 --> <appender name="filelog" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- rollover daily --> <fileNamePattern>${LOG_FOLDER}/${springAppName}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> <!-- each file should be at most 100MB, keep 6 days worth of history--> <maxFileSize>300MB</maxFileSize> <!--历史文件保留个数--> <maxHistory>5</maxHistory> </rollingPolicy> <encoder> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> 

注意这里使用SizeAndTimeBasedRollingPolicy而不是使用TimeBasedRollingPolicy+SizeBasedTriggeringPolicy。

后者是按文件大小优先级最高不会自动按日期生成新的log文件。

至此,一个打入kafka日志的代码就算完结了,功能完全,执行正确。

异常场景

思考下,在启动应用或在应用运行时,kafka无法正确接收信息,比如挂掉了。那么这个打日志的功能会怎么表现呢?

当然是每次写日志都会尝试去连kafka,但是失败,必然影响应用状态。

所以想到熔断的思路,假设kafka挂掉,可以通过熔断的方式降低对应用的影响。

这里就实现了一下熔断器的逻辑。

状态流转图:

 

熔断器:


 
/** * @desc 熔断器 * 1,使用failureCount和consecutiveSuccessCount控制断路器状态的流转,两者都使用了AtomicInteger以确保并发场数量的精准 * 2,successCount 没有使用AtomicInteger 不确保准确性 * 3,failureThreshold,consecutiveSuccessThreshold,timeout参数非法赋默认值 */ public class CircuitBreaker { private static final Logger logger = LoggerFactory.getLogger(CircuitBreaker.class); private String name; /** * 熔断器状态 */ private CircuitBreakerState state; /** * 失败次数阀值 */ private int failureThreshold; /** * 熔断状态时间窗口 */ private long timeout; /** * 失败次数 */ private AtomicInteger failureCount; /** * 成功次数 (并发不准确) */ private int successCount; /** * 半开时间窗口里连续成功的次数 */ private AtomicInteger consecutiveSuccessCount; /** * 半开时间窗口里连续成功的次数阀值 */ private int consecutiveSuccessThreshold; public CircuitBreaker(String name, int failureThreshold, int consecutiveSuccessThreshold, long timeout) { if(failureThreshold <= 0){ failureThreshold = 1; } if(consecutiveSuccessThreshold <= 0){ consecutiveSuccessThreshold = 1; } if(timeout <= 0){ timeout = 10000; } this.name = name; this.failureThreshold = failureThreshold; this.consecutiveSuccessThreshold = consecutiveSuccessThreshold; this.timeout = timeout; this.failureCount = new AtomicInteger(0); this.consecutiveSuccessCount = new AtomicInteger(0); state = new CloseCircuitBreakerState(this); } public void increaseFailureCount(){ failureCount.addAndGet(1); } public void increaseSuccessCount(){ successCount++; } public void increaseConsecutiveSuccessCount(){ consecutiveSuccessCount.addAndGet(1); } public boolean increaseFailureCountAndThresholdReached(){ return failureCount.addAndGet(1) >= failureThreshold; } public boolean increaseConsecutiveSuccessCountAndThresholdReached(){ return consecutiveSuccessCount.addAndGet(1) >= consecutiveSuccessThreshold; } public boolean isNotOpen(){ return !isOpen(); } /** * 熔断开启 关闭保护方法的调用 * @return */ public boolean isOpen(){ return state instanceof OpenCircuitBreakerState; } /** * 熔断关闭 保护方法正常执行 * @return */ public boolean isClose(){ return state instanceof CloseCircuitBreakerState; } /** * 熔断半开 保护方法允许测试调用 * @return */ public boolean isHalfClose(){ return state instanceof HalfOpenCircuitBreakerState; } public void transformToCloseState(){ state = new CloseCircuitBreakerState(this); } public void transformToHalfOpenState(){ state = new HalfOpenCircuitBreakerState(this); } public void transformToOpenState(){ state = new OpenCircuitBreakerState(this); } /** * 重置失败次数 */ public void resetFailureCount() { failureCount.set(0); } /** * 重置连续成功次数 */ public void resetConsecutiveSuccessCount() { consecutiveSuccessCount.set(0); } public long getTimeout() { return timeout; } /** * 判断是否到达失败阀值 * @return */ protected boolean failureThresholdReached() { return failureCount.get() >= failureThreshold; } /** * 判断连续成功次数是否达到阀值 * @return */ protected boolean consecutiveSuccessThresholdReached(){ return consecutiveSuccessCount.get() >= consecutiveSuccessThreshold; } /** * 保护方法失败后操作 */ public void actFailed(){ state.actFailed(); } /** * 保护方法成功后操作 */ public void actSuccess(){ state.actSuccess(); } public static interface Executor { /** * 任务执行接口 * */ void execute(); } public void execute(Executor executor){ if(!isOpen()){ try{ executor.execute(); this.actSuccess(); }catch (Exception e){ this.actFailed(); logger.error("CircuitBreaker executor error", e); } }else{ logger.error("CircuitBreaker named {} is open", this.name); } } public String show(){ Map<String, Object> map = new HashMap<>(); map.put("name:",name); map.put("state", isClose()); map.put("failureThreshold:",failureThreshold); map.put("failureCount:",failureCount); map.put("consecutiveSuccessThreshold:",consecutiveSuccessThreshold); map.put("consecutiveSuccessCount:",consecutiveSuccessCount); map.put("successCount:",successCount); map.put("timeout:",timeout); map.put("state class",state.getClass()); return JSONObject.toJSONString(map); } } 

状态机:


 
public interface CircuitBreakerState { /** * 保护方法失败后操作 */ void actFailed(); /** * 保护方法成功后操作 */ void actSuccess(); } public abstract class AbstractCircuitBreakerState implements CircuitBreakerState{ protected CircuitBreaker circuitBreaker; public AbstractCircuitBreakerState(CircuitBreaker circuitBreaker) { this.circuitBreaker = circuitBreaker; } @Override public void actFailed() { circuitBreaker.increaseFailureCount(); } @Override public void actSuccess() { circuitBreaker.increaseSuccessCount(); } } public class CloseCircuitBreakerState extends AbstractCircuitBreakerState{ public CloseCircuitBreakerState(CircuitBreaker circuitBreaker) { super(circuitBreaker); circuitBreaker.resetFailureCount(); circuitBreaker.resetConsecutiveSuccessCount(); } @Override public void actFailed() { // 进入开启状态 if (circuitBreaker.increaseFailureCountAndThresholdReached()) { circuitBreaker.transformToOpenState(); } } } public class HalfOpenCircuitBreakerState extends AbstractCircuitBreakerState{ public HalfOpenCircuitBreakerState(CircuitBreaker circuitBreaker) { super(circuitBreaker); circuitBreaker.resetConsecutiveSuccessCount(); } @Override public void actFailed() { super.actFailed(); circuitBreaker.transformToOpenState(); } @Override public void actSuccess() { super.actSuccess(); // 达到成功次数的阀值 关闭熔断 if(circuitBreaker.increaseFailureCountAndThresholdReached()){ circuitBreaker.transformToCloseState(); } } } public class OpenCircuitBreakerState extends AbstractCircuitBreakerState{ public OpenCircuitBreakerState(CircuitBreaker circuitBreaker) { super(circuitBreaker); final Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { circuitBreaker.transformToHalfOpenState(); timer.cancel(); } }, circuitBreaker.getTimeout()); } } /* @desc 熔断器工厂 集中应用中的CircuitBreaker * 注意:这里一个熔断器一旦生产,生命周期和应用一样,不会被清除 */ public class CircuitBreakerFactory { private static ConcurrentHashMap<String, CircuitBreaker> circuitBreakerMap = new ConcurrentHashMap(); public CircuitBreaker getCircuitBreaker(String name){ CircuitBreaker circuitBreaker = circuitBreakerMap.get(name); return circuitBreaker; } /** * * @param name 唯一名称 * @param failureThreshold 失败次数阀值 * @param consecutiveSuccessThreshold 时间窗内成功次数阀值 * @param timeout 时间窗 * 1,close状态时 失败次数>=failureThreshold,进入open状态 * 2,open状态时每隔timeout时间会进入halfOpen状态 * 3,halfOpen状态里需要连续成功次数达到consecutiveSuccessThreshold, * 即可进入close状态,出现失败则继续进入open状态 * @return */ public static CircuitBreaker buildCircuitBreaker(String name, int failureThreshold, int consecutiveSuccessThreshold, long timeout){ CircuitBreaker circuitBreaker = new CircuitBreaker(name, failureThreshold, consecutiveSuccessThreshold, timeout); circuitBreakerMap.put(name, circuitBreaker); return circuitBreaker; } } 

发送kafka消息时使用熔断器:


 
/** * 因日志为非业务应用核心服务,防止kafka不稳定导致影响应用状态,这里使用使用熔断机制 失败3次开启熔断,每隔20秒半开熔断,连续成功两次关闭熔断。 */ CircuitBreaker circuitBreaker = CircuitBreakerFactory.buildCircuitBreaker("KafkaAppender-c", 3, 2, 20000); public <K, V, E> boolean send(Producer<K, V> producer, ProducerRecord<K, V> record, final E event, final FailedDeliveryCallback<E> failedDeliveryCallback) { if(circuitBreaker.isNotOpen()){ try { producer.send(record, (metadata, exception) -> { if (exception != null) { circuitBreaker.actFailed(); failedDeliveryCallback.onFailedDelivery(event, exception); logger.error("kafka producer send log error",exception); }else{ circuitBreaker.actSuccess(); } }); return true; } catch (KafkaException e) { circuitBreaker.actFailed(); failedDeliveryCallback.onFailedDelivery(event, e); logger.error("kafka send log error",e); return false; } }else{ logger.error("kafka log circuitBreaker open"); return false; } } 

总结

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

1,elk搭建时需特别注意各个版本的兼容,kafka client的版本需和kafka版本保持一致

2,方案容许kafka日志失败,而本地日志更加可靠,所以用熔断器方案,以应对万一。也可用于对其他第三方请求时使用。

spring-microservices:具有Eureka Discovery,Zuul代理,OAuth2安全,Hystrix CircuitBreaker,Sleuth Zipkin,ELK Stack Logging,Kafka,Docker和许多新功能的Spring Cloud Micro Services Spring微服务展示 是一种体系结构样式,将应用程序构造为一组松散耦合的服务,其中每个服务都实现业务功能。 微服务在其自己的进程中运行,并通过HTTP API传达其他服务。 每个微服务都可以独立于应用程序中的其他服务进行部署,升级,扩展和重新启动。 它可以连续交付/部署大型,复杂的应用程序。 它可以更好地隔离组件,并具有较高的抵御组件故障的能力。 微服务中的较小组件可以轻松扩展,以满足对特定组件不断增长的需求。 它提高了开发人员的独立性,并允许跨多个较小的团队进行并行开发。 由于开发人员必须减轻容错能力,网络延迟并处理负载平衡,因此微服务带来了额外的复杂性。 而且,这种分布式系统的部署和测试是复杂且乏味的。 只需很少的配置就可以生成独立的,基于生产级的基于Spring的应用程序,因此广泛用于微服务领域。 它使用Spring的标准配置进行了预配置,并具有嵌入式Tomcat或Jetty,以提供完整的服务器功能。 框架针对构建分布式系统时遇到的一些常见模式提供了工具和解决方案的集合。 它解决了分布式系统中一些常见问题的解决方案,包括配置管理,服务发现,断路器和分布式会话。 是一个开放平台 立即下载

相关推荐

告别游戏卡关!用Cheat Engine 7.5修改单机游戏数据的保姆级教程(附Tutorial靶场练习)

本文提供了一份详细的Cheat Engine 7.5使用教程,帮助玩家轻松修改单机游戏数据。从基础操作到高级技巧,包括精确值扫描、模糊扫描、指针扫描和代码注入,教程还涵盖了《植物大战僵尸》和《星露谷物语》的实战案例,确保玩家能够安全、高效地优化游戏体验。

weixin_30082367的博客 790

logback异步输出日志的配置方法和源码分析

1,异步输出日志的配置 logback中的异步输出日志使用了AsyncAppender这个appender 配置方式如下: logs/context-log.%d{yyyy-MM-dd}.log 30 [%-5level] %date --%thread-- [%logger] %msg %n

lkforce 2万+

手把手教你用Arduino搭建惠斯通电桥:从原理到代码实现(附完整电路图)

本文详细介绍了如何利用Arduino搭建惠斯通电桥,从硬件选型、电路设计到代码实现和校准技巧,提供完整的电路图和实用代码示例。特别适合电子爱好者和创客学习高精度电阻测量技术,并扩展应用于应变片测量和温度监测等实际项目。

o4p5q6r7s的博客 405

PlantUML画复杂流程图

1.They are implicitly linked in their definition order. @startuml :Hello world; :This is on defined on several **lines**; @enduml basic diagram activity Start/Stop 2.Conditional You ca

zhangjikuan的专栏 7万+

TT电机、310电机、370电机在智能小车与机器人中的性能对比与选型指南

本文深入对比了TT电机、310电机和370电机在智能小车与机器人项目中的核心性能差异。文章从尺寸、扭矩、转速、电压电流等关键参数入手,结合不同应用场景(如入门小车、竞技机器人、机械臂),提供了清晰的选型指南和驱动控制建议,帮助开发者根据项目需求选择最合适的电机方案。

weixin_29065015的博客 411

060、模型蒸馏:把扩散模型压进单步的魔法

《端侧文生图模型蒸馏实战:从32步到1步的优化之道》摘要:针对边缘设备部署难题,本文分享了扩散模型蒸馏的核心经验。通过重构损失函数(感知损失+对抗训练+KL散度)、优化采样轨迹截取策略和改造学生模型架构,成功将Stable Diffusion压缩至单步推理。关键发现包括:小批量训练更有效、标签平滑保障生成多样性、双模型策略平衡质量与速度。在Cortex-A53芯片上实现300ms内生成,内存占用控制在200MB以下。特别强调工业部署需权衡压缩率与稳定性,建议保留迭代模式开关以适配不同场景需求。

2601_95651806的博客 150

用java实现一个自定义基于logback的日志工具类

✅ Class对象支持: 使用LogUtil.getLogger(MyClass.class)的方式获取日志。✅ 日期格式文件: 自动生成info.%d{yyyy-MM-dd}.log格式的日志文件。✅ 动态创建: 无需配置文件,通过代码动态创建logback日志对象。✅ 启动时清理: 每次启动程序时自动清理超过保留数量的历史文件。✅ 文件数量管理: 只保留最近3个文件,自动删除历史文件。✅ 单例保证: 相同类名和目录的日志对象保证是同一个实例。

韩俊的个人博客 489

基于STM32单片机设计的智能水温控制系统

本项目研究并实现了一种基于STM32单片机的智能水温控制系统。系统以STM32F103RCT6作为主控制器,通过DS18B20温度传感器实时采集水温数据,利用PID控制算法精确调节5V直流防水型70W加热棒的功率输出,确保水温恒定在设定范围内。此外,采用OLED显示屏显示当前水温和预设上限阀值,并集成ESP8266-WiFi模块与自研Android手机APP进行远程通信和控制。用户可通过本地按键或手机APP设置温度阀值,实现实时监控与智能化管理。整个系统设计旨在提高水资源利用效率,提升生活及工业用水环境的舒

8793

聊聊LoggingEventAsyncDisruptorAppender

本文主要研究一下LoggingEventAsyncDisruptorAppender。

go4it 200

新手必看!手把手教你如何在飞牛NAS上玩转Docker容器!(飞牛Nas(fnOS)如何创建Docke容器以及Compose部署项目)

新手必看!手把手教你如何在飞牛NAS上玩转Docker容器!(飞牛Nas(fnOS)如何创建Docke容器以及Compose部署项目)

XiaoqiangClub的博客 1万+

20、自定义用户窗体:从创建到使用

本文详细介绍了在Excel VBA中创建和使用自定义用户窗体的完整过程,涵盖从窗体设计、控件添加与属性设置,到事件处理程序编写及宏调用的各个环节。通过实际示例讲解了如何实现文本大小写转换和员工信息录入等功能,并深入解析了常用控件的属性与事件机制。同时提供了控件命名规范、快捷键使用、布局技巧等实用建议,帮助读者构建高效、交互性强的VBA用户界面。

h0i1j2k3l的博客 133

java并发编程之美-阅读记录11

java并发编程实践 11.1ArrayBlockingQueue的使用   有关logback异步日志打印中的ArrayBlockingQueue的使用   1、异步日志打印模型概述     在高并发、高流量并且响应时间要求比较小的系统中同步打印日志在性能上已经满足不了了,这是以因为打印本身是需要写磁盘的,写磁盘操作会暂时阻塞调用打印日志的业务系统,这会造成调用线程的响应时间增...

weixin_30587025的博客 434

华为 MetaERP 通过元数据驱动的多组织架构 + 内部交易协同 + 自动对账引擎 + 合并规则引擎,实现多组织内部往来与合并报表的全流程自动抵消,核心是源头标准化、过程自动化、结果可追溯

摘要: 华为MetaERP通过元数据驱动的多组织架构与主数据标准化,实现内部往来与合并报表全流程自动化。其核心包括:统一组织模型与主数据(确保数据口径一致)、内部交易协同(自动生成对称凭证)、智能对账引擎(多维度自动匹配差异)、合并规则引擎(配置化抵消模板)。依托GaussDB高性能数据库与流批一体架构,华为全球88家子公司合并报表效率提升400%,关账时间缩短至2小时,支持多准则、多币种自动抵消,审计全程可追溯。相比传统ERP,MetaERP在协同性、自动化与扩展性上优势显著,为跨国集团提供高效、准确的合

金牌架构师 265

日志打入kafka改造历程-我们到底能走多远系列49

方案 日志收集的方案有很多,包括各种日志过滤清洗,分析,统计,而且看起来都很高大上。本文只描述一个打入kafka的功能。 流程:app-&gt;kafka-&gt;logstash-&gt;es-&gt;kibana 业务应用直接将日志打入kafka,然后由logstash消费,数据进入es。 另一方面,应用在服务器上会打日志文件。 如图: 详细 初步实现 首先,我们来初步实现这个方案,搭建el...

weixin_33928467的博客 305

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

普通程序员怎么理解日志系统

当我们在做系统开发时,日志系统是绕不开的话题。作为日志系统的最终使用者,我们会接触不同的日志系统,比如 log4j、 logback 和 slf4j 等等,还会接触到日志...

Docker的专栏 1432

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

jpa的批量修改_JPA 批量新增

Shell scripts to Create a local dir base on the time&period;#!/bin/bash DATETIME=`date +%Y%m%d%H%M%S` echo "datetime = $DATETIME" mkdir $DATETIME # cd ...VTK初学一,动画加AVI录制终于做出来了#ifndef INITIAL_OPENG...

weixin_31448343的博客 608

java技术专家面试指南100问java学习+面试宝典】(八)

内存屏障,也叫内存栅栏,是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。LoadLoad屏障:对于这样的语句Load1;LoadLoad;Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。StoreStore屏障:对于这样的语句Store1;StoreStore;Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。LoadStore屏障:对于这样的语句Load1;LoadStore;

fengbeely的博客 826

20210617

一、filebeat连kafka报错:circuit breaker is open 现象:疯狂重复下列打印 2021-06-17T09:10:56.052Z INFO [publisher] pipeline/retry.go:219 retryer: send unwait signal to consumer 2021-06-17T09:10:56.052Z INFO [publisher] pipeline/retry.go:223 done 在filebeat的yml文件中添加下列选项打开调

deng942861的博客 4025

filebeat配置output到kafka启动失败

打算构建全链路日志系统,由filebeat采集数据,投递到kafka,再由logstash接收后传输到es中,但在配置filebeat的output如下时,启动错误了 报错信息如下:   原因是因为: filebeat的kafka版本库里没有改0.10.1.1的版本,把版本号修改成0.8.2.0即可...

码农的世界你不懂 4610

Fabric多机kafka部署(docker方式)

摘要Fabric基于docker方式、多机kafka配置部署详细过程。 基于docker方式的多机kafka的Fabric网络搭建步骤如下。 分布式节点规划: node1: kafka1 zookeeper1 orderer1 node2: kafka2 zookeeper2 orderer2 peer1 nod...

Harry Wong的博客 892

fabric部署kafka集群后创建通道无法连接处理过程记录

fabric框架繁杂的部署过程,会出现各种意料之外的错误,由于错误提示不明确,往往很简单的一个疏忽都会浪费大量的时间去找出问题。特此记录一下浪费了两天时间的处理过程。 为了在生产环境使用区块链,需要改造之前基于solo共识机制部署的fabric框架,参考该博主(HyperLedger Fabric 1.4 kafka生产环境部署)的方法将solo共识改为kafka集群。过程一切都顺风顺水,不过将其中的三个zookeeper改成了两个(后来才发现zookeeper集群数量有问题),在一切都安装好之后开始在pe

tiwolf的博客 1万+

springboot整合logback,自定义发送日志到kafka

项目动态获取kafka地址,还有对kafka是否在线ping 这个是通过获取log的日志输出,然后发送到kafka里。部署了一套ELK,logstash负责监听kafka,有消息直接写入到es里 首先项目的maven依赖s <!-- logback日志appender到kafka --> <dependency> <groupI...

bpqdwo的博客 1885

日志框架Logback核心组件appender

日志框架Logback核心组件appender 一、 logback核心 二、Appenders and Layouts 三、appender组件核心 日志框架Logback核心组件appender 我们知道logback是由log4j创始人Ceki Gülcü设计的又一个日志开源组件,logack分为三个模块logback-core、logback-classic、log...

寻路人在人间的博客 2049

CircuitBreakerOpenException断路器打开报错的解决方案

CircuitBreakerOpenException异常是指当断路器(Circuit Breaker)处于打开状态时,尝试通过断路器调用下游服务所抛出的异常。断路器模式是一种容错设计模式,用于防止一个服务的失败导致整个系统的崩溃。当断路器检测到下游服务连续失败达到一定阈值时,它会自动切断请求,即进入“打开”状态,以避免对下游服务造成更大压力。

2301_79779756的博客 735
上一篇: 如何优雅的使用RabbitMQ
下一篇: 分布式之数据库和缓存双写一致性方案解析
javaQQ群 866865133
博客等级 码龄8年 59粉丝 309原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值