异步重试_异步重试模式

异步重试

当您有一段经常失败且必须重试的代码时,此Java 7/8库提供了丰富且简洁的API以及针对此问题的快速且可扩展的解决方案:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
RetryExecutor executor = new AsyncRetryExecutor(scheduler).retryOn(SocketException.class).withExponentialBackoff(500, 2).     //500ms times 2 after each retrywithMaxDelay(10_000).               //10 secondswithUniformJitter().                //add between +/- 100 ms randomlywithMaxRetries(20);

现在,您可以运行任意代码块,并且库将为您重试该代码块,以防它抛出SocketException

final CompletableFuture<Socket> future = executor.getWithRetry(() ->new Socket("localhost", 8080)
);future.thenAccept(socket ->System.out.println("Connected! " + socket)
);

请仔细看! getWithRetry()不会阻止。 它立即返回CompletableFuture并异步调用给定的函数。 您可以一次收听该Future甚至是多个Future ,并同时进行其他工作。 因此,这段代码的作用是:尝试连接到localhost:8080 ,如果由于SocketException失败,它将在500毫秒后重试(带有一些随机抖动),每次重试后的延迟加倍,但不超过10秒。

等效但更简洁的语法:

executor.getWithRetry(() -> new Socket("localhost", 8080)).thenAccept(socket -> System.out.println("Connected! " + socket));

这是您可能期望的示例输出:

TRACE | Retry 0 failed after 3ms, scheduled next retry in 508ms (Sun Jul 21 21:01:12 CEST 2013)
java.net.ConnectException: Connection refusedat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]//...TRACE | Retry 1 failed after 0ms, scheduled next retry in 934ms (Sun Jul 21 21:01:13 CEST 2013)
java.net.ConnectException: Connection refusedat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]//...TRACE | Retry 2 failed after 0ms, scheduled next retry in 1919ms (Sun Jul 21 21:01:15 CEST 2013)
java.net.ConnectException: Connection refusedat java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0-ea]//...TRACE | Successful after 2 retries, took 0ms and returned: Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]Connected! Socket[addr=localhost/127.0.0.1,port=8080,localport=46332]

想象一下,您连接到两个不同的系统,一个系统速度 ,第二个系统不可靠并且经常失败:

CompletableFuture<String> stringFuture = executor.getWithRetry(ctx -> unreliable());
CompletableFuture<Integer> intFuture = executor.getWithRetry(ctx -> slow());stringFuture.thenAcceptBoth(intFuture, (String s, Integer i) -> {//both done after some retries
});

当缓慢而又不可靠的系统最终没有任何失败地进行答复时,异步执行thenAcceptBoth()回调。 类似地(使用CompletableFuture.acceptEither() ),您可以同时异步调用两个或多个不可靠的服务器,并在重试几次后第一个成功时会收到通知。

我对此不够强调–重试是异步执行的,并且有效地使用了线程池,而不是盲目地睡眠。

基本原理

通常我们被迫重试给定的代码段,因为它失败了,我们必须再次尝试,通常会稍有延迟以节省CPU。 这项要求非常普遍,并且很少有现成的通用实现,其中RetryTemplate是通过RetryTemplate类在Spring Batch中提供重试支持 。 但是几乎没有其他类似的方法( [1] , [2] )。 所有这些尝试(我敢打赌,你们中的许多人自己都实现了类似的工具!)遇到了同样的问题-它们正在阻塞,从而浪费了大量资源,并且扩展性不好。

本身并不坏因为它使编程模型更加简单-库负责重试,而您只需要等待比平常更长的返回值即可。 但是,这不仅会造成泄漏的抽象(由于重试和延迟,通常非常快的方法突然变得很慢),而且浪费了宝贵的线程,因为此类工具将在重试之间花费大部分时间。 因此
创建了Async-Retry实用程序,该实用程序针对Java 8 (现有Java 7 backport )并解决了上述问题。

主要的抽象是RetryExecutor ,它提供了简单的API:

public interface RetryExecutor {CompletableFuture<Void> doWithRetry(RetryRunnable action);<V> CompletableFuture<V> getWithRetry(Callable<V> task);<V> CompletableFuture<V> getWithRetry(RetryCallable<V> task);<V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task);
}

不必担心RetryRunnableRetryCallable –为方便起见,它们允许使用已检查的异常,并且在大多数情况下,无论如何我们都会使用lambda表达式。

