这篇文章介绍Kafka的Broker工作流程,包括其中控制器的选举过程;kafka副本的leader选举以及leader和follower故障流程;简单讲述了生产环境中如何调整分区副本;kafka的文件存储机制以及日志文件的删除策略;最后了解下kafka中使用的页缓冲和零拷贝的原理。
这一部分大体了解下kafka Broker的工作流程,看一下zookeeper在kafka broker工作中发挥的作用,那些重要数据在zookeeper中存储。
zookeeper在kafka中扮演了重要的角色,kafka使用zookeeper进行元数据管理,保存broker注册信息,包括主题(Topic)、分区(Partition)信息等,选择分区leader,在低版本kafka消费者的offset信息也会保存在zookeeper中。如图使用zookeeper客户端PrettyZoo查看内容如下:

下面具体看一下某些重要节点的作用。
Broker节点
kafka在zookeeper上存储的brokers节点信息如下:

其中/brokers/ids/[0...n]:是使用临时节点存储在线的是各个服务节点的信息,当下线后自动删除;
{
"listener_security_protocol_map" : {
"PLAINTEXT" : "PLAINTEXT"
},
"endpoints" : [ "PLAINTEXT://192.168.31.32:9091" ],
"jmx_port" : -1,
"features" : { },
"host" : "192.168.31.32",
"timestamp" : "1646557610504",
"port" : 9091,
"version" : 5
}
/brokers/seqid:辅助生成的brokerId,当用户没有配置broker.id时,ZK会自动生成一个全局唯一的id。
/brokers/topics/{topicName}:持久化数据节点存储topic的分区副本分配信息。在/brokers/topics/{topicName}//partitions/0/state中记录了leader和isr队列的内容。
{
"controller_epoch" : 5,
"leader" : 3,
"version" : 1,
"leader_epoch" : 4,
"isr" : [ 2, 3 ]
}
其中_transaction_offsets,是事务存储的节点。
Consumers节点
0.9版本之前用于保存offset信息,0.9版本之后offset存储在kafka主题中。
Controller和/Controller_epoch节点
/controller:保存控制器(broker的leader, 这里的leader要和副本的leader区分开,这里的leader是kafka集群中所有broker的leader)对应的brokerId信息等
/controller_epoch:这里用来解决脑裂问题,存放的是一个整形值(纪元编号,也称为隔离令牌)
其他的节点
/config/topics:存储动态修改主题级别的配置信息
/config/clients:存储动态修改客户端级别的配置信息
/config/changes:动态修改配置时存储相应的信息
/admin/delete_topics:在对主题进行删除操作时保存待删除主题的信息
/isr_change_notification:保存Kafka副本ISR列表发生变化时通知的相应路径
这里需要先明确一个概念leader选举,因为kafka中涉及多处选举机制,容易搞混,kafka由三个方面会涉及到选举:
本文会讲述Broker选leader和分区选leader的过程,后面将消费者的时候在说消费者选leader的过程。
在kafka集群中由很多的broker(也叫做控制器),但是他们之间需要选举出一个leader,其他的都是follower。broker的leader有很重要的作用,诸如:创建、删除主题、增加分区并分配leader分区;集群broker管理,包括新增、关闭和故障处理;分区重分配(auto.leader.rebalance.enable=true,后面会介绍);分区leader选举。
下面看一下Broker的leader选举过程和故障处理

如图可以大概描述如下:
什么是脑裂
说起脑裂,那么什么是脑裂,出字面意思上理解,就是一分为二有了多个脑袋。在分布式系统的高可用情况下很容易出现这种问题,简单来说就是因为网络或者其他的原因导致leader出现假死状态,此时会触发leader选举,这样就会出现两个leader进而产生一系列问题。
kafka broker的leader相当于整个kafka集群的master,负责很多重要的工作(上文有,此处不再累述),broker是通过抢占的方式在zookeeper中注册临时节点/controller来时实现的,先到先得。由于zookeeper的临时节点的有效性是通过session来判断的,若在session timeout时间内,controller所在的broker断开,就会触发重新选举。
那么发生脑裂又会什么影响?
从上文可以知道,broker的leader主要是用于管理主题的,那些发生脑裂之后创建主题、增加分区的操作都会报错;但是现有的主题的读写是不影响的,这是因为读写是获取分区的元数据在任意一个broker中有可以拿到。
发生脑裂的情况?
broker的leader进行GC的时间超过zookeeper session timeout;broker的leader发生网络故障。
kafka的解决方案
为了解决Controller脑裂问题,zookeeper中有一个持久节点/controller_epoch,存放的是一个整形值的epoch number(纪元编号,也称为隔离令牌),集群中每选举一次控制器,就会通过Zookeeper创建一个数值更大的epoch number,如果有broker收到比这个epoch数值小的数据,就会忽略消息。

