kafka 主动消费_Kafka核心API——Consumer消费者

Consumer之自动提交

在上文中介绍了Producer API的使用,现在我们已经知道如何将消息通过API发送到Kafka中了,那么现在的生产者/消费者模型就还差一位扮演消费者的角色了。因此,本文将介绍Consumer API的使用,使用API从Kafka中消费消息,让应用成为一个消费者角色。

还是老样子,首先我们得创建一个Consumer实例,并指定相关配置项,有了这个实例对象后我们才能进行其他的操作。代码示例:

/**

* 创建Consumer实例

*/

public static Consumer createConsumer() {

Properties props = new Properties();

// 指定Kafka服务的ip地址及端口

props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127。0.0.1:9092");

// 指定group.id,Kafka中的消费者需要在消费者组里

props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "test");

// 是否开启自动提交

props.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");

// 自动提交的间隔,单位毫秒

props.setProperty(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");

// 消息key的序列化器

props.setProperty(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,

"org.apache.kafka.common.serialization.StringDeserializer");

// 消息value的序列化器

props.setProperty(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,

"org.apache.kafka.common.serialization.StringDeserializer");

return new KafkaConsumer<>(props);

}

在以上代码中,可以看到设置了group.id这个配置项,这是一个Consumer的必要配置项,因为在Kafka中,Consumer需要位于一个Consumer Group里。具体如下图所示:

在上图中是一个Consumer消费一个Partition,是一对一的关系。但Consumer Group里可以只有一个Consumer,此时该Consumer可以消费多个Partition,是一对多的关系。如下图所示:

一个Consumer可以只消费一个Partition,也可以消费多个Partition,但需要注意的是多个Consumer不能消费同一个Partition:

总结一下Consumer的注意事项:

单个Partition的消息只能由Consumer Group中的某个Consumer来消费

Consumer从Partition中消费消息是顺序的,默认从头开始消费

如果Consumer Group中只有一个Consumer,那么这个Consumer会消费所有Partition中的消息

在Kafka中,当消费者消费数据后,需要提交数据的offset来告知服务端成功消费了哪些数据。然后服务端就会移动数据的offset,下一次消费的时候就是从移动后的offset位置开始消费。

这样可以在一定程度上保证数据是被消费成功的,并且由于数据不会被删除,而只是移动数据的offset,这也保证了数据不易丢失。若消费者处理数据失败时,只要不提交相应的offset,就可以在下一次重新进行消费。

和数据库的事务一样,Kafka消费者提交offset的方式也有两种,分别是自动提交和手动提交。在本例中演示的是自动提交,这也是消费数据最简单的方式。代码示例:

/**

* 演示自动提交offset

*/

public static void autoCommitOffset() {

Consumer consumer = createConsumer();

List topics = List.of("MyTopic");

// 订阅一个或多个Topic

consumer.subscribe(topics);

while (true) {

// 从Topic中拉取数据,每1000毫秒拉取一次

ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));

// 每次拉取可能都是一组数据,需要遍历出来

for (ConsumerRecord record : records) {

System.out.printf("partition = %d, offset = %d, key = %s, value = %s%n",

record.partition(), record.offset(), record.key(), record.value());

}

}

}

Consumer之手动提交

自动提交的方式是最简单的,但不建议在实际生产中使用,因为可控性不高。所以更多时候我们使用的是手动提交,但想要使用手动提交,就需要先关闭自动提交,修改配置项如下:

