Kafka 消费者应用解析

news2025/1/6 19:47:01

目录

1、Kafka 消费方式

2、Kafka 消费者工作流程

2.1、消费者工作流程

2.2、消费组者说明

1、消费者组

2、消费者组初始化流程

3、消费者 API

3.1、独立消费者-订阅主题

3.2、独立消费者-订阅分区

3.3、消费组

4、分区的分配策略以及再平衡

4.1、Range 策略

1、Range 分区分配策略示例

2、Range 分区分配再平衡示例

4.2、RoundRobin 策略

1、RoundRobin 分区分配策略示例

2、RoundRobin 分区分配再平衡示例

4.3、Sticky 策略

1、Sticky分区分配示例

2、Sticky 分区分配再平衡示例

5、offset 位移

5.1、自动提交 offset

5.2、手动提交 offset

1、同步提交 offset

2、异步提交 offset

5.3、指定 Offset 消费

1、任意指定 offset 位移开始消费

2、指定时间消费

6、数据积压

注:示例代码使用的语言是Python

1、Kafka 消费方式

1、pull(拉)模 式:

  • consumer采用从broker中主动拉取数据。
  • Kafka采用这种方式
  • 缺点:
    • pull模式不足之处是,如 果Kafka没有数 据,消费者可能会陷入循环中,一直返回 空数据。

2、push(推)模式:

Kafka没有采用这种方式,因为由broker 决定消息发送速率,很难适应所有消费者的 消费速率。若推送的速度是100m/s, Consumer1、Consumer2就来不及处理消息。

2、Kafka 消费者工作流程

2.1、消费者工作流程

2.2、消费组者说明

1、消费者组

  • Consumer Group(CG):消费者组,由多个consumer组成。形成一个消费者组的条件:是所有消费者的groupid相同。
    • 消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个组内消费者消费。
    • 消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。
    • 如果向消费组中添加更多的消费者,超过 主题分区数量,则有一部分消费者就会闲 置,不会接收任何消息。
    • 消费者组之间互不影响。所 有的消费者都属于某个消费 者组,即消费者组是逻辑上 的一个订阅者。

2、消费者组初始化流程

  • coordinator:辅助实现消费者组的初始化和分区的分配
    • coordinator节点选择 = groupid的hashcode值 % 50( __consumer_offsets的分区数量)
    • 例如: groupid的hashcode值 = 3,3% 50 = 3 (50是默认分区数),那么__consumer_offsets 主题的3号分区,在哪个broker上,就选择这个节点的coordinator 作为这个消费者组的老大。消费者组下的所有的消费者提交offset的时候就往这个分区去提交offset。
  • 每个消费者都会和coordinator保持心跳(默认3秒),一旦超时(session.timeout.ms=45s),该消费者会被移除,并触发再平衡或者消费者处理的时间过长(max.poll.interval.ms 5分钟),也会触发再平衡。

3、消费者 API

3.1、独立消费者-订阅主题

  • 示例: 创建一个独立消费者,消费 first 主题中数据
  • 注意:在消费者 API 代码中必须配置消费者组 id(JAVA)【Python中可不指定消费组ID,会默认生成】。命令行启动消费者不填写消费者组 id 会被自动填写随机的消费者组 id。
"""
独立消费者,指定消费组id
"""
import time

from kafka3 import KafkaConsumer, KafkaProducer

class Serializer:
    @staticmethod
    def deserialize_bytes(deserialized_data: bytes):
        # 将bytes类型反序列化为str类型
        data = str(deserialized_data, "utf-8")
        return data

    @staticmethod
    def serialize_str(serialize_data: str):
        # 将str类型序列化为bytes类型
        serialized_data = bytes(serialize_data, 'utf-8')
        return serialized_data

