作者:微信小助手
<section style="box-sizing: border-box;font-size: 16px;"> <section style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding-top: 10px;padding-right: 10px;padding-left: 10px;box-sizing: border-box;background-color: rgb(239, 239, 239);"> <span style="display: inline-block;width: 5%;line-height: 0.8;font-weight: bolder;font-size: 48px;box-sizing: border-box;" title="" opera-tn-ra-cell="_$.pages:0.layers:0.comps:0.txt1"> <section style="box-sizing: border-box;"> “ </section></span> <section style="display: inline-block;vertical-align: top;float: right;width: 90%;line-height: 1.5;font-size: 15px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><span style="letter-spacing: 1px;">本文希望帮助读者更加深刻地理解 MySQL 中的锁和事务,从而在业务系统开发过程中更好地优化与数据库的交互。</span></p> </section> <section style="clear: both;box-sizing: border-box;"></section> </section> </section> </section> <section style="line-height: 1.75em;"> <br> </section> <section style="text-align: center;margin-left: 8px;margin-right: 8px;"> <img class="rich_pages" data-ratio="0.6277258566978193" data-s="300,640" src="/upload/611b6074f5cb5645f844d46e40206a4c.png" data-type="png" data-w="642" style=""> </section> <p style="text-align: center;margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 14px;color: rgb(89, 89, 89);letter-spacing: 1px;"><em>图片来自 Pexels</em></span></p> <section style="line-height: normal;"> <br> </section> <section style="box-sizing: border-box;font-size: 16px;"> <section style="border-bottom-width: 1px;border-bottom-style: solid;border-bottom-color: black;margin-top: 0.5em;margin-bottom: 0.5em;line-height: 1.2;box-sizing: border-box;" powered-by="xiumi.us"> <section style="display: inline-block;border-bottom-width: 6px;border-bottom-style: solid;border-color: rgb(89, 89, 89);margin-bottom: -1px;font-size: 20px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;">锁的分类及特性</p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <section style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"> <span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问时变得有序所设计的一种规则。</span> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">对于任何一种数据库来说都需要有相应的锁定机制,所以 MySQL 自然也不能例外。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">MySQL 数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">MySQL 各存储引擎使用了三种类型(级别)的锁定机制:</span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">表级锁定</span></strong></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">行级锁定</span></strong></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">页级锁定</span></strong></p></li> </ul> <p style="white-space: normal;line-height: normal;"><br></p> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;"> <section style="width: 0.6em;display: inline-block;vertical-align: middle;box-sizing: border-box;"> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 0.2;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <span style="margin-top: 2px;margin-bottom: 2px;width: 0.6em;height: 0.6em;display: block;opacity: 0.6;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 1;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> </section> <section style="padding-left: 5px;display: inline-block;vertical-align: middle;font-size: 18px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><strong>表级锁定(table-level)</strong></p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">表级别的锁定是 MySQL 各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">所以获取锁和释放锁的速度很快。由于表级锁定一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">使用表级锁定的主要是 MyISAM,MEMORY,CSV 等一些非事务性存储引擎。 </span></p> <p style="white-space: normal;line-height: normal;"><br></p> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;"> <section style="width: 0.6em;display: inline-block;vertical-align: middle;box-sizing: border-box;"> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 0.2;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <span style="margin-top: 2px;margin-bottom: 2px;width: 0.6em;height: 0.6em;display: block;opacity: 0.6;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <strong><span style="width: 0.6em;height: 0.6em;display: block;opacity: 1;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span></strong> </section> <section style="padding-left: 5px;display: inline-block;vertical-align: middle;font-size: 18px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><strong>行级锁定(row-level)</strong></p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">此外,行级锁定也最容易发生死锁。</span><span style="color: rgb(89, 89, 89);font-size: 15px;letter-spacing: 1px;line-height: 1.75em;">使用行级锁定的主要是 InnoDB 存储引擎。</span></p> <p style="white-space: normal;line-height: normal;"><br></p> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;"> <section style="width: 0.6em;display: inline-block;vertical-align: middle;box-sizing: border-box;"> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 0.2;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <span style="margin-top: 2px;margin-bottom: 2px;width: 0.6em;height: 0.6em;display: block;opacity: 0.6;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <strong><span style="width: 0.6em;height: 0.6em;display: block;opacity: 1;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span></strong> </section> <section style="padding-left: 5px;display: inline-block;vertical-align: middle;font-size: 18px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><strong>页级锁定(page-level)</strong></p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">页级锁定是 MySQL 中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。</span><span style="color: rgb(89, 89, 89);font-size: 15px;letter-spacing: 1px;line-height: 1.75em;">使用页级锁定的主要是 BerkeleyDB 存储引擎。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">总的来说,MySQL 这三种锁的特性可大致归纳如下:</span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">表级锁:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">行级锁:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">页面锁:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。</span></p></li> </ul> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">适用:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如 Web 应用。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。 </span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 0.5em;margin-bottom: 0.5em;border-bottom-width: 1px;border-bottom-style: solid;border-bottom-color: black;line-height: 1.2;box-sizing: border-box;"> <section style="margin-bottom: -1px;display: inline-block;border-bottom-width: 6px;border-bottom-style: solid;border-color: rgb(89, 89, 89);font-size: 20px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;">表级锁定(MyISAM 举例)</p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">由于 MyISAM 存储引擎使用的锁定机制完全是由 MySQL 提供的表级锁定实现,所以下面我们将以 MyISAM 存储引擎作为示例存储引擎。</span></p> <p style="white-space: normal;line-height: normal;"><br></p> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;"> <section style="width: 0.6em;display: inline-block;vertical-align: middle;box-sizing: border-box;"> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 0.2;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <span style="margin-top: 2px;margin-bottom: 2px;width: 0.6em;height: 0.6em;display: block;opacity: 0.6;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 1;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> </section> <section style="padding-left: 5px;display: inline-block;vertical-align: middle;font-size: 18px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><strong>MySQL 表级锁的锁模式</strong></p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">MySQL 的表级锁有两种模式:</span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">表共享读锁(Table Read Lock)</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">表独占写锁(Table Write Lock)</span></p></li> </ul> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">锁模式的兼容性:</span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">对 MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="color: rgb(89, 89, 89);font-size: 15px;letter-spacing: 1px;line-height: 1.75em;">对 MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="line-height: 1.75em;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">MyISAM 表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。</span></p></li> </ul> <p style="white-space: normal;line-height: normal;"><br></p> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">总结:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">表锁,读锁会阻塞写,不会阻塞读。而写锁则会把读写都阻塞。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;"> <section style="width: 0.6em;display: inline-block;vertical-align: middle;box-sizing: border-box;"> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 0.2;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <strong><span style="margin-top: 2px;margin-bottom: 2px;width: 0.6em;height: 0.6em;display: block;opacity: 0.6;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span><span style="width: 0.6em;height: 0.6em;display: block;opacity: 1;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span></strong> </section> <section style="padding-left: 5px;display: inline-block;vertical-align: middle;font-size: 18px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><strong>如何加表锁</strong></p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。</span></p> <p style="white-space: normal;line-height: normal;"><br></p> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">显示加锁:</span><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">共享读锁:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">lock table tableName read</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">独占写锁:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">lock table tableName write</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">同时加多锁:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">lock table t1 write,t2 read</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">批量解锁:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">unlock tables</span><br></p></li> </ul> <p style="white-space: normal;line-height: normal;"><br></p> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;"> <section style="width: 0.6em;display: inline-block;vertical-align: middle;box-sizing: border-box;"> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 0.2;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <span style="margin-top: 2px;margin-bottom: 2px;width: 0.6em;height: 0.6em;display: block;opacity: 0.6;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <strong><span style="width: 0.6em;height: 0.6em;display: block;opacity: 1;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span></strong> </section> <section style="padding-left: 5px;display: inline-block;vertical-align: middle;font-size: 18px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><strong>MyISAM 表锁优化建议</strong></p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">对于 MyISAM 存储引擎,虽然使用表级锁定在锁定实现的过程中比实现行级锁定或者页级锁定所带来的附加成本都要小,锁定本身所消耗的资源也是最少。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">但是由于锁定的颗粒度比较大,所以造成锁定资源的争用情况也会比其他的锁定级别都要多,从而在较大程度上会降低并发处理能力。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">所以,在优化 MyISAM 存储引擎锁定问题的时候,最关键的就是如何让其提高并发度。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">由于锁定级别是不可能改变的了,所以我们首先需要尽可能让锁定的时间变短,然后就是让可能并发进行的操作尽可能的并发。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">①查询表级锁争用情况</span></strong></p> <p style="white-space: normal;line-height: normal;"><br></p> <section style="margin-right: 8px;margin-bottom: 5px;margin-left: 8px;white-space: normal;line-height: 1.75em;"> <span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">MySQL 内部有两组专门的状态变量记录系统内部锁资源争用情况:</span> </section> <section class="output_wrapper" style="letter-spacing: 0px;white-space: normal;font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code class="hljs ruby" style="margin-right: 2px;margin-left: 2px;padding: 0.5em;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);word-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;background: rgb(40, 43, 46);">mysql> show status like <span class="hljs-string" style="font-size: inherit;line-height: inherit;color: rgb(238, 220, 112);word-wrap: inherit !important;word-break: inherit !important;">'table%'</span>;<br>+----------------------------+---------+<br><span class="hljs-params" style="font-size: inherit;line-height: inherit;color: rgb(255, 152, 35);word-wrap: inherit !important;word-break: inherit !important;">| Variable_name |</span> Value <span class="hljs-params" style="font-size: inherit;line-height: inherit;color: rgb(255, 152, 35);word-wrap: inherit !important;word-break: inherit !important;">|<br>+----------------------------+---------+<br>|</span> Table_locks_immediate <span class="hljs-params" style="font-size: inherit;line-height: inherit;color: rgb(255, 152, 35);word-wrap: inherit !important;word-break: inherit !important;">| 100 |</span><br><span class="hljs-params" style="font-size: inherit;line-height: inherit;color: rgb(255, 152, 35);word-wrap: inherit !important;word-break: inherit !important;">| Table_locks_waited |</span> <span class="hljs-number" style="font-size: inherit;line-height: inherit;color: rgb(174, 135, 250);word-wrap: inherit !important;word-break: inherit !important;">11</span> <span class="hljs-params" style="font-size: inherit;line-height: inherit;color: rgb(255, 152, 35);word-wrap: inherit !important;word-break: inherit !important;">|<br>+----------------------------+---------+<br></span></code></pre> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">这里有两个状态变量记录 MySQL 内部表级锁定的情况,</span><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">两个变量说明如下:</span><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">Table_locks_immediate:</span></strong><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">产生表级锁定的次数。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">Table_locks_waited:</span></strong><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">出现表级锁定争用而发生等待的次数;此值越高则说明存在着越严重的表级锁争用情况。</span></p></li> </ul> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">此外,MyISAM 的读写锁调度是写优先,这也是 MyISAM 不适合做写为主表的存储引擎的原因。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永久阻塞。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">两个状态值都是从系统启动后开始记录,出现一次对应的事件则数量加 1。如果这里的 Table_locks_waited 状态值比较高,那么说明系统中表级锁定争用现象比较严重,就需要进一步分析为什么会有较多的锁定资源争用了。</span></p> <p style="white-space: normal;line-height: normal;"><br></p> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">②缩短锁定时间</span></strong></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">如何让锁定时间尽可能的短呢?</span><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">唯一的办法就是让我们的 Query 执行时间尽可能的短:</span><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">尽量减少大的复杂 Query,将复杂 Query 分拆成几个小的 Query 分布进行。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">尽可能的建立足够高效的索引,让数据检索更迅速。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">尽量让 MyISAM 存储引擎的表只存放必要的信息,控制字段类型。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">利用合适的机会优化 MyISAM 表数据文件。</span></p></li> </ul> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">③分离能并行的操作</span></strong></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">说到 MyISAM 的表锁,而且是读写互相阻塞的表锁,可能有些人会认为在 MyISAM 存储引擎的表上就只能是完全的串行化,没办法再并行了。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">大家不要忘记了,MyISAM 的存储引擎还有一个非常有用的特性,那就是 Concurrent Insert(并发插入)的特性。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">MyISAM 存储引擎有一个控制是否打开 Concurrent Insert 功能的参数选项:concurrent_insert,可以设置为 0,1 或者 2。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">三个值的具体说明如下:</span><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">concurrent_insert=2,</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">无论 MyISAM 表中有没有空洞,都允许在表尾并发插入记录。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">concurrent_insert=1,</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">如果 MyISAM 表中没有空洞(即表的中间没有被删除的行),MyISAM 允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是 MySQL 的默认设置。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">concurrent_insert=0,</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">不允许并发插入。</span></p></li> </ul> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">可以利用 MyISAM 存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">例如,将 concurrent_insert 系统变量设为 2,总是允许并发插入;同时,通过定期在系统空闲时段执行 OPTIMIZE TABLE 语句来整理空间碎片,收回因删除记录而产生的中间空洞。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">④合理利用读写优先级</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">MyISAM 存储引擎的读写是互相阻塞的,那么,一个进程请求某个 MyISAM 表的读锁,同时另一个进程也请求同一表的写锁,MySQL 如何处理呢?</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">这是因为 MySQL 的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">所以,如果我们可以根据各自系统环境的差异决定读与写的优先级:</span><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">通过执行命令 SET LOW_PRIORITY_UPDATES=1,使该连接读比写的优先级高。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">如果我们的系统是一个以读为主,可以设置此参数,如果以写为主,则不用设置。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">通过指定 INSERT、UPDATE、DELETE 语句的 LOW_PRIORITY 属性,降低该语句的优先级。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">虽然上面方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">另外,MySQL 也提供了一种折中的办法来调节读写冲突,即给系统参数 max_write_lock_count 设置一个合适的值,当一个表的读锁达到这个值后,MySQL 就暂时将写请求的优先级降低,给读进程一定获得锁的机会。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">这里还要强调一点:</span></strong><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">一些需要长时间运行的查询操作,也会使写进程“饿死”。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条 SELECT 语句来解决问题,因为这种看似巧妙的 SQL 语句,往往比较复杂,执行时间较长。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">在可能的情况下可以通过使用中间表等措施对 SQL 语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。</span></p> <p style="white-space: normal;line-height: normal;"><br></p> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">InnoDB 默认采用行锁,在未使用索引字段查询时升级为表锁。MySQL 这样设计并不是给你挖坑。它有自己的设计目的。</span><br></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">即便你在条件中使用了索引字段,MySQL 会根据自身的执行计划,考虑是否使用索引(所以 explain 命令中会有 possible_key 和 key)。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">如果 MySQL 认为全表扫描效率更高,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">因此,在分析锁冲突时,别忘了检查 SQL 的执行计划,以确认是否真正使用了索引。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">关于执行计划,第一种情况:</span><span style="font-size: 15px;line-height: 1.6;color: rgb(89, 89, 89);letter-spacing: 1px;">全表更新。</span></strong><span style="font-size: 15px;line-height: 1.6;color: rgb(89, 89, 89);letter-spacing: 1px;">事务需要更新大部分或全部数据,且表又比较大。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;line-height: 1.6;color: rgb(89, 89, 89);letter-spacing: 1px;">若使用行锁,会导致事务执行效率低,从而可能造成其他事务长时间锁等待和更多的锁冲突。</span></p> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;line-height: 1.6;color: rgb(89, 89, 89);letter-spacing: 1px;"></span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><strong><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">第二种情况:</span></strong><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span><span style="font-size: 15px;line-height: 1.6;color: rgb(89, 89, 89);letter-spacing: 1px;">多表级联。事务涉及多个表,比较复杂的关联查询,很可能引起死锁,造成大量事务回滚。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">这种情况若能一次性锁定事务涉及的表,从而可以避免死锁、减少数据库因事务回滚带来的开销。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="margin-top: 0.5em;margin-bottom: 0.5em;border-bottom-width: 1px;border-bottom-style: solid;border-bottom-color: black;line-height: 1.2;box-sizing: border-box;"> <section style="margin-bottom: -1px;display: inline-block;border-bottom-width: 6px;border-bottom-style: solid;border-color: rgb(89, 89, 89);font-size: 20px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;">行级锁定</p> </section> </section> </section> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">行级锁定不是 MySQL 自己实现的锁定方式,而是由其他存储引擎自己所实现的,如广为大家所知的 InnoDB 存储引擎,以及 MySQL 的分布式存储引擎 NDB Cluster 等都是实现了行级锁定。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">考虑到行级锁定均由各个存储引擎自行实现,而且具体实现也各有差别,而 InnoDB 是目前事务型存储引擎中使用最为广泛的存储引擎,所以这里我们就主要分析一下 InnoDB 的锁定特性。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <section style="white-space: normal;box-sizing: border-box;font-size: 16px;"> <section powered-by="xiumi.us" style="transform: rotate(0deg);box-sizing: border-box;"> <section style="margin-top: 10px;margin-bottom: 10px;box-sizing: border-box;"> <section style="width: 0.6em;display: inline-block;vertical-align: middle;box-sizing: border-box;"> <span style="width: 0.6em;height: 0.6em;display: block;opacity: 0.2;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span> <strong><span style="margin-top: 2px;margin-bottom: 2px;width: 0.6em;height: 0.6em;display: block;opacity: 0.6;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span><span style="width: 0.6em;height: 0.6em;display: block;opacity: 1;box-sizing: border-box;background-color: rgb(89, 89, 89);"></span></strong> </section> <section style="padding-left: 5px;display: inline-block;vertical-align: middle;font-size: 18px;color: rgb(89, 89, 89);box-sizing: border-box;"> <p style="box-sizing: border-box;"><strong>InnoDB 锁定模式及实现机制</strong></p> </section> </section> </section> </section> <section style="line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">总的来说,InnoDB 的锁定机制和 Oracle 数据库有不少相似之处。InnoDB 的行级锁定同样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB 也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务需要在锁定行的表上面添加一个合适的意向锁。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">所以,可以说 InnoDB 的锁定模式实际上可以分为四种:</span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">共享锁(S)</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">排他锁(X)</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">意向共享锁(IS)</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">意向排他锁(IX)</span></p></li> </ul> <section style="white-space: normal;line-height: normal;"> <br> </section> <section style="margin-right: 8px;margin-bottom: 5px;margin-left: 8px;white-space: normal;line-height: 1.75em;"> <span style="line-height: 1.6;font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">我们可以通过以下表格来总结上面这四种锁的共存逻辑关系:</span> <span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;"></span> </section> <section style="margin-right: 8px;margin-bottom: 5px;margin-left: 8px;white-space: normal;text-align: center;line-height: 1.75em;"> <img class="rich_pages" data-copyright="0" data-ratio="0.23801652892561984" data-s="300,640" src="/upload/4bddf04de72c9b961f5bb0555ee46a0e.png" data-type="png" data-w="605"> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">如果一个事务请求的锁模式与当前的锁兼容,InnoDB 就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。</span></p> <section style="white-space: normal;line-height: normal;"> <br> </section> <p style="margin-right: 8px;margin-left: 8px;white-space: normal;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;letter-spacing: 1px;color: rgb(71, 193, 168);">意向锁是 InnoDB 自动加的,不需用户干预:</span></p> <ul class=" list-paddingleft-2" style=""> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="font-size: 15px;line-height: 1.6;color: rgb(89, 89, 89);letter-spacing: 1px;">对于 UPDATE、DELETE 和 INSERT 语句,InnoDB 会自动给涉及数据集加排他锁(X)。</span></p></li> <li><p style="margin-right: 8px;margin-left: 8px;line-height: 1.75em;"><span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">对于普通 SELECT 语句,InnoDB 不会加任何锁。</span></p></li> </ul> <section style="white-space: normal;line-height: normal;"> <br> </section> <section style="margin-right: 8px;margin-bottom: 5px;margin-left: 8px;white-space: normal;line-height: 1.75em;"> <span style="line-height: 1.6;font-size: 15px;color: rgb(89, 89, 89);letter-spacing: 1px;">事务可以通过以下语句显示给记录集加共享锁或排他锁:</span> </section> <section class="output_wrapper" style="letter-spacing: 0px;white-space: normal;font-size: 16px;color: rgb(62, 62, 62);line-height: 1.6;font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"> <pre style="font-size: inherit;color: inherit;line-height: inherit;"><code class="hljs sql" style="margin-right: 2px;margin-left: 2px;padding: 0.5em;line-height: 18px;font-size: 14px;letter-spacing: 0px;font-family: Consolas, Inconsolata, Courier, monospace;border-radius: 0px;color: rgb(169, 183, 198);word-wrap: normal !important;word-break: normal !important;overflow: auto !important;display: -webkit-box !important;background: rgb(40, 43, 46);">共享锁(S):<span class="hljs-keyword" style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);word-wrap: inherit !important;word-break: inherit !important;">SELECT</span> * <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);word-wrap: inherit !important;word-break: inherit !important;">FROM</span> table_name <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);word-wrap: inherit !important;word-break: inherit !important;">WHERE</span> ... <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);word-wrap: inherit !important;word-break: inherit !important;">LOCK</span> <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);word-wrap: inherit !important;word-break: inherit !important;">IN</span> <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);word-wrap: inherit !important;word-break: inherit !important;">SHARE</span> <span class="hljs-keyword" style="font-size: inherit;line-height: inherit;color: rgb(248, 35, 117);word-wrap: inherit !important;word-break: inherit !important;">MODE</span><br>排他锁(X):<span class="hljs-keyword" style="font-size: inherit;line-height: inherit;
作者:微信小助手
<p style="white-space: normal;max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(255, 0, 0);font-size: 14px;box-sizing: border-box !important;overflow-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="white-space: normal;max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;text-align: justify;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <blockquote style="white-space: normal;max-width: 100%;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;text-align: justify;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p style="max-width: 100%;min-height: 1em;text-align: left;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">来源:ImportNew - liken</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">1、 为什么两个(1927年)时间相减得到一个奇怪的结果?</span></strong></p> <p>(3623个赞)</p> <p><br></p> <p>如果执行下面的程序,程序解析两个间隔1秒的日期字符串并比较:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">public static void main(String[] args) throws ParseException {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> String str3 = "1927-12-31 23:54:07"; </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> String str4 = "1927-12-31 23:54:08"; </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> Date sDt3 = sf.parse(str3); </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> Date sDt4 = sf.parse(str4); </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> long ld3 = sDt3.getTime() /1000; </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> long ld4 = sDt4.getTime() /1000;</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> System.out.println(ld4-ld3);</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">}</span></p> </blockquote> <p><br></p> <p>输出是:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">353</span></p> </blockquote> <p><br></p> <p>为什么 ld4-ld3 不是1(因为我希望这两个时间差是一秒),而是353?</p> <p><br></p> <p>如果将日期字符串各加一秒:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">String str3 = "1927-12-31 23:54:08"; </span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">String str4 = "1927-12-31 23:54:09";</span></p> </blockquote> <p><br></p> <p>ld4-ld3 的结果是1.</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">sun.util.calendar.ZoneInfo[id="Asia/Shanghai",</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">offset=28800000,dstSavings=0,</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">useDaylight=false,</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">transitions=19,</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">lastRule=null]</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);">Locale(Locale.getDefault()): zh_CN</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>这是上海时区,在12月31日有一个变化。</p> <p><br></p> <p>查阅这个网址来了解上海在1927年时区变化的细节。基本上在1927年年底的午夜,始终会回拨5分52秒。所以“1927-12-31 23:54:08”实际上发生了两次,看起来Java解析了后一次的时间作为当地的日期和时间导致了差异。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">2、Java是“引用传递”还是“值传递”?</span></strong></p> <p>(2480个赞)</p> <p><br></p> <p>我一直认为Java是引用传递;然而,我看了一堆博客(例如<span style="color: rgb(255, 104, 39);text-decoration: underline;">这篇</span>)声称不是这样的。我认为我没有理解它们之间的区别。</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">http://javadude.com/articles/passbyvalue.htm</span></p> </blockquote> <p><br></p> <p>给个解释?</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>Java一直是值传递。不幸的是,他们决定把指针叫做引用,因此新人总是被搞晕。因为这些引用也是通过值传递的。</p> <p><br></p> <p><span style="color: rgb(255, 76, 65);"><strong>3、一个关于Java += 操作符的问题</strong></span></p> <p>(2223赞)</p> <p><br></p> <p>直到今天我认为这个例子:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">i += j;</span></p> </blockquote> <p><br></p> <p>只是一个简写的:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">i = i + j;</span></p> </blockquote> <p><br></p> <p>但如果这样做:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">int i = 5;</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">long j = 8;</span></p> </blockquote> <p><br></p> <p>然而 i = i + j; 没法编译,而 i += j; 就可以编译。</p> <p><br></p> <p>这意味着i += j; 实际上是i = (type of i) (i + j)的简写么?</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>总有人问这类问题,JLS里有答案。参见 <span style="color: rgb(255, 104, 39);">§15.26.2复合赋值运算符</span>。摘录:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.26.2</span></p> </blockquote> <p><br></p> <p>E1 op= E2 型的复合赋值表达式等价于 E1 = (T)((E1) op (E2)),这里 T 是 E1 的类型,不同的是 E1 只计算一次。</p> <p><br></p> <p>一个例子,引自 <span style="color: rgb(255, 104, 39);text-decoration: underline;">§15.26.2</span></p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.26.2</span></p> </blockquote> <p><br></p> <p>[...] 下面的代码是正确的:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">short x = 3;</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">x += 4.6;</span></p> </blockquote> <p><br></p> <p>x的结果等于7,因为它等价于:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">short x = 3;</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">x = (short)(x + 4.6);</span></p> </blockquote> <p><br></p> <p>换句话说,你的假设是正确的。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">4、HashMap 和 Hashtable 之间的不同?</span></strong></p> <p>(1769个赞)</p> <p><br></p> <p>Java中 HashMap 和 Hashtable的不同是什么?</p> <p><br></p> <p>非多线程应用中使用哪个更有效率?</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>Java 中 HashMap 和 HashTable 有几个不同点:</p> <p><br></p> <ol class=" list-paddingleft-2" style="list-style-type: decimal;"> <li><p>Hashtable 是同步的,然而 HashMap不是。 这使得HashMap更适合非多线程应用,因为非同步对象通常执行效率优于同步对象。</p></li> <li><p>Hashtable 不允许 null 值和键。HashMap允许有一个 null 键和人一个 NULL 值。</p></li> <li><p>HashMap的一个子类是LinkedHashMap。所以,如果想预知迭代顺序(默认的插入顺序),只需将HashMap转换成一个LinkedHashMap。用Hashtable就不会这么简单。</p></li> </ol> <p><br></p> <p>因为同步对你来说不是个问题,我推荐使用HashMap。如果同步成为问题,你可能还要看看ConcurrentHashMap。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">5、(如何) 读取或者把一个 InputStream 转成一个 String</span></strong></p> <p>(1724个赞)</p> <p><br></p> <p>如果你有一个 java.io.InputStream 对象,如处理这个对象并生成一个字符串?</p> <p><br></p> <p>假定我有一个 InputStream 对象,它包含文本数据,我希望将它转化成一个字符串(例如,这样我可以将流的内容写到一个log文件中)。</p> <p><br></p> <p>InputStream 转化成 String 最简单方法是什么?</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>使用 Apache commons IOUtils库来拷贝InputStream到StringWriter是一种不错的方式,类似这样:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">StringWriter writer = new StringWriter();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">IOUtils.copy(inputStream, writer, encoding);</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">String theString = writer.toString();</span></p> </blockquote> <p><br></p> <p>甚至</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">// NB: does not close inputStream, you can use IOUtils.closeQuietly for that</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">// 注意:不关闭inputStream,你可以使用 IOUtils.closeQuietly</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">String theString = IOUtils.toString(inputStream, encoding);</span></p> </blockquote> <p><br></p> <p>或者,如果不想混合Stream和Writer,可以使用 ByteArrayOutputStream。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">6、为什么Java中的密码优先使用 char[] 而不是String?</span></strong></p> <p>(1574个赞)</p> <p><br></p> <p>在Swing中,密码字段有一个getPassword()(返回 char数组)方法而不是通常的getText()(返回String)方法。同样的,我遇到过一个建议,不要使用 String 来处理密码。</p> <p><br></p> <p>为什么String涉及到密码时,它就成了一个安全威胁?感觉使用char数组不太方便。</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>String是不可变的。这意味着一旦创建了字符串,如果另一个进程可以进行内存转储,在GC发生前,(除了反射)没有方法可以清除字符串数据。</p> <p><br></p> <p>使用数组操作完之后,可以显式地清除数据:可以给数组赋任何值,密码也不会存在系统中,甚至垃圾回收之前也是如此。</p> <p><br></p> <p>所以,是的,这是一个安全问题 – 但是即使使用了char数组,仅仅缩小了了攻击者有机会获得密码的窗口,它值针对制定的攻击类型。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">7、遍历HashMap的最佳方法</span></strong></p> <p>(1504个赞)</p> <p><br></p> <p>遍历HashMap中元素的最佳方法是什么?</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>这样遍历entrySet:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">public static void printMap(Map mp) {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> Iterator it = mp.entrySet().iterator();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> while (it.hasNext()) {</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> Map.Entry pair = (Map.Entry)it.next();</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> System.out.println(pair.getKey() + " = " + pair.getValue());</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> it.remove(); // avoids a ConcurrentModificationException</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>更多请查阅<span style="color: rgb(255, 104, 39);text-decoration: underline;">Map</span>。</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">http://docs.oracle.com/javase/7/docs/api/java/util/Map.html</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">8、(如何)从数组创建ArrayList</span></strong></p> <p>(1468个赞)</p> <p><br></p> <p>我有一个数组,初始化如下:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">Element[] array = {new Element(1), new Element(2), new Element(3)};</span></p> </blockquote> <p><br></p> <p>我希望将这个数组转化成一个ArrayList类的对象。</p> <p><br></p> <p>解决方案</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">new ArrayList<Element>(Arrays.asList(array))</span></p> </blockquote> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">9、产生一个Java的内存泄露</span></strong></p> <p>(1478个赞)</p> <p><br></p> <p>我有过一个面试,被问到如何产生一个Java内存泄露。不用说,我感到相当傻,甚至如何产生一个的线索都没有。</p> <p><br></p> <p>那么怎么才能产生一个内存泄露呢?</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>在纯Java中,有一个很好的方式可以产生真正的内存泄露(通过执行代码使对象不可访问但仍存在于内存中):</p> <p><br></p> <ol class=" list-paddingleft-2" style="list-style-type: decimal;"> <li><p>应用产生一个长时间运行的线程(或者使用一个线程池加速泄露)。</p></li> <li><p>线程通过一个(可选的自定义)类加载器加载一个类。</p></li> <li><p>该类分配大内存(例如,new byte[1000000]),赋值给一个强引用存储在静态字段中,再将它自身的引用存储到ThreadLocal中。分配额外的内存是可选的(泄露类实例就够了),但是这样将加速泄露工作。</p></li> <li><p>线程清除所有自定义类的或者类加载器载入的引用。</p></li> <li><p>重复上面步骤。</p></li> </ol> <p><br></p> <p>这样是有效的,因为ThreadLocal持有对象的引用,对象持有类的引用,接着类持有类加载器的引用。反过来,类加载器持有所有已加载类的引用。这会使泄露变得更加严重,因为很多JVM实现的类和类加载都直接从持久带(permgen)分配内存,因而不会被GC回收。</p> <p><br></p> <p><strong><span style="color: rgb(255, 76, 65);">10、使用Java在一个区间内产生随机整数数</span></strong></p> <p>(1422个赞)</p> <p><br></p> <p>我试着使用Java生成一个随机整数,但是随机被指定在一个范围里。例如,整数范围是5~10,就是说5是最小的随机值,10是最大的。5到10之间的书也可以是生成的随机数。</p> <p><br></p> <p><strong><span style="color: rgb(123, 12, 0);">解决方案</span></strong></p> <p><br></p> <p>标准的解决方式(Java1.7 之前)如下:</p> <p><br></p> <blockquote> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">import java.util.Random;</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);">public static int randInt(int min, int max) {</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);"> Random rand;</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);"> int randomNum = rand.nextInt((max - min) + 1) + min;</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);"> return randomNum;</span></p> <p><span style="font-size: 12px;color: rgb(136, 136, 136);">}</span></p> </blockquote> <p><br></p> <p>请查看相关的JavaDoc。在实践中,java.util.Random 类总是优于 java.lang.Math.random()。</p> <p><br></p> <p>特别是当标准库里有一个直接的API来完成这个工作,就没有必要重复制造轮子了。</p> <p><br></p> <section class="" powered-by="xiumi.us" style="white-space: normal;max-width: 100%;box-sizing: border-box;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;text-align: justify;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);overflow-wrap: break-word !important;"> <section class="" style="margin-top: 10px;margin-bottom: 10px;max-width: 100%;box-sizing: border-box;text-align: left;overflow-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;overflow-wrap: break-word !important;"> <section class="" powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;box-sizing: border-box;color: rgb(93, 93, 93);overflow-wrap: break-word !important;"> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;overflow-wrap: break-word !important;">【关于投稿】</p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;overflow-wrap: break-word !important;">如果大家有原创好文投稿,请直接给公号发送留言。</p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p class="" style="max-width: 100%;box-sizing: border-box;min-height: 1em;font-size: 13px;overflow-wrap: break-word !important;"><span style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);">① 留言格式:</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);">【投稿】+《 文章标题》+ 文章链接</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);">② 示例:</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);">【投稿】《不要自称是程序员,我十多年的 IT 职场总结》:http://blog.jobbole.com/94148/</span><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;color: rgb(53, 53, 53);box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(53, 53, 53);">③ 最后请附上您的个人简介哈~</span></span></p> <p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> </section> </section> </section> </section> </section> </section> <p style="white-space: normal;max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;text-align: justify;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p style="white-space: normal;max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 14px;color: rgb(255, 169, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;">看完本文有收获?请转发分享给更多人</span></p> <p style="white-space: normal;max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="max-width: 100%;color: rgb(255, 169, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;">关注「ImportNew」,提升Java技能</strong></p> <p style="white-space: normal;max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-size: 17px;letter-spacing: 0.544px;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;background-color: rgb(255, 255, 255);text-align: center;box-sizing: border-box !important;overflow-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;overflow-wrap: break-word !important;visibility: visible !important;width: 600px !important;"></p>
作者:不要哭啦
### 为服务器生成证书 ``` 1.cmd 进入jdk的bin目录 2.keytool -genkey -v -alias tomcat -keyalg RSA -keystore D:\key\tomcat.keystore -validity 36500 ``` (参数简要说明:“D:\key\tomcat.keystore”含义是将证书文件的保存路径,证书文件名称是tomcat.keystore ;“-validity 36500”含义是证书有效期,36500表示100年,默认值是90天 “tomcat”为自定义证书名称)。 ###在命令行填写必要参数: ``` 1、 输入keystore密码 2、TOMCAT部署主机的域名或者IP (剩下的随意填写) 为客户端生成证书 为浏览器生成证书,以便让服务器来验证它。为了能将证书顺利导入至IE和Firefox,证书格式应该是PKCS12,因此,使用如下命令生成: keytool -genkey -v -alias mykey -keyalg RSA -storetype PKCS12 -keystore D:\key\mykey.p12 双击D:\key\mykey.p12 导入到浏览器 ``` ### 让服务器信任客户端证书 由于是双向SSL认证,服务器必须要信任客户端证书,因此,必须把客户端证书添加为服务器的信任认证。由于不能直接将PKCS12格式的证书库导入,必须先把客户端证书导出为一个单独的CER文件,使用如下命令: ``` keytool -export -alias mykey -keystore D:\key\mykey.p12 -storetype PKCS12 -storepass adminroot -rfc -file D:\key\mykey.cer 将该文件导入到服务器的证书库,添加为一个信任证书使用命令如下: keytool -import -v -file D:\key\mykey.cer -keystore D:\key\tomcat.keystore 服务器的证书库 keytool -list -keystore D:\key\tomcat.keystore ``` ### 让客户端信任服务器证书 由于是双向SSL认证,客户端也要验证服务器证书,因此,必须把服务器证书添加到浏览的“受信任的根证书颁发机构”。由于不能直接将keystore格式的证书库导入,必须先把服务器证书导出为一个单独的CER文件,使用如下命令 ``` keytool -keystore D:\key\tomcat.keystore -export -alias tomcat -file D:\key\tomcat.cer (tomcat为你设置服务器端的证书名)。 ``` ### 配置Tomcat服务器 打开Tomcat根目录下的/conf/server.xml,找到Connector port="8443"配置段,修改为如下: ``` <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="D:\\home\\tomcat.keystore" keystorePass="123456" truststoreFile="D:\\home\\tomcat.keystore" truststorePass="123456" /> ``` (tomcat要与生成的服务端证书名一致) ### 属性说明: clientAuth:设置是否双向验证,默认为false,设置为true代表双向验证 keystoreFile:服务器证书文件路径 keystorePass:服务器证书密码 truststoreFile:用来验证客户端证书的根证书,此例中就是服务器证书 truststorePass:根证书密码 在浏览器中输入:https://localhost:8443/,会弹出选择客户端证书界面,点击“确定”, 免费https认证: 沃通 https://buy.wosign.com/free/
作者:じ☆ve宝贝
提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串) 第三方回复加密消息给公众平台 第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 在官方网站下载JCE无限制权限策略文件 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件 ``` JDK7版本JCE下载地址: http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html JDK8版本JCE下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html ```
作者:不要哭啦
``` package cn.studyjava.util; import java.io.*; import java.util.zip.*; /** * @author zsljava@163.com */ public class ZipUtil { public static final String EXT = ".zip"; private static final String BASE_DIR = ""; private static final String FILE_SEPARATOR = File.separator; private static final int BUFFER = 1024; public static void compress(File srcFile) throws Exception { String name = srcFile.getName(); String basePath = srcFile.getParent(); String destPath = basePath + FILE_SEPARATOR + name + EXT; compress(srcFile, destPath); } public static void compress(File srcFile, File destFile) throws Exception { CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(destFile), new CRC32()); ZipOutputStream zos = new ZipOutputStream(cos); compress(srcFile, zos, BASE_DIR); zos.flush(); zos.close(); } public static void compress(File srcFile, String destPath) throws Exception { compress(srcFile, new File(destPath)); } private static void compress(File srcFile, ZipOutputStream zos, String basePath) throws Exception { if (srcFile.isDirectory()) { compressDir(srcFile, zos, basePath); } else { compressFile(srcFile, zos, basePath); } } public static void compress(String srcPath) throws Exception { File srcFile = new File(srcPath); compress(srcFile); } public static void compress(String srcPath, String destPath) throws Exception { File srcFile = new File(srcPath); compress(srcFile, destPath); } private static void compressDir(File dir, ZipOutputStream zos, String basePath) throws Exception { File[] files = dir.listFiles(); if (files.length < 1) { ZipEntry entry = new ZipEntry(basePath + dir.getName() + FILE_SEPARATOR); zos.putNextEntry(entry); zos.closeEntry(); } for (File file : files) { compress(file, zos, basePath + dir.getName() + FILE_SEPARATOR); } } private static void compressFile(File file, ZipOutputStream zos, String dir) throws Exception { ZipEntry entry = new ZipEntry(dir + file.getName()); zos.putNextEntry(entry); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); int count; byte data[] = new byte[BUFFER]; while ((count = bis.read(data, 0, BUFFER)) != -1) { zos.write(data, 0, count); } bis.close(); zos.closeEntry(); } public static void decompress(String srcPath) throws Exception { File srcFile = new File(srcPath); decompress(srcFile); } public static void decompress(File srcFile) throws Exception { String basePath = srcFile.getParent(); decompress(srcFile, basePath); } public static void decompress(File srcFile, File destFile) throws Exception { CheckedInputStream cis = new CheckedInputStream(new FileInputStream(srcFile), new CRC32()); ZipInputStream zis = new ZipInputStream(cis); decompress(destFile, zis); zis.close(); } public static void decompress(File srcFile, String destPath) throws Exception { decompress(srcFile, new File(destPath)); } public static void decompress(String srcPath, String destPath) throws Exception { File srcFile = new File(srcPath); decompress(srcFile, destPath); } private static void decompress(File destFile, ZipInputStream zis) throws Exception { ZipEntry entry = null; while ((entry = zis.getNextEntry()) != null) { String dir = destFile.getPath() + File.separator + entry.getName(); File dirFile = new File(dir); fileProber(dirFile); if (entry.isDirectory()) { dirFile.mkdirs(); } else { decompressFile(dirFile, zis); } zis.closeEntry(); } } private static void fileProber(File dirFile) { File parentFile = dirFile.getParentFile(); if (!parentFile.exists()) { // 递归寻找上级目录 fileProber(parentFile); parentFile.mkdir(); } } private static void decompressFile(File destFile, ZipInputStream zis) throws Exception { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)); int count; byte data[] = new byte[BUFFER]; while ((count = zis.read(data, 0, BUFFER)) != -1) { bos.write(data, 0, count); } bos.close(); } } ```
作者:微信小助手
<p><strong style="text-align: center; font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif; letter-spacing: 0.544px; color: rgb(57, 137, 31);"><img class="rich_pages" data-copyright="0" data-ratio="0.6666666666666666" data-s="300,640" data-type="jpeg" data-w="1280" src="/upload/398d99ea818be829b0277e1b59a977c8.jpg" style="letter-spacing: 0.544px; box-shadow: rgb(170, 170, 170) 0em 0em 1em 0px; visibility: visible !important; width:auto !important;max-width:100% !important;height:auto !important;"></strong><br></p> <p style="margin-top: 25px;margin-bottom: 15px;white-space: normal;font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);line-height: 2em;text-align: center;"><span style="font-size: 20px;">扫描下方二维码<span style="color: rgb(201, 56, 28);"><strong>试读</strong></span></span></p> <p style="margin-bottom: 15px;white-space: normal;font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);text-align: center;"><span style="color: rgb(57, 137, 31);"><strong><img class="" data-ratio="0.6666666666666666" data-type="png" data-w="600" src="/upload/3f6bb75371fa02356c9a40f699003d25.png" style="border-radius: 0em;width: 216.625px;visibility: visible !important;"></strong></span></p> <p style="white-space: normal;"><br></p> <ul class=" list-paddingleft-2" style=""> <li><p style="max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;"><span style="font-size: 14px;">为什么要用validator</span></p></li> <li><p style="max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;"><span style="font-size: 14px;">实战演练</span></p></li> <li><p style="max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;"><span style="font-size: 14px;">自定义参数注解</span></p></li> <li><p style="max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;"><span style="font-size: 14px;">总结</span></p></li> </ul> <hr style="margin-top: 16px;margin-bottom: 16px;max-width: 100%;box-sizing: content-box;letter-spacing: 0.544px;white-space: normal;border-width: 0px;border-style: none;border-color: initial;height: 2px;background-color: rgb(231, 231, 231);color: rgb(52, 73, 94);font-family: Source Sans Pro, Helvetica Neue, Arial, sans-serif;font-size: 16px;text-align: start;overflow-wrap: break-word !important;"> <p style="margin-top: 0.8em; margin-bottom: 0.8em; max-width: 100%; min-height: 1em; letter-spacing: 0.544px; white-space: normal; color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-size: 16px; text-align: start; background-color: rgb(255, 255, 255); line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">很痛苦遇到大量的参数进行校验,在业务中还要抛出异常或者不断的返回异常时的校验信息,在代码中相当冗长, 充满了if-else这种校验代码</p> <p style="margin-top: 0.8em; margin-bottom: 0.8em; max-width: 100%; min-height: 1em; letter-spacing: 0.544px; white-space: normal; color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-size: 16px; text-align: start; background-color: rgb(255, 255, 255); line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">今天我们就来学习spring的javax.validation 注解式参数校验.</p> <h1 style="margin-top: 35px; margin-bottom: 15px; padding-bottom: 0.5em; font-weight: bold; font-size: 1.2rem; max-width: 100%; letter-spacing: 0.544px; white-space: normal; cursor: text; border-bottom: 1px solid rgb(221, 221, 221); color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; text-align: start; background-color: rgb(255, 255, 255); line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">为什么要用validator</h1> <ol class=" list-paddingleft-2" style=""> <li><p style="margin-bottom: 0.8em; max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;"><code style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">javax.validation</code>的一系列注解可以帮我们完成参数校验,免去繁琐的串行校验</p></li> </ol> <p style="margin-top: 0.8em; margin-bottom: 0.8em; max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">不然我们的代码就像下面这样:</p> <pre style="max-width: 100%;letter-spacing: 0.544px;color: rgb(52, 73, 94);font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="padding: 0.5em; max-width: 100%; display: block; font-family: Consolas, Inconsolata, Courier, monospace; overflow-x: auto; font-size: 11px; word-spacing: -3px; letter-spacing: 0px; background: rgb(29, 31, 33); color: rgb(197, 200, 198); line-height: 2em; box-sizing: border-box !important; overflow-wrap: normal !important; word-break: normal !important; overflow-y: auto !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">// http://localhost:8080/api/user/save/serial</span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">/**<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> * 走串行校验<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> *<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> * @param userVO<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> * @return<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> */</span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">@PostMapping</span>(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"/save/serial"</span>)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> public <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">Object</span> save(<span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">@RequestBody</span> UserVO userVO) {<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">String</span> mobile = userVO.getMobile();<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">//手动逐个 参数校验~ 写法</span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">if</span> (StringUtils.isBlank(mobile)) {<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">return</span> RspDTO.paramFail(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"mobile:手机号码不能为空"</span>);<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> } <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">else</span> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">if</span> (!<span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">Pattern</span>.matches(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"^[1][3,4,5,6,7,8,9][0-9]{9}$"</span>, mobile)) {<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">return</span> RspDTO.paramFail(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"mobile:手机号码格式不对"</span>);<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> }<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">//抛出自定义异常等~写法</span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">if</span> (StringUtils.isBlank(userVO.getUsername())) {<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">throw</span> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">new</span> BizException(Constant.PARAM_FAIL_CODE, <span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"用户名不能为空"</span>);<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> }<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">// 比如写一个map返回</span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">if</span> (StringUtils.isBlank(userVO.getSex())) {<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">Map</span><<span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">String</span>, <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">Object</span>> result = <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">new</span> HashMap<>(<span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">5</span>);<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> result.put(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"code"</span>, Constant.PARAM_FAIL_CODE);<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> result.put(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"msg"</span>, <span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"性别不能为空"</span>);<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">return</span> result;<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> }<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">//.........各种写法 ...</span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> userService.save(userVO);<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">return</span> RspDTO.success();<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> }<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"></p></pre> <p style="margin-top: 0.8em; margin-bottom: 0.8em; max-width: 100%; min-height: 1em; letter-spacing: 0.544px; white-space: normal; color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-size: 16px; text-align: start; background-color: rgb(255, 255, 255); line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">这被大佬看见,一定说,都9102了还这么写,然后被劝退了…..</p> <ol class=" list-paddingleft-2" style=""> <li><p style="max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;"><span style="color: rgb(57, 137, 31);">什么是</span><code style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(57, 137, 31);">javax.validation</span></code></p></li> </ol> <p style="margin-top: 0.8em; margin-bottom: 0.8em; max-width: 100%; min-height: 1em; letter-spacing: 0.544px; white-space: normal; color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-size: 16px; text-align: start; background-color: rgb(255, 255, 255); line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解</p> <p style="margin-top: 0.8em; margin-bottom: 0.8em; max-width: 100%; min-height: 1em; letter-spacing: 0.544px; white-space: normal; color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-size: 16px; text-align: start; background-color: rgb(255, 255, 255); line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">我们可以直接将这些注解加在我们JavaBean的属性上面(面向注解编程的时代),就可以在需要校验的时候进行校验了,在SpringBoot中已经包含在starter-web中,再其他项目中可以引用依赖,并自行调整版本:</p> <pre style="max-width: 100%;letter-spacing: 0.544px;color: rgb(52, 73, 94);font-size: 16px;text-align: start;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="padding: 0.5em; max-width: 100%; display: block; font-family: Consolas, Inconsolata, Courier, monospace; overflow-x: auto; font-size: 11px; word-spacing: -3px; letter-spacing: 0px; background: rgb(29, 31, 33); color: rgb(197, 200, 198); line-height: 2em; box-sizing: border-box !important; overflow-wrap: normal !important; word-break: normal !important; overflow-y: auto !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"></span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">dependency</span>></span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">groupId</span>></span>javax.validation<span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">groupId</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">artifactId</span>></span>validation-api<span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">artifactId</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">version</span>></span>1.1.0.Final<span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">version</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">dependency</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(150, 152, 150);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"></span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">dependency</span>></span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">groupId</span>></span>org.hibernate<span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">groupId</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">artifactId</span>></span>hibernate-validator<span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">artifactId</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"><<span class="" style="max-width: 100%;">version</span>></span>5.2.0.Final<span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">version</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(204, 102, 102);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">dependency</span>><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"></p></pre> <p style="max-width: 100%; letter-spacing: 0.544px; white-space: normal; color: rgb(52, 73, 94); font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif; font-size: 16px; text-align: start; background-color: rgb(255, 255, 255); line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;"><img class="" data-ratio="0.6666666666666666" data-type="other" data-w="1280" src="/upload/6c3f6d8fb559c0a4c3a576bdcd117493.other"></p> <ol class=" list-paddingleft-2" style=""> <li><p style="margin-bottom: 0.8em; max-width: 100%; min-height: 1em; line-height: 2em; box-sizing: border-box !important; overflow-wrap: break-word !important;">注解说明</p><pre style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><p style="padding: 0.5em; max-width: 100%; display: block; font-family: Consolas, Inconsolata, Courier, monospace; overflow-x: auto; font-size: 11px; word-spacing: -3px; letter-spacing: 0px; background: rgb(29, 31, 33); color: rgb(197, 200, 198); line-height: 2em; box-sizing: border-box !important; overflow-wrap: normal !important; word-break: normal !important; overflow-y: auto !important;"> <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">1.@NotNull</span>:不能为<span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">null</span>,但可以为empty(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">""</span>,<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">" "</span>,<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">" "</span>)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">2.@NotEmpty</span>:不能为<span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">null</span>,而且长度必须大于<span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">0</span> (<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">" "</span>,<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">" "</span>)<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"> <span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">3.@NotBlank</span>:只能作用在<span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">String</span>上,不能为<span class="" style="max-width: 100%;color: rgb(178, 148, 187);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">null</span>,而且调用trim()后,长度必须大于<span class="" style="max-width: 100%;color: rgb(222, 147, 95);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">0</span>(<span class="" style="max-width: 100%;color: rgb(181, 189, 104);box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;">"test"</span>) 即:必须有实际字符<br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: inherit !important;word-break: inherit !important;"></p></pre></li> </ol> <p style="padding: 6px 13px; word-break: break-all; border-top-width: 1px; border-bottom: 0px; border-top-color: rgb(223, 226, 229); border-right-color: rgb(223, 226, 229); border-left-color: rgb(223, 226, 229); max-width: 100%; box-sizing: border-box; text-align: center; line-height: 2em; overflow-wrap: break-word !important;"><br></p> <table> <thead style="max-width: 100%;box-sizing: border-box;background-color: rgb(250, 250, 250);overflow-wrap: break-word !important;"> <tr style="max-width: 100%;box-sizing: border-box;border-top: 1px solid rgb(223, 226, 229);overflow-wrap: break-word !important;" class="firstRow"> <td width="112.33333333333333"><span style="font-size: 14px;">验证注解</span></td> <td width="102.33333333333333"><span style="font-size: 14px;">验证的数据类型</span></td> <td><span style="font-size: 14px;">说明</span></td> </tr> </thead> <tbody style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <tr style="max-width: 100%;box-sizing: border-box;border-top: 1px solid rgb(223, 226, 229);overflow-wrap: break-word !important;"> <td style="padding: 6px 13px;word-break: break-all;border-color: rgb(223, 226, 229);max-width: 100%;box-sizing: border-box;text-align: center;overflow-wrap: break-word !important;" width="59"><p style="line-height: 2em;"><span style="font-size: 12px;">@AssertFalse</span></p></td> <td style="padding: 6px 13px;word-break: break-all;border-color: rgb(223, 226, 229);max-width: 100%;box-sizing: border-box;text-align: center;overflow-wrap: break-word !important;" width="82.33333333333333"><p style="line-height: 2em;"><span style="font-size: 12px;">Boolean,boolean</span></p></td> <td style="padding: 6px 13px;word-break: break-all;border-color: rgb(223, 226, 229);max-width: 100%;box-sizing: border-box;text-align: center;overflow-wrap: break-word !important;"><p style="line-height: 2em;"><span style="font-size: 12px;">验证注解的元素值是false</span></p></td> </tr> <tr style="max-width: 100%;box-sizing: border-box;border-top: 1px solid rgb(223, 226, 229);background-color: rgb(250, 250, 250);overflow-wrap: break-word !important;"> <td style="padding: 6px 13px;word-break: break-all;border-color: rgb(223, 226, 229);max-width: 100%;box-sizing: border-box;text-align: center;overflow-wrap: break-word !important;" width="59"><p style="line-height: 2em;"><span style="font-size: 12px;">@AssertTrue</span></p></td> <td style="padding: 6px 13px;word-break: break-all;border-color: rgb(223, 226, 229);max-width: 100%;box-sizing: border-box;text-align: center;overflow-wrap: break-word !important;" width="82.33333333333333"><p style="line-height: 2em;"><span style="font-size: 12px;">Boolean,boolean</span></p></td> <td style="padding: 6px 13px;word-break: break-all;border-color: rgb(223, 226, 229);max-width: 100%;box-sizing: border-box;text-align: center;overflow-wrap: break-word !important;"><p style="line-height: 2em;"><span style="font-size: 12px;">验证注解的元素值是true</span></p></td> </tr> <tr style="max-width: 100%;box-sizing: border-box;border-top: 1px solid rgb(223, 226, 229);overflow-wrap: break-word !important;"> <td style="padding: 6px 13p
作者:微信小助手
<p data-mpa-powered-by="yiban.io"></p> <section class="xmteditor" style="display:none;" data-tools="新媒体管家" data-label="powered by xmt.cn"></section> <p></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="max-width: 100%;min-height: 1em;white-space: normal;font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;background-color: rgb(255, 255, 255);text-align: left;letter-spacing: 1px;line-height: 1.5em;overflow-wrap: break-word !important;box-sizing: border-box !important;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;font-size: 12px;letter-spacing: 0.544px;text-align: right;">松若章</span><br></p></li> <li><p style="max-width: 100%;min-height: 1em;white-space: normal;text-align: left;overflow-wrap: break-word !important;box-sizing: border-box !important;"><span style="font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;letter-spacing: 0.544px;text-align: right;word-spacing: 2px;caret-color: rgb(51, 51, 51);background-color: rgb(255, 255, 255);color: rgb(0, 0, 0);font-size: 12px;">https://zhuanlan.zhihu.com/p/61423830</span><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: 14px;letter-spacing: 0.544px;text-align: right;word-spacing: 2px;caret-color: rgb(51, 51, 51);background-color: rgb(255, 255, 255);color: rgb(0, 0, 0);"></span></p></li> </ul> <p style="max-width: 100%;min-height: 1em;white-space: normal;overflow-wrap: break-word !important;box-sizing: border-box !important;"><span style="font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;letter-spacing: 0.544px;text-align: right;word-spacing: 2px;caret-color: rgb(51, 51, 51);background-color: rgb(255, 255, 255);color: rgb(0, 0, 0);font-size: 12px;"><br></span></p> <p></p> <section class="" style="max-width: 100%;color: rgb(62, 62, 62);font-family: Helvetica Neue, Helvetica, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;font-size: 16px;letter-spacing: 0px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <figure class="" style="max-width: 100%;box-sizing: inherit;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;letter-spacing: 0px;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="max-width: 100%;box-sizing: border-box;caret-color: rgb(51, 51, 51);font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;letter-spacing: 0.544px;text-size-adjust: auto;overflow-wrap: break-word !important;"> <section class="" powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="margin-top: 20px;margin-bottom: 10px;max-width: 100%;box-sizing: border-box;text-align: center;overflow-wrap: break-word !important;"> <section style="padding-right: 8px;padding-bottom: 8px;padding-left: 8px;max-width: 100%;box-sizing: border-box;display: inline-block;min-width: 10%;vertical-align: top;background-color: rgb(245, 213, 66);overflow-wrap: break-word !important;"> <section class="" powered-by="xiumi.us" style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="margin-top: -10px;max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> <section style="padding: 3px;max-width: 100%;box-sizing: border-box;display: inline-block;border-bottom: 1px solid rgb(62, 62, 62);line-height: 1;letter-spacing: 0.8px;overflow-wrap: break-word !important;"> <p style="max-width: 100%;box-sizing: border-box;min-height: 1em;overflow-wrap: break-word !important;"><strong style="max-width: 100%;box-sizing: border-box;overflow-wrap: break-word !important;"> 正文 </strong></p> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </figure> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> <section class="" style="max-width: 100%;color: rgb(62, 62, 62);font-size: 16px;white-space: normal;background-color: rgb(255, 255, 255);font-family: -apple-system-font, system-ui, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;letter-spacing: 0.544px;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;font-family: Helvetica Neue, Helvetica, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;line-height: 1.6;letter-spacing: 0px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p style="margin-top: 20px;margin-bottom: 20px;max-width: 100%;min-height: 1em;caret-color: rgb(51, 51, 51);font-family: -apple-system-font, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', Arial, sans-serif;letter-spacing: 0.544px;text-size-adjust: auto;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;white-space: pre-line;box-sizing: border-box !important;overflow-wrap: break-word !important;">曾经有这么一道经典面试题:<span style="max-width: 100%;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;white-space: pre-line;color: rgb(241, 136, 35);">从 URL 在浏览器被被输入到页面展现的过程中发生了什么?</span></span></p> <p style="margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">相信大多数准备过的同学都能回答出来,但是如果继续问:</p> <p style="margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式、什么顺序、建立了多少连接、使用什么协议被下载下来的呢?</p> <p style="margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: center;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="" data-ratio="0.6666666666666666" data-type="jpeg" data-w="255" src="/upload/e2b746f2f5ff0f6a26f3a0a414d8219c.jpg" style="box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 136px !important;"></p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">要搞懂这个问题,我们需要先解决下面五个问题:</p> <ol class=" list-paddingleft-2" style=""> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">现代浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开?</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></span></p></li> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">一个 TCP 连接可以对应几个 HTTP 请求?</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></span></p></li> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)?</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></span></p></li> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">为什么有的时候刷新页面不需要重新建立 SSL 连接?</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></span></p></li> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">浏览器对同一 Host 建立 TCP 连接到数量有没有限制?</span></span></p></li> </ol> <h3 style="margin-top: 1.5rem;margin-bottom: 1rem;font-size: 20px;max-width: 100%;letter-spacing: 0.544px;text-size-adjust: auto;color: rgb(21, 153, 87);text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: Menlo, Monaco, 'Source Code Pro', Consolas, Inconsolata, 'Ubuntu Mono', 'DejaVu Sans Mono', 'Courier New', 'Droid Sans Mono', 'Hiragino Sans GB', 微软雅黑, monospace !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;">第一个问题</span></strong></h3> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">现代浏览器在与服务器建立了一个 TCP 连接后是否会在一个 HTTP 请求完成后断开?什么情况下会断开?</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">在 HTTP/1.0 中,一个服务器在发送完一个 HTTP 响应后,会断开 TCP 链接。但是这样每次请求都会重新建立和断开 TCP 连接,代价过大。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所以虽然标准中没有设定,某些服务器对 Connection: keep-alive 的 Header 进行了支持。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">意思是说,完成这个 HTTP 请求之后,不要断开 HTTP 请求使用的 TCP 连接。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">这样的好处是连接可以被重新使用,之后发送 HTTP 请求的时候不需要重新建立 TCP 连接。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">另外,如果维持连接,那么 SSL 的开销也可以避免,两张图片是我短时间内两次访问 https://www.github.com 的时间统计:</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="" data-ratio="0.6666666666666666" data-type="jpeg" data-w="720" src="/upload/2de536fd1e409d34dcf41457e1c7a932.jpg" style="border-width: 2px;border-style: solid;border-color: rgb(238, 238, 238);border-radius: 6px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 677px !important;"></p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">头一次访问,有初始化连接和 SSL 开销</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="" data-ratio="0.6666666666666666" data-type="jpeg" data-w="720" src="/upload/c820d9321aac9ced923fea8a8ee016fe.jpg" style="border-width: 2px;border-style: solid;border-color: rgb(238, 238, 238);border-radius: 6px;box-sizing: border-box !important;overflow-wrap: break-word !important;visibility: visible !important;width: 677px !important;"></p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">初始化连接和 SSL 开销消失了,说明使用的是同一个 TCP 连接</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">持久连接:既然维持 TCP 连接好处这么多,HTTP/1.1 就把 Connection 头写进标准,并且默认开启持久连接</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">除非请求中写明 Connection: close,那么浏览器和服务器之间是会维持一段时间的 TCP 连接,不会一个请求结束就断掉。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所以第一个问题的<strong>答案</strong>是:默认情况下建立 TCP 连接不会断开,只有在请求报头中声明 Connection: close 才会在请求完成后关闭连接。</p> <h3 style="margin-top: 1.5rem;margin-bottom: 1rem;font-size: 20px;max-width: 100%;letter-spacing: 0.544px;text-size-adjust: auto;color: rgb(21, 153, 87);text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: Menlo, Monaco, 'Source Code Pro', Consolas, Inconsolata, 'Ubuntu Mono', 'DejaVu Sans Mono', 'Courier New', 'Droid Sans Mono', 'Hiragino Sans GB', 微软雅黑, monospace !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;">第二个问题</span></strong></h3> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">一个 TCP 连接可以对应几个 HTTP 请求?</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">了解了第一个问题之后,其实这个问题已经有了答案,如果维持连接,一个 TCP 连接是可以发送多个 HTTP 请求的。</p> <h3 style="margin-top: 1.5rem;margin-bottom: 1rem;font-size: 20px;max-width: 100%;letter-spacing: 0.544px;text-size-adjust: auto;color: rgb(21, 153, 87);text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: Menlo, Monaco, 'Source Code Pro', Consolas, Inconsolata, 'Ubuntu Mono', 'DejaVu Sans Mono', 'Courier New', 'Droid Sans Mono', 'Hiragino Sans GB', 微软雅黑, monospace !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;">第三个问题</span></strong></h3> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">一个 TCP 连接中 HTTP 请求发送可以一起发送么(比如一起发三个请求,再三个响应一起接收)?</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">HTTP/1.1 存在一个问题,单个 TCP 连接在同一时刻只能处理一个请求</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">它的意思是说:两个请求的生命周期不能重叠,任意两个 HTTP 请求从开始到结束的时间在同一个 TCP 连接里不能重叠。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">虽然 HTTP/1.1 规范中规定了 Pipelining 来试图解决这个问题,但是这个功能在浏览器中默认是关闭的。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">先来看一下 Pipelining 是什么,RFC 2616 中规定了:</p> <blockquote style="margin-bottom: 1.2em;padding: 15px 15px 15px 1rem;border-left-width: 6px;border-left-color: rgb(220, 230, 240);color: rgb(129, 145, 152);font-size: 14px;max-width: 100%;letter-spacing: 0.544px;text-size-adjust: auto;line-height: 22px;background-color: rgb(242, 247, 251);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p style="max-width: 100%;min-height: 1em;white-space: pre-line;color: rgb(74, 74, 74);line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">A client that supports persistent connections MAY "pipeline" its requests (i.e., send multiple requests without waiting for each response). A server MUST send its responses to those requests in the same order that the requests were received. 一个支持持久连接的客户端可以在一个连接中发送多个请求(不需要等待任意请求的响应)。收到请求的服务器必须按照请求收到的顺序发送响应。</p> </blockquote> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">至于标准为什么这么设定,我们可以大概推测一个原因:</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">由于 HTTP/1.1 是个文本协议,同时返回的内容也并不能区分对应于哪个发送的请求,所以顺序必须维持一致。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">比如你向服务器发送了两个请求 <code class="" style="max-width: 100%;background-color: rgb(243, 241, 241);color: rgb(88, 88, 88);line-height: 18px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: consolas, menlo, courier, monospace, Microsoft Yahei!important;border-width: 0px !important;border-style: initial !important;border-color: initial !important;"><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">GET</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">/</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">query</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">?</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">q</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">=</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">A</span></code> 和 <code class="" style="max-width: 100%;background-color: rgb(243, 241, 241);color: rgb(88, 88, 88);line-height: 18px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: consolas, menlo, courier, monospace, Microsoft Yahei!important;border-width: 0px !important;border-style: initial !important;border-color: initial !important;"><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">GET</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">/</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">query</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">?</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">q</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">=</span><span class="" style="padding-right: 2px;padding-left: 2px;max-width: 100%;display: inline-block;font-size: 14px;box-sizing: border-box !important;overflow-wrap: break-word !important;">B</span></code>,服务器返回了两个结果,浏览器是没有办法根据响应结果来判断响应对应于哪一个请求的。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">Pipelining 这种设想看起来比较美好,但是在实践中会出现许多问题:</p> <ul class=" list-paddingleft-2" style="list-style-type: square;"> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">一些代理服务器不能正确的处理 HTTP Pipelining。</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></span></p></li> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">正确的流水线实现是复杂的。</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></span></p></li> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;margin-bottom: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">Head-of-line Blocking 连接头阻塞:在建立起一个 TCP 连接之后,假设客户端在这个连接连续向服务器发送了几个请求,按照标准,服务器应该按照收到请求的顺序返回结果</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">假设服务器在处理首个请求时花费了大量时间,那么后面所有的请求都需要等着首个请求结束才能响应。</span></span></p></li> </ul> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所以现代浏览器默认是不开启 HTTP Pipelining 的。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">但是,HTTP2 提供了 Multiplexing 多路传输特性,可以在一个 TCP 连接中同时完成多个 HTTP 请求。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">至于 Multiplexing 具体怎么实现的就是另一个问题了。我们可以看一下使用 HTTP2 的效果。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><img class="" data-ratio="0.6666666666666666" data-type="jpeg" data-w="720" src="/upload/5992022a9ef4871fe6c3fc33ffef00d9.jpg" style="background-color: rgb(238, 237, 235);border-width: 2px;border-style: solid;border-color: rgb(238, 238, 238);background-size: 22px;border-radius: 6px;background-position: center center;background-repeat: no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">绿色是发起请求到请求返回的等待时间,蓝色是响应的下载时间,可以看到都是在同一个 Connection,并行完成的</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所以这个问题也有了<strong>答案</strong>:在 HTTP/1.1 存在 Pipelining 技术可以完成这个多个请求同时发送,但是由于浏览器默认关闭,所以可以认为这是不可行的。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">在 HTTP2 中由于 Multiplexing 特点的存在,多个 HTTP 请求可以在同一个 TCP 连接中并行进行。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">那么在 HTTP/1.1 时代,浏览器是如何提高页面加载效率的呢?主要有下面两点:</p> <ol class=" list-paddingleft-2" style=""> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">维持和服务器已经建立的 TCP 连接,在同一连接上顺序处理多个请求。</span></span></p><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></span></p></li> <li><p style="max-width: 100%;min-height: 1em;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(74, 74, 74);line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;font-size: 14px !important;"><span style="max-width: 100%;line-height: 22px;box-sizing: border-box !important;overflow-wrap: break-word !important;">和服务器建立多个 TCP 连接。</span></span></p></li> </ol> <h3 style="margin-top: 1.5rem;margin-bottom: 1rem;font-size: 20px;max-width: 100%;letter-spacing: 0.544px;text-size-adjust: auto;color: rgb(21, 153, 87);text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: Menlo, Monaco, 'Source Code Pro', Consolas, Inconsolata, 'Ubuntu Mono', 'DejaVu Sans Mono', 'Courier New', 'Droid Sans Mono', 'Hiragino Sans GB', 微软雅黑, monospace !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;">第四个问题</span></strong></h3> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">为什么有的时候刷新页面不需要重新建立 SSL 连接?</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">在第一个问题的讨论中已经有了<strong>答案:</strong>TCP 连接有的时候会被浏览器和服务端维持一段时间。TCP 不需要重新建立,SSL 自然也会用之前的。</p> <h3 style="margin-top: 1.5rem;margin-bottom: 1rem;font-size: 20px;max-width: 100%;letter-spacing: 0.544px;text-size-adjust: auto;color: rgb(21, 153, 87);text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;font-family: Menlo, Monaco, 'Source Code Pro', Consolas, Inconsolata, 'Ubuntu Mono', 'DejaVu Sans Mono', 'Courier New', 'Droid Sans Mono', 'Hiragino Sans GB', 微软雅黑, monospace !important;"><strong style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;">第五个问题</span></strong></h3> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">浏览器对同一 Host 建立 TCP 连接到数量有没有限制?</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">假设我们还处在 HTTP/1.1 时代,那个时候没有多路传输,当浏览器拿到一个有几十张图片的网页该怎么办呢?</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">肯定不能只开一个 TCP 连接顺序下载,那样用户肯定等的很难受</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">但是如果每个图片都开一个 TCP 连接发 HTTP 请求,那电脑或者服务器都可能受不了</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">要是有 1000 张图片的话总不能开 1000 个TCP 连接吧,你的电脑同意 NAT 也不一定会同意。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">所以<strong>答案</strong>是:有。Chrome 最多允许对同一个 Host 建立六个 TCP 连接。不同的浏览器有一些区别。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="font-size: 14px;">https://developers.google.com/web/tools/chrome-devtools/network/issues#queued-or-stalled-requestsdevelopers.google.com</span></p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">那么,<strong style="max-width: 100%;color: rgb(0, 0, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;">回到最开始的问题:</strong></p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式、什么顺序、建立了多少连接、使用什么协议被下载下来的呢?</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">如果图片都是 HTTPS 连接并且在同一个域名下,那么浏览器在 SSL 握手之后会和服务器商量能不能用 HTTP2</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">如果能的话就使用 Multiplexing 功能在这个连接上进行多路传输。不过也未必会所有挂在这个域名的资源都会使用一个 TCP 连接去获取,但是可以确定的是 Multiplexing 很可能会被用到。</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">如果发现用不了 HTTP2 呢?或者用不了 HTTPS(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1)</p> <p style="margin-top: 15px;margin-bottom: 15px;max-width: 100%;min-height: 1em;letter-spacing: 0.544px;text-size-adjust: auto;white-space: pre-line;color: rgb(74, 74, 74);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;text-align: start;line-height: 2em;box-sizing: border-box !important;overflow-wrap: break-word !important;">那浏览器就会在一个 HOST 上建立多个 TCP 连接,连接数量的最大限制取决于浏览器设置,这些连接会在空闲的时候被浏览器用来发送新的请求,如果所有的连接都正在发送请求呢?那其他的请求就只能等等了。</p> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> <p></p> <hr style="max-width: 100%;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);color: rgb(62, 62, 62);font-size: 16px;text-align: center;word-spacing: 2px;font-family: 微软雅黑;border-style: solid;border-right-width: 0px;border-bottom-width: 0px;border-left-width: 0px;border-color: rgba(0, 0, 0, 0.1);transform-origin: 0px 0px;transform: scale(1, 0.5);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p></p> <section style="margin-right: 8px;margin-left: 8px;max-width: 100%;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);color: rgb(62, 62, 62);font-size: 16px;text-align: center;word-spacing: 2px;font-family: 微软雅黑;min-height: 1em;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;word-break: normal !important;"> <span style="max-width: 100%;font-size: 14px;color: rgb(214, 214, 214);letter-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;">-END-</span> </section> <section class="" style="max-width: 100%;letter-spacing: 0.544px;color: rgb(62, 62, 62);font-family: 微软雅黑;font-size: 16px;word-spacing: 1px;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <p style="max-width: 100%;min-height: 1em;font-size: medium;font-weight: 700;word-spacing: 2px;widows: 1;text-align: center;letter-spacing: 1px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-size: 16px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span></p> <p style="max-width: 100%;min-height: 1em;font-size: medium;font-weight: 700;word-spacing: 2px;widows: 1;text-align: center;letter-spacing: 1px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <section data-role="outer" label="Powered by 135editor.com" style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section data-role="outer" label="Powered by 135editor.com" style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" data-tools="135编辑器" data-id="94711" style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section data-width="100%" style="max-width: 100%;width: 677px;text-align: right;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="max-width: 100%;display: inline-block;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section class="" data-brushtype="text" style="padding: 6px 12px 10px 14px;max-width: 100%;box-sizing: border-box;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/7QRTvkK2qC44DTRQaDYNceOqwlOmdicDzAb3MLb2byRokONFbgPMolIssQXEj2DA1DTSC1lMe61VHMRIbgNENMA/640?wx_fmt=png");background-repeat: no-repeat;text-align: center;background-size: 100% 100%;font-size: 14px;letter-spacing: 2px;overflow-wrap: break-word !important;"> 我就知道你在看! </section> </section> </section> </section> </section> </section> </section> <p></p>
作者:じ☆ve宝贝
# 代码优化细节 ### 1、尽量指定类、方法的final修饰符 ------------ 带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String,整个类都是final的。为类指定final修饰符可以让类不可以被继承,为方法指定final修饰符可以让方法不可以被重写。如果指定了一个类为final,则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法,内联对于提升Java运行效率作用重大,具体参见Java运行期优化。此举能够使性能平均提高50%。 ### 2、尽量重用对象 ------------ 特别是String对象的使用,出现字符串连接时应该使用StringBuilder/StringBuffer代替。由于Java虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。 ### 3、尽可能使用局部变量 ------------ 调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。 ### 4、及时关闭流 ------------ Java编程过程中,进行数据库连接、I/O流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。 ### 5、尽量减少对变量的重复计算 ------------ 明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作: ``` for (int i = 0; i < list.size(); i++){ ... } ``` 建议替换为: ``` for (int i = 0, int length = list.size(); i < length; i++){ ... } ``` 这样,在list.size()很大的时候,就减少了很多的消耗 ### 6、尽量采用懒加载的策略,即在需要的时候才创建 ------------ 例如: ``` String str = "aaa"; if (i == 1){ list.add(str); } ``` 建议替换为: ``` if (i == 1) { String str = "aaa"; list.add(str); } ``` ### 7、慎用异常 ------------ 异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。 ### 8、不要在循环中使用try…catch…,应该把其放在最外层 ------------ 除非不得已。如果毫无理由地这么写了,只要你的领导资深一点、有强迫症一点,八成就要骂你为什么写出这种垃圾代码来了 ### 9、如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度 ------------ 比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例: (1)StringBuilder() // 默认分配16个字符的空间 (2)StringBuilder(int size) // 默认分配size个字符的空间 (3)StringBuilder(String str) // 默认分配16个字符+str.length()个字符空间 可以通过类(这里指的不仅仅是上面的StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中—-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么: (1)在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间 (2)把原来的4096个字符拷贝到新的的字符数组中去 这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。 ### 10、当复制大量数据时,使用System.arraycopy()命令 ### 11、乘法和除法使用移位操作 ------------ 例如: ``` for (val = 0; val < 100000; val += 5) { a = val * 8; b = val / 2; } ``` 用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,因此建议修改为: ``` for (val = 0; val < 100000; val += 5) { a = val << 3; b = val >> 1; } ``` 移位操作虽然快,但是可能会使代码不太好理解,因此最好加上相应的注释。 ### 12、循环内不要不断创建对象引用 ------------ 例如: ``` for (int i = 1; i <= count; i++) { Object obj = new Object(); } ``` 这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为: ``` Object obj = null; for (int i = 0; i <= count; i++) { obj = new Object(); } ``` 这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。 ### 13、基于效率和类型检查的考虑,应该尽可能使用array,无法确定数组大小时才使用ArrayList ### 14、尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销 ### 15、不要将数组声明为public static final ------------ 因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变 ### 16、尽量在合适的场合使用单例 ------------ 使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: (1)控制资源的使用,通过线程同步来控制资源的并发访问 (2)控制实例的产生,以达到节约资源的目的 (3)控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信 ### 17、尽量避免随意使用静态变量 ------------ 要知道,当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的,如: ``` public class A { private static B b = new B(); } ``` 此时静态变量b的生命周期与A类相同,如果A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止 ### 18、及时清除不再需要的会话 ------------ 为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。当应用服务器需要保存更多的会话时,如果内存不足,那么操作系统会把部分数据转移到磁盘,应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘,甚至可能抛出内存不足的异常。如果会话要被转储到磁盘,那么必须要先被序列化,在大规模集群中,对对象进行序列化的代价是很昂贵的。因此,当会话不再需要时,应当及时调用HttpSession的invalidate()方法清除会话。 ### 19、实现RandomAccess接口的集合比如ArrayList,应当使用最普通的for循环而不是foreach循环来遍历 ------------ 这是JDK推荐给用户的。JDK API对于RandomAccess接口的解释是:实现RandomAccess接口用来表明其支持快速随机访问,此接口的主要目的是允许一般的算法更改其行为,从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明,实现RandomAccess接口的类实例,假如是随机访问的,使用普通for循环效率将高于使用foreach循环;反过来,如果是顺序访问的,则使用Iterator会效率更高。可以使用类似如下的代码作判断: ``` if (list instanceof RandomAccess) { for (int i = 0; i < list.size(); i++){} ... }else{ Iterator<?> iterator = list.iterable(); while (iterator.hasNext()){ iterator.next(); } } ``` foreach循环的底层实现原理就是迭代器Iterator,参见Java语法糖1:可变长度参数以及foreach循环原理。所以后半句”反过来,如果是顺序访问的,则使用Iterator会效率更高”的意思就是顺序访问的那些类实例,使用foreach循环去遍历。 ### 20、使用同步代码块替代同步方法 ------------ 这点在多线程模块中的synchronized锁方法块一文中已经讲得很清楚了,除非能确定一整个方法都是需要进行同步的,否则尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。 ### 21、将常量声明为static final,并以大写命名 ------------ 这样在编译期间就可以把这些内容放入常量池中,避免运行期间计算生成常量的值。另外,将常量的名字以大写命名也可以方便区分出常量与变量 ### 22、不要创建一些不使用的对象,不要导入一些不使用的类 ------------ 这毫无意义,如果代码中出现”The value of the local variable i is not used”、”The import java.util is never used”,那么请删除这些无用的内容 ### 23、程序运行过程中避免使用反射 ------------ 关于,请参见反射。反射是Java提供给用户一个很强大的功能,功能强大往往意味着效率不高。不建议在程序运行过程中使用尤其是频繁使用反射机制,特别是Method的invoke方法,如果确实有必要,一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存—-用户只关心和对端交互的时候获取最快的响应速度,并不关心对端的项目启动花多久时间。 ### 24、使用数据库连接池和线程池 ------------ 这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程 ### 25、使用带缓冲的输入输出流进行IO操作 ------------ 带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率 ### 26、顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList ------------ 这个,理解ArrayList和LinkedList的原理就知道了 ### 27、不要让public方法中有太多的形参 ------------ public方法即对外提供的方法,如果给这些方法太多形参的话主要有两点坏处: 1、违反了面向对象的编程思想,Java讲求一切都是对象,太多的形参,和面向对象的编程思想并不契合 2、参数太多势必导致方法调用的出错概率增加 至于这个”太多”指的是多少个,3、4个吧。比如我们用JDBC写一个insertStudentInfo方法,有10个学生信息字段要插如Student表中,可以把这10个参数封装在一个实体类中,作为insert方法的形参 ### 28、字符串变量和字符串常量equals的时候将字符串常量写在前面 ------------ 这是一个比较常见的小技巧了,如果有以下代码: ``` String str = "123"; if (str.equals("123")) { ... } ``` 建议修改为: ``` String str = "123"; if ("123".equals(str)){ ... } ``` 这么做主要是可以避免空指针异常 ### 29、请知道,在java中if (i == 1)和if (1 == i)是没有区别的,但从阅读习惯上讲,建议使用前者 ------------ 平时有人问,”if (i == 1)”和”if (1== i)”有没有区别,这就要从C/C++讲起。 在C/C++中,”if (i == 1)”判断条件成立,是以0与非0为基准的,0表示false,非0表示true,如果有这么一段代码: ``` int i = 2; if (i == 1){ ... }else{ ... } ``` C/C++判断”i==1″不成立,所以以0表示,即false。但是如果: `int i = 2;if (i = 1) { ... }else{ ... }` 万一程序员一个不小心,把”if (i == 1)”写成”if (i = 1)”,这样就有问题了。在if之内将i赋值为1,if判断里面的内容非0,返回的就是true了,但是明明i为2,比较的值是1,应该返回的false。这种情况在C/C++的开发中是很可能发生的并且会导致一些难以理解的错误产生,所以,为了避免开发者在if语句中不正确的赋值操作,建议将if语句写为: `int i = 2;if (1 == i) { ... }else{ ... }` 这样,即使开发者不小心写成了”1 = i”,C/C++编译器也可以第一时间检查出来,因为我们可以对一个变量赋值i为1,但是不能对一个常量赋值1为i。 但是,在Java中,C/C++这种”if (i = 1)”的语法是不可能出现的,因为一旦写了这种语法,Java就会编译报错”Type mismatch: cannot convert from int to boolean”。但是,尽管Java的”if (i == 1)”和”if (1 == i)”在语义上没有任何区别,但是从阅读习惯上讲,建议使用前者会更好些。 ### 30、不要对数组使用toString()方法 ------------ 看一下对数组使用toString()打印出来的是什么: ``` public static void main(String[] args){ int[] is = new int[]{1, 2, 3}; System.out.println(is.toString()); } ``` 结果是: `[I@18a992f` 本意是想打印出数组内容,却有可能因为数组引用is为空而导致空指针异常。不过虽然对数组toString()没有意义,但是对集合toString()是可以打印出集合里面的内容的,因为集合的父类AbstractCollections<E>重写了Object的toString()方法。 ### 31、不要对超出范围的基本数据类型做向下强制转型 ------------ 这绝不会得到想要的结果: ``` public static void main(String[] args){ long l = 12345678901234L; int i = (int)l; System.out.println(i); } ``` 我们可能期望得到其中的某几位,但是结果却是: 1942892530 解释一下。Java中long是8个字节64位的,所以12345678901234在计算机中的表示应该是: 0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010 一个int型数据是4个字节32位的,从低位取出上面这串二进制数据的前32位是: 0111 0011 1100 1110 0010 1111 1111 0010 这串二进制表示为十进制1942892530,所以就是我们上面的控制台上输出的内容。从这个例子上还能顺便得到两个结论: 1、整型默认的数据类型是int,long l = 12345678901234L,这个数字已经超出了int的范围了,所以最后有一个L,表示这是一个long型数。顺便,浮点型的默认类型是double,所以定义float的时候要写成”"float f = 3.5f” 2、接下来再写一句”int ii = l + i;”会报错,因为long + int是一个long,不能赋值给int ### 32、公用的集合类中不使用的数据一定要及时remove掉 ------------ 如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。 33、把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+”"最慢 把一个基本数据类型转为一般有三种方式,我有一个Integer型数据i,可以使用i.toString()、String.valueOf(i)、i+”"三种方式,三种方式的效率如何,看一个测试: ``` public static void main(String[] args){ int loopTime = 50000; Integer i = 0; long startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++){ String str = String.valueOf(i); } System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++){ String str = i.toString(); } System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++){ String str = i + ""; } System.out.println("i + \"\":" + (System.currentTimeMillis() - startTime) + "ms"); } ``` 运行结果为: `String.valueOf():11ms Integer.toString():5ms i + "":25ms` 所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单: 1、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断 2、Integer.toString()方法就不说了,直接调用了 3、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串 三者对比下来,明显是2最快、1次之、3最慢 ### 34、使用最有效率的方式去遍历Map ------------ 遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是: ``` public static void main(String[] args){ HashMap<String, String> hm = new HashMap<String, String>(); hm.put("111", "222"); Set<Map.Entry<String, String>> entrySet = hm.entrySet(); Iterator<Map.Entry<String, String>> iter = entrySet.iterator(); while (iter.hasNext()){ Map.Entry<String, String> entry = iter.next(); System.out.println(entry.getKey() + "\t" + entry.getValue()); } } ``` 如果你只是想遍历一下这个Map的key值,那用”Set<String> keySet = hm.keySet();”会比较合适一些 ### 35、对资源的close()建议分开操作 ------------ 意思是,比如我有这么一段代码: ``` try{ XXX.close(); YYY.close(); }catch (Exception e){ ... } ``` 建议修改为: ``` try{ XXX.close(); }catch (Exception e) { ... } try{ YYY.close(); }catch (Exception e) { ... } ``` 虽然有些麻烦,却能避免资源泄露。我们想,如果没有修改过的代码,万一XXX.close()抛异常了,那么就进入了cath块中了,YYY.close()不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,是可能引起资源句柄泄露的。而改为下面的写法之后,就保证了无论如何XXX和YYY都会被close掉。
作者:仲夏时节的梦想
 项目中用到的,可以用在短时间内只让执行一次的地方。具体情况还得进行细节修改,这里只是个小例子
