文章列表

Mybatis 一连串提问,被面试官吊打了!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">最近出去面试,在简历中写了些关于Mybatis的技术点,于是面试官就开始对我不断询问,本文特意记录下面试中遇到的一些问题。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">说说什么是Mybatis</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">Mybatis是一款对于Sql进行了一定封装的持久化sql框架,将常用的crud接口进行了一定的封装,减轻了开发人员对于SQL操作的繁琐性。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">在工作中为什么会选择使用这款框架?</span></h2> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 简化了sql的相关操作复杂度 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 自动处理好了链接的创建,释放,sql的参数组装 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 可以引入一些第三方缓存的插件 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 提供了对于Spring容器的集成功能 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 学习成本低,市面上也有比较多的资料信息 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">说一下正常的JDBC执行规范?</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">首先需要通过DriverManager建立链接,然后获取到Statement对象。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">JdbcApplication</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;String&nbsp;driverName&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"com.mysql.jdbc.Driver"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;String&nbsp;username&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"root"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;String&nbsp;password&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"test"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;String&nbsp;url&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"jdbc:mysql://cloud.db.com:3306/db_user"</span>;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">main</span><span style="line-height: 26px;">(String[]&nbsp;args)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;SQLException&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Connection&nbsp;connection&nbsp;=&nbsp;DriverManager.getConnection(url,username,password);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;sql&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"select&nbsp;*&nbsp;from&nbsp;t_user"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PreparedStatement&nbsp;preparedStatement&nbsp;=&nbsp;connection.prepareStatement(sql);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ResultSet&nbsp;resultSet&nbsp;=&nbsp;preparedStatement.executeQuery();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">while</span>(resultSet.next()){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;id&nbsp;=&nbsp;resultSet.getInt(<span style="color: #50a14f;line-height: 26px;">"id"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(id);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resultSet.close();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;preparedStatement.close();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connection.close();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">建立一个数据源的大致步骤:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 构建数据源 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 创建数据库链接,一般是可以通过 <code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">DriverManager.getConnection()</code>来获取 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 创建sql并且执行,例如调用Statement接口执行,JDBC的api中定义的 <code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">executeQuery()</code>方法执行查询操作, <code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">executeUpdate()</code>方法执行更新操作 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 结果集的处理,一般都是对resultSet进行getint,getString之类的操作。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 链接的释放,例如说.close相关操作。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">你说自己看过MyBatis的源码,列举些内部比较核心的类说说?</span></h2> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">Configuration</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">管理 mysql-config.xml 全局配置关系类</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">SqlSessionFactory</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">SqlSession 管理工厂接口</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">SqlSession</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">是一个面向用户(程序员)的接口。SqlSession 中提 供了很多操作数据库的方法</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">Executor</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">执行器是一个接口(基本执行器、缓存执行器)</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">作用:SqlSession 内部通过执行器操作数据库</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">MappedStatement</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">底层封装对象 作用:对操作数据库存储封装,包括 sql 语句、输入输出参数</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">StatementHandler</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">具体操作数据库相关的 handler 接口</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">ResultSetHandler</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">具体操作数据库返回结果的 handler 接口</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">SQL对象</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">mybatis内部拼接sql语句信息的封装对象</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">ScriptRunner</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">mybatis内部一个能接收链接参数信息的脚本运行器。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">SqlRunner</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">使用SqlRunner可以简化我们的jdbc执行操作,代码如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">main</span><span style="line-height: 26px;">(String[]&nbsp;args)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;SQLException&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;Connection&nbsp;connection&nbsp;=&nbsp;DriverManager.getConnection(url,username,password);<br>&nbsp;&nbsp;&nbsp;&nbsp;SqlRunner&nbsp;sqlRunner&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;SqlRunner(connection);<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;querySql&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;SQL(){{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SELECT("*");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FROM("t_user");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WHERE("1=1");<br>&nbsp;&nbsp;&nbsp;&nbsp;}}.toString();<br>&nbsp;&nbsp;&nbsp;&nbsp;List&lt;Map&lt;String,Object&gt;&gt;&nbsp;resultMap&nbsp;=&nbsp;sqlRunner.selectAll(querySql);<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">for</span>&nbsp;(Map&lt;String,&nbsp;Object&gt;&nbsp;stringObjectMap&nbsp;:&nbsp;resultMap)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(stringObjectMap.toString());<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">MetaObject</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">一个比较常用的反射工具类,在mybatis源码里面经常会看到。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">MetaClass</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这里面包含了一个<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">org.apache.ibatis.reflection.Reflector</code> 对象,这个对象内部似乎包含了比较多的关于反射获取的属性值,例如说方法信息,字段属性等。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">ObjectFactory</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这是一个创建对象的工厂设计,在创建某些对象之前会先又一层包装的warrper,可以适当地调整入参信息。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">ObjectFactory&nbsp;objectFactory&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;DefaultObjectFactory();<br>List&lt;Object&gt;&nbsp;strs&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ArrayList&lt;Object&gt;();<br>List&lt;Class&lt;?&gt;&gt;&nbsp;classList&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ArrayList&lt;&gt;();<br>String&nbsp;str&nbsp;=&nbsp;objectFactory.create(String<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>,<span style="color: #c18401;line-height: 26px;">classList</span>,<span style="color: #c18401;line-height: 26px;">strs</span>)</span>;<br><br></code></pre> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">ProxyFactory</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">是一个代理工厂,主要适配了mybatis里面的几种代理机制。ProxyFactory接口有两个不同的实现,分别为<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">CglibProxyFactory</code>和<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">JavassistProxyFactory</code></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">如果不使用Spring框架,Mybatis框架该如何使用?</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">原生mybatis1的执行流程:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">main</span><span style="line-height: 26px;">(String[]&nbsp;args)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;IOException&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;resource&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">"mybatis-config.xml"</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;InputStream&nbsp;inputStream&nbsp;=&nbsp;Resources.getResourceAsStream(resource);<br>&nbsp;&nbsp;&nbsp;&nbsp;SqlSessionFactory&nbsp;sqlSessionFactory&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;SqlSessionFactoryBuilder().build(inputStream);<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//核心点在这</span><br>&nbsp;&nbsp;&nbsp;&nbsp;SqlSession&nbsp;sqlSession&nbsp;=&nbsp;sqlSessionFactory.openSession();<br>&nbsp;&nbsp;&nbsp;&nbsp;UserMapper&nbsp;mapper&nbsp;=&nbsp;sqlSession.getMapper(UserMapper<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;List&lt;User&gt;&nbsp;userList&nbsp;=&nbsp;mapper.selectAll();<br>&nbsp;&nbsp;&nbsp;&nbsp;System.out.println(userList.toString());<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">首先通过sqlsessionfactory结合配置文件的数据(可以是io流信息),创建出一个sqlsession,然后根据sqlsession获取到通过MapperProxy创建的jdk代理,当执行对应的sql语句的时候会调用invocationhandler里面的invoke语句。接下来就是一系列的crud封装的handler处理。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">聊聊MyBatis内部的缓存设计</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">每当我们使用 MyBatis 开启一次和数据库的会话,MyBatis 会创建出一个 SqlSession 对象表 示一次数据库会话。在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次查询都会查询一次数据库,而我们在极短的时间内 做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">为了解决这一问题,减少资源的浪费,MyBatis 会在表示会话的 SqlSession 对象中建立一个 简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完 全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.39215686274509803" src="/upload/8aad195d1d244159843679efb552fc15.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">mybatis的内部的cache目录下包含有多种缓存的设计,全部都存放在了一个包内部,主要是对cache接口进行了多实现。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.4365671641791045" src="/upload/4ec922e93182d09c719354398145580e.png" data-type="png" data-w="268" style="margin-right: auto;margin-left: auto;width: 267px;border-radius: 5px;display: block;margin-bottom: 15px;height: 384px;"> </figure> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">一级缓存</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">一级缓存是和sqlsession做绑定关联的一种存储设计,当首次查询出来的数据会被存储到一个hashmap中,而对应的cacheKey这块可以在源代码<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">org.apache.ibatis.executor.BaseExecutor#createCacheKey</code>里看到。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.23039215686274508" src="/upload/5af86c01d83b397295d02d1dd082354a.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">如果需要查看缓存的更多细节部分,可以debug模式切换到<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">org.apache.ibatis.cache.impl.PerpetualCache</code>里面进行查看。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.9705882352941176" src="/upload/24c2c4c66282514eafa73b874640a865.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">内部包含了一个HashMap集合用于存储缓存数据。而所谓的清除缓存就是将这个hashmap执行clear操作。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3956043956043956" src="/upload/f7f08d1e60be5986295443fe4a7c15e8.png" data-type="png" data-w="273" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">缓存如何实现唯一值?</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">通过类的函数id+包名实现唯一值图片:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.49019607843137253" src="/upload/655196b32d701bb381729588c183665e.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">内部对于缓存还会区分环境类型:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img data-ratio="0.4395424836601307" src="/upload/e28768e44ec66d4638fb559f1c936eea.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这里的环境id正好对应了配置文件中所填写的id:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img data-ratio="0.44281045751633985" src="/upload/3e8e2b3bf6abf8b131e49c3f6849ce.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">为什么一级缓存也叫查询缓存</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">因为查询出来的数据会放到一个map中,一旦出现写操作,缓存就会失效。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">什么时候一级缓存会失效?</strong></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">一旦涉及到对应的update操作,就会清理缓存。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">源代码如下所示:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #4078f2;line-height: 26px;">@Override</span><br><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">update</span><span style="line-height: 26px;">(MappedStatement&nbsp;ms,&nbsp;Object&nbsp;parameter)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;SQLException&nbsp;</span>{<br>&nbsp;&nbsp;ErrorContext.instance().resource(ms.getResource()).activity(<span style="color: #50a14f;line-height: 26px;">"executing&nbsp;an&nbsp;update"</span>).object(ms.getId());<br>&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(closed)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">throw</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ExecutorException(<span style="color: #50a14f;line-height: 26px;">"Executor&nbsp;was&nbsp;closed."</span>);<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;clearLocalCache();<br>&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;doUpdate(ms,&nbsp;parameter);<br>}<br></code></pre> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">框架内部是如何识别该清理哪些缓存?</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">由于按照包名+类名+id的方式构建cache的key,所以没法识别出该清理哪个缓存,因此一旦进行更新操作的时候是直接将整个map进行清理。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.23202614379084968" src="/upload/28995c59b23e493f1c39b740255fe83d.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3880952380952381" src="/upload/c565df6061f4342ee800b9e2d1b9f2c2.png" data-type="png" data-w="420" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img data-ratio="0.41198501872659177" src="/upload/dca7eb39aa95f8485cf6724370f6fa2.png" data-type="png" data-w="267" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img data-ratio="0.34477124183006536" src="/upload/b14483cd8c36c362708514329793fdb3.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">一级缓存和sqlsession之间的关系</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">一个sqlsession会有一个自己专属的map用作一级缓存,不同sqlsession之间的缓存不会共享。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">多数据源的情况下会连接不同的<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">sqlsessionfactory</code>,不同的<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">sqlsessionfactory</code>则对应不同的sqlsession,所以一级缓存不同,因此查询和更新的时候不会互相影响。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">可以手动清理缓存吗?</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">可以通过<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">sqlSession.clearCache();</code>操作实现</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">如何判断两次查询是同一个查询:</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">按照cachekey的构建机制判断:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img data-ratio="0.7745098039215687" src="/upload/394b3b034acd55634888e641d3f1f2f8.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">缓存的key是由好几个参数构建出来的:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> hashcode </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 查询的条数范围:如果是selectall系列,就是0-2147483647(21亿左右) </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> sql语句 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> enviorment的配置id </section></li> </ul> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">二级缓存</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">框架的内部默认是没有开启二级缓存的,需要开发人员手动开启。二级缓存是根据sqlsession进行识别的,同一个mapper在不同的sqlsession中会共享缓存。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">代码案例:</strong></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">需要执行commit操作才会写入到二级缓存中</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.37745098039215685" src="/upload/7199a5f8f70c931f1558498c33ff8ec5.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">同时在xml配置里需要额外配置:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.24673202614379086" src="/upload/843535d46ba4627a6b9b166f14b332cf.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> <figcaption style="margin-top: 5px;text-align: center;color: #dda52d;font-size: 14px;"> mybatis-config.xml文件 </figcaption> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">对应到mapper文件中配置:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.36437908496732024" src="/upload/76e00ae6e59a400d244be57719c3f7f6.png" data-type="png" data-w="612" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">mybatis中还可以配置userCache和flushCache等配置项,userCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。</p> </section>

要想通过面试,MySQL的 Limit 子句底层原理你不可不知

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">文章目录</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 老样子,建个表 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 从sql执行计划看Limit的影响 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 从server层和存储引擎层分析Limit执行过程 </section></li> </ol> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">1.老样子,建个表</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">还是这张表,表里我创建了近10W条数据</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">CREATE</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">TABLE</span>&nbsp;demo_info(<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">id</span>&nbsp;<span style="color: #c18401;line-height: 26px;">INT</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">NOT</span>&nbsp;<span style="color: #0184bb;line-height: 26px;">NULL</span>&nbsp;auto_increment,<br>&nbsp;&nbsp;&nbsp;&nbsp;key1&nbsp;<span style="color: #c18401;line-height: 26px;">VARCHAR</span>(<span style="color: #986801;line-height: 26px;">100</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;key2&nbsp;<span style="color: #c18401;line-height: 26px;">INT</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;key3&nbsp;<span style="color: #c18401;line-height: 26px;">VARCHAR</span>(<span style="color: #986801;line-height: 26px;">100</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;key_part1&nbsp;<span style="color: #c18401;line-height: 26px;">VARCHAR</span>(<span style="color: #986801;line-height: 26px;">100</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;key_part2&nbsp;<span style="color: #c18401;line-height: 26px;">VARCHAR</span>(<span style="color: #986801;line-height: 26px;">100</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;key_part3&nbsp;<span style="color: #c18401;line-height: 26px;">VARCHAR</span>(<span style="color: #986801;line-height: 26px;">100</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;common_field&nbsp;<span style="color: #c18401;line-height: 26px;">VARCHAR</span>(<span style="color: #986801;line-height: 26px;">100</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;PRIMARY&nbsp;<span style="color: #a626a4;line-height: 26px;">KEY</span>&nbsp;(<span style="color: #a626a4;line-height: 26px;">id</span>),<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">KEY</span>&nbsp;idx_key1&nbsp;(key1),<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">UNIQUE</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">KEY</span>&nbsp;uk_key2&nbsp;(key2),<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">KEY</span>&nbsp;&nbsp;idx_key3&nbsp;(key3),<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">KEY</span>&nbsp;idx_key_part(key_part1,&nbsp;key_part2,&nbsp;key_part3)<br>)<span style="color: #a626a4;line-height: 26px;">ENGINE</span>&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">INNODB</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">CHARSET</span>=utf8mb4;<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">id列是主键,key1列是二级索引列。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">2.从sql执行计划看Limit的影响</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">分析一下sql执行计划</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">explain</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;demo_info&nbsp;<span style="color: #a626a4;line-height: 26px;">order</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">by</span>&nbsp;key1&nbsp;<span style="color: #a626a4;line-height: 26px;">limit</span>&nbsp;<span style="color: #986801;line-height: 26px;">1</span>;<br></code></pre> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.09322709163346614" src="/upload/1bdc51662bcd4b8491359796b1ea526c.png" data-type="png" data-w="1255" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">在二级索引<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">idx_key1</code>中,key1列是有序的,查找按key1列排序的第1条记录,MySQL只需要从<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">idx_key1</code>中获取到第一条二级索引记录,然后直接回表取得完整的记录即可,这个很容易理解。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">如果我们把上边语句的<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit 1</code>换成<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit 10000, 1</code>,则却需要进行全表扫描,并进行filesort,执行计划如下:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">explain</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;demo_info&nbsp;<span style="color: #a626a4;line-height: 26px;">order</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">by</span>&nbsp;key1&nbsp;<span style="color: #a626a4;line-height: 26px;">limit</span>&nbsp;<span style="color: #986801;line-height: 26px;">10000</span>,&nbsp;<span style="color: #986801;line-height: 26px;">1</span>;<br></code></pre> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.10299234516353514" src="/upload/691d75289f851156e854a1422659d489.png" data-type="png" data-w="1437" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">有的同学就很不理解了:<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit 10000</code>, 1也可以使用二级索引<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">idx_key1</code>呀,我们可以先扫描到第10001条二级索引记录,对第10001条二级索引记录进行回表操作就好了啊。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">由于MySQL实现缺陷,不会出现上述的理想情况,它只会<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">全表扫描+filesort</code>,下边我们分析一下。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">3. 从server层和存储引擎层分析Limit执行过程</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">MySQL其实是分为server层和存储引擎层的:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层负责处理一些通用的事情,诸如连接管理、SQL语法解析、分析执行计划之类的东西</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">存储引擎层负责具体的数据存储,诸如数据是存储到文件上还是内存里,具体的存储格式是什么样的之类的。我们现在基本都使用InnoDB存储引擎,其他存储引擎使用的非常少了,所以我们也就不讨论其他存储引擎了。</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">MySQL中一条SQL语句的执行是通过server层和存储引擎层的多次交互才能得到最终结果的。先不用Limit子句举一个简单例子分析:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">SELECT</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">FROM</span>&nbsp;demo_info&nbsp;<span style="color: #a626a4;line-height: 26px;">WHERE</span>&nbsp;key1&nbsp;&gt;&nbsp;<span style="color: #50a14f;line-height: 26px;">'a'</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">AND</span>&nbsp;key1&nbsp;&lt;&nbsp;<span style="color: #50a14f;line-height: 26px;">'b'</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">AND</span>&nbsp;common_field&nbsp;!=&nbsp;<span style="color: #50a14f;line-height: 26px;">'a'</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层会分析到上述语句可以使用下边两种方案执行:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">方案一:使用全表扫描</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">方案二:使用二级索引idx_key1,此时需要扫描key1列值在('a', 'b')之间的全部二级索引记录,并且每条二级索引记录都需要进行回表操作。</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层会分析上述两个方案哪个成本更低,然后选取成本更低的那个方案作为执行计划。然后就调用存储引擎提供的接口来真正的执行查询了。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这里假设采用方案二,也就是使用二级索引idx_key1执行上述查询。那么server层和存储引擎层的执行过程如下:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层:“去查查<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">idx_key1</code>二级索引的('a', 'b')区间的第一条记录,然后把回表后把完整的记录返给我”</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">InnoDB层:InnoDB就通过<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">idx_key1</code>二级索引对应的B+树,快速定位到扫描区间('a','b')的第一条二级索引记录,然后进行回表,得到完整的聚集索引记录返回给server层。server层收到完整的聚集索引记录后,继续判断<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">common_field!='a'</code>条件是否成立,如果不成立则舍弃该记录,否则将该记录发送到客户端。然后对存储引擎说:“请把下一条记录给我”</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">注意:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(255, 177, 27);background: rgb(255, 245, 227);"> <p style="font-size: 16px;line-height: 26px;color: rgb(89, 89, 89);">此处将记录发送给客户端其实是发送到本地的网络缓冲区,缓冲区大小由net_buffer_length控制,默认是16KB大小。等缓冲区满了才真正发送网络包到客户端。</p> </blockquote> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">InnoDB层:InnoDB找到idx_key1的('a', 'b')区间的下一条二级索引记录,然后进行回表操作,将得到的完整的聚集索引记录返回给server层。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">注意:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(255, 177, 27);background: rgb(255, 245, 227);"> <p style="font-size: 16px;line-height: 26px;color: rgb(89, 89, 89);">不论是聚集索引记录还是二级索引记录,都包含一个称作next_record的属性,各个记录根据next_record连成了一个链表,并且链表中的记录是按照键值排序的(对于聚集索引来说,键值指的是主键的值,对于二级索引记录来说,键值指的是二级索引列的值)。</p> </blockquote> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层收到完整的聚集索引记录后,继续判断common_field!='a'条件是否成立,如果不成立则舍弃该记录,否则将该记录发送到客户端。然后对存储引擎说:“请把下一条记录给我哈”</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">… 然后就不停的重复上述过程。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">直到InnoDB发现根据二级索引记录的next_record获取到的下一条二级索引记录不在('a', 'b')区间中,就跟server层说:“('a', 'b')区间没有下一条记录了”</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层收到InnoDB说的没有下一条记录的消息,就结束查询。</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">现在大家就知道了server层和存储引擎层的基本交互过程了。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">那limit在哪里起作用呢?</strong></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">MySQL是在server层准备向客户端发送记录的时候才会去处理limit子句中的内容。举个例子:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;demo_info&nbsp;<span style="color: #a626a4;line-height: 26px;">order</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">by</span>&nbsp;key1&nbsp;<span style="color: #a626a4;line-height: 26px;">limit</span>&nbsp;<span style="color: #986801;line-height: 26px;">10000</span>,&nbsp;<span style="color: #986801;line-height: 26px;">1</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">如果使用idx_key1执行上述查询,那么MySQL会这样处理:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层向InnoDB要第1条记录,InnoDB从idx_key1中获取到第一条二级索引记录,然后进行回表操作得到完整的聚集索引记录,然后返回给server层。server层准备将其发送给客户端,此时发现还有个<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit 10000, 1</code>的要求,意味着符合条件的记录中的第10001条才可以真正发送给客户端,所以在这里先做个统计,我们假设server层维护了一个称作<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit_count</code>的变量用于统计已经跳过了多少条记录,此时就应该将<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit_count</code>设置为1。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层再向InnoDB要下一条记录,InnoDB再根据二级索引记录的<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">next_record</code>属性找到下一条二级索引记录,再次进行回表得到完整的聚集索引记录返回给server层。server层在将其发送给客户端的时候发现<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit_count</code>才是1,所以就放弃发送到客户端的操作,将<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit_count</code>加1,此时<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit_count</code>变为了2。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">… 重复上述操作</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">直到<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit_count</code>等于10000的时候,server层才会真正的将InnoDB返回的完整聚集索引记录发送给客户端。</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">从上述过程中我们可以看到,MySQL中是在实际向客户端发送记录前才会去判断limit子句是否符合要求,所以如果使用二级索引执行上述查询的话,意味着要进行10001次回表操作。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">server层在进行执行计划分析的时候会觉得执行这么多次回表的成本太大了,还不如直接<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">全表扫描+filesort</code>快呢,<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">全表扫描+filesort</code>就是把聚集索引中的记录都依次与给定的搜索条件进行比较,把符合搜索条件的记录再进行排序,MySQL认为这样操作的成本比多次回表成本低,所以就选择了后者执行查询。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">MySQL是根据成本来选择对应索引查询的,如果你不知道成本怎么计算,可以看:</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(255, 177, 27);background: rgb(255, 245, 227);"> <p style="font-size: 16px;line-height: 26px;color: rgb(89, 89, 89);">https://blog.csdn.net/qq_34115899/article/details/120217907</p> </blockquote> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">注意:</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">有一个点很容易混淆,走PRIMARY索引和全表扫描有什么区别呢?他们其实都是在聚集索引上操作的(聚集索引B+树的叶子结点是根据主键排好序的完整的用户记录,包含表里的所有字段),区别就在于</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">全表扫描将聚集索引B+树的叶子结点依次顺序扫描并判断条件,在以下几种情况会走全表扫描:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">select * from demo_info</code>这种无条件的查询语句 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">select * from demo_info where common_field != 'a'</code>这种条件字段common_field没有建索引的情况 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">select * from demo_info order by key1 limit 10000, 1</code>条件字段key1建了索引但是MySQL认为走二级索引的成本比全表扫描成本高的情况。 </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">PRIMARY</code>索引是利用二分思想将聚集索引B+树到指定范围区间进行扫描,比如<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">select * from demo_info where id in (1, 2)</code>这种条件字段是主键id,可以很好的利用<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">PRIMARY</code>索引进行二分的快速查询。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">怎么解决这个问题?</strong></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">由于MySQL实现limit子句的局限性,在处理诸如<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">limit 10000, 1</code>这样的语句时就无法通过使用二级索引来加快查询速度了么?其实也不是,只要把上述语句改写成:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;demo_info&nbsp;d,&nbsp;<br>(<span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">id</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;demo_info&nbsp;<span style="color: #a626a4;line-height: 26px;">order</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">by</span>&nbsp;key1&nbsp;<span style="color: #a626a4;line-height: 26px;">limit</span>&nbsp;<span style="color: #986801;line-height: 26px;">10000</span>,&nbsp;<span style="color: #986801;line-height: 26px;">1</span>)&nbsp;t&nbsp;<br><span style="color: #a626a4;line-height: 26px;">WHERE</span>&nbsp;d.id&nbsp;=&nbsp;t.id;<br><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">--&nbsp;或者这么写</span><br><span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;demo_info&nbsp;d<br><span style="color: #a626a4;line-height: 26px;">join</span>&nbsp;<br>(<span style="color: #a626a4;line-height: 26px;">select</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">id</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">from</span>&nbsp;demo_info&nbsp;<span style="color: #a626a4;line-height: 26px;">order</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">by</span>&nbsp;key1&nbsp;<span style="color: #a626a4;line-height: 26px;">limit</span>&nbsp;<span style="color: #986801;line-height: 26px;">10000</span>,&nbsp;<span style="color: #986801;line-height: 26px;">1</span>)&nbsp;t<br><span style="color: #a626a4;line-height: 26px;">on</span>&nbsp;d.id&nbsp;=&nbsp;t.id<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这样,作为一个子查询单独存在,由于该子查询的查询列表只有一个id列,MySQL可以通过仅扫描二级索引的叶子结点不用回表,然后再根据子查询中获得到的主键值去表中进行查找。这样就省去了前10000条记录的回表操作,从而大大提升了查询效率!</p> </section>