def comsumer(*topics, filter=None, group_id="test", enable_auto_commit=False):
    """
    :fuction: 消费者, 完成数据消费
    :param topic: 需要消费数据的所在的topic, 可以消费多个topic
    :param filter: 过滤器 过滤展示/消费指定行为, 匹配方式为模糊匹配
    :param group_id: 消费组id 默认test
    :param enable_auto_commit: 是否自动提交消费,默认False
    :return:
    """
    print("开始消费数据......")
    consumer = KafkaConsumer(*topics,
                             bootstrap_servers=["170.22.70.174:9092", "170.22.70.178:9092", "170.22.70.179:9092"],
                             group_id=group_id,
                             enable_auto_commit=enable_auto_commit
                             )

    for message in consumer:
        # print(eval(str(message.value, "utf-8")), end="\n")
        print(Serializer.deserialize_bytes(message.value))
        print(f"消费消息的时间戳: {message.timestamp}")
        print(f"消息所在的topic: {message.topic}; 消息所在的分区: {message.partition}; 消息的偏移量: {message.offset}; "
              f"消息key值: {message.key}; 消费的时间: {time.strftime('%Y-%m-%d %H:%M:%S'), int(round(time.time() * 1000))}\n")

if __name__ == '__main__':
    topic = "first"
    comsumer(topic)

3.2、独立消费者-订阅分区

示例:创建一个独立消费者,消费 first 主题 0 号分区的数据。

"""
独立消费者,指定消费组id
指定分区消费数据
"""
import time
from kafka3 import KafkaConsumer, KafkaProducer, TopicPartition, KafkaClient

class Serializer:
    @staticmethod
    def deserialize_bytes(deserialized_data: bytes):
        # 将bytes类型反序列化为str类型
        data = str(deserialized_data, "utf-8")
        return data

    @staticmethod
    def serialize_str(serialize_data: str):
        # 将str类型序列化为bytes类型
        serialized_data = bytes(serialize_data, 'utf-8')
        return serialized_data

def comsumer(*topics, partition=0, filter=None, group_id="test", enable_auto_commit=False):
    """
    :fuction: 消费者, 完成数据消费
    :param topic: 需要消费数据的所在的topic, 可以消费多个topic
    :param partition: 消费指定分区数据,默认0
    :param filter: 过滤器 过滤展示/消费指定行为, 匹配方式为模糊匹配
    :param group_id: 消费组id 默认test
    :param enable_auto_commit: 是否自动提交消费,默认False
    :return:
    """
    print("开始消费数据......")
    consumer_config = {
        'bootstrap_servers': ["170.22.70.174:9092", "170.22.70.178:9092", "170.22.70.179:9092"],
        'client_id': group_id,
        'enable_auto_commit': enable_auto_commit
    }
    consumer = KafkaConsumer(**consumer_config)
    # 分配分区0给消费者
    consumer.assign([TopicPartition(topic, partition) for topic in topics])  
    for message in consumer:
        print(Serializer.deserialize_bytes(message.value))
        print(f"消费消息的时间戳: {message.timestamp}")
        print(f"消息所在的topic: {message.topic}; 消息所在的分区: {message.partition}; 消息的偏移量: {message.offset}; "
              f"消息key值: {message.key}; 消费的时间: {time.strftime('%Y-%m-%d %H:%M:%S'), int(round(time.time() * 1000))}\n")

3.3、消费组

  • 示例:测试同一个主题的分区数据,只能由一个消费者组中的一个消费。

1、案例实操

  • 1、复制两份份基础消费者的代码,在 pycharm中同时启动,即可启动同一个消费者组中 的三个消费者。
  • 2、启动代码中的生产者发送消息,在 pycharm 控制台即可看到三个消费者在消费不同 分区的数据(如果只发送到一个分区,可以在发送时增加延迟代码 Thread.sleep(2);)。

4、分区的分配策略以及再平衡

  • 1、一个consumer group中有多个consumer组成,一个 topic有多个partition组成,现在的问题是,到底由哪个consumer来消费哪个 partition的数据。
  • 2、Kafka有四种主流的分区分配策略: Range、RoundRobin、Sticky、CooperativeSticky。 可以通过配置参数partition.assignment.strategy,修改分区的分配策略。
    • 默认策略是Range + CooperativeSticky。Kafka可以同时使用 多个分区分配策略。(JAVA)
    • 默认策略是Range + RoundRobin。(Python)

注:下面这些默认参数配置是JAVA的,与Python的默认配置略有不同

【Python的默认配置如下】

'max_poll_interval_ms': 300000,
'session_timeout_ms': 10000,
'heartbeat_interval_ms': 3000,

