首页 文章 精选 留言 我的

精选列表

搜索[成长路径],共10009篇文章
优秀的个人博客,低调大师

🔥 神级成长性!Solon v2.9 发布

Solon 框架! Java “纯血国产”应用开发框架。开放原子开源基金会,孵化项目。从零开始构建(非 java-ee 架构),有灵活的接口规范与开放生态。 追求: 更快、更小、更简单 提倡: 克制、简洁、高效、开放、生态 有什么特点? 特点 描述 更高的计算性价比 并发高 300%;内存省 50% 更快的开发效率 代码少;入门快;调试重启快 10 倍 更好的生产与部署体验 打包小 90% 更大的兼容范围 非 java-ee 架构;同时支持 java8 ~ java22,graalvm native image 入门探索视频(用户录制): 最近更新了什么? 新增 solon.boot.vertx 插件 新增 solon.cloud.gateway 插件 新增 solon.rx 插件 添加 solon.data 配置节solon.dataSources(用于自动构建数据源),支持 ENC 加密符 添加 solon.docs 配置节solon.docs(用于自动构建文档摘要) 添加 solon.view.prefix 配置项支持 "file:" 前缀(支持体外目录) 添加 solon.scheduling.simple SimpleScheduler::isStarted 方法 添加 solon@Condition(onBean, onBeanName)条件属性 添加 solon.validation ValidUtils 工具类 添加 solon LifecycleBean:postStart 方法 添加 solon MethodInterceptor 接口,替代 Interceptor(旧接口保留) 添加 solon.net.httputils 扩展机制,并与 solon.cloud 自动整合 添加 solon.net.httputils HttpResponse::headerNames 方法 添加 solon.cloud CloudDiscoveryService:findServices 方法 添加 solonsolon.plugin.exclude应用属性配置 添加 solonsolon.app.enabled应用属性配置(Solon.cfg().appEnabled()可获取) 添加 solon${.url}应用属性配置本级引用 添加 solon--cfg启动参数支持(便于内嵌场景开发) 添加 托管类构造参数注入支持(对 kotlin 更友好) 调整 solon.cloud.httputils 标为弃用,由 solon.net.httputils 替代 调整 smarthttp,jetty,undertow 的非标准方法的 FormUrlencoded 预处理时机 调整 solon.auth maven 包更名为 solon.security.auth (原 maven 包保留) 调整 solon.validation maven 包更名为 solon.security.validation (原 maven 包保留) 调整 solon.vault maven 包更名为 solon.security.vault (原 maven 包保留) 优化 AppContext::beanMake 保持与 beanSacn 相同的类处理 优化 solon.serialization.jackson 兼容 @JsonFormat 注解时间格式和时间格式配置并存 优化 solon Context::body 的兼容性,避免不可读情况 优化 solon 调试模式与 gradle 的兼容性 优化 solon.boot FormUrlencodedUtils 预处理把 post 排外 优化 solon.web.rx 允许多次渲染输出 优化 kafka-solon-cloud-plugin 添加 username, password 简化配置支持(简化有账号的连接体验) 优化 solon.boot 413 状态处理 优化 solon.boot.smarthttp 适配的 maxRequestSize 设置(取 fileSize 和 bodySize 的大值) 优化 solon AppContext 注册和查找时以 rawClz 为主(避免以接口注册时,实例类型查不到) 优化 solon.mvc kotlin data class 带默认值的注入支持(表单模式下) 优化 solon PathAnalyzer 添加 addStarts 参数选择,支持域名匹配 优化 solon LifecycleBean 和 Lifecycle 设计 修复 solon.view.thymeleaf 模板不存在时没有输出 500 的问题 修复 solon.serialization.jackson 泛型注入失效的问题 修复 solon.boot.smarthttp 适配在 chunked 下不能读取 body string 的问题 修复 solon-openapi2-knife4j 没有配置时不能启动的问题(默认改为不启用) wood 升为 1.3.0 snack3 升为 3.2.109 socket.d 升为 2.5.11 zookeeper 升为 3.9.2 dromara-plugins 升为 0.1.2 kafka_2.13 升为 3.8.0 beetlsql 升为 3.30.10-RELEASE beetl 升为 3.17.0.RELEASE mybatis 升为 3.5.16 mybatis-flex 升为 1.9.6 sqltoy 升为 5.6.20 dbvisitor 升为 5.4.3 bean-searcher 升为 4.3.0 liteflow 升为 2.12.2 aws.s3 升为 1.12.769 powerjob 升为 5.1.0 netty 升为 4.1.112.Final reactor-core 升为 3.6.9 reactor-netty-http 升为 1.1.22 vertx 升为 4.5.9 undertow 升为 2.2.34.Final jetty 升为 9.4.55.v20240627 smarthttp 升为 1.5.9 项目仓库地址? gitee:https://gitee.com/opensolon/solon github:https://github.com/opensolon/solon 官网? https://solon.noear.org

优秀的个人博客,低调大师

Kafka成长3:Producer 元数据拉取源码原理(上)