props.setProperty(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");

关闭了自动提交后,就得在代码中调用commit相关的方法来提交offset,主要就是两个方法:commitAsync和commitSync,看方法名也知道一个是异步提交一个是同步提交。

这里以commitAsync为例,实现思路主要是在发生异常的时候不要调用commitAsync方法,而在正常执行完毕后才调用commitAsync方法。代码示例:

/**

* 演示手动提交offset

*/

public static void manualCommitOffset() {

Consumer consumer = createConsumer();

List topics = List.of("MyTopic");

// 订阅一个或多个Topic

consumer.subscribe(topics);

while (true) {

// 从Topic中拉取数据,每1000毫秒拉取一次

ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));

// 每次拉取可能都是一组数据,需要遍历出来

for (ConsumerRecord record : records) {

try {

// 模拟将数据写入数据库

Thread.sleep(1000);

System.out.println("save to db...");

System.out.printf("partition = %d, offset = %d, key = %s, value = %s%n",

record.partition(), record.offset(), record.key(), record.value());

} catch (Exception e) {

// 写入失败则不要调用commit,这样就相当于起到回滚的作用,

// 下次消费还是从之前的offset开始消费

e.printStackTrace();

return;

}

}

// 写入成功则调用commit相关方法去手动提交offset

consumer.commitAsync();

}

}

##针对Partition提交offset

在前文中有介绍到,一个Consumer Group里可以只有一个Consumer,该Consumer可以消费多个Partition。在这种场景下,我们可能会在Consumer中开启多线程去处理多个Partition中的数据,以提高性能。

为了防止某些Partition里的数据消费成功,而某些Partition里的数据消费失败,却都一并提交了offset。我们就需要针对单个Partition去提交offset,也就是将offset的提交粒度控制在Partition级别。

这里先简单演示一下如何针对单个Partition提交offset,代码示例:

/**

* 演示手动提交单个Partition的offset

*/

public static void manualCommitOffsetWithPartition() {

Consumer consumer = createConsumer();

List topics = List.of("MyTopic");

// 订阅一个或多个Topic

consumer.subscribe(topics);

while (true) {

// 从Topic中拉取数据,每1000毫秒拉取一次

ConsumerRecords records = consumer.poll(Duration.ofMillis(1000));

// 单独处理每一个Partition中的数据

for (TopicPartition partition : records.partitions()) {

System.out.println("======partition: " + partition + " start======");

// 从Partition中取出数据

List> partitionRecords = records.records(partition);

for (ConsumerRecord record : partitionRecords) {

try {

// 模拟将数据写入数据库

Thread.sleep(1000);

System.out.println("save to db...");

System.out.printf("partition = %d, offset = %d, key = %s, value = %s%n",

record.partition(), record.offset(), record.key(), record.value());

} catch (Exception e) {

// 发生异常直接结束,不提交offset

e.printStackTrace();

return;

}

}

// 执行成功则取出当前消费到的offset

long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();

// 由于下一次开始消费的位置是最后一次offset+1的位置,所以这里要+1

OffsetAndMetadata metadata = new OffsetAndMetadata(lastOffset + 1);

// 针对Partition提交offset

Map offsets = new HashMap<>();

offsets.put(partition, metadata);

// 同步提交offset

consumer.commitSync(offsets);

System.out.println("======partition: " + partition + " end======");

}

}

}

Consumer针对一个或多个Partition进行订阅

在之前的例子中,我们都是针对Topic去订阅并消费数据,实际上也可以更细粒度一些针对Partition进行订阅,这通常应用在一个Consumer多线程消费的场景下。代码示例:

/**

* 演示将订阅粒度控制到Partition级别

* 针对单个或多个Partition进行订阅

*/

public static void manualCommitOffsetWithPartition2() {

Consumer consumer = createConsumer();

// 该Topic中有两个Partition

TopicPartition p0 = new TopicPartition("MyTopic", 0);

TopicPartition p1 = new TopicPartition("MyTopic", 1);

// 订阅该Topic下的一个Partition

consumer.assign(List.of(p0));

// 也可以订阅该Topic下的多个Partition

// consumer.assign(List.of(p0, p1));

while (true) {

...与上一小节中的代码一致,略...

}

}

Consumer多线程并发处理

前面两个小节的内容基本都是为了本小节所介绍的多线程并发处理消息而铺垫的,因为为了提高应用对消息的处理效率,我们通常会使用多线程来并行消费消息,从而加快消息的处理速度。

而多线程处理消息的方式主要有两种,一种是按Partition数量创建线程,然后每个线程里创建一个Consumer,多个Consumer对多个Partition进行消费。这就和之前在介绍Consumer Group时,给出的那张图所展示的一样:

这种属于是经典模式,实现起来也比较简单,适用于对消息的顺序和offset控制有要求的场景。代码示例:

package com.zj.study.kafka.consumer;

import org.apache.kafka.clients.consumer.ConsumerRecord;

import org.apache.kafka.clients.consumer.ConsumerRecords;

import org.apache.kafka.clients.consumer.KafkaConsumer;

import org.apache.kafka.clients.consumer.OffsetAndMetadata;

import org.apache.kafka.common.TopicPartition;

import org.apache.kafka.common.errors.WakeupException;

import java.time.Duration;

import java.util.Collections;

import java.util.List;

import java.util.Properties;

import java.util.concurrent.atomic.AtomicBoolean;

/**

* 经典模式

*

* @author 01

* @date 2020-05-21

**/

public class ConsumerThreadSample {

private final static String TOPIC_NAME = "MyTopic";

/**

* 这种类型是经典模式,每一个线程单独创建一个KafkaConsumer,用于保证线程安全

*/

public static void main(String[] args) throws InterruptedException {

KafkaConsumerRunner r1 = new KafkaConsumerRunner();

Thread t1 = new Thread(r1);

t1.start();

Thread.sleep(15000);

r1.shutdown();

}

public static class KafkaConsumerRunner implements Runnable {

private final AtomicBoolean closed = new AtomicBoolean(false);

private final KafkaConsumer consumer;

public KafkaConsumerRunner() {

Properties props = new Properties();

props.put("bootstrap.servers", "192.168.220.128:9092");

props.put("group.id", "test");

props.put("enable.auto.commit", "false");

props.put("auto.commit.interval.ms", "1000");

props.put("session.timeout.ms", "30000");

props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

consumer = new KafkaConsumer<>(props);

TopicPartition p0 = new TopicPartition(TOPIC_NAME, 0);

TopicPartition p1 = new TopicPartition(TOPIC_NAME, 1);

consumer.assign(List.of(p0, p1));

}

@Override

public void run() {

try {

while (!closed.get()) {

//处理消息

ConsumerRecords records = consumer.poll(Duration.ofMillis(10000));

for (TopicPartition partition : records.partitions()) {

List> pRecord = records.records(partition);

// 处理每个分区的消息

for (ConsumerRecord record : pRecord) {

System.out.printf("patition = %d , offset = %d, key = %s, value = %s%n",

record.partition(), record.offset(), record.key(), record.value());

}

// 返回去告诉kafka新的offset

long lastOffset = pRecord.get(pRecord.size() - 1).offset();

// 注意加1

consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));

}

}

} catch (WakeupException e) {

if (!closed.get()) {

throw e;

}

} finally {

consumer.close();

}

}

public void shutdown() {

closed.set(true);

consumer.wakeup();

}

}

}

另一种多线程的消费方式则是在一个线程池中只创建一个Consumer实例,然后通过这个Consumer去拉取数据后交由线程池中的线程去处理。如下图所示:

但需要注意的是在这种模式下我们无法手动控制数据的offset,也无法保证数据的顺序性,所以通常应用在流处理场景,对数据的顺序和准确性要求不高。

经过之前的例子,我们知道每拉取一次数据返回的就是一个ConsumerRecords,这里面存放了多条数据。然后我们对ConsumerRecords进行迭代,就可以将多条数据交由线程池中的多个线程去并行处理了。代码示例:

package com.zj.study.kafka.consumer;

import org.apache.kafka.clients.consumer.ConsumerRecord;

import org.apache.kafka.clients.consumer.ConsumerRecords;

import org.apache.kafka.clients.consumer.KafkaConsumer;

import java.util.List;

import java.util.Properties;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.ThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

/**

* 一个Consumer,多个hander模式

*

* @author 01

* @date 2020-05-21

**/