4.1、Range 策略

Range 是对每个 topic 而言的。

  • 首先对同一个 topic 里面的分区按照序号进行排序,并 对消费者按照字母顺序进行排序。
  • 假如现在有 10 个分区,3 个消费者,排序后的分区将会 是0,1,2,3,4,5,6,7,8,9;消费者排序完之后将会是C0,C1,C2。
    • 例如,10/3 = 3 余 1 ,那么 消费者 C0 便会多 消费 1 个分区。 11/3=3余2,那么C0和C1分别多 消费一个。
  • 通过 partitions数/consumer数 来决定每个消费者应该 消费几个分区。如果有余数,那么前面几个消费者将会多 消费 1 个分区。

注意:如果只是针对 1 个 topic 而言,C0消费者多消费1 个分区影响不是很大。但是如果有 N 多个 topic,那么针对每 个 topic,消费者 C0都将多消费 1 个分区,topic越多,C0消 费的分区会比其他消费者明显多消费 N 个分区。 容易产生数据倾斜!

1、Range 分区分配策略示例

  • 1、修改主题 first 为 7 个分区。
    • 注意:分区数可以增加,但是不能减少。

bin/kafka-topics.sh --bootstrap-server node1:9092 --alter --topic first --partitions 7

  • 2、设置分区分配策略为Range。
# 设置分区分配策略为Range
partition_assignment_strategy = (RangePartitionAssignor,)
consumer = KafkaConsumer(*topics,
                         bootstrap_servers=["170.22.70.174:9092", "170.22.70.178:9092", "170.22.70.179:9092"],
                         group_id=group_id,
                         enable_auto_commit=enable_auto_commit,
                         partition_assignment_strategy=partition_assignment_strategy
                         )
  • 3、复制 CustomConsumer 类,创建 CustomConsumer2。这样可以由三个消费者 CustomConsumer、CustomConsumer1、CustomConsumer2 组成消费者组,组名都为“test”, 同时启动 3 个消费者。

  • 4、启动 CustomProducer 生产者,发送 500 条消息,随机发送到不同的分区。
  • 5、观察3 个消费者分别消费哪些分区的数据。
    • 可以看到consumer3消费了:0、1、2分区;consumer2消费了:3、4分区;consumer消费了:5、6分区

2、Range 分区分配再平衡示例

1、停止掉 0 号消费者,快速重新发送消息观看结果(10s 以内)。

  • 1 号消费者:消费到 0、1、2号分区数据。
  • 2 号消费者:消费到 3、4、5、6 号分区数据。
  • 0 号消费者的任务会整体被分配到 1 号消费者或者 2 号消费者。

说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需 要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。

2、再次重新发送消息观看结果(10s 以后)。

  • 1 号消费者:消费到 0、1、2、3 号分区数据。
  • 2 号消费者:消费到 4、5、6 号分区数据。

说明:消费者 0 已经被踢出消费者组,所以重新按照 range 方式分配。

4.2、RoundRobin 策略

  • RoundRobin 针对集群中所有Topic而言。
  • RoundRobin 轮询分区策略,是把所有的 partition 和所有的 consumer 都列出来,然后按照 hashcode 进行排序,最后 通过轮询算法来分配 partition 给到各个消费者。

1、RoundRobin 分区分配策略示例

  • 1、依次在 CustomConsumer、CustomConsumer1、CustomConsumer2 三个消费者代 码中修改分区分配策略为 RoundRobin。
# 设置分区分配策略为RoundRobinPartitionAssignor
partition_assignment_strategy = (RoundRobinPartitionAssignor,)
consumer = KafkaConsumer(*topics,
                         bootstrap_servers=["170.22.70.174:9092", "170.22.70.178:9092", "170.22.70.179:9092"],
                         group_id=group_id,
                         enable_auto_commit=enable_auto_commit,
                         partition_assignment_strategy=partition_assignment_strategy
                         )
  • 2、重启 3 个消费者,重复发送消息的步骤,观看分区结果。
    • 可以看到consumer3消费了:1、4分区;consumer2消费了:0、3、6分区;consumer消费了:2、5分区

2、RoundRobin 分区分配再平衡示例