IDEA 值得推荐的十几款优秀插件,狂,拽,屌!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">最近,闲来无事,为了改变一下枯燥的编程环境,特地搜寻了下有助提升代码功力的插件,够装逼,够狂,拽,屌~ &nbsp; 绚丽的画面,多彩的跳动,让你区别其他程序猿。产品,测试,开发看到你的界面,眼睛都会发光~ &nbsp;算了,我实在是编不下去,自己去体验吧~</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(255, 177, 27);background: rgb(255, 245, 227);"> <p style="font-size: 16px;line-height: 26px;color: rgb(89, 89, 89);">PS: ☆ 半星 &nbsp;★ 一星 &nbsp; 主要是以狂拽屌指数来排名</p> </blockquote> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">12、Stackoverflow</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这个插件其实是最实用的插件,程序猿遇到的问题,基本都能找到回答,但是它使用的是google搜索引擎,所以你懂的。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">选中需要搜索的问题,然后,右键点击</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5448113207547169" src="/upload/e7643d04091a852e263e89ddc4912e91.png" data-type="png" data-w="424" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:☆</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">11、FindBugs</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">Idea自带的检查工具已经很强大,如有需要也可以加上Alibaba Java Coding Guidelines的代码检查工具,但是,说白这些工具其实更多的是规范性检查,如果需要更深入的去检查异常,可以使用此插件~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">右键点击文件,包或者工程,会出现如下界面</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5662337662337662" src="/upload/bc9a73bcc1c89932f2e2057f8f0e3a84.png" data-type="png" data-w="770" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:☆</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">10、TranslationPlugin</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">对于不经常使用英语的同学来说,对类,变量,方法想取一个合适的名字,此时发现自己的词汇早已还给老师 ,怎么办,这个插件能帮到你~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">直接选中你想要翻译的词,然后右键选择,或者快捷键 Ctrl+Shift+F3</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5448113207547169" src="/upload/e7643d04091a852e263e89ddc4912e91.png" data-type="png" data-w="424" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★☆</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">9、Mybatis-log-plugin</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">开发的项目一般都少不了日志系统,而我们在书写mysql语句的时候,参数的对应,往往有时候会忽略,mybatis自己控制的参数编译对应,个人感觉有点反人类,我们可以使用这个插件变成自己比较直观的对应~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">选中需要转换的mybatis log日志,然后点击右键,选择Restore sql from slection</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.43831417624521074" src="/upload/f3931e1005fc8cf109178f923afb30f2.png" data-type="png" data-w="1305" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.22389791183294663" src="/upload/971089002bd9a71cd105267fc5cfa010.png" data-type="png" data-w="862" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★☆</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★☆</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">8、GrepConsole</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">Idea console输出日志一大推,想要快速找到自己想要的类型日志,使用此插件可以快速定位到自己关注的类型日志,比如error,warn,自己也可以配置自己喜欢的颜色~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">从settings进入,点击 other settings,可以配置自己喜欢的颜色提示,比如我只选择了默认~</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.40350877192982454" src="/upload/6c9f27a7803e710ca316f02cd03be4c8.png" data-type="png" data-w="1425" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.11457577955039884" src="/upload/b5f4a84feb60b6f8034ade0c8d1af9e4.png" data-type="png" data-w="1379" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★☆</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">7、GsonFormat</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">在与组外或者不同部门对接接口时候发现,有时候对方返回的是JSON对象,自己想要用一个对象去接受,以便于处理后续,此时,需要自己一个个手动去输入属性么,肯定很抓狂,不过咱们可以使用这个插件来解决这个尴尬问题,当然也可以使用外部网址解决,比如bejson这个网站~</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.76775956284153" src="/upload/e1d99f0474301ff2183cad7f4a82547e.png" data-type="png" data-w="366" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★☆</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">6、IdeaJad</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">以前查看class文件形式的时候或者jar,都会使用一个外部反编译工具,这样操作明显不方便,使用此插件可以一直在idea中查看文件~ &nbsp;ps:其实Inteli Idea这个编译器已经自带了<a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247516042&amp;idx=2&amp;sn=6b3f8c6551641f00a21c277ca10b79ce&amp;chksm=ebd580a6dca209b06461b8ae57f95e9c9199c5cfb7847adfcc7bc3a320b720223bb39914c604&amp;scene=21#wechat_redirect" textvalue="反编译功能" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" style="text-decoration: underline;" data-linktype="2">反编译功能</a>,老夫~~~~~~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">选择class文件,右键 Decompile,完成反编译</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.8537735849056605" src="/upload/29c48390a7d352189efeb3d078272327.png" data-type="png" data-w="424" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">5、Free-idea-mybatis</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">mybatis xml和对应的<a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247513193&amp;idx=1&amp;sn=47588d7fbfdeea82626556e14cd50feb&amp;chksm=ebd58b45dca2025382d71c19042d105f506629417dcd9e9156a1cd76d8fd7287f5e6f4af5b34&amp;scene=21#wechat_redirect" textvalue="mapper" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" style="text-decoration: underline;" data-linktype="2">mapper</a>之间来回切换的时候,有时候不同人开发,放置的位置又不同,使用此插件后,来回切换的时候异常方便,和所放置的位置无关~</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="2.21256038647343" src="/upload/9d78119cda7d6f226606b11c4c342c11.png" data-type="png" data-w="207" style="margin-right: auto;margin-left: auto;width: 277px;border-radius: 5px;display: block;margin-bottom: 15px;height: 613px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★☆</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">4、CodeGlance</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">再也不用疯狂拖拽到底去找一遍啦,多不方便呀,使用此插件可以查看缩略图一样,快速切换到自己需要去的地方~</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.0288808664259927" src="/upload/b3b268b38b748098ab8bb3c55e3dad1.png" data-type="png" data-w="277" style="margin-right: auto;margin-left: auto;width: 234px;border-radius: 5px;display: block;margin-bottom: 15px;height: 241px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">3、NyanProgressBar</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">都说了,相亲见面第一印象很重要,如果你邀请设计,前端小姐姐老观看你的Idea,她肯定会觉得原来男孩子也会这么精致呀~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">形象陡然上升~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">就问你,这么绚丽多彩的颜色,哪个小姐姐不为你着迷~</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.15723270440251572" src="/upload/2094af69259eff923272c9729836524c.png" data-type="png" data-w="318" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★☆</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">2、BackgroundImagePlus</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">如果你对面坐着你的女神或者男神,但是又想打扰她/他,只想撩她,静静地看着她,在公司的时候也能这样,是不是工作起来特爽,效率高,简直美滋滋呀,不要急,特款神器在手~ 帮你实现</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">哇哇,我的男神~(我是男的)</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.9670442842430484" src="/upload/bf4680025ea78f9c8231dad87a81575d.png" data-type="png" data-w="971" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★★★☆</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">1、Activate-power-mode或者Intellij_power_mode_II</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">Boom, Boom ,Boom, Boom &nbsp;还有谁?!整个屏幕都在颤抖和炸裂,来来,跟随我的脚步,不如不如跳舞,免费蹦迪,玩的是心跳~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">像火一样的热情,小姐姐感受到了你的热情了吧,祝你好运~</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.29615384615384616" src="/upload/faa443c69d830f611e07ee0a36ecb749.png" data-type="png" data-w="520" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">为了更加凸显,它的狂,拽,屌(自己不会弄动图,只能去网上盗图啦)</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.21" src="/upload/2842015d3cf45d7bd6ded93176df9665.png" data-type="gif" data-w="600" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.4666666666666667" src="/upload/3938a37ad0b797856e0d826460a35943.png" data-type="gif" data-w="360" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">狂拽屌指数:★★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:☆</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(255, 177, 27);background: rgb(255, 245, 227);"> <p style="font-size: 16px;line-height: 26px;color: rgb(89, 89, 89);">补充系列,PS:以推荐指数为准</p> </blockquote> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">1、MyBatisCodeHelperPro</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">这个是一款比较实用的插件。但是,现在需要收费啦,貌似是需要花费29块钱,送两个激活码。不过,也可以申请7天的免费测试码,体验一下在购买也可以的。收费掩盖不了她的魅力所在,这也是行业发展的趋势。具体功能如下(总有一款适合你~):</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 提供Mapper接口与配置文件中对应SQL的导航. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 编辑XML文件时自动补全. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 根据Mapper接口, 使用快捷键生成xml文件及SQL标签. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> ResultMap中的property支持自动补全,支持级联(属性A.属性B.属性C). </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 快捷键生成@Param注解. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> XML中编辑SQL时, 括号自动补全. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> XML中编辑SQL时, 支持参数自动补全(基于@Param注解识别参数). </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 自动检查Mapper XML文件中ID冲突. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 自动检查Mapper XML文件中错误的属性值. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持Find Usage. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持重构从命名. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 支持别名. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 自动生成ResultMap属性. </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 快捷键: Option + Enter(Mac) | Alt + Enter(Windows). </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">安装成功最明显的标志就是~ &nbsp;有好多小鸟在飞~</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.4631268436578171" src="/upload/baa70b3b55ea71ab8d92c1d90e2f1b30.png" data-type="png" data-w="1017" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">2、VisualVM Launcher</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">一般可用于在本地开发进行压力测试,<a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247516335&amp;idx=2&amp;sn=e65a85170369004c9ae097f08754c9e3&amp;chksm=ebd5bf83dca23695134cfd8f1757e1fcf67b2832f3bd81cdf7dcdceb127f7df7bb4403d30b03&amp;scene=21#wechat_redirect" textvalue="性能测试" linktype="text" imgurl="" imgdata="null" data-itemshowtype="11" tab="innerlink" style="text-decoration: underline;" data-linktype="2">性能测试</a>之类的监控器,其他场景一般不推荐使用此模式启动,还会启动另外一个Visual vm窗口,这个窗口是JDK bin目录下的JvisualVM</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.35481023830538394" src="/upload/8a144f934a09cb4fcebe1406cbdbad0e.png" data-type="png" data-w="1133" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">安装成功并且启动后的画面如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.6505190311418685" src="/upload/f79245725c36498543c7141a902374c1.png" data-type="png" data-w="1156" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">3、Jrebel</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">是一款比较常见的热部署插件,一般用于Run模式下的自动编译。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">4、JUnitGenerator V2.0</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">有一个好的编写单元测试习惯的开发者,代码质量肯定是很好的,可以随时校验自己开发和改写接口的快速检查工具。也避免了测试提的bug多而影响个人绩效(有些公司把bug计入考核范围内)。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">5、Maven Helper</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">主要功能如下:查找和排除冲突依赖项的简便方法,为包含当前文件或根模块的模块运行/调试maven目标的操作,运行/调试当前测试文件的操作</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">6、RestfulToolkit</span></h2> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 根据 URL 直接跳转到对应的方法定义 ( Ctrl \ or Ctrl Alt N ); </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 提供了一个 Services tree 的显示窗口; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 一个简单的 http 请求工具; </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在请求方法上添加了有用功能: 复制生成 URL;,复制方法参数... </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 其他功能: java 类上添加 Convert to JSON 功能,格式化 json 数据 ( Windows: Ctrl + Enter; Mac: Command + Enter ) </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">7、Alibaba Java Coding Guidelines</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">一款阿里巴巴公司试行的开发设计规范~</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">8、GenerateAllSetter</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">当你进行对象之间赋值的时候,你会发现好麻烦呀,能不能有一个更好的办法呢~ 有,只要你选中需要生成set方法的对象,按下快捷键 alt+enter 界面如下:</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.2957393483709273" src="/upload/8835a7c845775ae3e17831d9d8c5aede.png" data-type="png" data-w="798" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">8、Lombok</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">出现的神奇就是在源码中没有getter和setter方法,</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.5485008818342152" src="/upload/298ee52d76983eeab411e4c729be91b8.png" data-type="png" data-w="567" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3180046765393609" src="/upload/8ed5b31544873246ab446effbfe0a188.png" data-type="png" data-w="1283" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">在使用之前需要添加一下依赖:</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span>org.projectlombok<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">groupId</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span>lombok<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">artifactId</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">version</span>&gt;</span>1.18.8<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">version</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">scope</span>&gt;</span>provided<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">scope</span>&gt;</span><br><span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">dependency</span>&gt;</span><br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">实用指数:★★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">推荐指数:★★★★★</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">各位小伙们,今天就介绍到这啦,后期如发现更加有趣的插件,会定期更新的~ &nbsp;推荐指数只是根据自己实际用的感受来排名,如果和你有出入,欢迎点评哦</p> </section>

MyBatis 二级缓存 关联刷新实现

作者:微信小助手

