作者:じ☆ve宝贝
eclipse中使用maven插件的时候,运行run as maven build的时候报错 ` -Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match. ` ####直接的解决方法:使用低版本的maven 可以设一个环境变量M2_HOME指向你的maven安装目录 ` M2_HOME=D:\Apps\apache-maven-3.3.1 ` 然后在 `Window->Preference->Java->Installed JREs->Edit` 在Default VM arguments中设置 `-Dmaven.multiModuleProjectDirectory=$M2_HOME` 
作者:じ☆ve宝贝
> springboot+jsp 出现如下错误:No Java compiler available for configuration options compilerClassName: [null] and compiler: [null] ``` <dependency> <groupId>org.eclipse.jdt.core.compiler</groupId> <artifactId>ecj</artifactId> <version>4.6.1</version> <scope>provided</scope> </dependency> ```
作者:微信小助手
<p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(255, 0, 0);font-size: 14px;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(255, 41, 65);line-height: 22.4px;">(点击</span><span style="max-width: 100%;line-height: 22.4px;color: rgb(0, 128, 255);">上方公众号</span><span style="max-width: 100%;color: rgb(255, 41, 65);line-height: 22.4px;">,可快速关注)</span></span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;text-align: justify;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <blockquote style="max-width: 100%;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;text-align: justify;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;word-wrap: break-word !important;"> <p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;font-size: 14px;box-sizing: border-box !important;word-wrap: break-word !important;">来源:CarpenterLee ,</span></p> <p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;font-size: 14px;box-sizing: border-box !important;word-wrap: break-word !important;">www.cnblogs.com/CarpenterLee/p/9558026.html</span></p> </blockquote> <p><br></p> <p>构造一个线程池为什么需要几个参数?如果避免线程池出现OOM?Runnable和Callable的区别是什么?本文将对这些问题一一解答,同时还将给出使用线程池的常见场景和代码片段。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">基础知识</span></strong></p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">Executors创建线程池</span></strong></p> <p><br></p> <p>Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThreads),但是便捷不仅隐藏了复杂性,也为我们埋下了潜在的隐患(OOM,线程耗尽)。</p> <p><br></p> <p>Executors创建线程池便捷方法列表:</p> <p><br></p> <p style="text-align: center;"><img class="" data-ratio="0.28976377952755905" data-s="300,640" src="/upload/6c947e007a6f0e97ce3fc9792d6c18e4.png" data-type="png" data-w="1270" style=""></p> <p><br></p> <p>小程序使用这些快捷方法没什么问题,对于服务端需要长期运行的程序,创建线程池应该直接使用ThreadPoolExecutor的构造方法。没错,上述Executors方法创建的线程池就是ThreadPoolExecutor。</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">ThreadPoolExecutor构造方法</span></strong></p> <p><br></p> <p>Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">// Java线程池的完整构造函数</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">public ThreadPoolExecutor(</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> int maximumPoolSize, // 线程数的上限</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> // 超过这个时间,多余的线程会被回收。</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> BlockingQueue<Runnable> workQueue, // 任务的排队队列</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> ThreadFactory threadFactory, // 新线程的产生方式</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> RejectedExecutionHandler handler) // 拒绝策略</span></p> </blockquote> <p><br></p> <p>竟然有7个参数,很无奈,构造一个线程池确实需要这么多参数。这些参数中,比较容易引起问题的有corePoolSize, maximumPoolSize, workQueue以及handler:</p> <p><br></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p>corePoolSize和maximumPoolSize设置不当会影响效率,甚至耗尽线程;</p></li> <li><p>workQueue设置不当容易导致OOM;</p></li> <li><p>handler设置不当会导致提交任务时抛出异常。</p></li> </ul> <p><br></p> <p>正确的参数设置方式会在下文给出。</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">线程池的工作顺序</span></strong></p> <p><br></p> <blockquote> <p><span style="font-size: 14px;">If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.</span></p> <p><span style="font-size: 14px;">If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.</span></p> <p><span style="font-size: 14px;">If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.</span></p> </blockquote> <p><br></p> <p>corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">Runnable和Callable</span></strong></p> <p><br></p> <p>可以向线程池提交的任务有两种:Runnable和Callable,二者的区别如下:</p> <p><br></p> <ol class=" list-paddingleft-2" style="list-style-type: decimal;"> <li><p>方法签名不同,void Runnable.run(), V Callable.call() throws Exception</p></li> <li><p>是否允许有返回值,Callable允许有返回值</p></li> <li><p>是否允许抛出异常,Callable允许抛出异常。</p></li> </ol> <p><br></p> <p>Callable是JDK1.5时加入的接口,作为Runnable的一种补充,允许有返回值,允许抛出异常。</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">三种提交任务的方式:</span></strong></p> <p><br></p> <p style="text-align: center;"><img class="" data-ratio="0.2579113924050633" data-s="300,640" src="/upload/c7fe0212fcffa230bc918991c88fedec.png" data-type="png" data-w="1264" style=""></p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">如何正确使用线程池</span></strong></p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">避免使用无界队列</span></strong></p> <p><br></p> <p>不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为这种方式会使用无界的任务队列,为避免OOM,我们应该使用ThreadPoolExecutor的构造方法手动指定队列的最大长度:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">ExecutorService executorService = new ThreadPoolExecutor(2, 2, </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> 0, TimeUnit.SECONDS, </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> new ArrayBlockingQueue<>(512), // 使用有界队列,避免OOM</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> new ThreadPoolExecutor.DiscardPolicy());</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">明确拒绝任务时的行为</span></strong></p> <p><br></p> <p>任务队列总有占满的时候,这是再submit()提交新的任务会怎么样呢?RejectedExecutionHandler接口为我们提供了控制方式,接口定义如下:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">public interface RejectedExecutionHandler {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> void rejectedExecution(Runnable r, ThreadPoolExecutor executor);</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">}</span></p> </blockquote> <p><br></p> <p>线程池给我们提供了几种常见的拒绝策略:</p> <p><br></p> <p style="text-align: center;"><img class="" data-ratio="0.20957095709570958" data-s="300,640" src="/upload/96b4c5c2aad53c095b8572c64838b45.png" data-type="png" data-w="1212" style=""></p> <p><br></p> <p style="text-align: center;"><img class="" data-ratio="0.27672955974842767" data-s="300,640" src="/upload/97903ec57d97cb26f39aa5ff3c38a6ce.png" data-type="png" data-w="1272" style=""></p> <p><br></p> <p>线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionHandler异常,该异常是非受检异常,很容易忘记捕获。如果不关心任务被拒绝的事件,可以将拒绝策略设置成DiscardPolicy,这样多余的任务会悄悄的被忽略。</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">ExecutorService executorService = new ThreadPoolExecutor(2, 2, </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> 0, TimeUnit.SECONDS, </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> new ArrayBlockingQueue<>(512), </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> new ThreadPoolExecutor.DiscardPolicy());// 指定拒绝策略</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">获取处理结果和异常</span></strong></p> <p><br></p> <p>线程池的处理结果、以及处理过程中的异常都被包装到Future中,并在调用Future.get()方法时获取,执行过程中的异常会被包装成ExecutionException,submit()方法本身不会传递结果和任务执行过程中的异常。获取执行结果的代码可以这样写:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">ExecutorService executorService = Executors.newFixedThreadPool(4);</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">Future<Object> future = executorService.submit(new Callable<Object>() {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> @Override</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> public Object call() throws Exception {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> throw new RuntimeException("exception in call~");// 该异常会在调用Future.get()时传递给调用者</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> }</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> });</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">try {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> Object result = future.get();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">} catch (InterruptedException e) {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> // interrupt</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">} catch (ExecutionException e) {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> // exception in Callable.call()</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> e.printStackTrace();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">}</span></p> </blockquote> <p><br></p> <p>上述代码输出类似如下:</p> <p><br></p> <p style="text-align: center;"><img class="" data-ratio="0.27967479674796747" data-s="300,640" src="/upload/5a7180d2781bc160c23964644aa70c06.png" data-type="png" data-w="1230" style=""></p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">线程池的常用场景</span></strong></p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">正确构造线程池</span></strong></p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">int poolSize = Runtime.getRuntime().availableProcessors() * 2;</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">executorService = new ThreadPoolExecutor(poolSize, poolSize,</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> 0, TimeUnit.SECONDS,</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> queue,</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> policy);</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">获取单个结果</span></strong></p> <p><br></p> <p>过submit()向线程池提交任务后会返回一个Future,调用V Future.get()方法能够阻塞等待执行结果,V get(long timeout, TimeUnit unit)方法可以指定等待的超时时间。</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">获取多个结果</span></strong></p> <p><br></p> <p>如果向线程池提交了多个任务,要获取这些任务的执行结果,可以依次调用Future.get()获得。但对于这种场景,我们更应该使用ExecutorCompletionService,该类的take()方法总是阻塞等待某一个任务完成,然后返回该任务的Future对象。向CompletionService批量提交任务后,只需调用相同次数的CompletionService.take()方法,就能获取所有任务的执行结果,获取顺序是任意的,取决于任务的完成顺序:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">void solve(Executor executor, Collection<Callable<Result>> solvers)</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> throws InterruptedException, ExecutionException {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> CompletionService<Result> ecs = new ExecutorCompletionService<Result>(executor);// 构造器</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> for (Callable<Result> s : solvers)// 提交所有任务</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> ecs.submit(s);</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> int n = solvers.size();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> for (int i = 0; i < n; ++i) {// 获取每一个完成的任务</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> Result r = ecs.take().get();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> if (r != null)</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> use(r);</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> }</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">}</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">单个任务的超时时间</span></strong></p> <p><br></p> <p>V Future.get(long timeout, TimeUnit unit)方法可以指定等待的超时时间,超时未完成会抛出TimeoutException。</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">多个任务的超时时间</span></strong></p> <p><br></p> <p>等待多个任务完成,并设置最大等待时间,可以通过CountDownLatch完成:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">public void testLatch(ExecutorService executorService, List<Runnable> tasks) </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> throws InterruptedException{</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> CountDownLatch latch = new CountDownLatch(tasks.size());</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> for(Runnable r : tasks){</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> executorService.submit(new Runnable() {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> @Override</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> public void run() {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> try{</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> r.run();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> }finally {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> latch.countDown();// countDown</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> }</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> }</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> });</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> }</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> latch.await(10, TimeUnit.SECONDS); // 指定超时时间</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> }</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">线程池和装修公司</span></strong></p> <p><br></p> <p>以运营一家装修公司做个比喻。公司在办公地点等待客户来提交装修请求;公司有固定数量的正式工以维持运转;旺季业务较多时,新来的客户请求会被排期,比如接单后告诉用户一个月后才能开始装修;当排期太多时,为避免用户等太久,公司会通过某些渠道(比如人才市场、熟人介绍等)雇佣一些临时工(注意,招聘临时工是在排期排满之后);如果临时工也忙不过来,公司将决定不再接收新的客户,直接拒单。</p> <p><br></p> <p>线程池就是程序中的“装修公司”,代劳各种脏活累活。上面的过程对应到线程池上:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">// Java线程池的完整构造函数</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">public ThreadPoolExecutor(</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> int corePoolSize, // 正式工数量</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> int maximumPoolSize, // 工人数量上限,包括正式工和临时工</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> long keepAliveTime, TimeUnit unit, // 临时工游手好闲的最长时间,超过这个时间将被解雇</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> BlockingQueue<Runnable> workQueue, // 排期队列</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> ThreadFactory threadFactory, // 招人渠道</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> RejectedExecutionHandler handler) // 拒单方式</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">总结</span></strong></p> <p><br></p> <p>Executors为我们提供了构造线程池的便捷方法,对于服务器程序我们应该杜绝使用这些便捷方法,而是直接使用线程池ThreadPoolExecutor的构造方法,避免无界队列可能导致的OOM以及线程个数限制不当导致的线程数耗尽等问题。ExecutorCompletionService提供了等待所有任务执行结束的有效方式,如果要设置等待的超时时间,则可以通过CountDownLatch完成。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">参考</span></strong></p> <p><br></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p>ThreadPoolExecutor API Doc</p><p>https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadPoolExecutor.html<br></p></li> </ul> <p><br></p> <section class="" powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;text-align: justify;white-space: normal;background-color: rgb(255, 255, 255);word-wrap: break-word !important;"> <section class="" style="margin-top: 10px;margin-bottom: 10px;max-width: 100%;box-sizing: border-box;text-align: left;word-wrap: break-word !important;"> <section class="" style="padding: 10px;max-width: 100%;box-sizing: border-box;display: inline-block;width: 668px;border-width: 1px;border-style: solid;border-color: rgb(226, 226, 226);box-shadow: rgb(226, 226, 226) 0px 16px 1px -13px;word-wrap: break-word !important;"> <section class="" powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section class="" style="max-width: 100%;box-sizing: border-box;word-wrap: break-word !important;"> <section class="" style="max-width: 100%;box-sizing: border-box;color: rgb(93, 93, 93);word-wrap: break-word !important;"> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;word-wrap: break-word !important;">【关于投稿】</p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;word-wrap: break-word !important;">如果大家有原创好文投稿,请直接给公号发送留言。</p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;word-wrap: break-word !important;"><span style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;">① 留言格式:</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;">【投稿】+《 文章标题》+ 文章链接</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;"><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;">② 示例:</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;">【投稿】《不要自称是程序员,我十多年的 IT 职场总结》:http://blog.jobbole.com/94148/</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;"><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;word-wrap: break-word !important;">③ 最后请附上您的个人简介哈~</span></span></p> <p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> </section> </section> </section> </section> </section> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;text-align: justify;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;word-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;"></p> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;font-size: 14px;color: rgb(255, 169, 0);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;">看完本文有收获?请转发分享给更多人</span></p> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;word-wrap: break-word !important;"><strong style="max-width: 100%;color: rgb(255, 169, 0);box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;">关注「ImportNew」,提升Java技能</strong></p> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 17px;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;word-wrap: break-word !important;"><img class="" data-ratio="0.9166666666666666" data-s="300,640" data-type="png" data-w="600" width="auto" src="/upload/899866149276fa5fddb73c61ae04be64.png" style="box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;visibility: visible !important;width: 600px !important;"></p>
作者:微信小助手
<p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">InnoDB是非常适合互联网业务的存储引擎,其<strong>多版本并发控制</strong></span><span style="letter-spacing: 1px;font-size: 12px;">(Multi Version Concurrency Control, MVCC)</span><span style="letter-spacing: 1px;font-size: 14px;">,<strong>快照读</strong></span><span style="letter-spacing: 1px;font-size: 12px;">(Snapshot Read)</span><span style="letter-spacing: 1px;font-size: 14px;">机制,能够通过读取<strong>回滚段</strong></span><span style="letter-spacing: 1px;font-size: 12px;">(rollback segment)</span><span style="letter-spacing: 1px;font-size: 14px;">中数据的历史版本,在事务读取记录的时候</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">不用加锁</span><span style="letter-spacing: 1px;font-size: 14px;">,以支持超高的并发。</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><strong><span style="letter-spacing: 1px;font-size: 14px;">【并发控制,快照读,回滚段】</span></strong><span style="letter-spacing: 1px;font-size: 14px;">辅助阅读:</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">《<a href="http://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961444&idx=1&sn=830a93eb74ca484cbcedb06e485f611e&chksm=bd2d0db88a5a84ae5865cd05f8c7899153d16ec7e7976f06033f4fbfbecc2fdee6e8b89bb17b&scene=21#wechat_redirect" target="_blank">InnoDB并发如此高,原因竟然在这?</a>》</span><br></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">在<strong>读提交</strong></span><span style="letter-spacing: 1px;font-size: 12px;">(Read Committed, </span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 12px;">RC</span><span style="letter-spacing: 1px;font-size: 12px;">),</span><strong><span style="letter-spacing: 1px;font-size: 14px;">可重复读</span></strong><span style="letter-spacing: 1px;font-size: 12px;">(Repeated Read, </span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 12px;">RR</span><span style="letter-spacing: 1px;font-size: 12px;">)</span><span style="letter-spacing: 1px;font-size: 14px;">两个不同的事务的隔离级别下,</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">快照读的玩法有什么差异,又和什么因素有关呢?</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><strong><span style="letter-spacing: 1px;font-size: 14px;">【事务隔离级别】</span></strong><span style="letter-spacing: 1px;font-size: 14px;">辅助阅读:</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">《<a href="http://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961498&idx=1&sn=058097f882ff9d32f5cdf7922644d083&chksm=bd2d0d468a5a845026b7d2c211330a6bc7e9ebdaa92f8060265f60ca0b166f8957cbf3b0182c&scene=21#wechat_redirect" target="_blank">4种事务的隔离级别,如何巧妙实现?</a>》</span><br></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: normal;"><span style="letter-spacing: 1px;font-size: 14px;">假设有InnoDB表:<br></span><span style="letter-spacing: 1px;font-size: 12px;">t(id PK, name);</span><span style="letter-spacing: 1px;font-size: 14px;"><br> <br>表中有三条记录:<br></span><span style="letter-spacing: 1px;font-size: 12px;">1, shenjian<br>2, zhangsan<br>3, lisi</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="color: rgb(255, 76, 0);font-size: 16px;"><em><strong><span style="letter-spacing: 1px;font-size: 16px;">case 1</span></strong></em></span><span style="letter-spacing: 1px;font-size: 14px;">,两个并发事务A,B执行的时间序列如下(A先于B开始,B先于A结束):</span></p> <p style="line-height: normal;"><span style="letter-spacing: 1px;font-size: 12px;">A1: start transaction;<br> B1: start transaction;<br>A2: select * from t;<br> B2: insert into t values (4, wangwu);<br>A3: select * from t;<br> B3: commit;<br>A4: select * from t;</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><strong><span style="letter-spacing: 1px;font-size: 14px;">提问1</span></strong><span style="letter-spacing: 1px;font-size: 14px;">:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">可重复读RR</span><span style="letter-spacing: 1px;font-size: 14px;">,事务A中的三次查询,A2, A3, A4分别读到什么结果集?<br><strong>提问2</strong>:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">读提交RC</span><span style="letter-spacing: 1px;font-size: 14px;">,A2, A3, A4又分别读到什么结果集呢?</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><span style="background-color: transparent;color: rgb(255, 76, 0);"><em style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"><strong style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"><span style="color: rgb(255, 76, 0);font-size: 16px;letter-spacing: 1px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;">case 2</span></strong></em></span>,仍然是上面的两个事务,只是A和B开始时间稍有不同(B先于A开始,B先于A结束):</span><span style="font-size: 12px;letter-spacing: 1px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"><br style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"></span></p> <p style="margin: 0px;padding: 0px;text-align: left;color: rgb(0, 0, 0);text-transform: none;line-height: normal;text-indent: 0px;letter-spacing: normal;clear: both;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;text-decoration: none;word-spacing: 0px;white-space: normal;orphans: 2;-webkit-text-stroke-width: 0px;background-color: transparent;"><span style="margin: 0px;padding: 0px;color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 12px;"> B1: start transaction;</span></p> <p style="margin: 0px;padding: 0px;text-align: left;color: rgb(0, 0, 0);text-transform: none;line-height: normal;text-indent: 0px;letter-spacing: normal;clear: both;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;text-decoration: none;word-spacing: 0px;white-space: normal;orphans: 2;-webkit-text-stroke-width: 0px;background-color: transparent;"><span style="margin: 0px;padding: 0px;color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 12px;">A1: start transaction;</span></p> <p style="margin: 0px;padding: 0px;text-align: left;color: rgb(0, 0, 0);text-transform: none;line-height: normal;text-indent: 0px;letter-spacing: normal;clear: both;font-size: 16px;font-style: normal;font-variant: normal;font-weight: 400;text-decoration: none;word-spacing: 0px;white-space: normal;orphans: 2;-webkit-text-stroke-width: 0px;background-color: transparent;"><span style="margin: 0px;padding: 0px;letter-spacing: 1px;font-size: 12px;">A2: select * from t;<br style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"> B2: insert into t values (4, wangwu);<br style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;">A3: select * from t;<br style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"> B3: commit;<br style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;">A4: select * from t;</span><br></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"></span><br></p> <p style="line-height: 1.5em;"><strong><span style="letter-spacing: 1px;font-size: 14px;">提问3</span></strong><span style="letter-spacing: 1px;font-size: 14px;">:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">可重复读RR</span><span style="letter-spacing: 1px;font-size: 14px;">,事务A中的三次查询,A2, A3, A4分别读到什么结果集?<br></span></p> <p style="line-height: 1.5em;"><strong><span style="letter-spacing: 1px;font-size: 14px;">提问4</span></strong><span style="letter-spacing: 1px;font-size: 14px;">:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">读提交RC</span><span style="letter-spacing: 1px;font-size: 14px;">,A2, A3, A4的结果集又是什么呢?<br><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">事务的开始时间不一样,会不会影响“快照读”的结果呢?</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><span style="background-color: transparent;color: rgb(255, 76, 0);"><em style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"><strong style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"><span style="color: rgb(255, 76, 0);font-size: 16px;letter-spacing: 1px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;">case 3</span></strong></em></span>,仍然是并发的事务A与B(A先于B开始,B先于A结束):</span></p> <p style="line-height: normal;"><span style="letter-spacing: 1px;font-size: 12px;">A1: start transaction;<br> B1: start transaction;<br> B2: insert into t values (4, wangwu);<br> B3: commit;<br>A2: select * from t;</span><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p><span style="font-size: 14px;"><br></span></p> <p><strong><span style="letter-spacing: 1px;font-size: 14px;">提问5</span></strong><span style="letter-spacing: 1px;font-size: 14px;">:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">可重复读RR</span><span style="letter-spacing: 1px;font-size: 14px;">,事务A中的A2查询,结果集是什么?<br></span></p> <p><strong><span style="letter-spacing: 1px;font-size: 14px;">提问6</span></strong><span style="letter-spacing: 1px;font-size: 14px;">:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">读提交RC</span><span style="letter-spacing: 1px;font-size: 14px;">,A2的结果集又是什么呢?</span><br></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><span style="background-color: transparent;color: rgb(255, 76, 0);"><em style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"><strong style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"><span style="color: rgb(255, 76, 0);font-size: 16px;letter-spacing: 1px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;">case 4</span></strong></em></span>,事务开始的时间再换一下(B先于A开始,B先于A结束):</span></p> <p style="line-height: normal;"><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 12px;"> B1: start transaction;</span></p> <p style="line-height: normal;"><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 12px;display: inline !important;float: none;background-color: transparent;">A1: start transaction;</span></p> <p style="line-height: normal;"><span style="letter-spacing: 1px;font-size: 12px;"> B2: insert into t values (4, wangwu);<br style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"></span></p> <p style="line-height: normal;"><span style="letter-spacing: 1px;font-size: 12px;"> B3: commit;<br style="margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;">A2: select * from t;</span><span style="font-size: 14px;letter-spacing: 1px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;margin-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;padding-top: 0px;"></span></p> <p><br></p> <p style="line-height: 1.5em;"><strong><span style="letter-spacing: 1px;font-size: 14px;">提问7</span></strong><span style="letter-spacing: 1px;font-size: 14px;">:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">可重复读RR</span><span style="letter-spacing: 1px;font-size: 14px;">,事务A中的A2查询,结果集是什么?<br></span></p> <p style="line-height: 1.5em;"><strong><span style="letter-spacing: 1px;font-size: 14px;">提问8</span></strong><span style="letter-spacing: 1px;font-size: 14px;">:假设事务的隔离级别是</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">读提交RC</span><span style="letter-spacing: 1px;font-size: 14px;">,A2的结果集又是什么呢?</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">同样是读取历史数据版本,快照读究竟受什么影响呢?是不是很有意思?</span><strong><span style="letter-spacing: 1px;font-size: 14px;">答案与原理</span></strong><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">明天揭晓</span><span style="letter-spacing: 1px;font-size: 14px;">。</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">哦,对了,很多朋友问我,有什么推荐的MySQL相关的书籍,这里推荐两本。</span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;"><br></span></p> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">一本,对了解</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">底层实现</span><span style="letter-spacing: 1px;font-size: 14px;">有帮助:</span></p> <section> <mpcps class="js_editor_cps" style="width:100% !important;border:0;" data-type="1" data-color="#fa7834" data-uid="1535889352484" data-datakey="1535889352487_0.6635862549359679" frameborder="0" data-product="%7B%22productData%22%3A%5B%7B%22ad_block_status%22%3A0%2C%22appid%22%3A%22wx831660fe3ded4389%22%2C%22book%22%3A%7B%22author%22%3A%5B%22%5B%E7%BE%8E%5D%20%E4%BF%9D%E7%BD%97%C2%B7%E8%BF%AA%E5%B8%83%E7%93%A6%EF%BC%88Paul%20DuBois%EF%BC%89%E3%80%80%E8%91%97%22%5D%2C%22book_desc%22%3A%22%E3%80%80%E3%80%80%E3%80%8AMySQL%E6%8A%80%E6%9C%AF%E5%86%85%E5%B9%95%EF%BC%88%E7%AC%AC5%E7%89%88%EF%BC%89%E3%80%8B%E6%98%AFMySQL%E6%96%B9%E9%9D%A2%E5%90%8D%E5%89%AF%E5%85%B6%E5%AE%9E%E7%9A%84%E7%BB%8F%E5%85%B8%E8%91%97%E4%BD%9C%EF%BC%8C%E5%85%A8%E9%9D%A2%E4%BB%8B%E7%BB%8DMySQL%E7%9A%84%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86%E4%BB%A5%E5%8F%8AMySQL%E6%9C%89%E5%88%AB%E4%BA%8E%E5%85%B6%E4%BB%96%E6%95%B0%E6%8D%AE%E5%BA%93%E7%B3%BB%E7%BB%9F%E7%9A%84%E7%8B%AC%E7%89%B9%E5%8A%9F%E8%83%BD%EF%BC%8C%E4%B9%A6%E4%B8%AD%E7%89%B9%E5%88%AB%E5%85%B3%E6%B3%A8%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E4%BD%BF%E7%94%A8%E5%92%8C%E7%AE%A1%E7%90%86MySQL%E3%80%82%20%E3%80%80%E3%80%80%E3%80%8AMySQL%E6%8A%80%E6%9C%AF%E5%86%85%E5%B9%95%EF%BC%88%E7%AC%AC5%E7%89%88%EF%BC%89%E3%80%8B%E7%94%B14%E4%B8%AA%E9%83%A8%E5%88%86%E7%BB%84%E6%88%90%EF%BC%9A%E7%AC%AC%E4%B8%80%E9%83%A8%E5%88%86%E9%9B%86%E4%B8%AD%E4%BB%8B%E7%BB%8D%E4%B8%8E%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BD%BF%E7%94%A8%E7%9B%B8%E5%85%B3%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5%EF%BC%8C%E7%AC%AC%E4%BA%8C%E9%83%A8%E5%88%86%E9%87%8D%E7%82%B9%E5%85%B3%E6%B3%A8%E7%9A%84%E6%98%AF%E8%87%AA%E5%B7%B1%E5%A6%82%E4%BD%95%E5%8A%A8%E6%89%8B%E7%BC%96%E5%86%99%E5%92%8C%E4%BD%BF%E7%94%A8MySQL%E7%9A%84%E7%A8%8B%E5%BA%8F%EF%BC%8C%E7%AC%AC%E4%B8%89%E9%83%A8%E5%88%86%E4%B8%BB%E8%A6%81%E6%98%AF%E9%9D%A2%E5%90%91%E9%82%A3%E4%BA%9B%E8%B4%9F%E8%B4%A3%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86%E7%9A%84%E8%AF%BB%E8%80%85%EF%BC%8C%E7%AC%AC%E5%9B%9B%E9%83%A8%E5%88%86%E6%8F%90%E4%BE%9B%E4%BA%86%E4%B8%80%E4%BA%9B%E5%8F%82%E8%80%83%E9%99%84%E5%BD%95%E3%80%82%E4%B9%A6%E4%B8%AD%E5%8C%85%E5%90%AB%E5%A4%A7%E9%87%8F%E7%A4%BA%E4%BE%8B%EF%BC%8C%E8%AF%A6%E5%B0%BD%E5%9C%B0%E6%BC%94%E7%A4%BA%E4%BA%86MySQL%E7%9A%84%E5%90%84%E9%A1%B9%E5%8A%9F%E8%83%BD%E7%89%B9%E6%80%A7%E3%80%82%E6%AD%A4%E5%A4%96%EF%BC%8C%E6%9C%AC%E4%B9%A6%E8%BF%98%E4%B8%BA%E4%BD%BF%E7%94%A8C%E8%AF%AD%E8%A8%80%E3%80%81PHP%E8%AF%AD%E8%A8%80%E5%92%8CPerl%E8%AF%AD%E8%A8%80%E5%BC%80%E5%8F%91%E6%95%B0%E6%8D%AE%E5%BA%93%E5%BA%94%E7%94%A8%E7%9A%84%E8%AF%BB%E8%80%85%E6%8F%90%E4%BE%9B%E4%BA%86%E7%9B%B8%E5%85%B3%E5%86%85%E5%AE%B9%E3%80%82%E3%80%80%E3%80%80%E3%80%8AMySQL%E6%8A%80%E6%9C%AF%E5%86%85%E5%B9%95%EF%BC%88%E7%AC%AC5%E7%89%88%EF%BC%89%E3%80%8B%E4%B8%8D%E4%BB%85%E9%80%82%E5%90%88MySQL%E5%88%9D%E5%AD%A6%E8%80%85%E9%98%85%E8%AF%BB%EF%BC%8C%E4%B9%9F%E9%80%82%E5%90%88%E6%83%B3%E8%A6%81%E6%B7%B1%E5%85%A5%E4%BA%86%E8%A7%A3MySQL%E7%9A%84%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86%E4%BA%BA%E5%91%98%E5%92%8C%E5%BC%80%E5%8F%91%E4%BA%BA%E5%91%98%E5%8F%82%E8%80%83%EF%BF%BD......%22%2C%22publisher%22%3A%22%E4%BA%BA%E6%B0%91%E9%82%AE%E7%94%B5%E5%87%BA%E7%89%88%E7%A4%BE%22%7D%2C%22category_id%22%3A3%2C%22commission_ratio%22%3A%2210.00%25%22%2C%22has_commission%22%3Atrue%2C%22img_url%22%3A%22https%3A%2F%2Fres.wx.qq.com%2Fproduct_material%2FYjUCjWPcXAkQoktwVoG9O7_im01xm_awGpvVuKZL1hWEFVXjVwC7TxcvyGQqKCui%22%2C%22movie%22%3A%7B%22actor%22%3A%5B%5D%2C%22alt_name%22%3A%5B%5D%2C%22classify%22%3A%5B%5D%2C%22director%22%3A%5B%5D%7D%2C%22pid%22%3A%2223727515%22%2C%22price%22%3A133.4%2C%22source_logo_url%22%3A%22http%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F6nVmK0mHaRr8fd8C4yNUm5BrcwmJ17I867w7sZwrxtAmG0NdTKhZj8eAEeRt0Ycgc9pnL4ib77PJ0UKDoT43MsA%2F0%22%2C%22source_name%22%3A%22%E5%BD%93%E5%BD%93%22%2C%22title%22%3A%22MySQL%E6%8A%80%E6%9C%AF%E5%86%85%E5%B9%95%EF%BC%88%E7%AC%AC5%E7%89%88%EF%BC%89%22%2C%22cps_desc%22%3A%22%5B%E7%BE%8E%5D%20%E4%BF%9D%E7%BD%97%C2%B7%E8%BF%AA%E5%B8%83%E7%93%A6%EF%BC%88Paul%20DuBois%EF%BC%89%E3%80%80%E8%91%97%22%2C%22cps_desc_long%22%3A%22%E4%BD%9C%E8%80%85%EF%BC%9A%5B%E7%BE%8E%5D%20%E4%BF%9D%E7%BD%97%C2%B7%E8%BF%AA%E5%B8%83%E7%93%A6%EF%BC%88Paul%20DuBois%EF%BC%89%E3%80%80%E8%91%97%22%7D%5D%7D" data-templateid="list" data-pid="23727515" data-packid="" data-smartnum="" data-categoryid="3" data-appid="wx831660fe3ded4389"></mpcps> </section> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">一本,对</span><span style="color: rgb(255, 76, 0);letter-spacing: 1px;font-size: 14px;">应用优化</span><span style="letter-spacing: 1px;font-size: 14px;">有帮助:</span></p> <section> <mpcps class="js_editor_cps" style="width:100% !important;border:0;" data-type="1" data-color="#fa7834" data-uid="1535889352485" data-datakey="1535889352488_0.33506295696049815" frameborder="0" data-product="%7B%22productData%22%3A%5B%7B%22ad_block_status%22%3A0%2C%22appid%22%3A%22wx831660fe3ded4389%22%2C%22book%22%3A%7B%22author%22%3A%5B%22(%E7%BE%8E)%E6%96%BD%E7%93%A6%E8%8C%A8%2C(%E7%BE%8E)%E6%89%8E%E4%BC%8A%E9%87%87%E5%A4%AB%2C(%E7%BE%8E)%E7%89%B9%E5%8D%A1%E7%90%B4%E7%A7%91%22%5D%2C%22book_desc%22%3A%22%3Cp%3E%20%E3%80%80%E3%80%80%E3%80%8A%E9%AB%98%E6%80%A7%E8%83%BDMySQL%EF%BC%88%E7%AC%AC3%E7%89%88%EF%BC%89%E3%80%8B%E6%98%AFMySQL%20%E9%A2%86%E5%9F%9F%E7%9A%84%E6%9E%81%E4%BD%B3%E4%B9%8B%E4%BD%9C%EF%BC%8C%E6%8B%A5%E6%9C%89%E5%B9%BF%E6%B3%9B%E7%9A%84%E5%BD%B1%E5%93%8D%E5%8A%9B%E3%80%82%E7%AC%AC3%20%E7%89%88%E6%9B%B4%E6%96%B0%E4%BA%86%E5%A4%A7%E9%87%8F%E7%9A%84%E5%86%85%E5%AE%B9%EF%BC%8C%E4%B8%8D%E4%BD%86%E6%B6%B5%E7%9B%96%E4%BA%86%E6%96%B0%E7%9A%84MySQL5.5%E7%89%88%E6%9C%AC%E7%9A%84%E6%96%B0%E7%89%B9%E6%80%A7%EF%BC%8C%E4%B9%9F%E8%AE%B2%E8%BF%B0%E4%BA%86%E5%85%B3%E4%BA%8E%E5%9B%BA%E6%80%81%E7%9B%98%E3%80%81%E9%AB%98%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7%E8%AE%BE%E8%AE%A1%E5%92%8C%E4%BA%91%E8%AE%A1%E7%AE%97%E7%8E%AF%E5%A2%83%E4%B8%8B%E7%9A%84%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9B%B8%E5%85%B3%E7%9A%84%E6%96%B0%E5%86%85%E5%AE%B9%EF%BC%8C%E5%8E%9F%E6%9C%89%E7%9A%84%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95%E5%92%8C%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E9%83%A8%E5%88%86%E4%B9%9F%E5%81%9A%E4%BA%86%E5%A4%A7%E9%87%8F%E7%9A%84%E6%89%A9%E5%B1%95%E5%92%8C%E8%A1%A5%E5%85%85%E3%80%82%E5%85%A8%E4%B9%A6%E5%85%B1%E5%88%86%E4%B8%BA16%E7%AB%A0%E5%92%8C6%20%E4%B8%AA%E9%99%84%E5%BD%95%EF%BC%8C%E5%86%85%E5%AE%B9%E6%B6%B5%E7%9B%96MySQL%E6%9E%B6%E6%9E%84%E5%92%8C%E5%8E%86%E5%8F%B2%EF%BC%8C%E5%9F%BA%E5%87%86%E6%B5%8B%E8%AF%95%E5%92%8C%E6%80%A7%E8%83%BD%E5%89%96%E6%9E%90%EF%BC%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E8%BD%AF%E7%A1%AC%E4%BB%B6%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%EF%BC%8C%E5%A4%8D%E5%88%B6%E3%80%81%E5%A4%87%E4%BB%BD%E5%92%8C%E6%81%A2%E5%A4%8D%EF%BC%8C%E9%AB%98%E5%8F%AF%E7%94%A8%E4%B8%8E%E9%AB%98%E5%8F%AF%E6%89%A9%E5%B1%95%E6%80%A7%EF%BC%8C%E4%BB%A5%E5%8F%8A%E4%BA%91%E7%AB%AF%E7%9A%84MySQL%E5%92%8CMySQL%E7%9B%B8%E5%85%B3%E5%B7%A5%E5%85%B7%E7%AD%89%E6%96%B9%E9%9D%A2%E7%9A%84%E5%86%85%E5%AE%B9%E3%80%82%E6%AF%8F%E4%B8%80%E7%AB%A0%E9%83%BD%E6%98%AF%E7%9B%B8%E5%AF%B9%E7%8B%AC%E7%AB%8B%E7%9A%84%E4%B8%BB%E9%A2%98%EF%BC%8C%E8%AF%BB%E8%80%85%E5%8F%AF%E4%BB%A5%E6%9C%89%E9%80%89%E6%8B%A9%E6%80%A7%E5%9C%B0%E5%8D%95%E7%8B%AC%E9%98%85%E8%AF%BB%E3%80%82%3Cbr%20%2F%3E%E3%80%80%E3%80%80%E3%80%8A%E9%AB%98%E6%80%A7%E8%83%BDMySQL%EF%BC%88%E7%AC%AC3%E7%89%88%EF%BC%89%E3%80%8B%E4%B8%8D%E4%BD%86%E9%80%82%E5%90%88%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%A1%E7%90%86%E5%91%98%EF%BC%88DBA%EF%BC%89%E9%98%85%E8%AF%BB%EF%BC%8C%E4%B9%9F%E9%80%82%E5%90%88%E5%BC%80%E5%8F%91%E4%BA%BA%E5%91%98%E5%8F%82%E8%80%83%E5%AD%A6%E4%B9%A0%E3%80%82%E4%B8%8D%E7%AE%A1%E6%98%AF%E6%95%B0%E6%8D%AE%E5%BA%93%E6%96%B0%E6%89%8B%E8%BF%98%E6%98%AF%E4%B8%93%E5%AE%B6%EF%BC%8C%E7%9B%B8%E4%BF%A1%E9%83%BD%E8%83%BD%E4%BB%8E%E6%9C%AC%E4%B9%A6%E6%9C%89%E6%89%80......%22%2C%22publisher%22%3A%22%E7%94%B5%E5%AD%90%E5%B7%A5%E4%B8%9A%E5%87%BA%E7%89%88%E7%A4%BE%22%7D%2C%22category_id%22%3A3%2C%22commission_ratio%22%3A%2210.00%25%22%2C%22has_commission%22%3Atrue%2C%22img_url%22%3A%22https%3A%2F%2Fres.wx.qq.com%2Fproduct_material%2FgxWTmmF-CJbf1_-7ZGYgiMBq42JJ8-_1k0oGuSRLL5BZ_HfYc1clZNbJ-_glXm39%22%2C%22movie%22%3A%7B%22actor%22%3A%5B%5D%2C%22alt_name%22%3A%5B%5D%2C%22classify%22%3A%5B%5D%2C%22director%22%3A%5B%5D%7D%2C%22pid%22%3A%2223214590%22%2C%22price%22%3A122.9%2C%22source_logo_url%22%3A%22http%3A%2F%2Fmmbiz.qpic.cn%2Fmmbiz_png%2F6nVmK0mHaRr8fd8C4yNUm5BrcwmJ17I867w7sZwrxtAmG0NdTKhZj8eAEeRt0Ycgc9pnL4ib77PJ0UKDoT43MsA%2F0%22%2C%22source_name%22%3A%22%E5%BD%93%E5%BD%93%22%2C%22title%22%3A%22%E9%AB%98%E6%80%A7%E8%83%BDMySQL%EF%BC%88%E7%AC%AC3%E7%89%88%EF%BC%89%22%2C%22cps_desc%22%3A%22(%E7%BE%8E)%E6%96%BD%E7%93%A6%E8%8C%A8%2C(%E7%BE%8E)%E6%89%8E%E4%BC%8A%E9%87%87%E5%A4%AB%2C(%E7%BE%8E)%E7%89%B9%E5%8D%A1%E7%90%B4%E7%A7%91%22%2C%22cps_desc_long%22%3A%22%E4%BD%9C%E8%80%85%EF%BC%9A(%E7%BE%8E)%E6%96%BD%E7%93%A6%E8%8C%A8%2C(%E7%BE%8E)%E6%89%8E%E4%BC%8A%E9%87%87%E5%A4%AB%2C(%E7%BE%8E)%E7%89%B9%E5%8D%A1%E7%90%B4%E7%A7%91%22%7D%5D%7D" data-templateid="list" data-pid="23214590" data-packid="" data-smartnum="" data-categoryid="3" data-appid="wx831660fe3ded4389"></mpcps> </section> <p style="line-height: 1.5em;"><span style="letter-spacing: 1px;font-size: 14px;">只推荐自己看过的书,希望对大家有帮助。</span></p>
作者:じ☆ve宝贝
> 默认rc.local中写了脚本后,依旧无法开机自启。因为/etc/rc.d/rc.local 文件默认没有执行权限, 修改权限/etc/rc.d/rc.local的权限即可 # mysql 开机自启 chkconfig --list如果列表中没有mysqld这个,需要先用这个命令添加: chkconfig --add mysqld chkconfig --del mysqld 删除开机自动启动mysqld服务 然后用这个命令设置开机启动: chkconfig mysqld on chkconfig --level 2345 mysqld on 最后确认一下是否设置成功 --level<等级代号> 指定读系统服务要在哪一个执行等级中开启或关毕。 等级0表示:表示关机 等级1表示:单用户模式 等级2表示:无网络连接的多用户命令行模式 等级3表示:有网络连接的多用户命令行模式 等级4表示:不可用 等级5表示:带图形界面的多用户模式 等级6表示:重新启动 需要说明的是,level选项可以指定要查看的运行级而不一定是当前运行级。对于每个运行级,只能有一个启动脚本或者停止脚本。当切换运行级时,init不会重新启动已经启动的服务,也不会再次去停止已经停止的服务。 将MongoDB tomcat服务加入随机启动 vi /etc/rc.d/rc.local 使用vi编辑器打开配置文件,并在其中加入下面一行代码 export JAVA_HOME=/usr/java/jdk1.7.0_75 /usr/tomcat/bin/startup.sh start /usr/mongodb/bin/mongod --auth -dbpath=/usr/mongodb/data --logpath=/usr/mongodb/log/mongodb.log
作者:じ☆ve宝贝
 > 很多朋友,希望加一个在线下载抖音个人主页的功能,经过三个小时的奋斗,成功解析了抖音的signature,解析了个人主页 **https://www.studyjava.cn/douyin** ## 在线播放抖音小视频 **https://www.studyjava.cn/yule** 请大家使用电脑打开此页下载,个人服务器请勿压测。 手机上大家可以使用微信小程序 《Java学习者》 进行抖音、微视视频下载(暂不支持个人主页解析,预计近期推出) ## 本站小程序: 
作者:じ☆ve宝贝
## 操作过程如下: 1. 新建项目,编写代码; 2. 右键 - team- share - git - 打钩 use or create... - 选中列表中的项目 - 点击 create repository - finish; 3. 刷新项目(可选) - 右键 - team - commit - 输入commit message - 选择欲提交的文件 - 点击commit; 4. 以上步骤是将你的项目提交到本地git仓库; #### 下面讲解如何将本地项目同步到远程git仓库; 1. 以osc@git为例,在osc@git上创建项目 - 复制git地址; 2. 右键项目 - team - remote - push - URI中paste 远程git地址并输入用户名密码 - next - source ref选择refs/heads/master,选中后destination中的值会被自动初始化 - 点击add spec,此时下面列表中会多一条update.... - next - next - ok即可 3. 如要增加项目说明文件,在你的工程根目录创建一个readme.md文件,编辑内容即可
作者:じ☆ve宝贝
vi /etc/sysconfig/i18n 修改为: ``` LANG="en_US.UTF-8" ```
作者:じ☆ve宝贝
1.首先打开浏览器的开发者工具。一般为F12 2. 控制台(Console)里把下面的代码复制进去 ``` refuse_jzzsltj = function (){return false} ``` 然后回车,就可以绕过这个可恶的弹窗了
作者:微信小助手
<section class="xmteditor" style="display:none;" data-tools="新媒体管家" data-label="powered by xmt.cn"></section> <p style="text-align: left;"><span style="color: rgb(121, 123, 170);">本文转载自公众号 余晟以为</span></p> <p style="text-align: center;"><br></p> <p style="text-align: center;"><img class="" data-ratio="0.4179640718562874" data-s="300,640" src="/upload/47768c249dd1056411d70b3c9a5511f0.png" data-type="png" data-w="835" style=""></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">去年(2018年)1月,沪江的两位前端工程师翻译了O’Reilly的《HTTP/2基础教程》。似乎到目前为止,这似乎仍然是唯一关于HTTP/2的中文技术资料。</span><br></p> <p style="text-align: center;"><img class="" data-copyright="0" data-ratio="1.3227513227513228" data-s="300,640" src="/upload/be612c5f8d82585e6a94ace702fb780b.jpg" data-type="jpeg" data-w="378" style=""></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">然而技术的发展总是让人目不暇接,去年10月,HTTP/3又发布了。虽然已经有一些中文技术媒体做了报道,但大多数是翻译的,而且内容大同小异。最近我专门学习了点关于HTTP/3的知识,在这里随便写写,和大家做个分享。</span><br><span style="font-size: 15px;letter-spacing: 1px;"></span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">先简单回顾一下HTTP/2吧。自从1999年HTTP 1.1发布之后,Web一直在迅猛发展,可惜HTTP协议一直没有更新。等不及的Google自己搞了个SPDY(读音是“speedy”),并依靠Chrome浏览器大肆推广。看到SPDY的效果确实很好(可以带来近50%的性能提升),IETF推动制定了HTTP/2。 SPDY和HTTP/2的主要特性展示如下</span></p> <p style="text-align: center;margin-bottom: 20px;"><img class="" data-copyright="0" data-ratio="0.2562225475841874" data-s="300,640" src="/upload/b16696e964e333d951da4784a8f800e1.png" data-type="png" data-w="1366" style=""></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 1px;">如今HTTP/2已经不新鲜了,根据2019年2月对访问量最大的1000万个网站的统计,33.5%已经支持HTTP/2。在国内,如果你打开浏览器看看调试模式,会发现各大厂已经广泛使用HTTP/2,尤其是放置css、js、图片的资源站,HTTP/2基本是标配。这也很好理解,基本什么都不用做,就可以直接享受多路复用带来的好处,何乐而不为?</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 1px;">在传统HTTP中,概念模型非常简单:下层TCP通讯与上层HTTP完全不搭架,但TTP与TCP的“连接”是重合的,TCP传输的单位是packet,HTTP则采用request-response的模型。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="letter-spacing: 1px;"><span style="letter-spacing: 1px;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">在HTTP/2中,概念模型有所变化,HTTP/2中传输的基本单位是帧(frame)。与HTTP 1.1的明文传输不同的是,HTTP/2的帧是二进制的,同时TCP承载的“逻辑连接”叫数据流(stream),所有的状态流转、流控、优先级等等特性都是在数据流上实现的。HTTP/2中为大家所津津乐道的“多路复用”,简单说就是把数据流分解为多个帧,多个数据流的帧混合之后以同一个TCP连接来发送。</span><br><span style="letter-spacing: 1px;font-size: 15px;"></span></span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">值得注意的是,HTTP有1.0和1.1的区分,所以写作HTTP 1.0,HTTP 1.1,但HTTP/2不会有其它小版本,所以不要写作HTTP 2.0,而应当写成HTTP/2。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">虽然HTTP/2已经带来了巨大的性能提升,但大家对性能的渴求是没有止境的。在应用层的许多问题解决之后,下一个优化的重点就是传输层了。无论SPDY还是HTTP/2,传输层协议都是TCP,TCP有一些娘胎里带来的问题,比如慢启动,如果拥塞窗口尺寸设置不合理,TCP的性能会急剧下降。关于这个问题,网络上已经有许多讨论,这里不赘述。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">另一个重要问题是,HTTP/2的多路复用带来的效果并不如想象的那么好。虽然HTTP/2中的传输连接可以多路复用,但仍然无法避免队头阻塞的情况出现。因为TCP是需要保证有序的,假如单个TCP连接同时承载了四路逻辑连接,其中某个逻辑连接丢包了,则其它三路都会受影响,都必须从丢包的时刻开始重传,这无疑是极大的浪费。测试表明,如果丢包率超过2%,那么HTTP/2甚至不如HTTP 1.1,因为HTTP 1.1中各连接物理隔离,不会互相影响。</span></p> <p style="text-align: center;margin-bottom: 20px;"><img class="" data-copyright="0" data-ratio="0.47458893871449925" data-s="300,640" src="/upload/effc0b28c9cbccfac554e7ee10b85c84.png" data-type="png" data-w="1338" style=""></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">所以思路自然就是“改掉TCP的这些毛病”。考虑到现实中已经有成千上万的网络设备,它们只能识别TCP和UDP,软件不会进化,如果更新TCP协议当然不可行——虽然2014年12月发布了TCP的Fast Open,但现实应用中的情况并不让人满意。因此,可用的只有UDP了。对了,还有人考虑过SCTP,但SCTP在队头阻塞、TLS、四次握手等方面仍然存在缺陷,尚不能让人满意。</span><br><span style="font-size: 15px;letter-spacing: 1px;"></span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">大概有人听过QUIC(读音quick),知道它是基于UDP的HTTP,也知道它依然是Google最先提出来的。确实,上次是Google率先搞出了SPDY,这次Google又率先搞出了QUIC。根据Google本意,QUIC是把传统的HTTP/TCP/IP协议栈中的TCP换成UDP(当然需要加密),能通过加密的UDP传输HTTP/2的帧。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;letter-spacing: 1px;">按照Google的说法,这样的好处很多,比如UDP建立连接的延迟会低很多,而且避免了队头阻塞。除此之外,Google还提供了一个非常诱人的特性FEC(Forward Error Correction)。简单说,它想做到的是,一旦有packet丢失,接收方可以根据之前和之后的packet推断出丢失packet的数据,这样就避免了重传。但是这样必然要求增加冗余载荷,或者说,这就是网络协议中的RAID 5。按照目前看到的资料,其冗余比例大概是10%,也就是说,每10个pakcet中的冗余信息,就可以重构一个packet。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">尽管Google的QUIC很先进,但QUIC不止这一家,IETF也有QUIC,如今已经改名HTTP/3,所以Google的QUIC有时候也写作gQUIC。与Google单纯在传输层动手,应用层基本沿用HTTP/2不同,IETF的QUIC是一个混合方案,既包括传输层的改动,也包括HTTP层的改动(比如全新的头部压缩)。从另一个角度来说,它更“完整”。虽然理论上QUIC也可以支持HTTP之外的其它上层应用,但目前这只是计划而已,第一版QUIC并不包含这方面内容。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">在2018年11月,IETF正式宣布,HTTP-over-QUIC更名为HTTP/3。</span></p> <p style="text-align: center;margin-bottom: 20px;"><img class="" data-copyright="0" data-ratio="0.3998852553069421" data-s="300,640" src="/upload/60c500af45fe7356131b3b585131cef1.png" data-type="png" data-w="1743" style=""></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="letter-spacing: 1px;"><span style="letter-spacing: 1px;font-size: 15px;font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;">本文讨论的是IETF版本的QUIC,Google已经宣布,会逐步把IETF的规范纳入自己的协议版本,实现相同的规范。</span><br><span style="letter-spacing: 1px;font-size: 15px;"></span></span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">虽然TCP有各种问题,但换成UDP的话,TCP的不少功能也需要原样移植过来。许多人都知道,TCP是可靠的传输协议,而UDP是不可靠的。HTTP/3当然不能不可靠,所以它必须自己实现有序性、错误侦测、重传、拥塞控制、传输节奏调整等等特性。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">HTTP/2“似乎”必须用到HTTPS,但规范并不强求HTTP/2使用HTTPS,也就是说,如果你用HTTP来跑HTTP/2,理论上也是可以成立的,虽然这有点怪异。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">与此相反,QUIC的所有连接都是加密的,目前采用的是TLS 1.3。如果你仔细观察上面的图就会发现,TLS 1.3是“囊括”在QUIC当中的,也就是说,QUIC建立连接的握手过程当中就同时完成了加密握手。HTTP/3的握手很快,如果两台主机之间建立过连接,并且缓存了之前的secret,只要客户端验证之前缓存的server config就可以直接建立连接,相当于0-RTT,否则也只需要1-RTT就可以建立连接。此外,QUIC还容许在0-RTT的情况下从一开始就捎带数据,传统的“建立连接-加密握手-发送数据”如今可以三步并作一步(这个0-RTT和1-RTT的实现都非常有意思,有兴趣的话应当找资料来看看)。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">QUIC中虽然也有连接(Connection),也基于IP和port建立,但它并不能直接与TCP的连接对应,也不同于HTTP/2中的连接。原因在于QUIC建立连接时既完成了经典的传输握手,又完成了加密握手——你可以认为这样分层责任不清晰,但它确实提升了效率。QUIC的连接与HTTP/2类似,一个物理连接也可以承载多个逻辑连接(也就是数据流)。但与HTTP/2不同的是,QUIC中的逻辑连接是彼此独立的,所以避免了TCP上出现的“逻辑连接甲丢包导致逻辑连接乙、丙、丁都需要重传”的情况。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">QUIC连接的另一个特点是,每个连接都有一组连接ID。连接各端可以设定自己的连接ID,同时认可对方的连接ID。连接ID的作用在于从逻辑上标识当前连接。所以,如果用户的IP发生变化而连接ID没有变化,因为packet包含了网络ID标识符,所以只需要继续发送数据包就可以重新建立连接。而目前,如果用户的设备发生了网络切换,比如从Wi-Fi切换到4G,则所有连接都要断掉再重连。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">如果你详细研究过HTTP/2,应当知道它的header压缩采用的HPACK,因为gzip做header压缩有安全性隐患。HTTP/3同样提供了header压缩,但不能直接沿用HPACK。原因在于,HPACK粗略来说就是一张动态表(dynamic table),由request-response共同维护它,后续header中不会完整重复之前的条目,而是引用之前的条目,TCP的有序性保证了它一定是先修改再读取,也就是先编码再解码。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">然而如果使用HPACK,多个流的顺序是无法保证的,这样会导致解析错误。QUIC的解决方案是QPACK,其原理很简单:所有的header必须通过同一数据流来传输,而且必须严格有序。但是这样一来,从HTTP 1.1开始就困扰HTTP已久的队头阻塞又出现了。因此,QUIC的长期目标之一就是解决header的队头阻塞问题。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">做过在线升级的朋友都知道,在线升级中的一个必须成分是提供降级方案,以保证“退化”兼容。无论HTTP/2还是HTTP/3,都不能逃避这部分的工作量。HTTP/2虽然可以通过upgrade这个header来升级,但也有更简单的办法,就是在TLS握手时协商HTTP的版本,比如Nginx就有NPN(Nginx Protocol Negotiation)扩展,自动协商协议,并已经被IETF采纳,成为ALPN(Application Layer Protocol Negotiation)。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">如果web server有这样的特性,应用服务代码就不必为兼容HTTP 1.1和HTTP/2做太多工作。但是,如果应用程序中使用了Push等新特性,还是免不了要做很多事情。在业界,Google、Youtube、Wikipedia等大厂早已经提供了完整服务,HTTP/2和HTTP 1.1无缝切换,客户端完全无感知,它们的经验值得参考。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">与HTTP/2不同的是,HTTP/3中新定义了一个header,可以用来指示客户端“在另一个端口提供了专用的HTTP/3服务”。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;text-indent: 2em;"><span style="box-sizing: border-box;-webkit-font-smoothing: antialiased;white-space: pre;direction: ltr;border-width: initial;border-style: none;border-color: initial;overflow: auto;overflow-wrap: normal;break-inside: avoid;font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;background-position: 0px 0px;font-size: 15px;letter-spacing: 1px;">Alt-Svc: h3=":20003"</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">这个header说明,在本主机的20003端口开启了HTTP/3的服务。所以,客户端之后可以尝试和这个端口建立纯粹的HTTP/3连接。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">聊了这么多QUIC的好处之后,再谈谈它的问题,有些观点来自我个人,未必足够准确客观,欢迎讨论。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">虽然QUIC有这么多好处,但可以看到,相比HTTP/2,它的改动相当大,所以问题也不会少。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">第一个问题是与遗留的网络设备的兼容问题。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">基于目前的应用情况,许多网络设备对TCP和UDP的策略相当固定,TCP限制在常用端口,而UDP大概只开放了53端口(DNS)。所以如果HTTP/3使用UDP,兼容性方面可能会有不少问题需要解决。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">不过如果这个问题可以解决,未来大概不会再出现这种问题,因为HTTP/3的设计思想中已经为未来做了考虑,应用层和传输层职责严格隔离,避免再出现“传输层一看端口就知道应用层在干什么”的情况。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">第二个问题是QUIC的性能问题。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">TCP虽然也是很老的协议,但应用广泛,操作系统内核中有对应的处理代码,BBR之类的新特性也可以大幅提升TCP的性能。但是QUIC放弃了TCP,据Google的文档,恰恰是因为TCP太稳定了,内核里的代码更新特别麻烦。此外,因为Linux内核设计之初并没有考虑多核的扩展问题,在多核(core)情况下反而会产生反复的陷核,造成进程阻塞,严重影响性能。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">针对上面的问题,不少新的方案都把网络协议栈放到用户态处理,QUIC也顺应了这种大潮流。唯一的问题是,UDP的协议栈似乎还没有现成的让人满意的方案,或许我们还得再等待一段时间,才能用上可靠高效的方案。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">第三个问题是服务端推送。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">虽然很多人很想要这个特性,而且HTTP/2也确实加入了它,但关于它的应用仍然存在许多争议。简单说,HTTP/2的推送打破了HTTP传统的“一问一答”的通讯模式,在客户端没有请求的时候,服务端就可以给客户端发送数据,这难免被滥用(想想随处可见的那些最喜欢“在商言商”,最不喜欢谈“道德”的留言吧),尽管Chrome的开发人员说它们会检查推送并阻止恶意内容,那也是要在收到推送数据之后进行,这个方案并不完善。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">同时,服务端也可能不顾客户端的缓存,执意重复推送,造成带宽浪费。HTTP/3保留了推送,但机制有所不同。客户端需要先同意,服务端才可以推送。而且,客户端可以设置服务端推送上限,超过上限的推送会出错。尽管如此,推送如何能妥善利用,目前还没有公认明确的答案。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">最后一个问题来自调试和支持的工具。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">任何技术要想大规模工程应用,靠“标准实现”单打一肯定是不行的,因为无法切片,无法细粒度调试。在经典的HTTP技术栈中,各层都有对应的工具,比如IP层有ping和traceroute,传输层有telnet,应用层有curl,正是有这些工具簇拥着,开发人员才可以很方便地定位问题所处的层次和细节。HTTP/2虽然有改动,但调试工具也不少,curl可以支持,还有nghttp2、h2c等工具,初步形成了完整的体系。HTTP/3的改动很大,如果没有对应的调试支持工具,可以想象部署和迁移都不会容易。</span></p> <p style="margin-bottom: 20px;margin-left: 8px;margin-right: 8px;"><span style="letter-spacing: 1px;font-size: 15px;">最后感谢狗叔(Googol Lee)介绍的资料。同时推荐一本电子书,对HTTP/3做了详细完整的解读,比我说的要好,有兴趣可以点击“原文链接”前往。</span></p> <p style="margin-left: 8px;margin-right: 8px;margin-bottom: 20px;"><span style="font-size: 15px;letter-spacing: 1px;">以上就是我的学习笔记。如果你认为有不对的地方,欢迎留言指出。讨论HTTP/3相关问题的留言也同样欢迎。</span></p> <p style="margin-right: 16px;margin-bottom: 5px;margin-left: 16px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br></p> <p style="max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;text-align: center;color: rgb(62, 62, 62);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;widows: 1;line-height: 28.4444px;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 18px;line-height: 1.6;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;">—————END—————</span></p> <p><br></p> <p style="margin-right: 16px;margin-bottom: 5px;margin-left: 16px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br></p> <p style="margin-right: 16px;margin-bottom: 5px;margin-left: 16px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br></p> <p style="max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;color: rgb(62, 62, 62);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: center;widows: 1;line-height: 25.6px;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;font-size: 18px;line-height: 28.8px;box-sizing: border-box !important;word-wrap: break-word !important;">喜欢本文的朋友们,欢迎长按下图关注订阅号</span><span style="max-width: 100%;font-size: 18px;line-height: 28.8px;color: rgb(255, 76, 0);box-sizing: border-box !important;word-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;">程序员小灰</strong></span><span style="max-width: 100%;font-size: 18px;line-height: 28.8px;box-sizing: border-box !important;word-wrap: break-word !important;">,收看更多精彩内容</span></p> <p style="max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;color: rgb(62, 62, 62);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: center;widows: 1;line-height: 25.6px;box-sizing: border-box !important;word-wrap: break-word !important;"><br></p> <p style="max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;color: rgb(62, 62, 62);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: center;widows: 1;line-height: 25.6px;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;font-size: 18px;line-height: 28.8px;box-sizing: border-box !important;word-wrap: break-word !important;"><img class="" data-copyright="0" data-ratio="1" data-s="300,640" src="/upload/37f513ecb7627cdbb355876dab88096.jpg" data-type="jpeg" data-w="344" style="letter-spacing: 0.544px;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;visibility: visible !important;width: 298px !important;"></span></p>