1、停止掉 0 号消费者,快速重新发送消息观看结果(10s 以内)。

  • 1 号消费者:消费到 0、2、4、6号分区数据
  • 2 号消费者:消费到 1、3、5 号分区数据
  • 0 号消费者的任务会按照 RoundRobin 的方式,把数据轮询分成 2 和 5 号分区数据, 分别由 1 号消费者或者 2 号消费者消费。

说明:0 号消费者挂掉后,消费者组需要按照超时时间 10s 来判断它是否退出,所以需 要等待,时间到了 10s 后,判断它真的退出就会把任务分配给其他 broker 执行。

2、再次重新发送消息观看结果(10s 以后)。

  • 1 号消费者:消费到 0、2、4、6 号分区数据
  • 2 号消费者:消费到 1、3、5 号分区数据

说明:消费者 0 已经被踢出消费者组,所以重新按照 RoundRobin 方式分配。

4.3、Sticky 策略

  • 粘性分区定义:可以理解为分配的结果带有“粘性的”。即在执行一次新的分配之前, 考虑上一次分配的结果,尽量少的调整分配的变动,可以节省大量的开销。
  • 粘性分区是 Kafka 从 0.11.x 版本开始引入这种分配策略,首先会尽量均衡的放置分区 到消费者上面,在出现同一消费者组内消费者出现问题的时候,会尽量保持原有分配的分 区不变化。

示例 :设置主题为 first,7 个分区;准备 3 个消费者,采用粘性分区策略,并进行消费,观察 消费分配情况。然后再停止其中一个消费者,再次观察消费分配情况。

1、Sticky分区分配示例

注意:kafka-python3库中没有Sticky分区策略,以下的示例是JAVA示例

  • 1、修改分区分配策略为粘性。
    • 注意:3 个消费者都应该注释掉,之后重启 3 个消费者,如果出现报错,全部停止等 会再重启,或者修改为全新的消费者组。
// 修改分区分配策略
ArrayList<String> startegys = new ArrayList<>();
startegys.add("org.apache.kafka.clients.consumer.StickyAssignor");
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, startegys);
  • 2、使用同样的生产者发送 500 条消息。
    • 可以看到会尽量保持分区的个数近似划分分区。

2、Sticky 分区分配再平衡示例

1、停止掉 0 号消费者,快速重新发送消息观看结果(45s 以内)。

  • 1 号消费者:消费到 2、5、3 号分区数据。
  • 2 号消费者:消费到 4、6 号分区数据。
  • 0 号消费者的任务会按照粘性规则,尽可能均衡的随机分成 0 和 1 号分区数据,分别 由 1 号消费者或者 2 号消费者消费。

说明:0 号消费者挂掉后,消费者组需要按照超时时间 45s 来判断它是否退出,所以需 要等待,时间到了 45s 后,判断它真的退出就会把任务分配给其他 broker 执行。

2、再次重新发送消息观看结果(45s 以后)。

  • 1 号消费者:消费到 2、3、5 号分区数据。
  • 2 号消费者:消费到 0、1、4、6 号分区数据。

说明:消费者 0 已经被踢出消费者组,所以重新按照粘性方式分配。

5、offset 位移

说明:kafka 0.9版本之前。consumer默认将offset保存在zookeeper中,从0.9版本开始,consumer默认将offset保存在kafka一个内置的topic中,该topic为__consumer_offsets

__consumer_offsets :里面采用 key 和 value 的方式存储数据。key 是 group.id+topic+ 分区号,value 就是当前 offset 的值。每隔一段时间,kafka 内部会对这个 topic 进行 compact,也就是每个 group.id+topic+分区号就保留最新数据。

5.1、自动提交 offset

  • 自动提交offset的相关参数(JAVA):
    • enable.auto.commit:是否开启自动提交offset功能,默认是true
    • auto.commit.interval.ms:自动提交offset的时间间隔,默认是5s
  • 自动提交offset的相关参数(Python):

'enable_auto_commit': True,
'auto_commit_interval_ms': 5000,

代码示例(Python)

