阅读视图

发现新文章,点击刷新页面。
🔲 ⭐

旁路由的原理与配置一文通

最早听到旁路由这个词是在 2020 年折腾 N1 的时候,这台单网口的小盒子只能用网上所说的旁路由方案接入局域网来实现期望的功能。现在回想起来,旁路由这个词有可能就是在那个发烧友大量折腾斐讯 N1/P1/T1 的时期被发明出来的。你没办法在发烧友圈子外的互联网及各种学术材料中找到对旁路由的描述和定义,当然也找不到合适的英文翻译(导致这篇文章的 slug 定义困难);从拓扑上看,旁路由更像是杂糅了二级路由和透明网关的概念,除了实体确实多接了根网线放在主路由旁边,其本身并没有真正地开启一条旁路,做的事情也基本可以和网关的定义对齐。

不过,由于这个说法主要集中于网友的交流讨论之中,而且几年来在有相关需求的广大用户中被广为接受,所以只要这个词不变为一个学术概念,那我倒也不觉得有什么不妥。下文也仍然会用「旁路由」一词来指代在此类拓扑结构中担任代理网关角色的路由器。

此外,本文对于原理的解释偏多且为新手向,对于已经熟知相关概念的读者则可以将本文作为 cheat sheet 食用。

旁路由的适用场景

有人认为旁路由的加入(手动指定方式)不会在其出现问题时影响整个网络的可用性,所以应该把非路由功能都交给旁路由来实现。但实际上如果仅仅是为了达成这个目标,那在主路由上直接分流会是个更好的方案,因为这样不仅会大量减少疑难杂症的出现,而且还可以通过设置 fallback 的方式进一步提升网络的可用性。

所以我认为旁路由实际上只适用于一个场景,那就是出于某种考虑,主路由不能被替换或被大量修改,而主路由的固件又不能满足功能诉求,这时候旁路由便是一个可选的解决方案。

旁路由的原理

网上关于旁路由的配置教程多如牛毛,其中大部分是基于 F 大的 N1 OpenWrt 固件使用教程来写的,也有很多是结合了自己的踩坑经验的细化版本。由于版本太多、完整的说明太少,而且大部分没有讲清楚教程的环境上下文和原理,导致按照这些教程来配置的用户往往不得不一遍遍地折腾重试,甚至会遇到逐步配置最终却断网、访问不了部分网站等令人头疼的问题。

授之以鱼不如授之以渔,为了能彻底讲清楚旁路由该如何配置,我们暂且不谈具体步骤,而是先搞明白旁路由的运作原理。

名词解释

由于计算机网络的术语在不同时期、不同环境下,对于细节的含义其实有比较大的差异,故首先我们先定义好下文中将使用到的各类专有名词的含义,以免出现信息不对称的情况。同时为了方便理解,我们也尽量和 OpenWrt 的名词对齐,并尝试与现实生活场景进行类比。

  1. 路由:将数据从源地址传输到目的地址的行为。可以看出,这个行为抽象涵盖了两个动作,一是找到地址、二是转发数据。可以类比为网购时快递公司将商品从卖家发送给买家的过程。
  2. 路由表:可以简单理解为路由过程中的地址关联信息的合集,也就是现实中发货地址和收货地址的映射关系。
  3. 路由器:本文中指家用的、具备路由功能的实体网络设备,其本身并不一定需要真的承担或只承担路由功能,可以抽象理解为就是一台普通的电脑或服务器。当然,它也可以是一个容器实例、一个虚拟机。对应到现实中就是快递公司。
  4. 接口:这里特指网络接口,指两个网络设备或协议层的连接点。现实中连接路由器时,wan/lan 不同的网线插槽其实就可以理解为接口。只不过接口并不一定是物理实体,所以我们才会在 OpenWrt 的接口设置里看到甚至新增许多物理设备上所没有的接口。
  5. 网关:这里特指网络中的网关,负责执行数据转发的某个抽象设备。这里可能容易与路由器的功能混淆,毕竟路由器如果用来做路由似乎也是在做转发的工作。实际上这是由于术语的历史使用缺少规范导致的边界不够清晰,可以粗略理解为承担路由功能的路由器就是网关,但网关不一定是只能由路由器担任。
  6. IP:本身指 IP 协议,本文为方便也可能将其作为 IP 地址的简称,且默认为 IPv4 。本节所有的名词其实都是基于 IP 协议运作的,而 IP 地址即上文各类「地址」的实际值。可以理解为你用于发送和接收快递的门牌号。
  7. DHCP:IP 地址的管理和分配协议,本文中不单单指协议本身,还指负责执行该协议的设备。可以理解为给你分配门牌号的物业,只不过这个门牌号是动态变化的。
  8. DNS:上文说到数据的传输需要源地址和目的地址,而这个地址就是 IP 地址。但由于 IP 地址难以记忆,所以才会有了可以作为 IP 地址别名的域名,而 DNS 就是负责进行域名和 IP 地址映射转换的系统。本文中 DNS 也指代负责运行该系统的设备。
  9. NAT:出于各种考虑,局域网与因特网的 IP 地址是隔离的,NAT 可以理解为内外网 IP 地址转换的流程。类比网购,相当于快递公司把快递送到附近快递站,快递站的快递员再把货物送到你家门口的流程。
  10. SNAT:NAT 的一种,本质上是在修改网络包的源地址,目的是可以强制网络包返回时经过期望的地址。可以理解为支付宝的作用,即钱虽然表面上是点对点转账的,但经过支付宝后,支付宝就要求相关转账信息的回执必须由它中转一次再告知转账发起人。

