<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>架构 on 山行小记</title>
    <link>https://ruijingan.pages.dev/tags/%E6%9E%B6%E6%9E%84/</link>
    <description>Recent content in 架构 on 山行小记</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <copyright>@2025</copyright>
    <lastBuildDate>Tue, 03 Dec 2024 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://ruijingan.pages.dev/tags/%E6%9E%B6%E6%9E%84/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>手撸一个简易分布式定时任务的记录</title>
      <link>https://ruijingan.pages.dev/posts/2024/distributed-cron/</link>
      <pubDate>Tue, 03 Dec 2024 00:00:00 +0000</pubDate>
      <guid>https://ruijingan.pages.dev/posts/2024/distributed-cron/</guid>
      <description>&lt;p&gt;公司内部系统有个痛点：十几个 Go 服务各自跑着 cron，日志、监控、去重各玩各的，出了问题要去十几个服务里翻。申请专项做统一调度被拒了（理由：收益不明确），于是用了两个月的晚间时间自己撸了个最小可用版本，先在自己负责的服务里跑起来，跑通再说。这篇记录核心设计。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;需求收敛。&lt;/strong&gt; 首先想清楚不做什么：不做秒级调度（cron 表达式到分钟足够）、不做依赖编排（任务依赖用上线时间错开解决）、不做可视化界面（CLI + 配置文件够用）。砍完之后剩下的核心就三件事：&lt;strong&gt;时间触发、任务分发、不重复执行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键设计是抢锁。&lt;/strong&gt; 多实例部署下同一个任务只能有一个实例执行，我们没有引入额外的调度中心，而是直接用 MySQL 的行锁：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- t_job 表，每分钟由各实例尝试 CAS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UPDATE&lt;/span&gt; job_lock
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SET&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;owner&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;, locked_until &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; NOW() &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; INTERVAL &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;MINUTE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; job_name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;AND&lt;/span&gt; (locked_until &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; NOW() &lt;span style=&#34;color:#66d9ef&#34;&gt;OR&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;owner&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;抢到锁的实例负责执行并把心跳写进去，锁过期自然释放。整个方案只依赖已经有的 MySQL，没有新组件。有人会问为什么不用 Redis：因为任务幂等性出错的代价是重复扣库存，而 MySQL 事务的可靠性在我们团队是经过验证的，不熟的东西不引入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分片是第二个关键。&lt;/strong&gt; 单机任务量上来之后，把任务按 &lt;code&gt;jobName 的 hash % 实例数&lt;/code&gt; 分片，每个实例只认领自己的那份。简单，但配合上面的锁，二十万级任务/天的量完全够。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩的坑&lt;/strong&gt;：时钟漂移。有两台机器的 NTP 没配好，相差 40 秒，导致任务在整点附近被执行了两次。锁的有效期改成大于任务最长执行时间 + 漂移容忍之后解决。现在新机器进集群先校时，成了硬性流程。&lt;/p&gt;
&lt;p&gt;现在这个调度器稳定跑了三个月，另外两个组也接进来了。回头看，&amp;ldquo;先做一个丑但能跑的版本，让收益自己说话&amp;quot;这个策略是对的——下个季度统一调度的专项预算，批了。&lt;/p&gt;</description>
    </item>
    <item>
      <title>iOS 组件化改造：从私有 pod 源开始</title>
      <link>https://ruijingan.pages.dev/posts/2019/ios-component-architecture/</link>
      <pubDate>Tue, 21 May 2019 00:00:00 +0000</pubDate>
      <guid>https://ruijingan.pages.dev/posts/2019/ios-component-architecture/</guid>
      <description>&lt;p&gt;App 三岁了，单工程 40 万行代码，全量编译要 15 分钟，改一行代码喝杯水回来才能跑真机。终于说服老板让我们做组件化。这篇记录前期的方案取舍，给后来人省点力气。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;先说结论：不要一步到位上&amp;quot;总线&amp;quot;架构。&lt;/strong&gt; 网上流传的组件化方案大多是 CTMediator 那一派，运行时解注册表调用。演示很漂亮，但真落地会发现调用关系全靠字符串，IDE 跳转变瞎，重构基本靠全局搜索。我们最后的方案保守得多：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;底层工具库（网络、缓存、埋点）拆成独立的私有 pod，用 &lt;code&gt;cocoapods-packager&lt;/code&gt; 发到自建的私有源；&lt;/li&gt;
&lt;li&gt;业务模块先只在物理上拆目录，通过 protocol 文件互相依赖，暂时不做运行时解耦；&lt;/li&gt;
&lt;li&gt;主工程退化为一个壳，只负责组装 tab 和路由。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个方案跑了半年，编译时间从 15 分钟降到 4 分钟（业务模块可以用 &lt;code&gt;pod install&lt;/code&gt; 之后按需引入调试），虽然没到网上吹的&amp;quot;秒级编译&amp;quot;，但对日常开发体验的提升已经非常明显。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;私有源的坑。&lt;/strong&gt; 自建源用的是 nexus，权限管理一塌糊涂，最后是靠 git tag + CI 校验版本号一致性来保证不出乱子。后来听说公司内部有 Artifactory 的 license，就迁过去了，省心很多。建议准备做组件化的团队先解决制品库的问题，工具链不顺手，组件化推行不下去——这不是技术问题，是工程习惯问题。&lt;/p&gt;
&lt;p&gt;下一阶段的计划是把业务模块间的 protocol 依赖收敛到一个独立的接口层仓库里，谁改接口谁发版。半年后 hopefully 能再写一篇续集，写写中间件方案到底要不要上。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