请注意,它返回CompletableFuture 。 我们不再假装调用错误方法很快。 如果库遇到异常,它将使用预先配置的退避延迟重试我们的代码块。 调用时间将从几毫秒飞涨到几秒钟。 CompletableFuture清楚地表明了这一点。 而且,它不是愚蠢的java.util.concurrent.Future我们都知道– Java 8中的CompletableFuture非常强大 ,最重要的是–默认情况下是非阻塞的。

如果您毕竟需要阻止结果,只需在Future对象上调用.get()

基本API

该API非常简单。 您提供了一个代码块,该库将多次运行它,直到它正常返回为止,而不是引发异常。 它也可能在重试之间设置可配置的延迟:

RetryExecutor executor = //...executor.getWithRetry(() -> new Socket("localhost", 8080));

一旦成功连接到localhost:8080将解析返回的CompletableFuture<Socket> 。 (可选)我们可以使用RetryContext来获取额外的上下文,例如当前正在执行的重试:

executor.getWithRetry(ctx -> new Socket("localhost", 8080 + ctx.getRetryCount())).thenAccept(System.out::println);

此代码比看起来更聪明。 在第一次执行期间ctx.getRetryCount()返回0 ,因此我们尝试连接到localhost:8080 。 如果失败,则下一次重试将尝试localhost:80818080 + 1 ),依此类推。 而且,如果您意识到所有这些都是异步发生的,那么您可以扫描多台计算机的端口,并收到有关每个主机上第一个响应端口的通知:

Arrays.asList("host-one", "host-two", "host-three").stream().forEach(host ->executor.getWithRetry(ctx -> new Socket(host, 8080 + ctx.getRetryCount())).thenAccept(System.out::println));

对于每个主机, RetryExecutor将尝试连接到端口8080,并尝试使用更高的端口。

getFutureWithRetry()需要特别注意。 我想重试已经返回CompletableFuture<V> :例如异步HTTP调用的结果:

private CompletableFuture<String> asyncHttp(URL url) { /*...*/}//...final CompletableFuture<CompletableFuture<String>> response = executor.getWithRetry(ctx -> asyncHttp(new URL("http://example.com")));

asyncHttp()传递给getWithRetry()将产生CompletableFuture<CompletableFuture<V>> 。 与它一起工作不仅很尴尬,而且还很麻烦。 该库将仅调用asyncHttp()并仅在失败时重试,但在返回时不重试
CompletableFuture<String>失败。 解决方案很简单:

final CompletableFuture<String> response =executor.getFutureWithRetry(ctx ->asyncHttp(new URL("http://example.com")));

在这种情况下, RetryExecutor将理解从asyncHttp()返回的内容实际上只是一个Future并且将(异步)等待结果或失败。 该库功能更强大,因此让我们深入了解:

配置选项

通常,您可以配置两个重要因素: RetryPolicy ,用于控制是否应进行下一次重试;以及Backoff (可以有选择地增加后续重试之间的延迟)。

默认情况下, RetryExecutor在每个Throwable上无限重复用户任务,并在RetryExecutor重试之间增加1秒的延迟。

创建

RetryExecutor默认实现是AsyncRetryExecutor ,您可以直接创建:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();RetryExecutor executor = new AsyncRetryExecutor(scheduler);//...scheduler.shutdownNow();

唯一需要的依赖项是JDK的标准ScheduledExecutorService 。 在许多情况下,一个线程就足够了,但是如果您要同时处理数百个或更多任务的重试,请考虑增加池大小。

注意, AsyncRetryExecutor不会关闭ScheduledExecutorService 。 这是一个有意识的设计决策,将在后面进行解释。

AsyncRetryExecutor几乎没有其他构造函数,但是在大多数情况下,更改类的行为最方便的方法是with*()方法链接调用。 您将看到大量以此方式编写的示例。 稍后,我们将仅使用executor引用而不定义它。 假设它是RetryExecutor类型。

重试政策

例外情况

默认情况下,从用户任务抛出的每个Throwable (特殊AbortRetryException除外)都会导致重试。 显然,这是可配置的。 例如,在JPA中,您可能想重试由于OptimisticLockException而失败的事务-但其他所有异常都应立即失败:

executor.retryOn(OptimisticLockException.class).withNoDelay().getWithRetry(ctx -> dao.optimistic());

其中dao.optimistic()可能会引发OptimisticLockException 。 在这种情况下,您可能不希望重试之间有任何延迟,以后再说。 如果您不喜欢在每个Throwable上重试的默认设置,只需使用retryOn()来限制它:

executor.retryOn(Exception.class)

当然,也可能需要相反的做法–中止重试并在抛出某些异常的情况下立即失败而不是重试。 就这么简单:

executor.abortOn(NullPointerException.class).abortOn(IllegalArgumentException.class).getWithRetry(ctx -> dao.optimistic());

显然,您不想重试NullPointerExceptionIllegalArgumentException因为它们指示编程错误,而不是瞬时失败。 最后,您可以结合使用重试和中止策略。 如果发生任何retryOn()异常(或子类),则用户代码将重试,除非它应该abortOn()指定的异常。 例如,我们想重试每个IOExceptionSQLException但是如果遇到FileNotFoundExceptionjava.sql.DataTruncation (顺序无关),则中止:

executor.retryOn(IOException.class).abortIf(FileNotFoundException.class).retryOn(SQLException.class).abortIf(DataTruncation.class).getWithRetry(ctx -> dao.load(42));

如果这还不够,您可以提供将在每次失败时调用的自定义谓词:

executor.abortIf(throwable ->throwable instanceof SQLException &&throwable.getMessage().contains("ORA-00911"));

最大重试次数

中断重试“循环”的另一种方法(请记住此过程是异步的,没有阻塞循环 )是通过指定最大重试次数:

executor.withMaxRetries(5)

在极少数情况下,您可能希望禁用重试,而几乎不利用异步执行。 在这种情况下,请尝试:

executor.dontRetry()

重试之间的延迟(退避)

有时需要在失败后立即重试(请参阅OptimisticLockException示例),但是在大多数情况下,这是一个坏主意。 如果您无法连接到外部系统,请稍等片刻,然后再尝试下一次尝试。 您可以节省CPU,带宽和其他服务器的资源。 但是有很多选择要考虑:

  • 我们应该以固定的间隔重试还是增加每次失败后的延迟 ?
  • 轮候时间是否应该有上限和下限?
  • 我们是否应该添加随机“抖动”来延迟时间以及时分散许多任务的重试?

该库回答了所有这些问题。

重试间隔固定

默认情况下,每次重试之前都有1秒的等待时间。 因此,如果初始尝试失败,则将在1秒后执行第一次重试。 当然,我们可以将默认值更改为200毫秒:

executor.withFixedBackoff(200)

如果我们已经在此处,则默认情况下,执行用户任务后将应用退避。 如果用户任务本身消耗一些时间,则重试的频率将降低。 例如,重试延迟为RetryExecutor毫秒,而用户任务失败所需的平均时间约为50毫秒, RetryExecutor将每秒重试约4次(50毫秒+ RetryExecutor毫秒)。 但是,如果要将重试频率保持在更可预测的水平,则可以使用fixedRate标志:

executor.withFixedBackoff(200).withFixedRate()

这类似于ScheduledExecutorService “固定速率”与“固定延迟”方法。 顺便说一句,不要期望RetryExecutor会非常精确,这是最好的,但是在很大程度上取决于前面提到的ScheduledExecutorService准确性。

重试之间的间隔呈指数增长

它可能是一个活跃的研究主题,但是总的来说,您可能希望随着时间的推移扩展重试延迟,假设如果用户任务多次失败,我们应该减少尝试次数。 例如,假设我们从100ms的延迟开始,直到进行第​​一次重试为止,但是如果该尝试也失败了,我们应该再等待两次(200ms)。 再过400毫秒,800毫秒……您就会明白:

executor.withExponentialBackoff(100, 2)

这是一个指数函数,可以快速增长。 因此,将最大退避时间设置在某个合理的水平(例如10秒)非常有用:

executor.withExponentialBackoff(100, 2).withMaxDelay(10_000)      //10 seconds

随机抖动

在严重停机期间经常观察到的一种现象是系统趋于同步。 想象一个繁忙的系统突然停止响应。 成百上千的请求失败并被重试。 这取决于您的退避,但默认情况下,所有这些请求都会在一秒钟后重试,而一秒钟会产生大量流量。 最后,此类故障会传播到其他系统,这些系统又会进行同步。

为避免此问题,随着时间的推移扩展重试,使负载平坦化是很有用的。 一个简单的解决方案是添加随机抖动来延迟时间,以便并非所有请求都计划在完全相同的时间重试。 您可以在均匀抖动(随机值从-100ms到100ms)之间进行选择:

executor.withUniformJitter(100)     //ms

…和比例抖动,将延迟时间乘以随机因子,默认情况下为0.9到1.1(10%):