public class ConsumerRecordThreadSample {

private final static String TOPIC_NAME = "MyTopic";

public static void main(String[] args) throws InterruptedException {

String brokerList = "192.168.220.128:9092";

String groupId = "test";

int workerNum = 5;

ConsumerExecutor consumers = new ConsumerExecutor(brokerList, groupId, TOPIC_NAME);

consumers.execute(workerNum);

Thread.sleep(1000000);

consumers.shutdown();

}

/**

* Consumer处理

*/

public static class ConsumerExecutor {

private final KafkaConsumer consumer;

private ExecutorService executors;

public ConsumerExecutor(String brokerList, String groupId, String topic) {

Properties props = new Properties();

props.put("bootstrap.servers", brokerList);

props.put("group.id", groupId);

props.put("enable.auto.commit", "true");

props.put("auto.commit.interval.ms", "1000");

props.put("session.timeout.ms", "30000");

props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");

consumer = new KafkaConsumer<>(props);

consumer.subscribe(List.of(topic));

}

public void execute(int workerNum) {

executors = new ThreadPoolExecutor(workerNum, workerNum, 0L, TimeUnit.MILLISECONDS,

new ArrayBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy());

while (true) {

ConsumerRecords records = consumer.poll(200);

for (final ConsumerRecord record : records) {

executors.submit(new ConsumerRecordWorker(record));

}

}

}

public void shutdown() {

if (consumer != null) {

consumer.close();

}

if (executors != null) {

executors.shutdown();

}

try {

if (executors != null && !executors.awaitTermination(10, TimeUnit.SECONDS)) {

System.out.println("Timeout.... Ignore for this case");

}

} catch (InterruptedException ignored) {

System.out.println("Other thread interrupted this shutdown, ignore for this case.");

Thread.currentThread().interrupt();

}

}

}

/**

* 记录处理

*/

public static class ConsumerRecordWorker implements Runnable {

private ConsumerRecord record;

public ConsumerRecordWorker(ConsumerRecord record) {

this.record = record;

}

@Override

public void run() {

// 假如说数据入库操作

System.out.println("Thread - " + Thread.currentThread().getName());

System.err.printf("patition = %d , offset = %d, key = %s, value = %s%n",

record.partition(), record.offset(), record.key(), record.value());

}

}

}

Consumer控制offset起始位置

上一小节中介绍的第二种多线程消息模式,通过Consumer拉取数据后交由多线程去处理是没法控制offset的,如果此时程序出现错误或其他意外情况导致消息没有被正确消费,我们就需要人为控制offset的起始位置重新进行消费。

通过调用seek方法可以指定从哪个Partition的哪个offset位置进行消费,代码示例:

/**

* 手动控制offset的起始位置

*/

public static void manualCommitOffsetWithPartition2() {

Consumer consumer = createConsumer();

TopicPartition p0 = new TopicPartition("MyTopic", 0);

consumer.assign(List.of(p0));

// 指定offset的起始位置

consumer.seek(p0, 1);

while (true) {

...与上一小节中的代码一致,略...

}

}

实际应用中的设计思路:

第一次从某个offset的起始位置进行消费

如果本次消费了100条数据,那么offset设置为101并存入Redis等缓存数据库中

后续每次poll之前,从Redis中获取offset值,然后从这个offset的起始位置进行消费

消费完后,再次将新的offset值存入Redis,周而复始

Consumer限流

为了避免Kafka中的流量剧增导致过大的流量打到Consumer端将Consumer给压垮的情况,我们就需要针对Consumer进行限流。例如,当处理的数据量达到某个阈值时暂停消费,低于阈值时则恢复消费,这就可以让Consumer保持一定的速率去消费数据,从而避免流量剧增时将Consumer给压垮。大体思路如下:

在poll到数据之后,先去令牌桶中拿取令牌

如果获取到令牌,则继续业务处理

如果获取不到令牌,则调用pause方法暂停Consumer,等待令牌

当令牌桶中的令牌足够,则调用resume方法恢复Consumer的消费状态

接下来编写具体的代码案例简单演示一下这个限流思路,令牌桶算法使用Guava里内置的,所以需要在项目中添加对Guava的依赖。添加的依赖项如下:

com.google.guava

guava

29.0-jre