网络拓扑

旁路由架构的网络拓扑图

可以看到,无论如何配置,我们都需要保证数据按图中的拓扑进行流转,才能实现我们所希望的只在旁路由增加功能而不修改主路由的目的。由于流量(至少上行流量)总会流经旁路由,所以旁路由实质上就是一层透明代理。

工作原理

那么我们该怎么实现这样的网络拓扑呢?让我们先来看下数据在网络的更底层是如何流转的。

旁路由架构的数据流转示意图

从图中可以看到,当我们从手机等终端设备发出一个数据包时,数据包总是由我们的终端设备经由网关路由至目的地址,目的地址返回数据时也是相同的路径。这是因为终端设备本身通常是不具备路由功能的,单单一个路由表终端设备就搞不定。

既然数据必然经过网关,那么我们只要强制把旁路由作为终端设备对外数据交互的第一层网关即可。至此旁路由的工作原理其实就已经解释清楚了,即在另一台路由器上实现的基于网关的透明代理。而网上各式各样的教程其实都是在教我们解决如何配置网关的问题。

在这一章节还需要说明的是为什么各个教程都要求我们把旁路由的 IP 配置在和主路由相同的 IP 网段。所谓的 IP 网段实际上就是子网,同一子网下的主机(设备)可以直接通信,跨子网则需要通过某种形式转换后才能通信,而这些转换虽然可行但比较复杂,在旁路由这个场景下显然是没有必要的。本着不改变原有网络拓扑的原则,旁路由自然也要配置在和主路由及其他设备相同的子网才行。

旁路由的配置

那么我们该如何配置网关以让数据按上文的工作原理进行流转呢?

(一)旁路由的网关设置

首先我们先来解决旁路由的网关问题。在上文的拓扑图中我们可以看到,旁路由虽然挂着路由器的名字,但它本质上也是网络链路中的一个节点,因此它也需要请求上层网关才能完成数据流转。而主路由是这个网络拓扑的出口,所以旁路由的网关自然要配置为主路由的 IP 地址。这一项配置是必须且不会随终端网关配置方式的变化而改变的,无论如何指定网关请求旁路由,旁路由本身都要依赖此配置才能完成正常的流量转发。

此外,还有子网掩码需要进行配置。前面有提到,在同一子网内的主机之间才能直接通信,而 IP 和子网掩码相组合便能确定设备当前所在的子网。旁路由并不改变网络拓扑,所以需要和主路由在同一子网内。因此将旁路由的子网掩码配置设置为主路由的子网掩码即可。同理,下文的所有子网掩码配置也均需要与主路由的保持一致。

(二)旁路由的DHCP配置

虽然配置了网关后数据流转图中的左半边已经成型,但如果不对旁路由的 DHCP 进行配置,实际上会导致各种各样的疑难杂症或直接无法联网。

原因在于 DHCP 使用了 UDP 协议,UDP 是没有连接的,如果主路由和旁路由同时开启 DHCP,则任意一个 DHCP 服务器都可能会应答终端的申请,进而导致 IP 下发和路由表的混乱造成各种无法连接的疑难杂症。当然,我们可以将两个 DHCP 的子网网段拆分开来解决共存问题,但这个行为在旁路由场景下并没有实际意义。

因此我们需要保证网络中只有一个设备承担 DHCP 功能,出于不改变原网络拓扑和避免无意义 NAT 的考虑,我们通常选择关闭旁路由的 DHCP 功能(对应到 OpenWrt 则选择「忽略此接口」)。

(三)终端网关配置

对于手机、电脑等终端,我们的目标是将其网关配置为旁路由的 IP 。实现方案很多,成本较低的主要为以下两种。

手动指定

顾名思义,只需要在终端设备的网络设置中将网关手动配置为旁路由即可。以 iOS 系统为例:

iOS 网络配置界面

手动填写一个网络上未被占用的 IP 地址,而子网掩码以主路由为准,网关则填写旁路由的 IP 地址。

手动指定的好处在于完全不影响原网络的使用,设备按需配置是否使用旁路由作为网关以实现特定功能。当旁路由故障时,未手动指定的设备仍能正常上网。