在早期的kafka版本中,如果宕机的那个Broker上的Partition比较多, 会造成多个Watch被触发,造成集群内大量的调整,导致大量网络阻塞,这种羊群效应会导致zookeeper过载的隐患。之后kafka有一个controller的概念(也就是broker的leader)来对分区副本的状态进行管理,当某个分区的leader副本出现故障时,由控制器负责为该分区选举新的leader副本。当检测到某个分区的ISR集合发生变化时,由控制器负责通知所有broker更新其元数据信息。
在使用zookeeper的分布式中,这种脑裂和羊群效应都是不可避免的。
现阶段的kakfa集群中,只需要broker的leader在zookeeper去注册相应的监听器,其他的broker很少去监听zookeeper的数据变化,但是每个broker还是需要对/controller进行监听;当/controller节点发生数据变化的时候,每个broker都会更新自身内存中保存的activeControllerId。
当/controller节点被删除时,集群中的broker会进行选举,如果broker在节点被删除前是控制器的话,在选举前还需要有一个下线的操作(关闭相应的资源,比如关闭状态机、注销相应的监听器等)。如果有特殊需要,可以手动删除/controller节点来触发新一轮的选举。当然关闭控制器所对应的broker以及手动向/controller节点写入新的brokerid的所对应的数据同样可以触发新一轮的选举。
kafka副本的作用就是提高数据的可靠性,系统默认副本数量是1,生产环境一般配置数量是2个,保证数据可靠性;否则副本太多会增加磁盘的存储空间,增加网络上的数据传输,降低效率。
kafka的副本分为leader和follower,其中leader数据读写,follower只负责数据同步。关于副本有下面三个概念:
ISR:表示和leader保持同步的follower集合OSR:表示follower与leader同步延时过多的副本AR:分区中所有副本统称为AR(Assigned Repllicas),AR = ISR + OSR,一个分区的AR集合在分配的时候就被指定,并且只要不发生重分配的情况,集合内部副本的顺序是保持不变的,而分区的ISR集合中副本的顺序可能会改变。这里ISR在上一篇文章中也介绍了,如果follower长时间没有向leader发送通信请求或者同步数据,这个follower将会被提出ISR队列,这个时间阈值是由replica.lag.time.max.ms参数设置的,默认是30s。
如果leader发送故障,就会从ISR中选举出新的leader。
分区leader的选举由kafka的broker leader(后面文章会以controller代替broker leader的描述)负责具体实施。
当创建分区(创建主题或增加分区都有创建分区的动作)或分区上线(比如分区中原先的leader副本下线,此时分区需要选举一个新的leader上线来对外提供服务)的时候都需要leader选举。选举的时候将会从AR集合中副本的顺序查找第一个存活的副本,并且要保证这个副本在ISR队列中。
另外当分区发生重分配的情况(下面会讲)也是需要执行leader选举,此时从重分配的AR列表中找到第一个存活的副本,且这个副本在目前的ISR队列中。
再有就是当某一个borker节点关闭的时候,位于这个节点上的leader副本都会下线,所以与此对应的分区需要执行leader的选举。此时将会从AR列表中找到第一个存活的副本,且这个副本在目前的ISR列表中,另外还要确保这个副本不处于正在被关闭的节点上。
kafka还提供了一个参数配置:unclean.leader.election.enable,默认是true,参数规定是否允许非ISR的副本成为leader,如果设置为true,当ISR队列是空,ISR为空说明leader和follower都挂掉了,此时将选择那些不在ISR队列中的副本选择为新的leader,这写副本的消息可能远远落后于leader,所以可能会造成丢失数据的风险。生产环境中建议关闭这个参数,设置为false。
在生产环境中可能会出现follower和leader出现故障,那么Kafka是如何处理这些故障的呢?下面简单介绍一下流程,在讲流程之前,先了解一下LEO和HW这两个概念。
LEO和HW的概念产生其实是因为,数据先写入leader,然后follower拉取数据进行同步,但是同步速度不一致,会出现先后问题,那个这是后副本的offset是不一样的,此时kafka会使用所有副本中最小的offset+1,也是HW。

