作者:微信小助手
<p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">MySQL主从同步是一种数据复制技术,它允许数据从一个数据库服务器(主服务器)自动同步到一个或多个数据库服务器(从服务器)。这种技术主要用于实现读写分离、提升数据库性能、容灾恢复以及数据冗余备份等目的。下面将详细解析MySQL主从同步的一致性问题,并通过案例分析其工作原理。</p> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><img class="rich_pages wxw-img" data-imgfileid="100000445" data-ratio="0.559375" src="/upload/12f838e0c4ae9e4ec195ed601fd635b6.jpg" data-type="jpeg" data-w="1600" style="vertical-align: middle;border-width: 0px;border-style: initial;border-color: initial;"></p> <h3 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);font-size: 18px;line-height: 24px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">一、MySQL主从同步一致性详解</h3> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">1. 主从同步原理</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">MySQL主从同步基于二进制日志(binlog)进行。主库将数据的变更写入binlog日志,从库通过IO线程读取这些变更,并写入到本地的中继日志(relay log)中。之后,从库的SQL线程会读取中继日志中的SQL语句并执行,从而保持与主库数据的一致性。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">2. 同步模式</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">MySQL主从同步有三种主要模式:</p> <ul style="margin-top: 15px;margin-bottom: 15px;padding-left: 30px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li><p><span style="font-weight: 700;">异步复制</span>:主库执行完事务后立即返回结果给客户端,不关心从库是否接收并处理。这是MySQL的默认复制模式,但可能导致数据不一致。</p></li> <li><p><span style="font-weight: 700;">半同步复制</span>:主库执行完事务后,会等待至少一个从库接收到binlog并写入relay log后才返回结果给客户端。这种方式提高了数据安全性,但会增加延迟。</p></li> <li><p><span style="font-weight: 700;">全同步复制(组复制)</span>:主库执行完事务后,会等待所有从库都执行完该事务后才返回结果给客户端。这种方式保证了数据的一致性,但性能较低。</p></li> </ul> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">3. 数据一致性问题</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">主从同步中可能遇到的数据一致性问题主要包括:</p> <ul style="margin-top: 15px;margin-bottom: 15px;padding-left: 30px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li><p><span style="font-weight: 700;">延迟问题</span>:由于网络延迟、从库性能不足或大事务等原因,从库的数据可能会滞后于主库。</p></li> <li><p><span style="font-weight: 700;">数据丢失</span>:在主库发生故障时,如果数据尚未同步到从库,将导致数据丢失。</p></li> <li><p><span style="font-weight: 700;">数据冲突</span>:在复杂的同步场景中,如多主复制或级联复制,可能出现数据冲突。</p></li> </ul> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">4. 解决方案</h4> <ul style="margin-top: 15px;margin-bottom: 15px;padding-left: 30px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li><p><span style="font-weight: 700;">优化同步模式</span>:根据业务需求和数据安全性要求选择合适的同步模式。</p></li> <li><p><span style="font-weight: 700;">优化从库性能</span>:提升从库硬件配置、优化SQL查询等以减少延迟。</p></li> <li><p><span style="font-weight: 700;">使用数据库中间件</span>:如canal、otter等,实现读写分离和数据一致性校验。</p></li> <li><p><span style="font-weight: 700;">缓存记录写key法</span>:通过缓存记录写操作的关键信息,在读取时判断是否需要从主库获取最新数据。</p></li> </ul> <h3 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);font-size: 18px;line-height: 24px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">二、案例分析</h3> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">以下是一个简单的MySQL主从同步案例分析:</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">环境准备</h4> <ul style="margin-top: 15px;margin-bottom: 15px;padding-left: 30px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li><p>主服务器IP:192.168.4.51</p></li> <li><p>从服务器IP:192.168.4.52</p></li> </ul> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">配置主服务器</h4> <ol style="margin-top: 15px;margin-bottom: 15px;padding-left: 30px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li><p style="margin-bottom: 15px;">启用binlog日志并设置server_id。</p><pre style="margin-bottom: 15px;padding: 10px;background: rgb(39, 40, 34);font-variant-numeric: normal;font-variant-east-asian: normal;font-stretch: normal;font-size: 12px;line-height: 15px;font-family: Consolas, monospace, serif;color: rgb(248, 248, 242);tab-size: 4;overflow: auto;border-width: 1px;border-style: solid;border-color: rgb(221, 221, 221);border-radius: 3px;"><code style="display: block;padding: 0.5em;background: rgb(43, 43, 43);color: rgb(169, 183, 198);"><span style="color: rgb(249, 38, 114);"><span style="color: rgb(255, 198, 109);">[</span></span><span style="color: rgb(255, 198, 109);">mysqld</span><span style="color: rgb(249, 38, 114);"><span style="color: rgb(255, 198, 109);">]</span></span><br><span style="color: rgb(248, 248, 242);">server_id</span><span style="color: rgb(249, 38, 114);">=</span><span style="color: rgb(174, 129, 255);">51</span><br><span>log-bin</span><span style="color: rgb(249, 38, 114);">=</span><span><span style="color: rgb(204, 120, 50);">master51</span></span><br></code></pre></li> <li><p style="margin-bottom: 15px;">授权从服务器访问主服务器的binlog。</p><pre style="margin-bottom: 15px;padding: 10px;background: rgb(39, 40, 34);font-variant-numeric: normal;font-variant-east-asian: normal;font-stretch: normal;font-size: 12px;line-height: 15px;font-family: Consolas, monospace, serif;color: rgb(248, 248, 242);tab-size: 4;overflow: auto;border-width: 1px;border-style: solid;border-color: rgb(221, 221, 221);border-radius: 3px;"><code style="display: block;padding: 0.5em;background: rgb(43, 43, 43);color: rgb(169, 183, 198);"><span style="color: rgb(102, 217, 239);"><span><span style="color: rgb(204, 120, 50);">GRANT</span></span></span><span> </span><span style="color: rgb(248, 248, 242);">REPLICATION</span><span> </span><span style="color: rgb(248, 248, 242);">SLAVE</span><span> </span><span style="color: rgb(102, 217, 239);"><span><span style="color: rgb(204, 120, 50);">ON</span></span></span><span> </span><span style="color: rgb(249, 38, 114);">*</span><span style="color: rgb(248, 248, 242);">.</span><span style="color: rgb(249, 38, 114);">*</span><span> </span><span style="color: rgb(102, 217, 239);"><span><span style="color: rgb(204, 120, 50);">TO</span></span></span><span> </span><span style="color: rgb(230, 219, 116);"><span><span style="color: rgb(106, 135, 89);">'repluser'</span></span></span><span style="color: rgb(249, 38, 114);">@</span><span style="color: rgb(230, 219, 116);"><span><span style="color: rgb(106, 135, 89);">'192.168.4.52'</span></span></span><span> </span><span style="color: rgb(248, 248, 242);">IDENTIFIED</span><span> </span><span style="color: rgb(102, 217, 239);"><span><span style="color: rgb(204, 120, 50);">BY</span></span></span><span> </span><span style="color: rgb(230, 219, 116);"><span><span style="color: rgb(106, 135, 89);">'password'</span></span></span><span style="color: rgb(248, 248, 242);">;</span><br></code></pre></li> <li><p style="margin-bottom: 15px;">查看并记录binlog的文件名和位置。</p><pre style="margin-bottom: 15px;padding: 10px;background: rgb(39, 40, 34);font-variant-numeric: normal;font-variant-east-asian: normal;font-stretch: normal;font-size: 12px;line-height: 15px;font-family: Consolas, monospace, serif;color: rgb(248, 248, 242);tab-size: 4;overflow: auto;border-width: 1px;border-style: solid;border-color: rgb(221, 221, 221);border-radius: 3px;"><code style="display: block;padding: 0.5em;background: rgb(43, 43, 43);color: rgb(169, 183, 198);"><span style="color: rgb(102, 217, 239);"><span><span style="color: rgb(204, 120, 50);">SHOW</span></span></span><span> </span><span style="color: rgb(248, 248, 242);">MASTER</span><span> </span><span style="color: rgb(248, 248, 242);">STATUS</span><span style="color: rgb(248, 248, 242);">;</span><br></code></pre></li> </ol> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">配置从服务器</h4> <ol style="margin-top: 15px;margin-bottom: 15px;padding-left: 30px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li><p style="margin-bottom: 15px;">设置server_id并启动binlog(可选,如果需要配置从从复制)。</p><pre style="margin-bottom: 15px;padding: 10px;background: rgb(39, 40, 34);font-variant-numeric: normal;font-variant-east-asian: normal;font-stretch: normal;font-size: 12px;line-height: 15px;font-family: Consolas, monospace, serif;color: rgb(248, 248, 242);tab-size: 4;overflow: auto;border-width: 1px;border-style: solid;border-color: rgb(221, 221, 221);border-radius: 3px;"><code style="display: block;padding: 0.5em;background: rgb(43, 43, 43);color: rgb(169, 183, 198);"><span style="color: rgb(249, 38, 114);"><span style="color: rgb(255, 198, 109);">[</span></span><span style="color: rgb(255, 198, 109);">mysqld</span><span style="color: rgb(249, 38, 114);"><span style="color: rgb(255, 198, 109);">]</span></span><br><span style="color: rgb(248, 248, 242);">server_id</span><span style="color: rgb(249, 38, 114);">=</span><span style="color: rgb(174, 129, 255);">52</span><br><span>log-bin</span><span style="color: rgb(249, 38, 114);">=</span><span><span style="color: rgb(204, 120, 50);">slave52 </span></span><span style="color: rgb(117, 113, 94);"><span><span style="color: rgb(204, 120, 50);"># 如果需要配置从从复制</span></span></span><br></code></pre></li> <li><p style="margin-bottom: 15px;">指定主服务器信息并启动slave进程。</p><pre style="margin-bottom: 15px;padding: 10px;background: rgb(39, 40, 34);font-variant-numeric: normal;font-variant-east-asian: normal;font-stretch: normal;font-size: 12px;line-height: 15px;font-family: Consolas, monospace, serif;color: rgb(248, 248, 242);tab-size: 4;overflow: auto;border-width: 1px;border-style: solid;border-color: rgb(221, 221, 221);border-radius: 3px;"><code style="display: block;padding: 0.5em;background: rgb(43, 43, 43);color: rgb(169, 183, 198);"><span style="color: rgb(248, 248, 242);">CHANGE</span> <span style="color: rgb(248, 248, 242);">MASTER</span> <span style="color: rgb(102, 217, 239);"><span style="color: rgb(204, 120, 50);">TO</span></span><br><span style="color: rgb(248, 248, 242);">MASTER_HOST</span><span style="color: rgb(249, 38, 114);">=</span><span style="color: rgb(230, 219, 116);"><span style="color: rgb(106, 135, 89);">'192.168.4.51'</span></span><span style="color: rgb(248, 248, 242);">,</span><br><span style="color: rgb(248, 248, 242);">MASTER_USER</span><span style="color: rgb(249, 38, 114);">=</span><span style="color: rgb(230, 219, 116);"><span style="color: rgb(106, 135, 89);">'repluser'</span></span><span style="color: rgb(248, 248, 242);">,</span><br><span style="color: rgb(248, 248, 242);">MASTER_PASSWORD</span><span style="color: rgb(249, 38, 114);">=</span><span style="color: rgb(230, 219, 116);"><span style="color: rgb(106, 135, 89);">'password'</span></span><span style="color: rgb(248, 248, 242);">,</span><br><span style="color: rgb(248, 248, 242);">MASTER_LOG_FILE</span><span style="color: rgb(249, 38, 114);">=</span><span style="color: rgb(230, 219, 116);"><span style="color: rgb(106, 135, 89);">'master51.000001'</span></span><span style="color: rgb(248, 248, 242);">,</span><br><span style="color: rgb(248, 248, 242);">MASTER_LOG_POS</span><span style="color: rgb(249, 38, 114);">=</span><span style="color: rgb(174, 129, 255);">442</span><span style="color: rgb(248, 248, 242);">;</span><br><span style="color: rgb(102, 217, 239);">START</span> <span style="color: rgb(248, 248, 242);">SLAVE</span><span style="color: rgb(248, 248, 242);">;</span><br></code></pre></li> <li><p style="margin-bottom: 15px;">查看slave状态以确认同步是否成功。</p><pre style="margin-bottom: 15px;padding: 10px;background: rgb(39, 40, 34);font-variant-numeric: normal;font-variant-east-asian: normal;font-stretch: normal;font-size: 12px;line-height: 15px;font-family: Consolas, monospace, serif;color: rgb(248, 248, 242);tab-size: 4;overflow: auto;border-width: 1px;border-style: solid;border-color: rgb(221, 221, 221);border-radius: 3px;"><code style="display: block;padding: 0.5em;background: rgb(43, 43, 43);color: rgb(169, 183, 198);"><span style="color: rgb(102, 217, 239);">SHOW</span> <span style="color: rgb(248, 248, 242);">SLAVE</span> <span style="color: rgb(248, 248, 242);">STATUS</span><span style="color: rgb(150, 0, 80);background-color: rgb(30, 0, 16);"><span style="color: rgb(106, 135, 89);">\</span></span><span style="color: rgb(102, 217, 239);"><span style="color: rgb(106, 135, 89);">G</span></span><br></code></pre></li> </ol> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">验证配置</h4> <ol style="margin-top: 15px;margin-bottom: 15px;padding-left: 30px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);" class="list-paddingleft-1"> <li><p>在主服务器上创建数据库和表,并插入数据。</p></li> <li><p>在从服务器上查询相同的数据,验证数据是否一致。</p></li> </ol> <h3 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);font-size: 18px;line-height: 24px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">三、影响主从同步一致性的因素</h3> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">1.网络延迟</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">数据传输延迟:主库和从库之间的网络状况不佳时,从库获取主库二进制日志的时间会增加,可能导致从库的数据更新落后于主库。 网络中断:如果网络出现中断,从库在一段时间内无法获取主库的二进制日志,当网络恢复后,可能会出现数据不一致的情况。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">2.主从配置差异</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">存储引擎不同:如果主库和从库使用不同的存储引擎,可能会导致某些操作在主库和从库上的执行结果不同。 字符集不同:字符集的不一致可能会导致数据在存储和传输过程中出现乱码等问题,影响数据的一致性。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">3.事务处理</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">非事务性操作:在主库上执行非事务性操作(如 MyISAM 存储引擎下的操作)时,如果在操作过程中主库出现故障,可能会导致主从数据不一致。 事务提交顺序:如果主库上的事务提交顺序与从库上的事务重放顺序不同,也可能会导致数据不一致。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">4.锁机制</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">锁等待:在主库上,如果一个事务长时间持有锁,可能会导致从库在重放相关操作时出现锁等待,从而影响同步的及时性和一致性。 锁冲突:主库和从库上的锁冲突可能会导致某些操作无法正常执行,进而影响数据一致性。</p> <h3 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);font-size: 18px;line-height: 24px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">四、保证主从同步一致性的方法</h3> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">1.优化网络环境</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">使用高速网络:尽量使用高速、稳定的网络连接主库和从库,减少网络延迟。 网络监控与维护:定期监控网络状况,及时发现并解决网络问题,如网络拥塞、丢包等。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">2.统一主从配置</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">存储引擎统一:确保主库和从库使用相同的存储引擎,避免因存储引擎差异导致的问题。 字符集统一:在配置主从库时,统一字符集,保证数据在传输和存储过程中的准确性。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">3.事务处理优化</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">使用事务性存储引擎:如 InnoDB 存储引擎,它支持事务的 ACID 属性,能更好地保证数据的一致性。 事务提交顺序控制:在应用程序设计中,尽量保证事务提交的顺序在主从库上是一致的。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">4.合理使用锁机制</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">减少锁持有时间:在主库上,尽量减少事务持有锁的时间,避免从库出现长时间的锁等待。 避免锁冲突:合理设计数据库的锁策略,避免主从库上出现锁冲突。</p> <h3 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);font-size: 18px;line-height: 24px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">五、案例分析</h3> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">1.网络延迟导致的主从数据不一致</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">案例场景:</span><br>公司的数据库采用了主从复制架构,主库和从库位于不同的数据中心,之间通过广域网连接。在业务高峰期,网络出现了严重的拥塞,导致从库获取主库二进制日志的速度非常缓慢。</p> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">问题表现:</span><br>用户在主库上插入了一条新数据,但在从库上查询时,该数据在一段时间内并未出现。</p> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">解决方法:</span><br>优化网络连接,增加网络带宽,缓解网络拥塞。 调整主从复制的参数,如增加从库获取二进制日志的超时时间,避免因网络延迟导致复制中断。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">2.事务处理不当导致的主从数据不一致</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">案例场景:</span><br>在一个电商系统中,主库使用事务来处理订单的生成和库存的更新。在某些情况下,事务在主库上执行过程中出现了异常,但事务并未完全回滚。从库在复制这些操作时,由于事务的不完整性,导致数据不一致。</p> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">问题表现:</span><br>订单状态显示已支付,但库存并未减少。</p> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">解决方法:</span><br>优化事务处理代码,确保事务在出现异常时能够正确回滚。 在从库上增加数据校验机制,定期检查主从数据的一致性,发现问题及时修复。</p> <h4 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);line-height: 22px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">3.锁机制导致的主从数据不一致</h4> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">案例场景:</span><br>在一个高并发的数据库应用中,主库上存在大量的并发事务,这些事务在操作某些数据时需要获取锁。由于锁的竞争激烈,导致从库在重放相关操作时出现锁等待,进而影响了主从同步的一致性。</p> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">问题表现:</span><br>从库的数据更新明显落后于主库,在某些情况下,从库上的查询结果与主库不一致。</p> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);"><span style="font-weight: 700;">解决方法:</span><br>优化事务的并发控制策略,减少锁的竞争。 对频繁被锁的数据进行分区,降低锁冲突的概率。</p> <h3 style="margin-top: 25px;margin-bottom: 15px;font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;color: rgb(68, 68, 68);font-size: 18px;line-height: 24px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">六、总结</h3> <p style="margin-top: 15px;margin-bottom: 15px;color: rgb(68, 68, 68);font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;font-size: 14px;letter-spacing: normal;text-align: start;white-space: normal;background-color: rgb(255, 255, 255);">主从同步一致性是 MySQL 数据库架构中一个至关重要的问题。影响主从同步一致性的因素有很多,包括网络延迟、主从配置差异、事务处理和锁机制等。通过优化网络环境、统一主从配置、事务处理优化和合理使用锁机制等方法,可以有效地保证主从同步的一致性。在实际应用中,需要根据具体的案例场景进行分析和处理,及时发现并解决问题,确保数据库系统的稳定运行。</p> <p><br></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<section style="font-size: 14px;letter-spacing: 1px;line-height: 2;"> <section style="font-size: 16px;color: rgb(62, 62, 62);"> <p style="text-wrap: wrap;">数据架构是指组织和管理数据的方式,包括数据的存储、处理、流动和使用方式。它涉及到如何设计和构建数据模型、数据库系统、数据交换机制等,以确保数据的有效性、安全性和可用性。数据架构的目标是支持业务需求、提高数据的质量和一致性,并促进数据的共享和集成。</p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><br></p> <section style="text-align: left;justify-content: flex-start;display: flex;flex-flow: row;margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;vertical-align: bottom;width: auto;align-self: flex-end;flex: 0 0 auto;background-color: rgb(33, 120, 240);min-width: 5%;height: auto;padding: 8px;border-right: 0px solid rgba(255, 255, 255, 0);border-top-right-radius: 0px;margin-right: 13px;"> <section style="text-align: justify;font-size: 11px;color: rgb(238, 227, 208);"> <p style="text-wrap: wrap;"><br></p> </section> </section> <section style="display: inline-block;vertical-align: bottom;width: auto;min-width: 5%;flex: 0 0 auto;height: auto;align-self: flex-end;border-bottom: 3px solid rgb(33, 120, 240);border-bottom-right-radius: 0px;background-color: rgba(255, 255, 255, 0);"> <section style="margin-bottom: 6px;"> <section style="text-align: justify;font-size: 17px;"> <p style="text-wrap: wrap;"><strong>什么是数据架构?</strong></p> </section> </section> </section> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">数据架构描述如何管理从收集到转换、分发和使用的数据。它为数据及其在数据存储系统中流动的方式设定了蓝图。它是数据处理操作和人工智能 (AI) 应用程序的基础。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">数据架构的设计应该由业务需求驱动,数据架构师和数据工程师使用这些需求来定义相应的数据模型以及支持它的底层数据结构。这些设计通常有助于满足业务需求,例如报告或数据科学计划。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">随着物联网 (IoT) 等新兴技术的出现,新的数据源不断涌现,良好的数据架构可以确保数据易于管理且具有利用价值,从而支持数据生命周期管理。更具体地说,它可以避免冗余数据存储,通过清理和重复数据删除来<strong>提高数据质量</strong>,并支持新的应用程序。现代数据架构还提供了跨域(例如部门或地理区域之间)集成数据的机制,<strong>打破了数据孤岛</strong>,因而消除了将所有数据存储在同一地方所带来的巨大复杂性。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">现代数据架构经常利用云平台来管理和处理数据。虽然它的成本更高,但它的计算可扩展性使重要数据处理任务能够快速完成。存储可扩展性还有助于应对不断增长的数据量,并确保所有相关数据都可用。</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000569" data-ratio="0.6981481481481482" data-s="300,640" src="/upload/9f3baaa8df99d72b6ff699626c0073a2.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><br></p> <section style="text-align: left;justify-content: flex-start;display: flex;flex-flow: row;margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;vertical-align: bottom;width: auto;align-self: flex-end;flex: 0 0 auto;background-color: rgb(33, 120, 240);min-width: 5%;height: auto;padding: 8px;border-right: 0px solid rgba(255, 255, 255, 0);border-top-right-radius: 0px;margin-right: 13px;"> <section style="text-align: justify;font-size: 11px;color: rgb(238, 227, 208);"> <p style="text-wrap: wrap;"><br></p> </section> </section> <section style="display: inline-block;vertical-align: bottom;width: auto;min-width: 5%;flex: 0 0 auto;height: auto;align-self: flex-end;border-bottom: 3px solid rgb(33, 120, 240);border-bottom-right-radius: 0px;background-color: rgba(255, 255, 255, 0);"> <section style="margin-bottom: 6px;"> <section style="text-align: justify;font-size: 17px;"> <p style="text-wrap: wrap;"><strong>数据架构的发展历程</strong></p> </section> </section> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>1. 早期阶段(1960年代-1970年代)</strong></p> <p style="text-wrap: wrap;"><strong>文件系统时代:</strong>早期的数据存储主要依赖于文件系统,数据存储在平面文件中。数据管理和处理通常是应用程序内部的一部分,这种方式很难实现数据共享和整合。</p> <p style="text-wrap: wrap;"><strong>层次模型和网状模型:</strong>1960年代末和1970年代初,出现了层次数据模型(如IBM的Information Management System)和网状数据模型(如CODASYL DBTG模型)。这些模型允许更复杂的数据关系,但仍然较为复杂且不够灵活。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>2. 关系数据库的兴起(1970年代-1980年代)</strong></p> <p style="text-wrap: wrap;"><strong>关系模型:</strong>1970年,Edgar Codd提出了关系模型,它用数学理论来描述数据结构和操作方法。关系模型使得数据组织更加灵活,并支持使用结构化查询语言(SQL)来进行数据操作。</p> <p style="text-wrap: wrap;"><strong>数据库管理系统(DBMS):</strong>随着关系模型的普及,关系数据库管理系统(如IBM的DB2、Oracle、MySQL)迅速发展,成为企业数据管理的主要工具。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>3. 数据仓库和数据挖掘(1980年代-1990年代)</strong></p> <p style="text-wrap: wrap;"><strong>数据仓库:</strong>数据仓库概念由Bill Inmon和Ralph Kimball在1990年代提出,它涉及将来自不同数据源的数据整合到一个中央仓库中,以支持决策分析。数据仓库设计强调数据整合、历史数据的保存以及查询和报告的高效性。</p> <p style="text-wrap: wrap;"><strong>数据挖掘:</strong>数据挖掘技术开始得到关注,用于从大量数据中提取有价值的信息和模式。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>4. 大数据和NoSQL(2000年代)</strong></p> <p style="text-wrap: wrap;"><strong>大数据:</strong>随着互联网和社交媒体的兴起,数据的规模和多样性大幅增加,传统的关系数据库面临挑战。大数据技术(如Hadoop、Spark)应运而生,用于处理和分析海量数据。</p> <p style="text-wrap: wrap;"><strong>NoSQL数据库:</strong>为了处理非结构化数据和高并发请求,NoSQL数据库(如MongoDB、Cassandra)获得了广泛应用。NoSQL数据库不依赖于传统的关系模型,支持更灵活的数据存储和访问模式。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>5. 数据湖和云计算(2010年代至今)</strong></p> <p style="text-wrap: wrap;"><strong>数据湖:</strong>数据湖的概念强调将各种类型的数据(结构化、半结构化、非结构化)以原始格式存储在一个集中式存储系统中。数据湖支持灵活的数据访问和分析,常与大数据技术结合使用。</p> <p style="text-wrap: wrap;"><strong>云数据架构:</strong>云计算的普及使得数据存储和处理的模式发生了变化。云数据库(如Amazon RDS、Google BigQuery)和数据仓库服务(如Snowflake)提供了弹性、可扩展的解决方案,使得企业能够以更低的成本管理和分析数据。</p> <p style="text-wrap: wrap;"><br></p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000570" data-ratio="0.6240963855421687" data-s="300,640" src="/upload/289f42a8949d86ec73d9e6ca92929ce3.png" data-type="png" data-w="830" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;">数据架构的发展反映了技术的进步和业务需求的变化。从最初的简单文件存储,到复杂的关系模型、数据仓库、大数据技术,再到现代的云计算和数据湖架构,数据架构不断演变,以应对不断增长的数据量和复杂的数据处理需求。</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="text-align: left;justify-content: flex-start;display: flex;flex-flow: row;margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;vertical-align: bottom;width: auto;align-self: flex-end;flex: 0 0 auto;background-color: rgb(33, 120, 240);min-width: 5%;height: auto;padding: 8px;border-right: 0px solid rgba(255, 255, 255, 0);border-top-right-radius: 0px;margin-right: 13px;"> <section style="text-align: justify;font-size: 11px;color: rgb(238, 227, 208);"> <p style="text-wrap: wrap;"><br></p> </section> </section> <section style="display: inline-block;vertical-align: bottom;width: auto;min-width: 5%;flex: 0 0 auto;height: auto;align-self: flex-end;border-bottom: 3px solid rgb(33, 120, 240);border-bottom-right-radius: 0px;background-color: rgba(255, 255, 255, 0);"> <section style="margin-bottom: 6px;"> <section style="text-align: justify;font-size: 17px;"> <p style="text-wrap: wrap;"><strong>流行的企业架构 </strong></p> </section> </section> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;">数据架构可以借鉴流行的企业架构框架,包括 TOGAF、DAMA-DMBOK 2 和 Zachman 企业架构框架。</p> <p style="text-wrap: wrap;"><br></p> <p style="text-align: left;text-wrap: wrap;"><strong>1.The Open Group Architecture Framework</strong><strong>(TOGAF)</strong></p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;">这个企业架构方法由 The Open Group 于 1995 年开发,IBM 是该组织的白金会员。</p> <p style="text-wrap: wrap;">该架构有四大支柱:</p> <p style="text-wrap: wrap;"><strong>业务架构</strong>,它定义企业的组织结构、业务策略和流程。</p> <p style="text-wrap: wrap;"><strong>数据架构</strong>,它描述概念、逻辑和物理数据资产,以及这些资产在整个生命周期中的存储和管理方式。</p> <p style="text-wrap: wrap;"><strong>应用程序架构</strong>,它代表应用程序系统,以及这些系统与关键业务流程以及相互之间的关系。</p> <p style="text-wrap: wrap;"><strong>技术架构</strong>,它描述支持任务关键型应用程序所需的技术基础架构(硬件、软件和网络)。</p> <p style="text-wrap: wrap;">因此,TOGAF 为设计和实现企业的 IT 架构(包括其数据架构)提供了一个完整的框架。</p> <p style="text-wrap: wrap;"><br></p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000572" data-ratio="0.8118393234672304" data-s="300,640" src="/upload/97ba8b66c139e5cf61a9e2499d793bb7.png" data-type="png" data-w="946" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>2.DAMA-DMBOK 2</strong></p> <p style="text-wrap: wrap;"><strong><br></strong></p> <p style="text-wrap: wrap;text-align: left;">DAMA International 最初成立时的名称是 Data Management Association International,是一个致力于推进数据和信息管理的非营利组织。其数据管理知识体系 DAMA-DMBOK 2 涵盖数据架构以及治理和道德、数据建模和设计、存储、安全和集成。</p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000568" data-ratio="0.8169556840077071" data-s="300,640" src="/upload/68c4b28fc1828c48aa18f9f227280109.png" data-type="png" data-w="519" style="vertical-align: middle;width: 100%;"> </section> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><br></p> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>3.Zachman 企业架构框架</strong></p> <p style="text-wrap: wrap;"><strong><br></strong></p> <p style="text-wrap: wrap;text-align: left;">该框架最初由 IBM 的 John Zachman 于 1987 年开发,使用一个从上下文到详细信息的六层矩阵,映射了诸如为什么、怎么做和是什么等六个问题。它提供了一种正式的数据组织和分析方式,但不包括具体方法。</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000571" data-ratio="0.6453634085213033" data-s="300,640" src="/upload/a5691405a126dc1b3c28ff003e42e7e8.png" data-type="png" data-w="798" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><br></p> <section style="text-align: left;justify-content: flex-start;display: flex;flex-flow: row;margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;vertical-align: bottom;width: auto;align-self: flex-end;flex: 0 0 auto;background-color: rgb(33, 120, 240);min-width: 5%;height: auto;padding: 8px;border-right: 0px solid rgba(255, 255, 255, 0);border-top-right-radius: 0px;margin-right: 13px;"> <section style="text-align: justify;font-size: 11px;color: rgb(238, 227, 208);"> <p style="text-wrap: wrap;"><br></p> </section> </section> <section style="display: inline-block;vertical-align: bottom;width: auto;min-width: 5%;flex: 0 0 auto;height: auto;align-self: flex-end;border-bottom: 3px solid rgb(33, 120, 240);border-bottom-right-radius: 0px;background-color: rgba(255, 255, 255, 0);"> <section style="margin-bottom: 6px;"> <section style="text-align: justify;font-size: 17px;"> <p style="text-wrap: wrap;"><strong>数据管理系统及数据架构的类型 </strong></p> </section> </section> </section> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><br></p> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>1. 数据仓库</strong></p> <p style="text-wrap: wrap;">数据仓库将来自企业内不同关系数据源的数据聚合到单个集中的统一存储库中。提取后,数据流经 ETL 数据管道,经过各种数据转换,才能满足预定义数据模型的需求。一旦加载到数据仓库中,数据就可以支持不同的商业智能 (BI) 和数据科学应用程序。</p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000574" data-ratio="0.5222222222222223" data-s="300,640" src="/upload/0cd6f6562b5e40085425a498fb2a8213.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><br></p> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>2.数据集市</strong></p> <p style="text-wrap: wrap;">数据集市是一个有针对性的数据仓库版本,它包含一个较小的数据子集,这些数据对组织内的单个团队或选定用户组很重要且是必需的。由于数据集市包含较小的数据子集,因此在使用更广泛的数据仓库数据集时,数据集市使部门或业务线能够更快地发现更有针对性的洞察。最初创建数据集市的目的是应对组织在 20 世纪 90 年代建立数据仓库的困难。当时集成来自整个组织的数据需要进行大量手动编码,而且非常耗时。与集中式数据仓库相比,数据集市的范围更有限,使其实现起来更容易且更快速。</p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000575" data-ratio="0.5625" data-s="300,640" src="/upload/5dc9330f79c03e4b8e2f12a80f07b30e.png" data-type="png" data-w="640" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>3. 数据湖</strong></p> <p style="text-wrap: wrap;">数据仓库存储已处理的数据,而数据湖存储原始数据,通常为 PB 级别。数据湖可以存储结构化和非结构化数据,这使其与其他数据存储库不同。这种灵活的存储需求对于数据科学家、数据工程师和开发人员尤其有用。最初创建数据湖的目的是应对数据仓库无法处理数量、速度和种类不断增加的大数据的情况。虽然数据湖比数据仓库慢,但它们的价格也更低廉,因为在采集之前几乎不需要数据准备。</p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000577" data-ratio="0.7814814814814814" data-s="300,640" src="/upload/f44a5f857205c283e2104805c0366e30.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>4.数据结构</strong></p> <p style="text-wrap: wrap;">数据结构是一种架构,它侧重于数据提供者和数据使用者之间的数据价值链中的数据集成、数据工程和治理的自动化 数据结构基于“活动元数据”的概念,使用知识图、语义、数据挖掘和机器学习 (AI) 技术来发现各种类型元数据(例如系统日志、社交等)中的模式。然后,将这种洞察应用于自动化并编排数据价值链。例如,它可以使数据使用者能够找到数据产品,然后自动向他们提供该数据产品。数据产品和数据使用者之间数据访问的增加减少了数据孤岛,并提供了更完整的组织数据视图。数据结构是一种具有巨大潜力的新兴技术,可用于增强客户概要分析、欺诈检测和预防性维护。根据 Gartner 的数据,数据结构使集成设计时间减少 30%,部署时间减少 30%,维护时间减少 70%。</p> <p style="text-wrap: wrap;"><br></p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000573" data-ratio="0.4535840188014101" data-s="300,640" src="/upload/6dc8375168ac4eedd660ab7daa7dfb5a.png" data-type="png" data-w="851" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>5.数据网格</strong></p> <p style="text-wrap: wrap;">数据网格是一种去中心化的数据架构,按业务领域来组织数据。使用数据网格时,组织需要不再将数据视为流程的副产品,而是开始将其视为产品本身。数据生产者充当数据产品所有者。作为主题专家,数据生产者可以利用他们对数据主要使用者的理解为他们设计 API。这些 API 也可以从组织的其他部分访问,提供了更广泛的受管数据访问渠道。</p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000576" data-ratio="0.562037037037037" data-s="300,640" src="/upload/d67c3f0365849b65f61179b6dae832ff.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;">数据湖、数据仓库等相对传统的存储系统可以作为多个去中心化的数据存储库来实现数据网格。数据网格还可以与数据结构一起使用,借助数据结构的自动化,可以更快地创建新的数据产品或执行全球治理。</p> </section> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><br></p> <section style="text-align: left;justify-content: flex-start;display: flex;flex-flow: row;margin-top: 10px;margin-bottom: 10px;"> <section style="display: inline-block;vertical-align: bottom;width: auto;align-self: flex-end;flex: 0 0 auto;background-color: rgb(33, 120, 240);min-width: 5%;height: auto;padding: 8px;border-right: 0px solid rgba(255, 255, 255, 0);border-top-right-radius: 0px;margin-right: 13px;"> <section style="text-align: justify;font-size: 11px;color: rgb(238, 227, 208);"> <p style="text-wrap: wrap;"><br></p> </section> </section> <section style="display: inline-block;vertical-align: bottom;width: auto;min-width: 5%;flex: 0 0 auto;height: auto;align-self: flex-end;border-bottom: 3px solid rgb(33, 120, 240);border-bottom-right-radius: 0px;background-color: rgba(255, 255, 255, 0);"> <section style="margin-bottom: 6px;"> <section style="text-align: justify;font-size: 17px;"> <p style="text-wrap: wrap;"><strong>数据架构的优势</strong></p> </section> </section> </section> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;">结构完善的数据架构可以为企业提供许多关键优势,其中包括:</p> <p style="text-wrap: wrap;"><br></p> <p style="text-wrap: wrap;"><strong>1.减少冗余:</strong>不同来源中可能存在重叠的数据字段,从而会导致不一致、数据不准确和错失数据集成机会的风险。良好的数据架构可以使数据存储方式标准化,并且可能减少重复,从而改善质量和整体分析。</p> <p style="text-wrap: wrap;"><strong>2.提高数据质量:</strong>精心设计的数据架构可以解决管理不善的数据湖(也称为“数据沼泽”)所带来的一些挑战。数据沼泽缺乏适当的数据质量和数据治理实践来提供有洞察力的学习。数据架构可以帮助实施数据治理和数据安全标准,从而对数据管道进行适当的监督,使其按预期运行。通过改进数据质量和治理,数据架构可以确保数据以一种现在和将来都具有价值的方式存储。</p> <p style="text-wrap: wrap;"><strong>3.支持集成:</strong>由于数据存储的技术限制和企业内部的组织障碍,数据经常孤立存在。当今的数据架构应该旨在促进跨域数据集成,以便不同的地理区域和业务部门可以访问彼此的数据。这有助于对常用指标(例如费用、收入以及相关驱动因素)形成更准确、更一致的理解。它还支持更全面地了解客户、产品和地理位置,从而更好地为决策提供信息。</p> <p style="text-wrap: wrap;"><strong>4.数据生命周期管理:</strong>现代数据架构可以解决如何随时间推移管理数据的问题。随着存在时间的增加和访问频率的降低,数据的使用价值通常会减少。随着时间的推移,可以将数据迁移到成本更低、速度更慢的存储类型,这样就可以继续用于报告和审计,但无需使用高性能存储。</p> </section> <p style="text-wrap: wrap;"><br></p> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><span style="color: rgb(33, 120, 240);"><strong>FineDataLink</strong></span>是一站式数据集成平台,拥有低代码的优势,提供了简单易用的交互界面,用户可以通过拖拽等方式轻松实现<strong>数据抽取、数据清洗、数据转换、数据整合、数据加载</strong>等多个环节。此外,帆软FDL还提供了强大的数据处理功能,例如<span style="color: rgb(62, 62, 62);">数据清洗</span>规则自由组合、数据去重、数据合并、数据拆分、数据聚合等,大大提高了数据处理效率和准确性。</p> </section> <section style="text-align: center;margin-top: 10px;margin-bottom: 10px;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;"> <img class="rich_pages wxw-img" data-imgfileid="100000580" data-ratio="0.47962962962962963" data-s="300,640" src="/upload/a7e7817cb507ed1e9953721726214af1.png" data-type="png" data-w="1080" style="vertical-align: middle;width: 100%;"> </section> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0%;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;width: 100%;vertical-align: top;padding: 25px;align-self: flex-start;flex: 0 0 auto;"> <section style="justify-content: flex-start;transform: translate3d(-24px, 0px, 0px);margin-right: 0%;margin-bottom: 20px;margin-left: 0%;display: flex;flex-flow: row;"> <section style="display: inline-block;width: auto;vertical-align: top;border-left: 6px solid rgb(33, 120, 240);border-bottom-left-radius: 0px;min-width: 10%;flex: 0 0 auto;height: auto;padding-left: 11px;align-self: flex-start;"> <section style="text-align: justify;font-size: 16px;"> <p style="text-wrap: wrap;"><strong>往期推荐</strong></p> </section> </section> </section> <section style="margin: 15px 0%;text-align: justify;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;vertical-align: top;width: auto;min-width: 10%;flex: 0 0 auto;height: auto;align-self: flex-start;"> <p style="text-wrap: wrap;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzkwMDcyMTQ3OQ==&mid=2247484077&idx=1&sn=808ddba430e4f370fefe0d5d31ed185e&chksm=c0befc85f7c9759359b50859cfc071382c8fc4a58952405029deb83d50a29a8aca83406b386b&scene=21#wechat_redirect" textvalue="值得收藏!实时ODS层数仓搭建方案" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">值得收藏!实时ODS层数仓搭建方案</a><br></p> <p style="text-wrap: wrap;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzkwMDcyMTQ3OQ==&mid=2247483905&idx=1&sn=04c64875b3b787ce82067e03d2d34f56&chksm=c0befc29f7c9753f218a9fa61b0401154c5b335f319cca053ec9570e6b666926ea8df279b627&scene=21#wechat_redirect" textvalue="比kettle更好用的工具推荐这款——" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">比kettle更好用的工具推荐这款——</a><br></p> <p style="text-wrap: wrap;"><a target="_blank" href="http://mp.weixin.qq.com/s?__biz=MzkwMDcyMTQ3OQ==&mid=2247483977&idx=1&sn=d2ce42f26b27b93be54dfd99223975b6&chksm=c0befc61f7c975775f15bb2ac15278c50545fe7113012a8d2d733aa327f7770f8a57ba35fec4&scene=21#wechat_redirect" textvalue="看完了这篇实时数仓建设,才发现以前的都白看了" linktype="text" imgurl="" imgdata="null" data-itemshowtype="0" tab="innerlink" data-linktype="2">看完了这篇实时数仓建设,才发现以前的都白看了</a><br></p> </section> </section> </section> </section> <section style="font-size: 16px;"> <p style="text-wrap: wrap;"><strong>了解更多资讯请关注:</strong></p> <p style="text-wrap: wrap;"><strong><br></strong></p> </section> <section class="mp_profile_iframe_wrp"> <mp-common-profile class="js_uneditable custom_select_card mp_profile_iframe" data-pluginname="mpprofile" data-id="MzkwMzMxNjIwMg==" data-headimg="http://mmbiz.qpic.cn/mmbiz_png/OQWqPWFuqJtOWiaCEUzIENA0NBDLP1dV5ibael9LdTbURB8D5ahDgicdic9GVGic6icEnePbab023AzFz6OWffrrJoWA/0?wx_fmt=png" data-nickname="FineDataLink" data-alias="fdl_dataplatform" data-signature="FineDataLink,“快速、高效、易用”的企业级一站式数据集成平台。" data-from="0" data-is_biz_ban="0"></mp-common-profile> </section> <p style="text-wrap: wrap;"><br></p> <section style="margin: 10px 0%;text-align: left;justify-content: flex-start;display: flex;flex-flow: row;"> <section style="display: inline-block;vertical-align: middle;width: 12%;align-self: center;flex: 0 0 auto;"> <section style="text-align: center;margin-right: 0%;margin-left: 0%;line-height: 0;"> <section style="vertical-align: middle;display: inline-block;line-height: 0;width: 100%;"> <img data-imgfileid="100000579" data-ratio="1" data-s="300,640" src="/upload/8fc4b234cc27848bdb051919ececdda0.png" data-type="gif" data-w="300" style="vertical-align: middle;width: 100%;"> </section> </section> </section> <section style="display: inline-block;vertical-align: middle;width: 88%;padding-left: 10px;align-self: center;flex: 0 0 auto;"> <section style="margin-right: 0%;margin-left: 0%;"> <section style="text-align: justify;"> <p style="text-wrap: wrap;">点击<strong style="color: rgb(0, 69, 128);">“阅读原文”<span style="color: rgb(62, 62, 62);">,申请试用FineDataLink</span></strong></p> </section> </section> </section> </section> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<pre data-darkmode-color="rgb(168, 168, 168)" data-style="padding-right: 0.5em; padding-left: 0.5em; max-width: 100%; background-color: rgb(255, 255, 255); color: rgb(62, 62, 62); letter-spacing: 0.544px; font-size: 15px; text-align: start; word-spacing: 2px; box-sizing: border-box !important; overflow-wrap: break-word !important;" class="js_darkmode__40" data-darkmode-bgcolor="rgb(36, 36, 36)" data-mpa-powered-by="yiban.io"><pre style="margin-bottom: 0em;outline: 0px;letter-spacing: 0.544px;background-color: rgb(255, 255, 255);font-size: 16px;color: rgb(34, 34, 34);visibility: visible;"><p style="outline: 0px;color: rgb(62, 62, 62);font-size: 15px;letter-spacing: 0.544px;white-space: normal;text-align: center;font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;visibility: visible;"><span style="color: rgb(136, 136, 136);font-size: 12px;letter-spacing: 0.544px;text-align: justify;"></span></p></pre></pre> <p>在开发过程中经常会使用if...else...进行判断抛出异常、分支处理等操作。这些if...else...充斥在代码中严重影响了代码代码的美观,这时我们可以利用Java 8的Function接口来消灭if...else...。</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 12px;"><span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">if</span> (...){<br> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">throw</span> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">new</span> RuntimeException(<span style="font-size: 12px;color: rgb(152, 195, 121);line-height: 26px;">"出现异常了"</span>);<br>} <br><br><span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">if</span> (...){<br> doSomething();<br>} <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">else</span> {<br> doOther();<br>}</span></p></pre> <h3 data-tool="mdnice编辑器"><br></h3> <h3 data-tool="mdnice编辑器"><span style="font-size: 20px;"><strong>Function 函数式接口</strong></span></h3> <h3 data-tool="mdnice编辑器"><br></h3> <h3 data-tool="mdnice编辑器">使用注解<span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">@FunctionalInterface</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">标识,并且只包含一个抽象方法的接口是函数式接口。</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">函数式接口主要分为Supplier供给型函数、Consumer消费型函数、Runnable无参无返回型函数和Function有参有返回型函数。</span></h3> <blockquote data-tool="mdnice编辑器"> <p>Function可以看作转换型函数</p> </blockquote> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器"><span style="font-size: 20px;"><strong>Supplier供给型函数</strong></span></h4> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器">Supplier的表现形式为不接受参数、只返回数据</h4> <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-imgfileid="100037690" data-ratio="0.6090014064697609" src="/upload/6e3499c4879a87c6537fc49aac67707d.png" data-type="png" data-w="711" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: auto !important;visibility: visible !important;height: auto !important;" width="677px"> </figure> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器"><span style="font-size: 20px;"><strong>Consumer消费型函数</strong></span></h4> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器">Consumer消费型函数和Supplier刚好相反。<span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">Consumer接收一个参数,没有返回值</span></h4> <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-imgfileid="100037693" data-ratio="0.7353535353535353" src="/upload/f967a11d9328ad157ee9fd2e96998064.png" data-type="png" data-w="990" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: auto !important;visibility: visible !important;height: auto !important;" width="677px"> </figure> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器"><span style="font-size: 20px;"><strong>Runnable无参无返回型函数</strong></span></h4> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器">Runnable的表现形式为即没有参数也没有返回值</h4> <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-imgfileid="100037692" data-ratio="0.6064382139148494" src="/upload/db25778035f2c8aa10e4d9e13306e5c7.png" data-type="png" data-w="963" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 411.433px !important;height: auto !important;" width="677px"> </figure> <p>Function函数的表现形式为接收一个参数,并返回一个值。Supplier、Consumer和Runnable可以看作Function的一种特殊表现形式</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-imgfileid="100037691" data-ratio="0.6777777777777778" src="/upload/657056dc39c726dbd5616b83d7e9ce80.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 459.525px !important;height: auto !important;" width="677px"> </figure> <section> <br> </section> <section> <span style="font-size: 18px;"><strong>使用小技巧:处理抛出异常的if</strong></span> </section> <p><strong>1.定义函数</strong></p> <p>定义一个抛出异常的形式的函数式接口, 这个接口只有参数没有返回值是个消费型接口</p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 13px;"><span style="font-size: 13px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 抛异常接口<br> **/</span><br><span style="font-size: 13px;color: rgb(97, 174, 238);line-height: 26px;">@FunctionalInterface</span><br><span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">public</span> <span style="font-size: 13px;line-height: 26px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">interface</span> <span style="color: rgb(230, 192, 123);line-height: 26px;">ThrowExceptionFunction</span> </span>{<br><br> <span style="font-size: 13px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 抛出异常信息<br> *<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> message 异常信息<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@return</span> void<br> **/</span><br> <span style="font-size: 13px;line-height: 26px;"><span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">void</span> <span style="font-size: 13px;color: rgb(97, 174, 238);line-height: 26px;">throwMessage</span><span style="font-size: 13px;line-height: 26px;">(String message)</span></span>;<br>}</span></p></pre> <p><strong>2.编写判断方法</strong></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">创建工具类VUtils并创建一个isTure方法,方法的返回值为刚才定义的函数式接口-</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">ThrowExceptionFunction</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">。</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">ThrowExceptionFunction</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">的接口实现逻辑为当参数b为true时抛出异常</span></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 12px;"><span style="font-size: 12px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 如果参数为true抛出异常<br> * <br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> b <br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@return</span> com.example.demo.func.ThrowExceptionFunction<br> **/</span><br><span style="font-size: 12px;line-height: 26px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">public</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">static</span> ThrowExceptionFunction <span style="color: rgb(97, 174, 238);line-height: 26px;">isTure</span><span style="line-height: 26px;">(<span style="color: rgb(198, 120, 221);line-height: 26px;">boolean</span> b)</span></span>{<br><br> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">return</span> (errorMessage) -> {<br> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">if</span> (b){<br> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">throw</span> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">new</span> RuntimeException(errorMessage);<br> }<br> };<br>}</span></p></pre> <p><strong>3.使用方式</strong></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">调用工具类参数参数后,调用函数式接口的</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">throwMessage</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">方法传入异常信息。</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">当出入的参数为false时正常执行</span></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-imgfileid="100037689" data-ratio="0.5436554132712457" src="/upload/94aee75f4e480ed36fe6c2c7d8e8599b.png" data-type="png" data-w="859" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 369.069px !important;height: auto !important;" width="677px"> </figure> <p>当出入的参数为true时抛出异常</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-imgfileid="100037695" data-ratio="0.41725888324873095" src="/upload/4cd804030095b175341149d917d89273.png" data-type="png" data-w="985" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 283.779px !important;height: auto !important;" width="677px"> </figure> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器"><span style="font-size: 20px;"><strong>处理if分支操作</strong></span></h4> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器"><strong>1、定义函数式接口</strong></h4> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器">创建一个名为<span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">BranchHandle</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">的</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">函数式接口</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">,接口的参数为两个Runnable接口。</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">这两个两个Runnable接口分别代表了为true或false时要进行的操作</span></h4> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 13px;"><span style="font-size: 13px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 分支处理接口<br> **/</span><br><span style="font-size: 13px;color: rgb(97, 174, 238);line-height: 26px;">@FunctionalInterface</span><br><span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">public</span> <span style="font-size: 13px;line-height: 26px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">interface</span> <span style="color: rgb(230, 192, 123);line-height: 26px;">BranchHandle</span> </span>{<br><br> <span style="font-size: 13px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 分支操作<br> *<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> trueHandle 为true时要进行的操作<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> falseHandle 为false时要进行的操作<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@return</span> void<br> **/</span><br> <span style="font-size: 13px;line-height: 26px;"><span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">void</span> <span style="font-size: 13px;color: rgb(97, 174, 238);line-height: 26px;">trueOrFalseHandle</span><span style="font-size: 13px;line-height: 26px;">(Runnable trueHandle, Runnable falseHandle)</span></span>;<br><br>}</span></p></pre> <p><strong>2.编写判断方法</strong></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">创建一个名为isTureOrFalse的方法,方法的返回值为刚才定义的函数式接口-</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">BranchHandle</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">。</span></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 12px;"><span style="font-size: 12px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 参数为true或false时,分别进行不同的操作 <br> * <br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> b <br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@return</span> com.example.demo.func.BranchHandle <br> **/</span><br><span style="font-size: 12px;line-height: 26px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">public</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">static</span> BranchHandle <span style="color: rgb(97, 174, 238);line-height: 26px;">isTureOrFalse</span><span style="line-height: 26px;">(<span style="color: rgb(198, 120, 221);line-height: 26px;">boolean</span> b)</span></span>{<br> <br> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">return</span> (trueHandle, falseHandle) -> {<br> <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">if</span> (b){<br> trueHandle.run();<br> } <span style="font-size: 12px;color: rgb(198, 120, 221);line-height: 26px;">else</span> {<br> falseHandle.run();<br> }<br> };<br>}</span></p></pre> <p><strong>3.使用方式</strong></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">参数为true时,执行trueHandle</span></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-imgfileid="100037696" data-ratio="0.5805471124620061" src="/upload/fe7bb5f2aaace28495cb616b677680bf.png" data-type="png" data-w="987" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 393.963px !important;height: auto !important;" width="677px"> </figure> <p>参数为false时,执行falseHandle</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-imgfileid="100037697" data-ratio="0.4817012858555885" src="/upload/72d26125ad17e3f8af19afa401219db6.png" data-type="png" data-w="1011" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 327.264px !important;height: auto !important;" width="677px"> </figure> <h4 data-tool="mdnice编辑器"><strong>如果存在值执行消费操作,否则执行基于空的操作</strong></h4> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器"><strong>1、定义函数</strong></h4> <h4 data-tool="mdnice编辑器"><br></h4> <h4 data-tool="mdnice编辑器">创建一个名为<span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">PresentOrElseHandler</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">的函数式接口,接口的参数一个为Consumer接口。</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">一个为Runnable,分别代表值不为空时执行消费操作和值为空时执行的其他操作</span></h4> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 13px;"><span style="font-size: 13px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 空值与非空值分支处理<br> */</span><br><span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">public</span> <span style="font-size: 13px;line-height: 26px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">interface</span> <span style="color: rgb(230, 192, 123);line-height: 26px;">PresentOrElseHandler</span><<span style="color: rgb(230, 192, 123);line-height: 26px;">T</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">extends</span> <span style="color: rgb(230, 192, 123);line-height: 26px;">Object</span>> </span>{<br><br> <span style="font-size: 13px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 值不为空时执行消费操作<br> * 值为空时执行其他的操作<br> * <br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> action 值不为空时,执行的消费操作<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> emptyAction 值为空时,执行的操作<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@return</span> void <br> **/</span><br> <span style="font-size: 13px;line-height: 26px;"><span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">void</span> <span style="font-size: 13px;color: rgb(97, 174, 238);line-height: 26px;">presentOrElseHandle</span><span style="font-size: 13px;line-height: 26px;">(Consumer<? <span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">super</span> T> action, Runnable emptyAction)</span></span>;<br> <br>}</span></p></pre> <p><strong><br></strong></p> <p><strong>2.编写判断方法</strong></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">创建一个名为</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">isBlankOrNoBlank</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">的方法,方法的返回值为刚才定义的函数式接口-</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">PresentOrElseHandler</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">。</span></p> <pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);display: -webkit-box;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;margin-left: 8px;margin-right: 8px;line-height: 2em;"><span style="font-size: 13px;"><span style="font-size: 13px;color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/**<br> * 参数为true或false时,分别进行不同的操作<br> *<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@param</span> b<br> * <span style="color: rgb(198, 120, 221);line-height: 26px;">@return</span> com.example.demo.func.BranchHandle<br> **/</span><br><span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">public</span> <span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">static</span> PresentOrElseHandler<?> isBlankOrNoBlank(String str){<br><br> <span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">return</span> (consumer, runnable) -> {<br> <span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">if</span> (str == <span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">null</span> || str.length() == <span style="font-size: 13px;color: rgb(209, 154, 102);line-height: 26px;">0</span>){<br> runnable.run();<br> } <span style="font-size: 13px;color: rgb(198, 120, 221);line-height: 26px;">else</span> {<br> consumer.accept(str);<br> }<br> };<br>}</span></p></pre> <p><strong>3.使用方式</strong></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">调用工具类参数参数后,调用函数式接口的</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">presentOrElseHandle</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">方法传入一个</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">Consumer</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">和</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">Runnable</span></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">参数不为空时,打印参数</span></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-imgfileid="100037694" data-ratio="0.4865497076023392" src="/upload/ffba07a685bf0f1b74c511767c946234.png" data-type="png" data-w="855" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 330.535px !important;height: auto !important;" width="677px"> </figure> <p>参数不为空时</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-imgfileid="100037698" data-ratio="0.4105263157894737" src="/upload/24520836d97ebd0ad92f0f057c2cdd26.png" data-type="png" data-w="1045" style="display: block;margin-right: auto;margin-left: auto;width: 677px !important;height: 279.236px !important;height: auto !important;" width="677px"> </figure> <p><br></p> <p><span style="font-size: 20px;"><strong>结尾</strong></span></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">Function函数式接口是java 8非常重要的特性,利用好Function函数可以极大的简化代码。</span></p> <section style="-webkit-tap-highlight-color: transparent;margin-top: 8px;margin-bottom: 0px;padding-top: 1em;padding-bottom: 8px;outline: 0px;user-select: text;letter-spacing: 0.544px;white-space: pre-wrap;font-size: 15px;font-variant-ligatures: common-ligatures;font-weight: 700;orphans: 4;widows: 1;caret-color: rgb(255, 0, 0);background-color: rgb(255, 255, 255);font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;word-spacing: 0.8px;clear: both;min-height: 1em;color: rgb(74, 74, 74);line-height: 1.75em;text-align: center;visibility: visible;"> <span style="-webkit-tap-highlight-color: transparent;outline: 0px;user-select: text;font-weight: bolder;font-family: -apple-system, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;word-spacing: 2px;color: rgb(51, 51, 51);">- End-</span> </section> <section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="-webkit-tap-highlight-color: transparent;margin-bottom: 0px;padding-right: 10px;padding-left: 10px;outline: 0px;user-select: text;white-space: pre-wrap;color: rgb(89, 89, 89);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 15px;font-variant-ligatures: common-ligatures;font-weight: 700;letter-spacing: 0.05em;orphans: 4;text-align: left;widows: 1;caret-color: rgb(255, 0, 0);background-color: rgb(255, 255, 255);word-spacing: 0.8px;line-height: 1.6;word-break: break-word;visibility: visible;"> <section style="-webkit-tap-highlight-color: transparent;margin-top: 10px;margin-bottom: 10px;outline: 0px;user-select: text;line-height: 26px;color: rgb(1, 1, 1);"> <br style="-webkit-tap-highlight-color: transparent;outline: 0px;user-select: text;"> </section> <blockquote data-tool="mdnice编辑器" style="-webkit-tap-highlight-color: transparent;margin: 10px 5px;padding: 10px 10px 10px 20px;outline: 0px;user-select: text;border-left-color: rgb(53, 179, 120);color: rgb(97, 97, 97);font-size: 0.9em;border-top: none;border-bottom: none;overflow: auto;border-right: 0px solid rgb(53, 179, 120);quotes: none;background: rgb(251, 249, 253);"> <p style="-webkit-tap-highlight-color: transparent;padding-top: 8px;padding-bottom: 8px;outline: 0px;user-select: text;font-size: 16px;color: black;line-height: 26px;">DailyMart是一个基于 DDD 和Spring Cloud Alibaba的微服务商城系统,采用SpringBoot3.x以及JDK17。旨在为开发者提供集成式的学习体验,并将其无缝地应用于实际项目中。该专栏包含领域驱动设计(DDD)、Spring Cloud Alibaba企业级开发实践、设计模式实际应用场景解析、分库分表战术及实用技巧等内容。如果你对这个系列感兴趣,可在本公众号回复关键词 <span style="-webkit-tap-highlight-color: transparent;outline: 0px;user-select: text;font-weight: bolder;color: rgb(53, 179, 120);">DDD</span> 获取完整文档以及相关源码。</p> </blockquote> </section> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<p data-track="3">首先,我从微服务谈起,这样大家更能了解微服务的本质。</p> <p data-track="4">微服务的本质:在于将一个复杂的大型应用程序,拆分为多个独立的小服务。</p> <p data-track="5">每个服务之间,通过轻量级的通信机制(比如:RPC、或者HTTP/REST、消息队列...等形式),交互通信。</p> <p data-track="6">整个架构,如下图所示:</p> <p><img class="rich_pages wxw-img" data-height="632" data-imgfileid="100021659" data-ratio="1.011111111111111" src="/upload/5c6f92c3a829904735dbfba40476fa81.jpg" data-type="jpeg" data-w="1080" data-width="625"></p> <p data-track="7"><strong>1.单一职责</strong></p> <p data-track="8">每个微服务,都围绕特定的业务功能或能力展开,聚焦于做一件事,并将其做好。</p> <p data-track="9">比如:用户服务、交易服务、商品服务...职责非常分明,每个服务专注自己的事情。</p> <p data-track="10"><strong>2.独立部署</strong></p> <p data-track="11">每个微服务,一般都是采用“独立部署”,为什么要采用“独立部署”?</p> <p data-track="12">原因很简单:就是独立了后,可以单独开发、测试、部署、和扩展...等等。</p> <p data-track="13">通过这种方式,每个团队可以控制自己的节奏,不用担心别的团队影响到自己,本质是提高工作效率。</p> <p data-track="14"><strong>3.去中心化治理</strong></p> <p data-track="15">微服务架构的本质:是提倡去中心化的管理方式,每个团队负责各自的微服务,就是我上面讲到的。</p> <p data-track="16">除了独立之外,还有一个好处,就是各个团队,可以使用最合适的技术栈,比如:你是Java的、Go、Php...等等。</p> <p data-track="17">都可以采用(多语言、多数据库...等),这样每个团队不用被编程语言限制,可以更好的发挥自己的特长。</p> <p data-track="18">最后,各个团队通过微服务框架,来解决通信。</p> <p data-track="19">比如:你可以使用RPC,也可以使用,比如:HTTP(RESTful APIs)等等,以及消息队列...都可以。</p> <p data-track="20">这种松耦合的通信方式使得服务之间的依赖性较低,有助于提升系统的弹性。</p> <p data-track="21"><strong>4.弹性与容错</strong></p> <p data-track="22">通过将应用拆分为多个小服务,微服务架构可以更好地应对失败。</p> <p data-track="23">比如:某个服务出问题时,其他服务仍然能够继续工作。</p> <p data-track="24">服务可以通过负载均衡、限流、熔断.......等技术增强容错能力。</p> <p data-track="25">当你把上面的抓住了之后,你就需要具体运用哪种“微服务架构设计模式”了,下面我接着谈常见的“微服务设计模式”@mikechen</p> <h1 spellcheck="false" data-track="26"><strong>微服务数据共享设计</strong></h1> <p data-track="27">在微服务架构中,每个微服务应该拥有自己的独立数据存储,以保持自治性和独立性。</p> <p data-track="27">而这里,我提到的是“微服务数据共享模式”,大家要清楚这种模式,可以理解为“反模式”,只是一个“微服务过渡版”。</p> <p><img class="rich_pages wxw-img" data-height="634" data-imgfileid="100021658" data-ratio="0.619140625" src="/upload/162079d4c8db6965381bbeebe116e2d7.jpg" data-type="jpeg" data-w="1024" data-width="1024"></p> <p data-track="29">为什么说是“过渡”呢?原因很简单:多个微服务共享一个数据库,这种设计能更容易处理事务、和一致性问题。</p> <p data-track="30">但会降低服务的独立性、和扩展性,不符合微服务的自治原则。</p> <p data-track="31">这一点,一定要非常清楚,否则就本末倒置了。</p> <p data-track="32">所以,你可以采用微服务数据共享,来过渡微服务。</p> <p data-track="33">如果你的团队还没有这么强的能力,可以采用这种模式来“过渡”,这一点要记住!</p> <h1 spellcheck="false" data-track="34"><strong>微服务聚合设计</strong></h1> <p data-track="35">微服务聚合设计:是指如何将多个微服务的数据、或行为进行组合,提供一个统一的接口、或功能。</p> <p><img class="rich_pages wxw-img" data-height="528" data-imgfileid="100021657" data-ratio="0.515625" src="/upload/4f99c330edcbb9be3985d4d7f1046c90.jpg" data-type="jpeg" data-w="1024" data-width="1024"></p> <p data-track="36">这种设计模式,常见于需要将多个服务的响应整合为一个响应的场景,比如:用户界面调用“多个微服务”的情况。</p> <p data-track="37">举一个例子,前端请求用户详细信息,可以通过这种模式, 聚合用户服务、订单服务、支付服务...等等的数据,返回一个包含所有信息的响应。</p> <p data-track="38">这是这种模式的好处,但也有坏处。</p> <p data-track="39">比如:聚合操作可能需要等待多个微服务的响应,影响性能。</p> <p data-track="40">为此,可以使用异步请求、并行调用、或缓存机制来提高响应速度。</p> <h1 spellcheck="false" data-track="41"><strong>微服务代理设计</strong></h1> <p data-track="42">微服务代理设计:是通过在服务之间引入中间层(代理)来处理通信、负载均衡、安全...等非业务功能,简化微服务的实现并提升其灵活性。</p> <p><img class="rich_pages wxw-img" data-height="525" data-imgfileid="100021656" data-ratio="0.5126953125" src="/upload/c8d1b71bfa0af8cd9b3d7f27203a0fce.jpg" data-type="jpeg" data-w="1024" data-width="1024"></p> <p data-track="43">比如:最典型的就是服务网格(Sidecar 代理模式),如下图所示:</p> <p><img class="rich_pages wxw-img" data-height="346" data-imgfileid="100021662" data-ratio="0.5583333333333333" src="/upload/68b73bed6a66387bb2bf2ccc0787d7d8.jpg" data-type="jpeg" data-w="1080" data-width="620"></p> <p data-track="44">在服务网格中,Sidecar 代理是服务网格的核心组件。</p> <p data-track="45">每个微服务实例旁边,部署一个 Sidecar 代理,Sidecar 是一个运行在主服务旁边的辅助进程。</p> <p data-track="46">服务网格(Sidecar 模式),可以帮助主服务处理非业务逻辑,如:日志、监控、安全认证...等任务。</p> <p data-track="47">Sidecar 代理模式允许微服务将一些基础设施功能分离出去,减少主服务的复杂性。</p> <p data-track="48">常见的有:Istio、Linkerd ...等服务网格技术。</p> <p><img class="rich_pages wxw-img" data-height="1049" data-imgfileid="100021661" data-ratio="0.6648148148148149" src="/upload/9c6832cc1a7f2eec3b7d62dff6780084.png" data-type="png" data-w="1080" data-width="1578"></p> <p data-track="49">代理虽然有很多好处,但也要记住:代理层可能成为系统瓶颈。</p> <p data-track="50">因此需要优化代理的性能,防止其成为单点故障。</p> <h1 spellcheck="false" data-track="51"><strong>微服务异步消息设计</strong></h1> <p data-track="52">异步消息设计:主要是指通过消息队列、或事件,在微服务之间实现松耦合通信。</p> <p data-track="53">服务之间的交互不再是同步调用,而是通过异步消息传递信息,从而提高系统的扩展性、和性能。</p> <p><img class="rich_pages wxw-img" data-height="640" data-imgfileid="100021660" data-ratio="0.625" src="/upload/2901a91b2e591a074ea32033ca8f7d01.jpg" data-type="jpeg" data-w="1024" data-width="1024"></p> <p data-track="54">比如:消息队列用于微服务之间的异步通信,一个微服务可以将消息发送到消息队列,另一个微服务从队列中消费消息。</p> <p data-track="55">这种模式,适用于任务队列、异步处理...等场景。</p> <p data-track="56">但是,这种模式,也需要考虑:消息系统必须确保消息不丢失(可靠性)/和幂等性等问题。</p>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-bottom: 0px;padding-left: 10px;padding-right: 10px;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima, "Microsoft YaHei", PingFangSC-regular, serif;font-size: 16px;line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;text-align: left;"> <h1 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 24px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">MySQL锁总体结构</span><span style="display: none;"></span></h1> <figure data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100001250" data-ratio="0.6662484316185696" src="/upload/7976757fdd98fe589ccc879a1660253f.png" data-type="png" data-w="3188" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgb(136, 136, 136);font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: center;margin-top: 5px;"> 总结 </figcaption> </figure> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">MySQL 的锁可以分成三类:总体、类型、粒度。</p> <ol data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 总体上分成两种:乐观锁和悲观锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 类型上也是两种:读锁和写锁 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 锁的粒度上可以分成五种:表锁,行锁,页面锁,间隙锁,临键锁 </section></li> </ol> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">下面我们就来详细讲一下这些锁</p> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">1. 悲观锁</span><span style="display: none;"></span></h2> <blockquote data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0em;">悲观锁对于数据库中数据的读写持悲观态度,即在整个数据处理的过程中,他会悲观认为数据不会保持一致性,所以是会将相应的数据锁定。在数据库中,悲观锁的实现是依赖数据库提供的锁机制。</p> </blockquote> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">如果加上了悲观锁,那么就无法对这些数据进行读取操作。</p> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">2. 乐观锁</span><span style="display: none;"></span></h2> <blockquote data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0em;">乐观锁对于数据库的数据的读写持乐观态度,即在整个数据处理的过程中,他会很乐观的认为数据会保持一致性,所以不加锁,而是通过数据版本记录机制实现。</p> </blockquote> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">MySQL中的MVCC多版本控制就是乐观锁的一种实现方式。</p> <ol data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 往往会在数据表中增加 <strong>一个类型version的版本号字段</strong>。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 在查询数据库中的数据时,会将版本号字段的值一起读取出来。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 当更新数据时,会令 <strong>版本号字段的值加1</strong>。将提交数据的版本与数据库表对应记录的版本进行对比。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 如果提交的数据版本号 <strong>大于</strong>数据表中当前要修改的数据的版本号,则数据进行修改操作。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 否则不修改数据库表中的数据。 </section></li> </ol> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">3. 读锁</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">读写又称为<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">共享锁或者S锁(Shared Lock)</strong>,针对同一份数据,可以加多个读锁而互不影响。</p> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">4. 写锁</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">写锁又称为<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">排他锁或者X锁(Exclusive Lock)</strong>,如果当前写锁未释放,他会阻塞其他的写锁和读锁。</p> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">5. 表锁</span><span style="display: none;"></span></h2> <blockquote data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0em;">表锁也称为表级锁,就是在<strong style="background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0);width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">整个数据表上对数据进行加锁和释放锁</strong>。特点:开销小,加速快,粒度大,并发度最低,发生锁冲突概率高。</p> </blockquote> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">在MySQL中,有两种表锁模式:一种是<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">表共享锁(Table Shard Lock)</strong>,另一种是<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">表独占写锁(Table Write Lock)</strong>。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">当一个线程获取到一个表的读锁后,其他线程仍然可以进行<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">读操作</strong>,但不能对表进行<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">写操作</strong>。那么对应的如果一个线程获取到一个表的写锁后,只有这个线程可以进行<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">读写操作</strong>,其他线程无法对表进行<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">读写操作</strong>,直到写锁被释放为止。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">在mysql中可以通过以下命令手动添加表锁</p> <pre data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;">LOCK</span> <span style="color: #c678dd;line-height: 26px;">TABLE</span> 表名称 <span style="color: #c678dd;line-height: 26px;">read</span>(write);<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">eg: 添加读表锁</p> <pre data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"></span><span style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: normal;text-align: left;white-space-collapse: preserve;color: rgb(198, 120, 221);line-height: 26px;">LOCK</span><span style="color: rgb(171, 178, 191);font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: normal;text-align: left;white-space-collapse: preserve;background-color: rgb(40, 44, 52);"> </span><span style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: normal;text-align: left;white-space-collapse: preserve;color: rgb(198, 120, 221);line-height: 26px;">TABLE</span><span style="color: #c678dd;line-height: 26px;"></span> user_table <span style="color: #c678dd;line-height: 26px;">read</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">eg: 添加写表锁</p> <pre data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;"></span><span style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: normal;text-align: left;white-space-collapse: preserve;color: rgb(198, 120, 221);line-height: 26px;">LOCK</span><span style="color: rgb(171, 178, 191);font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: normal;text-align: left;white-space-collapse: preserve;background-color: rgb(40, 44, 52);"> </span><span style="font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;letter-spacing: normal;text-align: left;white-space-collapse: preserve;color: rgb(198, 120, 221);line-height: 26px;">TABLE</span><span style="color: #c678dd;line-height: 26px;"></span> user_table write;<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">使用如下命令可以查看数据表上增加的锁</p> <pre data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;">SHOW</span> <span style="color: #c678dd;line-height: 26px;">OPEN</span> <span style="color: #c678dd;line-height: 26px;">TABLES</span>;<br></code></pre> <figure data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 10px;margin-bottom: 10px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100001251" data-ratio="0.4166666666666667" src="/upload/796fd4ef89599c6937f9891337b2e54b.png" data-type="png" data-w="1080" style="display: block;margin-right: auto;margin-left: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgb(136, 136, 136);font-size: 14px;line-height: 1.5em;letter-spacing: 0em;text-align: center;margin-top: 5px;"> <br> </figcaption> </figure> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">删除表锁:</p> <pre data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;">UNLOCK</span> <span style="color: #c678dd;line-height: 26px;">TABLES</span>;<br></code></pre> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">6. 行锁</span><span style="display: none;"></span></h2> <blockquote data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-top: 3px none rgba(0, 0, 0, 0.4);border-bottom: 3px none rgba(0, 0, 0, 0.4);border-right: 3px none rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-radius: 0px;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(0, 0, 0, 0.05);width: auto;height: auto;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;overflow: auto;"> <span style="display: none;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.5em;letter-spacing: 0em;"></span> <p style="text-indent: 0em;padding-top: 8px;padding-bottom: 8px;color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0em;">行锁也称为行级别,就是在数据行上对数据进行加锁和释放锁。特点:开销大,加锁慢,粒度小,并发度高,锁冲突概率最小。</p> </blockquote> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">在mysql的InnoDB存储引擎中有两种行锁,排他锁和共享锁。</p> <ul data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 共享锁:允许一个事务读取一行数据,但 <strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">不允许一个事务对加了共享锁的当前行增加排他锁</strong>。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 排他锁:允许当前事务对数据行进行增删改查操作,不允许 <strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">其他事务对增加了排他锁的数据行增加共享锁和排他锁</strong>。 </section></li> </ul> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;"><strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">注意:</strong></p> <ol data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 行锁主要加在索引上,如果 <strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">对非索引字段设置条件进行更新,行锁可能会变成表锁</strong>。例如 <code style="color: rgb(30, 107, 184);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: Consolas, Monaco, Menlo, monospace;word-break: break-all;">UPDATE user_table SET number = 2 WHERE name = 'fanone'</code> 如果name没有加索引,那么可能会进行表锁。所以我们一般建议使用主键id作为更新数据的查询条件。 </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> InnoDB的行锁是针对索引加锁,不是针对记录加锁,并且加锁的索引 <strong style="color: rgb(0, 0, 0);background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">不能失效</strong>,否则行锁也有可能变成表锁。而导致索引失效的有很多,比如联合索引不遵循最左匹配原则会失效、OR会失效等等... </section></li> <li> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(1, 1, 1);line-height: 1.8em;letter-spacing: 0em;"> 锁定某一行时,可以使用 <code style="color: rgb(30, 107, 184);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: Consolas, Monaco, Menlo, monospace;word-break: break-all;">lock in share mode</code>命令来指定共享锁,使用 <code style="color: rgb(30, 107, 184);font-size: 14px;line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: Consolas, Monaco, Menlo, monospace;word-break: break-all;">for update</code>命令来指定排他锁。 </section></li> </ol> <pre data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;">UPDATE</span> user_table <span style="color: #c678dd;line-height: 26px;">SET</span> <span style="color: #e6c07b;line-height: 26px;">number</span> = <span style="color: #d19a66;line-height: 26px;">2</span> <span style="color: #c678dd;line-height: 26px;">WHERE</span> <span style="color: #c678dd;line-height: 26px;">name</span> = <span style="color: #98c379;line-height: 26px;">'fanone'</span> <span style="color: #c678dd;line-height: 26px;">LOCK</span> <span style="color: #c678dd;line-height: 26px;">IN</span> <span style="color: #c678dd;line-height: 26px;">SHARE</span> <span style="color: #c678dd;line-height: 26px;">MODE</span>;<br><span style="color: #c678dd;line-height: 26px;">UPDATE</span> user_table <span style="color: #c678dd;line-height: 26px;">SET</span> <span style="color: #e6c07b;line-height: 26px;">number</span> = <span style="color: #d19a66;line-height: 26px;">2</span> <span style="color: #c678dd;line-height: 26px;">WHERE</span> <span style="color: #c678dd;line-height: 26px;">name</span> = <span style="color: #98c379;line-height: 26px;">'fanone'</span> <span style="color: #c678dd;line-height: 26px;">FOR</span> <span style="color: #c678dd;line-height: 26px;">UPDATE</span>;<br></code></pre> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">7. 页面锁</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">页级锁定是 MySQL 中比较独特的一种锁定级别。特点:锁定颗粒度介于行级锁定与表级锁之间,锁开销和加锁时间界于表锁和行锁之间,并发处理能力也同样是介于上面二者之间,并发度一般。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">不过,随着锁定资源颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">使用页级锁定的主要是 BerkeleyDB 存储引擎。</p> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">8. 间隙锁</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">在mysql中使用范围查询的时,如果请求共享锁或者排他锁,InnoDB会给符合条件的已有数据的索引项加锁。<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">如果键值在条件范围内,而这个范围内并不存在记录,而认为此时出现了间隙,InnoDB会对这个间隙进行加锁,这也称为间隙锁。</strong></p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">eg:</p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 15px 16px 16px;background: rgb(40, 44, 52);border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color:#c678dd;">SELECT </span><span style="color: rgb(171, 178, 191);">* </span><span style="color:#c678dd;">FROM</span><span style="color:#abb2bf;"> user_user;<br>+</span><span style="color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">----+-------+-------+</span><span style="color:#abb2bf;"><br>|id | name | sex |<br>+</span><span style="color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">----+-------+-------+</span><span style="color:#abb2bf;"><br>| 1 |zhangsan| 1 |<br>| 2 |lisi | 2 |<br>| 3 |lisi2 | 2 |<br>| 7 |lisi3 | 2 |<br>| 10 |lisi4 | 2 |<br>| 21 |lisi5 | 2 |<br>+</span><span style="color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">----+-------+-------+</span><span style="color:#abb2bf;"><br></span></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">上面出现了间隙有 (3,7], (7,10], (10,21],(21,+∞] 的三个区间。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">如果执行以下sql</p> <pre data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;color: #abb2bf;padding-top: 15px;background: #282c34;border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;"><span style="color: #c678dd;line-height: 26px;">UPDATE</span> user_user <span style="color: #c678dd;line-height: 26px;">SET</span> sex = <span style="color: #d19a66;line-height: 26px;">1</span> <span style="color: #c678dd;line-height: 26px;">WHERE</span> <span style="color: #c678dd;line-height: 26px;">id</span> > <span style="color: #d19a66;line-height: 26px;">8</span> <span style="color: #c678dd;line-height: 26px;">AND</span> <span style="color: #c678dd;line-height: 26px;">id</span> < <span style="color: #d19a66;line-height: 26px;">18</span>;<br></code></pre> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">那么其他事务就无法在 (7,21] 这个区间内插入或者修改任何数据。间隙锁会锁住 (7,10], (10,21] 这两个间隙。不过间隙锁只会在 <strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">可重复读事务隔离级别</strong> 下才会生效。</p> <h2 data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);margin-top: 30px;margin-bottom: 15px;"><span style="display: none;"></span><span style="font-size: 22px;line-height: 1.5em;letter-spacing: 0em;font-weight: bold;display: block;">9. 临键锁</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">临键锁就是行锁和间隙锁的组合,也可以理解为一种特殊的间隙锁。通过临建锁可以解决幻读的问题。 </p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">会锁住一段左开右闭区间的数据</strong> 。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">需要强调的一点是,InnoDB 中行级锁是基于索引实现的,<strong style="background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;border-style: none;border-width: 3px;border-color: rgba(0, 0, 0, 0.4);border-radius: 0px;">临键锁只与非唯一索引列有关</strong> ,在唯一索引列(包括主键列)上不存在临键锁。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">上面的(7,21]就是临键锁。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">参考:</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);line-height: 1.8em;letter-spacing: 0em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;">[1] https://www.jianshu.com/p/478bc84a7721</p> </section> <p><br></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<p> 现在微服务开发中为了满足限流消峰、减少系统之间的耦合等实际业务的需要,于是系统中往往会引入了MQ,加入了MQ之后如何保证消费者的消费幂等性便是需要解决的问题了。<br></p> <p><strong>1、幂等性问题</strong><br></p> <p> <span style="letter-spacing: 0.578px;text-wrap: wrap;">幂等性是数学上的一个概念,在我们开发中可以理解成就是多次执行某个方法并且得到结果是一样的,那么我们就认为这个过程就是幂等性。如下就是一些常见的幂等性的案例:</span></p> <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="cs"><code><span class="code-snippet_outer"><span class="code-snippet__number">1</span>、查询幂等性</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">select</span> * <span class="code-snippet__keyword">from</span> user <span class="code-snippet__keyword">where</span> id = <span class="code-snippet__number">10</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__number">2</span>、更新的幂等性</span></code><code><span class="code-snippet_outer">update user <span class="code-snippet__keyword">set</span> name = <span class="code-snippet__string">'zhangsan'</span> <span class="code-snippet__keyword">where</span> id = <span class="code-snippet__number">20</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__number">3</span>、添加的幂等性(假设userId是唯一键)</span></code><code><span class="code-snippet_outer"><span class="code-snippet__function">insert info <span class="code-snippet__title">user</span>(<span class="code-snippet__params">user_id, name,age</span>) <span class="code-snippet__title">values</span>(<span class="code-snippet__params"><span class="code-snippet__number">1</span>, <span class="code-snippet__string">'zhangsan'</span>,<span class="code-snippet__number">20</span></span>)</span>;</span></code><code><span class="code-snippet_outer"><span class="code-snippet__number">4</span>、 删除幂等性</span></code><code><span class="code-snippet_outer">delete <span class="code-snippet__keyword">from</span> user <span class="code-snippet__keyword">where</span> id = <span class="code-snippet__number">21</span>;</span></code></pre> </section> </section> <p> 查询是具备天然的幂等性(不考虑数据更新/删除的情况下,多次查询始终是一个结果)。<br></p> <p> 如上所示的更新/删除都是具备幂等性的,无论这个更新执行多少次,最终的结果都是一样的。<span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></p> <p> 如上所示的添加也是具备幂等性的,因为userId字段是唯一键,重新添加数据库会提示异常的。</p> <p>非幂等性的就是指多次执行的结果会不一样,如下所示是常见的一些非幂等性的案例:</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="sql"><code><span class="code-snippet_outer">1、更新的非幂等性</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">update</span> <span class="code-snippet__keyword">user</span> <span class="code-snippet__keyword">set</span> age = age + <span class="code-snippet__number">1</span> <span class="code-snippet__keyword">where</span> <span class="code-snippet__keyword">id</span> = <span class="code-snippet__number">20</span>;</span></code><code><span class="code-snippet_outer">2、添加的非幂等性</span></code><code><span class="code-snippet_outer"><span class="code-snippet__keyword">insert</span> <span class="code-snippet__keyword">into</span> <span class="code-snippet__keyword">user</span>(<span class="code-snippet__keyword">name</span>, age, nick_name) <span class="code-snippet__keyword">values</span> (<span class="code-snippet__string">'zhangsan'</span>, <span class="code-snippet__number">20</span>, <span class="code-snippet__string">'sanzhang'</span>)</span></code></pre> </section> <p> 如上所示的案例是非幂等性的,因为执行多次的结果是不一样的。所以针对这些非幂等性操作需要做单独的处理,保证一次请求只会执行一次。<br></p> <p><strong>2、MQ出现幂等性问题的原因</strong></p> <p><strong>(1)生产者重复生产</strong></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000903" data-ratio="0.2966101694915254" data-s="300,640" data-type="png" data-w="708" style="height: auto !important;" src="/upload/b319fcc08891aee62262712a6fc55a8c.png"></p> <p> 由于网络原因,第一次生产者的发送的消息MQ已经接收到了,但是给服务响应的时候超时了,导致服务器再次投递了相同的消息,在消息队列中存在了两条相同的消息。下游的消费者就需要消费两次个相同的消息,消费者需要自己做幂等性的处理。<br></p> <p><strong>(2)MQ重试机制</strong></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000904" data-ratio="0.4387755102040816" data-s="300,640" data-type="png" data-w="490" style="height: auto !important;" src="/upload/8f7f971a1608d81c31ddb1e2619ca769.png"></p> <p><span style="letter-spacing: 0.578px;text-wrap: wrap;"> 消费者第一次消费id=1的消息时候,此时消费此时消费成功但是响应MQ的时候超时导致MQ认为当前的消息消费者没有成功消费,过一段时间之后重新投递给消费者消费,那么就导致同样的消息消费者消费了两次,此时<span style="letter-spacing: 0.578px;text-wrap: wrap;">消费者</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">需要</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">自己做幂等性的处理。</span></span></p> <p><strong><span style="letter-spacing: 0.578px;text-wrap: wrap;">3、</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">MQ的</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">幂等性</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">解决方案</span></strong><span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></p> <p> 分析了<span style="letter-spacing: 0.578px;text-wrap: wrap;">MQ出现幂等性问题</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">的原因之后我们需要对消费者端做一些幂等性措施来保证实际的业务安全性。常见的方案如下所示:</span></p> <p><strong>(1)查询法</strong></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000905" data-ratio="0.24296962879640044" data-s="300,640" data-type="png" data-w="889" style="height: auto !important;" src="/upload/6e89f143825bd0dd9fc9f8be66b94e24.png"></p> <p> 方案的原理:根据消息中的业务id(如订单的id)查询数据库中当前的业务是否执行过,如果业务执行过就不再处理当前消息。</p> <p> 方案存在缺陷,在高并发下会出现无法保证消息的幂等性问题,如下图所示的场景:</p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000907" data-ratio="1.080536912751678" data-s="300,640" data-type="png" data-w="447" style="width: 407px;height: auto !important;" src="/upload/838e61775a575ddc03a3094f8d721159.png"></p> <p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"> </span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">线程A和线程</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">B</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">同时到达查询</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">位置,</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">此时查询</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">的时候</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">没有</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">数据,此时线程A</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">继续</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">持有时间片,线程B时间</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">片</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">用完,</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">那么线程A</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">执行</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">业务并操作数据库,同时</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">线程</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">B又获得时间片开始</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">执行业务处理。</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">最终的结果就是线程A和线程B</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">执行了两次数据库操作</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">。所以高并发下查询法没有办法完成消息幂等性问题。</span><span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></p> <p><strong><span style="letter-spacing: 0.578px;text-wrap: wrap;">(2)加悲观锁方案</span></strong><span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000908" data-ratio="0.3446327683615819" data-s="300,640" data-type="png" data-w="531" style="height: auto !important;" src="/upload/49e9f0274df9a592cb54c23e91fc6062.png"></p> <p> 查询法中在高并发下仍然存在消息幂等性的问题,那么针对查询法的缺陷可以使用在底层加悲观锁的方案来解决,即就是查询的时候添加for update,这样可以保证在一个线程事务中加锁成功后,其他的线程就必须等待释放锁才能操作,如下图所示:<span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000909" data-ratio="0.3693181818181818" data-s="300,640" data-type="png" data-w="704" style="height: auto !important;" src="/upload/5d13ef7c0664a292011bcd0d531f81f3.png"></p> <p> 悲观锁可以很好的解决<span style="letter-spacing: 0.578px;text-wrap: wrap;">消息幂等性的问题,但是悲观锁的并发度低是一个不容忽视的性能问题。由于业务在事务中加锁了,如果业务相对复杂的情况下,消息的消费也会变长,那么在高并发下消息的消费速度会比较慢。</span></p> <p><strong><span style="letter-spacing: 0.578px;text-wrap: wrap;">(3)乐观锁机制方案</span></strong><span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></p> <p><span style="letter-spacing: 0.578px;text-wrap: wrap;"> 通过上述的分析我们知道悲观锁的执行效率是比较低的,我们可以采用了乐观锁的机制替代悲观锁,如下所示:<br></span></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000910" data-ratio="0.3333333333333333" data-s="300,640" data-type="png" data-w="996" style="height: auto !important;" src="/upload/2c4a0b4b03945efc6136de2d58bf6dc6.png"></p> <p> 在消息生产的时候携带消息的id、消息的版本号,然后在消费端根据乐观锁原理来执行,即就是根据id和version做更新操作,通过判断影响行是否大于0来判断执行是否成功;如果影响行数大于0表示当前的消息执行完成,反之就执行失败,执行失败不做抛异常。<span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></p> <p> 乐观锁机制虽然可以提高系统的并发度但是它对业务有侵入,生产者不仅要携带参数id,现在还需要携带version传到下游中,这样给开发带来了一些不便。<br></p> <p><strong>(4)去重表方案</strong></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000912" data-ratio="0.41284403669724773" data-s="300,640" data-type="png" data-w="981" style="height: auto !important;" src="/upload/4b33724c33450264ca82eac46518834a.png"></p> <p> 消费者先将消息中的唯一键(消息的id或者业务中的唯一键如订单id)获取到,然后数据插入到表message中(message表中设置msg_id为唯一键),如果数据添加成功就放行继续执行相关的业务逻辑;如果添加失败我们需要catch到异常,如果是DuplicateKeyException就直接吞掉异常并提示消息消费成功。核心的代码如下:<span style="letter-spacing: 0.578px;text-wrap: wrap;"><br></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="javascript"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">try</span> {</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">1</span>、添加数据到message表</span></code><code><span class="code-snippet_outer"> messageMapper.addMessage(message);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">2</span>、执行业务逻辑</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">this</span>.dealMessage(message);</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__number">3</span>、执行业务成功后 返回消息消费成功的标识</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> <span class="code-snippet__built_in">Boolean</span>.TRUE;</span></code><code><span class="code-snippet_outer">}<span class="code-snippet__keyword">catch</span>(DuplicateKeyException de){</span></code><code><span class="code-snippet_outer"> log.warn(<span class="code-snippet__string">"消息重复 messageId:{}"</span>, message.getId())</span></code><code><span class="code-snippet_outer"> <span class="code-snippet__keyword">return</span> <span class="code-snippet__built_in">Boolean</span>.TRUE;</span></code><code><span class="code-snippet_outer">}</span></code></pre> </section> <p> 基于<span style="letter-spacing: 0.578px;text-wrap: wrap;">去重表方案由于<span style="letter-spacing: 0.578px;text-wrap: wrap;">依赖的只是消息表而与<span style="letter-spacing: 0.578px;text-wrap: wrap;">具体业务本身</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">无关,所以此方案</span></span>可以扩展</span>到不同的应用场景中。</p> <p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"> <span style="letter-spacing: 0.578px;text-wrap: wrap;">去重表方案也存在一定的</span>局限性,如</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">消息的消费逻辑必须是依赖于关系型数据库事务,</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">如果消费过程中还涉及不支持事务的数据源数据的修改(如ES、Redis),那么执行过程有异常无法回滚数据。还要求</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">数据库的数据必须是在一个库中,不支持跨库操作。</span></p> <p><strong><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">(5)非事务的</span></strong><strong><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">去重表方</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">案</span></strong><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"></span></p> <p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="letter-spacing: 0.578px;text-wrap: wrap;"> 如果现在</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">业务</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">需要将数据使用RPC的方式同步通知其他的系统</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">,</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">那么</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">现在调整消费者消费</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">方案,如下所示</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">:</span></span></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000915" data-ratio="0.587737843551797" data-s="300,640" data-type="png" data-w="946" style="height: auto !important;" src="/upload/5678fb7d645d43a97572509cc8f41fe4.png"></p> <p>(1)消费者拉去MQ队列中的消息开始执行消费的逻辑,首先将消息信息携带过期时间的方式添加到数据表中,如下添加数据的sql:</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="sql"><code><span class="code-snippet_outer"><span class="code-snippet__keyword">insert</span> message (msg_id, <span class="code-snippet__keyword">desc</span>, exprire_time, <span class="code-snippet__keyword">status</span>) <span class="code-snippet__keyword">values</span> </span></code><code><span class="code-snippet_outer">(<span class="code-snippet__number">1</span>,<span class="code-snippet__string">'MQ消息'</span>, <span class="code-snippet__number">5</span>, <span class="code-snippet__number">0</span>) </span></code></pre> </section> <p>过期时间expire_time设置成5表示5分钟有效。</p> <p>(2)根据数据库插入消息是否成功相应的逻辑处理<br></p> <p> (a)MQ的消息添加数据库成功;首先完成消息的本地业务,然后使用RPC通知其他的系统,如果通知其他系统成功,此时将消息表数据状态修改成消费成功(status:0 -> 1),最后通知MQ本消息消费成功;如果通知其他系统失败就回滚业务、删除消息表的记录,通知MQ消息消费失败。等待下一次的推送继续业务处理。<br></p> <p> (b)MQ<span style="letter-spacing: 0.578px;text-wrap: wrap;">消息添加数据库失败;</span>查询已经添加到数据库中的MQ消息的的数据(如数据库中的数据:msgId=1, desc='MQ消息', expireTime=5 , status=0),根据数据status的值做判断:<span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">如果status=0</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">(表示消息正在消费中</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">)此时就要返回给MQ</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">消息</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">消费失败,</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">等待下一次继续推送</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">;<span style="letter-spacing: 0.578px;text-wrap: wrap;"></span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;">如果status=1(表示<span style="letter-spacing: 0.578px;text-wrap: wrap;">消</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">息消费成功</span>),此时<span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;">给MQ返回</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;">消息</span><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;">消费成功。</span></span></span></p> <p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;">(3)开启定时任务(如XXL-Job)每隔一段时间(如3分钟执行一次),拉取数据库中过期的消息数据,然后做删除操作。<span style="letter-spacing: 0.578px;text-wrap: wrap;">这里</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">设置过期时间的</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">主要目的是防止</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">出现</span><span style="letter-spacing: 0.578px;text-wrap: wrap;">死信的问题,如下所示:</span></span></span></span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100000916" data-ratio="0.5584415584415584" data-s="300,640" data-type="png" data-w="770" style="height: auto !important;" src="/upload/0db90763797c12492e17366bbb892b3a.png"></p> <p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;"><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;text-wrap: wrap;"><span style="letter-spacing: 0.578px;text-wrap: wrap;"> 第一条消息在处理中,由于某种原因一致处理很慢导致最终是失败了,第二条消息(其实是重复的消息)会走一遍先添加的操作,此时添加是失败的,那么会不断的走重试逻辑,重试一定次数之后就会加入到死信的队列中。添加了定时任务就是清理这些过期的数据保证下一次消息就可以执行正常的消费逻辑。</span><span style="letter-spacing: 0.578px;text-wrap: wrap;"></span></span></span></span></p> <p> 至此我们就完成利用消息的重试机制完成幂等+分布式事务的处理。</p> <p>总结:</p> <p>(1)查询法<span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">去重解决方案是<span style="letter-spacing: 0.578px;text-wrap: wrap;">先判断再操作</span>,但是会有并发重复消费的问题,针对并发去重问题可以借助select for update悲观锁或者乐观锁来解决。</span></p> <p><span style="font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;">(2)去重表方案可以很好的处理消息幂等问题,但是无法支持跨库操作以及<span style="letter-spacing: 0.578px;text-wrap: wrap;">不支持事务的数据源数据数据的修改,为此引入了非事务消息幂等性方案。</span></span></p> <p></p> <p></p> <p></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<h2 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(64, 184, 250);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 4px;border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;line-height: 1.75em;"><span style="font-size: 15px;"><span style="color: rgb(64, 184, 250);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 25px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">前言</span></span></h2> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"> <span style="font-size: 15px;">有些时候在进行一些业务迭代时需要我们对Mysql表中数据进行全表update,如果是在数据量比较小的情况下(万级别),可以直接执行sql语句,但是如果数据量达到一个量级后,就会出现一些问题,比如主从架构部署的Mysql,主从同步需要需要binlog来完成,而binlog格式如下,其中使用statement和row格式的主从同步之间binlog在update情况下的展示:</span> </section> <section style="text-align: center;line-height: 1.75em;"> <img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100027991" data-ratio="0.30462962962962964" data-s="300,640" src="/upload/a8a68a032bea65e1c7cccb6a391488fd.png" data-type="png" data-w="1080" style=""> </section> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <img class="rich_pages wxw-img" data-imgfileid="100027992" data-ratio="0.44814814814814813" src="/upload/418e29a3a8e02829c355d11def39c71b.png" data-type="other" data-w="1080"> </section> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">我们当前线上mysql是使用row格式binlog来进行的主从同步,因此如果在亿级数据的表中执行全表update,必然会在主库中产生大量的binlog,接着会在进行主从同步时,从库也需要阻塞执行大量sql,风险极高,因此直接update是不行的。本文就从我最开始的一个全表update sql开始,到最后上线的分批更新策略,如何优化和思考来展开说明。</span></p> <h2 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(64, 184, 250);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 4px;border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(64, 184, 250);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 25px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">正文</span></span></h2> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;background-attachment: scroll;background-clip: border-box;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/TLH3CicPVibrfGAIJD9UDNrzGdg9VPibr9oc6YI1mXDzPj0icROEtakxuWn2GfwJqAbM5J36yeztDXzrJ2z9QNuHlQ/640?wx_fmt=png&from=appmsg");background-origin: padding-box;background-position: 50% 50%;background-repeat: no-repeat;background-size: 30px 30px;width: auto;height: auto;align-items: unset;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;-webkit-box-reflect: unset;"><p style="font-size: 15px;line-height: 2em;"><span style="color: rgb(43, 43, 43);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;overflow: unset;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">直接update的问题</span></p></h3> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">我们前段时间需要将用户的一些基本信息存储从http转换为https,库中数据大概在几千w的级别,需要对一些大表进行全表update,最开始我试探性的跟dba同事抛出了一个简单的 update 语句,想着流量低的时候执行,如下:</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">update</span> tb_user_info <span style="color: rgb(198, 120, 221);line-height: 26px;">set</span> user_img=<span style="color: rgb(198, 120, 221);line-height: 26px;">replace</span>(user_img,<span style="color: rgb(152, 195, 121);line-height: 26px;">'http://'</span>,<span style="color: rgb(152, 195, 121);line-height: 26px;">'https://'</span>)<br></span></p></pre> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">这里也给大家提一个醒,在存储图片等 path 路径时,经历不要存储协议和域名之内的前缀部分。如果要改 http 协议、域名之类的就要涉及批量更新等操作,同时存储的容量也会不必要的增加。</span></p> <h2 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(64, 184, 250);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 4px;border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(64, 184, 250);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 25px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">深度分页问题</span></span></h2> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">上面肯定是不合理的会给主库生成binlog、从库接收binlog写数据带来很大的压力,于是就想使用脚本分批处理如下所示:写一个这样的脚本,依次分批替换,limit的游标不断增加。大概一看是没有问题的,但是仔细一想mysql的limit游标进行的范围查找原理,是下沉到B+数的叶子节点进行的向后遍历查找,在limit数据比较小的情况下还好,limit数据量比较大的情况下,效率很低接近于全表扫描,这也就是我们常说的“深度分页问题”。</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">update</span> tb_user_info <span style="color: rgb(198, 120, 221);line-height: 26px;">set</span> user_img=<span style="color: rgb(198, 120, 221);line-height: 26px;">replace</span>(user_img,<span style="color: rgb(152, 195, 121);line-height: 26px;">'http://'</span>,<span style="color: rgb(152, 195, 121);line-height: 26px;">'https://'</span>) <span style="color: rgb(198, 120, 221);line-height: 26px;">limit</span> <span style="color: rgb(209, 154, 102);line-height: 26px;">1</span>,<span style="color: rgb(209, 154, 102);line-height: 26px;">1000</span>;<br></span></p></pre> <h2 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(64, 184, 250);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 4px;border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(64, 184, 250);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 25px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">in的效率</span></span></h2> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">既然mysql的深分页有问题,那么我就把这批id全部查出来,然后更新的id in这些列表,进行批量更新可以吗?于是我又写了类似下面sql的脚本。结果是还不行,虽然mysql对于in这些查找有一些键值预测,但是仍然是很低效。</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;line-height: 2em;"><br><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">select</span> * <span style="color: rgb(198, 120, 221);line-height: 26px;">from</span> tb_user_info <span style="color: rgb(198, 120, 221);line-height: 26px;">where</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">id</span>> {<span style="color: rgb(198, 120, 221);line-height: 26px;">index</span>} <span style="color: rgb(198, 120, 221);line-height: 26px;">limit</span> <span style="color: rgb(209, 154, 102);line-height: 26px;">100</span>;<br><span style="color: rgb(198, 120, 221);line-height: 26px;">update</span> tb_user_info <span style="color: rgb(198, 120, 221);line-height: 26px;">set</span> user_img=<span style="color: rgb(198, 120, 221);line-height: 26px;">replace</span>(user_img,<span style="color: rgb(152, 195, 121);line-height: 26px;">'http'</span>,<span style="color: rgb(152, 195, 121);line-height: 26px;">'https'</span>)<span style="color: rgb(198, 120, 221);line-height: 26px;">where</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">id</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">in</span> {id1,id3,id2};<br></span></p></pre> <h2 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(64, 184, 250);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 4px;border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(64, 184, 250);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 25px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">最终版本</span></span></h2> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">最终在与dba的多次沟通下,我们写了如下的sql及脚本,这里有几个问题需要注意,我们在select sql中使用了这个语法</span><code style="color: rgb(53, 148, 247);line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span style="font-size: 15px;">/*!40001 SQL_NO_CACHE */</span></code><span style="font-size: 15px;">,这个语法的意思就是本次查询不使用innodb的buffer pool,也不会将本次查询的数据页放到buffer pool中作为热点数据的缓存。接着对于查询强制使用主键索引 </span><code style="color: rgb(53, 148, 247);line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span style="font-size: 15px;">FORCE INDEX(</span></code><span style="font-size: 15px;">PRIMARY</span><code style="color: rgb(53, 148, 247);line-height: 1.8em;letter-spacing: 0em;background: none 0% 0% / auto no-repeat scroll padding-box border-box rgba(27, 31, 35, 0.05);width: auto;height: auto;margin-left: 2px;margin-right: 2px;padding: 2px 4px;border-style: none;border-width: 3px;border-color: rgb(0, 0, 0) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);border-radius: 4px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;"><span style="font-size: 15px;">)</span></code><span style="font-size: 15px;"> ,并且根据主键索引排序,排序后的数据进行id游标的筛选。最后执行update更新时,由于我们在前面的sql中查询到的就是已经排序后的主键,因此可以对id执行范围查找。</span></p> <pre data-tool="mdnice编辑器" style="border-radius: 5px;box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;margin-top: 10px;margin-bottom: 10px;"><p style="overflow-x: auto;padding: 15px 16px 16px;color: rgb(171, 178, 191);background: rgb(40, 44, 52);border-radius: 5px;display: -webkit-box;font-family: Consolas, Monaco, Menlo, monospace;font-size: 12px;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(198, 120, 221);line-height: 26px;">select</span> <span style="color: rgb(92, 99, 112);font-style: italic;line-height: 26px;">/*!40001 SQL_NO_CACHE */</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">id</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">from</span> tb_user_info <span style="color: rgb(198, 120, 221);line-height: 26px;">FORCE</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">INDEX</span>(<span style="color: rgb(152, 195, 121);line-height: 26px;">`PRIMARY`</span>) <span style="color: rgb(198, 120, 221);line-height: 26px;">where</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">id</span>> <span style="color: rgb(152, 195, 121);line-height: 26px;">"1"</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">ORDER</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">BY</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">id</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">limit</span> <span style="color: rgb(209, 154, 102);line-height: 26px;">1000</span>,<span style="color: rgb(209, 154, 102);line-height: 26px;">1</span>;<br><br><span style="color: rgb(198, 120, 221);line-height: 26px;">update</span> tb_user_info <span style="color: rgb(198, 120, 221);line-height: 26px;">set</span> user_img=<span style="color: rgb(198, 120, 221);line-height: 26px;">replace</span>(user_img,<span style="color: rgb(152, 195, 121);line-height: 26px;">'http'</span>,<span style="color: rgb(152, 195, 121);line-height: 26px;">'https'</span>) <span style="color: rgb(198, 120, 221);line-height: 26px;">where</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">id</span> ><span style="color: rgb(152, 195, 121);line-height: 26px;">"{1}"</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">and</span> <span style="color: rgb(198, 120, 221);line-height: 26px;">id</span> <<span style="color: rgb(152, 195, 121);line-height: 26px;">"{2}"</span>;<br></span></p></pre> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">我们可以仅关注第一个sql,如下图所示,是buffer pool大概内容,我们可以通过这个no cache的关键字,对批量处理的数据进行强制指定不走buffer pool,不把这些冷数据影响到正常使用的缓存内容,防止效率的降低,其实mysql在一些备份的动作中。使用的数据扫描sql也会带上这个关键字,防止影响到正常的业务缓存;接着需要强制对当前查询指定的主键索引,然后进行排序,否则mysql有可能在计算io成本进行索引选择时,选择其他的索引。</span></p> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="color: rgba(0, 0, 0, 0.9);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"></span><span style="font-size: 15px;"><img class="rich_pages wxw-img" data-imgfileid="100027993" data-ratio="0.2914798206278027" src="/upload/d822b1db045a067e5d4c8e41359a4267.png" data-type="other" data-w="669"></span></p> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">使用这样的方式对数据库进行批量更新可以通过一个接口来控制速率,对于数据库主从同步、iops、内存使用率等关键属性进行观察,手动调整刷库速率。这样看是单线程阻塞的操作,其实接口也可以定义线程个数等属性,接口中根据赋予的线程个数,通过线程池并行刷数据,从而提高全表更新速率的上限,同时对速率进行控制控制。</span></p> <h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;background-attachment: scroll;background-clip: border-box;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/TLH3CicPVibrfGAIJD9UDNrzGdg9VPibr9oc6YI1mXDzPj0icROEtakxuWn2GfwJqAbM5J36yeztDXzrJ2z9QNuHlQ/640?wx_fmt=png&from=appmsg");background-origin: padding-box;background-position: 50% 50%;background-repeat: no-repeat;background-size: 30px 30px;width: auto;height: auto;align-items: unset;box-shadow: none;display: flex;flex-direction: unset;float: unset;justify-content: center;line-height: 1.5em;overflow: unset;text-shadow: none;transform: none;-webkit-box-reflect: unset;"><p style="font-size: 15px;line-height: 2em;"><span style="color: rgb(43, 43, 43);line-height: 2.4em;letter-spacing: 0em;margin-top: 38px;margin-bottom: 10px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: inline-block;font-weight: bold;flex-direction: unset;float: unset;height: 38px;justify-content: unset;overflow: unset;text-align: center;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">其他问题</span></p></h3> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">如果我们使用snowflake雪花算法或者自增主键来生成主键id的话,插入的记录都是根据主键id顺序插入的,如果使用uuid这种我们怎么处理?当然是业务中就预先处理了,先把入库的数据提前进行替换,进行代码上线后再进行的全量数据更新了。</span></p> <h2 data-tool="mdnice编辑器" style="border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(64, 184, 250);margin-top: 30px;margin-bottom: 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 4px;border-radius: 0px;box-shadow: none;flex-direction: unset;float: unset;height: auto;justify-content: unset;overflow: unset;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;line-height: 2em;"><span style="font-size: 15px;"><span style="color: rgb(64, 184, 250);line-height: 1.5em;letter-spacing: 0em;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none;border-width: 1px;border-color: rgb(0, 0, 0);border-radius: 0px;box-shadow: none;display: flex;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-left: 25px;overflow: unset;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset;">结语</span></span></h2> <p style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 2em;"><span style="font-size: 15px;">刷数据本来是一个异常枯燥的工作内容,但是从这次数据量较大的数据更新从而与dba同事的多次沟通后,也对mysql有了一些新的理解,包括不限于下面几个,共同学习。</span></p> <ol data-tool="mdnice编辑器" style="margin-top: 8px;margin-bottom: 8px;padding-left: 25px;" class="list-paddingleft-1"> <li style="font-size: 15px;"> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(89, 89, 89);font-size: 14px;letter-spacing: 0.02em;line-height: 1.75em;"> <span style="font-size: 15px;">binlog格式带来的大数据量更新的主从同步问题;</span> </section></li> <li style="font-size: 15px;"> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(89, 89, 89);font-size: 14px;letter-spacing: 0.02em;line-height: 1.75em;"> <span style="font-size: 15px;">Mysql深分页的效率问题;</span> </section></li> <li style="font-size: 15px;"> <section style="margin-top: 5px;margin-bottom: 5px;color: rgb(89, 89, 89);font-size: 14px;letter-spacing: 0.02em;line-height: 1.75em;"> <span style="font-size: 15px;">全表扫数据如何防止对buffer pool污染到我们业务正常的热点数据。</span> <span style="text-align: center;color: rgba(0, 0, 0, 0.9);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: var(--articleFontsize);letter-spacing: 0.034em;"></span> </section></li> </ol> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;">群友问题:</span> </section> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;">Q:第一个sql如果不走buffer pool,第二个更新sql也会把数据页载入到buffer pool吧?</span> </section> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;">A:读缓存和写缓存是不一样的。</span> </section> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;">Q:只要我知道min_id、max_id,只要序列差不多连续是不是可以直接分片执行,不需要一定要每次1000条执行的吧?</span> </section> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;">A:min和max这样直接分片的话,除非是自增id,否则是不能保证匀速的,后续多线程执行的任务分配也不能得到保证。</span> </section> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;">Q:写缓存指的是change buffer?这个修改应该用不了change buffer吧?</span> </section> <section style="color: rgb(43, 43, 43);font-size: 14px;letter-spacing: 0.02em;text-indent: 0em;padding-top: 8px;padding-bottom: 8px;line-height: 1.75em;"> <span style="font-size: 15px;">A:是,用得到。</span> </section>
作者:じ☆ve宝贝
> 修改宝塔webhook发送源码,可以在消息体中增加变量,替换告警内容。当前支持替换标题($title),类型($type), 以及消息内容($msg) ## 1. webhook源码地址 ``` cd /www/server/panel/class/msg mv web_hook_msg.py web_hook_msg.py.20241012 ``` ## 2. 新的web_hook_msg.py ``` # coding: utf-8 # +------------------------------------------------------------------- # | 宝塔Linux面板 # +------------------------------------------------------------------- # | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved. # +------------------------------------------------------------------- # | Author: baozi <baozi@bt.cn> # | 消息通道HOOK模块 # +------------------------------------------------------------------- import os import sys import json import re import copy import requests from typing import Optional, List, Dict, Any from urllib3.util import parse_url panel_path = "/www/server/panel" if os.getcwd() != panel_path: os.chdir(panel_path) if panel_path + "/class/" not in sys.path: sys.path.insert(0, panel_path + "/class/") import public class HooKConfig(object): _CONFIG_FILE = '{}/data/hooks_msg.json'.format(panel_path) def __init__(self): self._config: Optional[List[Dict[str, Any]]] = None def __getitem__(self, item: str) -> Optional[Dict[str, Any]]: if self._config is None: self._read_config() for d in self._config: if d.get('name', '') == item: return d return None def __setitem__(self, key: str, value: Dict[str, Any]): if self._config is None: self._read_config() if not isinstance(key, str) or not isinstance(value, dict): raise ValueError('参数类型错误') for idx, d in enumerate(self._config): if d.get('name', '') == key: target_idx = idx break else: target_idx = -1 value.update(name=key) if target_idx == -1: self._config.append(value) else: self._config[target_idx] = value self.save_to_file() def __delitem__(self, key): if self._config is None: self._read_config() target_idx = -1 for i, d in enumerate(self._config): if d.get('name', '') == key: target_idx = i break if target_idx != -1: del self._config[target_idx] self.save_to_file() return None def _read_config(self): data = [] if os.path.exists(self._CONFIG_FILE): js_data = public.readFile(self._CONFIG_FILE) if isinstance(js_data, str): try: data = json.loads(js_data) except json.JSONDecodeError: data = [] self._config = data def to_view(self) -> list: if self._config is None: self._read_config() return copy.deepcopy(self._config) def save_to_file(self): if self._config is None: self._read_config() public.writeFile(self._CONFIG_FILE, json.dumps(self._config)) @staticmethod def get_version_info(): """ 获取版本信息 """ data = { 'ps': '宝塔WEB HOOK消息通道,用于接收面板消息推送', 'version': '1.0', 'date': '2023-10-30', 'author': '宝塔', 'title': 'WEB HOOK', 'help': 'https://www.bt.cn/bbs/thread-121791-1-1.html' } return data @classmethod def clear_config(cls): if os.path.exists(cls._CONFIG_FILE): os.remove(cls._CONFIG_FILE) def set_all(self, status: bool): if self._config is None: self._read_config() for v in self._config: v["status"] = status self.save_to_file() def all_hook_name(self): names = [] for v in self._config: if v["status"] is True: names.append(v["name"]) return names _cfg = HooKConfig() # default = { # "name": "default", # "url": "https://www.bt.cn", # "query": { # "aaa": "111" # }, # "header": { # "AAA": "BBBB", # }, # "body_type": ["json", "form_data", "null"], # "custom_parameter": { # "rrr": "qqqq" # }, # "method": ["GET", "POST", "PUT", "PATCH"], # "ssl_verify": [True, False] # } # # # # 1.自动解析Query参数,拼接并展示给用户 # 可不做 # # 2.自定义Header头 # 必做 # # 3.Body中的内容是: type:str="首页磁盘告警", time:int=168955427, data:str="xxxxxx" # ? # # 4.自定义参数: key=value 添加在Body中 # 可不做 # # 5.请求类型自定义 # 必做 # # # 以上内容需要让用户可测试--! class RealHook(object): DEFAULT_HEADERS = { "User-Agent": "BT-Panel", } def __init__(self, hook_name, name: str = None, config: dict = None): if name is not None and config is not None: self.name = hook_name self._config = config return if not hook_name: raise ValueError("hook_name 不能为空") if _cfg[hook_name] is None: raise ValueError("没有配置指定的HOOK") self.name = hook_name self._config = _cfg[hook_name] def _replace_and_parse(self, value, real_data): """替换占位符并递归解析JSON字符串""" if isinstance(value, str): if value == '$title': value = real_data['title'] elif value == '$msg': value = real_data['msg'] elif value == '$type': value = real_data['type'] else: value = value.replace("$1", json.dumps(real_data, ensure_ascii=False)) elif isinstance(value, dict): for k, v in value.items(): value[k] = self._replace_and_parse(v, real_data) return value def send_msg(self, msg: str, title, push_type) -> Optional[str]: if self._config['status'] is False: return "该通道已关闭,不再发送" ssl_verify = self._config.get("ssl_verify", None) the_url = parse_url(self._config['url']) if ssl_verify is None: ssl_verify = the_url.scheme == "https" real_data = { "title": title, "msg": msg, "type": push_type, } # 处理custom_parameter,将$1替换为real_data内容并递归解析 custom_data = {} for k, v in self._config.get("custom_parameter", {}).items(): custom_data[k] = self._replace_and_parse(v, real_data) if custom_data: real_data = custom_data data = None json_data = None headers = self.DEFAULT_HEADERS.copy() if self._config["body_type"] == "json": json_data = real_data elif self._config["body_type"] == "form_data": data = real_data for k, v in self._config.get("headers", {}).items(): if not isinstance(v, str): v = str(v) headers[k] = v if data: for k, v in data.items(): if isinstance(v, str): continue else: data[k]=json.dumps(v) timeout = 5 for i in range(3): try: if json_data is not None: res = requests.request( method=self._config["method"], url=str(the_url), json=json_data, headers=headers, timeout=timeout, verify=False, ) else: res = requests.request( method=self._config["method"], url=str(the_url), data=data, headers=headers, timeout=timeout, verify=False, ) if res.status_code == 200: return None else: return res.text except requests.exceptions.Timeout: timeout += 5 continue except requests.exceptions.RequestException as e: return str(e) except Exception as e: return "发送失败,疑似是系统环境因素导致" + str(e) +",verify="+str(ssl_verify)+",url="+str(the_url) return None class web_hook_msg: _MODULE_NAME = "hook" def __init__(self, name: str = None): if name is None: self._real_hook = None elif _cfg[name] is None: self._real_hook = None else: self._real_hook = RealHook(name) @staticmethod def get_version_info(get=None): """ 获取版本信息 """ return _cfg.get_version_info() @staticmethod def get_config(get=None): """ 获取配置 """ return _cfg.to_view() @staticmethod def set_status(status: bool, name=None) -> None: if name is None: _cfg.set_all(status) else: if _cfg[name] is not None: _cfg[name]["status"] = status @staticmethod def del_hook_by_name(name: str) -> None: del _cfg[name] @staticmethod def set_config(get): """配置hook""" try: hook_data = get.hook_data if isinstance(hook_data, str): hook_data = json.loads(hook_data) else: return ValueError name = hook_data['name'] url = hook_data["url"] query = hook_data.get("query", {}) headers = hook_data.get("headers", {}) body_type = hook_data.get("body_type", "json") custom_parameter = hook_data.get("custom_parameter", {}) method = hook_data.get("method", "POST") ssl_verify = hook_data.get("ssl_verify", None) # null Ture status = bool(hook_data.get("status", True)) except (ValueError, KeyError, json.JSONDecodeError, AttributeError): return public.returnMsg(False, "参数错误") test_or_save = int(getattr(get, "test_or_save", "0")) try: the_url = parse_url(url) if the_url.scheme is None or the_url.host is None: # 如果解析结果表明这不是一个有效的URL,则返回错误信息 return public.returnMsg(False, "URL字段不是一个有效的URL") except: return public.returnMsg(False, "URL字段不是一个有效的URL") for i in (query, headers, custom_parameter): if not isinstance(i, dict): return public.returnMsg(False, "参数错误") if body_type not in ('json', 'form_data', 'null'): return public.returnMsg(False, "body_type必须为json,form_data或者null") if method not in ('GET', 'POST', 'PUT', 'PATCH'): return public.returnMsg(False, "发送方式选择错误") if ssl_verify not in (True, False, None): return public.returnMsg(False, "是否验证ssl选项错误") name = name.strip() if name == "": return public.returnMsg(False, "名称不能为空") if name in ("dingding", "feishu", "mail", "sms", "weixin", "wx_account", "web_hook"): return public.returnMsg(False, "不能使用包含歧义的名称") the_conf = { "url": url, "query": query, "headers": headers, "body_type": body_type, "custom_parameter": custom_parameter, "method": method, "ssl_verify": ssl_verify, "status": True } if test_or_save == 1: hook = RealHook(hook_name="", name=name, config=the_conf) res = hook.send_msg( msg="宝塔面板自定义HOOK通道-测试信息", title="测试信息", push_type="测试信息" ) if res is None: return public.returnMsg(True, "测试信息发送成功") else: return public.returnMsg(False, "测试信息发送失败" + res) else: the_conf['status'] = status _cfg[name] = the_conf return public.returnMsg(True, "配置保存成功") @staticmethod def get_send_msg(msg): """ @name 处理md格式 """ title = None if msg.find("####") >= 0: try: title = re.search(r"####(.+)\n", msg).groups()[0] except KeyError: pass msg = msg.replace("\n\n", "<br>").strip() pass return msg, title def send_msg(self, msg, title: str = '宝塔面板消息推送', push_type: str = 'unknown'): """ 触发web_hook, 发送信息 """ if self._real_hook is None: public.returnMsg(False, "未指定对应的hook用于发送") error, success, total = 0, 0, 1 msg, n_title = self.get_send_msg(msg) if n_title: title = n_title error_msg = self._real_hook.send_msg(msg, title, push_type) if error_msg is None: status_msg = '<span style="color:#20a53a;">成功</span>' success += 1 else: status_msg = '<span style="color:red;">失败</span>' error += 1 log = '标题:【{}】,通知方式:【Api-{}】,发送状态:{}'.format(title, self._real_hook.name, status_msg) public.WriteLog('告警通知', log) result = public.returnMsg(True, '发送完成,共发送【{}】条,成功【{}】,失败【{}】。'.format(total, success, error)) result['error_msg'] = error_msg result['success'] = success result['error'] = error return result def push_data(self, data): if "hook_name" in data: self._real_hook = RealHook(data.get("hook_name")) if "push_type" in data: push_type = data.get("push_type") return self.send_msg(data['msg'], data['title'], push_type) return self.send_msg(data['msg'], data['title']) @staticmethod def uninstall(): _cfg.clear_config() @staticmethod def get_all_hooks_name(): return _cfg.all_hook_name() ``` ## 3. 重启宝塔面板 ``` bt 1 ``` ## 4. 宝塔webhook配置 - 重启后就可以在自定义参数中替换变量了,当前支持 $title, $msg, $type  
作者:微信小助手
<p> <span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">随着社交网络的蓬勃发展,点赞功能逐渐成为了一个网站中不可或缺的功能。因为点赞功能不仅可以让用户更直观地了解自己的视频、文章等内容被多少人认可,而且也提升了用户互动体验感。下面我们来聊聊通用的点赞系统设计的方案。</span></p> <p><strong><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">1、点赞系统的数据表设计</span></strong><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"><br></span></p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"> 在设计数据表的时候我们需要知道点赞系统需要完成的基础功能有哪些,点赞系统</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">通常需要实现以下功能:</span></p> <p>(1)用户可以点赞一个视频、文章、评论等内容</p> <p>(2)用户可以查看一个<span style="letter-spacing: 0.578px;">视频、文章、评论</span>等内容的点赞数</p> <p>(3)用户可以取消对<span style="letter-spacing: 0.578px;">视频、文章、评论</span>等内容的点赞</p> <p> 针对如上所示的功能,我们可以设计一张点赞记录表和点赞计数表来记录数据,如下是两张表的字段设计:<br></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100001418" data-ratio="0.3371868978805395" data-s="300,640" src="/upload/fdff1fc1d38c28bce6ef365027243785.png" data-type="png" data-w="519" style=""></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100001419" data-ratio="0.38424821002386633" data-s="300,640" src="/upload/9af0af6739fb4d5dfb7305820d0a5744.png" data-type="png" data-w="419" style=""></p> <p> 点赞计数表中记录了稿件(<span style="letter-spacing: 0.578px;">视频、文章、评论等等</span>)被点赞和取消点赞的总数,用作总的点赞数据展示;点赞记录表用于记录哪些用户在何时给哪个稿件点赞或取消点赞。<span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"></span></p> <p>2、系统设计<span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;"></span></p> <p><strong>2.1 点赞数据写入的设计方案</strong></p> <p style="text-align: left;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100001422" data-ratio="0.37904015670910873" data-s="300,640" src="/upload/8ccf31f7ec77fe280bf995d844abea85.png" data-type="png" data-w="1021" style=""></p> <p> 点赞系统一般流量是比较大的,特别是在某个稿件突然成为热点之后,那么流量就会突增上来,为了应对大流量,我们在设计点赞系统的时候采用MQ来做削峰处理,整个点赞数据写入的流程如下所示:</p> <p>(1)用户发送来点赞请求,经过Nginx和网关转发到点赞服务上,点赞服务组装必要的数据(稿件的id、稿件用户id等数据)发送MQ消息,并发响应客户端写入点赞数据成功。</p> <p>(2)点赞服务消费MQ消息,首先要保存点赞的数据,在保存点赞数据的时候需要做一些逻辑检验工作,如下的流程图所示:</p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100001428" data-ratio="0.4833729216152019" data-s="300,640" src="/upload/3a2f828e8d5bd9c86acb27faf511b6af.png" data-type="png" data-w="842" style=""></p> <p> 首先根据用户的id和点赞的稿件id查询数据库获取用户的点赞记录数据,根据查询的结果分如下的情况分析:</p> <p>(a)如果没有查询到用户的点赞记录数据,那么直接保存用户的点赞记录到记录表中,将点赞计数表中的总的点赞数量加1。</p> <p>(b)如果已经存在了用户的点赞记录,那么就需要根据点赞的时间和点赞的动作进一步的检查<br></p> <p> (b1)数据表中的点赞时间 > MQ中用户的点赞时间,说明可能存在重复的点赞,此时我们这表MQ消息直接丢弃。<br></p> <p> (b2)<span style="letter-spacing: 0.578px;">数据表中的</span><span style="letter-spacing: 0.578px;">点赞时间 < </span><span style="letter-spacing: 0.578px;">MQ</span><span style="letter-spacing: 0.578px;">中用户的点赞时间,比较数据库中当前的用户点赞状态是否为点赞,如果是点赞状态那么当前的MQ也不消费了,如果是数据库中状态是取消状态,那么MQ消息我们就需要消费,此时修改记录表的数据状态为点赞状态、点赞计数表中当前的稿件的点赞数量加1。</span><br></p> <p><span style="letter-spacing: 0.578px;">(3)点赞的数据写入缓存中从而减轻数据库的压力,点赞记录和点赞计数表的设计如下所示:<br></span></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100001424" data-ratio="0.3860103626943005" data-s="300,640" src="/upload/46d50812004edd6b2b7b49e29f86c46f.png" data-type="png" data-w="772" style=""></p> <p> 在redis中稿件的点赞总数可以采用String类型的数据结构来缓存,点赞记录数据采用Zset的数据格式来存缓存数据,这里需要给redis设置适当的过期时间。</p> <p>(4)数据库的设计采用读写分离的架构,使用canal来同步数据到从库中,所有的写请求都打到主库上,所有的读请求都转发到从库上。<span style="letter-spacing: 0.578px;"></span></p> <p>(5)为了保证数据的一致性,我们采用定时任务定期从数据库中同步数据到redis上,这样即使是redis在某个时间中写失败了,我们通过定时任务的方式将数据补偿到redis中。</p> <p> 取消的点赞数据的写入流程也是一样设计的,只是最终逻辑是要点赞计数表中点赞的数量减1,取消点赞的数量加1的,还要在点赞记录表中更新或者添加用户取消点赞的记录,所以取消点赞的这里就不在赘述。<br></p> <p> <mpcpc js_editor_cpcad="" class="js_cpc_area res_iframe cpc_iframe" src="/cgi-bin/readtemplate?t=tmpl/cpc_tmpl#1730125314686" data-category_id_list="1|2|5|6|7|16|17|21|24|29|31|35|36|37|41|42|43|46|48|50|51|57|58|59|60|61|62|63|64|65|66|67|68" data-id="1730125314686"></mpcpc></p> <p><strong>2.2 用户读取点赞的数据</strong><br></p> <p style="text-align: center;"><img class="rich_pages wxw-img" data-galleryid="" data-imgfileid="100001425" data-ratio="0.3206751054852321" data-s="300,640" src="/upload/119b16143ad74f76abed1a453e56674a.png" data-type="png" data-w="948" style=""></p> <p> 当点赞数据成功的写入缓存和数据库之后,用户读取点赞数据的流程如下:<br></p> <p>(1)读请求转发到点赞服务上之后,点赞服务优先查询redis中是否存在数据,如果有数据的情况下,直接响应数据给客户端。<br></p> <p>(2)如果redis中无点赞数据,那么此时就需要到数据库中查询数据,此时读取数据库的时候需要添加锁,防止短时间内由于缓存失效等原因造成大量的请求直接请求数据库从而导致数据库崩溃的问题。数据库上查询的数据要缓存一份到redis中。</p> <p>总结:</p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">(</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">1</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">)</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">点赞系统本文介绍的是</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">一种</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">通过MQ</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">+</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">主从</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">架构+</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">redis的设计方案</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">来</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">应对</span><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">大流量</span></p> <p>(2)高并发下点赞系统的redis缓存推荐使用更新的方案,因为高并发如果频繁的删除缓存就会导致缓存的命中率下降,那么就发挥不了缓存的作用</p> <p><span style="font-size: var(--articleFontsize);letter-spacing: 0.034em;">(3)主从架构中,主从的通过是通过canal来同步的,canal也是依赖与主库的binlog,如果主库由于系统的压力较大生成binlog速度慢了,就可能会发生从库和redis之间的数据不一致性,此时定时任务可以做数据补偿,修复从库和redis之间的数据不一致性</span></p> <p style="display: none;"> <mp-style-type data-value="3"></mp-style-type></p>
作者:微信小助手
<section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin: 0px;padding: 0px;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;width: auto;font-family: Optima, "Microsoft YaHei", PingFangSC-regular, serif;font-size: 16px;color: rgb(0, 0, 0);line-height: 1.5em;word-spacing: 0em;letter-spacing: 0em;word-break: break-word;overflow-wrap: break-word;text-align: left;"> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">对于读多写少并且要求高性能的业务逻辑,我们通常在应用服务器访问MySQL数据库的中间加上一层<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">Redis缓存层</strong>,以提高数据的查询效率,减轻MySQL数据库的压力,避免在MySQL出现性能瓶颈。</p> <figure data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);margin: 10px 0px 20px;padding: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100003350" data-ratio="0.4807370184254606" src="/upload/c7087168ba7278de68c246066eade140.png" data-type="png" data-w="1194" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgba(136, 136, 136, 0);font-size: 12px;line-height: 1.2em;letter-spacing: 0em;text-align: center;font-weight: normal;margin-top: 5px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"> image-20240916023730395 </figcaption> </figure> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">该问题,如果在数据存储后,只读场景下是不会出现MySQL与Redis缓存的一致性问题的,所以真正需要考虑的是<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">并发读写场景</strong>下的数据一致性问题。</p> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">如果我们不加分析,单独利用MySQL和Redis的知识进行回答并发场景下如何保证MySQL与Redis缓存一致性?很难把这个问题回答好,因为看起来很简单的方案实际上是漏洞百出的。</p> <h2 data-tool="mdnice编辑器" style=" background-color: unset;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgb(255, 180, 0);margin: 60px 0px 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;padding: 0px; text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="display: none;"></span><span style=" font-size: 24px;color: rgb(0, 0, 0);border-bottom-color: rgb(0, 100, 200);line-height: 1.5em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: solid;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 2px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset; text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">简单方案下的漏洞百出</span><span style="display: none;"></span></h2> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">我们先看下简单的更新数据库、删除缓存和更新缓存方案下,会出现什么问题?</p> <figure data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);margin: 10px 0px 20px;padding: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100003353" data-ratio="0.46536144578313254" src="/upload/87c95525de820bd86557d5ab90511daa.png" data-type="png" data-w="1328" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgba(136, 136, 136, 0);font-size: 12px;line-height: 1.2em;letter-spacing: 0em;text-align: center;font-weight: normal;margin-top: 5px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"> image-20240915220902509 </figcaption> </figure> <h3 data-tool="mdnice编辑器" style=" background-color: unset;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgba(89, 89, 89, 0);margin: 30px 0px 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;padding: 0px; text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="display: none;"></span><span style=" font-size: 22px;color: rgb(89, 89, 89);border-bottom-color: rgb(0, 100, 200);line-height: 1.5em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: solid;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 2px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset; text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">更新缓存,再更新数据库</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">先说结论:<code style="color: rgb(60, 112, 198);font-size: 16px;line-height: 1.8em;letter-spacing: 0em;background-attachment: scroll;background-clip: border-box;background-color: rgba(27, 31, 35, 0.05);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 2px;margin-right: 2px;padding-top: 2px;padding-bottom: 2px;padding-left: 4px;padding-right: 4px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgb(0, 0, 0);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;overflow-wrap: break-word;font-family: Consolas, Monaco, Menlo, monospace;word-break: break-all;">不考虑</code>。</p> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">原因是更新缓存成功后,数据库可能更新失败,出现数据库为旧值,缓存为新值。导致后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持了数据的完全不一致!并且当前数据库中的值为旧值,而业务数据的正确性应该以数据库的为准。</p> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">那么如果更新缓存成功后,数据库可能更新失败,我们<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">重新更新缓存</strong>是不是可以了?</p> <figure data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);margin: 10px 0px 20px;padding: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100003352" data-ratio="0.48154657293497366" src="/upload/9b6639f8815c92847ae13725ee80ba08.png" data-type="png" data-w="1138" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgba(136, 136, 136, 0);font-size: 12px;line-height: 1.2em;letter-spacing: 0em;text-align: center;font-weight: normal;margin-top: 5px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"> image-20240916004707314 </figcaption> </figure> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">抛开需要重新更新缓存时,要单表或多表重新查询数据,再更新数据带来的性能问题,还可能期间有数据变更再次陷入脏数据的情况。实际上仍然还是会出现并发一致性问题。</p> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">只要缓存进行了更新,后续的读请求<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">在更新数据库前</strong>、<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">更新数据库失败并准备更新缓存前</strong>,基本上都能命中缓存情况,而这时返回的数据都是未落库的脏数据。</p> <figure data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);margin: 10px 0px 20px;padding: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100003351" data-ratio="0.35797665369649806" src="/upload/86e2e11632abe605ed7915d0510a8828.png" data-type="png" data-w="1542" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgba(136, 136, 136, 0);font-size: 12px;line-height: 1.2em;letter-spacing: 0em;text-align: center;font-weight: normal;margin-top: 5px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"> image-20240916004728685 </figcaption> </figure> <h3 data-tool="mdnice编辑器" style=" background-color: unset;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgba(89, 89, 89, 0);margin: 30px 0px 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;padding: 0px; text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="display: none;"></span><span style=" font-size: 22px;color: rgb(89, 89, 89);border-bottom-color: rgb(0, 100, 200);line-height: 1.5em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: solid;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 2px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset; text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">更新数据库,再更新缓存</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">不考虑。</p> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">原因是当数据库更新成功后,缓存更新失败,出现数据库为最新值,缓存为旧值。导致后续的所有的读请求,在缓存未过期或缓存未重新正确更新的情况下,会一直保持了数据的完全不一致!</p> <figure data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);margin: 10px 0px 20px;padding: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100003349" data-ratio="1.2207207207207207" src="/upload/3d367368ecebda89853353b3a3d0bce8.png" data-type="png" data-w="444" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgba(136, 136, 136, 0);font-size: 12px;line-height: 1.2em;letter-spacing: 0em;text-align: center;font-weight: normal;margin-top: 5px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"> image-20240916004758430 </figcaption> </figure> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">该方案就算在更新数据库、更新缓存都成功的情况下,还是会存在并发引发的一致性问题,如下图所示(点击图片查看大图):</p> <figure data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);margin: 10px 0px 20px;padding: 0px;display: flex;flex-direction: column;justify-content: center;align-items: center;"> <img class="rich_pages wxw-img" data-imgfileid="100003355" data-ratio="0.4207650273224044" src="/upload/45bf3f7f60c212e6a90f6453c281130f.png" data-type="png" data-w="1830" style="display: block;margin-top: 0px;margin-right: auto;margin-bottom: 0px;margin-left: auto;max-width: 100%;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;object-fit: fill;box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px;"> <figcaption style="color: rgba(136, 136, 136, 0);font-size: 12px;line-height: 1.2em;letter-spacing: 0em;text-align: center;font-weight: normal;margin-top: 5px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;"> image-20240916005545173 </figcaption> </figure> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">可以看到在<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">并发多写多读的场景</strong>下数据存在的不一致性问题。</p> <h3 data-tool="mdnice编辑器" style=" background-color: unset;border-color: rgb(0, 0, 0) rgb(0, 0, 0) rgba(89, 89, 89, 0);margin: 30px 0px 15px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-image: none;background-origin: padding-box;background-position: 0% 0%;background-repeat: no-repeat;background-size: auto;border-style: none none solid;border-width: 1px 1px 2px;border-radius: 0px;box-shadow: none;display: flex;flex-direction: unset;float: unset;height: auto;justify-content: unset;line-height: 1.5em;overflow: unset;padding: 0px; text-align: left;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; "><span style="display: none;"></span><span style=" font-size: 22px;color: rgb(89, 89, 89);border-bottom-color: rgb(0, 100, 200);line-height: 1.5em;letter-spacing: 0em;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;align-items: unset;background-attachment: scroll;background-clip: border-box;background-color: unset;background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;border-top-style: none;border-bottom-style: solid;border-left-style: none;border-right-style: none;border-top-width: 1px;border-bottom-width: 2px;border-left-width: 1px;border-right-width: 1px;border-top-color: rgb(0, 0, 0);border-left-color: rgb(0, 0, 0);border-right-color: rgb(0, 0, 0);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-left-radius: 0px;border-bottom-right-radius: 0px;box-shadow: none;display: block;font-weight: bold;flex-direction: unset;float: unset;height: auto;justify-content: unset;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;overflow-x: unset;overflow-y: unset; text-align: left;text-indent: 0em;text-shadow: none;transform: none;width: auto;-webkit-box-reflect: unset; ">先删除缓存,再更新数据库</span><span style="display: none;"></span></h3> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">不考虑,但是通过使用<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">延时双删策略</strong>后可以考虑。</p> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">采用“<strong style="color: rgb(7, 7, 8);font-weight: bold;background-attachment: scroll;background-clip: border-box;background-color: rgba(0, 0, 0, 0);background-image: none;background-origin: padding-box;background-position-x: 0%;background-position-y: 0%;background-repeat: no-repeat;background-size: auto;width: auto;height: auto;margin-top: 0px;margin-bottom: 0px;margin-left: 0px;margin-right: 0px;padding-top: 0px;padding-bottom: 0px;padding-left: 0px;padding-right: 0px;border-top-style: none;border-bottom-style: none;border-left-style: none;border-right-style: none;border-top-width: 3px;border-bottom-width: 3px;border-left-width: 3px;border-right-width: 3px;border-top-color: rgba(0, 0, 0, 0.4);border-bottom-color: rgba(0, 0, 0, 0.4);border-left-color: rgba(0, 0, 0, 0.4);border-right-color: rgba(0, 0, 0, 0.4);border-top-left-radius: 0px;border-top-right-radius: 0px;border-bottom-right-radius: 0px;border-bottom-left-radius: 0px;">先删除缓存,再更新数据库</strong>”的方案是一种常见的方法来尝试解决这个问题的策略。</p> <p data-tool="mdnice编辑器" style="background-color: rgba(0, 0, 0, 0);color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">这种方法逻辑较为简单,易于理解和实现,理论上删除旧缓存后,下次读取时将从数据库获取最新数据。</p> <p data-tool="mdnice编辑器" style="color: rgb(0, 0, 0);font-size: 16px;line-height: 1.8em;letter-spacing: 0.1em;text-align: left;text-indent: 0em;margin: 0px;padding: 8px 0px;">但在并发的极端情况下,删除缓存成功后,如果再有大量的并发请求进来,那么便会直接请求到数据库中,对数据库造成巨大的压力。而且此方案还是可能会发生数据不一致性问