缺点在于操作烦琐。手机、电脑还好,但电视或根本没有屏幕的设备设置起来就会很麻烦。

依赖DHCP指定

DHCP 除了可以管理 IP 的分配,还会下发网关和 DNS 服务器信息,因此我们还可以借助 DHCP 的这一机制来为所有终端统一设置网关,而不再需要逐个手动修改。

前面提到,我们关闭了旁路由的 DHCP 功能,因此这个统一下发的工作就要交给主路由来完成。只需要在主路由的 DHCP 配置中将网关配置为旁路由的 IP 地址即可。

OpenWrt 的 DHCP 配置界面

以 OpenWrt 为例,将主路由 DHCP 下发的网关配置为旁路由 IP 地址即可(3 表示网关,6 表示 DNS 服务器地址)。

不过有些路由器的默认固件没有开放该配置项,对于这些设备,除非可以 SSH 连接后手动改配置,不然无法使用此种指定方式。

这种方式的好处显而易见,一次配置全家受用;缺点在于当旁路由出现故障时,所有连接的设备都会无法上网。

(四)DNS的配置

细心的读者可能发现了上文只提到了网关的配置,但未提到很多教程中的 DNS 配置。实际上单单就旁路由本身来说,网关配置完成后整个网络拓扑就已经搭建完毕了。但对于一些特定诉求,比如依赖旁路由进行统一的 DNS 劫持(很多功能的底层都依赖于此),则需要将对应位置的 DNS 也配置为旁路由的 IP 以将域名解析工作也完全交由旁路由处理。

配置步骤总结

手动指定方案

  1. 为旁路由配置和主路由同网段的静态 IP 地址,同时将旁路由的网关和 DNS 指向主路由,子网掩码与主路由保持一致。
  2. 旁路由关闭 DHCP 服务。
  3. 在主路由防火墙开启 SYN-flood 防御的情况下,关闭旁路由防火墙的 SYN-flood 防御(可选)。
  4. 在需要接入旁路由的终端设备中,将网关和 DNS 配置为旁路由 IP 地址,配置同网段的 IP 地址和与主路由相同的子网掩码。

DHCP下发方案

  1. 为旁路由配置和主路由同网段的静态 IP 地址,同时将旁路由的网关和 DNS 指向主路由,子网掩码与主路由保持一致。
  2. 旁路由关闭 DHCP 服务。
  3. 在主路由防火墙开启 SYN-flood 防御的情况下,关闭旁路由防火墙的 SYN-flood 防御(可选)。
  4. 在主路由的 DHCP 配置中,将其下发的网关和 DNS 配置为旁路由的 IP 地址。

疑难杂症的解决

虽然配置步骤看上去很简单,但很多人在实际使用中都会遇到逐步配置却上不了网或网络慢的问题,这里挑几个典型案例来解析。

(一)该不该设置iptables的MASQUERADE

这可能是争议最大的一条,有人说这条规则加上后影响性能而且没意义,但也有很多人表示不配这条就是连不上网(大多为连接不上国内网络)。

这条规则的作用本质上是在旁路由上做 SNAT,只不过修改的地址不需要指定而是动态获取旁路由对应接口网卡的 IP 地址,在 OpenWrt 里被称为「IP 伪装」。

配置 MASQUERADE 后的数据流转示意图

单从旁路由的网络拓扑来说,这条规则确实没意义,因为主路由作为对外出口必做 NAT,但旁路由本身就在局域网内且只是链路上的一环,没有必要再对内网 IP 进行耗费性能的 NAT 操作。

但不要忘了,理论和现实是两回事,物理网络拓扑中的不同设备、不同固件都有可能产生各种奇怪的兼容问题。我自己倒是没有遇到过该问题,但检索网友们的各种帖子,大概可以分为以下几种原因:

  1. 主路由固件数据包处理问题:部分路由器(似乎主要为国产品牌)的无线网在桥接和 iptables 处理过程中,当旁路由将国内流量重新转发回主路由时,主路由根据流中的首个数据包的状态做判断导致后续数据包未进行 NAT 就直接访问了互联网。这篇文章对此有比较详细的讲解(CSDN 也是有很多好文章的)。
  2. 主路由 NAT 硬件加速导致的问题:可能是由于硬件加速流程对数据包的处理出现了类似于原因 1 的问题导致无法正确完成数据交互。由于硬件加速本质上是在运行特殊驱动,而各家厂商的该驱动几乎都是闭源的,所以网上也没见到有探究深层原因的资料。这种情况下把硬件加速关闭即可(会一定程度牺牲主路由 NAT 性能)。
  3. IP 和 MAC 校验机制导致包被丢弃的推论:由旁路由的网络拓扑可知,配置网关后上行流量必然经过旁路由,但在旁路由不进行 NAT 时数据包中的 IP 仍然是终端设备的,所以理论上下行数据并不会经过旁路由而是由主路由直接转发至终端。显然,由于旁路由的存在,IP 地址和 MAC 地址在某个环节会有不匹配的情况,如果主路由对此有校验,那数据包就会被丢弃掉。但这也只是个推论没有证据佐证,同时由于网络可能的复杂性,主路由通常也不会主动做这种校验。