# 设置自动提交
enable_auto_commit = True
# 设置提交时间周期1000ms
auto_commit_interval_ms = 1000
consumer = KafkaConsumer(*topics,
                         bootstrap_servers=["170.22.70.174:9092", "170.22.70.178:9092", "170.22.70.179:9092"],
                         group_id=group_id,
                         partition_assignment_strategy=partition_assignment_strategy,
                         enable_auto_commit=enable_auto_commit,
                         auto_commit_interval_ms=auto_commit_interval_ms
                         )

5.2、手动提交 offset

  • 虽然自动提交offset十分简单便利,但由于其是基于时间提交的,开发人员难以把握offset提交的时机。因 此Kafka还提供了手动提交offset的API。
  • 手动提交offset的方法有两种:分别是commitSync(同步提交)和commitAsync(异步提交)。两者的相 同点是,都会将本次提交的一批数据最高的偏移量提交;不同点是,同步提交阻塞当前线程,一直到提交成 功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而异步提交则没有失败重试机制,故 有可能提交失败。
    • commitSync(同步提交):必须等待offset提交完毕,再去消费下一批数据
    • commitAsync(异步提交) :发送完提交offset请求后,就开始消费下一批数据了。

1、同步提交 offset

  • 由于同步提交 offset 有失败重试机制,故更加可靠,但是由于一直等待提交结果,提 交的效率比较低。以下为同步提交 offset 的示例。
consumer_config = {
    'bootstrap_servers': ["170.22.70.174:9092", "170.22.70.178:9092", "170.22.70.179:9092"],
    'group_id': group_id,
    'enable_auto_commit': False
}
consumer = KafkaConsumer(*topics, **consumer_config)
# 设置offset同步提交
consumer.commit()

2、异步提交 offset

  • 虽然同步提交 offset 更可靠一些,但是由于其会阻塞当前线程,直到提交成功。因此 吞吐量会受到很大的影响。因此更多的情况下,会选用异步提交 offset 的方式。
  • 以下为异步提交 offset 的示例:
consumer_config = {
    'bootstrap_servers': ["170.22.70.174:9092", "170.22.70.178:9092", "170.22.70.179:9092"],
    'group_id': group_id,
    'enable_auto_commit': False
}
consumer = KafkaConsumer(*topics, **consumer_config)
# 设置offse异步提交
consumer.commit_async()

5.3、指定 Offset 消费

  • auto.offset.reset = earliest | latest | none 默认是 latest。(JAVA);参数:auto_offset_reset 默认是 latest。(Python)
  • 当 Kafka 中没有初始偏移量(消费者组第一次消费)或服务器上不再存在当前偏移量 时(例如该数据已被删除),该怎么办?
    • earliest:自动将偏移量重置为最早的偏移量,--from-beginning。
    • latest(默认值):自动将偏移量重置为最新偏移量。
    • none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。

1、任意指定 offset 位移开始消费

assignment = consumer.assignment()
while len(assignment) == 0:
    consumer.poll() # 获取分区分配情况
    assignment = consumer.assignment()

for partition in assignment:
    consumer.seek(partition, 600)  # 设置偏移量为 600,

2、指定时间消费

在生产环境中,会遇到最近消费的几个小时数据异常,想重新按照时间消费。 例如要求按照时间消费前一天的数据,就需要指定时间消费。

assignment = consumer.assignment()
while len(assignment) == 0:
    consumer.poll() # 获取分区分配情况
    assignment = consumer.assignment()
# 指定从什么时间开始消费
assign_time = int(time.time() - 24*60*60) * 1000

for partition in assignment:
    timestamps = {}
    timestamps[partition] = assign_time
    # 通过指定的时间戳,获取每个分区中时间戳对应offset
    topic_partition_timestamp = consumer.offsets_for_times(timestamps)
    for topic_partition, timestamps_offset in topic_partition_timestamp.items():
        if timestamps_offset and timestamps_offset:
            # 通过获取到与时间戳对应的offset,然后指定每个分区从offset开始消费
            consumer.seek(topic_partition, timestamps_offset[0])

6、数据积压

如果消费端出现数据积压问题,可能是以下两个原因导致的:

  • 1、如果是kafka消费能力不足,则可以考虑增加topic的分区数,并且同时提升消费组的消费者梳理,消费者数=分区数。
  • 2、如果是消费者处理速度比生产速度快,也会造成数据积压,提高每批次拉取的数量。