此时假如Broker1上的follower发生故障会出现什么情况呢?首先Broker1上的follower会被踢出ISR队列中,但是leader和其他的follower都还是会继续接受数据,并不会收到影响,对应的LEO和HW都会往后移动;如果此时发生故障的Broker1上的follower恢复后,此时Broker1上的follower会读取本地磁盘记录的上次HW位置,并将log文件中高于HW的部分截取掉,从HW开始向Leader进行同步;直到Broker1上的follower的LEO大于等于该分区的HW,此时说明这个follower追上了leader,就会将其重新加入ISR队列中。

上面了解了follower故障的情况,那么如果leader发生故障呢?接着上面的图片来看,首先如果Broker0上的leader发生故障之后,也是一样会先从ISR队列中被踢出,然后从ISR中选出一个新的Leader来;此时为了保证多个副本之间的数据一致性,其他的follower会先将各自的log文件中高于HW的部分截取掉,然后从新的leader同步数据(由此可知这只能保证副本之间数据一致性,并不能保证数据不丢失或者不重复)。

从上一篇文章知道在kafka集群中分区的副本分布是做到尽量的均匀的分配的到各个节点中,以此来保证每台机器的读写吞吐量是均匀的,但是出现某些broker宕机,会导致leader都集中在几台broker中,造成读写压力过大,并且就算恢复了宕机的broker,原来的leader也会变成follower并无法分担压力,造成集群负载不均衡。
为了解决上述问题kafka出现了自动平衡的机制。kafka提供了下面几个参数进行控制:
auto.leader.rebalance.enable:自动leader parition平衡,默认是trueleader.imbalance.per.broker.percentage:每个broker允许的不平衡的leader的比率,默认是10%,如果超过这个值,控制器将会触发leader的平衡leader.imbalance.check.interval.seconds:检查leader负载是否平衡的时间间隔,默认是300秒但是在生产环境中是不开启这个自动平衡,因为触发leader partition的自动平衡会损耗性能,或者可以将触发自动平和的参数leader.imbalance.per.broker.percentage的值调大点。
会导致服务器的性能不一样,服务器磁盘不足或者其他的原因需要将性能好,磁盘空间大的服务器节点多存放副本,那么在生产环境中如何去手动调整分区副本的分布比例呢?
下面先创建一个测试的主题:

下面演示一下如何更新分区间的副本配比,首先创建一个assign-replicas.json的文件,内容如下:
{
"version": 1,
"partitions": [
{"topic": "test-assign", "partition": 0, "replicas": [1, 2]},
{"topic": "test-assign", "partition": 1, "replicas": [1, 2]},
{"topic": "test-assign", "partition": 2, "replicas": [1, 2]}
]
}

接着执行命令:
bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file assign-replicas.json --execute
bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file assign-replicas.json --verify

最后看一个这个主题的副本分布情况:
bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test-assign

生产环境中由于某个主题的重要等级需要提升,考虑增加副本。下面演示下如何增加副本
创建一个Json文件:add-relication-factor.json
{
"version": 1,
"partitions": [
{"topic": "test-assign", "partition": 0, "replicas": [3, 2, 1]},
{"topic": "test-assign", "partition": 1, "replicas": [1, 3, 2]},
{"topic": "test-assign", "partition": 2, "replicas": [2, 1, 2]}
]
}
执行副本存储计划:
bin/kafka-reassign-partitions.sh --zookeeper localhost:2181 --reassignment-json-file add-relicati
on-factor.json --execute