MASQUERADE 配置其实并没有定向地去解决上面这些具体问题,而是通过 NAT 来隐藏终端设备、只向主路由暴露旁路由 IP 的方式,一刀切地避免了上述原因导致的问题。但由于上下行流量都会经过旁路由,所有流量都会被二次 NAT 和二次转发,网络的吞吐会有不小的下降,直观感受就是下载速度变慢了。

所以比较理想的策略是,先不加 MASQUERADE 规则观察是否有问题(尤其是国内流量),如果确实有问题,在权衡可以接受性能的损失后再配上该规则。

(二)LAN和WAN是否需要绑定

具体操作是取消桥接,再设置 WAN 和 LAN 共用同一个网卡(如 eth0 )。这个操作其实和 MASQUERADE 规则的效果类似,因为绑定后经过 WAN 的流量必然会被 SNAT 。适用于确实遇到了疑难杂症且能接受性能损失场景下的备选方案。

(三)是否需要关闭旁路由桥接

在许多教程里,这个操作和添加 MASQUERADE 规则是配套的。但桥接与否实际上并不会影响整体的网络拓扑,这看上去又是一项没有意义的配置。我猜测可能是网上流传的添加 MASQUERADE 规则的方式是下面这条固定命令:

1
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE

也就是要求 SNAT 时从 eth0 网卡动态获取 IP 。而在桥接模式下,MASQUERADE 时是需要从 br-lan (常见的桥接后的默认网卡名称,也可能是其他名称,要视实际情况而定)获取 IP 的,直接复制粘贴上面的命令会导致 IP 伪装失败。所以只要把 -o 参数的值改为 br-lan 即可:

1
iptables -t nat -I POSTROUTING -o br-lan -j MASQUERADE

但似乎确实有网友不取消桥接就无法联网,这种情况大概率是旁路由固件对桥接模式的处理有问题(多见于 ARM 架构的固件,这类固件通常要做很多的魔改适配),如果真的遇到这种情况的话则确实可以取消桥接尝试下,毕竟大多数情况下旁路由内部的桥接也没什么实际意义,去掉后由于少了流量判断流程可能还会有非常微小的性能提升。

至于 N1 这种自带无线功能但实际上没人用的设备,在关掉无线后,确实可以顺便取消掉没有意义的桥接。只是在 OpenWrt 取消桥接保存配置时,要确定选中了有线网卡对应的接口比如 LAN/eth0 ,不然如果后台的前端存在体验问题自动选择了 wlan ,那保存后路由器可就直接失联了。

(四)是否需要设置DHCP强制

如果我想通过 DHCP 下发网关但又不想改变主路由的原配置该怎么办呢?在 OpenWrt 的接口 DHCP 配置中有一个选项叫做「强制」,勾选后,此设备会忽略网络上已经存在的 DHCP 服务,强制启动本机的 DHCP 服务,所以似乎我们只需要在旁路由上配置此选项,并在旁路由 DHCP 中配置好网关,主路由不做任何变更,即可实现本段开头的诉求。

但上文提到同一子网中只能有一个用于分配指定网段的 DHCP 服务,因此旁路由强制开启 DHCP 后,还需要主路由具备主动判断网络情况停止提供 DHCP 服务的能力,这个流程才能真正运转起来。但并不是所有的路由器和固件都支持这个能力,目前来看 OpenWrt 作为主路由固件时可以正确检测并停用 DHCP ,其他固件则需要在使用时做下兼容性测试。从工程角度上讲,由于这样的操作过于依赖外部能力,属于和外部组件产生了强耦合,不利于未来维护,故不太推荐此种方案。

(五)旁路由的某些功能无法使用

各种组件内部实现大不相同,如果某个组件的代码对网络结构做了强限定(如上文所说的校验 IP 和 MAC 的匹配关系),那旁路由的加入可能就会打破组件预期的网络结构导致其无法运行。这种情况在组件本身不做适配时基本无解,只能尝试下开启 MASQUERADE 等配置,看看多加一层 NAT 后的网络拓扑是否能符合组件要求,但代价同样是会牺牲性能。

(六)是否应该关闭旁路由防火墙的SYN-flood防御

SYN-flood 是一种常见的攻击方式,SYN 指 TCP 建连三次握手中的第一步报文,flood 指大量发起该步骤的报文。由于 TCP 的实现原理要求服务端接收到 SYN 报文后回复客户端 SYN+ACK 报文表明请求被接受,并在一段时间内等待客户端回复最终的 ACK 报文,那么大量的 SYN 报文就会导致服务端出现大量等待最终资源耗尽挂掉,而攻击者并不需要真的完成建连,只要持续发送 ACK 包即可。