<p style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);" data-mpa-powered-by="yiban.io"><strong style="outline: 0px;letter-spacing: 0.544px;color: rgb(0, 0, 0);font-size: 16px;text-align: left;font-family: 微软雅黑;"><span style="outline: 0px;font-size: 15px;">点击关注公众号,实用技术文章</span></strong><span style="outline: 0px;letter-spacing: 0.544px;font-size: 16px;text-align: left;color: rgb(123, 12, 0);"><strong style="outline: 0px;font-family: 微软雅黑;"><span style="outline: 0px;font-size: 15px;">及时了解</span></strong></span><img class="rich_pages wxw-img" data-fileid="100031040" data-ratio="1" data-type="png" data-w="64" src="/upload/29316807de8eec165a101cfe6173a39c.png" style="outline: 0px;letter-spacing: 0.544px;color: rgb(0, 0, 0);font-size: 16px;text-align: left;font-family: 微软雅黑;box-sizing: border-box !important;max-height: 20px !important;visibility: visible !important;width: 20px !important;"><br style="outline: 0px;"></p> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzI4Njc5NjM1NQ==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/eQPyBffYbueAgIuCqZdZnW3NW44AOD32W2BOe28vCWLC2XdcNqJufjmlCCI2YVbFh0fjL6qCxEoNjHN9jTBItQ/0?wx_fmt=png" data-nickname="Java知音" data-alias="Java_friends" data-signature="专注于java。分享java基础、原理性知识、JavaWeb实战、spring全家桶、设计模式及面试资料、开源项目,助力开发者成长!" data-from="0"></mpprofile> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style=""> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">1、MyBatis缓存介绍</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">Mybatis提供对缓存的支持,但是在没有配置的默认情况下,它只开启一级缓存,二级缓存需要手动开启。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">一级缓存只是相对于同一个SqlSession而言。</strong> 也就是针对于同一事务,多次执行同一Mapper的相同查询方法,第一查询后,MyBatis会将查询结果放入缓存,在中间不涉及相应Mapper的数据更新(Insert,Update和Delete)操作的情况下,后续的查询将会从缓存中获取,而不会查询数据库。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">二级缓存是针对于应用级别的缓存,也就是针对不同的SqlSession做到缓存。</strong> 当开启二级缓存时,MyBatis会将首次查询结果存入对于Mapper的全局缓存,如果中间不执行该Mapper的数据更新操作,那么后续的相同查询都将会从缓存中获取。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">2、二级缓存问题</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">根据二级缓存的介绍发现,如果Mapper只是单表查询,并不会出现问题,但是如果Mapper涉及的查询出现 联表 查询,如 UserMapper 在查询 user 信息时需要关联查询 组织信息,也就是需要 user 表和 organization 表关联,OrganizationMapper 在执行更新时并不会更新 UserMapper 的缓存,结果会导致在使用相同条件 使用 UserMapper 查询 user 信息时,会等到未更新前的 organization 信息,造成数据不一致的情况。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">2.1、数据不一致问题验证</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">查询SQL</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">SELECT</span><br>&nbsp;u.*,&nbsp;o.name&nbsp;org_name&nbsp;<br><span style="color: #a626a4;line-height: 26px;">FROM</span><br>&nbsp;<span style="color: #a626a4;line-height: 26px;">user</span>&nbsp;u<br>&nbsp;<span style="color: #a626a4;line-height: 26px;">LEFT</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">JOIN</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">organization</span>&nbsp;o&nbsp;<span style="color: #a626a4;line-height: 26px;">ON</span>&nbsp;u.org_id&nbsp;=&nbsp;o.id&nbsp;<br><span style="color: #a626a4;line-height: 26px;">WHERE</span><br>&nbsp;u.id&nbsp;=&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">#{userId}</span><br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">UserMapper</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">UserInfo&nbsp;queryUserInfo(@Param(<span style="color: #50a14f;line-height: 26px;">"userId"</span>)&nbsp;String&nbsp;userId);<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">UserService</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;UserEntity&nbsp;<span style="color: #4078f2;line-height: 26px;">queryUser</span><span style="line-height: 26px;">(String&nbsp;userId)</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;UserInfo&nbsp;userInfo&nbsp;=&nbsp;userMapper.queryUserInfo(userId);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;userInfo;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">调用查询,得到查询结果(多次查询,得到缓存数据),这里 <code style="">userId = 1</code>,data为user查询结果</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">{<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"code"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"message"</span>:&nbsp;null,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"data"</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"id"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"username"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"password"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"orgName"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"组织1"</span><br>&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">查询 对应 organization 信息,结果</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">{<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"code"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"message"</span>:&nbsp;null,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"data"</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"id"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"name"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"组织1"</span><br>&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">发现和user缓存数据一致。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">执行更新 organization 操作,将 组织1 改为 组织2,再次查询组织信息</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">{<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"code"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"message"</span>:&nbsp;null,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"data"</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"id"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"name"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"组织2"</span><br>&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">再次查询user信息,发现依旧从缓存中获取</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">{<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"code"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"message"</span>:&nbsp;null,<br>&nbsp;<span style="color: #50a14f;line-height: 26px;">"data"</span>:&nbsp;{<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"id"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"username"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"password"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"orgName"</span>:&nbsp;<span style="color: #50a14f;line-height: 26px;">"组织1"</span><br>&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">造成此问题原因为 organization 数据信息更新只会自己Mapper对应的缓存数据,而不会通知到关联表organization 的一些Mapper更新对应的缓存数据。</p> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">2.2、问题处理思路</span><span style="display: none;"></span></h3> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在 Mapper1 定义时,手动配置 相应的关联 Mapper2 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在 Mapper1 缓存 cache1 实例化时,读取 所关联的 Mapper2 的缓存 cache2相关信息 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 在 cache1 中存储 cache2 的引用信息 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> cache1 执行clear时,同步操作 cache2 执行clear </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">3、关联缓存刷新实现</span></h2> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">打开二级缓存,本地项目使用 MyBatis Plus</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><code style="">mybatis-plus.configuration.cache-enabled=true</code></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">主要用到自定义注解CacheRelations,自定义缓存实现RelativeCache和缓存上下文RelativeCacheContext。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">注解CacheRelations,使用时需标注在对应mapper上</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #4078f2;line-height: 26px;">@Target</span>(ElementType.TYPE)<br><span style="color: #4078f2;line-height: 26px;">@Retention</span>(RetentionPolicy.RUNTIME)<br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">@interface</span>&nbsp;CacheRelations&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;from中mapper&nbsp;class对应的缓存更新时,需要更新当前注解标注mapper的缓存</span><br>&nbsp;&nbsp;&nbsp;&nbsp;Class&lt;?&gt;[]&nbsp;from()&nbsp;<span style="color: #a626a4;line-height: 26px;">default</span>&nbsp;{};<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;当前注解标注mapper的缓存更新时,需要更新to中mapper&nbsp;class对应的缓存</span><br>&nbsp;&nbsp;&nbsp;&nbsp;Class&lt;?&gt;[]&nbsp;to()&nbsp;<span style="color: #a626a4;line-height: 26px;">default</span>&nbsp;{};<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">自定义缓存RelativeCache实现 MyBatis Cache 接口</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">RelativeCache</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">implements</span>&nbsp;<span style="color: #c18401;line-height: 26px;">Cache</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Map&lt;Object,&nbsp;Object&gt;&nbsp;CACHE_MAP&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ConcurrentHashMap&lt;&gt;();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;List&lt;RelativeCache&gt;&nbsp;relations&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ArrayList&lt;&gt;();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;ReadWriteLock&nbsp;readWriteLock&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ReentrantReadWriteLock(<span style="color: #a626a4;line-height: 26px;">true</span>);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;String&nbsp;id;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Class&lt;?&gt;&nbsp;mapperClass;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">boolean</span>&nbsp;clearing;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">RelativeCache</span><span style="line-height: 26px;">(String&nbsp;id)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;Exception&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.id&nbsp;=&nbsp;id;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.mapperClass&nbsp;=&nbsp;Class.forName(id);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RelativeCacheContext.putCache(mapperClass,&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loadRelations();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;String&nbsp;<span style="color: #4078f2;line-height: 26px;">getId</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;id;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">putObject</span><span style="line-height: 26px;">(Object&nbsp;key,&nbsp;Object&nbsp;value)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CACHE_MAP.put(key,&nbsp;value);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;Object&nbsp;<span style="color: #4078f2;line-height: 26px;">getObject</span><span style="line-height: 26px;">(Object&nbsp;key)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;CACHE_MAP.get(key);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;Object&nbsp;<span style="color: #4078f2;line-height: 26px;">removeObject</span><span style="line-height: 26px;">(Object&nbsp;key)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;CACHE_MAP.remove(key);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">clear</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ReadWriteLock&nbsp;readWriteLock&nbsp;=&nbsp;getReadWriteLock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Lock&nbsp;lock&nbsp;=&nbsp;readWriteLock.writeLock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lock.lock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;判断&nbsp;当前缓存是否正在清空,如果正在清空,取消本次操作</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;避免缓存出现&nbsp;循环&nbsp;relation,造成递归无终止,调用栈溢出</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(clearing)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;clearing&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">true</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CACHE_MAP.clear();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;relations.forEach(RelativeCache::clear);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">finally</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;clearing&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">false</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">finally</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lock.unlock();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">int</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">getSize</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;CACHE_MAP.size();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;ReadWriteLock&nbsp;<span style="color: #4078f2;line-height: 26px;">getReadWriteLock</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;readWriteLock;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">addRelation</span><span style="line-height: 26px;">(RelativeCache&nbsp;relation)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(relations.contains(relation)){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;relations.add(relation);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">loadRelations</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;加载&nbsp;其他缓存更新时&nbsp;需要更新此缓存的&nbsp;caches</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;将&nbsp;此缓存&nbsp;加入至这些&nbsp;caches&nbsp;的&nbsp;relations&nbsp;中</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List&lt;RelativeCache&gt;&nbsp;to&nbsp;=&nbsp;UN_LOAD_TO_RELATIVE_CACHES_MAP.get(mapperClass);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(to&nbsp;!=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;to.forEach(relativeCache&nbsp;-&gt;&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.addRelation(relativeCache));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;加载&nbsp;此缓存更新时&nbsp;需要更新的一些缓存&nbsp;caches</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;将这些缓存&nbsp;caches&nbsp;加入&nbsp;至&nbsp;此缓存&nbsp;relations&nbsp;中</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List&lt;RelativeCache&gt;&nbsp;from&nbsp;=&nbsp;UN_LOAD_FROM_RELATIVE_CACHES_MAP.get(mapperClass);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(from&nbsp;!=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;from.forEach(relativeCache&nbsp;-&gt;&nbsp;relativeCache.addRelation(<span style="color: #a626a4;line-height: 26px;">this</span>));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CacheRelations&nbsp;annotation&nbsp;=&nbsp;AnnotationUtils.findAnnotation(mapperClass,&nbsp;CacheRelations<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(annotation&nbsp;==&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&lt;?&gt;[]&nbsp;toMappers&nbsp;=&nbsp;annotation.to();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Class&lt;?&gt;[]&nbsp;fromMappers&nbsp;=&nbsp;annotation.from();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(toMappers&nbsp;!=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>&nbsp;&amp;&amp;&nbsp;toMappers.length&nbsp;&gt;&nbsp;<span style="color: #986801;line-height: 26px;">0</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">for</span>&nbsp;(Class&nbsp;c&nbsp;:&nbsp;toMappers)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RelativeCache&nbsp;relativeCache&nbsp;=&nbsp;MAPPER_CACHE_MAP.get(c);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(relativeCache&nbsp;!=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;将找到的缓存添加到当前缓存的relations中</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.addRelation(relativeCache);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">else</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;如果找不到&nbsp;to&nbsp;cache,证明to&nbsp;cache还未加载,这时需将对应关系存放到&nbsp;UN_LOAD_FROM_RELATIVE_CACHES_MAP</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;也就是说&nbsp;c&nbsp;对应的&nbsp;cache&nbsp;需要&nbsp;在&nbsp;当前缓存更新时&nbsp;进行更新</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List&lt;RelativeCache&gt;&nbsp;relativeCaches&nbsp;=&nbsp;UN_LOAD_FROM_RELATIVE_CACHES_MAP.putIfAbsent(c,&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ArrayList&lt;RelativeCache&gt;());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;relativeCaches.add(<span style="color: #a626a4;line-height: 26px;">this</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(fromMappers&nbsp;!=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>&nbsp;&amp;&amp;&nbsp;fromMappers.length&nbsp;&gt;&nbsp;<span style="color: #986801;line-height: 26px;">0</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">for</span>&nbsp;(Class&nbsp;c&nbsp;:&nbsp;fromMappers)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RelativeCache&nbsp;relativeCache&nbsp;=&nbsp;MAPPER_CACHE_MAP.get(c);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(relativeCache&nbsp;!=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;将找到的缓存添加到当前缓存的relations中</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;relativeCache.addRelation(<span style="color: #a626a4;line-height: 26px;">this</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">else</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;如果找不到&nbsp;from&nbsp;cache,证明from&nbsp;cache还未加载,这时需将对应关系存放到&nbsp;UN_LOAD_TO_RELATIVE_CACHES_MAP</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;也就是说&nbsp;c&nbsp;对应的&nbsp;cache&nbsp;更新时需要更新当前缓存</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List&lt;RelativeCache&gt;&nbsp;relativeCaches&nbsp;=&nbsp;UN_LOAD_TO_RELATIVE_CACHES_MAP.putIfAbsent(c,&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ArrayList&lt;RelativeCache&gt;());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;relativeCaches.add(<span style="color: #a626a4;line-height: 26px;">this</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">缓存上下文RelativeCacheContext</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">RelativeCacheContext</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;存储全量缓存的映射关系</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">final</span>&nbsp;Map&lt;Class&lt;?&gt;,&nbsp;RelativeCache&gt;&nbsp;MAPPER_CACHE_MAP&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ConcurrentHashMap&lt;&gt;();<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;存储&nbsp;Mapper&nbsp;对应缓存&nbsp;需要to更新缓存,但是此时&nbsp;Mapper&nbsp;对应缓存还未加载</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;也就是&nbsp;Class&lt;?&gt;&nbsp;对应的缓存更新时,需要更新&nbsp;List&lt;RelativeCache&gt;&nbsp;中的缓存</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">final</span>&nbsp;Map&lt;Class&lt;?&gt;,&nbsp;List&lt;RelativeCache&gt;&gt;&nbsp;UN_LOAD_TO_RELATIVE_CACHES_MAP&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ConcurrentHashMap&lt;&gt;();<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;存储&nbsp;Mapper&nbsp;对应缓存&nbsp;需要from更新缓存,但是在&nbsp;加载&nbsp;Mapper&nbsp;缓存时,这些缓存还未加载</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;也就是&nbsp;List&lt;RelativeCache&gt;&nbsp;中的缓存更新时,需要更新&nbsp;Class&lt;?&gt;&nbsp;对应的缓存</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">final</span>&nbsp;Map&lt;Class&lt;?&gt;,&nbsp;List&lt;RelativeCache&gt;&gt;&nbsp;UN_LOAD_FROM_RELATIVE_CACHES_MAP&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ConcurrentHashMap&lt;&gt;();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">putCache</span><span style="line-height: 26px;">(Class&lt;?&gt;&nbsp;clazz,&nbsp;RelativeCache&nbsp;cache)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MAPPER_CACHE_MAP.put(clazz,&nbsp;cache);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">getCache</span><span style="line-height: 26px;">(Class&lt;?&gt;&nbsp;clazz)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MAPPER_CACHE_MAP.get(clazz);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">使用方式</strong></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">UserMapper.java</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #4078f2;line-height: 26px;">@Repository</span><br><span style="color: #4078f2;line-height: 26px;">@CacheNamespace</span>(implementation&nbsp;=&nbsp;RelativeCache<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>,&nbsp;<span style="color: #c18401;line-height: 26px;">eviction</span>&nbsp;</span>=&nbsp;RelativeCache<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>,&nbsp;<span style="color: #c18401;line-height: 26px;">flushInterval</span>&nbsp;</span>=&nbsp;<span style="color: #986801;line-height: 26px;">30</span>&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">60</span>&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">1000</span>)<br><span style="color: #4078f2;line-height: 26px;">@CacheRelations</span>(from&nbsp;=&nbsp;OrganizationMapper<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)<br><span style="color: #c18401;line-height: 26px;">public</span>&nbsp;<span style="color: #c18401;line-height: 26px;">interface</span>&nbsp;<span style="color: #c18401;line-height: 26px;">UserMapper</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">extends</span>&nbsp;<span style="color: #c18401;line-height: 26px;">BaseMapper</span>&lt;<span style="color: #c18401;line-height: 26px;">UserEntity</span>&gt;&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">UserInfo&nbsp;<span style="color: #4078f2;line-height: 26px;">queryUserInfo</span><span style="line-height: 26px;">(@Param(<span style="color: #50a14f;line-height: 26px;">"userId"</span>)</span>&nbsp;String&nbsp;userId)</span>;<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">queryUserInfo是xml实现的接口,所以需要在对应xml中配置<code style="">&lt;cache-ref namespace=“com.mars.system.dao.UserMapper”/&gt;</code>,不然查询结果不会被缓存化。如果接口为 BaseMapper实现,查询结果会自动缓存化。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">UserMapper.xml</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">mapper</span>&nbsp;<span style="color: #986801;line-height: 26px;">namespace</span>=<span style="color: #50a14f;line-height: 26px;">"com.mars.system.dao.UserMapper"</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">cache-ref</span>&nbsp;<span style="color: #986801;line-height: 26px;">namespace</span>=<span style="color: #50a14f;line-height: 26px;">"com.mars.system.dao.UserMapper"</span>/&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;<span style="color: #e45649;line-height: 26px;">select</span>&nbsp;<span style="color: #986801;line-height: 26px;">id</span>=<span style="color: #50a14f;line-height: 26px;">"queryUserInfo"</span>&nbsp;<span style="color: #986801;line-height: 26px;">resultType</span>=<span style="color: #50a14f;line-height: 26px;">"com.mars.system.model.UserInfo"</span>&gt;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;select&nbsp;u.*,&nbsp;o.name&nbsp;org_name&nbsp;from&nbsp;user&nbsp;u&nbsp;left&nbsp;join&nbsp;organization&nbsp;o&nbsp;on&nbsp;u.org_id&nbsp;=&nbsp;o.id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;where&nbsp;u.id&nbsp;=&nbsp;#{userId}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">select</span>&gt;</span><br><span style="line-height: 26px;">&lt;/<span style="color: #e45649;line-height: 26px;">mapper</span>&gt;</span><br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">OrganizationMapper.java</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #4078f2;line-height: 26px;">@Repository</span><br><span style="color: #4078f2;line-height: 26px;">@CacheNamespace</span>(implementation&nbsp;=&nbsp;RelativeCache<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>,&nbsp;<span style="color: #c18401;line-height: 26px;">eviction</span>&nbsp;</span>=&nbsp;RelativeCache<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>,&nbsp;<span style="color: #c18401;line-height: 26px;">flushInterval</span>&nbsp;</span>=&nbsp;<span style="color: #986801;line-height: 26px;">30</span>&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">60</span>&nbsp;*&nbsp;<span style="color: #986801;line-height: 26px;">1000</span>)<br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">interface</span>&nbsp;<span style="color: #c18401;line-height: 26px;">OrganizationMapper</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">extends</span>&nbsp;<span style="color: #c18401;line-height: 26px;">BaseMapper</span>&lt;<span style="color: #c18401;line-height: 26px;">OrganizationEntity</span>&gt;&nbsp;</span>{<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">CacheNamespace中flushInterval 在默认情况下是无效的,也就是说缓存并不会定时清理。ScheduledCache是对flushInterval 功能的实现,MyBatis 的缓存体系是用装饰器进行功能扩展的,所以,如果需要定时刷新,需要使用ScheduledCache给到 RelativeCache添加装饰。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">至此,配置和编码完成。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">开始验证:</strong></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">查询 userId=1的用户信息</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"code"</span>:<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"message"</span>:null,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"data"</span>:{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"id"</span>:<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"username"</span>:<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"password"</span>:<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"orgName"</span>:<span style="color: #50a14f;line-height: 26px;">"组织1"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">更新组织信息,将 组织1 改为 组织2</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"code"</span>:<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"message"</span>:null,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"data"</span>:{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"id"</span>:<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"name"</span>:<span style="color: #50a14f;line-height: 26px;">"组织2"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">再次查询用户信息</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"code"</span>:<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"message"</span>:null,<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"data"</span>:{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"id"</span>:<span style="color: #50a14f;line-height: 26px;">"1"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"username"</span>:<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"password"</span>:<span style="color: #50a14f;line-height: 26px;">"admin"</span>,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #50a14f;line-height: 26px;">"orgName"</span>:<span style="color: #50a14f;line-height: 26px;">"组织2"</span><br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">符合预期。</p> </section> <h3 data-tool="mdnice编辑器"><em style="outline: 0px;color: rgb(136, 136, 136);font-size: 12px;letter-spacing: 0.5px;">来源:blog.csdn.net/qq_38245668/article/</em></h3> <h3 data-tool="mdnice编辑器"><em style="outline: 0px;color: rgb(136, 136, 136);font-size: 12px;letter-spacing: 0.5px;">details/105803298</em></h3> <p style="outline: 0px;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);color: rgb(0, 0, 0);font-family: PingFangSC-Light;font-size: 16px;text-align: left;"><br style="outline: 0px;"></p> <section style="outline: 0px;letter-spacing: 0.544px;white-space: normal;color: rgb(0, 0, 0);font-size: 16px;background-color: rgb(255, 255, 255);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, Arial, sans-serif;widows: 1;text-align: center;"> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;font-size: 16px;background-color: rgb(255, 255, 255);text-align: center;line-height: 1.6;color: rgb(63, 63, 63);"><span style="outline: 0px;color: rgb(2, 30, 170);"><strong style="outline: 0px;color: rgb(217, 33, 66);font-size: 15px;letter-spacing: 0.2em;word-spacing: 0.1em;font-family: PingFangSC-Light;">推荐</strong></span></p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;font-size: 16px;background-color: rgb(255, 255, 255);text-align: center;line-height: 1.6;color: rgb(63, 63, 63);"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzU2MTI4MjI0MQ==&amp;mid=2247506972&amp;idx=2&amp;sn=790959d9ee2b74e0d0c8a0a5395bc665&amp;chksm=fc79b1b2cb0e38a498933a034926df45b51524b044762583488d498ae2112c06cf35d412fb3b&amp;scene=21#wechat_redirect" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" wah-hotarea="click" style="outline: 0px;color: rgb(2, 30, 170);text-decoration: underline;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;font-size: 15px;letter-spacing: 0.2em;word-spacing: 0.1em;font-family: PingFangSC-Light;"><span style="outline: 0px;">主流Java进阶技术(学习资料分享)</span></a></p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;font-size: 16px;background-color: rgb(255, 255, 255);text-align: center;line-height: 1.6;color: rgb(63, 63, 63);"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzIyNDU2ODA4OQ==&amp;mid=2247489003&amp;idx=1&amp;sn=69bf19d900079e204e36df58525654bf&amp;chksm=e80da39ddf7a2a8bf0765f9b95f359a3944fc40c4a192bb3fe9adedfbcd0070cd27234bcf6b3&amp;scene=21#wechat_redirect" textvalue="Java面试题宝典" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" wah-hotarea="click" hasload="1" style="outline: 0px;color: rgb(2, 30, 170);text-decoration: underline;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;"><span style="outline: 0px;">Java面试题宝典</span></a></p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;font-size: 16px;background-color: rgb(255, 255, 255);text-align: center;line-height: 1.6;color: rgb(63, 63, 63);"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&amp;mid=2247515252&amp;idx=2&amp;sn=48dbf5183636474b94436e6dcdd9496c&amp;chksm=ebd58358dca20a4e6b2e39dd504adc12a5fad49985b57b77919fe66d54a4a8ebdd7fad717334&amp;scene=21#wechat_redirect" textvalue="加入Spring技术开发社区" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2" wah-hotarea="click" hasload="1" style="outline: 0px;color: rgb(2, 30, 170);text-decoration: underline;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;"><span style="outline: 0px;">加入Spring技术开发社区</span></a><br style="outline: 0px;"></p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);line-height: 1.6;color: rgb(63, 63, 63);"><img class="rich_pages wxw-img" data-backh="205" data-backw="562" data-fileid="100031039" data-ratio="0.3648148148148148" src="/upload/7b4bfc3268e1a42781eadc207342c9df.jpg" data-type="jpeg" data-w="1080" style="outline: 0px;color: rgb(0, 0, 0);letter-spacing: 0.544px;box-sizing: border-box !important;visibility: visible !important;width: 661px !important;"></p> <p data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;padding-top: 8px;padding-bottom: 8px;outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;font-size: 16px;text-align: left;background-color: rgb(255, 255, 255);line-height: 1.6;color: rgb(63, 63, 63);"><span style="outline: 0px;font-size: 15px;letter-spacing: 0.544px;color: rgba(0, 0, 0, 0.8);font-family: Optima-Regular, PingFangTC-light;widows: 1;">PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下</span><strong style="outline: 0px;letter-spacing: 0.544px;color: rgba(0, 0, 0, 0.8);font-family: Optima-Regular, PingFangTC-light;font-size: 12px;widows: 1;">“在看”</strong><span style="outline: 0px;font-size: 15px;letter-spacing: 0.544px;color: rgba(0, 0, 0, 0.8);font-family: Optima-Regular, PingFangTC-light;widows: 1;">,加个</span><strong style="outline: 0px;letter-spacing: 0.544px;color: rgba(0, 0, 0, 0.8);font-family: Optima-Regular, PingFangTC-light;font-size: 12px;widows: 1;">“星标”</strong><span style="outline: 0px;font-size: 15px;letter-spacing: 0.544px;color: rgba(0, 0, 0, 0.8);font-family: Optima-Regular, PingFangTC-light;widows: 1;">,这样每次新文章推送才会第一时间出现在你的订阅列表里。</span><span style="outline: 0px;font-size: 15px;letter-spacing: 0.544px;font-family: Optima-Regular, PingFangTC-light;widows: 1;visibility: visible;color: rgb(255, 0, 0);">点<strong style="outline: 0px;">“在看”</strong>支持我们吧!</span></p> </section>