在kafka中主题(Topic)是一个逻辑上的概念,分区(partition)是物理上的存在的。每个partition对应一个log文件,该log文件中存储的就是Producer生产的数据。Producer生产的数据会被不断追加到该log文件末端。为防止log文件过大导致数据定位效率低下,kafka采用了分片和索引机制,将每个partition分为多个segment,每个segment包括.index文件、.log文件和.timeindex等文件。这些文件位于文件夹下,该文件命名规则为:topic名称+分区号。

上图对应的是某一个主题的文件结构图,一个主题是对应多个分区,一个分区对应一个日志(Log),如果只通过一个log文件记录的话,这就会导致日志过大,导致数据定位效率低下,所以kafka采用了分片和索引机制。
kafka引入了日志分段(LogSegment),将日志分为多个较小的文件;Log会存储在配置的log.dirs文件夹内,而每个LogSegment由三个文件组成:偏移量索引文件(.index后缀)、时间戳的索引文件(.timeindex后缀)和消息数据文件(.log后缀);注意这里面还有一个leader-epoch-checkpoint文件,保存的是Leader Epoch的值(解决副本数据一致性需要)。
目录结构如下:
├── long-topic-0
├── 00000000000000000000.index
├── 00000000000000000000.log
├── 00000000000000000000.timeindex
├── 00000000000000000113.index
├── 00000000000000000113.log
├── 00000000000000000113.timeindex
└── leader-epoch-checkpoint
分段文件名规则:分区的第一个segment是从0开始的,后续每个segment文件名为上一个segment文件最后一条消息的offset,ofsset的数值最大为64位(long类型),20位数字字符长度,没有数字用0填充。
log文件默认写满1G后,会进行log rolling形成一个新的分段(segment)来记录消息,这里面的分段大小取决于:log.segment.bytes参数决定。
index和timeindex文件在刚使用时会分配10M的大小,当进行log rolling后,它会修剪为实际的大小,所以看到前几个索引文件的大小,只有几百K。
查看log文件内容的方法:
./bin/kafka-run-class.sh kafka.tools.DumpLogSegments --files 00000000000000000000.log --print-data-log
当log文件写入4k(这里可以通过log.index.interval.bytes设置)数据,就会写入一条索引信息到index文件中,这样的index索引文件就是一个稀疏索引,它并不会每条日志都建立索引信息。
log日志文件是顺序写入,大体上由message+实际offset+position组成,而索引文件的数据结构则是由相对offset(4byte)+position(4byte)组成。
当kafka查询一条offset对应实际消息时,可以通过index进行二分查找,获取最近的低位offset,然后从低位offset对应的position开始,从实际的log文件中开始往后查找对应的消息。