那路由器的防火墙又是如何作防御的呢?以 OpenWrt 为例,虽然 OpenWrt 的防火墙配置已经迁移到了 fw3 ,但翻看代码历史我们就会看到当时 OpenWrt 直接基于 iptables 的早期实现:

1
2
3
4
$IPTABLES -N syn_flood
$IPTABLES -A syn_flood -p tcp --syn -m limit --limit $rate/second --limit-burst $burst -j RETURN
$IPTABLES -A syn_flood -j DROP
$IPTABLES -A INPUT -p tcp --syn -j syn_flood

即借助 iptables 的 SYN 限流能力进行防御,同时此配置位于 default 配置中,会在 NAT 等具体网络操作之前执行。

对应到主路由,wan 口接收到攻击流量后便会进行限流,攻击者无法直接向后攻击到内网主机,那么同样为内网主机的旁路由自然理论上也就不会被攻击到。

但对于旁路由来说,理论上网络中的所有上行流量都会通过它来转发,那当流量超过防火墙的限流阈值时便会触发拦截,进而在终端上表现为网络时断时续。所以在主路由已开启 SYN-flood 防御的情况下,旁路由关闭该配置可以避免出现可能的网络不稳定问题。

旁路由与IPv6

IPv6 在家用网络中通常默认是没有 NAT 转换流程的,同时其动态地址配置方案比 IPv4 要复杂得多,比如 SLAAC 和之前的 DHCP 可以说完全不是一套机制,而 DHCPv6 又分有状态和无状态两类。而前面提到,我们实现旁路由网络拓扑的过程,其实就是在指定一个具备透明代理功能的网关的过程,但 SLAAC/DHCPv6 都没有提供网关下发能力,终端设备总是会以其所交互的主机作为网关,同时大多也不支持直接修改网关。此外,运营商不支持 DHCPv6-PD 、IPv6 子网限定范围等情况,都使得旁路由支持 IPv6 非常困难,在不同场景、不同网络下要面临不同的配置,甚至无方案可配置。

网上比较流行的旁路由 IPv6 实现是个曲线救国的折中方案,即先开启主路由的内网的 IPv6 地址分配进而让旁路由获得内网 IPv6 地址,随后再通过在旁路由开启一个 DHCPv6-Client 的方式获取到公网的 IPv6 地址,这样便可以将主路由的 DHCPv6 下发的 DNSv6 配置为旁路由的 IPv6 地址。此时除了 DNSv6 的解析是在旁路由进行,其他流程仍按原链路直连。而在需要分流的场景中,OpenWrt 的相关组件可以选择在解析域名时放过不需要处理的 IPv6 流量让其正常解析出 AAAA 记录,而对域名名单中的流量强制解析为 A 记录以继续走 IPv4 协议,从而实现和此前的类似的旁路由功能。

当然,这种解析实际上依赖于组件的能力。如果组件并不支持,那通过各种方式强制定义 IPv6 的路由表保证相关 IPv6 流量必然经过旁路由也是一种解决方案。不过无论哪种实现,由于不借助网关配置,其实都已经和本文的旁路由不相关了。

此外,还有种方案是通过 radvd 等支持配置路由单播和优先级的工具,用更高的优先级来指定终端的 IPv6 路由(可以简单理解为 IPv4 的网关),这样就替代了 IPv4 下手动配置或 DHCP 下发网关对应的功能,完美满足本文所说的旁路由网络拓扑,同时对 IPv6 动态地址配置方案的要求很低,但要求终端设备支持路由优先级配置。

总结

旁路由实际上是运行透明代理功能的网关,有人认为这个概念很民科,但我并不认同,毕竟它只是在通过已有的能力来解决特定场景的问题,和我们写代码、做产品没有本质区别,而「旁路由」这个名词也不过是个约定俗成的叫法而已,不应该被批判。

另一方面,由于旁路由在不同设备、不同网络环境有可能遇到很多奇怪问题,其实对于非专业用户来说付出的时间成本很有可能会远大于直接替换主路由的成本。但生命不息,折腾不止,如果是为了收获折腾的快乐、学习到新的知识,那又有何不可呢?

🔲 ⭐

该不该使用关系型数据库的物理外键

没想到 2021 年还有很多人在争论是否该使用关系型数据库的外键,这种外键我们更习惯称其为物理外键,与之相对的是由业务逻辑控制的逻辑外键,实际上当今稍稍复杂些的业务都在使用外键,只是使用的是逻辑外键而非物理外键。