通过以下两个参数调整:

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

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

相关文章

吴恩达深度学习笔记:深度学习的 实践层面 (Practical aspects of Deep Learning)1.6-1.8

目录 第一门课&#xff1a;第二门课 改善深层神经网络&#xff1a;超参数调试、正 则 化 以 及 优 化 (Improving Deep Neural Networks:Hyperparameter tuning, Regularization and Optimization)第一周&#xff1a;深度学习的 实践层面 (Practical aspects of Deep Learning)…

基于JavaWEB的学生考勤管理系统(含论文)

本系统是用Java语言写的&#xff0c;基于JavaWEB的学生考勤管理系统 主要有三大模块&#xff0c;学生&#xff0c;教师和管理员模块&#xff0c;功能如下&#xff1a; 学生模块 教师模块&#xff1a; 管理员模块

Allure精通指南(05)定制化报告内容(环境信息、图标、缺陷类别)

文章目录 Allure 自定义测试环境信息Allure 自定义缺陷类别信息Allure 自定义图标步骤一步骤二步骤三 Allure 自定义测试环境信息 步骤 1&#xff1a;创建 environment.properties 文件 在项目根目录或任何其他不会被--clean-alluredir参数影响的目录下创建 environment.proper…

Redis事务以及Watch监听(通俗易懂)

在执行事务时&#xff0c;要不全执行&#xff0c;要不全不执行。Redis 事务我个人认为不是严格意义上的事务&#xff0c;只是用于帮助用户在一个步骤中执行多个命令。它是通过multi[mʌlti] 表示开启事务&#xff0c;EXEC执行事务&#xff0c;discard 丢失任务。当我们开启事务…

动手学深度学习14 数值稳定性+模型初始化和激活函数

动手学深度学习14 数值稳定性模型初始化和激活函数 1. 数值稳定性2. 模型初始化和激活函数3. QA **视频&#xff1a;**https://www.bilibili.com/video/BV1u64y1i75a/?spm_id_fromautoNext&vd_sourceeb04c9a33e87ceba9c9a2e5f09752ef8 **电子书&#xff1a;**https://zh-v…

如何利用人工智能加速临床试验

如何利用人工智能加速临床试验 Matthew Hutson 著 李升伟 译 从研究设计到患者招募&#xff0c;研究人员正在研究如何运用AI技术加速临床试验过程。 几十年来&#xff0c;计算能力遵循摩尔定律&#xff08;Moore’s law&#xff09;&#xff0c;以可预测的速度前进。集成电路上…

Linux:服务器间同步文件的脚本(实用)

一、功能描述 比如有三台服务器&#xff0c;hadoop102、hadoop103、hadoop104&#xff0c;且都有atguigu账号 循环复制文件到所有节点的相同目录下&#xff0c;且脚本可以在任何路径下使用 二、脚本实现 1、查看环境变量 echo $PATH2、进入/home/atguigu/bin目录 在该目录下…

结合创新!LSTM+Transformer新成果登Nature,精度高达95.65%

推荐一个能发表高质量论文的好方向&#xff1a;LSTM结合Transformer。 LSTM通过门控机制有效捕捉序列中的长期依赖关系&#xff0c;防止梯度消失或爆炸&#xff0c;在处理具有长期依赖性的时间序列数据时有显著优势。而Transformer通过自注意力和多头注意力机制全面捕捉序列依…

OpenHarmony实战开发-媒体查询 (@ohos.mediaquery)

概述 媒体查询作为响应式设计的核心&#xff0c;在移动设备上应用十分广泛。媒体查询可根据不同设备类型或同设备不同状态修改应用的样式。媒体查询常用于下面两种场景&#xff1a; 针对设备和应用的属性信息&#xff08;比如显示区域、深浅色、分辨率&#xff09;&#xff0…

21.基础乐理-等音调扩展篇、为何一共十五个大调

首先 等音调 的概念是基于 等音 的概念&#xff0c;比如下图中的音名&#xff1a;因为用的按键相同&#xff0c;音名不同&#xff0c;所以被称为等音调 然后音名一共有35个&#xff0c;如下图&#xff1a;所以在理论上它会有35个大调&#xff0c;但是人总是倾向于选择简单、简洁…