上一节我们分析了Producer的核心组件,我们得到了一张关键的组件图。你还记得么? 简单概括下上面的图就是: 创建了Metadata组件,内部通过Cluster维护元数据 初始化了发送消息的内存缓冲器RecordAccumulator 创建了NetworkClient,内部最重要的是创建了NIO的Selector组件 启动了一个Sender线程,Sender引用了上面的所有组件,开始执行run方法。 图的最下方可以看到,上一节截止到了run方法的执行,这一节我们首先会看看run方法核心脉络做了什么。接着分析下Producer第一个核心流程:元数据拉取的源码原理。 让我们开始吧! Sender的run方法在做什么? 这一节我们就继续分析下,sender的run方法开始执行会做什么。 public void run() { log.debug("Starting Kafka producer I/O thread."); // main loop, runs until close is called while (running) { try { run(time.milliseconds()); } catch (Exception e) { log.error("Uncaught error in kafka producer I/O thread: ", e); } } log.debug("Beginning shutdown of Kafka producer I/O thread, sending remaining records."); // okay we stopped accepting requests but there may still be // requests in the accumulator or waiting for acknowledgment, // wait until these are completed. while (!forceClose && (this.accumulator.hasUnsent() || this.client.inFlightRequestCount() > 0)) { try { run(time.milliseconds()); } catch (Exception e) { log.error("Uncaught error in kafka producer I/O thread: ", e); } } if (forceClose) { // We need to fail all the incomplete batches and wake up the threads waiting on // the futures. this.accumulator.abortIncompleteBatches(); } try { this.client.close(); } catch (Exception e) { log.error("Failed to close network client", e); } log.debug("Shutdown of Kafka producer I/O thread has completed."); } 这个run方法的核心脉络很简单。主要就是2个while循环+线程的close,而2个while循环,他们都调用了run(long time)的这个方法。 通过注释你可以看到,第二个while是处理特殊情况的,当第一个while退出后,还有未发送的请求,需要第二个while循环处理完成,才会关闭线程。 整体脉络如下图所示: 接着其实就该看下run方法主要在干什么了? /** * Run a single iteration of sending * * @param now * The current POSIX time in milliseconds */ void run(long now) { Cluster cluster = metadata.fetch(); // get the list of partitions with data ready to send RecordAccumulator.ReadyCheckResult result = this.accumulator.ready(cluster, now); // if there are any partitions whose leaders are not known yet, force metadata update if (result.unknownLeadersExist) this.metadata.requestUpdate(); // remove any nodes we aren't ready to send to Iterator<Node> iter = result.readyNodes.iterator(); long notReadyTimeout = Long.MAX_VALUE; while (iter.hasNext()) { Node node = iter.next(); if (!this.client.ready(node, now)) { iter.remove(); notReadyTimeout = Math.min(notReadyTimeout, this.client.connectionDelay(node, now)); } } // create produce requests Map<Integer, List<RecordBatch>> batches = this.accumulator.drain(cluster, result.readyNodes, this.maxRequestSize, now); if (guaranteeMessageOrder) { // Mute all the partitions drained for (List<RecordBatch> batchList : batches.values()) { for (RecordBatch batch : batchList) this.accumulator.mutePartition(batch.topicPartition); } } List<RecordBatch> expiredBatches = this.accumulator.abortExpiredBatches(this.requestTimeout, now); // update sensors for (RecordBatch expiredBatch : expiredBatches) this.sensors.recordErrors(expiredBatch.topicPartition.topic(), expiredBatch.recordCount); sensors.updateProduceRequestMetrics(batches); List<ClientRequest> requests = createProduceRequests(batches, now); // If we have any nodes that are ready to send + have sendable data, poll with 0 timeout so this can immediately // loop and try sending more data. Otherwise, the timeout is determined by nodes that have partitions with data // that isn't yet sendable (e.g. lingering, backing off). Note that this specifically does not include nodes // with sendable data that aren't ready to send since they would cause busy looping. long pollTimeout = Math.min(result.nextReadyCheckDelayMs, notReadyTimeout); if (result.readyNodes.size() > 0) { log.trace("Nodes with data ready to send: {}", result.readyNodes); log.trace("Created {} produce requests: {}", requests.size(), requests); pollTimeout = 0; } for (ClientRequest request : requests) client.send(request, now); // if some partitions are already ready to be sent, the select time would be 0; // otherwise if some partition already has some data accumulated but not ready yet, // the select time will be the time difference between now and its linger expiry time; // otherwise the select time will be the time difference between now and the metadata expiry time; this.client.poll(pollTimeout, now); } 上面的代码,你如果第一次看,你肯定会觉得,这个脉络非常不清晰,不知道重点在哪里。不过还好有些注释,你能大体猜到他在干嘛。 accumulator的ready,networkclient的ready、networkclient的send、networkclient的poll 这些好像是在准备内存区域、准备网络连接的node节点、发送数据、拉取响应结果的意思。 可是如果你猜不到,该怎么办呢? 这时候就可以祭出debug这个杀器了。由于是producer,我们可以在Hellowolrd的这个客户端打断点,一步一步看下。 当你对run方法一步一步打了断点之后你会发现: accumulator的ready,networkclient的ready、networkclient的send 这些的逻辑几乎都没有执行,全部都是初始化空对象,或者方法内部直接return。 直接一路执行到了client.poll方法。如下图所示: 那么,你可以得出一个结论,while第一次循环这个run方法的核心逻辑,其实只有一句话: client.poll(pollTimeout, now) 整体脉络如下所示: 看来接下来,这个NetworkClient的poll方法,就是关键中的关键了: /** * Do actual reads and writes to sockets. * 对套接字进行实际读取和写入 * * @param timeout The maximum amount of time to wait (in ms) for responses if there are none immediately, * must be non-negative. The actual timeout will be the minimum of timeout, request timeout and * metadata timeout * @param now The current time in milliseconds * @return The list of responses received */ @Override public List<ClientResponse> poll(long timeout, long now) { long metadataTimeout = metadataUpdater.maybeUpdate(now); try { this.selector.poll(Utils.min(timeout, metadataTimeout, requestTimeoutMs)); } catch (IOException e) { log.error("Unexpected error during I/O", e); } // process completed actions long updatedNow = this.time.milliseconds(); List<ClientResponse> responses = new ArrayList<>(); handleCompletedSends(responses, updatedNow); handleCompletedReceives(responses, updatedNow); handleDisconnections(responses, updatedNow); handleConnections(); handleTimedOutRequests(responses, updatedNow); // invoke callbacks for (ClientResponse response : responses) { if (response.request().hasCallback()) { try { response.request().callback().onComplete(response); } catch (Exception e) { log.error("Uncaught error in request completion:", e); } } } return responses; } 这个方法的脉络就清晰多了,通过方法名和注释,我们几乎可以猜出他的一些作用主要有: 1)注释说:对套接字进行实际读取和写入 2)metadataUpdater.maybeUpdate(),你还记得NetworkClient的组件DefaultMetadataUpdater么,方法名意思是可能进行元数据更新。这个好像很关键的样子 3)接着执行了Selector的poll方法,这个是NetworkClient的另一个组件Selector,还记得么?它底层封装了原生的NIO Selector。这个方法应该也比较关键。 4)后续对response执行了一系列的方法,从名字上看, handleCompletedSends 处理完成发送的请求、handleCompletedReceives处理完成接受的请求、handleDisconnections处理断开连接的请求、handleConnections处理连接成功的请求、处理超时的请求handleTimedOutRequests。根据不同情况有不同的处理。 5)最后还有一个response的相关的回调处理,如果注册了回调函数,会执行下。这个应该不是很关键的逻辑 也就是简单的说就是NetworkClient执行poll方法,主要通过selector处理请求的读取和写入,对响应结果做不同的处理而已。 如下图所示: 到这里其实我们基本摸清出了run方法主要在做的一件事情了,由于是第一次循环,之前的accumulator的ready,networkclient的ready、networkclient的send 什么都没做,第一次while循环run方法核心执行的是networkclient.poll方法。而poll方法的主要逻辑就是上面图中所示的了。 maybeUpdate可能在在拉取元数据? 刚才我们分析到,poll方法首先执行的是DefaultMetadataUpdater的maybeUpdate方法,它是可能更新的意思。我们来一起看下他的逻辑吧。 public long maybeUpdate(long now) { // should we update our metadata? long timeToNextMetadataUpdate = metadata.timeToNextUpdate(now); long timeToNextReconnectAttempt = Math.max(this.lastNoNodeAvailableMs + metadata.refreshBackoff() - now, 0); long waitForMetadataFetch = this.metadataFetchInProgress ? Integer.MAX_VALUE : 0; // if there is no node available to connect, back off refreshing metadata long metadataTimeout = Math.max(Math.max(timeToNextMetadataUpdate, timeToNextReconnectAttempt), waitForMetadataFetch); if (metadataTimeout == 0) { // Beware that the behavior of this method and the computation of timeouts for poll() are // highly dependent on the behavior of leastLoadedNode. Node node = leastLoadedNode(now); maybeUpdate(now, node); } return metadataTimeout; } /** * The next time to update the cluster info is the maximum of the time the current info will expire and the time the * current info can be updated (i.e. backoff time has elapsed); If an update has been request then the expiry time * is now */ public synchronized long timeToNextUpdate(long nowMs) { long timeToExpire = needUpdate ? 0 : Math.max(this.lastSuccessfulRefreshMs + this.metadataExpireMs - nowMs, 0); long timeToAllowUpdate = this.lastRefreshMs + this.refreshBackoffMs - nowMs; return Math.max(timeToExpire, timeToAllowUpdate); } 原来这里有一个时间的判断,当判断满足才会执行maybeUpdate。 这个时间计算好像比较复杂,但是大体可以看出来,metadataTimeout是根据三个时间综合判断出来的,如果是0才会执行真正的maybeUpdate()。 像这种时候,我们可以直接在metadataTimeout这里打一个断点,看下它的值是如何计算的,比如下图: 你会发现,当第一次执行while循环,执行到poll方法,执行到这个maybeUpdate的时候,决定metadataTimeout的3个值,有两个是0,其中一个是非0,是一个299720的值。最终导致metadataTimeout也是非0,是299720。 也就是说,第一次while循环不会执行maybeUpdate的任何逻辑。 那么接着向下执行 Selector的poll()方法。 /** * Do whatever I/O can be done on each connection without blocking. This includes completing connections, completing * disconnections, initiating new sends, or making progress on in-progress sends or receives. * 在不阻塞的情况下,在每个连接上做任何可以做的 I/O。这包括完成连接完成、断开连接,启动新的发送,或在进行中的发送或接收请求 */ @Override public void poll(long timeout) throws IOException { if (timeout < 0) throw new IllegalArgumentException("timeout should be >= 0"); clear(); if (hasStagedReceives() || !immediatelyConnectedKeys.isEmpty()) timeout = 0; /* check ready keys */ long startSelect = time.nanoseconds(); //这个方法是NIO底层Selector.select(),会阻塞监听 int readyKeys = select(timeout); long endSelect = time.nanoseconds(); currentTimeNanos = endSelect; this.sensors.selectTime.record(endSelect - startSelect, time.milliseconds()); //如果监听到有操作的SelectionKeys,也就是readyKeys>0< 会执行一些操作 if (readyKeys > 0 || !immediatelyConnectedKeys.isEmpty()) { pollSelectionKeys(this.nioSelector.selectedKeys(), false); pollSelectionKeys(immediatelyConnectedKeys, true); } addToCompletedReceives(); long endIo = time.nanoseconds(); this.sensors.ioTime.record(endIo - endSelect, time.milliseconds()); maybeCloseOldestConnection(); } private int select(long ms) throws IOException { if (ms < 0L) throw new IllegalArgumentException("timeout should be >= 0"); if (ms == 0L) return this.nioSelector.selectNow(); else return this.nioSelector.select(ms); } 上面的脉络主要是2步: 1)select(timeout): NIO底层selector.select(),会阻塞监听 2)pollSelectionKeys(): 监听到有操作的SelectionKeys,做了一些操作 也就是说,最终,Sender线程的run方法,第一次while循环执行poll方法,最后什么都没干,会被selector.select()阻塞住。 如下图所示: new KafkaProducer之后 分析完了run方法的执行 ,我们分析的KafkaProducerHelloWorld第一步new KafkaProducer()基本就完成了。 大家经历了一节半的时间,终于分析清楚了KafkaProducer创建的原理。不不知道你对Kafka的Producer是不是有了更深的理解了。 分析了new KafkaProducer()之后呢? 我们继续接着KafkaProducerHelloWorld往下分析,你还记得KafkaProducerHelloWorld的代码么? public class KafkaProducerHelloWorld { public static void main(String[] args) throws Exception { //配置Kafka的一些参数 Properties props = new Properties(); props.put("bootstrap.servers", "mengfanmao.org:9092"); props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); // 创建一个Producer实例 KafkaProducer<String, String> producer = new KafkaProducer<>(props); // 封装一条消息 ProducerRecord<String, String> record = new ProducerRecord<>( "test-topic", "test-key", "test-value"); // 同步方式发送消息,会阻塞在这里,直到发送完成 // producer.send(record).get(); // 异步方式发送消息,不阻塞,设置一个监听回调函数即可 producer.send(record, new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { if(exception == null) { System.out.println("消息发送成功"); } else { exception.printStackTrace(); } } }); Thread.sleep(5 * 1000); // 退出producer producer.close(); } KafkaProducerHelloWorld主要就3步: 1)new KafkaProducer 这个我们已经分析完了,主要分析了配置文件的解析、各个组件是什么、有什么,还有就是刚才分析的run线程第一次循环到底执行了什么。 2) new ProducerRecord 创建待发送的消息 3) producer.send() 发送消息 首先创建待发送的消息: ProducerRecord<String, String> record = new ProducerRecord<>("test-topic", "test-key", "test-value"); public ProducerRecord(String topic, K key, V value) { this(topic, null, null, key, value); } /** * Creates a record with a specified timestamp to be sent to a specified topic and partition * 创建具有指定时间戳的记录以发送到指定主题和分区 * @param topic The topic the record will be appended to * @param partition The partition to which the record should be sent * @param timestamp The timestamp of the record * @param key The key that will be included in the record * @param value The record contents */ public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value) { if (topic == null) throw new IllegalArgumentException("Topic cannot be null"); if (timestamp != null && timestamp < 0) throw new IllegalArgumentException("Invalid timestamp " + timestamp); this.topic = topic; this.partition = partition; this.key = key; this.value = value; this.timestamp = timestamp; } 我们之前提过,Record表示了一条消息的抽象封装。这个ProducerRecord其实就表示了一条消息。 从构造函数的注释可以看出来**,ProducerRecord可以指定往哪个topic,哪一个分区partition,并且消息可以设置一个时间戳。分区和时间戳默认可以不指定** 其实看这块源码,我们主要得到的信息就是这些了,这些都比较简单。就不画图了。 发送消息时的元数据拉取触发 当Producer和Record都创建好了之后,可以用同步或者异步的方式发送消息。 // 同步方式发送消息,会阻塞在这里,直到发送完成 // producer.send(record).get(); // 异步方式发送消息,不阻塞,设置一个监听回调函数即可 producer.send(record, new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { if(exception == null) { System.out.println("消息发送成功"); } else { exception.printStackTrace(); } } }); //同步发送 @Override public Future<RecordMetadata> send(ProducerRecord<K, V> record) { return send(record, null); } //异步发送 public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) { // intercept the record, which can be potentially modified; this method does not throw exceptions ProducerRecord<K, V> interceptedRecord = this.interceptors == null ? record : this.interceptors.onSend(record); return doSend(interceptedRecord, callback); } 同步和异步的整个发送逻辑如下图所示: 从上图你会发现,但是无论同步发送还是异步底层都会调用同一个方法doSend()。区别就是有没有callBack回调函数而已,他们还都在调用前注册一些拦截器,这里我们抓大放小下,我们重点还是关注doSend方法。 doSend方法如下: /** * Implementation of asynchronously send a record to a topic. Equivalent to <code>send(record, null)</code>. * See {@link #send(ProducerRecord, Callback)} for details. */ private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) { TopicPartition tp = null; try { // first make sure the metadata for the topic is available long waitedOnMetadataMs = waitOnMetadata(record.topic(), this.maxBlockTimeMs); long remainingWaitMs = Math.max(0, this.maxBlockTimeMs - waitedOnMetadataMs); byte[] serializedKey; try { serializedKey = keySerializer.serialize(record.topic(), record.key()); } catch (ClassCastException cce) { throw new SerializationException("Can't convert key of class " + record.key().getClass().getName() + " to class " + producerConfig.getClass(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG).getName() + " specified in key.serializer"); } byte[] serializedValue; try { serializedValue = valueSerializer.serialize(record.topic(), record.value()); } catch (ClassCastException cce) { throw new SerializationException("Can't convert value of class " + record.value().getClass().getName() + " to class " + producerConfig.getClass(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG).getName() + " specified in value.serializer"); } int partition = partition(record, serializedKey, serializedValue, metadata.fetch()); int serializedSize = Records.LOG_OVERHEAD + Record.recordSize(serializedKey, serializedValue); ensureValidRecordSize(serializedSize); tp = new TopicPartition(record.topic(), partition); long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp(); log.trace("Sending record {} with callback {} to topic {} partition {}", record, callback, record.topic(), partition); // producer callback will make sure to call both 'callback' and interceptor callback Callback interceptCallback = this.interceptors == null ? callback : new InterceptorCallback<>(callback, this.interceptors, tp); RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey, serializedValue, interceptCallback, remainingWaitMs); if (result.batchIsFull || result.newBatchCreated) { log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition); this.sender.wakeup(); } return result.future; // handling exceptions and record the errors; // for API exceptions return them in the future, // for other exceptions throw directly } catch (ApiException e) { log.debug("Exception occurred during message send:", e); if (callback != null) callback.onCompletion(null, e); this.errors.record(); if (this.interceptors != null) this.interceptors.onSendError(record, tp, e); return new FutureFailure(e); } catch (InterruptedException e) { this.errors.record(); if (this.interceptors != null) this.interceptors.onSendError(record, tp, e); throw new InterruptException(e); } catch (BufferExhaustedException e) { this.errors.record(); this.metrics.sensor("buffer-exhausted-records").record(); if (this.interceptors != null) this.interceptors.onSendError(record, tp, e); throw e; } catch (KafkaException e) { this.errors.record(); if (this.interceptors != null) this.interceptors.onSendError(record, tp, e); throw e; } catch (Exception e) { // we notify interceptor about all exceptions, since onSend is called before anything else in this method if (this.interceptors != null) this.interceptors.onSendError(record, tp, e); throw e; } } 这个方法的脉络虽然比较长,但是脉络还是比较清晰,主要先执行了: 1)waitOnMetadata 应该是等待元数据拉取 2)keySerializer.serialize和valueSerializer.serialize,很明显就是将Record序列化成byte字节数组 3)通过partition进行路由分区,按照一定路由策略选择Topic下的某个分区 4)accumulator.append将消息放入缓冲器中 5)唤醒Sender线程的selector.select()的阻塞,开始处理内存缓冲器中的数据。 用图来表示如下所示: 这两节我们重点分析元数据拉取的这个场景的源码原理。 所以这里我们着重先看下步骤1 ,之后的4步我们之后会分析到的。 waitOnMetadata 如何等待元数据拉取的? 既然send的第一步是执行waitOnMetadata方法,首先看下它的代码: /** * Wait for cluster metadata including partitions for the given topic to be available. * @param topic The topic we want metadata for * @param maxWaitMs The maximum time in ms for waiting on the metadata * @return The amount of time we waited in ms */ private long waitOnMetadata(String topic, long maxWaitMs) throws InterruptedException { // add topic to metadata topic list if it is not there already. if (!this.metadata.containsTopic(topic)) this.metadata.add(topic); if (metadata.fetch().partitionsForTopic(topic) != null) return 0; long begin = time.milliseconds(); long remainingWaitMs = maxWaitMs; while (metadata.fetch().partitionsForTopic(topic) == null) { log.trace("Requesting metadata update for topic {}.", topic); int version = metadata.requestUpdate(); sender.wakeup(); metadata.awaitUpdate(version, remainingWaitMs); long elapsed = time.milliseconds() - begin; if (elapsed >= maxWaitMs) throw new TimeoutException("Failed to update metadata after " + maxWaitMs + " ms."); if (metadata.fetch().unauthorizedTopics().contains(topic)) throw new TopicAuthorizationException(topic); remainingWaitMs = maxWaitMs - elapsed; } return time.milliseconds() - begin; } /** * Get the current cluster info without blocking */ public synchronized Cluster fetch() { return this.cluster; } public synchronized int requestUpdate() { this.needUpdate = true; return this.version; } /** * Wait for metadata update until the current version is larger than the last version we know of */ public synchronized void awaitUpdate(final int lastVersion, final long maxWaitMs) throws InterruptedException { if (maxWaitMs < 0) { throw new IllegalArgumentException("Max time to wait for metadata updates should not be < 0 milli seconds"); } long begin = System.currentTimeMillis(); long remainingWaitMs = maxWaitMs; while (this.version <= lastVersion) { if (remainingWaitMs != 0) wait(remainingWaitMs); long elapsed = System.currentTimeMillis() - begin; if (elapsed >= maxWaitMs) throw new TimeoutException("Failed to update metadata after " + maxWaitMs + " ms."); remainingWaitMs = maxWaitMs - elapsed; } } 这个方法核心就是判断了是否有Cluster元数据信息,如果没有,进行了如下操作: 1)metadata.requestUpdate(); 更新了一个needUpdate标记,这个值会影响之前maybeUpdate的metadataTimeout的计算,可以让metadataTimeout为0 2)sender.wakeup();唤醒之前nioSelector.select()的阻塞,继续执行 3)metadata.awaitUpdate(version, remainingWaitMs); 主要进行了版本比较,如果不是最新版本,调用了Metadata.wait()方法(wait方法是每个Object都会有的方法,一般和notify或者notifyAll组合使用) 整个过程我直接用图给大家表示一下,如下所示: 整个图就是今天我们分析的关键结果了,**这里通过两种阻塞和唤醒机制,一个是NIO中Selector的select()和wakeUp(),一个是MetaData对象的wait()和notifyAll()机制。**所以这里要结合之前Sender线程的阻塞逻辑一起来理解。 是不是很有意思一种使用,这里没有用任何线程的join、sleep、wait、park、unpark、notify这些方法。 小结 最后我们简单小结下,这里一节我们主要分析了如下Producer的源码原理: 初始化KafkaProducer时并没有去拉取元数据,但是创建了Selector组件,启动了Sender线程,select阻塞等待请求响应。由于还没有发送任何请求,所以初始化时并没有去真正拉取元数据。 真正拉取元数据是在第一次send方法调用时,会唤醒唤醒Selector之前阻塞的select(),进入第二次while循环,从而发送拉取元数据请求,并且通过Obejct.wait的机制等待60s,等到从Broker拉取元数据成功后,才会继续执行真正的生产消息的请求,否则会报拉取元数据超时异常。 这一节我们只是看到了进行了wait如何等待元数据拉取。 而唤醒Selector的select之后应该会进入第二次while循环 那第二次while循环如何发送请求拉取元数据请求,并且在成功后notifyAll()进行唤醒操作的呢? 让我们下一节继续分析,大家敬请期待! 我们下一节见! 本文由博客一文多发平台 OpenWrite 发布!

优秀的个人博客,低调大师

阿里云:愿用技术助力中小企业成长

9月1日,十二届全国人大常委会第二十九次会议表决通过了新修订的《中华人民共和国中小企业促进法》。 这部法律分为十章,共六十一条,将于2018年1月1日起施行。 最新数据显示:中小企业占我国企业总量的90%以上,为国家提供了80%以上的就业岗位,是经济发展的重要动力,也是社会发展的稳定器。 即将实行的《中华人民共和国中小企业促进法》将部分现行的税收优惠政策上升为法律,特别提出对小型微型企业发展予以税收上大力支持。 促进法同时提出: 鼓励互联网平台向中小企业开放技术、开发、营销、推广等资源,加强资源共享与合作,为中小企业创业提供服务; 支持中小企业在研发设计、生产制造、运营管理等环节应用互联网、云计算、大数据、人工智能等现代技术手段,创新生产方式,提高生产经营效率。 阿里巴巴集团董事局主席马云曾多次公开呼吁减轻小微企业税负,阿里云也将一如既往用我

优秀的个人博客,低调大师

政府支撑智慧城市建设 楼宇对讲投身社区成长

随着智慧城市建设的推动,在互联网、移动互联网、物联网、云计算、大数据等交织影响下,越来越多的安防企业涉足智能家居。近年,在智能家居领域,创新智能硬件、软件更是层出不穷,这为智能家居产品创新带来新思潮。 一、智能家居概念及组成 智能家居是一个居住环境,是以住宅为平台安装有智能家居系统的居住环境,实施智能家居系统的过程就称为智能家居集成。 “智能家居“(SmartHome),又称智能住宅。通俗地说,它是融合了自动化控制系统、计算器网络系统和网络通讯技术于一体的网络化智能化的家居控制系统。将家中的各种设备(如音视频设备、照明系统、窗帘控制、空调控制、安防系统、数字影院系统、网络家电以及三表抄送等)通过家庭网络连接到一起。与普通家居相比,智能家居不仅具有传统的居住功能,提供舒适安全、高品位且宜人的家庭生活空间;还由原来的被动静止结构转变为具有能动智慧的工具,提供全方位的信息交互功能,帮助家庭与外部保持信息交流畅通,优化人们的生活方式,帮助人们有效安排时间,增强家居生活的安全性,甚至为各种能源消费节约资金。 智能家居系统包含的主要子系统有:家居布线系统、家庭网络系统、智能家居(中央)控制管理系统、家居照明控制系统、家庭安防系统、背景音乐系统、家庭影院与智能家居控制系统、多媒体系统、家庭环境控制系统等八大系统。其中,智能家居(中央)控制管理系统、家居照明控制系统、家庭安防系统是必备系统,家居布线系统、家庭网络系统、背景音乐系统、家庭影院与多媒体系统、家庭环境控制系统为可选系统。 智能家居是为住户提供基础的家庭安保和集中控制功能,由前端探测器,智能家庭控制器,网络信号传输系统,控制中心控制系统等组成。该系统具有很强的适应性及兼容性,具有住户室内智能报警、周界自动报警、视频监控、感应卡门禁及内部通讯等不同功能,各种功能利用智能系统网络传输部分以及系统前端家居智能控制器,在设置自己的控制器和管理软件基础上,便可完成各自的控制,同时该系统还为用户提供了对家中的电器设备多种控制方式,无论主人身居何处,都能够及时了解家中电器的使用状况以及对它们进行控制。 智能家居是智慧城市建设的重要领域之一,也是所谓惠民生的重要体现。 二、智能家居安防应用 目前,随着生活水平的不断提高,人们对于家庭安全的重视也是今非昔比,而威胁着人们生命财产安全的事故如火灾,煤气泄漏,盗窃等一系列灾祸都是极容易在户内无人的情况下发生,而家庭物联网与无线音视频监控的结合应用能极大限度的避免家庭财产遭受不必要的损失。较之传统的住房,智能家居的优势毋庸置疑。 1、卧室,客厅等重要区域安装烟雾传感器,结合无线视频终端,可及时侦测到燃烧产生的烟雾及高温并立刻发出警报信息,户主可通过手机或电脑调动室内的视频监控系统进行实时查看,排除误报的可能,进而及时有效的扼制事态的进一步扩展 2、厨房内可安置有害气体检测装置,一旦发生煤气泄露,传感装置立即发动警报,户主能第一时间通过视频接收到现场情况,在收到警报信息后可通过物联网远程启动厨房内抽油烟机进行应急工作,为进一步抢救赢得时间。 3、无线视频监控结合物联网技术的新型防盗窗对于入室盗窃应对的更加游刃有余。与传统的栅栏式防盗窗不同,窗户上罩着一层薄网,由一根根相隔5厘米的细钢丝组成,肉眼很难看出,并与小区安防无线系统监控平台连接。一旦钢丝线被大力冲击或被剪断,系统就会即时报警。从消防角度说,这一新型防盗窗也便于居民逃生和获得救助。 人们的生活离不开衣食住行,除了能增进家庭生活的安全性,智能家居还能提供高品质的生活空间。从最早的电话转接等技术发展到家具的远程控制,直到家具的自主智能行为,物联网的发展让人们在忙碌工作的同时保证了生活质量,带来了便利。 三、智能家居中楼宇对讲的“井喷”式发展 在本世纪初期,楼宇对讲产业搭乘房地产行业快速发展的“顺风车”,在市场销售中出现“井喷式”的增长态势,并迅速在珠三角、福建等地区形成楼宇对讲产业集群,国内楼宇对产业形成了两大主要阵营:“粤军”和“闽军”。“粤军”声势较大,主要集中在深圳、珠海、中山、广州四个城市;“闽军”主要集中在厦门、泉州和福州三地。 据调查,珠三角地区楼宇对讲企业的市场占有率达40%左右,主要企业有视得安罗格朗、安居宝、等品牌;而福建地区也毫不示弱,市场占有率约占35%,主要企业为立林、冠林、等品牌。高度集中的楼宇对讲集群、持续增长的楼宇对讲企业让珠三角、福建等地成为众厂商博弈的“角斗场”。 在竞争过程中,企业的经营方式发生了微妙的变化。越来越多的企业将精力放在销量、销售价格上,价格战在行业中此起彼伏,业内大多数企业的利润受到严重挤压,行业的整体利润迅速下滑。 徘徊于价格战中的不少企业,寄希望于技术升级,希望以技术为依托,走出低价竞争的“怪圈”。而此时,信息技术、互联网不断成熟,很多信息技术逐渐渗透到建筑领域,并获得了一定发展。这为数字对讲的发展奠定了坚实的技术基础。 而随着城市人口的持续增长,让无论是楼宇对讲晴雨表的房地产业或是国家的保障房建设都在不断发展,市场规模是有增无减,且近段时间“智慧城市”“智慧社区”等概念层出不穷,也大大推动了楼宇对讲产业的又一轮发展。 四、智能家居发展目标 智慧社区理念是通过实现设备的管理带动社区的管理,从而实现整个社区的运营,视频对讲、平板电脑、手机都可以充分利用起来,为大家带来更加舒适的体验。虽然现阶段应用项目并不多,不过发展下去智慧社区必将成为各大企业业务的争夺点,智慧社区有望成为智能家居企业下一个发展目标。 在可以预见的未来,楼宇对讲将会更多地增加一些智能家居的功能,将集安防、家电控制、信息服务、娱乐为一身,从而使得楼宇对讲系统发生质的改变,两者会更加紧密地融合起来。 与纯模拟系统不同,数字/模拟混合系统在单元内采用成本较低的模拟设备,主干网络则采用基于以太网的数字TCP/IP协议进行联网。数字/模拟混合系统是当前解决大型社区联网最经济、有效的方法之一。大型社区的联网拓扑结构有很大的自由度,网络调整十分方便,并且成本合理,性能稳定,维护简便,因此数字/模拟混合系统得到较快发展。 数字/模拟混合组网方式是当前最经济实用的工程解决方案,在短期内会是大型社区楼宇对讲联网系统的首选。其解决了长距离传输以及模拟联网通道阻塞限制等问题,得到较好的音视频效果,相对于纯数字系统以及模拟系统,此种方式具有明显的价格优势;并且此种网络结构便于平稳扩容,联网性能稳定。 虽然数字/模拟混合组网方式有诸多优势,但其受到整个物理平台的限制,终端设备是模拟产品,在功能以及联网方面有很难逾越的障碍,数字化社区以及与多系统的集成方面主要得依赖于TCP/IP纯数字可视对讲系统。单纯对讲潜力是有限的,关键要和三网合一融合,提供综合智能家居管理平台和数字安防系统一体化的解决方案才算一个飞跃。在这个系统中,物业公司的管理水平,民众的接受程度,网络运营商的前瞻性思维缺一不可。总体来说,在国内智能家居系统的发展与可视对讲系统的发展出现了趋同化现象。就发展趋势而言,智能家居与楼宇对讲同样由小屏幕到大屏幕,并朝着触屏控制发展,外观上要求更美观、更漂亮,安装上要求更符合家居环境,更节省空间,功能上要求更丰富。 楼宇对讲与智能家居的结合,在某种意义上来讲可以说是楼宇对讲系统从模拟到数字化发展的必然产物,对讲的数字化从根本上打开了对讲系统的发展瓶颈,使得智能家居与之结合成为可能。而对讲与智能家居系统的结合又进一步提高对讲系统的增值空间,系统的性价比也得到了提高,从而使新系统的推广应用更加广泛。未来智能家居的发展方向应是以实用、易用为主。随着智能家居配套技术的不断成熟和产品化,数字化对讲必然在其功能上实现与智能家居系统更加深入的合作,在更宽广的范围内结合,以实现更多的功能。 未来社会转型,需要信息化的增长点。经过十年的快速发展,平安城市、智慧城市的投资和应用实施已经达到一定的规模;通过社区信息化,促进整个城市的信息消费,带动地区经济发展,促进社会和谐,正成为继软件和动漫产业之后,各地政府致力扶持的下一个重点。 传统的智慧城市应用以数字电力、数字城管等政府应用为主,与居民生活关联不大,智慧城市的应用如何真正落地,如何真正为城市居民生活服务,正是行业萌发突破的下一个焦点。 楼宇对讲系统由于自身独特的安防属性,具备一定的用户粘性,同时深入住户家中,24小时开机,占据客厅的主要位置,占据社区和家庭的出入口。在承载智慧社区和智慧城市的落地应用方面,具有天然的优势。 智慧城市当中的智慧交通等应用与居民生活息息相关。随着城市交通越来越拥挤,公交出行正成为政府广泛提倡的交通方式。智慧公交查询与住宅电梯的联动,正是智慧城市和智慧社区应用落地的优秀案例之一。 未来的信息化楼宇对讲系统将会从单一的对讲功能,升级成智慧社区的有效组成部分,功能和应用范围将得到极大的延伸和扩展,也将行业带来新的增长机会。 五、智能家居领域安防的发展机遇与挑战 智能家居市场的潜力将日益增长。但与此同时另一方面,受智能家居产业结构复杂、价值链长、产业技术成熟度低等诸多客观因素,智能家居同样也面临着行业管辖权之争,技术缺陷、缺乏资金支持等等一系列现实问题。综上所述,中国智能家居产业实际情况面临着巨大的机遇与挑战。 国内安防企业唯有广泛参与到小区及可视对讲、家庭监控、家庭防盗报道等家庭有关的小区一卡通等诸多领域建设,建立一套完整的安防系统,包括报警系统、视频监控和可视对讲,及众多合理的分支系统,来供应消费者的不同选择需求。如选择厨房安全警报和周界防范系统,尤其是针对别墅区周界最隐蔽,最实用的安防系统等。同时国内安防企业也要重视消费者和政府节能减排的态势,坚持节能环保方向,在智能家居还处于不明朗的竞争环境下,结合智能家居相关各行业、企业相关管理部门都参与进来,共同协商建立出统一规范的行业标准,进而使用得智能家居发展方向更加明确清晰。以上这些要点的关注,都可以让国内众多安防企业在智能家居行业尚未明朗的态势下,争取到一体化和多赢的发展,同时这些也是国内安防企业努力的方向。 如今,随着智慧城市建设的不断推进,智能家居也将越来越受到人们的关注与欢迎。虽然智能家居在国内的发展时间并不长,但是市场潜力巨大,而安防设备又是智能安居的重要组成部分,这也让国内不少优秀安防企业对这块市场表现出趋之若鹜的态度,众多安防企业都积极融入智能家居一体化发展的队伍中。面对智能家居给安防行业带来的发展机遇与挑战,让我们共同期待中国安防企业能激流勇进,取得最终的胜利! 2014年3月中共中央、国务院出台了《国家新型城镇化规划》,将智慧城市作为城市发展的全新模式,要求大力推进智慧城市建设。根据工信部电信研究院预测,“十二五”期间智慧城市建设总投资或将超过1.6万亿元,巨大的市场需求打开了智慧城市发展空间,许多公司纷纷转型进入智慧城市建设领域。 一、政策给力,智慧社区前景广阔 智慧城市建设得到了国家战略的有力支持。党的“十八大”报告提出,要实施创新驱动发展战略,着力构建以企业为主体、市场为导向、产学研相结合的技术创新体系。国务院发布了《“十二五”国家战略性新兴产业发展规划》,列出了七大战略性新兴产业的重点发展方向和主要任务以及20项重大工程。其中,加快建设宽带、融合、安全、泛在的下一代信息网络,突破超高速光纤与无线通信、物联网、云计算、数字虚拟、先进半导体和新型显示等新一代信息技术直接关系到安防产业的发展。 数据显示,目前,我国95%的副省级以上城市、76%的地级以上城市,总计约230多个城市提出或在建智慧城市,超过80%的城市在“十二五”期间将智慧城市作为加快经济发展转型的战略导向,物联网和第三方平台技术的应用持续深化,云平台、大数据技术将会有良好的发展环境。 这些与安防息息相关的行政法规及政策措施,为规范社会管理提供了依据,对行业发展提供了良好的法律政策环境。党和国家的鼓励政策,必将推动战略性新兴产业的发展,带动楼宇对讲产业的发展。 二、创建智慧社区:楼宇对讲义不容辞 过去几年,楼宇对讲行业整体稳中求进。首先是安防技术与信息产业融合继续深化,产业结构调整步伐加快,行业总体保持了较高的增长速度,行业文化正在形成。其次,智慧城市建设提速,为楼宇对讲系统的开发和市场拓展打开了更大的空间;社区、居民安防应用持续推进,为民用安防业务的扩展创造了条件。三是楼宇对讲企业持续发展。随着产业链不断完善、产业集中化程度有所提升,一批领军企业崛起,国际知名度有所提高。对讲企业在自身发展方式转变的同时,在各级政府管理部门领导下,参与安防产业结构的调整和升级,加大自身科技创新力度,参与行业标准化工作,为促进产业持续健康发展奠定了良好的基础。 作为智慧社区的缩影,智能家居是楼宇对讲的延伸和扩展。智能家居是一个更加广泛的系统工程,不仅包括常用的楼宇对讲、家电和灯光控制等功能,还涵盖远程监控、家居安全、报警联动、能效管理、信息服务等诸多功能子系统。对于房地产开发商而言,楼宇对讲是住宅小区一项最基本的功能,智能家居则是提升住宅价值的增值功能。 楼宇对讲企业进入智能家居领域具有先天优势。楼宇对讲进入智能家居的先天优势,在市场推广上,可视对讲厂商多年积累的工程商渠道、数量庞大的自建渠道以及在对讲“甲购”市场积累的房产商资源,无疑将成为助推智能家居市场发力的重要砝码。 楼宇对讲系统的安防属性也是其优势之一。由于深入住户家中,24小时开机,占据客厅的主要位置,占据社区和家庭的出入口,具备一定的用户粘性,在承载智慧社区和智慧城市的落地应用方面,具有天然的优势。 三、楼宇对讲如何投身智慧社区建设? (一)积极投身数字化浪潮 数字化技术是社会发展的必然趋势,社区建设也必须走数字之路。数字技术提高了信息采集、传播、处理、显示的性能,增强了安全性和抗干扰的能力。从技术方面来看,可视对讲数字化后,很好地解决了模拟系统很难解决的两个联网主要问题:长距离的音视频传输问题和联网多通道问题,并且能够以数字可视终端作为节点,实现与家居报警、家居照明系统、家电控制系统的整合。集成多种社区功能,如公告、留影/留言、电子相册、安防报警、门禁、智能家居、社区购物,社区医疗等功能和增值服务。 如今中国楼宇对讲市场正经历着从模拟向数字、从总线向TCP/IP转换的过程,在新小区的住宅楼宇可视对讲项目中,70%以上是使用基于国际标准的TCP/IP传输协议下的全数字楼宇可视对讲。随着TCP/IP全数字楼宇对讲技术日趋成熟,规模化效应会使得数字技术的成本逐步下降,实现更为广泛的应用。 (二)顺应三网融合趋势 网络通信技术从有线网络向无线网络和移动无线网络发展,特别是移动无线网络从2.5G的GPRS到3G的WCDMA、CDMA和TDSCDMA的普及,以及4G的发展,使无时和无处不在的信息通信能力大大增强,为物联网和泛在计算提供了网络基础设施。 2013年8月17日,国务院发布了“宽带中国”战略实施方案,宽带入户上升到国家战略高度,在此基础上,电信网、广播电视网、互联网的技术功能趋于一致,业务范围趋于相同,网络互联互通、资源共享,智能家居要利用好这个趋势,拓展应用范围。 (三)传感技术,打造社区物联网 物联网技术是智慧城市的主要构成。智能芯片和传感器将会有不可想象的应用空间,实现信息多源实时获取和智能控制的泛在计算阶段,使智能环境和周边计算得到发展。物联网的发展从根本上提高了信息采集能力,也将促进以人为中心的周边环境智能化。 (四)集成社区功能,实现持续发展 智慧社区应该是一个完善的生态系统,不仅包含报警、家居控制、监控、电梯等的联动控制,另一方面还要接入周边环境信息,将社区中原本各自离散的功能集成起来,实现资源共享对用户的生活带来便利,比如物业信息、生活服务、购物、水电缴费、医疗服务、教育信息、家政服务、居委会。除此之外,理想中的智慧社区,要注重环保节能,实现资源可持续利用,满足时下需要的同时,也要对子孙后代不能有所损害,实现人类居住环境的可持续发展目标。 四、结束语 智慧社区作为智慧城市的一部分,必将在广泛关注下得到快速发展,而楼宇对讲作为智慧社区的重要组成部分,应当顺势而为,借助发展的东风不断壮大 本文转自d1net(转载)

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册