作者:微信小助手
<section class="xmteditor" style="display:none;" data-tools="新媒体管家" data-label="powered by xmt.cn"></section> <section class="mpa-template" data-mpa-template-id="2214" data-mpa-color="#ffffff" data-mpa-category="引导" style="text-align: center;" data-mpa-powered-by="yiban.io"> <img class="__bg_gif" data-ratio="0.13125" src="/upload/d35c65b85456da212335716db2388b76.gif" data-type="gif" data-w="640" style="width: auto !important;height: auto !important;visibility: visible !important;" title="1711018374.gif"> </section> <p style="white-space: normal;text-align: center;"><br></p> <section class="" style="white-space: normal;widows: 1;"> <section class=""> <section class="" powered-by="xiumi.us" style="line-height: 25.6px;"> <section class=""> <section class=""> <section class="" powered-by="xiumi.us"> <section class=""> <section class=""> <blockquote style="margin-bottom: 20px;padding: 10px 20px 10px 15px;border-left-width: 5px;border-left-color: rgb(214, 219, 223);background-color: rgba(112, 138, 153, 0.1);"> <p style="font-size: 16px;"><span style="font-size: 13px;">本文来源:石杉的架构笔记 (ID:shishan100)</span></p> </blockquote> </section> </section> </section> </section> </section> </section> </section> </section> <p><br></p> <section class="KolEditor" data-tools-id="14640"> <section class="KolEditor checkSelected" data-tools-id="56033"> <section class="KolEditor" data-tools-id="57528" style="padding-right: 10px;padding-left: 10px;"> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;font-size: 17px;background-color: rgb(255, 255, 255);text-align: center;letter-spacing: 1px;line-height: 1.5em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;box-sizing: border-box;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;color: rgb(136, 136, 136);font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: PingFangSC-Regular;vertical-align: baseline;font-size: 16px;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, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;font-size: 17px;background-color: rgb(255, 255, 255);text-align: center;letter-spacing: 1px;line-height: 1.5em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;box-sizing: border-box;border-width: 0px;border-style: initial;border-color: initial;font-style: inherit;color: rgb(136, 136, 136);font-variant: inherit;font-weight: inherit;font-stretch: inherit;line-height: inherit;font-family: PingFangSC-Regular;vertical-align: baseline;font-size: 16px;overflow-wrap: break-word !important;"><br></span></p> <section class="KolEditor" data-tools-id="59247"> <section style="margin: 10px auto;text-align: center;box-sizing: border-box;"> <section style="display: inline-block;border-radius: 20px;background: #33cc33;margin-left: 2px;"> <section style="border-width: 2px;border-style: solid;border-color: rgb(0, 86, 31);border-radius: 20px;margin: -5px 5px 5px -2px;background: rgb(255, 255, 255);padding: 5px 20px;"> <p style="font-size: 16px;"><strong><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: 17px;letter-spacing: 1px;color: rgb(255, 169, 0);">目录</span></strong></p> <p style="font-size: 16px;text-align: left;"><span style="font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;letter-spacing: 1px;font-size: 14px;color: rgb(255, 169, 0);">一、写在前面<br>二、可靠消息最终一致性方案的核心流程<br>二、可靠消息最终一致性方案的高可用保障生产实践<br></span></p> </section> </section> </section> </section> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;font-size: 17px;background-color: rgb(255, 255, 255);text-align: center;letter-spacing: 1px;line-height: 1.5em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br></p> <p style="max-width: 100%;min-height: 1em;color: rgb(51, 51, 51);font-family: -apple-system-font, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Hiragino Sans GB, Microsoft YaHei UI, Microsoft YaHei, Arial, sans-serif;font-size: 17px;background-color: rgb(255, 255, 255);text-align: center;letter-spacing: 1px;line-height: 1.5em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br></p> <section class="KolEditor" data-tools-id="60561"> <section style="border-bottom-width: 4px;border-bottom-style: solid;border-bottom-color: rgb(170, 166, 149);margin-top: 10px;margin-bottom: 10px;"> <section style="border-bottom-width: 8px;border-bottom-style: solid;border-color: rgb(120, 116, 97);font-size: 14px;line-height: 20px;display: inline-block;margin-bottom: -5px;color: inherit;"> <p style="border-color: rgb(170, 166, 149);color: rgb(170, 166, 149);font-size: 18px;line-height: 1.5em;background-color: rgb(255, 255, 255);"><strong style="border-color: rgb(170, 166, 149);color: inherit;">一、写在前面 </strong></p> </section> </section> </section> <p><br></p> </section> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">上一篇文章咱们聊了聊<strong>TCC分布式事务</strong>,对于常见的微服务系统,大部分接口调用是同步的,也就是一个服务直接调用另外一个服务的接口。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这个时候,用TCC分布式事务方案来保证各个接口的调用,要么一起成功,要么一起回滚,是比较合适的</span><span style="letter-spacing: 1px;">。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">但是在实际系统的开发过程中,可能服务间的调用是异步的。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">也就是说,一个服务发送一个消息给MQ,即消息中间件,比如RocketMQ、RabbitMQ、Kafka、ActiveMQ等等。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">然后,另外一个服务从MQ消费到一条消息后进行处理。这就成了基于MQ的异步调用了。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">那么针对这种基于MQ的异步调用,<span style="font-size: 16px;letter-spacing: 1px;color: rgb(64, 179, 230);">如何保证各个服务间的分布式事务呢</span>?</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">也就是说,我希望的是基于MQ实现异步调用的多个服务的业务逻辑,要么一起成功,要么一起失败。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这个时候,就要用上<strong>可靠消息最终一致性方案</strong>,来实现分布式事务</span><span style="letter-spacing: 1px;">。</span></p> <p><br></p> <p style="text-align: center;"><img class="" data-copyright="0" data-ratio="0.3398148148148148" data-s="300,640" src="/upload/1a0ed8ced3e21983f6a4b6a8a87bcbd1.png" data-type="png" data-w="1080" style=""></p> <p><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">大家看看上面那个图,其实如果不考虑各种高并发、高可用等技术挑战的话,单从“<span style="letter-spacing: 1px;color: rgb(64, 179, 230);">可靠消息</span>”以及“<span style="letter-spacing: 1px;color: rgb(64, 179, 230);">最终一致性</span>”两个角度来考虑,这种分布式事务方案还是比较简单的。</span></p> <p><span class="author-p-53348239"><br></span></p> <p><span class="author-p-53348239"><br></span></p> <section class="KolEditor" data-tools-id="94178"> <section style="border-bottom-width: 4px;border-bottom-style: solid;border-bottom-color: rgb(170, 166, 149);margin-top: 10px;margin-bottom: 10px;"> <section style="border-bottom-width: 8px;border-bottom-style: solid;border-color: rgb(120, 116, 97);font-size: 14px;line-height: 20px;display: inline-block;margin-bottom: -5px;color: inherit;"> <p style="border-color: rgb(170, 166, 149);color: rgb(170, 166, 149);font-size: 18px;line-height: 1.5em;background-color: rgb(255, 255, 255);"><strong style="border-color: rgb(170, 166, 149);color: inherit;">二、可靠消息最终一致性方案的核心流程</strong></p> </section> </section> </section> <p><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><strong><span style="letter-spacing: 1px;color: rgb(241, 136, 35);">(1)上游服务投递消息</span></strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果要实现可靠消息最终一致性方案,一般你可以自己写一个可靠消息服务,实现一些业务逻辑。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><span style="font-size: 16px;letter-spacing: 1px;color: rgb(51, 51, 51);">首先</span>,上游服务需要发送一条消息给可靠消息服务。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这条消息说白了,你可以认为是对下游服务一个接口的调用,里面包含了对应的一些请求参数。</span></p> <p><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">然后,可靠消息服务就得把这条消息存储到自己的数据库里去,状态为<span style="font-size: 16px;letter-spacing: 1px;color: rgb(51, 51, 51);">“待确认”。</span></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><span style="font-size: 16px;letter-spacing: 1px;color: rgb(51, 51, 51);">接着</span>,上游服务就可以执行自己本地的数据库操作,根据自己的执行结果,再次调用可靠消息服务的接口。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果本地数据库操作执行成功了,那么就找可靠消息服务确认那条消息。如果本地数据库操作失败了,那么就找可靠消息服务删除那条消息。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">此时如果是确认消息,那么可靠消息服务就把数据库里的消息状态更新为<span style="font-size: 16px;letter-spacing: 1px;color: rgb(51, 51, 51);">“已发送”,</span>同时将消息发送给MQ。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这里有一个<strong>很关键的点</strong>,就是更新数据库里的消息状态和投递消息到MQ。这俩操作,你得放在一个方法里,而且得开启本地事务。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(64, 179, 230);letter-spacing: 1px;font-size: 16px;">啥意思呢?</span></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果数据库里更新消息的状态失败了,那么就抛异常退出了,就别投递到MQ;</span></p></li> </ul> <p><br></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果投递MQ失败报错了,那么就要抛异常让本地数据库事务回滚。</span></p></li> </ul> <p><br></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这俩操作必须得一起成功,或者一起失败。</span></p><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"></span></p><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"></span></p></li> </ul> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果上游服务是通知删除消息,那么可靠消息服务就得删除这条消息。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(241, 136, 35);letter-spacing: 1px;font-size: 16px;"><strong>(2)下游服务接收消息</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">下游服务就一直等着从MQ消费消息好了,如果消费到了消息,那么就操作自己本地数据库。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果操作成功了,就反过来通知可靠消息服务,说自己处理成功了,然后可靠消息服务就会把消息的状态设置为“已完成”。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><strong><span style="font-size: 16px;letter-spacing: 1px;color: rgb(241, 136, 35);">(3)如何上游服务对消息的100%可靠投递?</span></strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">上面的核心流程大家都看完:一个很大的问题就是,如果在上述投递消息的过程中各个环节出现了问题该怎么办?</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;"><span style="font-size: 16px;letter-spacing: 1px;color: rgb(64, 179, 230);">我们如何保证消息100%的可靠投递,一定会从上游服务投递到下游服务</span><span style="font-size: 16px;letter-spacing: 1px;">?别着急,下面我们来逐一分析。</span></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果上游服务给可靠消息服务发送待确认消息的过程出错了,那没关系,上游服务可以感知到调用异常的,就不用执行下面的流程了,这是没问题的。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果上游服务操作完本地数据库之后,通知可靠消息服务确认消息或者删除消息的时候,出现了问题。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">比如:没通知成功,或者没执行成功,或者是可靠消息服务没成功的投递消息到MQ。<span style="font-size: 16px;letter-spacing: 1px;color: rgb(64, 179, 230);">这一系列步骤出了问题怎么办</span>?</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">其实也没关系,因为在这些情况下,那条消息在可靠消息服务的数据库里的状态会一直是“待确认”。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">此时,我们在可靠消息服务里开发一个<strong>后台定时运行的线程</strong>,不停的检查各个消息的状态。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果一直是“待确认”状态,就认为这个消息出了点什么问题。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">此时的话,就可以回调上游服务提供的一个接口,问问说,兄弟,这个消息对应的数据库操作,你执行成功了没啊?</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果上游服务答复说,我执行成功了,那么可靠消息服务将消息状态修改为“已发送”,同时投递消息到MQ。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果上游服务答复说,没执行成功,那么可靠消息服务将数据库中的消息删除即可。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;"><strong><span style="font-size: 16px;letter-spacing: 1px;">通过这套机制,就可以保证,可靠消息服务一定会尝试完成消息到MQ的投递。</span></strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><strong><span style="font-size: 16px;letter-spacing: 1px;color: rgb(241, 136, 35);">(4)如何保证下游服务对消息的100%可靠接收?</span></strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">那如果下游服务消费消息出了问题,没消费到?或者是下游服务对消息的处理失败了,怎么办?</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">其实也没关系,<strong>在可靠消息服务里开发一个后台线程,不断的检查消息状态。</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果消息状态一直是“已发送”,始终没有变成“已完成”,那么就说明下游服务始终没有处理成功。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">此时可靠消息服务就可以再次尝试重新投递消息到MQ,让下游服务来再次处理。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">只要下游服务的接口逻辑<strong>实现幂等性</strong>,保证多次处理一个消息,不会插入重复数据即可。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;color: rgb(241, 136, 35);font-size: 16px;"><strong>(5)如何基于RocketMQ来实现可靠消息最终一致性方案?</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">在上面的通用方案设计里,完全依赖可靠消息服务的各种自检机制来确保:</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果上游服务的数据库操作没成功,下游服务是不会收到任何通知</span></p></li> </ul> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果上游服务的数据库操作成功了,可靠消息服务死活都会确保将一个调用消息投递给下游服务,而且一定会确保下游服务务必成功处理这条消息。</span></p><p><br></p></li> </ul> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">通过这套机制,保证了基于MQ的异步调用/通知的服务间的分布式事务保障。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">其实阿里开源的RocketMQ,就实现了可靠消息服务的所有功能,核心思想跟上面类似。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">只不过RocketMQ为了保证高并发、高可用、高性能,做了较为复杂的架构实现,非常的优秀。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">有兴趣的同学,自己可以去查阅RocketMQ对分布式事务的支持</span><span style="letter-spacing: 1px;">。</span></p> <p style="margin-left: 8px;margin-right: 8px;"><br></p> <section class="KolEditor" data-tools-id="15758"> <section style="border-bottom-width: 4px;border-bottom-style: solid;border-bottom-color: rgb(170, 166, 149);margin-top: 10px;margin-bottom: 10px;"> <section style="border-bottom-width: 8px;border-bottom-style: solid;border-color: rgb(120, 116, 97);font-size: 14px;line-height: 20px;display: inline-block;margin-bottom: -5px;color: inherit;"> <p style="border-color: rgb(170, 166, 149);color: rgb(170, 166, 149);font-size: 18px;line-height: 1.5em;background-color: rgb(255, 255, 255);"><strong style="border-color: rgb(170, 166, 149);color: inherit;">三、可靠消息最终一致性方案的高可用保障生产实践</strong></p> </section> </section> </section> <p><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(241, 136, 35);letter-spacing: 1px;font-size: 16px;"><strong>(1)背景引入</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">其实上面那套方案和思想,很多同学应该都知道是怎么回事儿,我们也主要就是铺垫一下这套理论思想。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">在实际落地生产的时候,如果没有高并发场景的,完全可以参照上面的思路自己基于某个MQ中间件开发一个可靠消息服务。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果有高并发场景的,可以用RocketMQ的分布式事务支持,上面的那套流程都可以实现。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">今天给大家分享的一个核心主题,就是<strong>这套方案如何保证99.99%的高可用</strong>。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">其实大家应该发现了这套方案里保障高可用性最大的一个依赖点,就是<strong>MQ的高可用性</strong>。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">任何一种MQ中间件都有一整套的高可用保障机制,无论是RabbitMQ、RocketMQ还是Kafka。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">所以在大公司里使用可靠消息最终一致性方案的时候,我们通常对可用性的保障都是依赖于公司基础架构团队对MQ的高可用保障。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">也就是说,大家应该相信兄弟团队,99.99%可以保障MQ的高可用,绝对不会因为MQ集群整体宕机,而导致公司业务系统的分布式事务全部无法运行。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">但是现实是很残酷的,很多中小型的公司,甚至是一些中大型公司,或多或少都遇到过MQ集群整体故障的场景。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">MQ一旦完全不可用,就会导致业务系统的各个服务之间无法通过MQ来投递消息,导致业务流程中断。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">比如最近就有一个朋友的公司,也是做电商业务的,就遇到了MQ中间件在自己公司机器上部署的集群整体故障不可用,导致依赖MQ的分布式事务全部无法跑通,业务流程大量中断的情况。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这种情况,就需要针对这套分布式事务方案实现一套高可用保障机制。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><strong><span style="font-size: 16px;letter-spacing: 1px;color: rgb(241, 136, 35);">(2)基于KV存储的队列支持的高可用降级方案</span></strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="font-size: 16px;"><span style="font-size: 16px;letter-spacing: 1px;color: rgb(64, 179, 230);">大家来看看下面这张图</span><span style="font-size: 16px;letter-spacing: 1px;"><span style="font-size: 16px;letter-spacing: 1px;color: rgb(51, 51, 51);"></span>,这是我曾经指导过朋友的一个公司针对可靠消息最终一致性方案设计的一套高可用保障降级机制。</span></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这套机制不算太复杂,可以非常简单有效的保证那位朋友公司的高可用保障场景,一旦MQ中间件出现故障,立马自动降级为备用方案。</span></p> <p><br></p> <p style="text-align: center;"><img class="" data-copyright="0" data-ratio="0.5685185185185185" data-s="300,640" src="/upload/1c87d485cdcdb5808e6d227917560bc5.png" data-type="png" data-w="1080" style=""></p> <p><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(64, 179, 230);letter-spacing: 1px;font-size: 16px;">(1)自行封装MQ客户端组件与故障感知</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">首先第一点,你要做到自动感知MQ的故障接着自动完成降级,那么必须动手对MQ客户端进行封装,发布到公司Nexus私服上去。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">然后公司需要支持MQ降级的业务服务都使用这个自己封装的组件来发送消息到MQ,以及从MQ消费消息。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">在你自己封装的MQ客户端组件里,你可以根据写入MQ的情况来判断MQ是否故障。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">比如说,如果连续10次重试尝试投递消息到MQ都发现异常报错,网络无法联通等问题,说明MQ故障,此时就可以自动感知以及自动触发降级开关。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(64, 179, 230);letter-spacing: 1px;font-size: 16px;">(2)基于kv存储中队列的降级方案</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果MQ挂掉之后,要是希望继续投递消息,那么就必须得找一个MQ的替代品。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">举个例子,比如我那位朋友的公司是没有高并发场景的,消息的量很少,只不过可用性要求高。此时就可以类似redis的kv存储中的队列来进行替代。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">由于redis本身就支持队列的功能,还有类似队列的各种数据结构,所以你可以将消息写入kv存储格式的队列数据结构中去。</span></p> <p><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><strong>ps</strong>:关于redis的数据存储格式、支持的数据结构等基础知识,请大家自行查阅了,网上一大堆</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">但是,这里有几个大坑,一定要注意一下。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><span style="font-size: 16px;letter-spacing: 1px;color: rgb(51, 51, 51);"><strong>第一个</strong></span>,任何kv存储的集合类数据结构,建议不要往里面写入数据量过大,否则会导致大value的情况发生,引发严重的后果。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">因此绝不能在redis里搞一个key,就拼命往这个数据结构中一直写入消息,这是肯定不行的。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><span style="font-size: 16px;letter-spacing: 1px;color: rgb(51, 51, 51);"><strong>第二个</strong></span>,绝对不能往少数key对应的数据结构中持续写入数据,那样会<span style="font-size: 16px;letter-spacing: 1px;color: rgb(64, 179, 230);">导致热key的产生</span>,也就是某几个key特别热。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">大家要知道,一般kv集群,都是根据key来hash分配到各个机器上的,你要是老写少数几个key,会导致kv集群中的某台机器访问过高,负载过大。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><strong>基于以上考虑,下面是笔者当时设计的方案:</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><strong><br></strong></span></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">根据他们每天的消息量,在kv存储中固定划分上百个队列,有上百个key对应。</span></p></li> </ul> <p><br></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">这样保证每个key对应的数据结构中不会写入过多的消息,而且不会频繁的写少数几个key。</span></p></li> </ul> <p><br></p> <ul class=" list-paddingleft-2" style="list-style-type: disc;"> <li><p style="line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">一旦发生了MQ故障,可靠消息服务可以对每个消息通过hash算法,均匀的写入固定好的上百个key对应的kv存储的队列中。</span></p></li> </ul> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">同时此时需要通过zk触发一个降级开关,整个系统在MQ这块的读和写全部立马降级。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(241, 136, 35);letter-spacing: 1px;font-size: 16px;"><strong>(3)下游服务消费MQ的降级感知</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">下游服务消费MQ也是通过自行封装的组件来做的,此时那个组件如果从zk感知到降级开关打开了,首先会判断自己是否还能继续从MQ消费到数据?</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果不能了,就开启多个线程,并发的从kv存储的各个预设好的上百个队列中不断的获取数据。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">每次获取到一条数据,就交给下游服务的业务逻辑来执行。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">通过这套机制,就实现了MQ故障时候的自动故障感知,以及自动降级。如果系统的负载和并发不是很高的话,用这套方案大致是没没问题的。</span></p> <p><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">因为在生产落地的过程中,包括大量的容灾演练以及生产实际故障发生时的表现来看,都是可以有效的保证MQ故障时,业务流程继续自动运行的。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(241, 136, 35);letter-spacing: 1px;font-size: 16px;"><strong>(4)故障的自动恢复</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果降级开关打开之后,自行封装的组件需要开启一个线程,每隔一段时间尝试给MQ投递一个消息看看是否恢复了。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">如果MQ已经恢复可以正常投递消息了,此时就可以通过zk关闭降级开关,然后可靠消息服务继续投递消息到MQ,下游服务在确认kv存储的各个队列中已经没有数据之后,就可以重新切换为从MQ消费消息。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><br></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="color: rgb(241, 136, 35);letter-spacing: 1px;font-size: 16px;"><strong>(5)更多的业务细节</strong></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">其实上面说的那套方案主要是一套通用的降级方案,但是具体的落地是要结合各个公司不同的业务细节来决定的,很多细节多没法在文章里体现。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">比如说你们要不要保证消息的顺序性?是不是涉及到需要根据业务动态,生成大量的key?等等。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">此外,这套方案实现起来还是有一定的成本的,所以建议大家尽可能还是push公司的基础架构团队,保证MQ的99.99%可用性,不要宕机。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">其次就是根据大家公司的实际对高可用需求来决定,如果感觉MQ偶尔宕机也没事,可以容忍的话,那么也不用实现这种降级方案。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">但是如果公司领导认为MQ中间件宕机后,一定要保证业务系统流程继续运行,那么还是要考虑一些高可用的降级方案,比如本文提到的这种。</span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;"><br></span></p> <p style="margin-left: 8px;margin-right: 8px;line-height: 1.75em;"><span style="letter-spacing: 1px;font-size: 16px;">最后再说一句,真要是一些公司涉及到每秒几万几十万的高并发请求,那么对MQ的降级方案会设计的更加的复杂,那就远远不是这么简单可以做到的。</span></p> <p style="white-space: normal;vertical-align: baseline;letter-spacing: 1px;line-height: 1.5em;"><br></p> <section class="KolEditor" data-tools-id="23409" style="white-space: normal;"> <p style="text-align: center;"><br></p> <section class="" style="widows: 1;"> <section class=""> <section class="" powered-by="xiumi.us" style="line-height: 25.6px;"> <section class=""> <section class=""> <section class="" powered-by="xiumi.us"> <section class=""> <section class=""> <blockquote style="margin-bottom: 20px;padding: 10px 20px 10px 15px;border-left-width: 5px;border-left-color: rgb(214, 219, 223);background-color: rgba(112, 138, 153, 0.1);"> <p style="font-size: 16px;"><span style="font-size: 13px;">作者:中华石杉,十余年BAT架构经验,倾囊相授</span></p> <p style="font-size: 16px;"><span style="font-size: 13px;">个人微信公众号:石杉的架构笔记(id:shishan100)</span></p> <p style="font-size: 16px;"><span style="font-size: 13px;font-family: 宋体;letter-spacing: 2px;">(本文版权归原作者所有。转载文章仅为传播更多信息之目的,如有侵权请与我们联系,我们将及时处理。)</span></p> </blockquote> </section> </section> </section> </section> </section> </section> </section> </section> </section> <section class="" label="Powered by bj.96weixin.com" style="white-space: normal;"> <section class=""> <section> <section powered-by="bj.96weixin.com"> <section powered-by="bj.96weixin.com" style="padding: 5px;"> <section powered-by="bj.96weixin.com" style="color: rgb(156, 156, 116);letter-spacing: 2px;line-height: 22px;text-align: center;"> <section class="" data-mpa-template-id="1208" data-mpa-color="#ffffff" data-mpa-category="divider" style="color: rgb(51, 51, 51);"> <section> <section class="" data-mpa-template-id="1225" data-mpa-color="#ffffff" data-mpa-category="divider"> <section> <section style="text-align: left;"> <section style="margin-right: auto;margin-left: auto;padding-right: 15px;padding-left: 15px;background-color: rgb(254, 254, 254);display: inline-block;"> <section class="" data-mpa-template-id="1208" data-mpa-color="#ffffff" data-mpa-category="divider"> <section> <section class="mpa-template" data-mpa-template-id="1419" data-mpa-color="#ffffff" data-mpa-category="divider"> <p class="mpa-highlight" style="padding-right: 0em;padding-left: 0em;color: rgb(62, 62, 62);font-size: 16px;list-style-type: none;line-height: 25.6px;text-align: center;"><img class="__bg_gif" data-ratio="1" src="/upload/dd153ab34eb13de7d968f500ef94e526.null" data-type="gif" data-w="19" width="19px" style="visibility: visible !important;width: 19px !important;"></p> <p class="mpa-highlight" style="padding-right: 0em;padding-left: 0em;color: rgb(62, 62, 62);font-size: 16px;list-style-type: none;line-height: 25.6px;text-align: center;"><br></p> <p class="" style="max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;text-align: center;box-sizing: border-box !important;word-wrap: break-word !important;"><span style="max-width: 100%;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;letter-spacing: 0.544px;color: rgb(137, 135, 145);font-size: 16px;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;">—————END—————</span></p> <p class="" style="max-width: 100%;min-height: 1em;letter-spacing: 0.544px;white-space: normal;text-align: center;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;letter-spacing: 0.544px;text-indent: 0em;white-space: normal;word-spacing: 2px;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;"><img class="" data-copyright="0" data-ratio="1" data-s="300,640" src="/upload/b5b5c2f05535c1a82d6645d73092e98f.jpg" data-type="jpeg" data-w="258" style="font-family: -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;text-indent: 0em;border-radius: 4px;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;visibility: visible !important;width: 258px !important;"><br></p> <p style="margin-top: 15px;margin-right: 8px;margin-left: 8px;max-width: 100%;min-height: 1em;font-variant-numeric: normal;font-variant-east-asian: normal;white-space: normal;background-color: rgb(255, 255, 255);font-size: 16px;line-height: 1.75em;text-align: center;letter-spacing: 1px;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(127, 127, 127);line-height: 1.75em;">识别图片二维码,关注“</span><span style="max-width: 100%;line-height: 1.75em;color: rgb(0, 176, 240);">无敌码农</span><span style="max-width: 100%;color: rgb(127, 127, 127);line-height: 1.75em;">”获取精彩内容</span></span></p> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section> </section>