活动介绍

[2023-07-14 17:30:13,604][WARN ][org.springframework.kafka.KafkaListenerEndpointContainer#0-0-C-1][NetworkClient][handleServerDisconnect] - [Consumer clientId=consumer-gdmc-imp-exp-1, groupId=gdmc-imp-exp] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected

时间: 2024-01-20 19:02:36 浏览: 233
你好!对于你提到的警告信息,它表明在连接到 Kafka 服务器时出现问题。具体而言,根据日志中的信息,消费者(Consumer)的clientId为"consumer-gdmc-imp-exp-1",groupId为"gdmc-imp-exp"。警告信息中还提到了Bootstrap broker的地址为"localhost:9092"。 这种警告通常发生在以下情况下: 1. Kafka服务器未运行或未正常启动。 2. Kafka服务器的网络连接发生问题,导致消费者无法连接。 你可以检查以下几点: 1. 确保Kafka服务器正在运行,并且端口9092可用。 2. 检查Kafka服务器的配置文件,确保正确配置了bootstrap.servers参数。 3. 检查消费者的配置文件,确保正确配置了bootstrap.servers、groupId和clientId参数。 如果问题仍然存在,请提供更多上下文信息,以便我能够更好地帮助你解决问题。
相关问题

2025-05-13 17:25:50 [main] ERROR SpringApplication:818 - Application run failed org.springframework.context.ApplicationContextException: Failed to start bean 'org.springframework.kafka.config.internalKafkaListenerEndpointRegistry'; nested exception is org.apache.kafka.common.KafkaException: Failed to construct kafka consumer 如何关闭kafka服务

<think>好的,我现在需要帮助用户解决Spring Boot微服务中Kafka启动失败导致的ApplicationContextException问题,并了解如何正确关闭Kafka服务。首先,我得理清楚用户的问题,他们遇到了启动失败的情况,并且想知道如何正确关闭Kafka服务。 首先,启动失败的原因可能有多个。根据常见的经验,首先应该检查Kafka的配置是否正确。比如,用户可能配置了错误的bootstrap-servers地址,或者端口不对。需要确认application.yml中的spring.kafka.bootstrap-servers是否正确指向了Kafka集群的地址,比如是否是localhost:9092或者正确的服务器IP和端口。 另外,Kafka服务器是否真的在运行?有时候可能Kafka服务没有启动,或者网络问题导致连接不上。这时候,用户需要检查Kafka服务状态,使用命令如systemctl status kafka或者查看日志确认服务是否正常。 接下来,依赖问题也可能导致启动失败。用户是否在pom.xml或build.gradle中正确引入了spring-kafka依赖?如果版本不兼容,比如Spring Boot版本和Kafka客户端版本不匹配,也可能引发问题。需要确认依赖版本是否匹配,比如Spring Boot 2.6.x对应spring-kafka 2.8.x,而Spring Boot 3.x可能需要更高版本的spring-kafka。 然后,SSL配置错误也是一个常见问题。如果Kafka启用了SSL,而Spring Boot配置中没有正确设置信任库或密钥库的路径、密码,就会导致连接失败。需要检查spring.kafka.ssl相关的配置是否正确,包括文件路径是否有效,密码是否正确。 关于正确关闭Kafka服务的方法,用户可能希望优雅地关闭,避免数据丢失或资源未释放。需要指导他们如何正确关闭,比如使用KafkaTemplate的destroy方法,或者通过Spring的生命周期管理,比如@PreDestroy注解来确保资源释放。另外,如果是通过命令行启动的Kafka服务,应该使用正确的停止命令,比如使用systemctl stop kafka或者kill -TERM而不是强制终止进程。 另外,用户可能没有正确配置消费者或生产者的关闭流程,导致应用关闭时仍有线程在运行,从而引发异常。需要提醒他们在配置中设置spring.kafka.listener.shutdown-timeout,确保消费者有足够的时间完成当前任务并优雅关闭。 总结下来,我需要分步骤说明如何排查启动失败的原因,并提供对应的解决方案,同时给出正确关闭Kafka服务的方法,包括代码示例和配置建议。同时,需要引用用户提供的参考资料中的相关内容,比如使用KafkaTemplate、KafkaProperties的配置注入等,来增强回答的可信度。</think>### Spring Boot微服务Kafka启动失败与关闭方法指南 --- #### 一、Kafka启动失败原因分析与解决方案(ApplicationContextException) 1. **配置错误** - **问题表现**:`Failed to configure a DataSource` 或 `Bootstrap servers not configured` - **检查点**: - 确认`application.yml`中Kafka连接配置正确: ```yaml spring: kafka: bootstrap-servers: localhost:9092 # 需与实际Kafka集群地址一致[^4] ``` - 验证Kafka服务端口是否开放(使用`telnet`或`nc`命令测试) - **解决方案**:修正错误配置后重启应用 2. **Kafka服务未启动** - **验证方法**: ```bash # 检查Kafka服务状态 systemctl status kafka # 查看Kafka日志(默认路径:/var/log/kafka/server.log) ``` - **解决方案**:启动Kafka服务后重试应用 ```bash systemctl start kafka ``` 3. **依赖版本冲突** - **典型场景**:Spring Boot 2.6.x需使用`spring-kafka:2.8.x`,Spring Boot 3.x需使用`spring-kafka:3.0.x+`[^3] - **解决方案**: ```xml <!-- pom.xml示例 --> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> <version>2.8.12</version> <!-- 根据Spring Boot版本选择 --> </dependency> ``` 4. **SSL配置错误** - **错误特征**:`SSL handshake failed`或`Truststore not found` - **配置示例**: ```yaml spring: kafka: ssl: key-password: 123456 keystore-location: classpath:kafka.client.keystore.jks truststore-location: classpath:kafka.client.truststore.jks ``` - **验证方法**:使用`openssl s_client -connect`测试SSL连接 --- #### 二、正确关闭Kafka服务的方法 1. **应用内优雅关闭** ```java @PreDestroy public void destroy() { // 关闭KafkaTemplate连接池 if(kafkaTemplate != null) { kafkaTemplate.destroy(); // 释放生产者资源[^2] } // 停止监听器容器(如果使用@KafkaListener) registry.getListenerContainers().forEach(Container::stop); } ``` 2. **配置消费者优雅关闭** ```yaml spring: kafka: listener: shutdown-timeout: 60000 # 等待1分钟完成处理 idle-event-interval: 5000 ``` 3. **服务端关闭操作** ```bash # 通过systemctl关闭(推荐) systemctl stop kafka # 使用Kafka自带脚本 bin/kafka-server-stop.sh ``` --- #### 三、典型错误日志诊断表 | 错误类型 | 关键日志片段 | 解决方案 | |----------------------------|----------------------------------|---------------------------------| | 连接拒绝 | `Connection to node -1 failed` | 检查Kafka服务状态和防火墙设置 | | 序列化异常 | `SerializationException` | 确认Producer的key/value序列化配置 | | 消费者组协调失败 | `CommitFailedException` | 调整`max.poll.interval.ms`参数 | | Topic不存在 | `UNKNOWN_TOPIC_OR_PARTITION` | 配置`auto.create.topics.enable=true` | --- #### 四、调试技巧 1. **启用DEBUG日志**: ```yaml logging: level: org.springframework.kafka: DEBUG org.apache.kafka: WARN ``` 2. **使用Kafka命令行工具验证**: ```bash # 测试生产者 kafka-console-producer.sh --broker-list localhost:9092 --topic test-topic # 测试消费者 kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test-topic ``` ---

DATASOURCE_DIALECT: oracle REDIS_PASSWORD: Longshare12345 server: bes: basedir: # 如bes.lic.txt文件位于/bes/license/bes.lic.txt,则只需要配置到/bes/ tongweb: license: type: file path: # 配置到license.dat文件的绝对路径 forward-headers-strategy: ${FORWAD_HEADERS_STRATEGY:none} # ta所有应用的公共配置 spring: datasource: url: ${DATASOURCE_FULL_URL:jdbc:oracle:thin:@10.102.25.247:1521:htdb} #url: ${DATASOURCE_FULL_URL:jdbc:oracle:thin:@10.102.21.80:1521:htdb} username: ${DATASOURCE_USER:zgta} password: ${DATASOURCE_PASSWORD:Longshare#123} druid: initial-size: 100 min-idle: 10 max-active: 100 max-wait: 60000 time-between-eviction-runs-millis: 60000 pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 validation-query: select 1 from dual test-while-idle: true test-on-borrow: false test-on-return: false redis: database: 5 host: ${REDIS_HOST:10.102.25.247} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:Longshare12345} ignite: url: ${IGNITE_URL:10.102.25.247:49500} kafka: bootstrap-servers: ${KAFKA_URL:10.102.25.247:9092} zk-nodes: ${ZOOKEEPER_URL:10.102.25.247:2181} mail: enable: false host: ${MAIL_SERVICE_URL:smtp.163.com} username: ${MAIL_USER:[email protected]} password: ${MAIL_PASSWORD:longshare1} port: 465 platform: dbtype: ${DATASOURCE_DIALECT:oracle} mapper: identity: ${DATASOURCE_DIALECT:oracle} pagehelper: helper-dialect: ${DATASOURCE_DIALECT:oracle} rest: swagger: enable: false file-bus: enable: false # 是否开通文件总线功能,默认关闭 sync: hs-data: false # 默认关闭同步hs数据 jodconverter: local: enabled: true office-home: ${OFFICE_HOME:/opt/libreoffice7.3} exception: openLogPosition: false #默认关闭日志定位提示 robin: route: host: ${HOST_ROUTE:false} data: mask: enable: ${DATA_MASK:false} # 是否开启脱敏 excel: export: customize: true # excel导出过滤客户自定义列 statistic: true # excel导出统计行列 feign: # 如果需要ta运管集成功能,比如净值自动复核功能,则手动开启 audit: name: ta-compare uri: localhost:5301 context-path: /audit indicator: name: ta-compare uri: localhost:5301 context-path: /audit system: name: ta-system context-path: /system scheduler: name: ta-system context-path: /system compare: table: save: 1 data-maintenance: enable: true feign: client: config: default: connectTimeout: 300000 readTimeout: 300000 logging: open: true #日志收集开启 #日志根目录 #basedir: ${LOG_DIR:/home/ta/logs/} #quota: #path: ${logging.basedir:}quota/ file: #name: ${logging.basedir:}${spring.application.name}/${spring.application.name}.log name: ./${spring.application.name}.log level: root: error com.longshare: warn com.longshare.rest.core.util.RequestContextUtil: warn org.springframework.jdbc.core: warn logback: rollingpolicy: max-file-size: 50MB max-history: 10000 management: endpoint: health: show-details: never # 生产环境修改为never 介绍这段nacos,application.yml

<think>我们正在讨论的是Nacos配置中心中application.yml文件的作用和常见配置项。根据引用内容,我们可以总结如下: 1. Nacos是一个配置中心,用于集中管理应用程序的配置。 2. 在Spring Boot/Cloud应用中,我们可以将application.yml(或application.properties)的配置内容放到Nacos配置中心进行集中管理。 3. 应用启动时,会从Nacos配置中心获取配置,而不是从本地文件获取。 具体到application.yml在Nacos中的作用: - **作用**:在Nacos中存储的application.yml配置,相当于替代了本地application.yml文件。它用于配置应用程序级别的参数,这些参数通常是应用程序特有的,用于定义后续各个模块中需使用的公共参数等(如数据源、服务器端口等)[^4]。 - **常见配置项**:在Nacos中配置的application.yml可以包含任何原本在本地application.yml中配置的内容。例如: - 服务器端口:`server.port` - 数据库连接信息:`spring.datasource.url`, `spring.datasource.username`, `spring.datasource.password` - 应用名称:`spring.application.name` - 日志级别:`logging.level` - 以及其他自定义配置。 另外,根据引用,我们还需要注意在Spring Cloud Alibaba Nacos中,如何配置Nacos本身的信息(在bootstrap.yml中配置): ```yaml spring: cloud: nacos: config: server-addr: ${nacos.server-addr} # Nacos服务器地址 file-extension: yml # 配置文件扩展名 namespace: your-namespace-id # 命名空间ID,用于隔离不同环境或项目 group: DEFAULT_GROUP # 配置分组,默认为DEFAULT_GROUP ``` 这里需要强调的是: - 这些配置(server-addr, namespace, group)是告诉应用程序去哪里找Nacos配置中心,以及从哪个namespace和group下获取配置。这些配置通常放在bootstrap.yml中,因为bootstrap.yml的加载优先级高于application.yml,它会在应用启动初期就加载,从而能够从Nacos获取到application.yml的配置。 在Nacos管理平台上,我们需要创建配置,其中: - Data ID:默认为`${spring.application.name}.${file-extension}`,例如应用名为`user-service`,则Data ID为`user-service.yml`。 - Group:与上面配置的group一致,默认为`DEFAULT_GROUP`。 - 配置内容:就是原本application.yml中的内容。 所以,总结回答: ### Nacos配置中心中application.yml文件的作用 在Nacos配置中心中存储的`application.yml`文件,其作用等同于本地`application.yml`配置文件,用于集中管理应用程序的配置。通过将配置集中到Nacos,可以实现配置的动态更新、版本管理、环境隔离(通过命名空间)和配置共享等优势。 ### 常见配置项 在Nacos中配置的`application.yml`可以包含任何Spring Boot应用所需的配置,常见的有: 1. **应用基本信息**: ```yaml spring: application: name: user-service # 应用名称,用于服务发现和配置定位 ``` 2. **服务器配置**: ```yaml server: port: 8080 # 应用启动端口 ``` 3. **数据源配置**: ```yaml spring: datasource: url: jdbc:mysql://localhost:3306/mydb username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver ``` 4. **日志配置**: ```yaml logging: level: root: info com.example: debug ``` 5. **自定义配置**:任何在应用中通过`@Value`或`@ConfigurationProperties`注入的配置都可以在这里定义。 ### 配置Nacos连接信息(bootstrap.yml) 要让应用从Nacos获取`application.yml`配置,需要在项目的`bootstrap.yml`(或`bootstrap.properties`)中配置Nacos服务器的连接信息: ```yaml spring: cloud: nacos: config: server-addr: 127.0.0.1:8848 # Nacos服务器地址 namespace: 你的命名空间ID # 可选,用于隔离环境(如dev, test, prod) group: DEFAULT_GROUP # 配置分组,默认为DEFAULT_GROUP file-extension: yml # 配置文件扩展名 # 其他配置如refresh-enabled: true 支持动态刷新 ``` ### 动态配置更新 Nacos支持配置的动态更新。在Spring Cloud应用中,只需在需要动态刷新的Bean上使用`@RefreshScope`注解,当Nacos中的配置变更时,应用会自动更新这些Bean的配置值[^3]。 ### 配置的优先级 当应用同时存在本地配置文件和Nacos远程配置时,远程配置(Nacos中的配置)优先级高于本地配置(bootstrap.yml和application.yml的本地文件),但bootstrap.yml的本地配置用于连接Nacos,所以它的优先级在启动初期是最高的。 ### 注意事项 - 确保Nacos配置中心已经启动(默认端口8848)[^2]。 - 在Nacos管理控制台创建配置时,Data ID的格式为`${spring.application.name}.${file-extension}`(例如:`user-service.yml`),并且要确保与bootstrap.yml中配置的`file-extension`一致。 - 命名空间(namespace)和分组(group)的配置必须与Nacos服务端创建的保持一致[^1]。 通过以上方式,我们可以将应用的配置集中管理,实现配置的动态更新和环境隔离。 --- §§相关问题§§ 1. 如何在Nacos中创建application.yml配置? 2. 如何实现Nacos配置的动态更新? 3. 在Spring Boot应用中,bootstrap.yml和application.yml有什么区别?</think>### Nacos 配置中心中 `application.yml` 的作用与常见配置项 #### 一、核心作用 1. **应用程序级配置** - 替代本地 `application.yml`,**集中管理** Spring Boot/Cloud 应用的专属配置(如数据源、端口、缓存等)[^4]。 - 支持**环境隔离**(开发/测试/生产环境),通过不同命名空间(Namespace)实现配置分离[^2][^3]。 2. **动态配置更新** - 应用运行时**无需重启**即可获取最新配置(通过 `@RefreshScope` 注解实现)[^3]。 - 例如:修改数据库连接池参数后实时生效。 3. **配置共享与隔离** - **共享配置**:通过 `group`(如 `DEFAULT_GROUP`)实现多服务共用基础配置(如公共日志格式)[^1][^3]。 - **隔离配置**:利用 `namespace` 隔离不同租户或环境的配置(如 `dev-namespace` 与 `prod-namespace`)[^1][^2]。 --- #### 二、常见配置项(Nacos 控制台) 在 Nacos 配置中心创建 `application.yml` 时需指定以下元数据: | **配置项** | **说明** | **示例值** | |------------------|--------------------------------------------------------------------------|--------------------------| | **Data ID** | 配置的唯一标识,通常格式为 `${spring.application.name}.yml` | `user-service.yml` | | **Group** | 配置分组,用于逻辑隔离(默认 `DEFAULT_GROUP`) | `DEV_GROUP` | | **Namespace** | 环境隔离标识(需与 `bootstrap.yml` 中的 `namespace` 一致) | `dev-namespace-id` | | **配置格式** | 文件类型(YAML/Properties) | `YAML` | | **配置内容** | 具体的键值对配置 | 见下方示例 | ##### 典型配置内容示例: ```yaml # Nacos 中存储的 application.yml 内容 server: port: 8080 # 服务端口 spring: datasource: url: jdbc:mysql://localhost:3306/db username: root password: 123456 logging: level: root: INFO com.example: DEBUG ``` --- #### 三、与 `bootstrap.yml` 的协作关系 应用启动时,配置加载优先级为: 1. **`bootstrap.yml`**(本地文件): - **优先加载**,用于配置 Nacos 连接信息(服务地址、命名空间等)[^1][^4]。 ```yaml spring: cloud: nacos: config: server-addr: 127.0.0.1:8848 # Nacos 服务器地址 namespace: dev-namespace-id # 命名空间 ID group: DEV_GROUP # 配置分组 ``` 2. **Nacos 中的 `application.yml`**: - 通过 `bootstrap.yml` 指定的地址动态加载,覆盖本地配置。 --- #### 四、操作流程 1. **在 Nacos 创建配置** - 登录 Nacos 控制台(默认 `localhost:8848`)[^2]。 - 进入 **配置管理 → 新建配置**,填写 Data ID、Group、Namespace 及 YAML 内容。 2. **应用接入配置** - 在项目的 `bootstrap.yml` 中声明 Nacos 地址和命名空间[^1]。 - 添加依赖:`spring-cloud-starter-alibaba-nacos-config`。 --- #### 五、优势与场景 - **优势**: - **配置集中化**:避免配置散落在各服务节点[^3]。 - **动态更新**:无需重启服务,秒级生效[^3]。 - **适用场景**: - 微服务架构中的多环境配置管理。 - 敏感配置(如密码)的加密存储与轮转。 > 注:若同时存在本地 `application.yml` 和 Nacos 配置,**Nacos 的配置优先级更高**(通过 `spring.cloud.config.override-none=true` 可调整)[^4]。 ---
阅读全文

相关推荐

<?xml version="1.0" encoding="UTF-8"?> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.1</version> <relativePath/> <groupId>com.example</groupId> <artifactId>main</artifactId> <version>0.0.1</version> <name>main</name> <description>为Spring Cloud Alibaba升级铺垫的主服务</description> <java.version>17</java.version> <encoding>UTF-8</encoding> ${encoding} ${encoding} <maven.compiler.encoding>${encoding}</maven.compiler.encoding> <spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version> <spring-cloud.version>2023.0.2</spring-cloud.version> <mybatis-plus.version>3.5.7</mybatis-plus.version> <fastjson2.version>2.0.52</fastjson2.version> <modelmapper.version>3.2.1</modelmapper.version> <jjwt.version>0.11.5</jjwt.version> <redisson.version>3.50.0</redisson.version> <hutool.version>5.8.32</hutool.version> 5.4.0 <druid.version>1.2.13</druid.version> <minio.version>8.5.12</minio.version> <taos-jdbcdriver.version>3.5.3</taos-jdbcdriver.version> <xxl-job.version>2.4.1</xxl-job.version> <activiti.version>7.1.0.M6</activiti.version> <springdoc.version>2.1.0</springdoc.version> <californium.version>3.5.0</californium.version> <wechatpay-sdk.version>0.6.0</wechatpay-sdk.version> <alipay-sdk.version>4.34.0.ALL</alipay-sdk.version> <volc-sdk.version>LATEST</volc-sdk.version> <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>jakarta.annotation</groupId> <artifactId>jakarta.annotation-api</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.apache.tomcat</groupId> <artifactId>annotations-api</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-stream-rocketmq</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>${jjwt.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>${redisson.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>${mybatis-plus.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.eclipse.californium</groupId> <artifactId>californium-core</artifactId> <version>${californium.version}</version> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>${fastjson2.version}</version> </dependency> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>${modelmapper.version}</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${poi.version}</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>${minio.version}</version> </dependency> <dependency> <groupId>com.taosdata.jdbc</groupId> <artifactId>taos-jdbcdriver</artifactId> <version>${taos-jdbcdriver.version}</version> </dependency> <dependency> <groupId>com.xuxueli</groupId> <artifactId>xxl-job-core</artifactId> <version>${xxl-job.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti.core.common</groupId> <artifactId>activiti-spring-security</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>${wechatpay-sdk.version}</version> </dependency> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>${alipay-sdk.version}</version> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${springdoc.version}</version> </dependency> <dependency> <groupId>com.volcengine</groupId> <artifactId>volcengine-java-sdk-ark-runtime</artifactId> <version>${volc-sdk.version}</version> </dependency> <dependency> <groupId>com.volcengine</groupId> <artifactId>volc-sdk-java</artifactId> <version>${volc-sdk.version}</version> </dependency> </dependencies> <repositories> <repository> <id>aliyun</id> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-cloud-alibaba</id> <name>Spring Cloud Alibaba Repository</name> <url>https://maven.aliyun.com/repository/spring</url> </repository> </repositories> <build> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> <id>dev</id> <activation> <activeByDefault>true</activeByDefault> </activation> <spring-boot.run.profiles>dev</spring-boot.run.profiles> <id>prod</id> <spring-boot.run.profiles>prod</spring-boot.run.profiles> </configuration> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${encoding}</encoding> </configuration> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> <include>**/*.yml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>application.properties</include> <include>application-${profiles.active}.properties</include> <include>application-${profiles.active}.yml</include> </includes> <filtering>true</filtering> </resource> </resources> </build> <id>dev</id> dev <activation> <activeByDefault>true</activeByDefault> </activation> <id>prod</id> prod 这是我的pom报错了 3.5.7 19:09:36.166 [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig': Unsatisfied dependency expressed through field 'deviceAdminService': Error creating bean with name 'deviceAdminServiceImpl' defined in file [E:\core\main\target\classes\com\example\main\service\device\impl\DeviceAdminServiceImpl.class]: Post-processing of merged bean definition failed 19:09:36.183 [main] INFO o.a.catalina.core.StandardService - Stopping service [Tomcat] 19:09:36.204 [main] WARN o.a.c.loader.WebappClassLoaderBase - The web application [ROOT] appears to have started a thread named [Thread-2] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread: [email protected]/sun.net.dns.ResolverConfigurationImpl.notifyAddrChange0(Native Method) [email protected]/sun.net.dns.ResolverConfigurationImpl$AddressChangeListener.run(ResolverConfigurationImpl.java:176) 19:09:36.224 [main] INFO o.s.b.a.l.ConditionEvaluationReportLogger - Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 19:09:36.246 [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter - *************************** APPLICATION FAILED TO START *************************** Description: An attempt was made to call a method that does not exist. The attempt was made from the following location: org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$LegacyResourceElement.<init>(CommonAnnotationBeanPostProcessor.java:780) The following method did not exist: 'java.lang.String javax.annotation.Resource.lookup()' The calling method's class, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$LegacyResourceElement, was loaded from the following location: jar:file:/E:/maven-repository/org/springframework/spring-context/6.1.10/spring-context-6.1.10.jar!/org/springframework/context/annotation/CommonAnnotationBeanPostProcessor$LegacyResourceElement.class The called method's class, javax.annotation.Resource, is available from the following locations: jar:file:/E:/maven-repository/org/apache/tomcat/annotations-api/6.0.53/annotations-api-6.0.53.jar!/javax/annotation/Resource.class jar:file:/E:/maven-repository/javax/annotation/javax.annotation-api/1.3.2/javax.annotation-api-1.3.2.jar!/javax/annotation/Resource.class The called method's class hierarchy was loaded from the following locations: javax.annotation.Resource: file:/E:/maven-repository/org/apache/tomcat/annotations-api/6.0.53/annotations-api-6.0.53.jar Action: Correct the classpath of your application so that it contains compatible versions of the classes org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$LegacyResourceElement and javax.annotation.Resource Disconnected from the target VM, address: '127.0.0.1:3673', transport: 'socket'

现在这个死信队列消费程序可以获取到日志消费7条后仍存入es批量失败,但是随后我模拟一条存入es成功的或者一条解析格式有问题的,结果消费程序拉取的时候没有拉取到之前没有提交偏移量的存入es失败的7条,只拉取到这后面1条可以成功的,结果这一条成功提交偏移量,把之前7条全部也都给提交了,这7条消息就这样丢失了!为什么。现在的情况是前面7条不会一直拉取,取了几次就不取了,我新插入到死信队列消息时是指定可以成功写入es或者解析失败的消息丢弃(丢弃时,也会提交偏移量)而且consumer拉取了这一条,前面7条不在拉取了,但是确实是没有提交的,这样前面7条也被提交了。问题不是提交第8条偏移量会导致0-7条偏移量全部被标记为"已提交"。问题是消费拉取消息时第二次没有拉取之前7条,反而只拉取了新来的这一条导致消息全部提交,为什么不再拉取旧的失败未提交的消息了?package com.gtjaqh.logprocess.service; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.gtjaqh.logprocess.handle.DeadLetterHandler; import com.gtjaqh.logprocess.utils.DateUtils; import org.apache.kafka.common.TopicPartition; import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; @Service public class RetryService { private static final Logger logger = LoggerFactory.getLogger(RetryService.class); private final ObjectMapper objectMapper; private final ElasticsearchService esService; private final DeadLetterHandler deadLetterHandler; public RetryService(ElasticsearchService esService, ObjectMapper objectMapper, DeadLetterHandler deadLetterHandler) { this.esService = esService; this.objectMapper = objectMapper; this.deadLetterHandler = deadLetterHandler; } @KafkaListener(topics = "#{logConfig.getDeadLetterTopic()}", containerFactory = "kafkaListenerContainerFactory") private void consume(List<ConsumerRecord<String, String>> records, Acknowledgment ack) { if (records.isEmpty()) { ack.acknowledge(); return; } // 1.记录每个分区首次出现的索引(k:分区,v:该分区第一个消息在批次的索引值) Map<TopicPartition, Integer> firstOccurrenceMap = new HashMap<>(); for (int i = 0; i < records.size(); i++) { ConsumerRecord<String, String> record = records.get(i); TopicPartition tp = new TopicPartition(record.topic(), record.partition()); firstOccurrenceMap.putIfAbsent(tp, i); // 只记录首次出现位置 } // 2.按首次出现索引排序分区 List<TopicPartition> sortedPartitions = firstOccurrenceMap.keySet().stream() .sorted(Comparator.comparingInt(firstOccurrenceMap::get)) .toList(); // 3.按分区分组获取分组数据(不排序,k:分区,v:分区内消息) Map<TopicPartition, List<ConsumerRecord<String, String>>> grouped = records.stream() .collect(Collectors.groupingBy( record -> new TopicPartition(record.topic(), record.partition()) )); List<Map<String, Object>> documents; int globalFailureIndex = -1; // 4.按首次出现索引顺序处理各个分区,处理每个分区(按分区起始索引顺序) for (TopicPartition partition : sortedPartitions) { List<ConsumerRecord<String, String>> partitionRecords = grouped.get(partition); // 处理当前分区的消息 documents = new ArrayList<>(partitionRecords.size()); for (ConsumerRecord<String, String> record : partitionRecords) { try { Map<String, Object> jsonMap = objectMapper.readValue(record.value(), new TypeReference<Map<String, Object>>() { }); // 修改消息从死信消费处理的时间字段 jsonMap.put("fetch_timestamp", DateUtils.currentTimeUTC()); jsonMap.put("source", "kafka_deadQueue"); documents.add(jsonMap); } catch (Exception e) { // 5.解析失败的消息丢弃 logger.error("消费死信消息时解析失败,消息来源topic-partition-offset-index={},异常类型={}, 错误={}, 位置={}", record.key(), e.getClass().getSimpleName(), // 异常类型 e.getMessage(), e.getStackTrace().length > 0 ? e.getStackTrace()[0] : "未知位置" // 首行堆栈 ); } } // 6. 当前分区下的同步写入ES(异步写入存在跨索引提交偏移量问题,消息会丢失) if (!documents.isEmpty()) { try { BulkResponse response = esService.bulkIndex(documents).get(30, TimeUnit.SECONDS); if (response != null && response.hasFailures()) { // 处理ES部分失败 globalFailureIndex = findFirstFailedIndex(partitionRecords, response, records); break; } } catch (InterruptedException | ExecutionException | TimeoutException e) { logger.warn("消费死信队列中消息-ES批量写入失败: 数量={}, 原因={}", documents.size(), e.getMessage()); // 整批失败,记录第一个消息的索引作为当前分区的失败点 globalFailureIndex = records.indexOf(partitionRecords.get(0)); break; } } } // 7. 确定提交点(提交点=globalFailureIndex - 1,提交的这条是成功的) if (globalFailureIndex != -1) { int commitIndex = globalFailureIndex - 1; if (commitIndex >= 0) { ack.acknowledge(commitIndex); } else { logger.warn("消费死信队列中消息-首个消息失败,整个批次重试"); } } else { ack.acknowledge(records.size() - 1); logger.info("消费死信队列中消息-ES成功写入,全部提交: count={}", records.size()); } } private int findFirstFailedIndex(List<ConsumerRecord<String, String>> partitionRecords, BulkResponse bulkResponse, List<ConsumerRecord<String, String>> allRecords) { for (BulkItemResponse item : bulkResponse.getItems()) { if (item.isFailed()) { // 获取当前分区下第一个存入ES失败的文档 ConsumerRecord<String, String> failedRecord = partitionRecords.get(item.getItemId()); logger.warn("消费死信队列中消息-ES部分写入失败,提交offset到失败点前: topic={}, partition={}, offset={}, 原因={}", failedRecord.topic(), failedRecord.partition(), failedRecord.offset(), item.getFailureMessage()); return allRecords.indexOf(failedRecord); } } // 不应该执行到这里,因为response.hasFailures()=true logger.error("逻辑错误:ES报告有失败但未找到具体失败项"); // 返回分区第一条的索引,确保整个分区重试 return allRecords.indexOf(partitionRecords.get(0)); } }

package com.alibaba.otter.canal.server.embedded.handle.oracle; import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.meta.FileMixedMetaManager; import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.server.embedded.handle.Constant; import com.alibaba.otter.canal.server.embedded.handle.NotMysqlHandler; import com.alibaba.otter.canal.server.exception.CanalServerException; import com.alibaba.otter.canal.utils.OracleUtil; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.*; import java.util.concurrent.atomic.AtomicLong; /** * @author : yangpeng * create at: 2022/5/21 13:09 * @description: oracle连接实现 */ public class OracleHandler implements NotMysqlHandler { private static final Logger logger = LoggerFactory.getLogger(OracleHandler.class); private final static String ORACLE_DRIVER_URL = "jdbc:oracle:thin:@"; private final static String DATA_DICTIONARY_PATH = "/oracle/oradata/orcl/logmnr"; private Connection connection; private AuthenticationInfo authInfo; private AtomicLong currentScn = new AtomicLong(0); private Map<String, List<String>> pkMaps; private List<String> whiteList; //归档日志本文件最后一个点位-1, 下一个文件的开始点位 private Long nextChange = null; private Long firstChange = null; private String currentModel; private String currentRedoGroups; private String lastRedoGroups; private String opt = ">="; private List<String> action = new ArrayList<>(); static { //加载驱动 try { Class.forName("oracle.jdbc.OracleDriver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override public void subscribe(CanalInstance canalInstance) { //数据库基本信息 CanalEventParser eventParser = canalInstance.getEventParser(); MysqlEventParser mysqlEventParser = (MysqlEventParser) eventParser; this.authInfo = mysqlEventParser.getMasterInfo(); connect(); String filterRegex = authInfo.getFilterRegex(); if(!org.springframework.util.StringUtils.isEmpty(filterRegex) && !filterRegex.endsWith(".*")){ whiteList = Arrays.asList(filterRegex.split(",")); } logger.warn("订阅实例: {}, 过滤规则: {}, 初始 SCN: {}", canalInstance.getDestination(), filterRegex, currentScn.get()); // 新增日志 String index = FileMixedMetaManager.getScnLocal(canalInstance.getDestination()); //首次启动, 文件中的index是0, 同步类型: 全量+增量时使用衔接点位作为开始点位, //增量时衔接点位可能为空, 为空时获取系统最新点位作为开始点位 if("0".equals(index)){ String scn = authInfo.getTimestamp(); if(StringUtils.isEmpty(scn)) scn = getStartPosition(); try { this.currentScn.set(Long.valueOf(scn)); FileMixedMetaManager.saveScnLocal(canalInstance.getDestination(), Long.valueOf(scn)); } catch (IOException e) { e.printStackTrace(); } }else{ //index.scn文件中是非0点位, 说明当前实例运行被停止了或者被删除后实例重建了, 再次启动时比较文件点位和衔接点位 //衔接点位大于文件点位说明是删除后实例重建了, 删除后重建实例应使用最新的衔接点位开始增量同步 //衔接点位小于等于文件点位说明是暂停后重启了, 暂停后重启应使用文件中点位继续增量同步 String scn = authInfo.getTimestamp(); if(!StringUtils.isEmpty(scn) && Long.valueOf(scn) > Long.valueOf(index)){ try { this.currentScn.set(Long.valueOf(scn)); FileMixedMetaManager.saveScnLocal(canalInstance.getDestination(), Long.valueOf(scn)); } catch (IOException e) { e.printStackTrace(); } }else{ opt = ">"; this.currentScn.set(Long.valueOf(index)); } } if(StringUtils.isEmpty(authInfo.getAction())){ action.add("INSERT"); action.add("UPDATE"); action.add("DELETE"); }else{ action = Arrays.asList(authInfo.getAction().split(",")); } logger.warn("开始分析的点位:"+this.currentScn.get()); currentRedoGroups = queryCurrentRedoGroups(); lastRedoGroups = currentRedoGroups; queryLastArchivedLogScn(); authInfo.setArchived(true); updateSettings(); //获取主键 pkMaps = new HashMap<>(); for (String table : whiteList) { List<String> pkNames = getPkNames(authInfo.getDefaultDatabaseName(), table); pkMaps.put(authInfo.getDefaultDatabaseName()+"."+table, pkNames); } } private String queryCurrentRedoGroups(){ //获取当前正在使用的redo日志文件的相关组信息 Statement statement = null; ResultSet resultSet = null; try { statement = connection.createStatement(); resultSet = statement.executeQuery("SELECT GROUP# FROM V$LOG WHERE STATUS = 'CURRENT'"); resultSet.next(); return resultSet.getString(1); } catch (SQLException e) { logger.warn("Query Current Redo Group failed!"); } finally { close(statement, resultSet); } return ""; } private void queryLastArchivedLogScn(){ Statement statement = null; ResultSet resultSet = null; try { logger.warn("获取最后一个归档日志的开始点位和结束点位..."); statement = connection.createStatement(); String sql = "SELECT FIRST_CHANGE#, NEXT_CHANGE# FROM v$archived_log WHERE DEST_ID = 1 ORDER BY RECID DESC"; logger.warn(sql); resultSet = statement.executeQuery(sql); if(resultSet.next()){ firstChange = resultSet.getLong(1); nextChange = resultSet.getLong(2); } } catch (Exception e) { logger.warn("query last one Archive log startScn and endScn failed!"); } finally { close(statement, resultSet); } } private void isArchived(ArrayList<String> logFiles){ Statement statement = null; ResultSet resultSet = null; try { logger.warn("分析归档日志..."); currentModel = "archive"; statement = connection.createStatement(); String sql = "SELECT NAME, FIRST_CHANGE#, NEXT_CHANGE# FROM v$archived_log WHERE DEST_ID = 1 AND " + "FIRST_CHANGE# <= "+currentScn.get()+" AND NEXT_CHANGE# > "+currentScn.get(); logger.warn(sql); logger.warn("### [ARCHIVE] 查询归档日志: {}", sql); resultSet = statement.executeQuery(sql); logger.warn("分析归档日志,当前 SCN: {}, 匹配条件: FIRST_CHANGE# <= {} AND NEXT_CHANGE# > {}", currentScn.get(), currentScn.get(), currentScn.get()); // 新增日志 if(resultSet.next()){ String NAME = resultSet.getString(1); firstChange = resultSet.getLong(2); nextChange = resultSet.getLong(3); logger.warn("找到归档日志文件: {}, SCN 范围: [{}, {})", NAME, firstChange, nextChange); // 新增日志 //添加归档日志 logFiles.add(NAME); if(!Objects.equals(currentRedoGroups, lastRedoGroups)){ lastRedoGroups = currentRedoGroups; } } if(CollectionUtils.isEmpty(logFiles) && !Objects.equals(currentRedoGroups, lastRedoGroups)){ int num = 0; while (true){ resultSet = statement.executeQuery(sql); if(resultSet.next()){ String NAME = resultSet.getString(1); firstChange = resultSet.getLong(2); nextChange = resultSet.getLong(3); //添加归档日志 logFiles.add(NAME); } //在线日志写入到归档异步进行, 可能刚切换归档日志还没有落地, 若没获取到日志文件就阻塞, 最大60秒 if(CollectionUtils.isEmpty(logFiles)){ Thread.sleep(1000L); num++; logger.warn("在线日志写入到归档未完成, 线程已等待: "+num+"秒"); if(num >= 60) { logger.warn("在线日志写入到归档未完成等待60秒未完成, 默认放弃分析本次归档日志, 可能造成数据丢失, 丢失可能发生的点位:"+currentScn.get()); break; } } else{ lastRedoGroups = currentRedoGroups; break; } } } } catch (Exception e) { logger.warn("Archive log parsing failed!"); } finally { close(statement, resultSet); } } private void addLogfile(ArrayList<String> logFiles){ if(CollectionUtils.isEmpty(logFiles)){ Statement statement = null; ResultSet resultSet = null; try { logger.warn("分析在线日志..."); currentModel = "online"; statement = connection.createStatement(); //分析前校验日志组是否发生了变更, 发生变更的话从分析归档开始 String group = queryCurrentRedoGroups(); if(!currentRedoGroups.equals(group)){ currentRedoGroups = group; isArchived(logFiles); }else{ String sql = "SELECT f.member,g.group#,g.FIRST_CHANGE# FROM v$log g left join v$logfile f on g.group# = f.group# " + "where g.FIRST_CHANGE# <= " + currentScn.get() + " AND g.NEXT_CHANGE# > " + currentScn.get(); resultSet = statement.executeQuery(sql); logger.warn(sql); logger.warn("### [ONLINE] 查询在线日志: {}", sql); while (resultSet.next()) { String fileName = resultSet.getString(1); if(!fileName.contains("sredo")){ logFiles.add(fileName); firstChange = resultSet.getLong(3); } } } } catch (SQLException e) { logger.warn("Description Failed to query online logs!"); } finally { close(statement, resultSet); } } } private void offsetLogfile(ArrayList<String> logFiles){ if(CollectionUtils.isEmpty(logFiles)){ Statement statement = null; ResultSet resultSet = null; try { logger.warn("补偿措施, 分析最新的在线日志..."); currentModel = "online"; statement = connection.createStatement(); String sql = "SELECT f.member,g.group#,g.FIRST_CHANGE# FROM v$log g left join v$logfile f on g.group# = f.group# where g.status = 'CURRENT'"; resultSet = statement.executeQuery(sql); logger.warn(sql); while (resultSet.next()) { String fileName = resultSet.getString(1); if(!fileName.contains("sredo")){ logFiles.add(fileName); firstChange = resultSet.getLong(3); } } if(!CollectionUtils.isEmpty(logFiles)){ currentScn.set(firstChange); opt = ">="; } } catch (SQLException e) { logger.warn("Description Failed to query online logs!"); } finally { close(statement, resultSet); } } } private void setSqlTrace() { String setSqlTraceSql = "BEGIN\n" + " DBMS_SESSION.SET_SQL_TRACE(FALSE);\n" + " END;"; logger.warn(setSqlTraceSql); getCallableStatement(setSqlTraceSql); } private void setLogAnalyseDir(){ String createDictSql = "BEGIN dbms_logmnr_d.build(dictionary_filename =>'" + authInfo.getOraName() + "', dictionary_location =>'" + DATA_DICTIONARY_PATH + "'); END;"; logger.warn(createDictSql); getCallableStatement(createDictSql); } private void addAllfile(ArrayList<String> logFiles){ logger.warn("添加 LogMiner 日志文件: {}", logFiles); // 新增日志 StringBuilder sbSQL = new StringBuilder(); //logmnr最大分析文件个数3 for (int i = 0; i < logFiles.size(); i++) { if (i == 0) { sbSQL.append("BEGIN\n"); } sbSQL.append("dbms_logmnr.add_logfile(logfilename=>'").append(logFiles.get(i)).append("',options=>dbms_logmnr.") .append(i == 0 ? "new" : "addfile").append(");\n"); } logFiles.clear(); sbSQL.append("END;\n"); logger.warn(sbSQL.toString()); getCallableStatement(sbSQL.toString()); } public void updateSettings() { ArrayList<String> logFiles = new ArrayList<>(); // 禁用当前会话的SQL跟踪功能 setSqlTrace(); // 设置日志分析目录 // setLogAnalyseDir(); // 是否分析归档日志 isArchived(logFiles); // 分析在线日志 addLogfile(logFiles); // 归档和在线都没有符合点位的文件, 可能是停机久了再次启动时归档文件被迁移或删除了, 从最新的在线日志文件开始点位开始分析 if (logFiles.isEmpty()) offsetLogfile(logFiles); // 归档丢失, 在线不存在, 数据库可能出现故障 if (logFiles.isEmpty()) throw new RuntimeException("The Oracle log file was not read"); // 添加所有日志文件 addAllfile(logFiles); logger.warn("### [LOGMINER-CFG] 模式: {}, 起始SCN: {}, 加载文件: {}", currentModel, currentScn.get(), logFiles); try { // 开启logmnr视图 String startsql = "BEGIN\n" + "dbms_logmnr.start_logmnr(startScn=>'" + currentScn.get() + "'" //开始的scn号 // + ",dictfilename=>'" + DATA_DICTIONARY_PATH + "/" + authInfo.getOraName() + "'" //字典路径 + ",options=>" + " dbms_logmnr.dict_from_online_catalog" + //使用在线数据字典来查看相关的元数据信息 " + dbms_logmnr.skip_corruption" + //跳过损坏的日志快 " + dbms_logmnr.no_sql_delimiter" + //去掉SQL分隔符 " + dbms_logmnr.no_rowid_in_stmt" + //去掉SQL中的ROWID // " + dbms_logmnr.committed_data_only" + //去掉未提交事务的数据 " + dbms_logmnr.string_literals_in_stmt) ;\n" //输出SQL语句中的字符串常量 + "END;"; logger.warn(startsql); logger.warn("### [DICTIONARY] 使用{}数据字典", startsql.contains("dict_from_online_catalog") ? "在线" : "持久化"); getCallableStatement(startsql); logger.warn("dbms_logmnr successfully! 当前分析模式: " + currentModel); } catch (Exception e) { logger.error(e.getMessage(), e); } } private void logSwitchVerify(){ String crgs = queryCurrentRedoGroups(); if(StringUtils.isNotEmpty(crgs) && !crgs.equals(currentRedoGroups)){ logger.warn("### [REDO-SWITCH] 日志组切换! 旧组: {} → 新组: {}", currentRedoGroups, crgs); currentRedoGroups = crgs; unsubscribe(); updateSettings(); } } @Override public synchronized Message getWithoutAck(CanalInstance canalInstance) { try { if (connection.isClosed()) { reconnect(); } } catch (Exception e) { logger.error(e.getMessage(), e); } logSwitchVerify(); List<FlatMessage> data = new ArrayList<>(); Statement statement = null; ResultSet resultSet = null; String sql =""; try { statement = connection.createStatement(); long endScn = currentScn.get(); String queryLogSql = "SELECT scn,operation,sql_redo,table_space,seg_owner,table_name,timestamp,csf FROM v$logmnr_contents " + "where scn %s %d and table_name = '%s' and operation in('INSERT','DELETE','UPDATE') order by scn asc"; //logger.warn(String.format(queryLogSql, opt, currentScn.get(), whiteList.get(0))); resultSet = statement.executeQuery(String.format(queryLogSql, opt, currentScn.get(), whiteList.get(0))); opt = ">"; long lastScn = currentScn.get(); while (resultSet.next()) { endScn = resultSet.getLong("scn"); String operation = resultSet.getString(Constant.operation); if(!action.contains(operation.toUpperCase())) continue; // String segOwner = resultSet.getString(Constant.seg_owner); // String tableSpace = resultSet.getString(Constant.table_space); String tableName = resultSet.getString(Constant.table_name); sql = resultSet.getString(Constant.sql_redo); logger.warn("### [ORACLE-LOG] 解析到原始SQL - SCN: {}, 操作: {}, 表: {}, SQL: {}", endScn, operation, tableName, sql); if(StringUtils.isEmpty(sql)){ logger.error(operation + "->" + tableName+", sql为空默认跳过"); continue; } FlatMessage flatMessage = new FlatMessage(); String timestamp = resultSet.getString(Constant.timestamp); if(!StringUtils.isEmpty(timestamp)){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); long es = sdf.parse(timestamp).getTime() / 1000; flatMessage.setEs(es); }else{ long es = new Date().getTime() / 1000; flatMessage.setEs(es); } flatMessage.setDatabase(authInfo.getDefaultDatabaseName()); flatMessage.setTable(tableName); flatMessage.setIsDdl(false); flatMessage.setType(operation); flatMessage.setTs(System.currentTimeMillis()); flatMessage.setSql(sql); OracleUtil.toMqformat(flatMessage);//转换sql为mq所需json格式 if(null == flatMessage.getData()) { logger.warn("异常SQL: " + sql); continue; } logger.warn("### [CANAL-MSG] 转换后消息 - 类型: {}, 主键: {}, 数据: {}", flatMessage.getType(), flatMessage.getPkNames(), flatMessage.getData()); //主键设置 List<String> list = pkMaps.get(authInfo.getDefaultDatabaseName() + "." + tableName); if(!CollectionUtils.isEmpty(list)){ flatMessage.setPkNames(list); }else{ flatMessage.setPkNames(new ArrayList<>()); } data.add(flatMessage); } if(endScn != currentScn.get()){ logger.warn("### [SCN-UPDATE] 更新SCN: 旧={}, 新={}", currentScn.get(), endScn); currentScn.set(endScn); FileMixedMetaManager.saveScnLocal(canalInstance.getDestination(), currentScn.get()); } //本次的归档日志已分析结束, 结束本次会话, 开始下次分析 if("archive".equals(currentModel) && CollectionUtils.isEmpty(data)){ currentScn.set(nextChange); opt = ">="; FileMixedMetaManager.saveScnLocal(canalInstance.getDestination(), currentScn.get()); logger.warn("本轮归档日志已分析结束, 结束本次会话, 开始下次分析, 开始点位: "+nextChange); unsubscribe(); updateSettings(); } if (!CollectionUtils.isEmpty(data)) { return new Message(lastScn, data, Constant.NOT_MYSQL); } } catch (Exception e) { logger.error("oracle logminer select v$logmnr_contents failed"); logger.warn(sql); logger.error("### [ERROR] SQL解析异常: {}", sql, e); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); // 将出错的栈信息输出到printWriter中 e.printStackTrace(pw); pw.flush(); sw.flush(); try { sw.close(); } catch (IOException ex) { throw new RuntimeException(ex); } pw.close(); unsubscribe(); updateSettings(); } finally { close(statement, resultSet); } return new Message(-1, true, null); } //TRIGGER private String getStartPosition() { String sql = "SELECT MAX(CURRENT_SCN) CURRENT_SCN FROM GV$DATABASE"; Statement statement = null; ResultSet resultSet = null; try { statement = connection.createStatement(); resultSet = statement.executeQuery(sql); while (resultSet.next()) { long scn = resultSet.getLong(1); return String.valueOf(scn); } } catch (SQLException e) { e.printStackTrace(); } finally { close(statement, resultSet); } return "0"; } public List<String> getPkNames(String datasourceName, String tableName) { List<String> pkNames = Lists.newArrayList(); Statement statement = null; ResultSet resultSet = null; logger.warn("### [PRIMARY-KEY] 查询主键: {}.{}", datasourceName, tableName); try { DatabaseMetaData metaData = connection.getMetaData(); resultSet = metaData.getPrimaryKeys(datasourceName, null, tableName); while (resultSet.next()) { String pkName = resultSet.getString("COLUMN_NAME"); // +++ 新增: 记录找到的主键 +++ logger.warn("### [PRIMARY-KEY] 找到主键列: {}", pkName); pkNames.add(pkName); } if (pkNames.isEmpty()) { logger.error("### [PRIMARY-KEY-WARN] 表 {}.{} 未找到主键!", datasourceName, tableName); } else { logger.warn("### [PRIMARY-KEY] 共找到 {} 个主键列", pkNames.size()); } return pkNames; } catch (Exception e) { logger.error(datasourceName+"."+tableName+"oracle get table primary key returns null", e); } finally { close(statement, resultSet); } return null; } private void close(Statement statement, ResultSet resultSet){ try { if (resultSet != null) resultSet.close(); } catch (SQLException e1) { e1.printStackTrace(); } finally { try { if (statement != null) statement.close(); } catch (SQLException e) { e.printStackTrace(); } } } @Override public void connect() { String jdbcUrl = ORACLE_DRIVER_URL + authInfo.getAddress().getHostString() + ":" + authInfo.getAddress().getPort() + ":" + authInfo.getServerName(); try { this.connection = DriverManager.getConnection(jdbcUrl, authInfo.getUsername(), authInfo.getPassword()); logger.warn("成功连接到 Oracle: {}:{}", authInfo.getAddress().getHostString(), authInfo.getAddress().getPort()); // 新增日志 } catch (SQLException e) { e.printStackTrace(); } } @Override public void reconnect() { disconnect(); connect(); } @Override public void disconnect() { if (connection != null) { try { unsubscribe(); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } @Override public void ack(CanalInstance canalInstance) { return; } @Override public boolean checkConsistent(CanalInstance canalInstance) { CanalEventParser eventParser = canalInstance.getEventParser(); MysqlEventParser parser = (MysqlEventParser) eventParser; AuthenticationInfo newAuthInfo = parser.getMasterInfo(); String newUrl = newAuthInfo.getAddress().getHostName() + newAuthInfo.getAddress().getPort(); String oldUrl = this.authInfo.getAddress().getHostName() + this.authInfo.getAddress().getPort(); if (Objects.equals(newUrl, oldUrl)) { return true; } return false; } @Override //回退到指定位置 public void rollback(CanalInstance canalInstance, Long batchId) { try { FileMixedMetaManager.saveScnLocal(canalInstance.getDestination(), batchId); currentScn.set(batchId); } catch (IOException e) { e.printStackTrace(); } logger.warn("回退到 SCN: {}, 实例: {}", batchId, canalInstance.getDestination()); // 新增日志 } public void getCallableStatement(String sql) { CallableStatement callableStatement = null; try { callableStatement = connection.prepareCall(sql); callableStatement.execute(); } catch (SQLException e) { logger.error(e.getMessage(), e); e.printStackTrace(); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); // 将出错的栈信息输出到printWriter中 e.printStackTrace(pw); logger.error("执行 SQL 失败: {}, 错误码: {}, 堆栈: {}", sql, e.getErrorCode(), sw.toString()); // 新增日志 pw.flush(); sw.flush(); try { sw.close(); } catch (IOException ex) { throw new RuntimeException(ex); } pw.close(); if (e.getErrorCode() == 1291) { throw new CanalServerException("1291"); } } finally { close(callableStatement, null); } } /** * description: 关闭查询会话 * * @param {} * @return void */ public void unsubscribe() { logger.warn("### [SESSION-END] 结束会话! 当前SCN: {}", currentScn.get()); String endLogmnrSql = "BEGIN\n" + " dbms_logmnr.end_logmnr;\n" + " END;"; logger.warn(endLogmnrSql); getCallableStatement(endLogmnrSql); logger.warn("end_logmnr succeeded !"); }你觉得增量数据未同步的原因是啥 分析下代码 }

<?xml version="1.0" encoding="UTF-8" ?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml" /> <include resource="org/springframework/boot/logging/logback/console-appender.xml" /> <appender name="LOGEventPublisher" class="org.jetlinks.community.logging.logback.SystemLoggingAppender"/> <appender name="ErrorLOGEventPublisher" class="org.jetlinks.community.logging.logback.SystemLoggingAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> </appender> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <encoder> ${FILE_LOG_PATTERN} <charset>${FILE_LOG_CHARSET}</charset> </encoder> <file>${LOG_FILE}</file> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern> <cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart> <maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize> <totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap> <maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory> </rollingPolicy> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>WARN</level> </filter> </appender> <appender name="AsyncConsoleAppender" class="ch.qos.logback.classic.AsyncAppender"> <queueSize>256</queueSize> <discardingThreshold>0</discardingThreshold> <maxFlushTime>1000</maxFlushTime> <neverBlock>true</neverBlock> <includeCallerData>true</includeCallerData> <appender-ref ref="CONSOLE"/> </appender> <springProfile name="dev"> <logger name="system" level="debug"> <appender-ref ref="LOGEventPublisher"/> </logger> <root level="INFO"> <appender-ref ref="AsyncConsoleAppender"/> <appender-ref ref="ErrorLOGEventPublisher"/> </root> </springProfile> <springProfile name="test"> <root level="INFO"> <appender-ref ref="AsyncConsoleAppender"/> <appender-ref ref="FILE"/> </root> </springProfile> <springProfile name="prod"> <root level="INFO"> <appender-ref ref="AsyncConsoleAppender"/> <appender-ref ref="LOGEventPublisher"/> <appender-ref ref="FILE"/> </root> </springProfile> </configuration>在这基础优化

大家在看

recommend-type

cloudwatch-logback-appender:将签发日志条目发布到AWS CloudWatch的Appender

适用于AWS CloudWatch的Logback日志附加程序 背景 该程序包提供了一个将其日志事件写入Cloudwatch的logback附加程序。 在您说出它之前,似乎有很多这样的项目,但是我发现没有一个项目是独立的并且已经发布到中央Maven存储库中。 可以从获取代码。 Maven软件包通过发布 享受,格雷·沃森 Maven配置 com.j256.cloudwatchlogbackappender cloudwatchlogbackappender &lt;!-- NOTE: change the version to the most recent release version from the re
recommend-type

使用wxWidgets跨平台设计

wxWidgets跨平台设计类库用C++写的啊. 还有使用wxWidgets的总体框架文档.编译并安装的文档搭建Eclipse+CDT+MinGW+wxWidgets开发环境
recommend-type

A First Course in Probability, CN HD, English eBook, Solution Manual

CSDN 好像不支持 0 积分上传,那就 1 积分意思意思吧 A First Course in Probability, 9th Edition, Original eBook 概率论基础教程,高清,9,书签 Solution Manual 答案 拒绝知识垄断,拒绝盗版再收益
recommend-type

物理引擎Havok教程

Havok引擎,全称为Havok游戏动力开发工具包(Havok Game Dynamics SDK),一般称为Havok,是一个用于物理系统方面的游戏引擎,为电子游戏所设计,注重在游戏中对于真实世界的模拟。使用碰撞功能的Havok引擎可以让更多真实世界的情况以最大的拟真度反映在游戏中。
recommend-type

佳博打印机编程手册esc tspl cpcl

佳博打印机编程手册,包括esc、tspl、cpcl指令

最新推荐

recommend-type

python版本基于ChatGLM的飞书机器人.zip

python版本基于ChatGLM的飞书机器人.zip
recommend-type

CSP竞赛动态规划与图论高效代码实现:Dijkstra算法及状态压缩DP的应用与优化

内容概要:本文聚焦于CSP竞赛中从动态规划到图论的高效代码实现,重点介绍了动态规划中的背包问题及其代码实现,通过状态转移方程和滚动数组优化空间复杂度;阐述了状态压缩的概念,特别是位运算表示状态的方法,适用于子集枚举问题;详细讲解了图论中的Dijkstra算法,利用优先队列优化最短路径计算,确保每次取出距离最小的节点,并进行松弛操作更新邻接节点的最短距离。最后展望了多语言支持的发展趋势以及竞赛平台智能化的趋势。; 适合人群:对CSP竞赛感兴趣并有一定编程基础的学生或爱好者,尤其是希望提高算法竞赛水平的参赛者。; 使用场景及目标:①理解动态规划的核心思想,掌握背包问题的状态转移方程和优化技巧;②学会使用位运算进行状态压缩,解决子集枚举问题;③掌握Dijkstra算法的实现细节,理解优先队列的作用和松弛操作的原理。; 阅读建议:本文涉及较多代码实例,建议读者在阅读过程中亲自编写和调试代码,以便更好地理解和掌握相关算法的实现细节。同时关注未来发展趋势,为参加竞赛做好准备。
recommend-type

企业网络结构设计与拓扑图的PKT文件解析

企业网络拓扑设计是网络架构设计的一个重要组成部分,它涉及到企业内部网络的布局结构,确保信息传递的高效和网络安全。网络拓扑设计需要详细规划网络中每个组件的位置、连接方式、设备类型等关键要素。在设计过程中,通常会使用网络拓扑图来形象地表示这些组件和它们之间的关系。 网络拓扑设计中重要的知识点包括: 1. 拓扑图的类型:网络拓扑图主要有以下几种类型,每一种都有其特定的应用场景和设计要求。 - 总线拓扑:所有设备都连接到一条共享的主干线上,信息在全网中广播。适合小型网络,维护成本低,但故障排查较为困难。 - 星型拓扑:所有设备通过点对点连接到一个中心节点。便于管理和监控,中心节点的故障可能导致整个网络瘫痪。 - 环形拓扑:每个节点通过专用链路形成一个闭合环路。信息单向流动,扩展性较差,对单点故障敏感。 - 网状拓扑:网络中的设备通过多条路径连接,提供极高的冗余性。适合大型网络,成本较高。 2. 网络设备的选择:网络设备包括路由器、交换机、防火墙、无线接入点等。设计时需根据实际需求选择适合的设备类型和配置。 3. IP地址规划:合理的IP地址分配能确保网络的有序运行,包括私有地址和公有地址的规划,子网划分,以及IP地址的动态分配(DHCP)和静态分配。 4. 网络安全设计:保护企业网络不受攻击至关重要。包括设置防火墙规则、配置入侵检测系统(IDS)、实施访问控制列表(ACL)等安全策略。 5. 网络冗余和负载均衡:为防止网络中的单点故障,设计时需要考虑使用冗余技术和负载均衡技术,例如多线路接入、链路聚合、VRRP(虚拟路由器冗余协议)等。 6. 物理布线规划:这是指网络中的物理连接方式和布线方案,通常根据实际建筑环境和网络设备位置来决定,包括线缆的种类和长度限制等。 7. 虚拟化和云计算:在现代企业网络设计中,虚拟化技术和云计算服务的集成也变得越来越重要,需要合理规划服务器资源和云服务接口。 从提供的文件描述来看,包含了详细的网络结构设计和拓扑图PKT文件,以及源代码。这说明该课设文件不仅包括了网络拓扑图的视觉展示,还涉及到了实际的网络配置和编程实现。PKT文件可能指的是Packet Tracer(网络模拟软件)生成的网络拓扑文件, Packet Tracer 是Cisco开发的网络模拟工具,用户可以通过它创建网络拓扑图并模拟网络设备之间的数据通信。 由于文件名仅标注为“课设”,我们无法得知具体的课程名称或者网络拓扑设计的具体案例。不过,可以推测这份文件可能是一个教学材料或课程设计项目,旨在帮助学生或者网络工程师通过实践来加深对网络拓扑设计的理解和应用。其中包含的源代码可能是用于配置网络设备(如路由器和交换机的命令脚本),或者是一些网络应用的编程实现,比如网络监控工具或者数据分析脚本等。
recommend-type

【技术解读】:5个步骤深入自定义你的Winform窗口

# 摘要 本文全面概述了Winform窗口自定义开发的关键方面,涵盖了从基础操作到高级功能开发以及性能调优的各个方面。文章首先介绍了Winform窗口的基本操作、设计元素及响应式设计实现方法。随后,深入探讨了自定义控件创建、多线程处理以及插件机制等高级功能开发技术。为了优化交互体验,文中还阐述了用户输入处理、动画效果集成以及高级用户界面交互技术。此外,文章详细讨论了窗口的国
recommend-type

ARM/x86/c86 的具体区别

<think>好的,用户之前问过国产化服务器和操作系统,现在又想知道ARM、x86、x86的具体区别。看起来用户可能是在做技术选型或者需要了解不同架构的优缺点,以便在实际项目中选择合适的硬件和系统。 首先,用户提到的c86可能是个笔误,应该是x86。需要确认这一点,避免误解。然后需要分别从架构、设计理念、应用场景、性能、生态等方面对比ARM和x86。另外,用户可能对国产芯片如鲲鹏、飞腾等基于ARM的处理器感兴趣,所以需要联系之前的回答,说明这些国产芯片的架构背景。 接下来,需要检查技术细节的准确性,比如指令集类型、功耗、扩展性、授权模式等。还要考虑用户可能的实际需求,比如是否需要低功耗设备
recommend-type

最新Swift语言iOS开发实战教程免费下载

标题《Intermediate_swift_ios_12_book》表明了本书是一本关于Swift语言以及iOS 12平台的中阶开发教程。在Swift语言方面,它侧重于深入探讨和实践,旨在帮助读者提升在iOS开发方面的技能水平。自从2014年苹果公司首次推出Swift语言以来,它就成为了开发iOS、macOS、watchOS和tvOS应用的首选语言。Swift语言以其安全、快速、现代的特性逐渐取代了Objective-C,成为苹果生态系统中的主流开发语言。iOS 12作为苹果公司推出的最新操作系统版本,它引入了许多新特性,比如ARKit 2、MeasureKit和新的Screen Time功能,因此开发者需要学习和适应这些变化以充分利用它们。 描述强调了这本书是由Appcoda出版的,Appcoda是一家专注于提供高质量iOS和Swift编程教程的在线平台。通过Appcoda出版的教程,读者通常能够获得紧跟行业标准和实践的教学材料。此书被推荐给希望学习使用最新的Swift语言进行iOS开发的人群。这暗示了该书涵盖了iOS 12的新特性和API,这些内容对于想要掌握最新开发技术的开发者来说至关重要。 标签"ios swift programming practice"则进一步明确了这本书的三个主要知识点:iOS开发、Swift编程和编程实践。这些标签指向了iOS开发的核心技能和知识领域。iOS开发涉及到使用Xcode作为主要的开发环境,掌握使用Interface Builder构建用户界面,以及理解如何使用UIKit框架来创建和管理用户界面。Swift编程则集中在语言本身,包括其基本语法、类型系统、面向协议编程、闭包、泛型等高级特性。编程实践则强调实际编写代码的能力,如编写可测试、可维护和高性能的代码,以及如何使用设计模式来解决常见的开发问题。 文件名称列表中的"Intermediate swift ios12 book.epub"指出了该教程的电子书格式。EPUB是一种广泛使用的电子书标准格式,它支持可调整的布局,使得内容在不同尺寸的屏幕上都可阅读。EPUB格式允许用户在各种阅读设备上阅读书籍,如平板电脑、智能手机、电子书阅读器等。而文件名"._Intermediate swift ios12 book.epub"前面的点和下划线可能表明这是一个隐藏文件或在某种特定环境下被创建的临时文件。 综上所述,知识点涉及: 1. Swift语言基础:Swift是一种安全、快速、现代的编程语言,由苹果公司开发,用于iOS、macOS、watchOS和tvOS应用的开发。Swift语言的特性包括语法简洁、类型安全、内存管理自动化、对闭包和泛型的支持等。 2. iOS 12平台特性:iOS 12作为当时较新的操作系统版本,提供了许多新API和功能,如ARKit 2、MeasureKit等。开发者需要掌握如何在应用中利用这些API实现增强现实(AR)、时间管理等高级功能。 3. Xcode和UIKit框架:Xcode是iOS开发的主要集成开发环境(IDE),它提供了代码编辑器、调试工具、性能分析工具以及用户界面构建器等工具。UIKit框架是构建iOS应用用户界面的基础框架,它提供了丰富的用户界面组件和控件。 4. Swift高级特性和编程实践:学习Swift的高级特性有助于编写高效和可维护的代码。这包括理解闭包的使用、泛型编程、面向协议的设计等。同时,学习和实践良好的编程习惯,如编写可测试的代码、应用设计模式、以及遵循苹果的编码规范和最佳实践。 5. Appcoda及其教程特点:Appcoda是一家提供高质量iOS和Swift编程教程的平台,其教学材料通常紧跟技术发展和行业标准,很适合用于自我学习和提升技能。
recommend-type

【核心攻略】:掌握Winform界面构建的10大黄金法则

# 摘要 Winform界面构建是开发桌面应用程序的重要组成部分,本文从界面布局、数据管理、性能优化、安全性以及进阶技术等多方面进行深入探讨。第一章提供了一个概览,接下来的章节分别详细阐述了如何设计高效的Winform布局,包括布局容器的选择与嵌套布局策略;如何通过数据绑定简化数据管理并保证数据的正确性;以及如何优化界面性能,提高渲染效率并
recommend-type

给我讲解一下boost升压电路

<think>嗯,用户想了解Boost升压电路的工作原理和应用场景。首先,我需要回忆一下Boost电路的基本结构。根据引用[1],Boost升压电路由控制IC、功率电感和MOSFET组成,非同步Boost架构可能更简单,适合用来解释原理。那它的工作原理是怎样的呢? 记得Boost电路属于开关电源,通过开关管的通断来控制电感储能和释放。当MOSFET导通时,电流流过电感,电感开始储能,此时二极管截止,电容负责维持输出电压。而当MOSFET关闭时,电感电流不能突变,会产生反向电动势,这时候电感的电压加上输入电压,通过二极管给电容充电,从而提升输出电压。这个过程需要控制IC来调节开关的占空比,以维
recommend-type

全国国道矢量数据下载与arcgis软件应用

根据提供的文件信息,我们可以生成以下知识点: ### 地理信息系统(GIS) 地理信息系统,简称GIS,是一种特定的、全面的信息系统,它用来捕捉、存储、操纵、分析、管理和呈现地理数据。GIS技术可以对空间数据进行分析,以解决各种地理问题。在GIS中,空间数据通常包括矢量数据和栅格数据。矢量数据是一种图形化的数据格式,用于表示地图上的点、线、面等要素。 ### 国道数据 国道数据特指中国境内的国道信息,国道是指国家主要干线公路,具有连接城市、具有较大运输量、承担全国公路运输主要任务的特点。国道数据可以包括道路的位置、长度、宽度、类型、交通流量等信息。在地理信息系统中,国道数据的准确性对于路线规划、交通管理、城市规划等多个领域至关重要。 ### 矢量数据 矢量数据是GIS中的一个关键概念,它利用几何图形(如点、线、多边形等)来表示真实世界中的物体或区域。矢量数据与栅格数据相对,栅格数据通过像素阵列来表示信息,而矢量数据则通过坐标表示形状和位置。矢量数据具备以下几个特点: - 可无限放大缩小而不失真。 - 在空间分析和拓扑运算方面具有优势。 - 数据量相对较小,易于编辑和管理。 - 可以更好地表达地理要素的属性信息。 ### ArcGIS软件 ArcGIS是由美国Esri公司开发的地理信息系统软件,是业界广泛使用的一套GIS软件平台。ArcGIS提供了众多的工具来捕捉、分析、管理、展示地理信息。用户可以利用ArcGIS进行数据编辑、地图制作、地理分析、数据管理和应用开发等多种操作。ArcGIS支持多种数据格式,包括我们这里提到的矢量数据格式。 ### SHP文件格式 SHP文件格式是一种流行的矢量数据文件格式,它是由Esri公司在其ArcGIS产品中创建的一种空间数据存储格式,用于存储空间和属性信息。SHP文件包含了构成矢量图形的几何形状(点、线、面)和相关的属性信息。每个SHP文件通常都伴随着DBF文件(属性表)和.prj文件(定义空间参考系统的文件)。SHP格式由于其广泛的支持和开放性,成为了交换GIS数据的常用格式之一。 ### 全国国道数据的应用 全国国道数据在GIS中的应用非常广泛,包括但不限于: - **交通规划**:分析国道的通行能力,规划新的交通线路,优化现有路线。 - **应急响应**:在自然灾害或紧急情况中,用于规划救援路线和物资分配。 - **市政建设**:帮助规划城市扩展、土地利用以及基础设施建设。 - **旅游规划**:制定旅游路线,提升旅游服务的便捷性和舒适度。 - **车辆导航**:为导航系统提供精确的道路数据,帮助驾驶者快速到达目的地。 ### 数据处理与分析 利用ArcGIS等GIS软件,用户可以对全国国道数据进行一系列的空间分析和处理。包括但不限于以下几点: - **缓冲区分析**:分析国道周边一定范围内的情况,如人口分布、环境影响等。 - **网络分析**:进行道路连通性分析,为交通管理提供决策支持。 - **叠加分析**:将国道数据与其他地理数据层进行叠加,提取有用信息。 - **数据转换**:将国道数据转换为不同格式或投影,以适应不同的GIS平台或系统。 ### 数据共享与标准化 为了促进数据的共享和再利用,国家和地方制定了相应的GIS数据标准和规范。通过标准化的数据格式,不同的机构和组织可以交换数据,从而在更广泛的范围内发挥GIS数据的作用。对于全国国道数据而言,使用标准格式和协议可以提高数据的互操作性,便于进行区域间的协作与信息交流。 总结来说,全国国道数据作为一种重要的地理信息资源,对于国家交通网络的建设和管理具有重要的参考价值。通过GIS软件,特别是ArcGIS,用户可以充分利用矢量数据格式的优点,进行深入的数据分析和应用开发,从而更好地服务于社会经济发展和公众的日常需要。
recommend-type

Creo 1.0曲面设计宝典:提升模型质量的光顺技巧

# 摘要 本文旨在全面介绍Creo软件在曲面设计领域的应用,涵盖了从理论基础到高级技巧的全方位知识。首先,文章概述了Creo曲面设计的概念和重要性,随后深入探讨了曲面光顺的理论基础,包括曲面的连续性和曲率分析方法,以及光顺技术的数学原理。接着,文章通过实例操作演示了如何在Creo中高效执行曲面光顺,并分享了提高光顺效率的技巧。进一步地,介绍了优化曲面设计的高级技巧,重点在于复杂曲面建模策略和模型优化方法。最后,探讨了曲面设计评审与质量控制的最佳实践,并对曲面设计技术未来的发展趋势进行了展望,强调了新兴技术和人工智能的潜在应用。 # 关键字 Creo曲面设计;曲面光顺;曲面连续性;曲面曲率分析