然后我们就可以使用Guava的限流器对Consumer进行限流了,代码示例:

public class ConsumerCurrentLimiting {

/*** 令牌生成速率,单位为秒 */

public static final int permitsPerSecond = 1;

/*** 限流器 */

private static final RateLimiter LIMITER = RateLimiter.create(permitsPerSecond);

/**

* 创建Consumer实例

*/

public static Consumer createConsumer() {

... 与之前小节的代码类似,略 ...

}

/**

* 演示对Consumer限流

*/

public static void currentLimiting() {

Consumer consumer = createConsumer();

TopicPartition p0 = new TopicPartition("MyTopic", 0);

TopicPartition p1 = new TopicPartition("MyTopic", 1);

consumer.assign(List.of(p0, p1));

while (true) {

// 从Topic中拉取数据,每100毫秒拉取一次

ConsumerRecords records = consumer.poll(Duration.ofMillis(1));

if (records.isEmpty()) {

continue;

}

// 限流

if (!LIMITER.tryAcquire()) {

System.out.println("无法获取到令牌,暂停消费");

consumer.pause(List.of(p0, p1));

} else {

System.out.println("获取到令牌,恢复消费");

consumer.resume(List.of(p0, p1));

}

// 单独处理每一个Partition中的数据

for (TopicPartition partition : records.partitions()) {

System.out.println("======partition: " + partition + " start======");

// 从Partition中取出数据

List> partitionRecords = records.records(partition);

for (ConsumerRecord record : partitionRecords) {

try {

// 模拟将数据写入数据库

Thread.sleep(1000);

System.out.println("save to db...");

System.out.printf("partition = %d, offset = %d, key = %s, value = %s%n",

record.partition(), record.offset(), record.key(), record.value());

} catch (Exception e) {

// 发生异常直接结束,不提交offset

e.printStackTrace();

return;

}

}

// 执行成功则取出当前消费到的offset

long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();

// 由于下一次开始消费的位置是最后一次offset+1的位置,所以这里要+1

OffsetAndMetadata metadata = new OffsetAndMetadata(lastOffset + 1);

// 针对Partition提交offset

Map offsets = new HashMap<>();

offsets.put(partition, metadata);

// 同步提交offset

consumer.commitSync(offsets);

System.out.println("======partition: " + partition + " end======");

}

}

}

public static void main(String[] args) {

currentLimiting();

}

}

Consumer Rebalance解析

Consumer有个Rebalance的特性,即重新负载均衡,该特性依赖于一个协调器来实现。每当Consumer Group中有Consumer退出或有新的Consumer加入都会触发Rebalance。

之所以要重新负载均衡,是为了将退出的Consumer所负责处理的数据再重新分配到组内的其他Consumer上进行处理。或当有新加入的Consumer时,将组内其他Consumer的负载压力,重新进均匀分配,而不会说新加入一个Consumer就闲在那。

下面就用几张图简单描述一下,各种情况触发Rebalance时,组内成员是如何与协调器进行交互的。

1、新成员加入组(member join):

Tips:图中的Coordinator是协调器,而generation则类似于乐观锁中的版本号,每当成员入组成功就会更新,也是起到一个并发控制的作用

2、组成员崩溃/非正常退出(member failure):

3、组成员主动离组/正常退出(member leave group):

4、当Consumer提交位移(member commit offset)时,也会有类似的交互过程:

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

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

相关文章

android下拉弹性gif,android-pulltorefresh 下拉加载中使用gif动图

效果预览&#xff1a;xml布局xmlns:ptr"http://schemas.android.com/apk/res-auto"android:layout_width"fill_parent"android:layout_height"fill_parent"android:divider"#00000000"android:cacheColorHint"#00000000"and…

linux本地时间与utc不一致_Linux下CST和UTC时间的区别以及不一致的解决方法

1.在linux中&#xff0c;用date查看时间的时候显示&#xff1a;2013年 2月 17日 星期三 19:04:14 CST世界协调时间(Universal Time Coordinated,UTC):GPS 系统中有两种时间区分&#xff0c;一为UTC&#xff0c;另一为LT(地方时)两者的区别为时区不同&#xff0c;UTC就是0时区的…