物理外键是我们学习数据库原理和设计时都会遇到的章节,它的主要优势是可以通过数据库实现强制的 Referential Integrity ,即引用完整性。但这样的完整性使用逻辑外键也完全能实现,有人认为逻辑外键由于完全依赖业务代码所以无法真正保证完整性,但这其实是个伪命题,因为物理外键也是由「人」来设置的,你只能确定已经设置过的物理外键能保证引用完整性,至于那些没考虑到的、设计错误的数据关联关系仍然是物理外键无法解决的,在这一点上物理外键和逻辑外键是没有实质区别的。而实际上当今的云原生架构在数据层面追求的是分布式和最终一致性,单个 DB 存储所有数据的时代早已过去,数据在服务间流转已经是常态,此外国内场景下很多数据也不被允许直接物理删除,物理外键的作用在现代架构下变得微乎其微。物理外键不是银弹,它甚至都没有成为银弹的实力。

而说到劣势,物理外键在现代后端架构中的缺点已经越发明显。分场景分析如下:

  • 对于传统企业应用,交付后几乎没有大面积迭代,使用物理外键是无可厚非的,这也是对传统软件开发模式和架构的传承。但现在有越来越多的企业选择使用 SaaS 或自主进行研发和维护,这也就意味着产品的迭代会比此前频繁得多,进而变为下文提到的流量小但迭代频繁的项目。
  • 玩具型项目用不用外键都没有区别。
  • 大流量项目使用物理外键无疑是在挖坑,抛开颇受争议的性能问题不谈,物理外键无法满足分库分表、单元化等现代架构设计的需要,甚至在这些架构下还会成为累赘要额外花费时间改造掉。
  • 小流量项目的迭代速度可不慢,领域模型很难稳定下来,而使用了物理外键也就意味着系统是基于数据库进行的建模,那么当前的物理外键设计迟早有一天要面临变更,这所带来的维护成本(改表困难、业务拆分和聚合困难等)是巨大的,这也是为什么现在很少有人使用存储过程的原因。

可见物理外键在数据模型迭代频繁以及大流量场景下具有非常明显的劣势。

其次是职责问题,复杂的物理外键维护需要 DBA 的参与,但在人员职责上,DBA 与业务强耦合本身就是不合理的,这和为什么要做前后端分离是一个道理,这也是为什么当今很多互联网公司会选择一名 DBA 对接一个后端大组甚至事业部的原因,DBA 的职责已经下沉到更核心的数据库稳定性和性能提升上。而在架构中的分层职责上,在持久层耦合业务逻辑是非常不明智的,因为这意味着你的架构会严重依赖某个数据库选型甚至某个特定版本数据库的功能,领域模型与数据模型的耦合也会产生很多人噩梦中的一个 Service 层走天下的情况,业务逻辑很难做进一步的抽象和拆分,至于读写模型分离、CQRS 也就是更不可能的事情了。

综上,使用物理外键能带来收益非常有限,但隐性成本(只要业务还在发展,那未来早晚会变为显性成本)却非常高,其本身又可以被逻辑外键所替代,那除了个人或团队喜好,我实在找不到继续使用物理外键的理由。

🔲 ⭐

如何避免被老代码坑

我承认有些标题党,事实上写下此文时,我还没有从被老代码坑出故障带来的低落心情中走出来。本文来简单探讨下维护历史代码尤其是历史业务代码时,有哪些原则和思考点可以帮助我们减少被坑的概率。

项目开发流程的现状决定了你本次上线的风险

代码的核心逻辑有单测覆盖吗?单测是基于 TDD 或者 BDD 编写的还是只是打印结果靠人肉 assert ?平时团队的大段代码改动会做好拆分再提 pull request 吗?团队审阅 pull requests 时有没有人认真看内容?代码结构和具体的实现问题有人提出疑问并得到作者解决了吗?QA 团队平时愿意给技术改造做测试吗?QA 团队有设计相对完善的自动化测试用例吗?

如果当你审视这些问题时,有超过 2 项问题的答案都是否定的,那你就要小心了,你改动的可能是一段除了原作者之外其他人都是知其然不知其所以然的代码,改动和上线带来的风险骤增。

永远不要无限信任历史业务代码

我们常常称历史业务代码为屎山,但不得不承认,这些代码如果没人改动,大部分都会本本分分地在线上运行数年。

然而作为历史代码,常常存在交接、奇葩需求的代码设计取舍、不规范的编程语言使用方式和较差的编程习惯。我们常常会体验到看到自己几年前所写代码时的陌生感,同样,历史代码是危机四伏的,可能仅仅一个异常类型的差异,就会给你带来不小的线上事故。因此我们在对历史代码进行修改、或迭代其底层代码依赖时,必须对其设计和逻辑进行逐行地重新审视,对于缺少单测的核心逻辑也要补全测试。不要觉得这会拖慢进度,事实上再快的进度在线上故障的影响下都是苍白无力的。

尝试让测试团队发挥真正的作用