时间戳索引文件,它的作用是可以查询某一个时间段内的消息,它的数据结构是:时间戳(8byte)+ 相对offset(4byte),如果要使用这个索引文件,先要通过时间范围找到对应的offset,然后再去找对应的index文件找到position信息,最后在遍历log文件,这个过程也是需要用到index索引文件的。
从上面知道消息数据会被不断追加到该log文件末端,这就会面临一个问题,就是log文件越来越大,磁盘空间是一定的,那么此时kakfa可以通过配置log.cleanup.policy参数,默认是delete(删除,按照一定的保留策略直接删除不符合条件的日志分段LogSegment),另一个是compact(压缩,日志压缩就是根据key来保留最后一条消息)两种。
delete删除kafka中默认的日志保存时间为7天,可以通过调整如下参数修改保存时间。
log.retention.hours:最低优先级小时,默认7天log.retention.minutes:分钟log.retention.ms:最高优先级毫秒log.retention.check.interval.ms:负责设置检查周期,默认5分钟file.delete.delay.ms:延迟执行删除时间log.retention.bytes:当设置为-1时表示运行保留日志最大值(相当于关闭);当设置为1G时,表示日志文件最大值具体的保留日志策略有三种:
log.retention.ms > log.retention.minutes > log.retention.hours,默认只会配置log.retention.hours参数,值为168即为7天。.deleted的后缀;最后交给一个以delete-file命名的延迟任务来删除这些以.deleted为后缀的文件,默认是1分钟执行一次,可以通过file.delete.delay.ms来配置。log.retention.bytes,默认是-1,表示无穷大),来寻找可删除的日志段文件集合。baseOffset是否小于等于 logStartOffset,如果是,则可以删除此日志分段。logStartOffset,一般情况下,日志文件的起始偏移量 logStartOffset等于第一个日志分段的 baseOffset,但这并不是绝对的,logStartOffset的值可以通过 DeleteRecordsRequest请求、使用 kafka-delete-records.sh 脚本、日志的清理和截断等操作进行修改。compact压缩日志压缩对于有相同key的不同value值,只保留最后一个版本。如果应用只关心 key对应的最新 value值,则可以开启 Kafka相应的日志清理功能,Kafka会定期将相同 key的消息进行合并,只保留最新的 value值。
kafka之所以可以快速读写的原因如下:
在 Kafka 中,大量使用了 PageCache, 这也是 Kafka 能实现高吞吐的重要因素之一。
首先看一下读操作,当一个进程要去读取磁盘上的文件内容时,操作系统会先查看要读取的数据页是否缓冲在PageCache 中,如果存在则直接返回要读取的数据,这就减少了对于磁盘 I/O的 操作;但是如果没有查到,操作系统会向磁盘发起读取请求并将读取的数据页存入 PageCache 中,之后再将数据返回给进程,就和使用redis缓冲是一个道理。
接着写操作和读操作是一样的,如果一个进程需要将数据写入磁盘,操作系统会检查数据页是否在PageCache 中已经存在,如果不存在就在 PageCache中添加相应的数据页,接着将数据写入对应的数据页。另外被修改过后的数据页也就变成了脏页,操作系统会在适当时间将脏页中的数据写入磁盘,以保持数据的一致性。
零拷贝并不是不需要拷贝,而是减少不必要的拷贝次数,通常使用在IO读写过程中。常规应用程序IO过程如下图,会经过四次拷贝:
从上面的流程可以知道内核态和用户态之间的拷贝相当于执行两次无用的操作,之间切换也会花费很多资源;当数据从磁盘经过DMA 拷贝到内核缓存(页缓存)后,为了减少CPU拷贝的性能损耗,操作系统会将该内核缓存与用户层进行共享,减少一次CPU copy过程,同时用户层的读写也会直接访问该共享存储,本身由用户层到Socket缓存的数据拷贝过程也变成了从 内核到内核的CPU拷贝过程,更加的快速,这就是零拷贝,IO流程如下图。