ios uiview 如何刷新_ios-如何在Swift中刷新imageview而无需重新加载页面?

我有一个更新照片按钮,该按钮连接到facebookgraphAPI并下载当前用户的个人资料图片.我希望刷新视图上的图像,而无需重新加载viewController.有什么办法可以做到这一点&#xff1f;我知道tableview具有reloadData和refreshcontrol,但是我没有使用tableview.class ProfileViewCon…

unity3d android aar,Android Stuido导出AAR 给Unity3D调用注意事项

1. 导出 AAR过程需要注意&#xff0c;依赖的文件并没有打进去&#xff0c;所以要收到拷入到 Unity项目中&#xff0c;如下图中的 support包&#xff0c;是我项目中要用到的&#xff1a;2.删除aar包中重复的 libs下的 jar文件和 jni下的 .so文件&#xff0c;否则调用的时候会报…

识别产品外观的合格软件_产品外观质量视觉检测系统.PDF

产品外观质量视觉检测系统产品外观质量视觉检测系统北京大恒图像视觉有限公司Beijing Daheng Image Vision Co., Ltd公司简介中国大恒(集团)有限公司是中国科学院于 1987 年报经国务院批准创办的高新技术企业&#xff0c;公司于 1996 年至2007 年连续十二年进入全国电子百强企业…

html中图片响应式怎么写,如何使用 HTML5 的picture元素处理响应式图片

图片在响应式网页设计中是出了名的最具挑战性的方面之一。今天我们就来看看如何使用元素来处理响应式图片.让我们先了解一下问题固定宽度&#xff0c;像素完美的网站设计已经离我们远去了。在宽屏显示器&#xff0c;互联网电视&#xff0c;多尺寸的平板电脑和智能手机的今天&am…

安卓 多条通知_安卓11第一版发布:原生滚动截屏、屏幕录像、抄国内ROM这么多...

自2008年第一部Android智能手机HTC G1发布&#xff0c;安卓手机系统已经走过十几个年头。虽然系统存在一些大家吐槽较多的问题&#xff0c;但安卓一直活跃在智能手机系统前沿&#xff0c;不断发展完善着。昨天谷歌刚刚发布了全新的安卓11新系统&#xff0c;这次的系统更新出一些…

python樱花手绘_GitHub 硬核项目:动漫生成器让照片秒变手绘日漫风!!!

根据真实店铺照片生成的效果图&#xff0c;一度以为&#xff0c;这就是某个日漫番剧的截图本文转自&#xff1a;机器之心随手拍张照片&#xff0c;顺势转换为宫崎骏、新海诚等日漫大师的手绘风格作品&#xff0c;这个专门生成动漫图像的 GAN&#xff0c;实测很好用。尽管最近 2…

html表格字体格式转换,【转】常用HTML代码速查表

在搏客中比较常用的代码一、美化文字的代码&#xff1a;1.文字间换行&#xff1a;文字内容文字内容2.一个段落&#xff1a;文字内容3.字体加粗&#xff1a;文字内容4.字体加大&#xff1a;文字内容5.文字标题&#xff1a; 从一号标题H1到六号标题H6&#xff0c;逐渐减小文字内容…

70个python毕设项目_56个具有开创性的Python开源项目-开始使用Python

Python正在蓬勃发展&#xff0c;它的Gistub页面也是如此。今年对于Python来说很棒&#xff0c;我们看到了一些非常强大的python开源项目。今天&#xff0c;我们将列出一些最好的python开源项目&#xff1b;尝试至少对其中一个项目做出贡献&#xff0c;这将有助于提高您的Python…

mysqlmodify_modify与change的区别

对mysql的表的表结构进行修改时&#xff0c;有用到change&#xff0c;modify两个&#xff0c;它们都有“改变”的意思&#xff0c;那它们在功能上有什么区别了&#xff1f;做个试验比较下1、字段重命名&#xff1a;1)changemysql> alter table t1 change number id char(2);…