executor.withProportionalJitter(0.1)        //10%

您还可以对延迟时间设置严格的下限,以避免安排较短的重试时间:

executor.withMinDelay(50)   //ms

实施细节

该库是在考虑Java 8的情况下构建的,以利用lambda和新的CompletableFuture抽象(但存在具有Guava依赖项的Java 7 port )。 它在下面使用ScheduledExecutorService来运行任务并计划将来的重试-这样可以最大程度地利用线程。

但是真正有趣的是,整个库是完全不变的,根本没有单个可变字段。 起初这可能是违反直觉的,例如以以下简单代码示例为例:

ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();AsyncRetryExecutor first = new AsyncRetryExecutor(scheduler).retryOn(Exception.class).withExponentialBackoff(500, 2);AsyncRetryExecutor second = first.abortOn(FileNotFoundException.class);AsyncRetryExecutor third = second.withMaxRetries(10);

似乎所有with*()方法或retryOn() / abortOn()方法retryOn()现有执行程序变异。 但是事实并非如此,每次配置更改都会创建一个新实例 ,而旧实例则保持不变。 因此,例如,当first执行者将重试FileNotFoundExceptionsecondthird执行者则不会。 但是,它们都共享同一scheduler 。 这就是AsyncRetryExecutor不关闭ScheduledExecutorService (甚至没有任何close()方法)的原因。 由于我们不知道有多少个AsyncRetryExecutor副本指向同一调度程序,因此我们甚至不尝试管理其生命周期。 但是,这通常不是问题(请参见下面的Spring集成 )。

您可能想知道,为什么这么笨拙的设计决定? 有以下三个原因:

  • 在编写并发代码时,不变性可以大大降低多线程错误的风险。 例如, RetryContext保留重试次数。 但是,我们无需更改变量,而只需创建具有递增但final计数器的新实例(副本)。 没有比赛条件或能见度。
  • 如果给您现有的RetryExecutor几乎完全是您想要的,但是您需要进行一些细微调整,则只需调用executor.with...()并获取一个新副本。 您不必担心使用同一执行程序的其他地方(请参阅: Spring集成以获取更多示例)
  • 如今,功能编程和不可变数据结构非常流行

注意: AsyncRetryExecutor 标记为final ,您可以通过将其子类化并添加可变状态来打破不变性。 请不要这样做,子类只允许更改行为。

依存关系

该库需要Java 8和SLF4J进行记录。 Java 7端口还取决于Guava 。

Spring整合

如果您即将在Spring中使用RetryExecutor ,请随时使用,但配置API可能对您不起作用。 Spring通过大量的设置来促进(或用于促进)可变服务的约定。 在XML中,您定义bean并在其上调用setter(通过<property name="..."/> )。 该约定假定存在变异设置器。 但是我发现这种方法在某些情况下容易出错并且违反直觉。

假设我们全局定义了org.springframework.transaction.support.TransactionTemplate bean并将其注入到多个位置。 大。 现在有一个请求,它的超时要求略有不同:

@Autowired
private TransactionTemplate template;

后来在同一个班级:

final int oldTimeout = template.getTimeout();
template.setTimeout(10_000);
//do the work
template.setTimeout(oldTimeout);

此代码在许多级别上都是错误的! 首先,如果发生故障,我们将永远不会恢复oldTimeout 。 好了, finally救了。 但还要注意我们如何更改全局共享的TransactionTemplate实例。 谁知道不知道更改配置的其他几个bean和线程将要使用它?