一个不太好用的弹出层jquery.colorbox第二次点击不出来的解决方法

使用jquery.colorbox的时候第一次正常显示&#xff0c;但第二次的时候不显示。 需要先移除&#xff0c;再重新点击即可&#xff0c;代码如下 $.colorbox.remove();$.colorbox({href: url,iframe: true,width: options.width || 800,height: options.height || 600 });

企业应该如何选择适合的ERP?

今天跟大家聊一点不一样的。一直写了很多关于企业内部系统集成的文章和案例&#xff0c;很多人私信我又或者是很多客户在做数字化选型的时候会问到我一个问题&#xff0c;那就是“ERP改如何去选择&#xff0c;金蝶跟用友该怎么选&#xff1f;”。因为做集成这个领域也有十多年了…

宁盾2FA双因子认证助力某城商行TCE平台达到密评要求

《密码法》规定&#xff0c;法律、行政法规和国家有关规定要求使用商用密码进行保护的关键信息基础设施&#xff0c;其运营者应当使用商用密码进行保护&#xff0c;自行或者委托商用密码检测机构开展商用密码应用安全性评估。商用密码应用安全性评估&#xff08;简称&#xff1…

小红书KOL推广应该注意什么?

小红书上的KOL往往具有极大影响力&#xff0c;可以协助品牌快速构建产品形象&#xff0c;并短时间内为品牌带来大量热度和搜索量&#xff0c;引导消费。品牌通过与达人进行合作推广&#xff0c;也可以借鉴达人的影响力&#xff0c;提高品牌的信任度和转化率。 不过网红达人推广…

SpringBoot源码阅读2-自动配置

SpringBoot源码阅读2-自动配置 在传统的Spring应用中&#xff0c;开发者需要手动配置一系列Web应用的核心组件&#xff0c;例如DispatcherServlet用于处理请求分发、ViewResolver用于视图解析、CharacterEncodingFilter用于字符编码过滤等。 然而在SpringBoot中只要引入了spr…

4 -26

4-26 1 英语单词100个一篇六级翻译 2 div 4 补题目 3 概率论期中卷子一张&#xff0c;复习复习。 4 备课ing 晚上出去炫饭&#xff0c;串串香&#xff0c;无敌了。 中间一些模拟题是真的恶心&#xff0c;思维题是真的想不到&#xff0c;感觉自己就是一个废物呢。 1.是将一个数…

Neo-reGeorg明文流量

Neo-reGeorg 1 同IP对&#xff0c;同一个URI&#xff0c;第一个TCP流是“GET”请求&#xff0c;随后的TCP流请求为“POST”。&#xff08;jsp\jspx\php&#xff09; 2 第一个TCP流中&#xff0c;GET只有一个会话。&#xff08;jsp\jspx\php&#xff09;&#xff0c;响应body79…

本地体验最强开源模型Llama3+Qnw(支持Windows和Mac)

一键运行大模型本地软件&#xff08;含模型&#xff09;&#xff1a;点击下载 Meta放出Llama3模型了&#xff0c;也应该是这段时间里的一个科技大新闻了。 Llama一直都是开源大语言模型的领头羊驼。 而Llama3又是所有羊驼中最新的领头羊。 可以简单地来看一下官方的对比数据…

03_Scala变量和数据类型

文章目录 [toc] **变量和数据类型****1.注释****2.变量和常量****3. 标识符的命名规范****4.scala的字符串****5.键盘输入****5.1 StdIn.readLine()****5.2 从文件中读取数据****5.3 Scala向外写数据** 变量和数据类型 1.注释 和Java完全一样 ** ** 2.变量和常量 var name…

Gin的中间件执行流程与用法

一、背景 我们在使用Gin框架进行Web开发的时候&#xff0c;基本上都会遇到登录拦截的场景。 例如某些接口必须在登录以后才能访问&#xff0c;根据登录用户的信息以及权限&#xff0c;拿到属于自己的数据, 反之&#xff0c;没登录过则直接拒绝访问。 那么我们怎么做到这些登录…