删库不跑路,我含泪写下MySQL数据恢复大法

作者:微信小助手

<p style="margin-bottom: 15px;outline: 0px;max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">日常工作中,总会有因手抖、写错条件、写错表名、错连生产库造成的误删库表和数据的事情发生,那么,如果连数据都恢复不了,还要什么 DBA。</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;"></span></p> <p><br></p> <article> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <p data-mid=""><span style="color: rgb(123, 12, 0);"><strong>前 言</strong></span></p> </section> </section> </section> </section> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <br> </section> </section> </section> </section> </section> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">数据恢复的前提的做好备份,且开启</span><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;binlog</span></strong><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">, 格式为</span><span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;row</strong></span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">。</span></p> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">如果没有备份文件,那么删掉库表后就真的删掉了,</span><span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">lsof</strong></span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;中还有记录的话,有可能恢复一部分文件,但若刚好数据库没有打开这个表文件,那就只能跑路了。</span></p> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">如果没有开启&nbsp;</span><span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">binlog</strong></span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">,那么恢复数据后,从备份时间点开始的数据都没得了。</span></p> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">如果&nbsp;</span><span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">binlog&nbsp;</strong></span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">格式不为&nbsp;</span><span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">row</strong></span><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">,那么在误操作数据后就没有办法做闪回操作,只能老老实实地走备份恢复流程。</span></p> <section data-mpa-template="t" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <section data-mid="" mpa-from-tpl="t"> <p data-mid=""><span style="color: rgb(123, 12, 0);"><strong>直接恢复</strong></span></p> </section> </section> </section> </section> </section> <p><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">直接恢复是使用备份文件做全量恢复,这是最常见的场景。</span></p> <h2 style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(123, 12, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(123, 12, 0);outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">| mysqldump备份全量恢复</span></strong></span></h2> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">使用 mysqldump 文件恢复数据非常简单,直接解压了执行</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="xml"><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">gzip -d backup.sql.gz | mysql -u<span class="code-snippet__tag" style="outline: 0px;max-width: 1000%;">&lt;<span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">user</span>&gt;</span> -h<span class="code-snippet__tag" style="outline: 0px;max-width: 1000%;">&lt;<span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">host</span>&gt;</span> -P<span class="code-snippet__tag" style="outline: 0px;max-width: 1000%;">&lt;<span class="code-snippet__name" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">port</span>&gt;</span> -p</span></code></pre> </section> <section style="margin-top: 15px;margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;color: rgb(123, 12, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(123, 12, 0);outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">| xtrabackup备份全量恢复</span></strong></span> </section> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">恢复过程</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="properties"><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"># 步骤一:解压(如果没有压缩可以忽略这一步)</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet__attr" style="outline: 0px;max-width: 1000%;">innobackupex</span> <span class="code-snippet__string" style="outline: 0px;max-width: 1000%;">--decompress &lt;备份文件所在目录&gt;</span></span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer"><br></span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"># 步骤二:应用日志</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet__attr" style="outline: 0px;max-width: 1000%;">innobackupex</span> <span class="code-snippet__string" style="outline: 0px;max-width: 1000%;">--apply-log &lt;备份文件所在目录&gt; </span></span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer"><br></span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"># 步骤三:复制备份文件到数据目录</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet__attr" style="outline: 0px;max-width: 1000%;">innobackupex</span> <span class="code-snippet__string" style="outline: 0px;max-width: 1000%;">--datadir=&lt;MySQL数据目录&gt; --copy-back &lt;备份文件所在目录&gt;</span></span></code></pre> </section> <pre style="margin-top: 15px;margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;color: rgb(123, 12, 0);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="color: rgb(123, 12, 0);outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">| 基于时间点恢复</span></strong></span><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></pre> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">基于时间点的恢复依赖的是binlog日志,需要从&nbsp;binlog&nbsp;中找过从备份点到恢复点的所有日志,然后应用,我们测试一下</span></p> <p style="margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">新建测试表</span></p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="markdown"><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">chengqm-3306&gt;&gt;show create table mytest.mytest \G;</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">** 1. row **</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span><span class="code-snippet__strong" style="outline: 0px;max-width: 1000%;">*****</span></span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> Table: mytest</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">Create Table: CREATE TABLE <span class="code-snippet__code" style="outline: 0px;max-width: 1000%;">`mytest`</span> (</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet__code" style="outline: 0px;max-width: 1000%;">`id`</span> int(11) NOT NULL AUTO_INCREMENT,</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span class="code-snippet__code" style="outline: 0px;max-width: 1000%;">`ctime`</span> datetime DEFAULT NULL,</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> PRIMARY KEY (<span class="code-snippet__code" style="outline: 0px;max-width: 1000%;">`id`</span>)</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">) ENGINE=InnoDB DEFAULT CHARSET=utf8</span></code></pre> </section> <section style="margin-top: 15px;margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">每秒插入一条数据</span> </section> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="ruby"><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">[mysql@mysql-test ~]$ <span class="code-snippet__keyword" style="outline: 0px;max-width: 1000%;">while</span> <span class="code-snippet__literal" style="outline: 0px;max-width: 1000%;">true</span>; <span class="code-snippet__keyword" style="outline: 0px;max-width: 1000%;">do</span> mysql -S /tmp/mysql.sock -e <span class="code-snippet__string" style="outline: 0px;max-width: 1000%;">'insert into mytest.mytest(ctime)values(now())'</span>;date;sleep <span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">1</span>;done</span></code></pre> </section> <section style="margin-top: 15px;margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">备份</span> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> </section> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="kotlin"><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">[<span class="code-snippet__symbol" style="outline: 0px;max-width: 1000%;">mysql@</span>mysql-test ~]$ mysqldump --opt --single-transaction --master-<span class="code-snippet__keyword" style="outline: 0px;max-width: 1000%;">data</span>=<span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">2</span> --<span class="code-snippet__keyword" style="outline: 0px;max-width: 1000%;">default</span>-character-<span class="code-snippet__keyword" style="outline: 0px;max-width: 1000%;">set</span>=utf8 -S /tmp/mysql.sock -A &gt; backup.sql</span></code></pre> </section> <section style="margin-top: 15px;margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">找出备份时的日志位置</span> </section> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="perl"><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">[mysql@mysql-test ~]$ head -n <span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">25</span> backup.sql | <span class="code-snippet__keyword" style="outline: 0px;max-width: 1000%;">grep</span> <span class="code-snippet__string" style="outline: 0px;max-width: 1000%;">'CHANGE MASTER TO MASTER_LOG_FILE'</span></span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">-- CHANGE MASTER TO MASTER_LOG_FILE=<span class="code-snippet__string" style="outline: 0px;max-width: 1000%;">'mysql-bin.000032'</span>, MASTER_LOG_POS=<span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">39654</span>;</span></code></pre> </section> <section style="margin-top: 15px;margin-bottom: 15px;outline: 0px;max-width: 100%;line-height: 1.75em;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">假设要恢复到</span> <span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;2019-08-09 11:01:54&nbsp;</strong></span> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">这个时间点,我们从 binlog 中查找从&nbsp;</span> <span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">39654&nbsp;</strong></span> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">到</span> <span style="outline: 0px;max-width: 100%;font-size: 15px;color: rgb(217, 33, 66);box-sizing: border-box !important;overflow-wrap: break-word !important;"><strong style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">&nbsp;019-08-09 11:01:54&nbsp;</strong></span> <span style="outline: 0px;max-width: 100%;font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;">的日志</span> <br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> </section> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="ruby"><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">[mysql@mysql-test ~]$ mysqlbinlog --start-position=<span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">39654</span> --stop-datetime=<span class="code-snippet__string" style="outline: 0px;max-width: 1000%;">'2019-08-09 11:01:54'</span> /data/mysql_log/mysql_test/mysql-bin.<span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">000032</span> &gt; backup_inc.sql</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-align: left;display: flex;font-family: Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span class="code-snippet_outer" style="outline: 0px;max-width: 1000%;box-sizing: border-box !important;overflow-wrap: break-word !important;">[mysql@mysql-test-<span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">83</span> ~]$ tail -n <span class="code-snippet__number" style="outline: 0px;max-width: 1000%;">20</span> backup_inc.sql</span></code><code style="white-space:pre-wrap;outline: 0px;max-width: 1000%;text-a

这十个事件,让“永不宕机”变成了一个笑话

作者:微信小助手

<section style="text-align: center;margin-bottom: 20px;"> <img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.6640625" data-s="300,640" src="/upload/17a3969be46813c84b37c4672a755a37.jpg" data-type="jpeg" data-w="1280" style=""> </section> <section style="margin-top: 13px;padding-left: 14px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;font-size: 14px;background-color: rgb(255, 255, 255);color: rgb(145, 145, 145);text-align: left;line-height: 1em;overflow-wrap: break-word !important;"> 整理 | Tina </section> <section style="margin-top: 40px;margin-right: 8px;margin-left: 8px;padding: 19px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;font-size: 15px;line-height: 27px;color: rgb(89, 89, 89);background-color: rgb(239, 239, 239);overflow-wrap: break-word !important;"> 这一年,那些“崩溃”过的互联网企业。 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">互联网技术发展到了 2022 年,理论上来说是可以做到“永不宕机”的。但过去的 2021 年,宕机事故看起来一点也没有减少。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">随着“国民级应用”增多,大家对技术的依赖程度越来越高,面临的风险比以往任何时候都多。宕机影响的不仅是内部用户,连带还会影响到客户和合作伙伴的收入、信誉和生产力等各个方面。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">宕机事故不可预测,因此它也被称为系统中的“黑天鹅”。当前大型互联网系统架构日趋复杂,稳定性风险也在升高,系统中一定会有一些黑天鹅潜伏着,只是还没被发现。然而墨菲定律告诉我们“该出错的终究会出错”。我们整理了 2021 年发生的十个重大宕机事件,并总结了故障原因。这些故障大部分是人为造成的,并且依然是我们在系统建设中需要特别注意的地方。</p> <section style="margin-top: 40px;margin-right: 8px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzlYGr4HPPFSZv2hALJeicvs8Ms4X2MgRbibicSr355WOCVKjLWcXvqicZ4Uw/640?wx_fmt=jpeg&quot;) left 28px / 100% 6px no-repeat rgb(255, 255, 255);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;font-size: 20px;color: rgb(0, 179, 139);text-align: left;line-height: 35px;overflow-wrap: break-word !important;"> <span style="margin-bottom: 10px;padding-left: 22px;outline: 0px;max-width: 100%;display: block;font-size: 32px;box-sizing: border-box !important;overflow-wrap: break-word !important;">1</span>国内宕机事件:交待清楚故障原因也是一种能力 </section> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;B 站崩溃,让年轻人无心睡觉 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">7 月 13 日晚间,视频网站哔哩哔哩(B 站)出现服务器宕机事故,无法登陆的用户涌向其它站点,连锁导致了一系列宕机事故。“B 站崩了”、“豆瓣崩了”、“A 站也崩了”、“晋江崩了”等接连冲上了热搜。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">据数据显示,当时 B 站月活用户为 2.23 亿,其中 35 岁及以下的用户比重超过 86%。显然这些年轻人非常能熬夜,虽然宕机发生在深夜,但是大家吵吵闹闹地分析原因甚至还惊动了消防局。有网友认为“B 站崩了是因为有火情发生”,上海消防回复说:“经了解,位于上海市政立路 485 号国正中心内的哔哩哔哩弹幕网 B 站(总部)未出现火情,未接到相关报警。具体情况以站方公布为准。”</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">半夜 2 点之后,B 站终于发了一个非常简短的说明:“部分服务器机房发生故障,造成无法访问。”</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">只是 B 站这个解释,像是什么都说了,又像是什么都没说。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.24336973478939158" data-s="300,640" src="/upload/dbc5b2dff7c9f21729f2798f65433ae6.png" data-type="png" data-w="641" style="outline: 0px;text-align: center;color: rgb(51, 51, 51);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 17px;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 641px !important;visibility: visible !important;"></p> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;富途证券服务中断,创始人发 2000 字硬核长文解释技术故障 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">10 月 9 日凌晨,互联网券商富途证券 App 出现故障,用户无法登录进行交易。到了下午,富途证券发布了相关说明并致歉。富途证券表示,事故原因为“运营商机房电力闪断导致的多机房网络故障”,公司已于第一时间联系运营商进行修复,并在 2 小时内陆续恢复核心服务。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">这次宕机本来并未引起证券行业之外的关注,但是随后富途创始人李华(叶子哥)的文章却让这次宕机事件火出了圈。11 日中午,技术出身的李华发布了一篇 2000 字长文,向用户致歉,文章里更多的篇幅却是从技术角度解释为什么会“宕机”。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">虽然和 B 站一样是因为服务器机房故障,李华却从容灾设计的各个环节给了大家详细的说明。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-galleryid="" data-ratio="0.4294770206022187" data-s="300,640" src="/upload/8dc3e524dfcf04a3d0568779c88bebbe.png" data-type="png" data-w="631" style="outline: 0px;text-align: center;color: rgb(51, 51, 51);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 17px;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 631px !important;visibility: visible !important;"></p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">李华表示,富途的证券系统中从行情到交易、从服务器到交易网关到网络传输都有做双路或多路的冗余设计。不同的子系统设计会有所不同。以行情为例,单向传输为主、对时延的敏感度也不是那么高,富途很早就作了多区域多 IDC 的容灾设计;尤其像美股行情,涉及到越洋传输,为避免中断,富途选择了全球顶级的两家行情供应商分别提供行情源,分别从美国、香港多地多点接入,当这些都不可用时,富途还保留了富途美国 IDC 直传的能力。不考虑其他的冗余设计,光是因为行情源的冗余,富途一年增加的成本过千万港元。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">李华指出,在实时热备的多路冗余交易系统的设计上会面临着两种选择。一是较差的交易性能更大的订单延时但更好容灾能力的跨 IDC 多路冗余方案,二是更好的交易性能较小的订单提交延时单一 IDC 的多路冗余方案,但 IDC 本身会成为故障的单点。这也间接导致了一定要做出选择。在李华看来,考虑到 IDC 的建设标准,IDC 的大级别事故是罕见的,尤其是在电力故障方面。经过综合推演之后,富途选择了更好性能的方案二,也因此留下了 IDC 的单点故障隐患。这次事故恰恰就是 IDC 出了问题,而且是最不应该出现问题的电力系统出了问题,不间断电源和柴油发电机都没能发挥应有的作用。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">李华的硬核文章也得到了很多富途证券用户的支持和鼓励。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.14859437751004015" data-s="300,640" src="/upload/b9eb2f3f91f0886c153829689b81748b.png" data-type="png" data-w="747" style="outline: 0px;text-align: center;color: rgb(51, 51, 51);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 17px;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"><br style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;西安“一码通”半个月崩溃两次 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">2021 年 12 月 20 日,西安“一码通”因访问量过大导致系统崩溃。当时西安市大数据资源管理局称,“一码通”注册用户已达 4695.2 万人,日均扫码量超 800 万人次。由于在各公共场所加大了扫码查验,同时开展多轮全员核酸检测,“一码通”每秒访问量达到以往峰值的 10 倍以上,并建议市民非必要不展码、亮码。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">2022 年 1 月 4 日上午 9 时,西安“一码通”第二次崩溃。西安市开启新一轮核酸筛查,许多西安网友反应,“西安一码通”系统再次崩溃,无法显示疫情防控码。话题 # 西安一码通 # 一度冲上微博热搜第一。西安市相关部门公开回应称,因访问量太大,全市“一码通”均出现无法正常显示的问题。当天下午西安“一码通”已经逐步恢复正常使用。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">据了解,西安“一码通”是 2020 年 2 月西安市针对疫情防控牵头开发的大数据平台,业主单位是西安市大数据资源管理局。据工信部官网 1 月 4 日的报道,12 月 30 日 -31 日,工信部曾对陕西省通信管理局展开疫情防控工作调研,并要求西安“一码通”加强技术改进和网络扩容,确保不拥塞宕机。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">碰巧的是,2022 年 1 月 10 日上午 8:30 左右,不少用户反映“粤康码”打不开了。上午 10:00 之后,情况逐渐得到缓解。随后,“粤康码”App 发布了一个很专业的官方说明。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);text-align: center;overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="1.197215777262181" data-s="300,640" src="/upload/3cb89bbf1664323cb50e551dea0a0fea.png" data-type="png" data-w="431" style="outline: 0px;color: rgb(51, 51, 51);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 17px;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 431px !important;visibility: visible !important;"></p> <blockquote data-type="2" data-url="" data-author-name="" data-content-utf8-length="123" data-source-title="" style="outline: 0px;color: rgba(0, 0, 0, 0.5);max-width: 100%;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> <section style="outline: 0px;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"> 今天(10 日)上午 8:31,平台监测到粤康码流量异常增大,最高达每分钟 140 万次,超出承载极限,触发系统保护机制,导致部分用户访问粤康码缓慢或者异常,运行保障团队紧急处置,于 9:04 部分缓解,9:56 完全恢复顺畅运行。由此给您带来不便,敬请谅解! </section> </section> </blockquote> <section style="margin-top: 40px;margin-right: 8px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzlYGr4HPPFSZv2hALJeicvs8Ms4X2MgRbibicSr355WOCVKjLWcXvqicZ4Uw/640?wx_fmt=jpeg&quot;) left 28px / 100% 6px no-repeat rgb(255, 255, 255);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;font-size: 20px;color: rgb(0, 179, 139);text-align: left;line-height: 35px;overflow-wrap: break-word !important;"> <span style="margin-bottom: 10px;padding-left: 22px;outline: 0px;max-width: 100%;display: block;font-size: 32px;box-sizing: border-box !important;overflow-wrap: break-word !important;">2</span>国际宕机事件:小 Bug 引起大麻烦 </section> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;Facebook 史上最严重宕机,市值一夜蒸发三千亿 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">10 月 4 日,美国社交媒体 Facebook、Instagram 和即时通讯软件 WhatsApp 出现大规模宕机,此次宕机长达近 7 个小时,刷新了 Facebook 自 2008 年以来的最长宕机时长。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">WhatsApp 和 Facebook Messenger 两款“微信”类即时通信产品,分别在全球范围拥有 20 亿用户和 13 亿用户,社交平台 Instagram 用户数也达到了 10 亿用户,也就是说这次宕机影响了超 30 亿用户。宕机期间,绝望的用户涌向了 Twitter、Discord、Signal 和 Telegram,又导致这些应用程序的服务器纷纷崩溃。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">Facebook 事后发表了故障报告,表示在一项日常维护工作中,工程师们发出一条用于评估全球骨干网容量可用性的指令,但意外切断了骨干网络中的所有连接,这实质上就是断开了 Facebook 全球数据中心之间的连接。服务中断之后,Facebook 的工程师们因无法通过正常方式访问 Facebook 数据中心进行修复,导致故障持续了 7 个小时之久。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">据悉,这次事故让脸书一夜之间市值蒸发约 473 亿美元 (约合 3049 亿元人民币)。</p> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;Roblox 发生超长宕机,表示关键业务坚决不上云 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">10 月 28 日,Roblox 发生了一次长达 73 小时的宕机事故。Roblox 是目前在全球范围内备受欢迎的在线游戏平台,日活跃用户超过 5000 万,其中许多人的年龄在 13 岁或以下。值得一提的是,Roblox 还被认为是“元宇宙”(metaverse)的关键参与者。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">Roblox 随后发布了非常详细的故障报告。在报告中,Roblox 的技术人员解释到,Roblox 程序运行在他们自己的数据中心中。为了管理自己众多的服务器,Roblox 使用了开源 Consul 进行服务发现、健康检查。Roblox 表示宕机主要是因启用了 Consul 里的流式传输功能代替长轮询机制,但流式传输功能存在 bug,最终导致性能下降而引起系统崩溃。宕机 54 个小时后才排查出故障原因,通过禁止流式传输功能,逐渐恢复了系统的服务能力。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">在这样的服务中断之后,很多人很自然地询问 Roblox 是否会考虑迁移到公共云,让第三方管理 Roblox 的基础计算、存储和网络服务。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">Roblox 技术人员表示,与使用公有云相比,自建数据中心能够显着控制成本。此外,拥有自己的硬件并构建自己的边缘基础设施能使 Roblox 最大限度地减少性能变化并管理全球玩家的延时。但也并不拘泥于任何特定的方法:“我们将公共云用于对我们的玩家和开发人员最有意义的用例,例如突发容量、大部分 DevOps 工作流程以及大部分内部分析。但对于对性能和延迟至关重要的工作负载,我们选择在本地构建和管理自己的基础架构。这样才能使我们能够建立一个更好的平台。”</p> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;Salesforce 工程师走捷径修 Bug 引起全球大宕机 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">Salesforce 是目前最受欢迎的云软件应用程序之一。据报道该软件应用程序已被全球大约 150,000 个组织中的数百万名员工使用。Salesforce 提供的服务涉及客户关系管理的各个方面,从普通的联系人管理、产品目录到订单管理、机会管理、销售管理等。用户无需花费大量资金和人力用于记录的维护、储存和管理,所有的记录和数据都储存在 Salesforce.com 上面。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">5 月 11 日,Salesforce 的服务开始不可用,宕机持续了 5 个小时。事后,Salesforce 公司组织了一次客户简报会,完整披露了事件情况与相关工程师的操作流程。虽然 Salesforce 向来以高度自动化的内部业务流程为傲,但其中不少环节仍然只能手动操作完成——DNS 正是其中之一。工程师使用的配置脚本执行一项配置变更,变更后需要重启服务器生效,不幸的是,脚本更新发生超时失败。随后更新又在 Salesforce 各数据中心内不断部署,超时点也被不断引爆...... 对这位决心绕开既有管理政策、意外肇事的工程师本人,Salesforce 表示“我们已经对这位员工做出了适当处理。”</p> <section style="margin-top: 40px;margin-right: 8px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzlYGr4HPPFSZv2hALJeicvs8Ms4X2MgRbibicSr355WOCVKjLWcXvqicZ4Uw/640?wx_fmt=jpeg&quot;) left 28px / 100% 6px no-repeat rgb(255, 255, 255);font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;font-size: 20px;color: rgb(0, 179, 139);text-align: left;line-height: 35px;overflow-wrap: break-word !important;"> <span style="margin-bottom: 10px;padding-left: 22px;outline: 0px;max-width: 100%;display: block;font-size: 32px;box-sizing: border-box !important;overflow-wrap: break-word !important;">3</span>云计算相关服务提供商:一旦出岔子,“爆炸半径”就很大! </section> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;云计算巨头 OVH 数据中心失火,360 万个网站被迫下线 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">3 月份,欧洲云计算巨头 OVH 位于法国斯特拉斯堡的机房近日发生严重火灾,该区域总共有 4 个数据中心 (Strasbourg Data Center),发生起火的 SBG2 数据中心被完全烧毁,另有一个数据中心 SBG1 的建筑物部分受损。当地报纸称 115 位消防员投入 6 个小时才将其扑灭。经过长达 6 个小时的持续燃烧,SBG2 内的数据应该会损失惨重。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">这场大火对欧洲范围内的众多网站造成严重影响。据悉,总共有跨 464000 个域的多达 360 万个网站下线。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">受到此次大火影响的客户包括欧洲航天局的数据与信息访问服务 ONDA 项目,此项目负责为用户托管地理空间数据并在云端构建应用程序。Rust 旗下的游戏工作室 Facepunch Studios 证实,有 25 台服务器被烧毁,他们的数据已在这场大火中全部丢失。即使数据中心重新上线后,也无法恢复任何数据。其他客户还包括法国政府,其 data.gouv.Fr 网站也被迫下线。另外还有加密货币交易所 Deribit,以及负责跟踪 DDoS 僵尸网络与其他网络滥用问题的信息安全威胁情报厂商 Bad Packets......</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">其中还有些人很不走运:“不!!!我靠!!!我的服务器在机架 70C09 上,我就是个普通客户,我没有任何灾难恢复计划……”</p> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;搞瘫全球大半个互联网,Fastly 是何方神圣? </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">6 月 8 日,当全球各地数以亿计的互联网用户登陆自己平日经常登陆的网站时,发现页面无法打开,并出现了“503 Errors”的错误提示,包括亚马逊、Twitter、Reddit、Twitch、HBO Max、Hulu、PayPal、Pinterest 以及包括纽约时报、CNN 等在内的各种类型的网站均悉数中招。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">大约持续了一个小时之后,人们才发现这场大规模故障是由 CDN 服务公司 Fastly 引起的。Fastly 通过其官方推特和博客称,“我们发现一个服务配置的更改引发了全球服务的短暂中断,目前已将这一配置关闭,我们全球服务网络已恢复正常。”</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;"><img class="rich_pages wxw-img" data-ratio="0.2182952182952183" data-s="300,640" src="/upload/7e9142cf89d892e6cdda2bbc8fd2da74.png" data-type="png" data-w="962" style="outline: 0px;text-align: center;color: rgb(51, 51, 51);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;font-size: 17px;box-sizing: border-box !important;overflow-wrap: break-word !important;width: 677px !important;visibility: visible !important;"></p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">于 2011 年成立的 Fastly 是全球为数不多的大型 CDN 供应商之一,可加快用户浏览速度和体验。有意思的是,出问题之后 Fastly 的股价在当天出现大涨,因为通过这起事件,投资者意识到,这家总部位于旧金山,员工数不到 1000 人的小公司,对互联网世界有着举足轻重的影响力。</p> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;谷歌云全球宕机 2 小时 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">11 月 16 日,据国外媒体报道,全球最大的云服务提供商之一谷歌云(Google Cloud)出现了宕机,导致许多依赖于谷歌云的大型公司网站中断服务。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">中断持续约 2 个小时,其中包括家得宝、Spotify 等公司都接到用户关于服务中断的反馈,另外 Etsy 和 Snap 的服务也发生网络故障。此外本次宕机对谷歌自家服务影响颇深,YouTube、Gmail、Google Search 均停止了工作。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">据悉此事件是谷歌云用户错误配置外部代理负载平衡 (GCLB) 所导致,算是一个漏洞,在 6 个月前被引入,极少数情况下,该漏洞允许损坏的配置文件被推送到 GCLB。11 月 12 日,一位 Google 工程师就发现此漏洞。谷歌原计划于 11 月 15 日推出补丁,但是不巧的是还没修复完,服务中断就发生了。</p> <section style="margin-top: 30px;margin-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;white-space: normal;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;text-align: left;color: rgb(0, 179, 138);overflow-wrap: break-word !important;"> <span style="margin-right: 10px;outline: 0px;max-width: 100%;display: inline-block;width: 15px;height: 15px;background: url(&quot;https://mmbiz.qpic.cn/mmbiz_jpg/YriaiaJPb26VNTgkEkJ6g90uvzOib6nRXzl9bWS1TmZTDwvm2qVVjJDPwvHCP8XqR72NCpHhljDrd574N3ZEKU5Iw/640?wx_fmt=jpeg&quot;) center center / 100% 100% no-repeat;box-sizing: border-box !important;overflow-wrap: break-word !important;"></span>&nbsp;AWS 一个月内发生 3 次宕机 </section> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">在 2021 年的最后一个月,AWS 发生了 3 次宕机。第一次宕机发生美国东部时间 7 日,从上午 10 点 45 分持续到下午 2 点 22 分,包括迪斯尼、奈飞、Robinhood、Roku 等大量热门网站和应用都发生了网络中断。同时,亚马逊的 Alexa AI 助理、Kindle 电子书、亚马逊音乐、Ring 安全摄像头等业务也受到影响。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">12 月 10 日,AWS 公布了本次宕机的原因:某内部客户端的意外行为导致连接活动激增,使内部网络和主 AWS 网络之间的联网设备不堪重负,从而导致这些网络之间的通信延迟。这些延迟增加了在网络之间通信的服务延迟和错误,从而导致更多的连接尝试和重试,最终引发持续的堵塞和性能问题。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">12 月第二次宕机发生在 16 日上午 7 点 43 分左右,包括 Twitch、Zoom、PSN、Xbox Live、Doordash、Quickbooks Online 和 Hulu 等在线服务均受到影响。AWS 随后公布了故障原因:由于主网络中某自动化软件原因,错误得将一些流量转移到主干网,结果影响了一些互联网应用的连接。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">12 月第三次宕机发生在 23 日美国东部时间 7 点 30 分左右,包括 Slack、Epic Games、加密货币交易所 Coinbase Global、游戏公司 Fortnite 、约会应用程序 Grindr 和交付公司 Instacart。对于此次中断,AWS 初步调查称是数据中心供电的问题。</p> <p style="padding-top: 23px;padding-right: 8px;padding-left: 8px;outline: 0px;max-width: 100%;box-sizing: border-box;letter-spacing: 0.544px;font-family: Avenir, -apple-system-font, 微软雅黑, sans-serif;background-color: rgb(255, 255, 255);font-size: 16px;white-space: pre-line;line-height: 27px;color: rgb(74, 74, 74);overflow-wrap: break-word !important;">最后,希望 2022 年大家都不会经历宕机~</p>

Redis解决websocket在分布式场景下session共享问题

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="font-size: 16px;color: black;padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;"> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">在显示项目中遇到了一个问题,需要使用到websocket与小程序建立长链接。由于项目是负载均衡的,存在项目部署在多台机器上。这样就会存在一个问题,当一次请求负载到第一台服务器时,socketsession在第一台服务器线程上,第二次请求,负载到第二台服务器上,需要通过id查找当前用户的session时,是查找不到的。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.22007042253521128" src="/upload/5eb26e67a216d2bbaea3af8689d32d9a.png" data-type="png" data-w="568" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">可以看到,由于websocket的session并没有实现序列化接口。所以无法将session序列化到redis中。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">web的中的httpsession 主要是通过下面的两个管理器实现序列化的。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">&nbsp;&nbsp;org.apache.catalina.session.StandardManager<br><br>&nbsp;&nbsp;org.apache.catalina.session.PersistentManager<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">StandardManager是Tomcat默认使用的,在web应用程序关闭时,对内存中的所有HttpSession对象进行持久化,把他们保存到文件系统中。默认的存储文件为</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">&lt;tomcat安装目录&gt;/work/Catalina/&lt;主机名&gt;/&lt;应用程序名&gt;/sessions.ser<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">PersistentManager比StandardManager更为灵活,只要某个设备提供了实现<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">org.apache.catalina.Store</code>接口的驱动类,PersistentManager就可以将HttpSession对象保存到该设备。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.13575757575757577" src="/upload/f94d9fab350caa8177927d16f3bc825.png" data-type="png" data-w="825" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">所以<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">spring-session-redis</code> 解决分布场景下的session共享就是将session序列化到redis中间件中,使用filter 加装饰器模式解决分布式场景httpsession 共享问题。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 22px;line-height: 1.5em;margin-top: 2.2em;margin-bottom: 35px;"><span style="display: none;"></span><span style="display: inline-block;background-image: linear-gradient(rgb(255, 255, 255) 60%, rgb(255, 177, 27) 40%);background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;color: rgb(81, 81, 81);padding: 2px 13px;margin-right: 3px;height: 50%;">解决方案</span></h2> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 使用消息中间件解决websocket session共享问题。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> 使用redis的发布订阅模式解决 </section></li> </ul> <h3 data-tool="mdnice编辑器" style="font-weight: bold;font-size: 20px;line-height: 1.4;padding-top: 10px;margin-top: 10px;margin-bottom: 5px;"><span style="display: none;"></span><span style="color: rgb(81, 81, 81);font-size: 1em;padding-left: 20px;border-left: 3px solid rgb(249, 191, 69);">本文使用方式二</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">使用StringRedisTemplate的convertAndSend方法向指定频道发送指定消息:</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a626a4;line-height: 26px;">this</span>.execute((connection)&nbsp;-&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;connection.publish(rawChannel,&nbsp;rawMessage);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;<br>&nbsp;},&nbsp;<span style="color: #a626a4;line-height: 26px;">true</span>);<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">redis的命令<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">publish channel message</code></p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">添加一个监听的容器以及一个监听器适配器</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">&nbsp;<span style="color: #4078f2;line-height: 26px;">@Bean</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">RedisMessageListenerContainer&nbsp;<span style="color: #4078f2;line-height: 26px;">container</span><span style="line-height: 26px;">(RedisConnectionFactory&nbsp;connectionFactory,&nbsp;MessageListenerAdapter&nbsp;listenerAdapter)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;RedisMessageListenerContainer&nbsp;container&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;RedisMessageListenerContainer();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container.setConnectionFactory(connectionFactory);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;可以添加多个&nbsp;messageListener,配置不同的交换机</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container.addMessageListener(listenerAdapter,&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;PatternTopic(Constants.REDIS_CHANNEL));<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;订阅最新消息频道</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;container;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Bean</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;">MessageListenerAdapter&nbsp;<span style="color: #4078f2;line-height: 26px;">listenerAdapter</span><span style="line-height: 26px;">(RedisReceiver&nbsp;receiver)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;消息监听适配器</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;MessageListenerAdapter(receiver,&nbsp;<span style="color: #50a14f;line-height: 26px;">"onMessage"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">添加消息接收器</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;*&nbsp;消息监听对象,接收订阅消息<br>&nbsp;*/</span><br><span style="color: #4078f2;line-height: 26px;">@Component</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">RedisReceiver</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">implements</span>&nbsp;<span style="color: #c18401;line-height: 26px;">MessageListener</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;Logger&nbsp;log&nbsp;=&nbsp;LoggerFactory.getLogger(<span style="color: #a626a4;line-height: 26px;">this</span>.getClass());<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Autowired</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;WebSocketServer&nbsp;webSocketServer;<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;处理接收到的订阅消息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Override</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">onMessage</span><span style="line-height: 26px;">(Message&nbsp;message,&nbsp;<span style="color: #a626a4;line-height: 26px;">byte</span>[]&nbsp;pattern)</span><br>&nbsp;&nbsp;&nbsp;&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;channel&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;String(message.getChannel());<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;订阅的频道名称</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;msg&nbsp;=&nbsp;<span style="color: #50a14f;line-height: 26px;">""</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;msg&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;String(message.getBody(),&nbsp;Constants.UTF8);<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//注意与发布消息编码一致,否则会乱码</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!StringUtils.isEmpty(msg)){<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(Constants.REDIS_CHANNEL.endsWith(channel))<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//&nbsp;最新消息</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JSONObject&nbsp;jsonObject&nbsp;=&nbsp;JSON.parseObject(msg);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webSocketServer.sendMessageByWayBillId(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Long.parseLong(jsonObject.get(Constants.REDIS_MESSAGE_KEY).toString())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;,jsonObject.get(Constants.REDIS_MESSAGE_VALUE).toString());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<span style="color: #a626a4;line-height: 26px;">else</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">//TODO&nbsp;其他订阅的消息处理</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<span style="color: #a626a4;line-height: 26px;">else</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #50a14f;line-height: 26px;">"消息内容为空,不处理。"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(Exception&nbsp;e)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(<span style="color: #50a14f;line-height: 26px;">"处理消息异常:"</span>+e.toString());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">websocket的配置类</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@description</span>:&nbsp;websocket的配置类<br>&nbsp;*/</span><br><span style="color: #4078f2;line-height: 26px;">@Configuration</span><br><span style="color: #4078f2;line-height: 26px;">@EnableWebSocket</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">WebSocketConfiguration</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Bean</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;ServerEndpointExporter&nbsp;<span style="color: #4078f2;line-height: 26px;">serverEndpointExporter</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ServerEndpointExporter();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">添加websocket的服务组件</strong></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #383a42;background: #fafafa;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #4078f2;line-height: 26px;">@ServerEndpoint</span>(<span style="color: #50a14f;line-height: 26px;">"/websocket/{id}"</span>)<br><span style="color: #4078f2;line-height: 26px;">@Component</span><br><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">class</span>&nbsp;<span style="color: #c18401;line-height: 26px;">WebSocketServer</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">final</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">long</span>&nbsp;sessionTimeout&nbsp;=&nbsp;<span style="color: #986801;line-height: 26px;">600000</span>;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">final</span>&nbsp;Logger&nbsp;log&nbsp;=&nbsp;LoggerFactory.getLogger(WebSocketServer<span style="line-height: 26px;">.<span style="color: #a626a4;line-height: 26px;">class</span>)</span>;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;当前在线连接数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;AtomicInteger&nbsp;onlineCount&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;AtomicInteger(<span style="color: #986801;line-height: 26px;">0</span>);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;用来存放每个客户端对应的&nbsp;WebSocketServer&nbsp;对象<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;ConcurrentHashMap&lt;Long,&nbsp;WebSocketServer&gt;&nbsp;webSocketMap&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;ConcurrentHashMap&lt;&gt;();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;与某个客户端的连接会话,需要通过它来给客户端发送数据<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Session&nbsp;session;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;接收&nbsp;id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;Long&nbsp;id;<br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@Autowired</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">private</span>&nbsp;StringRedisTemplate&nbsp;template;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;连接建立成功调用的方法<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@OnOpen</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">onOpen</span><span style="line-height: 26px;">(Session&nbsp;session,&nbsp;@PathParam(<span style="color: #50a14f;line-height: 26px;">"id"</span>)</span>&nbsp;Long&nbsp;id)&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;session.setMaxIdleTimeout(sessionTimeout);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.session&nbsp;=&nbsp;session;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.id&nbsp;=&nbsp;id;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(webSocketMap.containsKey(id))&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webSocketMap.remove(id);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webSocketMap.put(id,&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">else</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webSocketMap.put(id,&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;addOnlineCount();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #50a14f;line-height: 26px;">"编号id:"</span>&nbsp;+&nbsp;id&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"连接,当前在线数为:"</span>&nbsp;+&nbsp;getOnlineCount());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sendMessage(<span style="color: #50a14f;line-height: 26px;">"连接成功!"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(IOException&nbsp;e)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(<span style="color: #50a14f;line-height: 26px;">"编号id:"</span>&nbsp;+&nbsp;id&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">",网络异常!!!!!!"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;连接关闭调用的方法<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@OnClose</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">onClose</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(webSocketMap.containsKey(id))&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webSocketMap.remove(id);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;subOnlineCount();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #50a14f;line-height: 26px;">"编号id:"</span>&nbsp;+&nbsp;id&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">"退出,当前在线数为:"</span>&nbsp;+&nbsp;getOnlineCount());<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;收到客户端消息后调用的方法<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@param</span>&nbsp;message&nbsp;客户端发送过来的消息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@OnMessage</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">onMessage</span><span style="line-height: 26px;">(String&nbsp;message,&nbsp;Session&nbsp;session)</span>&nbsp;</span>{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #50a14f;line-height: 26px;">"编号id消息:"</span>&nbsp;+&nbsp;id&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">",报文:"</span>&nbsp;+&nbsp;message);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;发生错误时调用<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@param</span>&nbsp;session<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@param</span>&nbsp;error<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #4078f2;line-height: 26px;">@OnError</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">onError</span><span style="line-height: 26px;">(Session&nbsp;session,&nbsp;Throwable&nbsp;error)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(<span style="color: #50a14f;line-height: 26px;">"编号id错误:"</span>&nbsp;+&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.id&nbsp;+&nbsp;<span style="color: #50a14f;line-height: 26px;">",原因:"</span>&nbsp;+&nbsp;error.getMessage());<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@description</span>:&nbsp;&nbsp;分布式&nbsp;&nbsp;使用redis&nbsp;去发布消息<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@dateTime</span>:&nbsp;2021/6/17&nbsp;10:31<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">sendMessage</span><span style="line-height: 26px;">(@NotNull&nbsp;String&nbsp;key,String&nbsp;message)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;String&nbsp;newMessge=&nbsp;<span style="color: #a626a4;line-height: 26px;">null</span>;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;newMessge&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;String(message.getBytes(Constants.UTF8),&nbsp;Constants.UTF8);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(UnsupportedEncodingException&nbsp;e)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Map&lt;String,String&gt;&nbsp;map&nbsp;=&nbsp;<span style="color: #a626a4;line-height: 26px;">new</span>&nbsp;HashMap&lt;String,&nbsp;String&gt;();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map.put(Constants.REDIS_MESSAGE_KEY,&nbsp;key);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;map.put(Constants.REDIS_MESSAGE_VALUE,&nbsp;newMessge);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;template.convertAndSend(Constants.REDIS_CHANNEL,&nbsp;JSON.toJSONString(map));<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@description</span>:&nbsp;单机使用&nbsp;&nbsp;外部接口通过指定的客户id向该客户推送消息。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;<span style="color: #a626a4;line-height: 26px;">@dateTime</span>:&nbsp;2021/6/16&nbsp;17:49<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">sendMessageByWayBillId</span><span style="line-height: 26px;">(@NotNull&nbsp;Long&nbsp;key,&nbsp;String&nbsp;message)</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WebSocketServer&nbsp;webSocketServer&nbsp;=&nbsp;webSocketMap.get(key);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">if</span>&nbsp;(!StringUtils.isEmpty(webSocketServer))&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">try</span>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;webSocketServer.sendMessage(message);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.info(<span style="color: #50a14f;line-height: 26px;">"编号id为:"</span>+key+<span style="color: #50a14f;line-height: 26px;">"发送消息:"</span>+message);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<span style="color: #a626a4;line-height: 26px;">catch</span>&nbsp;(IOException&nbsp;e)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.printStackTrace();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(<span style="color: #50a14f;line-height: 26px;">"编号id为:"</span>+key+<span style="color: #50a14f;line-height: 26px;">"发送消息失败"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error(<span style="color: #50a14f;line-height: 26px;">"编号id号为:"</span>+key+<span style="color: #50a14f;line-height: 26px;">"未连接"</span>);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a0a1a7;font-style: italic;line-height: 26px;">/**<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;实现服务器主动推送<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">sendMessage</span><span style="line-height: 26px;">(String&nbsp;message)</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">throws</span>&nbsp;IOException&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">this</span>.session.getBasicRemote().sendText(message);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">synchronized</span>&nbsp;AtomicInteger&nbsp;<span style="color: #4078f2;line-height: 26px;">getOnlineCount</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: #a626a4;line-height: 26px;">return</span>&nbsp;onlineCount;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">synchronized</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">addOnlineCount</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WebSocketServer.onlineCount.getAndIncrement();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="line-height: 26px;"><span style="color: #a626a4;line-height: 26px;">public</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">static</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">synchronized</span>&nbsp;<span style="color: #a626a4;line-height: 26px;">void</span>&nbsp;<span style="color: #4078f2;line-height: 26px;">subOnlineCount</span><span style="line-height: 26px;">()</span>&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WebSocketServer.onlineCount.getAndDecrement();<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">项目结构</strong></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="1.3721518987341772" src="/upload/ce474e9d8128d249fa7361409dc5f445.png" data-type="png" data-w="395" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">将该项目使用三个端口号启动三个服务</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.3609022556390977" src="/upload/70ab739aca8eef2a7df459f87f38d194.png" data-type="png" data-w="399" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">使用下面的这个网站进行演示。</strong></p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> http://www.easyswoole.com/wstool.html </section></li> </ul> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.44591029023746703" src="/upload/b6420b5cc8206cf802721ef30cf1ecd6.png" data-type="png" data-w="1137" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">启动两个页面网址分别是:</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">ws://127.0.0.1:8081/websocket/456</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(1, 1, 1);"> <p style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">ws://127.0.0.1:8082/websocket/456</p> </section></li> </ul> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">使用postman给<code style="font-size: 14px;border-radius: 4px;font-family: &quot;Operator Mono&quot;, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(155, 110, 35);background-color: rgb(255, 245, 227);padding: 3px;margin: 3px;">http://localhost:8080/socket/456</code> 发送请求</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.7453027139874739" src="/upload/7db0179937144154edee0b4bc38e5bc.png" data-type="png" data-w="958" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">可以看到,我们给8080服务发送的消息,我们订阅的8081和8082 服务可以也可以使用该编号进行消息的推送。</p> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);"><strong style="color: black;">使用8082服务发送这个消息格式{"KEY":456,"VALUE":"aaaa"} 的消息。其他的服务也会收到这个信息。</strong></p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-ratio="0.45484581497797355" src="/upload/90178aafdea1433be9883e7588066792.png" data-type="png" data-w="1816" style="margin-right: auto;margin-left: auto;width: 100%;border-radius: 5px;display: block;margin-bottom: 15px;"> </figure> <p data-tool="mdnice编辑器" style="margin-bottom: 20px;line-height: 1.8em;color: rgb(58, 58, 58);">以上就是使用redis的发布订阅解决websocket 的分布式session 问题。</p> <blockquote data-tool="mdnice编辑器" style="border-top: none;border-right: none;border-bottom: none;font-size: 0.9em;overflow: auto;color: rgb(106, 115, 125);padding: 10px 10px 10px 20px;margin-bottom: 20px;margin-top: 20px;border-left-color: rgb(255, 177, 27);background: rgb(255, 245, 227);"> <p style="font-size: 16px;line-height: 26px;color: rgb(89, 89, 89);">码云地址是:https://gitee.com/jack_whh/dcs-websocket-session</p> </blockquote> </section>

微服务下蓝绿发布、滚动发布、灰度发布等方案,必须懂!

作者:微信小助手

<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="line-height: 1.6;word-break: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, &quot;PingFang SC&quot;, Cambria, Cochin, Georgia, Times, &quot;Times New Roman&quot;, serif;padding: 5px;font-size: 16px;color: rgb(53, 53, 53);word-spacing: 0.8px;letter-spacing: 0.8px;border-radius: 16px;" data-mpa-powered-by="yiban.io"> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">在项目迭代的过程中,不可避免需要<span style="font-weight: 700;color: rgb(248, 57, 41);">上线</span>。上线对应着部署,或者重新部署;部署对应着修改;修改则意味着风险。目前有很多部署发布的技术, 这儿将常见的做一个总结。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">上面所说难免有些抽象, 举一个情景例子, 假如你是微博项目负责人员, 现在新版本较原来的老版本有很大的改变, 这设计到服务架构、前端UI等等, 经过测试功能没有障碍, 那么这时候如何让用户切换到新的版本呢?</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">显而易见, 第一次发布的应用是没有所谓的这个问题的, 这种如何发布的思考只会出现在后面的版本迭代中。</p> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">蓝绿发布</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">蓝绿部署中,一共有两套系统:一套是正在提供服务系统(也就是上面说的<span style="font-weight: 700;color: rgb(248, 57, 41);">旧版</span>),标记为“<span style="font-weight: 700;color: rgb(248, 57, 41);">绿色</span>”;另一套是准备发布的系统,标记为“<span style="font-weight: 700;color: rgb(248, 57, 41);">蓝色</span>”。两套系统都是功能完善的,并且正在运行的系统,只是系统版本和对外服务情况不同。正在对外提供服务的老系统是绿色系统,新部署的系统是蓝色系统。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.5058078141499472" src="/upload/a3b2dcf0c94d13ba84b01eb6acdc6293.png" data-type="png" data-w="947" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">蓝色系统不对外提供服务,用来做啥?</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">用来做发布前测试,测试过程中发现任何问题,可以直接在蓝色系统上修改,不干扰用户正在使用的系统。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">蓝色系统经过反复的测试、修改、验证,确定达到上线标准之后,直接将用户切换到蓝色系统, 切换后的一段时间内,依旧是蓝绿两套系统并存,但是用户访问的已经是蓝色系统。这段时间内观察蓝色系统(新系统)工作状态,如果出现问题,直接切换回绿色系统。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当确信对外提供服务的蓝色系统工作正常,不对外提供服务的绿色系统已经不再需要的时候,蓝色系统正式成为对外提供服务系统,成为新的绿色系统。原先的绿色系统可以销毁,将资源释放出来,用于[部署下一个蓝色系统。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">蓝绿发布特点</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 蓝绿部署的目的是减少发布时的中断时间、能够快速撤回发布。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 两套系统没有耦合的时候才能百分百保证不干扰 </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">蓝绿发布注意事项</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">蓝绿部署只是[上线策略中的一种,它不是可以应对所有情况的万能方案。蓝绿部署能够简单快捷实施的前提假设是目标系统是非常内聚的,如果目标系统相当复杂,那么如何切换、两套系统的数据是否需要以及如何同步等,都需要仔细考虑。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">当你切换到蓝色环境时,需要妥当处理未完成的业务和新的业务。如果你的数据库后端无法处理,会是一个比较麻烦的问题。</p> <ul data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 可能会出现需要同时处理“微服务架构应用”和“传统架构应用”的情况,如果在蓝绿[部署中协调不好这两者,还是有可能会导致服务停止。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 需要提前考虑数据库与应用部署同步迁移 /回滚的问题。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 蓝绿部署需要有基础设施支持。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 在非隔离基础架构( VM 、 Docker 等)上执行蓝绿[部署,蓝色环境和绿色环境有被摧毁的风险。 </section></li> </ul> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">滚动发布</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">一般是取出一个或者多个服务器停止服务,执行更新,并重新将其投入使用。周而复始,直到集群中所有的实例都更新成新版本。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.38598442714126807" src="/upload/2aa0a6efac5133f392b529c7fe515435.png" data-type="png" data-w="899" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">发布流程:</span></p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">相对于蓝绿发布需要一套完备的机器不同, 滚动发布只需要一台机器(这儿这是为了理解, 实际可能是多台), 我们只需要将部分功能部署在这台机器上, 然后去替换正在运行的机器。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如上图, 将更新后的功能部署在<span style="font-weight: 700;color: rgb(248, 57, 41);">Server1</span> 上, 然后<span style="font-weight: 700;color: rgb(248, 57, 41);">Server1</span>去替换正在运行的Server, 替换下来的物理机又可以继续部署<span style="font-weight: 700;color: rgb(248, 57, 41);">Server2</span>的新版本, 然后去替换正在工作的<span style="font-weight: 700;color: rgb(248, 57, 41);">Server2</span> , 以此类推, 直到替换完所有的服务器, 至此 ,服务更新完成。</p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">滚动发布特点</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 这种部署方式相对于蓝绿部署,更加 <span style="font-weight: 700;color: rgb(248, 57, 41);">节约资源</span>——它不需要运行两个集群、两倍的实例数。我们可以部分部署,例如每次只取出集群的20%进行升级。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <span style="font-weight: 700;color: rgb(248, 57, 41);">回滚困难</span> </section></li> </ol> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;color: black;font-size: 20px;"><span style="display: none;"></span><span style="font-size: 16px;color: #222;">滚动发布注意事项</span><span style="display: none;"></span></h3> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">滚动发布没有一个确定可行的环境。使用蓝绿[部署,我们能够清晰地知道老版本是可行的,而使用滚动发布,我们无法确定。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">修改了现有的环境。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">回滚困难。举个例子,在某一次发布中,我们需要更新100个实例,每次更新10个实例,每次部署需要5分钟。当滚动发布到第80个实例时,发现了问题,需要回滚,这个回滚却是一个痛苦,并且漫长的过程。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">有的时候,我们还可能对系统进行动态伸缩,如果部署期间,系统自动扩容/缩容了,我们还需判断到底哪个节点使用的是哪个代码。尽管有一些自动化的运维工具,但是依然令人心惊胆战。</p> </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">因为是逐步更新,那么我们在上线代码的时候,就会短暂出现新老版本不一致的情况,如果对上线要求较高的场景,那么就需要考虑如何做好兼容的问题。</p> </section></li> </ol> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">灰度发布</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">灰度发布, 也叫金丝雀发布。是指在黑与白之间,能够平滑过渡的一种发布方式。<span style="font-weight: 700;color: rgb(248, 57, 41);">AB test</span>就是一种灰度发布方式,让一部分用户继续用<span style="font-weight: 700;color: rgb(248, 57, 41);">A</span>,一部分用户开始用<span style="font-weight: 700;color: rgb(248, 57, 41);">B</span>,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度,而我们平常所说的<span style="font-weight: 700;color: rgb(248, 57, 41);">金丝雀部署</span>也就是灰度发布的一种方式。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">具体到服务器上, 实际操作中还可以做更多控制,譬如说,给最初更新的<span style="font-weight: 700;color: rgb(248, 57, 41);">10</span>台服务器设置较低的权重、控制发送给这10台服务器的请求数,然后逐渐提高权重、增加请求数。一种平滑过渡的思路, 这个控制叫做“<span style="font-weight: 700;color: rgb(248, 57, 41);">流量切分</span>”。</p> <blockquote data-tool="mdnice编辑器" style="border-width: initial;border-style: none;border-color: initial;font-size: 0.9em;overflow: auto;margin-bottom: 20px;margin-top: 20px;padding-top: 15px;padding-right: 10px;padding-bottom: 15px;line-height: 1.75;border-radius: 13px;color: rgb(53, 53, 53);background: rgb(245, 245, 245);"> <span style="display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">“</span> <p style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;font-size: 16px;margin-right: 10px;margin-left: 10px;">17世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离。</p> <span style="float: right;display: block;font-size: 2em;color: rgb(248, 57, 41);font-family: Arial, serif;line-height: 1em;font-weight: 700;">”</span> </blockquote> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.48416751787538304" src="/upload/7c614ec7adb842885ed718690d5b4bb2.png" data-type="png" data-w="979" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;"><span style="font-weight: 700;color: rgb(248, 57, 41);">过程:</span></p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 准备好部署各个阶段的工件,包括:构建工件,测试脚本,配置文件和部署清单文件。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 将“金丝雀”服务器部署进服务器中, 测试。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 从负载均衡列表中移除掉“金丝雀”服务器。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 升级“金丝雀”应用(排掉原有流量并进行[部署)。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 对应用进行自动化测试。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 将“金丝雀”服务器重新添加到负载均衡列表中(连通性和健康检查)。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> 如果“金丝雀”在线使用测试成功,升级剩余的其他服务器。(否则就回滚) </section></li> </ol> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">A/B 测试</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">A/B测试和蓝绿发布、滚动发布以及金丝雀发布,完全是两回事。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">蓝绿发布、滚动发布和金丝雀是发布策略,目标是确保新上线的系统稳定,关注的是新系统的BUG、隐患。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">A/B测试是效果测试,同一时间有多个版本的服务对外服务,这些服务都是经过足够测试,达到了上线标准的服务,有差异但是没有新旧之分(它们上线时可能采用了蓝绿部署的方式)。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">A/B测试关注的是不同版本的服务的实际效果,譬如说转化率、订单情况等。</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">A/B测试时,线上同时运行多个版本的服务,这些服务通常会有一些体验上的差异,譬如说页面样式、颜色、操作流程不同。相关人员通过分析各个版本服务的实际效果,选出效果最好的版本。</p> <figure data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;border-radius: 16px;overflow: hidden;padding: 0px 0.5em;"> <img class="rich_pages wxw-img" data-ratio="0.484297520661157" src="/upload/225c2714493959ca73f7cf83293359e0.png" data-type="png" data-w="605" style="border-radius: 6px;display: block;margin: 20px auto;max-width: 95%;object-fit: contain;box-shadow: rgb(210, 210, 210) 0em 0em 0.5em 0px;font-size: 17px;"> </figure> <h2 data-tool="mdnice编辑器" style="font-weight: bold;color: black;font-size: 22px;margin-top: 20px;margin-right: 10px;"><span style="display: none;"></span><span style="font-size: 18px;color: rgb(34, 34, 34);display: inline-block;padding-left: 10px;border-left: 5px solid rgb(248, 57, 41);">最后说一句(别白嫖,求关注)</span></h2> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">陈某每一篇文章都是精心输出,已经写了<span style="font-weight: 700;color: rgb(248, 57, 41);">3个专栏</span>,整理成<span style="font-weight: 700;color: rgb(248, 57, 41);">PDF</span>,获取方式如下:</p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;color: rgb(248, 57, 41);" class="list-paddingleft-2"> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU3MDAzNDg1MA==&amp;action=getalbum&amp;album_id=2042874937312346114#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">《Spring Cloud 进阶》</a>PDF:关注公众号:【 <span style="font-weight: 700;color: rgb(248, 57, 41);">码猿技术专栏</span>】回复关键词 <span style="font-weight: 700;color: rgb(248, 57, 41);">Spring Cloud 进阶</span> 获取! </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU3MDAzNDg1MA==&amp;action=getalbum&amp;album_id=1532834475389288449#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">《Spring Boot 进阶》</a>PDF:关注公众号:【 <span style="font-weight: 700;color: rgb(248, 57, 41);">码猿技术专栏</span>】回复关键词 <span style="font-weight: 700;color: rgb(248, 57, 41);">Spring Boot进阶</span> 获取! </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;line-height: 26px;color: rgb(53, 53, 53);"> <a href="https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzU3MDAzNDg1MA==&amp;action=getalbum&amp;album_id=1500819225232343046#wechat_redirect" style="color: rgb(248, 57, 41);border-bottom: 1px solid rgb(248, 57, 41);" data-linktype="2">《Mybatis 进阶》</a>PDF:关注公众号:【 <span style="font-weight: 700;color: rgb(248, 57, 41);">码猿技术专栏</span>】回复关键词 <span style="font-weight: 700;color: rgb(248, 57, 41);">Mybatis 进阶</span> 获取! </section></li> </ol> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">如果这篇文章对你有所帮助,或者有所启发的话,帮忙<span style="font-weight: 700;color: rgb(248, 57, 41);">点赞</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">在看</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">转发</span>、<span style="font-weight: 700;color: rgb(248, 57, 41);">收藏</span>,你的支持就是我坚持下去的最大动力!</p> <p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 1.75;margin-top: 0.8em;margin-bottom: 0.8em;">关注公众号:<span style="font-weight: 700;color: rgb(248, 57, 41);">【码猿技术专栏】</span>,公众号内有超赞的粉丝福利,回复:<span style="font-weight: 700;color: rgb(248, 57, 41);">加群</span>,可以加入技术讨论群,和大家一起讨论技术,吹牛逼!</p> </section> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzU3MDAzNDg1MA==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/19cc2hfD2rA07Je2pY1o0ic2KcPRn44icO8GVcKRdwiaYvrE6bNeTbWPicyV7c7jWmSyzsiaWASjjckzBcsJMJw06pA/0?wx_fmt=png" data-nickname="码猿技术专栏" data-alias="oneswholife" data-signature="前蚂蚁P8,纯粹的技术人,专注于Java后端技术分享,只写外面看不到的干货,你想要的都在这里……" data-from="0"></mpprofile> </section> <p style="text-align: right;"><span style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-align: right;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);"></span></p> <p style="text-align: right;"><span style="outline: 0px;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-align: right;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);">求点赞、在看、分享三连</span><img class="rich_pages wxw-img" data-fileid="100011967" data-ratio="1" data-type="png" data-w="20" src="/upload/1d550a991385b842a21e2b301725407e.png" style="outline: 0px;vertical-align: text-bottom;font-family: -apple-system, BlinkMacSystemFont, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif;letter-spacing: 0.544px;text-align: right;white-space: normal;color: rgb(53, 53, 53);font-size: 16px;word-spacing: 0.8px;background-color: rgb(255, 255, 255);display: inline-block;box-sizing: border-box !important;visibility: visible !important;width: 20px !important;"></p>

Redis 在 vivo 推送平台的应用与优化实践

作者:微信小助手

<section style="font-size: 15px;line-height: 1.9;letter-spacing: 0.75px;box-sizing: border-box;"> <section powered-by="xiumi.us" style="box-sizing: border-box;"> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="24" data-source-title=""> <section class="js_blockquote_digest"> <section> 作者:vivo互联网服务器团队-Yu Quan </section> </section> </blockquote> <p><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">一、推送平台特点</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">vivo推送平台是vivo公司向开发者提供的消息推送服务,通过在云端与客户端之间建立一条稳定、可靠的长连接,为开发者提供向客户端应用实时推送消息的服务,支持百亿级的通知/消息推送,秒级触达移动用户。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">推送平台的特点是并发高、消息量大、送达及时性较高。目前现状最高推送速度140w/s,单日最大消息量150亿,端到端秒级在线送达率99.9%。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">二、推送平台Redis使用介绍</p> </section> </section> <section powered-by="xiumi.us" style="box-sizing: border-box;"> <p style="box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">基于vivo推送平台的特点,对并发和时效性要求较高,并且消息数量多,消息有效期短。所以,推送平台选择使用Redis中间件作为消息存储和中转,以及token信息存储。之前主要使用两个Redis集群,采用Redis Cluster 集群模式。两个集群如下:</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="354" data-backw="578" data-ratio="0.6121031746031746" data-s="300,640" src="/upload/753d32df291955779c993351bc3652db.png" data-type="png" data-w="1008" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">对Redis的操作,主要包括如下几方面:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">1)推送环节,在接入层存储消息体到msg Redis集群,消息过期时间为msg Redis存储消息的过期时间。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">2)推送服务层经过一系列逻辑后,从msg Redis集群查出消息体,查询client Redis集群client信息,如果client在线,直接推送。如果client不在线,将消息id写到等待队列。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">3)如果连接上来,推送服务层,读取等待队列消息,进行推送。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">4)存储管理服务,会定期扫描cii索引,根据cii存储的最后更新时间,如果14天都没更新,说明是不活跃用户,会清理该token信息,同时清理该token对应的等待队列消息。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">推送环节操作Redis流程图如下:</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="451" data-backw="578" data-ratio="0.7797001153402537" data-s="300,640" src="/upload/386345ab2d3146ab34ef411a876a98cd.png" data-type="png" data-w="867" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">三、推送平台线上问题</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">如上面介绍,推送平台使用Redis主要msg集群和client集群,随着业务的发展,系统对性能要求越来越高,Redis出现一些瓶颈问题,其中msg Redis集群在优化前,规模已达到220个master,4400G容量。随着集群规模变大,维护难度增加,事故率变高。特别是4月份,某某明星离婚事件,实时并发消息量5.2亿,msg Redis集群出现单节点连接数、内存暴增问题,其中一个节点连接数达到24674,内存达到23.46G,持续30分钟左右。期间msg Redis集群读写响应较慢,平均响应时间500ms左右,影响到整体系统的稳定性和可用性,可用性降到85%。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="56" data-backw="578" data-ratio="0.0968096809680968" data-s="300,640" src="/upload/8de2a1834f3c7f44673d608b37a20e89.png" data-type="png" data-w="909" style="width: 100%;height: auto;"></p> <p style="box-sizing: border-box;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">四、推送平台Redis优化</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">Redis一般从以下几方面优化:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong>1)</strong><strong>容量</strong>:Redis属于内存型存储,相较于磁盘存储型数据库,存储成本较昂贵,正是由于内存型存储这个特性使得它读写性能较高,但是存储空间有限。因此,业务在使用时,应注意存储内容尽量是热数据,并且容量是可预先评估的,最好设置过期时间。在存储设计时,合理使用对应数据结构,对于一些相对大的value,可以压缩后存储。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong>2)</strong><strong>热key倾斜</strong>:Redis-Cluster把所有的物理节点映射到[0-16383]slot(槽)上,每个节点负责一部分slot。当有请求调用时,根据 CRC16(key) mod 16384的值,决定将key请求到哪个slot中。由于Redis-cluster这个特性,每个节点只负责一部分slot,因此,在设计key的时候应保证key的随机性,特别是使用一些hash算法映射key时,应保证hash值的随机分布。另外,控制热点key并发问题,可以采用限流降级或者本地缓存方式,防止热点key并发请求过高导致Redis热点倾斜。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong>3)</strong><strong>集群过大</strong>:Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。每个节点都保存所有节点与slot映射关系。当节点较多时,每个节点保存的映射关系也会变多。各节点之间心跳包的消息体内携带的数据越多。在扩缩容时,集群重新进行clusterSlots时间相对较长。集群会存在阻塞风险,稳定性受影响。因此,在使用集群时,应该尽量避免集群节点过多,最后根据业务对集群进行拆分。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">这里有个问题:为什么Redis-Cluster使用16384个slot,而不是更多,最多可以有多少个节点?</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">官方作者给出了<a target="_blank" href="https://github.com/redis/redis/issues/2576" textvalue="解释" linktype="text" imgurl="" tab="outerlink" style="text-decoration: underline;" data-linktype="2">解释</a>,并且在解释中说明,Redis-Cluster不建议超过1000个主节点。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="394" data-backw="578" data-ratio="0.6818181818181818" data-s="300,640" src="/upload/200d360549e9ee7cee8ff1cbcf33c593.png" data-type="png" data-w="660" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">基于以上一些优化方向,和自身业务特性,推送平台从以下几方面开启Redis优化之路。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">msg Redis集群容量优化;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">msg Redis大集群根据业务属性拆分;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">Redis热点key排查;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">client Redis集群并发调用优化。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">4.1 msg Redis集群容量优化</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">前文提及,msg Redis集群规模达到220个master、4400G容量,高峰期已使用容量达到3650G,使用了83%左右,如果后续推送提量,还需扩容,成本太高。于是对msg Redis集群存储内容进行分析,使用的分析工具是雪球开源RDB分析工具RDR 。<a target="_blank" href="https://github.com/xueqiu/rdr" textvalue="github网址" linktype="text" imgurl="" tab="outerlink" style="text-decoration: underline;" data-linktype="2">github网址</a>:这里不多介绍,大家可以去github网址下载相应的工具使用。这个工具可以分析Redis快照情况,包括:Redis不同结构类型容量、key数量、top 100 largest keys、前缀key数量和容量。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">分析后的结论:msg Redis集群中,mi:开头的结构占比80%左右,其中单推消息占比80%。说明:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">单推</strong>:1条消息推送1个用户</p></li> <li><p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">群推</strong>:1条消息可以重复推送多个用户,消息可以复用。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">单推的特点是一对一推送,推送完或者推送失败(被管控、无效用户等)消息体就不再使用。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">优化方案</strong>:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">及时清理单推消息,如果用户已经收到单推消息,收到puback回执,直接删除Redis消息。如果单推消息被管控等原因限制发送,直接删除单推消息体。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">对于相同内容的消息,进行聚合存储,相同内容消息存储一条,消息id做标识推送时多次使用。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">经过这个优化后,缩容效果较明显。全量上线后容量缩小了2090G,原最高容量为3650G,<strong>容量缩小了58%</strong>。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="360" data-backw="534" data-cropselx1="5" data-cropselx2="530" data-cropsely1="0" data-cropsely2="360" data-ratio="0.6747211895910781" data-s="300,640" src="/upload/fd1c2ae45aa25e9446303b7e893c6d65.jpg" data-type="jpeg" data-w="538" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">4.2 msg Redis大集群根据业务属性拆分</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">虽然进行了集群容量优化,但是高峰期msg Redis压力依然很大。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">主要原因</strong>:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">1)连接msg Redis的节点很多,导致高峰期连接数较高。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">2)消息体还有等待队列都存储在一个集群,推送时都需要操作,导致Redis并发很大,高峰期cpu负载较高,到达90%以上。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">3)老集群Redis版本是3.x,拆分后,新集群使用4.x版本。相较于3.x版本有如下优势:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">PSYNC2.0:优化了之前版本中,主从节点切换必然引起全量复制的问题。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">提供了新的缓存剔除算法:LFU(Last Frequently Used),并对已有算法进行了优化。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">提供了非阻塞del和flushall/flushdb功能,有效解决删除了bigkey可能造成的Redis阻塞。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">提供了memory命令,实现对内存更为全面的监控统计。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">&nbsp;更节约内存,存储同样多的数据,需要更少的内存空间。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">可以做内存碎片整理,逐步回收内存。当使用Jemalloc内存分配方案的时候,Redis可以使用在线内存整理。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">拆分方案根据业务属性对msg Redis存储信息进行拆分,把消息体和等待队列拆分出来,放到独立的两个集群中去。这样就有两种拆分方案。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">方案一</strong>:<strong style="box-sizing: border-box;">把等待队列从老集群拆分出来</strong></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">只需推送节点进行修改,但是发送等待队列连续的,有状态,与clientId在线状态相关,对应的value会实时更新,切换会导致数据丢失。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">方案二</strong>:<strong style="box-sizing: border-box;">把消息体从老集群拆分出来</strong></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">所有连接msg Redis的节点替换新地址重启,推送节点进行双读,等到老集群命中率为0时,直接切换读新集群。由于消息体的特点是只有写和读两个操作,没有更新,切换不用考虑状态问题,只要保证可以写入读取没问题。并且消息体容量具有增量属性,需要能方便快速的扩容,新集群采用4.0版本,方便动态扩缩容。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <table> <tbody> <tr> <td width="50" valign="top" style="background-color: rgb(214, 214, 214);"><br></td> <td width="103" valign="top" style="word-break: break-all;background-color: rgb(214, 214, 214);"><strong>方案一</strong><br></td> <td width="360" valign="top" style="word-break: break-all;background-color: rgb(214, 214, 214);"><strong>方案二</strong><br></td> </tr> <tr> <td width="50" valign="top" style="word-break: break-all;">优点<br></td> <td width="103" valign="top" style="word-break: break-all;"><span style="color: rgb(23, 43, 77);font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, Oxygen, Ubuntu, &quot;Fira Sans&quot;, &quot;Droid Sans&quot;, &quot;Helvetica Neue&quot;, sans-serif;font-size: 14px;text-align: left;background-color: rgb(255, 255, 255);">涉及节点少,改造只在推送节点</span></td> <td width="360" valign="top" style="word-break: break-all;"><p style="color: rgb(23, 43, 77);font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, Oxygen, Ubuntu, &quot;Fira Sans&quot;, &quot;Droid Sans&quot;, &quot;Helvetica Neue&quot;, sans-serif;font-size: 14px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);">1、数据不会丢失</p><p style="margin-top: 10px;color: rgb(23, 43, 77);font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, Oxygen, Ubuntu, &quot;Fira Sans&quot;, &quot;Droid Sans&quot;, &quot;Helvetica Neue&quot;, sans-serif;font-size: 14px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);">2、消息体容量具有增量属性,消息体存4.0新集群,性能更好,后续运维更方便</p></td> </tr> <tr> <td width="50" valign="top" style="word-break: break-all;">缺点<br></td> <td width="103" valign="top" style="word-break: break-all;"><span style="color: rgb(23, 43, 77);font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, Oxygen, Ubuntu, &quot;Fira Sans&quot;, &quot;Droid Sans&quot;, &quot;Helvetica Neue&quot;, sans-serif;font-size: 14px;text-align: left;background-color: rgb(255, 255, 255);">会丢失数据</span></td> <td width="360" valign="top" style="word-break: break-all;"><span style="color: rgb(23, 43, 77);font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, Oxygen, Ubuntu, &quot;Fira Sans&quot;, &quot;Droid Sans&quot;, &quot;Helvetica Neue&quot;, sans-serif;font-size: 14px;text-align: left;background-color: rgb(255, 255, 255);">涉及节点多,但除了推送节点需要考虑双读外,其他节点只需配置中心修改msg Redis地址到新集群服务重启即可</span></td> </tr> </tbody> </table> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">考虑到对业务的影响及服务可用性,保证消息不丢失,最终我们选择方案二。采用双读单写方案设计:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">由于将消息体切换到新集群,那在切换期间一段时间(最多30天),新的消息体写到新集群,老集群存储老消息体内容。这期间推送节点需要双读,保证数据不丢失。为了保证双读的高效性,需要支持不修改代码,不重启服务的动态规则调整措施。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">大致规则分为4个:只读老、只读新、先读老后读新、先读新后读老。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="32" data-source-title=""> <section class="js_blockquote_digest"> <section> 设计思路:服务端支持4种策略,通过配置中心的配置决定走哪个规则。 </section> </section> </blockquote> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="86" data-source-title=""> <section class="js_blockquote_digest"> <section> 规则的判断依据:根据老集群的命中数和命中率决定。上线初期规则配置“先读老再读新”;当老集群命中率低于50%,切换成"先读新后读老";当老集群命中数为0后,切换成“只读新”。 </section> </section> </blockquote> <p><br></p> <p style="white-space: normal;box-sizing: border-box;">老集群的命中率和命中数通过通用监控增加埋点。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">方案二流程图如下:</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="290" data-backw="578" data-ratio="0.5016077170418006" data-s="300,640" src="/upload/2fe51605ca98fb1728b47a4ceeabcfcc.png" data-type="png" data-w="1244" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">拆分后效果:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">拆分前,老msg Redis集群同时期高峰期负载95%以上。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">拆分后,同时期高峰期负载降低到70%,下降15%。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="712" data-backw="523" src="/upload/44db6d7ccc6c384f1b72fb500498d36d.png" data-cropx1="1.095602294455067" data-cropx2="570.8087954110899" data-cropy1="0" data-cropy2="732.9579349904398" data-ratio="1.2864674868189807" data-s="300,640" src="https://mmbiz.qpic.cn/mmbiz_jpg/4g5IMGibSxt4lPKsCuKS2c7OSSUu1OdBC704HZ1rQn1OibB1bbxcXj9OAE2ibovxPZsHflxJib8WBZiaKU2d1a4tsug/640?wx_fmt=jpeg" data-type="jpeg" data-w="569" style="width: 520px;height: 669px;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">拆分前,msg Redis集群同时期高峰期平均响应时间1.2ms,高峰期存在调用Redis响应慢情况。拆分后,平均响应时间降低到0.5ms,高峰期无响应慢问题。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">4.3 Redis热点key排查</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">前面有说过,4月赵丽颖热点事件,出现msg Redis单节点连接数、内存飙升问题,单节点节点连接数达到24674,内存达到23.46G。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">由于Redis集群使用的虚拟机,起初怀疑是虚拟机所在宿主机存在压力问题,因为根据排查发现出现问题的节点所在宿主机上挂载Redis主节点很多,大概10个左右,而其他宿主机挂载2-4个左右主节点,于是对master进行了一轮均衡化优化,使每台宿主机分配的主节点都比较均衡。均衡化之后,整体有一定改善。但是,在推送高峰期,尤其是全速全量推送时,还是会偶尔出现单节点连接数、内存飙升问题。观察宿主机网卡出入流量,都没出现瓶颈问题,同时也排除了宿主机上其他业务节点的影响。因此怀疑还是业务使用Redis存在热点倾斜问题。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">通过高峰期抓取调用链监控,从下图可以看到,我们11:49到12:59这期间调用msg Redis的hexists命令耗时很高,该命令主要是查询消息是否在mii索引中,链路分析耗时的key大都为mii:0。同时对问题节点Redis内存快照进行分析,发现mii:0容量占比很高,存在读取mii:0热点问题。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="265" data-backw="578" data-ratio="0.4578125" data-s="300,640" src="/upload/56052b7776e040bf46f0a96cb104d2a0.png" data-type="png" data-w="1280" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">经过分析排查,发现生成消息id的雪花算法生成的messageId,存在倾斜问题,由于同一毫秒的序列值都是从0开始,并且序列长度为12位,所以对于并发不是很高的管理后台及api节点,生成的messageId基本都是最后12位为0。由于mii索引key是mi:${messageId%1024},messageId最后12位为0,messageId%1024即为0,这样就导致msg Redis中mii:0这个key很大,查询时命中率高,因此导致了Redis的热key问题。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="173" data-backw="578" data-ratio="0.2995090016366612" data-s="300,640" src="/upload/69f32047811cf5c129599b24fd96bbc6.png" data-type="png" data-w="611" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">优化措施</strong>:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">1)雪花算法改造,生成消息id时使用的sequence初始值不再是0,而是从0~1023随机取一个数,防止热点倾斜问题。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">2)通过msg消息体中消息类型及消息体是否存在来替换调hexists命令。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">最终效果</strong>:优化后,mii索引已分布均匀,Redis连接数很平稳,内存增长也较平稳,不再出现Redis单节点内存、连接数暴增问题。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">4.4 client Redis集群并发调用优化</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">上游节点调用推送节点是通过clientId进行一致性hash调用的,推送节点会缓存clientInfo信息到本地,缓存时间7天,推送时,优先查询本地缓存,判断该client是否有效。对于重要且经常变更的信息,直接查询client Redis获取,这样导致推送高峰期,client Redis集群压力很大,并发高,cpu负载高。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">优化前推送节点操作缓存和client Redis流程图:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="1.0589887640449438" data-s="300,640" src="/upload/a37352ccceb71d9f9f36aae6189852dd.png" data-type="png" data-w="712" style=""></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">优化方案</strong>:对原有clientInfo缓存进行拆分,拆分成三个缓存,采取分级方案。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">cache还是保存原来clientInfo一些信息,这些信息是不经常变更的,缓存时间还是7天。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">cache1缓存clientInfo经常变更的信息,如:在线状态、cn地址等。</p></li> <li><p style="white-space: normal;box-sizing: border-box;">cache2缓存ci加密部分参数,这部分缓存只在需要加密时使用,变更频率没那么高,只有连接时才会变更。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">由于新增了缓存,需考虑缓存一致性问题,于是新增一下措施:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">1)推送缓存校验,调用broker节点,根据broker的返回信息,更新和清理本地缓存信息。broker新增不在线、aes不匹配错误码。下次推送或者重试时,会重新从Redis中加载,获取最新的client信息。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">2)根据手机端上行事件,connect和disconnect时,更新和清理本地缓存信息,下次推送或者重试时,会重新从Redis中加载,获取最新的client信息。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">整体流程:消息推送时,优先查询本地缓存,缓存不存在或者已过期,才从client Redis中加载。推送到broker时,根据broker返回信息,更新或失效缓存。上行,收到disconnect、connect事件,及时更新或失效缓存,再次推送时重新从client Redis加载。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">优化后推送节点操作缓存和client Redis流程图:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="1.2366522366522366" data-s="300,640" src="/upload/4527ff0019bb1791c31285262bdf39e6.png" data-type="png" data-w="693" style=""></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong>优化后效果</strong>:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">1)新增cache1缓存命中率52%,cache2缓存命中率30%。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">2)client Redis并发调用量减少了近20%。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-ratio="0.41639871382636656" data-s="300,640" src="/upload/c7a056a8cb339e89b47b10ab4bb8aba0.jpg" data-type="jpeg" data-w="622" style=""></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">3)高峰期Redis负载降低15%左右。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="202" data-backw="578" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="202" data-ratio="0.34962406015037595" data-s="300,640" src="/upload/f6adfc9e74e246c2f63858fa5264b550.jpg" data-type="jpeg" data-w="1064" style="width: 100%;height: auto;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="192" data-backw="578" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="192" data-ratio="0.3330179754020814" data-s="300,640" src="/upload/f52433580b63fa635b826e321c1138cc.jpg" data-type="jpeg" data-w="1057" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">五、总结</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">Redis由于其高并发性能和支持丰富的数据结构,在高并发系统中作为缓存中间件是较好的选择。当然,Redis是否能发挥高性能,还依赖业务是否真的理解和正确使用Redis。有如下几点需要注意:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="120" data-source-title=""> <section class="js_blockquote_digest"> <section> 1)由于Redis集群模式,每个主节点只负责一部分slot,业务在设计Redis key时要充分考虑key的随机性,均匀分散在Redis各节点上,同时应避免大key出现。另外,业务上应避免Redis请求热点问题,同一时刻请求打到少部分节点。 </section> </section> </blockquote> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="138" data-source-title=""> <section class="js_blockquote_digest"> <section> 2)Redis实际吞吐量还与请求Redis的包数据大小,网卡有关, <a target="_blank" href="http://www.redis.cn/topics/benchmarks.html" textvalue="官方文档" linktype="text" imgurl="" tab="outerlink" style="text-decoration: underline;" data-linktype="2">官方文档</a>有相关说明,单个包大小超过1000bytes时,性能会急剧下降。所以在使用Redis时应尽量避免大key。另外,最好根据实际业务场景和实际网络环境,带宽和网卡情况进行性能压测,对集群实际吞吐量做摸底。 </section> </section> </blockquote> <p><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">以我们client Redis集群为例:(仅供参考)</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">Network</strong>:10000Mb;</p></li> <li><p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">Redis Version</strong>:3.x;</p></li> <li><p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">Payload size</strong>:250bytes avg;</p></li> <li><p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">命令</strong>:hset(25%)、hmset(10%)、hget(60%)、hmget(5%);</p></li> <li><p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">性能情况</strong>:连接数5500、48000/s、cpu 95%左右。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="180" data-backw="578" data-cropselx1="0" data-cropselx2="578" data-cropsely1="0" data-cropsely2="180" data-ratio="0.3121495327102804" data-s="300,640" src="/upload/cdc709b5773e39d3cf2cdb2a41bbcea7.png" data-type="png" data-w="1070" style="width: 578px;height: 180px;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-cropselx1="0" data-cropselx2="448" data-cropsely1="0" data-cropsely2="322" data-ratio="0.71875" data-s="300,640" src="/upload/634b858132a9ae780a630bafb2c9fdbf.png" data-type="png" data-w="448" style="width: 231px;height: 166px;"></p> <p style="white-space: normal;box-sizing: border-box;"><br></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">Redis在实时分析这块支持较少,除了基本指标监控外,实时内存数据分析暂不支持。在实际业务场景下如果出现Redis瓶颈,往往监控数据也会缺失,定位问题较难。对Redis的数据分析只能依赖分析工具对Redis快照文件进行分析。因此,对Redis的使用依赖业务对Redis的充分认知,方案设计的时候充分考虑。同时根据业务场景对Redis做好性能压测,了解瓶颈在哪,做好监控和扩缩容准备。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="margin-right: 0%;margin-bottom: 20px;margin-left: 0%;box-sizing: border-box;" powered-by="xiumi.us"> <section style="display: inline-block;vertical-align: middle;width: 40%;box-sizing: border-box;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;box-sizing: border-box;" powered-by="xiumi.us"> <section style="border-top: 1px dotted rgb(90, 98, 114);box-sizing: border-box;"> <section> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> <section style="display: inline-block;vertical-align: middle;width: 20%;box-sizing: border-box;"> <section style="text-align: center;color: rgb(45, 66, 87);font-size: 11px;box-sizing: border-box;" powered-by="xiumi.us"> <p style="box-sizing: border-box;">END</p> </section> </section> <section style="display: inline-block;vertical-align: middle;width: 40%;box-sizing: border-box;"> <section style="margin-top: 0.5em;margin-bottom: 0.5em;box-sizing: border-box;" powered-by="xiumi.us"> <section style="border-top: 1px dotted rgb(90, 98, 114);box-sizing: border-box;"> <section> <svg viewbox="0 0 1 1" style="float:left;line-height:0;width:0;vertical-align:top;"></svg> </section> </section> </section> </section> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: left;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding-left: 1em;padding-right: 1em;display: inline-block;text-align: center;box-sizing: border-box;"> <span style="display: inline-block;padding: 0.3em 0.5em;border-radius: 0.5em;background-color: rgb(65, 94, 255);color: rgb(255, 255, 255);box-sizing: border-box;" title="" opera-tn-ra-cell="_$.pages:0.layers:0.comps:77.title1"><p style="box-sizing: border-box;">猜你喜欢</p></span> </section> <section style="border-width: 1px;border-style: solid;border-color: transparent;margin-top: -1em;padding: 20px 10px 10px;background-color: rgb(239, 239, 239);text-align: center;box-sizing: border-box;"> <section style="font-size: 14px;box-sizing: border-box;" powered-by="xiumi.us"> <ul class="list-paddingleft-2"> <li style="box-sizing: border-box;"><p style="text-align: left;box-sizing: border-box;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247492422&amp;idx=2&amp;sn=5a02b92ede62a94486c96cfd98a914c9&amp;chksm=ebdb93d4dcac1ac2a48a48f0bc42a57c7bef7d707a139d99de9471c9660122e813168dede58f&amp;scene=21#wechat_redirect" textvalue="vivo 云服务业务数据库数据压缩实践" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">Redis线程模型的前世今生</a></p></li> <li style="box-sizing: border-box;"><p style="text-align: left;box-sizing: border-box;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247492881&amp;idx=1&amp;sn=33780f813ebf2ce92d5738f1006d4659&amp;chksm=ebdb9583dcac1c9580105180262c5467d45b77cb9bc14bd8ffc9aeef96224429e6dcd8d4b95c&amp;scene=21#wechat_redirect" textvalue="vivo统一告警平台建设与实践" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">vivo 推送平台架构演进</a></p></li> <li style="box-sizing: border-box;"><p style="text-align: left;box-sizing: border-box;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzI4NjY4MTU5Nw==&amp;mid=2247492111&amp;idx=1&amp;sn=3bc3d6c6fc350b2157015a63a62f11c6&amp;chksm=ebdb929ddcac1b8b4c93fff98446a4a9582c2764fd86422f7a143d990a2a50d2c90885eb6f1e&amp;scene=21#wechat_redirect" textvalue="Hystrix 如何解决 ThreadLocal 信息丢失" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">Redis大集群扩容性能优化实践</a></p></li> </ul> </section> </section> </section> <section powered-by="xiumi.us" style="box-sizing: border-box;"> <section class="mp_profile_iframe_wrp"> <mpprofile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzI4NjY4MTU5Nw==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/4g5IMGibSxt45QXJZicZ9gaNU2mRSlvqhQd94MJ7oQh4QFj1ibPV66xnUiaKoicSatwaGXepL5sBDSDLEckicX1ttibHg/0?wx_fmt=png" data-nickname="vivo互联网技术" data-alias="vivoVMIC" data-signature="分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。" data-from="0"></mpprofile> </section> </section> </section>