qnetworkreply 获取状态_谈谈Unity Shader中的采样器状态和(Texture Filtering)纹理滤波方式

参考文章&#xff1a;使用采样器状态 - Unity 手册​docs.unity3d.comhttps://blog.csdn.net/chenjinxian_3D/article/details/51816918​blog.csdn.net根据Unity的参考文档&#xff0c;Unity Shader使用采样器状态有三种方式&#xff0c;分别是&#xff1a;耦合的纹理和采样器…

python记录日志_记录python日志

在线运行的应用就是黑盒子&#xff0c;需要被跟踪监控。最简单也最重要的方式就是记录日志。记录日志允许我们在开发软件的同时&#xff0c;让程序在系统运行时发出信息&#xff0c;这些信息对于我们和系统管理员来说都是有用的。就像为将来的程序员写代码文档一样&#xff0c;…

android systemui机制,在AS中开发SystemUI(4):设置项目运行机制

1、期望项目如何运行&#xff1f;普通 App 在 run 之后&#xff0c;会编译出 APK&#xff0c;然后 AS 会自动调用 install 命令进行安装&#xff0c;完成安装后会启动 App 的 Launcher Activity。这些 AS 已经帮你自动完成了。如果你的 App 不像 SystemUI 或 Settings 这样贴近…

手机屏幕厂家信息软件_警惕假个税手机软件蹭热点,千万别被窃取私人信息

新个税法从1月初开始实施。2018年12月31日&#xff0c;国家税务总局推出“个人所得税”APP&#xff0c;方便纳税人线上填报资料进行专项抵扣。几天来&#xff0c;这款APP的下载量和注册量大幅增长。随之而来的是&#xff0c;很多商业公司制作的各类“个税”APP也成为热门。这其…

paramiko执行nohup_记一次使用django+paramiko远程操作时报错无法返回问题

前提&#xff1a;以前能力不足&#xff0c;只能用linux命令行形式写了个线上发布工具。采用的是paramiko来调用远程指令。最近自学了点前端的东西&#xff0c;打算用django写一个web版的发布工具&#xff0c;在做正常异步远程操作时候发现都没有什么问题。但是当调用我们游戏的…

图片动画效果html5,8个实用炫酷的HTML5图片动画应用

原标题&#xff1a;8个实用炫酷的HTML5图片动画应用近期我们发布了不少关于HTML5和jQuery的图片动画应用&#xff0c;很多都比较实用&#xff0c;也有一些效果非常炫酷&#xff0c;比如一些HTML5 3D图片动画特效。本文精选了8个实用而且炫酷的HTML5图片动画应用&#xff0c;希望…

ado execute open区别_二极管IN4148和IN4007的应用区别

二极管IN4148和IN4007的定义1N4148 是开关二极管&#xff0c;耐压100V&#xff0c;电流150mA&#xff0c;反向恢复速度快。1N4007 是普通整流二极管&#xff0c;耐压1000V&#xff0c;电流1A &#xff0c;反向恢复时间在ms级别&#xff0c;只能用于低频电路中。二极管IN4148和I…

cmd imp导入dmp文件_cmd 导入oracle数据的dmp文件

在前面已经安装好orcale&#xff0c;现在导入数据库并开始使用。步骤如下&#xff1a;设置表空间自动扩容登录 用system as sysdba 登录 密码为空查询表SYSTEM表空间的数据文件的物理路径&#xff0c;语句为SELECT FILE_NAME FROM DBA_DATA_FILES WHERE (TABLESPACE_NAME SYST…

html列目录带图片,根据目录下的图片的个数,往html文件填充对应数量的img标签,请问有没有实现这种需求的工具?...

这直接JS就能实现了&#xff0c;实现方式分为【预加载】和【延迟加载】下面是个预加载的例子&#xff1a;首先来一个空的HTML页面和最基本的CSS初始化样式Document* {margin: 0;padding: 0;}.imgwrap li {list-style-type: none;}img {vertical-align: top;}然后我们要往标签里…