如今国内大部分互联网公司和软件公司仍然在建设测试团队,我们必须要和测试团队做好沟通、拉近关系、讲清利弊,做 CI/CD 系统、搞稳定性建设固然比编写测试用例、测需求更容易晋升和拿好绩效,但这是以系统平稳运行为基础的,长期的忽视基础测试能力最终也会影响测试团队本身的发展。测试向来是个技术活,自测的最大问题就在于你永远也无法发现你默认没问题的问题,测试与研发协力发展才能真正地保障线上质量。

对于历史代码则更是这样,团队几经变革、代码多次易手、文档可能早已滞后,但 CI/CD 流程中的强制自动化用例检测却是提交代码的硬性门槛,只要守住了这个门槛,历史代码的维护总能少些心惊肉跳。

小心!监控可能早已无效

正如当前的稳定性手段在未来却有可能成为稳定性隐患一样,过去帮了团队大忙的监控随着业务的发展可能早已不能满足需求。而对于历史代码来说,年久失修的指标和采集都是一颗定时炸弹。

比起修改历史代码导致的故障,更可怕的是故障已然发生许久,你却没有任何感知,原因就在于历史逻辑的监控可能早已失效或根本就没设置过!

上线之心态

我常常觉得后端服务的上线犹如驾驶飞机,过度的自信总能招致各种问题的叠加酿成惨剧,而谨慎则总能为我们构筑我们能把控的一道底线。无论你是 L8 、P7 还是 2.2 甚至更高的职级,只要你面对的是历史代码且存在上文提到的风险和隐患,上线就总是危险的。端正态度,做好事前准备和事中预案,谨慎总是没错的

🔲 ☆

简易增强版hexo-theme-even

Even 主题的简洁深得我心,它也是本博客当前的主题。不过有许多常用功能在原主题中并未直接支持,因此就自己 fork 后动手做了些小的体验优化和功能实现,供有类似功能需要的朋友直接使用。优化的部分思路可以参考前文

当然我只做了很小的一部分优化,主题作者 Yuexun Jiang 的主题代码创建和维护才是最核心的贡献。后续我也会通过 PR 的形式将本文内容中的通用部分提交到原主题中。

下文为增强版 even 主题的中文使用说明。

地址

增强版功能

  • 支持使用 Font Awesome 作为图标库
  • 支持使用不蒜子(busuanzi)进行站点访问量统计
  • 支持文末的站内热点文章推荐
  • 支持整站字数的统计和展示
  • 将百度推送置为可选项
  • 支持自定义页脚,可添加微信公众号信息等内容
  • 加重了首页文章列表中的标题字体以便能更加清晰地区分标题和文章内容
  • 将版权信息放置于文章内容中以对抗不遵守协议的爬虫
  • 修正了原主题在文章列表中获取访问次数时会发起多次请求的问题
  • 支持配置 Twitter Cards
  • 支持 Open Graph 协议
  • 支持搜狗和神马搜索的站点验证
  • 可在页脚添加站点地图链接
  • 在文章列表中使用 h2 标签替换 h1 标签以符合 Bing 等平台的 SEO 要求

原主题功能

原主题的所有功能均支持,可查看原主题文档进行配置:hexo-theme-even docs

增强功能的使用

使用 Font Awesome 图标

原主题的图标为作者自定义的 iconfont 图标库,新图标的增加需要作者进行额外支持。

增强版引入了 Font Awesome 依赖,从而实现可以使用 Font Awesome 网站 上的任意图标。

例如,增强版原生支持了 Telegram 页脚社交图标的展示,在主题配置中添加以下内容即可:

1
telegram: <Telegram chat url>

使用不蒜子进行站点统计和展示

添加以下内容到主题配置文件即可开启:

1
busuanzi: true

展示站内热点文章推荐

  1. 安装依赖:npm install hexo-related-popular-posts -S
  2. 阅读该插件的文档以了解如何进行参数配置 hexo-related-popular-posts docs
  3. 添加以下内容到主题配置文件即可开启推荐功能:
1
2
3
4
popular_posts:
  enable: true
  maxCount: 5
  PPMixingRate: 0.5

页脚添加微信公众号信息

添加以下内容到主题配置文件即可开启:

1
2
3
wxOfficialAccount:
  enable: true
  url: <The QRCode image url>

设置 Twitter Cards

  1. 设置 Twitter Cards 后,在 Twitter 中发送的链接可展示预览信息,详见Twitter cards docs
  2. 添加以下内容到主题配置文件即可开启:
1
2
3
twitter_card:
  style: <See Twitter card docs>
  creator: <Twitter username>

设置 Open Graph

  1. 阅读文档:Open Graph docs
  2. 添加以下内容到主题配置文件即可开启:
1
2
open_graph:
  type: <See https://ogp.me/#types>

整站字数统计与展示

  1. 安装依赖: npm install hexo-wordcount -S
  2. 添加以下内容到 hexo 配置中即可开启
1
word_count: true