Redis大集群扩容性能优化实践

作者:微信小助手

<section style="font-size: 15px;line-height: 1.9;letter-spacing: 0.75px;box-sizing: border-box;"> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <blockquote class="js_blockquote_wrap" data-type="2" data-url="" data-author-name="" data-content-utf8-length="28" data-source-title=""> <section class="js_blockquote_digest"> <section> 作者:vivo互联网数据库团队—Yuan Jianwei </section> </section> </blockquote> <p><br></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">一、背景</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">在现网环境,一些使用Redis集群的业务随着业务量的上涨,往往需要进行节点扩容操作。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">之前有了解到运维同学对一些节点数比较大的Redis集群进行扩容操作后,业务侧反映集群性能下降,具体表现在访问时延增长明显。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">某些业务对Redis集群访问时延比较敏感,例如现网环境对模型实时读取,或者一些业务依赖读取Redis集群的同步流程,会影响业务的实时流程时延。业务侧可能无法接受。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">为了找到这个问题的根因,我们对某一次的Redis集群迁移操作后的集群性能下降问题进行排查。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">1.1 问题描述</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">这一次具体的Redis集群问题的场景是:某一个Redis集群进行过扩容操作。业务侧使用Hiredis-vip进行Redis集群访问,进行MGET操作。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">业务侧感知到访问Redis集群的时延变高。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">1.2 现网环境说明</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">目前现网环境部署的Redis版本多数是3.x或者4.x版本;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">业务访问Redis集群的客户端品类繁多,较多的使用Jedis。本次问题排查的业务使用客户端<span style="font-size: 15px;letter-spacing: 0.75px;">H</span><span style="font-size: 15px;letter-spacing: 0.75px;">iredis</span>-vip进行访问;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">Redis集群的节点数比较大,规模是100+;</p></li> <li> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">集群之前存在扩容操作。</p> </section></li> </ul> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <br> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">1.3 观察现象</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">因为时延变高,我们从几个方面进行排查:</p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">带宽是否打满;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">CPU是否占用过高;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">OPS是否很高;</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">通过简单的监控排查,带宽负载不高。但是发现CPU表现异常:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="381" data-backw="578" data-ratio="0.659047619047619" data-s="300,640" src="/upload/a810ad641f8011441d0ae3e4434bbfe2.jpg" data-type="jpeg" data-w="1050" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">1.3.1 对比ops和CPU负载</strong></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">观察业务反馈使用的<span style="font-size: 15px;letter-spacing: 0.75px;">MGET</span>和CPU负载,我们找到了对应的监控曲线。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">从时间上分析,<span style="font-size: 15px;letter-spacing: 0.75px;">MGET</span>和CPU负载高并没有直接关联。<span style="letter-spacing: 0.75px;">业务侧反馈的是<span style="font-size: 15px;letter-spacing: 0.75px;">MGET</span>的时延普遍增高。</span><span style="letter-spacing: 0.75px;">此处看到<span style="font-size: 15px;letter-spacing: 0.75px;">MGET</span>的OPS和CPU负载是错峰的。</span></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="171" data-backw="567" src="/upload/5bf1f3699ae184e2b0a7d4adbeb4369d.png" data-cropx1="26.574394463667822" data-cropx2="1280" data-cropy1="19.930795847750865" data-cropy2="398.6159169550173" data-ratio="0.30185185185185187" data-s="300,640" src="https://mmbiz.qpic.cn/mmbiz_jpg/4g5IMGibSxt6N79JAdcPZibeIgZP4JMpNcibLjiaRURv5Yf2zJs3IC45t3ibOZ5oCI4o7s5SFUeqgIZeNlWq1dQPHfQ/640?wx_fmt=jpeg" data-type="jpeg" data-w="1080" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">此处可以暂时确定业务请求和CPU负载暂时没有直接关系,但是从曲线上可以看出:在同一个时间轴上,业务请求和cpu负载存在错峰的情况,两者间应该有间接关系。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong>1.3.2 对比Cluster指令OPS和CPU负载</strong></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">由于之前有运维侧同事有反馈集群进行过扩容操作,必然存在slot的迁移。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">考虑到业务的客户端一般都会使用缓存存放Redis集群的slot拓扑信息,因此怀疑C<span style="font-size: 15px;letter-spacing: 0.75px;">lust</span><span style="font-size: 15px;letter-spacing: 0.75px;">er</span>指令会和CPU负载存在一定联系。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">我们找到了当中确实有一些联系:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="674" data-backw="578" data-ratio="1.1671041119860017" data-s="300,640" src="/upload/aac1f0fe2e7c156833f159c6c1f81c5d.png" data-type="png" data-w="1143" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">此处可以明显看到:某个实例在执行<span style="font-size: 15px;letter-spacing: 0.75px;">C</span><span style="font-size: 15px;letter-spacing: 0.75px;">lust</span><span style="font-size: 15px;letter-spacing: 0.75px;">er</span>指令的时候,CPU的使用会明显上涨。</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">根据上述现象,大致可以进行一个简单的聚焦:</p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;">业务侧执行<span style="font-size: 15px;letter-spacing: 0.75px;">MGET</span>,因为一些原因执行了<span style="font-size: 15px;letter-spacing: 0.75px;">C</span><span style="font-size: 15px;letter-spacing: 0.75px;">lust</span><span style="font-size: 15px;letter-spacing: 0.75px;">er</span>指令;</p></li> <li><p style="white-space: normal;box-sizing: border-box;"><span style="font-size: 15px;letter-spacing: 0.75px;">C</span><span style="font-size: 15px;letter-spacing: 0.75px;">lust</span><span style="font-size: 15px;letter-spacing: 0.75px;">er</span>指令因为一些原因导致CPU占用较高影响其他操作;</p></li> <li><p style="white-space: normal;box-sizing: border-box;">怀疑<span style="font-size: 15px;letter-spacing: 0.75px;">C</span><span style="font-size: 15px;letter-spacing: 0.75px;">lust</span><span style="font-size: 15px;letter-spacing: 0.75px;">er</span>指令是性能瓶颈。</p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">同时,引申几个需要关注的问题:</p> <ul class="list-paddingleft-2" style="list-style-type: disc;"> <li><p style="white-space: normal;box-sizing: border-box;"><strong>为什么会有较多的<span style="font-size: 15px;letter-spacing: 0.75px;">C</span><span style="font-size: 15px;letter-spacing: 0.75px;">lust</span><span style="font-size: 15px;letter-spacing: 0.75px;">er</span>指令被执行?</strong></p></li> <li><p style="white-space: normal;box-sizing: border-box;"><strong>为什么<span style="font-size: 15px;letter-spacing: 0.75px;">C</span><span style="font-size: 15px;letter-spacing: 0.75px;">lust</span><span style="font-size: 15px;letter-spacing: 0.75px;">er</span>指令执行的时候CPU资源比较高?</strong></p></li> <li><p style="white-space: normal;box-sizing: border-box;"><strong>为什么节点规模大的集群迁移slot操作容易“中招”?</strong></p></li> </ul> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="margin-top: 10px;margin-bottom: 10px;text-align: center;box-sizing: border-box;" powered-by="xiumi.us"> <section style="padding: 3px;display: inline-block;border-bottom: 1px solid rgb(65, 94, 255);font-size: 17px;color: rgb(65, 94, 255);box-sizing: border-box;"> <p style="box-sizing: border-box;">二、问题排查</p> </section> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="font-size: 16px;color: rgb(70, 97, 246);box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">2.1 Redis热点排查</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">我们对一台现场出现了CPU负载高的Redis实例使用perf top进行简单的分析:</p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="text-align: center;"><img class="rich_pages wxw-img js_insertlocalimg" data-backh="70" data-backw="578" data-ratio="0.12109375" data-s="300,640" src="/upload/e5c4218d3697b29dd8c763bc25472641.png" data-type="png" data-w="1280" style="width: 100%;height: auto;"></p> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> <p style="white-space: normal;box-sizing: border-box;">从上图可以看出来,<strong>函数(ClusterReplyMultiBulkSlots)占用的CPU资源高达</strong> <strong>51.84%,存在异常</strong>。</p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><strong style="box-sizing: border-box;">2.1.1 ClusterReplyMultiBulkSlots实现原理</strong></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;"><br style="box-sizing: border-box;"></p> </section> <section style="box-sizing: border-box;" powered-by="xiumi.us"> <p style="white-space: normal;box-sizing: border-box;">我们对clusterReplyMultiBulkSlots函数进行分析:</p> <section class="code-snippet__fix code-snippet__js"> <ul class="code-snippet__line-index code-snippet__js"> </ul> <pre class="code-snippet__js" data-lang="objectivec"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">void</span> clusterReplyMultiBulkSlots(client *c) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/* Format: 1) 1) start slot</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 2) end slot</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 3) 1) master IP</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 2) master port</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 3) node ID</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 4) 1) replica IP</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 2) replica port</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * 3) node ID</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * ... continued until done</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> */</span></span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">int</span> num_masters = <span class="code-snippet__number">0</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">void</span> *slot_replylen = addDeferredMultiBulkLength(c);</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> dictEntry *de;</span></code><code><span class="code-snippet_outer"> dictIterator *di = dictGetSafeIterator(server.cluster-&gt;nodes);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">while</span>((de = dictNext(di)) != <span class="code-snippet__literal">NULL</span>) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/*注意:此处是对当前Redis节点记录的集群所有主节点都进行了遍历*/</span></span></code><code><span class="code-snippet_outer"> clusterNode *node = dictGetVal(de);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">int</span> j = <span class="code-snippet__number">0</span>, start = <span class="code-snippet__number">-1</span>;</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/* Skip slaves (that are iterated when producing the output of their</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * master) and masters not serving any slot. */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/*跳过备节点。备节点的信息会从主节点侧获取。*/</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (!nodeIsMaster(node) || node-&gt;numslots == <span class="code-snippet__number">0</span>) <span class="code-snippet__keyword">continue</span>;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">for</span> (j = <span class="code-snippet__number">0</span>; j &lt; <span class="code-snippet__built_in">CLUSTER_SLOTS</span>; j++) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/*注意:此处是对当前节点中记录的所有slot进行了遍历*/</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">int</span> bit, i;</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/*确认当前节点是不是占有循环终端的slot*/</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> ((bit = clusterNodeGetSlotBit(node,j)) != <span class="code-snippet__number">0</span>) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (start == <span class="code-snippet__number">-1</span>) start = j;</span></code><code><span class="code-snippet_outer"> }</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/*简单分析,此处的逻辑大概就是找出连续的区间,是的话放到返回中;不是的话继续往下递归slot。</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> 如果是开始的话,开始一个连续区间,直到和当前的不连续。*/</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (start != <span class="code-snippet__number">-1</span> &amp;&amp; (!bit || j == <span class="code-snippet__built_in">CLUSTER_SLOTS</span><span class="code-snippet__number">-1</span>)) {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">int</span> nested_elements = <span class="code-snippet__number">3</span>; <span class="code-snippet__comment">/* slots (2) + master addr (1). */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">void</span> *nested_replylen = addDeferredMultiBulkLength(c);</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (bit &amp;&amp; j == <span class="code-snippet__built_in">CLUSTER_SLOTS</span><span class="code-snippet__number">-1</span>) j++;</span></code><code><span class="code-snippet_outer"> </span></code><code><span class="code-snippet_outer"> <span class="code-snippet__comment">/* If slot exists in output map, add to it's list.</span></span></code><code><span class="code-snippet_outer"><span class="code-snippet__comment"> * else, create a new output map for this slot */</span></span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">if</span> (start == j<span class="code-snippet__number">-1</span>) {</span></code><code><span class="code-snippet_outer"> addReplyLongLong(c, start); <span class="code-snippet__comment">/* only one slot; low==high */</span></span></code><code><span class="code-snippet_outer"> addReplyLongLong(c, start);</span></code><code><span class="code-snippet_outer"> } <span class="code-snippet__keyword">else</span>