Java的JDK NIO中方法transferTo()方法就能够实现零拷贝操作,这个实现依赖于操作系统底层的sendFile()实现的:
public void transferTo(long position, long count, WritableByteChannel target);
底层调用的是:
#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
但是需要注意零拷贝和系统底层有很大的关系,所以是否可以进行零拷贝的系统调用的看具体的操作系统是否实现。
下面看一下Java nio的零拷贝例子:
import java.io.*;
import java.nio.channels.FileChannel;
public class ZeroCopy {
public static void main(String[] args) {
File source = new File("G:/source.zip");
File target = new File("G:/target.zip");
NioZeroCopy(source, target);
}
public static void NioZeroCopy(File source, File target) {
try (
FileChannel sourceChannel = new FileInputStream(source).getChannel();
FileChannel targetChannel = new FileOutputStream(target).getChannel();
) {
for(long count = sourceChannel.size(); count > 0;) {
long transfer = sourceChannel.transferTo(sourceChannel.position(), count, targetChannel);
sourceChannel.position(sourceChannel.position() + transfer);
count -= transfer;
}
} catch (IOException e) {
System.out.println("异常:" + e.getMessage());
}
}
}
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?
文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景 最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。 在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记
文章目录认识unity打包目录结构游戏逆向流程Unity游戏攻击面可被攻击原因mono的打包建议方案锁血飞天无限金币攻击力翻倍以上统称内存挂透视自瞄压枪瞬移内购破解Unity游戏防御开发时注意数据安全接入第三方反作弊系统外挂检测思路狠人自爆实战查看目录结构用il2cppdumper例子2-森林whoishe后记认识unity打包目录结构dll一般很大,因为里面是所有的游戏功能编译成的二进制码游戏逆向流程开发人员代码被编译打包到GameAssembly.dll中使用il2ppDumper工具,并借助游戏名_Data\il2cpp_data\Metadata\global-metadata.dat
【动态规划】一、背包问题1.背包问题总结1)动规四部曲:2)递推公式总结:3)遍历顺序总结:2.01背包1)二维dp数组代码实现2)一维dp数组代码实现3.完全背包代码实现4.多重背包代码实现一、背包问题1.背包问题总结暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!背包问题是动态规划(DynamicPlanning)里的非常重要的一部分,关于几种常见的背包,其关系如下:在解决背包问题的时候,我们通常都是按照如下五部来逐步分析,把这五部都搞透了,算是对动规来理解深入了。1)动规四部曲:(1)确定dp数组及其下标的含义(2)确定递推公式(3)dp数组的初始化(4)确定遍历顺
前言 Slowloris攻击是我在李华峰老师的书——《MetasploitWeb 渗透测试实战》里面看的,感觉既简单又使用,现在这种攻击是很容易被防护的啦。不过我也不敢真刀实战的去试,只是拿个靶机玩玩罢了。 废话还是写在结语里面吧。(划掉)结语可以不看(划掉)Slowloris攻击的原理 Slowloris是一种资源消耗类DoS攻击,它利用部分HTTP请求进行操作。也叫做慢速攻击,这里的慢速并不是说发动攻击慢,而是访问一条链接的速度慢。Slowloris攻击的功能是打开与目标Web服务器的连接,然后尽可能长时间的保持这些连接打开。如果由多台电脑同时发起Slo
一、解决痛点使用spring-kafka客户端,每次新增topic主题,都需要硬编码客户端并重新发布服务,操作麻烦耗时长。kafkaListener虽可以支持通配符消费topic,缺点是并发数需要手动改并且重启服务。对于业务逻辑相似场景,创建新主题动态监听可以用kafka-batch-starter组件二、组件能力1、新增topic名称为:auto.topic1(由于配置spring.kafka.consumer.prefix为auto,因此只有auto前缀的topic,才会被组件动态监听。)2、应用输出日志,监听到新增auto.topic1,并初始化客户端(主题刷新间隔为10s)3、发新的消
Asitcurrentlystands,thisquestionisnotagoodfitforourQ&Aformat.Weexpectanswerstobesupportedbyfacts,references,orexpertise,butthisquestionwilllikelysolicitdebate,arguments,polling,orextendeddiscussion.Ifyoufeelthatthisquestioncanbeimprovedandpossiblyreopened,visitthehelpcenter提供指导。已关闭8年。什么是学习ruby语言
目录一、原理部分1、什么是串行通信(1)并行通信与串行通信(2)串行通信的制式(3)串行通信的主要方式 2、配置串口(1)SCON和PCON:串行口1的控制寄存器(2)SBUF:串行口数据缓冲寄存器 (3)AUXR:辅助寄存器编辑(4)ES、PS:与串行口1中断相关的寄存器(5)波特率设置 3、串口框架编写二、程序案例一、原理部分1、什么是串行通信(1)并行通信与串行通信微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。串行通信:数据一位一
例如,我一直看到称为String#split的方法,但从未见过String.split,这似乎更合乎逻辑。或者甚至可能是String::split,因为您可以认为#split位于String的命名空间中。当假定/隐含类(#split)时,我什至单独看到了该方法。我知道这是ri中识别方法的方式。哪个先出现?例如,这是为了区分方法和字段吗?我还听说这有助于区分实例方法和类方法。但这从哪里开始呢? 最佳答案 不同之处在于您如何访问这些方法。类方法使用::分隔符来表示消息可以发送到类/模块对象,而实例方法使用#分隔符表示消息可以发送到实例对