在页脚展示站点地图

添加以下内容到主题配置文件即可开启:

1
footer_sitemap: true

开启搜狗和神马搜索的站点验证

添加以下内容到主题配置文件即可开启:

1
2
3
4
5
# Sogou verification
sogou_verification:

# Shenma verification
shenma_verification: 

停止百度推送

增强版默认关闭了百度推送功能,添加以下内容到主题配置文件中即可重新开启:

1
baidu_push: true

修复 Leancloud 计数器

原始实现使用了旧版的 Leancloud CDN ,其中的 API 目前已经 404 了。为保证计数器可用,主题升级和更换了 CDN 地址,同时支持了自定义域名以解决目前 Leancloud 要求中国区应用需要使用特定域名的问题。

1
2
3
4
5
# LeanCloud
leancloud:
  app_id: <Your Leancloud appId>
  app_key: <Your Leancloud appKey>
  server_url: <Your Leancloud domain>
🔲 ⭐

2021常用临时网盘汇总

当我们希望在网络上面向所有网友分享文件时,我们总希望能找到一款容量适中、上传下载较快的临时网盘(也可以叫做临时文件传输工具)。

这个场景下 Onedrive/Dropbox/Google Drive 虽然具备分享功能,但总是会暴露账号信息,同时国内的朋友访问会存在一定困难,而从各类 Tampermonkey 中各类网盘限制破解插件就可以看出百度云这类国内网盘的分享功能并不好用。本文就来盘点一下截止 2021 年的常用临时网盘(临时文件传输工具)。

临时网盘(临时文件传输工具)的特点

临时网盘的产品形态与普通网盘不同,使用体验优秀的临时网盘通常需要具有以下几个特点:

  • 容量限制适中:支持 500MB 以上的临时文件分享,基本能满足大部分的临时文件分享需求。
  • 速度较快:不一定能跑满带宽,但也不会出现以 KB/s 为单位的下载速度。
  • 匿名发布:发布者至少可以做到半匿名发布。
  • 下载限制少:不对下载者做注册要求,同时分享次数限制也应较为宽松。
  • 过期自动删除:不需要发布者手动维护文件的生命周期,发布时设定好有效期,到期平台自动删除。
  • 平台不易跑路:有的临时文件分享的周期是会达到月级别的,所以要保证网盘平台本身相对老牌可靠,不会轻易跑路造成死链。

那么满足这些要求的临时网盘都有哪些呢?下面就来罗列些我体验过的临时文件传输工具,并以上述几点特性作为评测出发点。

汇总

城通网盘

地址

https://ctfile.com

介绍

非常老牌的临时网盘,老网民应该会在各类奇奇怪怪的网站都看到过他的身影。产品的形态其实与 115 、百度云之类很相似。

登录后才可以上传和分享文件且不支持直接设置有效期,同时下载页面会直接暴露分享者的邮箱。而下载者需要输入密码才可访问且不氪金只能使用普通下载通道。

可见城通网盘基本不具备上述特性,那么这里为什么要将它加入汇总列表呢?原因就是稳定,如果你想找一款跑路概率很低、需要长期稳定分享的临时网盘,那么城通网盘还是值得考虑的。

速度测评

  • 上传(上传时的数据似乎做了美化或者哈希后在服务器上发现了相同的文件所以有些失真):城通网盘上传速度
  • 下载:城通网盘下载速度

奶牛快传

地址

https://cowtransfer.com

介绍

这款产品刚发布时风靡一时,然而随着其用户协议调整,尤其是对非临时分享功能进行阉割和诸多限制后,奶牛快传的口碑也有了很大程度的下降。不过由于其部署节点的问题,速度非常快,在小范围分享的场景下依然是个不错的选择。

非注册用户可传输 2GB 的内容,分享的临时文件内容可被免费下载 5 次,有效期为 1 天,注册后可以使用更多功能。

速度测评

  • 上传:奶牛快传上传速度
  • 下载:奶牛快传下载速度

tmp.link

地址

https://app.tmp.link

介绍

由微林在最近新推出的一款临时网盘产品,功能简单易懂,没有下载量的限制。注册后才可以上传文件,临时文件的大小没有任何限制也就是无限容量,不过不清楚能坚持多久。

值得一提的是,tmp.link 上的临时文件虽然可选择有效期为 24 小时、3 天和 7 天,但当有用户下载了被分享的文件后,临时文件即可被续期,所以理论上只要一直有人下载你的临时文件,这份文件的有效期就是永久的。很适合分享各类小工具和热门数据的场景使用。

速度测评

  • 上传:tmp.link上传速度
  • 下载:tmp.link下载速度

其他选择

以上是个人使用体验不错的临时网盘,当然很多人对网盘的稳定性没那么多要求但对分发渠道的多样性有着特定的需求。下面再罗列下我收藏的其他临时文件传输工具:

❌