即使您确实想全局更改事务超时,也足够公平,但是这样做仍然是错误的方法。 private timeout字段不是volatile ,因此对其进行的更改对于其他线程可能可见,也可能不可见。 真是一团糟! 同样的问题出现在许多其他类(如JmsTemplate

你知道我要去哪里吗? 只需创建一个不变的服务类,并在需要时通过创建副本来安全地对其进行调整。 现在,使用此类服务​​同样简单:

@Configuration
class Beans {@Beanpublic RetryExecutor retryExecutor() {return new AsyncRetryExecutor(scheduler()).retryOn(SocketException.class).withExponentialBackoff(500, 2);}@Bean(destroyMethod = "shutdownNow")public ScheduledExecutorService scheduler() {return Executors.newSingleThreadScheduledExecutor();}}

嘿! 进入21世纪,我们在Spring不再需要XML。 Bootstrap也很简单:

final ApplicationContext context = new AnnotationConfigApplicationContext(Beans.class);
final RetryExecutor executor = context.getBean(RetryExecutor.class);
//...
context.close();

如您所见,将现代的,不可变的服务与Spring集成非常简单。 顺便说一句,如果您在设计自己的服务时没有准备好进行如此大的更改,请至少考虑构造函数注入 。

到期

该库包含大量的单元测试。 但是,尚未在任何生产代码中使用它,并且该API可能会更改。 当然,我们鼓励您提交错误,功能请求和提取请求 。 它是在考虑到Java 8的情况下开发的,但是Java 7 backport存在一些更冗长的API和强制性Guava依赖关系( ListenableFuture而不是
Java 8的CompletableFuture )。

GitHub上的完整源代码。

参考:来自Java和社区博客的JCG合作伙伴 Tomasz Nurkiewicz的异步重试模式 。

翻译自: https://www.javacodegeeks.com/2013/08/asynchronous-retry-pattern.html

异步重试

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

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

相关文章

linux网卡e1000下载,Linux E1000网卡驱动分析

本分析主要针对e1000网卡,驱动源码为7.3.20-k2。本文的目的不是为了讲述如何编写驱动程序,主要是分析网卡驱动内部的实现机制。Linux-千兆网卡驱动实现机制浅析作者: Minit, 出处:博客, 责任编辑: 罗丽艳,2009-03-29 00:001.引言本分析主要针对e1000网卡&#xff0c;驱动源码为…

迈入JavaWeb第一步,Java网络编程基础,TCP网络编程URL网络编程等

文章目录网络编程概述网络通信要素要素一IP和端口号要素二网络协议TCP网络编程UDP网络编程URL网络编程Java网络编程基础网络编程概述 Java是Internet上的语言&#xff0c;它从语言级上提供了对网络应用程序的支持&#xff0c;程序员能够很容易开发常见的网络应用程序。 Java提…

Linux脚本双引号字符去除,shell命令去除字符串里双引号

echo \"refs/changes/84/727284/1\" | sed s/\"//gshell变量内字符替换和变量字符修改a12345123#将${a}里的第一个123替换为321b${a/123/321};echo "echo variable a"echo $aecho "echo variable b"echo $ba12345123#将${a}里的所有123替换…

一个小例子体会Java反射的动态性

背景&#xff1a;需要在程序运行时或者说某段代码运行后才能知道要创建哪个类的对象。 import java.util.Random;/*** Author: Yeman* Date: 2021-10-02-22:18* Description:*/ public class ReflectionTest {public static Object getInstance(String path) throws Exception…

Linux脚本让我选择文件,linux – 用于选择文件和打印文件大小的Awk脚本

我们在这看……select all regular files (not directories or links)到目前为止,你还没有解决这个问题,但如果你在ls -l …的输出中输入,这很容易,请选择/^-/因为目录以d开头,符号链接以l开头,依此类推.只有普通旧文件以 – 开头.现在print out the number of files followed好…

12000+字Java反射,一起全面了解Java反射机制,为学习框架铺路

文章目录Java反射机制理解Class类获取Class类实例类的加载过程类加载器ClassLoader创建运行时类的对象获取运行时类的结构调用运行时类的指定结构动态代理Java反射机制 Reflection是被视为动态语言的关键&#xff0c;反射机制允许程序在执行期借助于Reflection API取得任何类的…

坚实原则:Liskov替代原则

以前&#xff0c;我们深入研究了坚实的原则&#xff0c;包括单一责任和开放/封闭原则。 Liskov替代原则&#xff08;LSP&#xff09;是子类型关系的一种特殊定义&#xff0c;称为&#xff08;强&#xff09;行为子类型&#xff0c; 假设对象S是对象T的子类型&#xff0c;则可…

linux 中 id指令,Linux id 命令

要登入一台计算机&#xff0c;我们需要一个用户名。用户名是一个可以被计算机识别的身份。基于此&#xff0c;计算机会对使用这个用户名的登陆的人应用一系列的规则。在Linux系统下&#xff0c;我们可以使用 id 命令。什么是 id 命令id 命令可以显示真实有效的用户 ID(UID) 和组…

提高Java表达能力!不落伍一起掌握Java8中Lambda表达式、函数式接口及方法构造器数组引用

文章目录函数式接口概述函数式接口示例内置函数式接口Lambda简述Lambda语法方法引用构造器引用数组引用函数式接口概述 只包含一个抽象方法的接口&#xff0c;称为函数式接口。 可以通过 Lambda 表达式来创建该接口的对象。 可以在一个接口上使用 FunctionalInterface 注解&…

linux启动weblogic指令,linux下如何启动和关闭weblogic .

在你定义的域中可以找到如下命令&#xff1a;/[youHome]/domains/[yourDomain]/startWebLogic.sh/[youHome]/domains/[yourDomain]/stopWebLogic.sh用telnet远程控制服务器&#xff0c; 远程启动WEBLOGIC服务&#xff0c;启动后关闭telnet&#xff0c;WebLogic服务也跟着停止&a…

一文一起,学习功能强大的Java8新StreamAPI,让集合的操作得心应手

前些天发现了十分不错的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;没有广告&#xff0c;分享给大家&#xff0c;大家可以自行看看。&#xff08;点击跳转人工智能学习资料&#xff09; 文章目录Stream 概述Stream 实例化Stream 中间操作Stream 终止…

java erlang_Java开发人员的Erlang

java erlang您可能没有注意到&#xff0c;但是距离我上次发布博客已经过去了几个星期。 这是由于我的Soleus骨折了&#xff0c;而且我的腿是石膏模型。 不能动弹&#xff0c;我认为调查完全不同的东西是个好主意–要么看那一天&#xff0c;要么看白天的电视&#xff0c;尽管Koj…

linux 取消混杂模式,Linux下网卡混杂模式设置和取消

1、Linux下网卡常用的几种模式说明&#xff1a;广播方式&#xff1a;该模式下的网卡能够接收网络中的广播信息。组播方式&#xff1a;设置在该模式下的网卡能够接收组播数据。直接方式&#xff1a;在这种模式下&#xff0c;只有目的网卡才能接收该数据。混杂模式&#xff1a;在…

新容器!不再担心空指针, Java8 Optional类

Optional<T>类(java.util.Optional)是一个容器类&#xff0c;它可以保存类型T的值&#xff0c;代表这个值存在。或者仅仅保存null&#xff0c;表示这个值不存在。原来用 null 表示一个值不存在&#xff0c;现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常…

在Docker容器中的Tomcat上运行ADF Essentials

我经常开发示例应用程序。 我尝试了一些想法&#xff0c;运用了一些技巧&#xff0c;并通过示例应用程序与同事和博客读者分享了我的调查结果。 当某人想要了解该技术的实现方式时&#xff0c;他们只需查看源代码&#xff0c;就足以理解这个想法。 但是&#xff0c;如果他们想了…

非cpu0启动linux,SD卡无法启动Linux的问题及解决

最近在Zynq板上启动linaro桌面系统时&#xff0c;发现一个奇怪的问题&#xff0c;系统在启动到最后&#xff0c;已经打印了如下信息&#xff1a;[drm] Initialized axi_hdmi_drm 1.0.0 20120930 on minor 0&#xff0c;按理说后面就该是登录系统并显示shell提示符了&#xff0c…

新!详细!win10下MySQL数据库干净卸载安装与配置

微信公众号&#xff1a;创享日记 发送&#xff1a;sqlyog 获取SQLyog客户端安装包 文章目录官方下载卸载清除安装变量配置测试SQLyog客户端下载官方下载 链接直达&#xff1a;点击官方下载 1、选择 2、下载&#xff1a;点击download进入下载页面&#xff0c;选择“No thanks,…

linux系统基本使用教程,Linux系统的基本使用入门

终端&#xff1a;用户与主机交互&#xff0c;必然会使用到的设备&#xff0c;在Linux中也使用文件进行标识物理终端:直接接入到本机的显示器和键盘设备&#xff1a;又叫控制台&#xff1a;console物理终端设备文件&#xff1a;/dev/console虚拟终端&#xff1a;附加在物理终端之…

最基础,MySQL基础查询SELECT

前些天发现了十分不错的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;没有广告&#xff0c;分享给大家&#xff0c;大家可以自行看看。&#xff08;点击跳转人工智能学习资料&#xff09; 1、查询表中字段 USE 要操作的数据库名; SELECT 查询的字段 …

linux编码 form表单,Linux以form表单形式上传文件讲解

先cd到要上传文件的目录下&#xff1a;rootiZ2zee1przeygbuu4rkwvxZ:/usr/local/test# ls1.png DSC_0192.JPG test.apk使用curl -F 或 curl --formcurl --form "fileDSC_0192.JPG" http://ip:port/projectName/busnews/upload*注意&#xff1a;不一定写file&#xff…