<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Moli'blog]]></title><description><![CDATA[莫粒的个人博客，分享自己的世界，包括不限于个人生活，软件开发，产品分享]]></description><link>https://mozz.in</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 04:20:44 GMT</lastBuildDate><atom:link href="https://mozz.in/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[会有越来的多的side projects出现]]></title><description><![CDATA[什么是side project
可以理解为工作之余开发的产品，通常是收费的服务，可作为工作之外额外收入的产品。
在目前经济下行、公司开源节流（裁员）、失业率上升的大环境下，每一个程序员都应该拥有自己的side projects来对冲未来的不可靠风险。
所以side project 不仅仅是多一种「被动收入」，他也是你未来的「筹码」——工作累了，不想干了、有小孩了、买房了、家人生病了等等这些事情发生的时候，你可以「任性」一下。
而不是一些不可靠的风险出现的时候，再来提高自己的「抗风险」能力。
上面...]]></description><link>https://mozz.in/side-projects</link><guid isPermaLink="true">https://mozz.in/side-projects</guid><category><![CDATA[side project]]></category><category><![CDATA[fly.io]]></category><category><![CDATA[PlanetScale]]></category><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Fri, 28 Jul 2023 17:02:18 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-side-project">什么是side project</h3>
<p>可以理解为工作之余开发的产品，通常是收费的服务，可作为工作之外额外收入的产品。</p>
<p>在目前经济下行、公司开源节流（裁员）、失业率上升的大环境下，每一个程序员都应该拥有自己的side projects来对冲未来的不可靠风险。</p>
<p>所以side project 不仅仅是多一种「被动收入」，他也是你未来的「筹码」——工作累了，不想干了、有小孩了、买房了、家人生病了等等这些事情发生的时候，你可以「任性」一下。</p>
<p>而不是一些不可靠的风险出现的时候，再来提高自己的「抗风险」能力。</p>
<p>上面把 side project 吹得有点高了……</p>
<h3 id="heading-side-project-1">写什么side project比较好？</h3>
<p>对于大部分人来说，对于写什么其实是比较纠结的，以下是些思路：</p>
<ul>
<li><p>基于解决自己生活中的问题</p>
<ul>
<li>自己持续用的产品是最重要的，因为你自己就是开发、测试、使用者，没人比你更懂需求了，除了解决自己的问题，产品化之后贩售变得是一件很自然的事情。</li>
</ul>
</li>
<li><p>山寨一个你自己喜欢的产品</p>
<ul>
<li><p>比如你觉得Dash（文档搜索）这个Mac app不错，你可以山寨一个，加上 AI 的能力，缝合一个新的产品</p>
</li>
<li><p>单纯的山寨我个人觉得没什么意思，但是开发一个满足自己需求，同时缝合一些特色功能，其实是不错的选择。</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-side-project-2">为什么说现在是做side project最好的时代</h3>
<p>实际上随着行业发展，市面上出现了非常多PaaS产品，而且大部分都有入门级的套餐，价格低廉，甚至免费。</p>
<p>开发者前期可以薅一薅，当产品做大做强了，可以考虑升级套餐，满足用户需求了。</p>
<p>比如 <a target="_blank" href="https://bitbear.net">https://bitbear.net</a> 这个网站，我使用的paas产品有：</p>
<p><strong><em>前后端错误收集：</em></strong>https://sentry.io/<br />行为分析：https://clarity.microsoft.com/<br />流量分析：https://dash.cloudflare.com/</p>
<p>后端：https://fly.io/<br />前端：https://dash.cloudflare.com/<br />数据库：https://planetscale.com/</p>
<p>对与这种简单的网站，前期几乎免费运行起来，而且大部分都是自动化的（github actions），不用自己维护linux服务器、部署产品了。</p>
<h3 id="heading-andamp">持续迭代 &amp; 持续反馈</h3>
<p>对于个人开发者来说，最大的风险反而不是卖出去，而是「做出来」。</p>
<p>把产品「做出来」又分很多种</p>
<ol>
<li><p>把产品做得尽善尽美而耗费大量的时间，最终产品出来，反响一般，导致信心受挫。</p>
</li>
<li><p>持续迭代，持续发布。及时验证商业上可行性，避免时间投入</p>
</li>
</ol>
<p>对于个人开发者来说，及时验证商业上的可行性，快速试错是比较节省时间的。当产品做得很完美了，结果发出来发现商业上不可行，造成浪费大量的时间。</p>
<p>一些原则和见解</p>
<ul>
<li><p>产品不一定要十分完美才发布出去，能用就可以尝试推广给一部分用户使用了</p>
<ul>
<li>建群，保持沟通（其实也是一种「正反馈」）</li>
</ul>
</li>
<li><p>持续开发 &amp; 迭代 &amp; 反馈</p>
<ul>
<li><p>与期望使用的客户保持沟通，收集意见</p>
<ul>
<li>包括产品对开发者的反馈，让开发者持续保持热情</li>
</ul>
</li>
<li><p>产品收入上的「持续反馈」是很重要的，这能让你保持对产品的信心</p>
<ul>
<li>前期产品可能仅仅是「能用」的状态时，建议还是避免收费（或者收很低的费用）</li>
</ul>
</li>
<li><p>持续开发并保持持续的发版</p>
<ul>
<li>随着上面与客户的沟通、身边人的反馈、自己的使用过程对产品做持续的改进既可以让客户和自己都对产品的未来提供良好的信心。</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="heading-5aac5l2v5o6o5bm5l2g55qe5lqn5zob">如何推广你的产品</h3>
<ul>
<li><p>你自己</p>
</li>
<li><p>你身边的人</p>
</li>
<li><p>博客、公众号、推特、油管、b站等互联网产品</p>
</li>
<li><p>广告</p>
<ul>
<li>google ad等产品</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Hello world]]></title><description><![CDATA[Hi，Im Moli.
咱们以后的新文章就放在 hashnode 吧， 更好的老文章就继续放在github吃灰吧～（导不进来）。
因为有更好的编辑器了～ 所以后应该会有更多的「流水账」。]]></description><link>https://mozz.in/hello-world</link><guid isPermaLink="true">https://mozz.in/hello-world</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Sat, 06 May 2023 11:32:36 GMT</pubDate><content:encoded><![CDATA[<p>Hi，Im Moli.</p>
<p>咱们以后的新文章就放在 hashnode 吧， 更好的老文章就继续放在github吃灰吧～（导不进来）。</p>
<p>因为有更好的编辑器了～ 所以后应该会有更多的「流水账」。</p>
]]></content:encoded></item><item><title><![CDATA[Xbox Cloud Gaming 游戏加速尝试]]></title><description><![CDATA[Xbox Cloud Gaming 游戏加速
之前有个很老的xbox游戏机，因为性能有点差劲了，所以卖了。 偶尔还是想玩玩游戏，但是老婆不让给买xbox的物理机（怀恋单身），所以含泪玩xbox cloud gaming（以下简称 xcg），xpg会员游戏还是很丰富的。
于是买了uu加速器，坦白说uu加速器不算便宜的，但是xcg在晚上高峰期，一样卡得怀疑人生，那种被马赛克糊满脸的感受，上一次这种体验还是看小姐姐的电影。
其实用uu加速器玩是ok的，就是国内的网络情况大家都知道，dddd（懂得都懂）...]]></description><link>https://mozz.in/xbox-cloud-gaming</link><guid isPermaLink="true">https://mozz.in/xbox-cloud-gaming</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Mon, 05 Sep 2022 17:30:01 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-xbox-cloud-gaming">Xbox Cloud Gaming 游戏加速</h2>
<p>之前有个很老的xbox游戏机，因为性能有点差劲了，所以卖了。 偶尔还是想玩玩游戏，但是老婆不让给买xbox的物理机（怀恋单身），所以含泪玩xbox cloud gaming（以下简称 xcg），xpg会员游戏还是很丰富的。</p>
<p>于是买了uu加速器，坦白说uu加速器不算便宜的，但是xcg在晚上高峰期，一样卡得怀疑人生，那种被马赛克糊满脸的感受，上一次这种体验还是看小姐姐的电影。</p>
<p>其实用uu加速器玩是ok的，就是国内的网络情况大家都知道，dddd（懂得都懂），晚上高峰期出口网络抖得怀疑人生。</p>
<p>于是我想自己建一个加速网络，会不会更好呢，趁着疫情被关在家里，做了一轮测试和体验，给需要的人做一些参考。</p>
<p>线路方案：</p>
<ol>
<li>直接买azure的韩国服务器，直连</li>
<li>买联通、移动vps中转到韩国服务器，跳转</li>
</ol>
<p>这里最大的问题其实高峰期出口会不会抖。</p>
<p>上面2个方案其实会有一些根据实际情况做一些调整，例如你本来就是联通，那可能不用中转了，直连。如果你是电信，晚上出口会抖成马赛克，可以考虑联通服务器中转。</p>
<h2 id="heading-xbox">了解Xbox会请求哪些服务器</h2>
<p>xbox是限制区域的，还没对国内开放（未来估计也不太可能），亚洲基本只能使用 韩国、日本服务器，这俩服务器在国内服务器基本能做到50ms的延迟左右，玩非fps游戏是够用了。</p>
<p>于是浏览器开启开发者模式，截网络包，这里不复述了，直接给clash的规则：</p>
<p>    rules:</p>
<ul>
<li>DOMAIN-SUFFIX,xbox.com,your-proxy</li>
<li>DOMAIN-SUFFIX,live.com,your-proxy</li>
<li>DOMAIN-SUFFIX,xboxlive.com,your-proxy</li>
<li>DOMAIN-SUFFIX,microsoft.com,your-proxy</li>
<li>DOMAIN-SUFFIX,visualstudio.com,your-proxy</li>
<li>DOMAIN-SUFFIX,msn.com,your-proxy</li>
<li>DOMAIN-SUFFIX,gamepass.com,your-proxy</li>
<li>DOMAIN-SUFFIX,demdex.net,your-proxy</li>
<li>DOMAIN-SUFFIX,microsoftonline.com,your-proxy</li>
</ul>
<p>如果你自己有🪜，上面的规则配到你的 clash 中，也能实现加速了，不过速度嘛就看你的机场良心了～</p>
<h2 id="heading-5rwl6kv">测试</h2>
<p>买了微软的azure，主要是其对国内宽带比较友好，当然还是要看高峰期表现。</p>
<p>可以尝试本地用 clash 配置，加上上面的 rules，应该就好啦。服务器和客户端的加密理论上越简单越好，这样实时性会更好一点。</p>
<p>搭建好了之后，直接上 xcg，没有提示「不可用区域」就ok啦。</p>
<p>开玩「杀手47」，很流畅，爱了爱了，心理甚至嘲讽了一波uu加速器，不过「开心不过3秒」，打脸总是非常及时。</p>
<p>晚上10点左右，高峰期，一样卡得满屏幕的马赛克。尝试用uu加速器，也卡哭了。</p>
<p>有钱还是上电信的精品网，没钱就老老实实买xbox物理机吧（并且你媳妇同意）。</p>
<p>当然后来还常识移动中转，发现我那台移动宽带到韩国的速度也不咋滴， 国内联通中转就没测了，晚点试试阿里云搞台上海到韩国的中转，之后再把结论同步到这篇blog。</p>
<h2 id="heading-5oc757ut">总结</h2>
<p>还是买个xbox的物理机吧，折腾。</p>
<p>放过自己，不要跟自己过不去，就像每次做核酸的时候：心理mmp，嘴张得比谁都大。</p>
]]></content:encoded></item><item><title><![CDATA[github codespaces 在ipad上的最佳浏览器]]></title><description><![CDATA[Github Codespaces
github codespaces 是github在被微软收购后，提供的一款在线web IDE，基本与vscode一致，只是运行在浏览器上而已。
而且非常土豪的提供了4核8G内存，微软就是壕。
so，通过ipad来使用codespaces就是一件比较顺其自然的事。
可是其实也没那么简单。。
IPad使用codespaces的快捷键问题
其实最大的问题就是快捷键的问题，不管你是用saferi，还是chrome，他们提供的快捷键或多或少会与你的vscode的快捷键...]]></description><link>https://mozz.in/github-codespaces-ipad</link><guid isPermaLink="true">https://mozz.in/github-codespaces-ipad</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Fri, 08 Oct 2021 10:30:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-github-codespaces">Github Codespaces</h3>
<p>github codespaces 是github在被微软收购后，提供的一款在线web IDE，基本与vscode一致，只是运行在浏览器上而已。</p>
<p>而且非常土豪的提供了4核8G内存，微软就是壕。</p>
<p>so，通过ipad来使用codespaces就是一件比较顺其自然的事。</p>
<p>可是其实也没那么简单。。</p>
<h3 id="heading-ipadcodespaces">IPad使用codespaces的快捷键问题</h3>
<p>其实最大的问题就是快捷键的问题，不管你是用saferi，还是chrome，他们提供的快捷键或多或少会与你的vscode的快捷键冲突。</p>
<p>在ios中，浏览器就是一个webview控件，也就是本身其实没啥快捷键，快捷键都是浏览器厂商自己家的，所以，问题就变成，找一个没有快捷键的浏览器，或者webview控件只是用来打开网页的app。</p>
<h3 id="heading-562b6ycj">筛选</h3>
<p>于是筛了n个浏览器，火狐、chrome、oprea等等。以及各类非浏览器，但是有内置webview的app。</p>
<p>最终得出的结论如下：</p>
<p>最适合coodespace的浏览器竟然是美区的：yandex。也就是俄罗斯的搜索巨头推出的浏览器。</p>
<p>这个浏览器几乎没有快捷键，外接键盘时，几乎不会与codespaces的快捷键冲突。</p>
<p>如果你没有美区的账号，那么国区最合适的是微软的：bing。</p>
<p>这个app有一个简单的webview，但是有点太占屏幕空间。</p>
<h3 id="heading-5a6m5qv">完毕</h3>
<p>so，大家可以参考这个思路去找相关的app，或者直接使用我筛选过的app。目前我的ipad mini6 使用yandex体验codespaces，还是挺棒的。</p>
]]></content:encoded></item><item><title><![CDATA[基于binlog检查数据错误]]></title><description><![CDATA[起因
某个表的 status 「莫名其妙」变成 0 了
其实可以判断出是 status 没有被赋值，通常是结构体的 status 默认是 0 才会被插入数据库。
于是问题看起来就很简单了：只要检查相关的更新操作中的 status 字段有没有被赋值即可。
但是
这个表是用户表。

因为历史原因，源码中的更新函数很多
调用更新函数的地方也很多
无法复现该问题，测试人员也不知道做了什么操作状态变成 0 的

所以同事关注这个问题挺久了，也没看到问题原因（当然我也没看到……）
但是恰好我在做导出 bin...]]></description><link>https://mozz.in/binlog</link><guid isPermaLink="true">https://mozz.in/binlog</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Thu, 05 Aug 2021 11:30:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-6lw35zug">起因</h3>
<p>某个表的 status 「莫名其妙」变成 0 了</p>
<p>其实可以判断出是 status 没有被赋值，通常是结构体的 status 默认是 0 才会被插入数据库。</p>
<p>于是问题看起来就很简单了：只要检查相关的更新操作中的 status 字段有没有被赋值即可。</p>
<h3 id="heading-5l2g5piv">但是</h3>
<p>这个表是用户表。</p>
<ul>
<li>因为历史原因，源码中的更新函数很多</li>
<li>调用更新函数的地方也很多</li>
<li>无法复现该问题，测试人员也不知道做了什么操作状态变成 0 的</li>
</ul>
<p>所以同事关注这个问题挺久了，也没看到问题原因（当然我也没看到……）</p>
<p>但是恰好我在做导出 binlog 日志，判断某行数据丢失的问题，于是顺手将该问题对应的数据库、表结构一并导出，查看 status=0 的 binlog 看看情况。</p>
<h3 id="heading-coding">coding</h3>
<p>因为我们内部对 <code>https://github.com/go-mysql-org/go-mysql</code> 这个库做了很多的封装。 所以下面我们在处理 binlog 时，仅展示事件部分，其他的数据库连接之类的暂且忽略。</p>
<p>    func (f <em>FileOutBound) OnEvent(event </em>types.Event) error {
        if event.Database != "project" {
            return nil
        }
        mustTableSet := map[string]bool{
            "invitation":  true,
            "org_user":    true,
            "team_member": true,
        }</p>
<p>        <em>, err := f.f.WriteString(event.Print())
        if err != nil {
            panic(err)
        }
        </em>, err = f.f.Write([]byte("\r\n"))
        if err != nil {
            panic(err)
        }
        return nil
    }</p>
<p>上面的代码就是响应 binlog 的 row 日志（原始的 row 已被解析成 Event 结构体）。 为了避免日志过多过大，所以仅处理 project 这个数据库，并且仅仅关注 invitaion 等 3 张表。</p>
<p>这里是直接将事件输出到文件，便于我们在电脑上直接处理。</p>
<h3 id="heading-5yig5p6q5pel5bx5pah5lu2">分析日志文件</h3>
<p>数据导出后，我们找到将某行数据的 status 改成 0 的 binlog row。 通常一行 row event 数据长这个样子：</p>
<p>    {
      "action": "UPDATE",
      "database": "project",
      "old_values": "('QmPCusvS','呀呀呀','yayaya','xxxx@yeah.net',1)",
      "pos": {
        "part": "mysql-bin.001591",
        "offset": 515629163
      },
      "table": "org_user",
      "values": "('QmPCusvS','呀呀呀','yayaya','xxxx@yeah.net',0)"
    }</p>
<p>可以看到数据结构大概是 action 操作方式；database 数据库；old_value 、values 旧新数据；pos 表示这行数据出现在哪个 binlog 文件以及对应的 offset 偏移。</p>
<p>上面的 json 中我们可以看到 旧数据中 stauts 还是 1（最后一个字段），新数据中 status 变成了 0。</p>
<p>因为我们知道了 pos 中这行数据的位置，所以我们可以看看这个 binlog 的上下文长什么样了。</p>
<p>在数据库中执行：</p>
<p>    show binlog events in 'mysql-bin.001591' FROM 515629163 limit 100;</p>
<p>返回结果：</p>
<p>    mysql-bin.001591    515629163    Anonymous_Gtid    1    515629228    SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
    mysql-bin.001591    515629228    Query    1    515629309    BEGIN
    mysql-bin.001591    515629309    Table_map    1    515629403    table_id: 3407638 (project.team)
    mysql-bin.001591    515629403    Write_rows    1    515629521    table_id: 3407638 flags: STMT_END_F
    mysql-bin.001591    515629521    Table_map    1    515629593    table_id: 3411634 (project.team_config)
    mysql-bin.001591    515629593    Write_rows    1    515629665    table_id: 3411634 flags: STMT_END_F
    mysql-bin.001591    515629665    Table_map    1    515629771    table_id: 3407636 (project.org_user)
    mysql-bin.001591    515629771    Update_rows    1    515630301    table_id: 3407636 flags: STMT_END_F
    mysql-bin.001591    515630301    Table_map    1    515630372    table_id: 3407637 (project.team_member)
    mysql-bin.001591    515630372    Write_rows    1    515630435    table_id: 3407637 flags: STMT_END_F
    mysql-bin.001591    515630435    Table_map    1    515630513    table_id: 3411635 (project.dashboard)
    mysql-bin.001591    515630513    Write_rows    1    515630610    table_id: 3411635 flags: STMT_END_F
    mysql-bin.001591    515630610    Table_map    1    515630695    table_id: 3411637 (project.card)</p>
<p>可以看到上下文中有 BEGIN 开启事务（因为这个事务比较多，下面还有很多内容已删除忽略），所以我们看到事务中有 team、team_config、<code>org_user</code> 等等表。</p>
<p>因为这种上下午内容通常只有一个地方会有，所以我们锁定了「创建团队」这个操作，并且将操作定位到 team_config 表操作和 team_member 表 到中间操作。</p>
<p>那么在代码中缩小了范围之后，很快就定位到问题了：</p>
<blockquote>
<p>插入到 org_user 数据在读取的适合，没有取 status 状态，所以默认是 0，于是被插入到了数据库中。</p>
</blockquote>
<h3 id="heading-5a6m">完</h3>
<p>随着代码规模的变大，有些问题没那么好检查了，那么通过 binlog 的方式，还是能帮助定位这些问题的。</p>
<p>整个处理过程就完事了，最开始是想通过 <code>https://github.com/zendesk/maxwell</code> 这个 java 库来导出 binlog，并输出成 txt 文件。</p>
<p>结果这个库的 filter 功能并不好用，无法满足我的筛选功能，所以索性手写。</p>
<p>整个查处过程还是挺好玩的，自己踩的坑并没有在文中提及。</p>
]]></content:encoded></item><item><title><![CDATA[go-git 的grpc的实现]]></title><description><![CDATA[go-git 是什么
go-git 是 github 上一个 go 开发的开源项目。

go-git is a highly extensible git implementation library written in pure Go.

实现了大部分的 git 操作，可以对 git 仓库进行一些相关的操作。
为什么要实现 grpc 版

growerlab 项目需要，因为一开始就期望 growerlab 能实现将底层的 git 操作是一个分布式的微服务。
包括之后可能会支持 push/pu...]]></description><link>https://mozz.in/go-git-grpc</link><guid isPermaLink="true">https://mozz.in/go-git-grpc</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Fri, 23 Apr 2021 00:30:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-go-git">go-git 是什么</h2>
<p><a target="_blank" href="https://github.com/go-git/go-git">go-git</a> 是 github 上一个 go 开发的开源项目。</p>
<blockquote>
<p>go-git is a highly extensible git implementation library written in pure Go.</p>
</blockquote>
<p>实现了大部分的 git 操作，可以对 git 仓库进行一些相关的操作。</p>
<h3 id="heading-grpc">为什么要实现 grpc 版</h3>
<ol>
<li>growerlab 项目需要，因为一开始就期望 growerlab 能实现将底层的 git 操作是一个分布式的微服务。<ol>
<li>包括之后可能会支持 push/pull 操作</li>
</ol>
</li>
<li>感觉可能某些组织需要？那写一个出来可能能帮助大家</li>
<li>gitlab 也有类似的服务，不过是远程调用 git 命令，将 git 命令 grpc 化，我不觉得性能更高</li>
</ol>
<p>其实主要是「贪玩」。</p>
<h3 id="heading-5aac5l2v5a6e546w">如何实现</h3>
<p>其实还没啥难度，go-git 本身对仓库读写操作封装非常好，仅实现<code>storage.Storer</code>接口，并将其 grpc 化即可。</p>
<p>甚至一些流式的操作，grpc 也都支持了，所以接入还是比较简单的。</p>
<h3 id="heading-6ykj5lmi5lut5bqt5zyo5zoq5zgi">那么仓库在哪呢</h3>
<p>github 地址：<a target="_blank" href="https://github.com/growerlab/go-git-grpc">go-git-grpc</a></p>
<p>欢迎提供 pr</p>
<h3 id="heading-todo-andamp">TODO &amp; 测试</h3>
<p>目前测试也不充分，并且还有一些功能并不完善，例如</p>
<ul>
<li>中间件的支持</li>
<li>push/pull 的实现</li>
</ul>
<p>有空会加上更多测试 以及 完成上面的 todo（码农立的 todo，呵呵……）</p>
]]></content:encoded></item><item><title><![CDATA[海量数据去重]]></title><description><![CDATA[起因
有个哥们，有 5000G 数据需要去重。
这么大数据其实挺不好处理的，尤其是超不注意就内存/磁盘炸裂。
所以，如何做到性能、内存、磁盘之间的平衡，就是这个问题的难题……
其实这个问题让我想起「编程珠玑」中的一篇内容……
方案
刚开始大家觉得使用使用 redis 的 hash 能力来处理，set/hash 都可以，但是不管是直接丢字符串，还是将字符串 hash 计算后存储，其实都会比较耗费内存。并且 hash 后的数据还会存在一定概率 hash 碰撞，此时更不好处理了。
于是在我们小群里面进...]]></description><link>https://mozz.in/5rw36yep5pww5o2u5y676yen</link><guid isPermaLink="true">https://mozz.in/5rw36yep5pww5o2u5y676yen</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Wed, 21 Apr 2021 16:30:00 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-6lw35zug">起因</h2>
<p>有个哥们，有 5000G 数据需要去重。</p>
<p>这么大数据其实挺不好处理的，尤其是超不注意就内存/磁盘炸裂。</p>
<p>所以，如何做到性能、内存、磁盘之间的平衡，就是这个问题的难题……</p>
<p>其实这个问题让我想起「编程珠玑」中的一篇内容……</p>
<h2 id="heading-5pa55qgi">方案</h2>
<p>刚开始大家觉得使用使用 redis 的 hash 能力来处理，set/hash 都可以，但是不管是直接丢字符串，还是将字符串 hash 计算后存储，其实都会比较耗费内存。并且 hash 后的数据还会存在一定概率 hash 碰撞，此时更不好处理了。</p>
<p>于是在我们小群里面进行了一些讨论……</p>
<p>于是大家纷纷出招：</p>
<ul>
<li>磁盘换内存式的 kv 数据库（例如 leveldb、boltdb）</li>
<li>redis 布隆过滤器（布隆过滤器内部也是用的 bitmap，关键）</li>
<li>redis bitmap</li>
<li>mapReduce</li>
</ul>
<p>其实都会有一些优缺点吧。</p>
<ul>
<li>磁盘式 kv db 性能不太行，同时读写磁盘可能会炸裂</li>
<li>redis 布隆过滤器的话 存在误差，毕竟 「存在的不一定存在，不存在的一定不存在……」</li>
<li>redis bitmap 如上，存在一定误差，毕竟还是会有撞库的可能</li>
<li>mapReduce 好像有大炮打蚊子的感觉……</li>
</ul>
<p>ok，最终因为忙工作，也没细想这个问题了，反正也是嘴炮一番，这事就过了。</p>
<h2 id="heading-5yeg5asp5lml5zco4ocm4ocm">几天之后……</h2>
<p>过了几天看到哥们发了几篇布隆过滤器的文章，然后怎么还在研究这事。</p>
<p>此时刚好在上班的路上，又重新想了想这个方案。</p>
<p>因为之前公司的产品中有同事提出使用布隆过滤器的「存在则可能存在」的特性做一个减少检索范围的筛选器。</p>
<p>所以恰好好像可以反过来想想，此时方案流程可以是这样：</p>
<ol>
<li>利用布隆过滤器「不存在则一定不存在」的特性</li>
<li>例如<ol>
<li>传入「张三」<ol>
<li>不存在<ol>
<li>并标记到「存储器」中（如果为了省内存，其实也可以使用安全 hash 算法进行计算后存入）</li>
</ol>
</li>
<li>存在<ol>
<li>从「存储器」中拿到撞库的字符串列表<ol>
<li>进行暴力匹配</li>
<li>并将「张三」存入布隆过滤器，也就是上面撞库的字符串列表中</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
</ol>
</li>
<li>重复上面的步骤。</li>
</ol>
<h3 id="heading-5lik6z2i6k055qe44cm5a2y5yko5zmo44cn5piv5lua5lmi5zgi77yf">上面说的「存储器」是什么呢？</h3>
<ul>
<li>可以是磁盘 kvdb</li>
<li>可以是 redis 内存 set/hash<ul>
<li>如果重复字符串是低概率的事情才可以这么做，否内存炸裂</li>
</ul>
</li>
</ul>
<h3 id="heading-5zyo44cm6lb6kgm5pq05yqb5yy56ywn44cn6lz5q2l6aqk5lit55qe6zeu6aky">在「进行暴力匹配」这步骤中的问题</h3>
<p>如果重复是大概率的事情，那么暴力匹配的耗时会非常多，变成一个 (O)n 的搜索，性能急剧下降。</p>
<p>此时有点像普通的二叉树，搜索匹配可能变成一个「链表」。</p>
<p>针对这种问题，可以做一些改进：</p>
<ol>
<li>直接存入磁盘 kvdb</li>
<li>将字符串通过 安全 hash 算法计算成数字存入 redis bitmap/hash<ol>
<li>对字符串进行 hash 的目的是降低内存占用</li>
</ol>
</li>
</ol>
<h2 id="heading-54s26icm">然而</h2>
<p>然而哥们直接用了布隆过滤器的「不存在一定不存在」的特性直接「粗」过滤了。</p>
<p>所以……还是水一篇博客吧。</p>
]]></content:encoded></item><item><title><![CDATA[Keycloak 源码分析 - 账号拉取]]></title><description><![CDATA[Keycloak 是什么
wiki 百科

Keycloak 是一个开源软件产品，旨在为现代的应用程序和服务，提供包含身份管理和访问管理功能的单点登录工具。

所以这个工具主要是为了解决账号的同步、身份管理与统一访问下的单点登录工具。
例如你可以将 github、google、facebook 等基于 OAuth2 的账号体系，以及基于 ldap 的账号管理体系集成到 keycloak。
我们并不需要关心账号的来源，只需要与 keycloak 进行通讯，即可解决多平台下不同账号体系的登录问题。
...]]></description><link>https://mozz.in/keycloak</link><guid isPermaLink="true">https://mozz.in/keycloak</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Mon, 12 Apr 2021 11:30:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-keycloak">Keycloak 是什么</h3>
<p>wiki 百科</p>
<blockquote>
<p>Keycloak 是一个开源软件产品，旨在为现代的应用程序和服务，提供包含身份管理和访问管理功能的单点登录工具。</p>
</blockquote>
<p>所以这个工具主要是为了解决账号的同步、身份管理与统一访问下的单点登录工具。</p>
<p>例如你可以将 github、google、facebook 等基于 OAuth2 的账号体系，以及基于 ldap 的账号管理体系集成到 keycloak。</p>
<p>我们并不需要关心账号的来源，只需要与 keycloak 进行通讯，即可解决多平台下不同账号体系的登录问题。</p>
<p>我这下载的版本是 keycloak v12.0.1</p>
<h4 id="heading-5yws5y45lqn5zob55qe6kej5yaz5pa55qgi">公司产品的解决方案</h4>
<p>事实上在公司的产品中，我们需要提供类似 keycloak 的能力给客户，但是并不能给我们提供完整的解决方案。</p>
<p>但是致所有要看 keycloak 的源码，主要是有几点期望。</p>
<ul>
<li>了解用户列表的拉取<ul>
<li>ldap 拉取如何实现大规模用户的拉取</li>
<li>如何合并用户</li>
</ul>
</li>
</ul>
<h4 id="heading-5rwb56il">流程</h4>
<p>Keycloak 主要做几件事</p>
<ol>
<li>将用户拉取下来</li>
<li>将用户合并到系统中</li>
<li>将用户通过协议（例如 OAuth2）暴露出来</li>
</ol>
<p>因为我们的业务系统中，比较比较重「用户合并」这个能力，但是目前看起来，keycloak 解决的问题跟公司的业务不是很 match。</p>
<p>不过还是大概将 keycloak 的源码大概看了一下。</p>
<p>下面会主要关注 ldap 相关的实现，之所以关注 ldap 主要是因为我们的客户群体用 ldap 的较多，其次是 ldap 的用户量通常比较多，以及具有一定的代表性。</p>
<h4 id="heading-ldap">LDAP 的用户拉取</h4>
<blockquote>
<p>federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/IdentityStore.java</p>
</blockquote>
<p>    public interface IdentityStore {</p>
<p>        LDAPConfig getConfig();</p>
<p>        void add(LDAPObject ldapObject);</p>
<p>        void update(LDAPObject ldapObject);</p>
<p>        void remove(LDAPObject ldapObject);</p>
<p>        public void addMemberToGroup(String groupDn, String memberAttrName, String value);</p>
<p>        public void removeMemberFromGroup(String groupDn, String memberAttrName, String value);</p>
<p>        List fetchQueryResults(LDAPQuery LDAPQuery);</p>
<p>        int countQueryResults(LDAPQuery LDAPQuery);</p>
<p>        Set queryServerCapabilities();</p>
<p>        void validatePassword(LDAPObject user, String password) throws AuthenticationException;</p>
<p>        void updatePassword(LDAPObject user, String password, LDAPOperationDecorator passwordUpdateDecorator);</p>
<p>    }</p>
<p>上面的代码删除了一些注释，ldap 的查询实现中主要实现了这个接口，诸如 拉取、验证、数量、配置等等。</p>
<p>具体的实现在</p>
<blockquote>
<p>federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/store/ldap/LDAPIdentityStore.java</p>
</blockquote>
<p>    public class LDAPIdentityStore implements IdentityStore {
     // ……
    }</p>
<p>然后在 <code>LDAPQuery</code> 中会进一步封装调用上面的代码，</p>
<blockquote>
<p>federation/ldap/src/main/java/org/keycloak/storage/ldap/idm/query/internal/LDAPQuery.java</p>
</blockquote>
<p>    public class LDAPQuery implements AutoCloseable {
    }</p>
<p>当然，在往上找，就是各种工具类的封装了。</p>
<p>其实本身这些代码没有太多亮点，基本上都是业务上的逻辑（当然可能是我本身 java 接触不多，仅仅是 n 年前写过安卓的水平，海涵）。</p>
<h4 id="heading-55so5oi355qe5zci5bm2">用户的合并</h4>
<p>keycloak 的用户的合并逻辑其实跟我们公司业务中的用户合并差别很大，几乎不 match……</p>
<p>但是还是看一下，当学习……</p>
<p>用户合并实现了很多的接口。</p>
<blockquote>
<p>federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProvider.java</p>
</blockquote>
<p>    public class LDAPStorageProvider implements UserStorageProvider,
            CredentialInputValidator,
            CredentialInputUpdater.Streams,
            CredentialAuthentication,
            UserLookupProvider.Streams,
            UserRegistrationProvider,
            UserQueryProvider.Streams,
            ImportedUserValidation {
    }</p>
<blockquote>
<p>federation/ldap/src/main/java/org/keycloak/storage/ldap/LDAPStorageProviderFactory.java</p>
</blockquote>
<p>    public class LDAPStorageProviderFactory implements UserStorageProviderFactory, ImportSynchronization {</p>
<p>      public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) {
      }</p>
<p>      protected SynchronizationResult importLdapUsers(KeycloakSessionFactory sessionFactory, final String realmId, final ComponentModel fedModel, List ldapUsers) {
      }</p>
<p>      // ……
    }</p>
<p>上面的工厂类主要就是同步并导入用户到 keycloak 中。</p>
<h3 id="heading-5zco6kd">后话</h3>
<p>看到 keycloak 其实有地方也挺简单直接的，比如在 ldap 不管用户量的情况下，也是直接 fetch 加载到内存中的。</p>
<p>在我司的业务场景中，合并还是一个比较重的操作，并没有用流式方式来合并处理这些业务，当然跟业务场景有关。</p>
<p>其实 java 的代码比 go 的代码好看很多啊（可能是知名项目本身的质量较高…毕竟红帽出品…），基本上看到其名字就知道其他业务功能。</p>
<p>我司的代码质量还有提升空间～～～</p>
]]></content:encoded></item><item><title><![CDATA[golang1.5之后gopls在vscode中无法启动的问题]]></title><description><![CDATA[gopls 在 go1.5 之后的坑 - 简述
由于 go1.5 之后官方的 go module 是默认强制开启的。
所以 gopls 在一些未使用 go module 的项目中会有一些坑。
比如公司的老项目还用的 govendor 这个依赖管理工具，官方早就未维护了。
其实迁移到新的 go module 也不麻烦，但是似乎并没人来推动这个事。
未使用 go module 的坑
一、gopls 无法启动，提示必须使用 go mod
解决方式：修改 vscode 的配置 settings.json...]]></description><link>https://mozz.in/golang15goplsvscode</link><guid isPermaLink="true">https://mozz.in/golang15goplsvscode</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Fri, 13 Nov 2020 15:30:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-gopls-go15">gopls 在 go1.5 之后的坑 - 简述</h3>
<p>由于 go1.5 之后官方的 go module 是默认强制开启的。</p>
<p>所以 gopls 在一些未使用 go module 的项目中会有一些坑。</p>
<p>比如公司的老项目还用的 govendor 这个依赖管理工具，官方早就未维护了。</p>
<p>其实迁移到新的 go module 也不麻烦，但是似乎并没人来推动这个事。</p>
<h3 id="heading-go-module">未使用 go module 的坑</h3>
<h4 id="heading-gopls-go-mod">一、gopls 无法启动，提示必须使用 go mod</h4>
<p>解决方式：修改 vscode 的配置 settings.json 文件，新增配置</p>
<p>    {
      "go.toolsEnvVars": {
        "GO111MODULE": "auto"
      }
    }</p>
<p>gopls 似乎无法识别的系统环境变量中的 <code>GO111MODULE</code> 变量，但是在设置中修改，是有效的。</p>
<h4 id="heading-gopls">二、gopls 警告：依赖模块不对</h4>
<p>    The code in the workspace failed to compile (see the error message below).
    If you believe this is a mistake, please file an issue: https://github.com/golang/go/issues/new.
    unexpected directory layout:
        import path: <em>/Users/me/go-project/src/github.com/compeny/compeny-api/app/models
        root: /Users/me/go-project/src
        dir: /Users/me/go-project/src/github.com/compeny/compeny-api/app/models
        expand root: /Users/me/go-project
        expand dir: /Users/me/go-project/src/github.com/compeny/compeny-api/app/models
        separator: /
    unexpected directory layout:
        import path: </em>/Users/me/go-project/src/github.com/compeny/compeny-api/app/models
        root: /Users/me/go-project/src
        dir: /Users/me/go-project/src/github.com/compeny/compeny-api/app/models
        expand root: /Users/me/go-project
        expand dir: /Users/me/go-project/src/github.com/compeny/compeny-api/app/models
        separator: /
    : packages.Load error</p>
<p>解决方式：根据提示，删除 $GOPATH 中 pkg、src 中可能重复的代码即可</p>
]]></content:encoded></item><item><title><![CDATA[最近的一些git研究]]></title><description><![CDATA[前话
最近在写 git 的一个服务，也就是 growerlab 的一个组件。
mensa - https://github.com/growerlab/mensa
最近做的研究主要是用户的推送时以及推送到服务器的 hook 相关的功能。
推送时的权限验证时机
整个服务流程

git push
mensa - 调用 git-refs 权限权限
mensa - 调用 git-receive-pack
mensa - 调用 hooks 创建 events
hook 时机
新分支的 commits


...]]></description><link>https://mozz.in/git</link><guid isPermaLink="true">https://mozz.in/git</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Thu, 12 Nov 2020 18:30:00 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-5ymn6kd">前话</h3>
<p>最近在写 git 的一个服务，也就是 growerlab 的一个组件。</p>
<p>mensa - https://github.com/growerlab/mensa</p>
<p>最近做的研究主要是用户的推送时以及推送到服务器的 hook 相关的功能。</p>
<h3 id="heading-5o6o6ycb5pe255qe5p2d6zmq6aqm6kb5pe25py6">推送时的权限验证时机</h3>
<p>整个服务流程</p>
<ul>
<li>git push</li>
<li>mensa - 调用 git-refs 权限权限</li>
<li>mensa - 调用 git-receive-pack</li>
<li>mensa - 调用 hooks 创建 events<ul>
<li>hook 时机</li>
<li>新分支的 commits</li>
</ul>
</li>
</ul>
<h4 id="heading-hook">hook 时机的坑</h4>
<p>最开始是想在创建仓库的时候，ln -s 软链一个 update 的 hook 到对应的钩子，这个是很久之前的 gitlab 的版本的方式。</p>
<p>后来查阅 gitlab 开发的 gitaly 代码，才发现 gitlab 这些年做了很多的改造，避开了很多 n 年前的坑。</p>
<p>比如 hook 的接入时机改成了调用 git 命令时</p>
<p>    git xxxx -c core.hooksPath=hook path</p>
<p>这样其实还是非常方便的，避免了无避免的软链</p>
<h4 id="heading-commits">新分支的 commits</h4>
<p>hook 的功能之一就是创建 events。</p>
<p>普通的 commits 还好说，但是如果一个新的 分支，并且里面有几个新的 commits 时，这个问题倒是花了我一点时间研究测试。</p>
<p>后来发现，调用 update hook 时，还并不会在 refs 中创建对应的分支。</p>
<p>并且通过 go-git 试验发现，新提交的分支中的新 commit，查找他的 parents 时，只能拿到新提交的 commits。</p>
<p>所以当不管用户提交新的 branch 还是新的 tag，都是可以拿到新提交的 commit 的。</p>
<h3 id="heading-5a6m">完</h3>
<p>在做代码托管平台时，其实很多 git 的本身的很多问题都是可以通过测试发现的，有的坑必须踩呀。</p>
<p>又记一片流水账。。</p>
]]></content:encoded></item><item><title><![CDATA[通过UCloud GlobalSSH加速github推拉速度/暴露内网替代花生壳]]></title><description><![CDATA[前话
本来正在想重构 growerlab 的权限功能 github.com/growerlab，考虑到未来权限的验证是一件非常麻烦的事，以及在其他微服务的功能通用性上，所以打算重新设计权限能力。
当然，功能是跟在 ONES 的同事讨论沟通的设计，非原创，改天推到仓库中。
ONES 是一个非常棒的项目管理系统，欢迎大佬们尝试使用。
UCloud GlobalSSH 的妙用
首先感谢 ucloud 的 GlobalSSH 功能，这个太棒了！希望不要停掉这个服务（目前免费，啥时候收费也支持一波！）
目...]]></description><link>https://mozz.in/ucloud-globalsshgithub</link><guid isPermaLink="true">https://mozz.in/ucloud-globalsshgithub</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Sun, 16 Aug 2020 22:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683387834314/116d9435-1ac2-4b12-9757-48544d87e57a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-5ymn6kd">前话</h3>
<p>本来正在想重构 growerlab 的权限功能 <a target="_blank" href="https://github.com/growerlab">github.com/growerlab</a>，考虑到未来权限的验证是一件非常麻烦的事，以及在其他微服务的功能通用性上，所以打算重新设计权限能力。</p>
<p>当然，功能是跟在 ONES 的同事讨论沟通的设计，非原创，改天推到仓库中。</p>
<p>ONES 是一个非常棒的项目管理系统，欢迎大佬们尝试使用。</p>
<h3 id="heading-ucloud-globalssh">UCloud GlobalSSH 的妙用</h3>
<p>首先感谢 ucloud 的 GlobalSSH 功能，这个太棒了！希望不要停掉这个服务（目前免费，啥时候收费也支持一波！）</p>
<p>目前我主要用来做 github 的推拉加速、暴露内网替代花生壳。</p>
<h4 id="heading-github">github 推拉加速</h4>
<p>因为国内众所周知的原因，我们使用 github 推拉代码非常的卡、慢。</p>
<p>但是其实我们可以通过 TCP 级别的代理转发 SSH 流量。</p>
<p>如果自建一个 VPS 来做这个事情，其实使用、维护成本还是比较高的。</p>
<p>所以我们可以通过 UCloud 的 GlobalSSH 服务（所谓是全球 SSH 加速服务？）</p>
<p>亲测可行。</p>
<p><img src="/assets/images/2020/08/github-ssh-1.png" alt="截图" /></p>
<p><img src="/assets/images/2020/08/github-ssh-2.png" alt="截图2" /></p>
<p>创建一个你的 GlobalSSH 服务即可，通过 ping github.com 即可拿到服务器地址。</p>
<p>然后我们得到「加速域名」。</p>
<p>比如现在我们要加速 github 项目。</p>
<p>这个项目现在的 ssh 地址是 <code>git@github.com:xxx/xxx.git</code></p>
<p>现在我们将他改成 <code>git@xxxx.ipssh.net:xxx/xxx.git</code></p>
<p>注意加粗部分就是我们的加速服务器地址。</p>
<p>然后我们在 github 项目目录下执行：</p>
<p>    $ git remote set-url origin git@xxxx.ipssh.net:xxx/xxx.git</p>
<h5 id="heading-5ym5asw5lia56en566a5y2v5pa55rov">另外一种简单方法</h5>
<p>    $ vim ~/.ssh/config</p>
<p>新增配置：</p>
<p>     Host github.com
         User    git
         Hostname 52.xxx.xxx.xxx.ipssh.net
         Port    22</p>
<p>这样所有的 github.com 走 ssh 时，都会走该 hostname</p>
<p>完成替换后就是使用新的加速地址了。</p>
<h4 id="heading-5pq06zyy5yaf572r5pu5luj6iqx55sf5aoz">暴露内网替代花生壳</h4>
<p>因为最近做飞书的机器人开发，所有需要调试机器人，OAuth2 授权、ticket 回调等，都需要暴露本地的内网，使内网程序能被公网访问。</p>
<p>相同的功能有花生壳，ngrok。</p>
<ul>
<li>花生壳：域名要购买，有点麻烦</li>
<li>ngrok：免费不固定域名，而且不管免费、收费速度都不快，毕竟是国外服务。</li>
</ul>
<p>这个功能其实是要一个 VPS 的，但是因为我的服务器在国外，直接 ssh 上经常断断续续非常苦恼。</p>
<p>目前国内这个冲浪环境，程序员应该是人手一台 VPS？</p>
<p>所以使用 GlobalSSH 来中转，效率速度提升非常！</p>
<ol>
<li>在 vps 上启动 nginx</li>
<li>新增一个反向代理配置，将请求全部转到 2222 端口</li>
<li><p>如下方式启动 ssh 代理</p>
<p>$ ssh -vnNt -i ~/.ssh/id_rsa -R 2222:localhost:9001 root@123.123.123.123.ipssh.net</p>
</li>
</ol>
<p>这样 nginx 会将请求转到 2222 端口，ssh 代理会将 2222 的请求转到本地的 9001 端口。</p>
<p>在本地服务启动并监听 9001 端口，当用户访问服务器时，nginx 会将整个请求转到本地监听 9001 端口的程序。</p>
<h4 id="heading-5a6m5qv">完毕</h4>
<p>记了一篇流水账，希望能帮到有需要的人。</p>
]]></content:encoded></item><item><title><![CDATA[程序设计的一些总结]]></title><description><![CDATA[确实太久没写文章了，没啥理由，就是懒了。
没想到 2020 年过得真快，眼看进度条已经快 40%了。
2020 年发生了太多事，大到全球疫情，小到家庭矛盾，总体来说不太走运，但是人总要乐观一点，否则真的会不太好过。
偏题了，这篇文字是想写一些程序设计相关的一些感悟，观察，感慨。观察一些厉害的人，学习他人优点，补充一些自己的不足。
聪明人的自带天赋

如果一定要说聪明人之所以聪明，我一定会说：记忆力。

印象中物理界、数学界大佬，现实中认识的聪明人，无一例外记忆力都挺强。对曾经发生的事，甚至一些细...]]></description><link>https://mozz.in/56il5bqp6k66k6h55qe5lia5lqb5oc757ut</link><guid isPermaLink="true">https://mozz.in/56il5bqp6k66k6h55qe5lia5lqb5oc757ut</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Wed, 22 Apr 2020 00:30:00 GMT</pubDate><content:encoded><![CDATA[<p>确实太久没写文章了，没啥理由，就是懒了。</p>
<p>没想到 2020 年过得真快，眼看进度条已经快 40%了。</p>
<p>2020 年发生了太多事，大到全球疫情，小到家庭矛盾，总体来说不太走运，但是人总要乐观一点，否则真的会不太好过。</p>
<p>偏题了，这篇文字是想写一些程序设计相关的一些感悟，观察，感慨。观察一些厉害的人，学习他人优点，补充一些自己的不足。</p>
<h3 id="heading-6igq5pio5lq655qe6ieq5bim5asp6lwl">聪明人的自带天赋</h3>
<blockquote>
<p>如果一定要说聪明人之所以聪明，我一定会说：记忆力。</p>
</blockquote>
<p>印象中物理界、数学界大佬，现实中认识的聪明人，无一例外记忆力都挺强。对曾经发生的事，甚至一些细节末梢依然描述得非常清晰。</p>
<p>有一次问个大佬一个代码问题，看 git 记录可能已经是 2 年前的代码，主要细节依然很清楚。</p>
<p>而「推理」跟大脑中的信息量是有直接关系的。</p>
<p>so~ 有些天赋得认命~</p>
<h4 id="heading-5oq6auy6k6w5bg5yqb">提高记忆力</h4>
<ul>
<li>好记性不如烂笔头</li>
<li>想更多，更深，持续改进</li>
<li>睡眠、睡眠、睡眠！</li>
</ul>
<p>在程序开发中，厉害的程序员会想清楚方方面面，文字、UML、脑图、架构图、流程图，越细越好，这些东西本质上是在帮助记忆。</p>
<p>当然更重要的是，提前想好，规划好你要做的事。</p>
<p>良好的睡眠则是最好的大脑养护。</p>
<p>某大佬：程序设计=睡好觉</p>
<p>大脑的保养很重要。</p>
<h3 id="heading-6zeu6aky55qe5oq96lgh5rgh5oc75lio5pa55qgi6l6t5ye6">问题的抽象汇总与方案输出</h3>
<p>工作中，随着开发、迭代，我们总会碰到一系列的问题，这些问题可能会有一些联系，规律。</p>
<p>那么，对问题的 分类，归纳，总结，找出共同点，或本质的问题，最终输出一个合理的方案来解决问题。</p>
<p>这种能力需要经验以及丰富的知识，视野来提供支撑。</p>
<p>其实也需要一定的创造力。</p>
<h3 id="heading-56il5bqp6k66k6h5piv5lia6zeo5yib6ycg5ocn55qe5bel5l2c">程序设计是一门创造性的工作</h3>
<p>上面说到程序设计中的一系列问题，工程师本质上就是一直在解决各类问题。</p>
<p>而各类解决方案的输出，本质上是一门创造性的工作，也许有一些方案已经存在了轮子，可以拿过来用，比如搜索功能 es。</p>
<p>但是某些没有的功能，是创造性的，需要权衡各种利弊的，甚至需要一些文献来提供支撑的，至少在逻辑上合情合理的。</p>
<p>普通程序员与高阶程序员一定程度上就是这种差异，这种差异往往是很难追赶的。</p>
<p>普通程序员如果想往上爬，做更厉害的程序员，需要思考更多。</p>
<p>与厉害的程序员交流是非常好的方式，当然你得提前将你的问题整理成问题，你的解决方案，你的思路，要解决的问题，这样更大佬交流才能更高效。</p>
]]></content:encoded></item><item><title><![CDATA[数据库中的重型字段迁移优化（golang）]]></title><description><![CDATA[最近在数据迁移中的一些优化点，在这里分享一些，当然也不一定是最优的，欢迎讨论。
首先，在某些数据表中的字段，存着非常大的json数据。
并且有几十万行数据，需要更改json中的某个数组字段，追加一些元素。
常规方式

读取所有的行
json反序列化
修改数据
json序列号并保持到数据库

有几个问题

内存占用非常大，每行大概至少有8KB的数据，各种对象，内存申请，这个迁移程序可能会吃到数G的内存。
性能也不好，在读取数据库期间，其实是浪费了的

第二次优化
按每个团队划分，分别查询，内存降低...]]></description><link>https://mozz.in/golang</link><guid isPermaLink="true">https://mozz.in/golang</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Mon, 16 Dec 2019 19:30:00 GMT</pubDate><content:encoded><![CDATA[<p>最近在数据迁移中的一些优化点，在这里分享一些，当然也不一定是最优的，欢迎讨论。</p>
<p>首先，在某些数据表中的字段，存着非常大的json数据。</p>
<p>并且有几十万行数据，需要更改json中的某个数组字段，追加一些元素。</p>
<h4 id="heading-5bi46kee5pa55byp">常规方式</h4>
<ul>
<li>读取所有的行</li>
<li>json反序列化</li>
<li>修改数据</li>
<li>json序列号并保持到数据库</li>
</ul>
<p>有几个问题</p>
<ul>
<li>内存占用非常大，每行大概至少有8KB的数据，各种对象，内存申请，这个迁移程序可能会吃到数G的内存。</li>
<li>性能也不好，在读取数据库期间，其实是浪费了的</li>
</ul>
<h4 id="heading-56ys5lqm5qyh5lyy5yyw">第二次优化</h4>
<p>按每个团队划分，分别查询，内存降低了一点，因为golang中，使用过的对象并不会立即GC掉。</p>
<p>由于频繁的make对象，所以内存不会立即gc，内存还是比较高的。</p>
<h4 id="heading-56ys5lij5qyh5lyy5yyw">第三次优化</h4>
<p>使用golang中的Pool功能来复用对象：</p>
<p>    var issueTypePool = sync.Pool{
        New: func() interface{} {
            return make([]*IssueType, 0)
        },
    }</p>
<p>数据库查询</p>
<p>    issueTypes := issueTypePool.Get().([]*IssueType)
    _, err := tx.Select(&amp;issueTypes, sql, teamUUID)
    if err != nil {
        return nil, err
    }</p>
<p>恢复对象到对象池：</p>
<p>    defer issueTypePool.Put(issueTypes[:0])</p>
<p>这样已经不错了，对象能复用，内存占用取决于最大的团队的内存占用。。</p>
<h4 id="heading-56ys5zub5qyh5lyy5yyw">第四次优化</h4>
<p>使用golang中的游标方式。</p>
<p>下面的代码中有第二个参数就是channel缓冲区，并且在rows中边读边丢入channel</p>
<p>    func AllObjects(tx <em>sq.DB, issueTypeChan chan&lt;- </em>IssueType) error {
        // 。。。。
        for rows.Next() {
            it := new(IssueType)
            err = rows.Scan(&amp;it.UUID, &amp;it.TeamUUID, &amp;it.DefaultConfigs)
            if err != nil {
                return err
            }
            issueTypeChan &lt;- it
        }
        return nil
    }</p>
<p>启动goroutine，并在持续从chan缓冲区读取数据，同时对数据修改迁移，并写入数据库。</p>
<p>    issueTypeChan := make(chan <em>IssueType, 1000)
    doneChan := make(chan bool)
    timer := time.NewTimer(5 </em> time.Second)
    defer timer.Stop()</p>
<p>    go func() {
        for {
            select {
            case &lt;-timer.C:
                close(doneChan)
                fmt.Println("done...")
                return
            case it := &lt;-issueTypeChan:
                timer.Reset(5 * time.Second)
                // TODO ....
            }
        }
    }()</p>
<p>这样就比较满意了，但是这种方式还没有经过测试，理论上读写分离之后，性能是更优的。</p>
]]></content:encoded></item><item><title><![CDATA[升级go1.12版本遇到的问题]]></title><description><![CDATA[升级到 go1.12 碰到一个依赖版本问题
    xxxxxxx/vendor/go4.org/reflectutil.typedmemmove: relocation target runtime.typedmemmove not defined for ABI0 (but is defined for ABIInternal)
解决方案：
删除 vendor 目录中的 go4.org 目录
    执行：govendor update go4.org/reflectutil
    总之就...]]></description><link>https://mozz.in/go112</link><guid isPermaLink="true">https://mozz.in/go112</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Fri, 16 Aug 2019 15:30:00 GMT</pubDate><content:encoded><![CDATA[<p>升级到 go1.12 碰到一个依赖版本问题</p>
<p>    xxxxxxx/vendor/go4.org/reflectutil.typedmemmove: relocation target runtime.typedmemmove not defined for ABI0 (but is defined for ABIInternal)</p>
<p>解决方案：</p>
<p>删除 vendor 目录中的 go4.org 目录</p>
<p>    执行：govendor update go4.org/reflectutil</p>
<p>    总之就是更新这个包到新版</p>
<p>即可</p>
<p>\=========</p>
<p>奶一波我的一个网站：<a target="_blank" href="https://v2geek.com">https://v2geek.com</a> ，欢迎大家来贩卖自己的软件。</p>
]]></content:encoded></item><item><title><![CDATA[换个新博客皮肤]]></title><description><![CDATA[呼呼，晚上加班上线比较无聊，所以换个博客皮肤，感觉清爽一些~
瞧了下十多年前写的博客，感觉好傻逼。。。🙄
感慨时光飞逝~~ 竟已是孩子他爹~~~~
\=========
奶一波我的一个网站：https://v2geek.com ，欢迎大家来贩卖自己的软件。]]></description><link>https://mozz.in/5o2i5liq5paw5y2a5a6i55qu6ikk</link><guid isPermaLink="true">https://mozz.in/5o2i5liq5paw5y2a5a6i55qu6ikk</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Wed, 07 Aug 2019 00:30:00 GMT</pubDate><content:encoded><![CDATA[<p>呼呼，晚上加班上线比较无聊，所以换个博客皮肤，感觉清爽一些~</p>
<p>瞧了下十多年前写的博客，感觉好傻逼。。。🙄</p>
<p>感慨时光飞逝~~ 竟已是孩子他爹~~~~</p>
<p>\=========</p>
<p>奶一波我的一个网站：<a target="_blank" href="https://v2geek.com">https://v2geek.com</a> ，欢迎大家来贩卖自己的软件。</p>
]]></content:encoded></item><item><title><![CDATA[学习Rust]]></title><description><![CDATA[最近想学 rust 主要是好奇心。。
其实 2016 年就接触过，但是当时学得有点难受，于是放弃了，也没有目标性。
于是现在又学一下 rust~ 感觉还是有必要掌握一门高性能的语言，虽然 c/cpp 也可以，但是毕竟还是有点古老了。
而且比较看好 rust，哈哈。
而且最近想整一个小项目，需要一点高性能的东西，虽然理论上 golang 完全够用了，但是就当学一个新东西，顺便用起来，也挺好的。
完毕。。。
\=========== 8 月 7 号更新
rust 学习确实比较陡，估计学 1 个月才能...]]></description><link>https://mozz.in/rust</link><guid isPermaLink="true">https://mozz.in/rust</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Sat, 03 Aug 2019 22:10:00 GMT</pubDate><content:encoded><![CDATA[<p>最近想学 rust 主要是好奇心。。</p>
<p>其实 2016 年就接触过，但是当时学得有点难受，于是放弃了，也没有目标性。</p>
<p>于是现在又学一下 rust~ 感觉还是有必要掌握一门高性能的语言，虽然 c/cpp 也可以，但是毕竟还是有点古老了。</p>
<p>而且比较看好 rust，哈哈。</p>
<p>而且最近想整一个小项目，需要一点高性能的东西，虽然理论上 golang 完全够用了，但是就当学一个新东西，顺便用起来，也挺好的。</p>
<p>完毕。。。</p>
<p>\=========== 8 月 7 号更新</p>
<p>rust 学习确实比较陡，估计学 1 个月才能上手~ 所以投入产出比太低了~ 先弃坑了~~~</p>
<p>奶一波我的一个网站：<a target="_blank" href="https://v2geek.com">https://v2geek.com</a> ，欢迎大家来贩卖自己的软件。</p>
]]></content:encoded></item><item><title><![CDATA[golang写一个配置同步工具]]></title><description><![CDATA[开源地址：https://github.com/molizz/synconfig
Synconfig 是什么
这个工具是基于 boltDB 开发的基于 http 协议的 配置同步工具。
这个的初衷是之前写一个你懂得工具，然后在分布在各地服务器，如果每次增加个新的服务器，那么就要重新同步配置，总之非常容易出错，是一个非常不可靠的设计。
于是就想整一个“配置中心”的功能，最开始想用 etcd，但是好像有点复杂了，所以感觉自己写了一个 服务端和客户端。
第三方的软件，直接集成客户端的功能，就实现配置的...]]></description><link>https://mozz.in/golang-1</link><guid isPermaLink="true">https://mozz.in/golang-1</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Sun, 07 Jul 2019 22:56:00 GMT</pubDate><content:encoded><![CDATA[<p>开源地址：<a target="_blank" href="https://github.com/molizz/synconfig">https://github.com/molizz/synconfig</a></p>
<h3 id="heading-synconfig">Synconfig 是什么</h3>
<p>这个工具是基于 boltDB 开发的基于 http 协议的 配置同步工具。</p>
<p>这个的初衷是之前写一个你懂得工具，然后在分布在各地服务器，如果每次增加个新的服务器，那么就要重新同步配置，总之非常容易出错，是一个非常不可靠的设计。</p>
<p>于是就想整一个“配置中心”的功能，最开始想用 etcd，但是好像有点复杂了，所以感觉自己写了一个 服务端和客户端。</p>
<p>第三方的软件，直接集成客户端的功能，就实现配置的同步了。</p>
<p>实现 配置 与 服务的解耦。</p>
<p>当有新的配置更新时，所有的客户端能自动收到新的配置，自动更新自身。</p>
<p>造这个轮子不仅仅是为了“你懂得”的软件，未来我的任何新的一些服务，都可以直接集成客户端，并且快速上线</p>
<h3 id="heading-5z65pys5a6e546w">基本实现</h3>
<p>“配置中心” 服务端需要运行在某个服务器的。我是在 vultr 上购买了一个最低配的服务器，每个月 3.5 刀，还可以，上面还跑了一些其他的服务。</p>
<p>“客户端” 配置好服务器端后，就会隔几秒通过自己的 stamp 来获取新的配置，从而更新自身的配置。</p>
<p>通讯上使用 token 来校验，并且可以配置 https 来通讯，避免 token 外泄，也建议使用 https 来与服务端通讯，避免 token 被检测到。</p>
<h3 id="heading-5yw25luw">其他</h3>
<p>通过写这么一个简单的“基础设施”，还是挺好玩的。</p>
<p>我设想的一些很多功能，都会集成这个服务端。</p>
<p>奶一波我的一个网站：<a target="_blank" href="https://v2geek.com">https://v2geek.com</a> ，欢迎大家来贩卖自己的软件。</p>
]]></content:encoded></item><item><title><![CDATA[Golang函数级别的监控]]></title><description><![CDATA[golang函数级别的监控主要使用pprof包中的StartCPUProfile 函数与 StopCPUProfile
本报告主要是围绕这个监控来.

监控函数性能的目的
除了sql慢查询监控, 网络情况等监控, 我们应该也必须监控到函数的性能. 如果因为不合理的代码导致接口变慢, 内存变多等问题, 应能被监控并处理的.

提高接口响应速度
提前预知问题, 并尽早处理, 而不是等到大范围的客户感知被动响应(减少被动率, 提高主动率)
让程序员写出更好的代码
最终的目标是在性能问题被客户感知,放大...]]></description><link>https://mozz.in/golang-1</link><guid isPermaLink="true">https://mozz.in/golang-1</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Mon, 28 Jan 2019 00:56:00 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>golang函数级别的监控主要使用pprof包中的<a target="_blank" href="https://golang.org/src/runtime/pprof/pprof.go?s=23119:23158#L730">StartCPUProfile</a> 函数与 <a target="_blank" href="https://golang.org/src/runtime/pprof/pprof.go?s=25047:25068#L789">StopCPUProfile</a></p>
<p>本报告主要是围绕这个监控来.</p>
</blockquote>
<h2 id="heading-55ur5o6n5ye95pww5ocn6io955qe55uu55qe">监控函数性能的目的</h2>
<p>除了sql慢查询监控, 网络情况等监控, 我们应该也必须监控到函数的性能. 如果因为不合理的代码导致接口变慢, 内存变多等问题, 应能被监控并处理的.</p>
<ul>
<li>提高接口响应速度</li>
<li>提前预知问题, 并尽早处理, 而不是等到大范围的客户感知被动响应(减少被动率, 提高主动率)</li>
<li>让程序员写出更好的代码</li>
<li>最终的目标是在性能问题被客户感知,放大之前, 将性能问题解决掉</li>
</ul>
<h3 id="heading-pprof">PProf简单介绍</h3>
<p>进程中的性能问题主要采用StartCPUProfile &amp; StopCPUProfile函数.</p>
<p>这两个函数是golang自带的性能采集函数, 用于帮助开发者检查并分析进程中的性能问题.</p>
<ul>
<li>StartCPUProfile 开始采集运行栈, 并保存起来</li>
<li>StopCPUProfile 停止采集</li>
</ul>
<h3 id="heading-pprof-1">生产环境中使用PProf会有什么问题</h3>
<p>那么直接在生产环境中会对主进程造成什么影响呢?</p>
<p>golang中的底层readProfile函数:</p>
<p>        // readProfile, provided by the runtime, returns the next chunk of
        // binary CPU profiling stack trace data, blocking until data is available.
        // If profiling is turned off and all the profile data accumulated while it was
        // on has been returned, readProfile returns eof=true.
        // The caller must save the returned data and tags before calling readProfile again.
        func readProfile() (data []uint64, tags []unsafe.Pointer, eof bool)</p>
<p>目测不会对主城造成负担.</p>
<h2 id="heading-6kej5yaz5pa55qgi">解决方案</h2>
<h3 id="heading-prof">分析prof数据</h3>
<p>刚开始接触使用StartCPUProfile的时候, 感觉是一个本地使用的工具, 并不觉得可以用在生产环境(没看源码)</p>
<p>但是参考 <a target="_blank" href="http://github.com/stackimpact/stackimpact-go">http://github.com/stackimpact/stackimpact-go</a> 的源码后, 发现其实StartCPUProfile是可以被集成到生产环境的性能监控的.</p>
<p>疑惑</p>
<ul>
<li>如何解码StartCPUProfile产生的pprof数据</li>
<li>如何统计&amp;量化profile数据 </li>
</ul>
<p>顺着这个思路, 开始研究golang中的go tool pprof 相关的命令源码, 并查阅 stackimpact 源码.</p>
<p>go tool pprof 的命令是基于 <a target="_blank" href="https://github.com/google/pprof/">https://github.com/google/pprof/</a> 实现的.</p>
<ul>
<li>数据通过 <a target="_blank" href="https://developers.google.com/protocol-buffers">protocol-buffers</a> 协议存储</li>
<li>profile的数据结构: <a target="_blank" href="https://github.com/google/pprof/blob/master/proto/profile.proto">https://github.com/google/pprof/blob/master/proto/profile.proto</a></li>
<li>解码功能可以在 google/pprof 库里面找到.<ul>
<li>google/pprof/profile.Parse()</li>
</ul>
</li>
</ul>
<p>查阅完上面的文档后可知道:</p>
<ul>
<li>StartCPUProfile输出的结果是用的google/pprof保存 + GZip方式压缩过的</li>
<li>输出的结果(xxx.prof) 文件是可以通过gzip.Reader 解码后, 通过google/pprof中的序列化成一个结构体的</li>
<li><p>结构体中内容如下:</p>
<p>    type Profile struct {
        SampleType []<em>ValueType
        Sample     []</em>Sample
        Mapping    []<em>Mapping
        Location   []</em>Location
        Function   []*Function</p>
<p>        DropFrames string
        KeepFrames string</p>
<p>        TimeNanos     int64
        DurationNanos int64
        PeriodType    *ValueType
        Period        int64</p>
<p>        dropFramesX int64
        keepFramesX int64
        stringTable []string
    }</p>
</li>
</ul>
<h4 id="heading-pprof-2">解码PProf数据:</h4>
<p>        // 将监控的结果写入内存
        buff := bytes.Buffer{}
        w := bufio.NewWriter(buff)
        pprof.StartCPUProfile(w)
        defer pprof.StopCPUProfile()</p>
<p>        // 解码数据(伪代码)
        import github.com/google/pprof/profile
        encodeBuff := gzip.Reader(buff)
        profile.Parse(encodeBuff)</p>
<p>通过上面的伪代码可以了解到这个搜集pprof的结果.</p>
<h4 id="heading-57ut6k66oidmmolml7blj6rogipomzeg4occ5a6a5pe255ur5o6n4ocdioeahoawuew8jw">结论: 暂时只考虑 “定时监控” 的方式</h4>
<p>        func StartCPUProfile(w io.Writer) error {
                // cpu锁
            cpu.Lock()
            defer cpu.Unlock()
            if cpu.done == nil {
                cpu.done = make(chan bool)
            }
            // 双重锁
            if cpu.profiling {
                return fmt.Errorf("cpu profiling already in use")
            }
            cpu.profiling = true
                // ...
            return nil
        }</p>
<h3 id="heading-4occ5a6a5pe255ur5o6n4ocdiowunueosa">“定时监控” 实现</h3>
<p>        // ...
        func (f *TickerProfiler) tickerFunc() {
            // ...
        }</p>
<p>        // 保持到队列
        func (p *ProfilesData) AddNewProfile(newProfile map[string]int64) {
            // ...
            p.list.Push(profile)
        }</p>
<p>        // 通过接口+时间戳获取队列中的增量数据</p>
<p>        func GetProfiles(stamp int64) []*Profile {
            return profiles
        }</p>
<p>通过每隔一段时间执行 StartCPUProfile 进行采集并保持到队列列表中</p>
<p>定时监控”优点:</p>
<ul>
<li>对主进程影响较低</li>
</ul>
<p>缺点:</p>
<ul>
<li>颗粒度较大, 不一定能实时命中到到较慢的函数(但理论上可以命大所有的函数)</li>
</ul>
<h3 id="heading-4occ5lit6ze05lu24ocdiowunueosa">“中间件” 实现</h3>
<p>通过router.go中加入begin/done的方式监控各个接口请求的性能</p>
<p>        // 伪代码
        Use(StartCPUProfile)
        // 接口
        Done(StopCPUProfile)</p>
<p>优点:</p>
<ul>
<li>颗粒度够细</li>
</ul>
<p>缺点</p>
<ul>
<li>StartCPUProfile 本身是全局锁, 同时只能运行一个该函数</li>
<li>在并发条件下, 依然可能会导致其他接口无法被监控到.</li>
</ul>
<h3 id="heading-5aac5l2v6yep5yyw55ur5o6n5yiw55qe5pww5o2u">如何量化监控到的数据</h3>
<p>采集到数据后, 可通过grafana将数据展示出来, 并提供预警等</p>
<h3 id="heading-5bya5rqq5bqt">开源库</h3>
<p>根据这篇文章写一个golang的监控库:</p>
<p>[https://github.com/molizz/funcpprof](https://github.com/molizz/funcpprof)</p>
]]></content:encoded></item><item><title><![CDATA[最近的一些总结]]></title><description><![CDATA[哇, 这还没到农历年末呢, 就开始总结了吗.
今年年初加入 ONES 这家公司, 做了半年多 golang 后端开发.

坦白说还是写编译型语言舒服.
不喜欢 ruby 了,这种必测型语言很讨厌: 改一个地方, 必须要有个测试校验, 否则真不知道哪里会有坑. golang 没这么泛滥, 基本上编译器已经解决了不少问题, 测试也可以不用写那么复杂.
现在能用 golang 写的, 绝不会用 ruby 了, 除非写网站. 写网站还是 ruby on rails 舒服啊.. 高效..

开发流程总结
...]]></description><link>https://mozz.in/5pya6lr55qe5lia5lqb5oc757ut</link><guid isPermaLink="true">https://mozz.in/5pya6lr55qe5lia5lqb5oc757ut</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Fri, 30 Nov 2018 23:56:00 GMT</pubDate><content:encoded><![CDATA[<p>哇, 这还没到农历年末呢, 就开始总结了吗.</p>
<p>今年年初加入 ONES 这家公司, 做了半年多 golang 后端开发.</p>
<ol>
<li>坦白说还是写编译型语言舒服.</li>
<li>不喜欢 ruby 了,这种必测型语言很讨厌: 改一个地方, 必须要有个测试校验, 否则真不知道哪里会有坑. golang 没这么泛滥, 基本上编译器已经解决了不少问题, 测试也可以不用写那么复杂.</li>
<li>现在能用 golang 写的, 绝不会用 ruby 了, 除非写网站. 写网站还是 ruby on rails 舒服啊.. 高效..</li>
</ol>
<h2 id="heading-5bya5yr5rwb56il5oc757ut">开发流程总结</h2>
<ol>
<li>最近要改掉一些毛病: 思考不够深入, “将就”,”可以” 的想法要戒掉.</li>
<li>开发某个功能前, 一定要画好流程图, 反复思考, 反复校验, 跟他人讨论, 一点思路直接开工会增加更多的工时, 浪费更多的脑细胞</li>
<li>流程图之后, 接口的设计, 命名要更清晰</li>
<li>功能要拆分, 越拆分越深入, 越能提前避免一些坑(但是坑总是避免不了的, 减少一点算一点咯</li>
</ol>
<h2 id="heading-5a2m5lmg">学习</h2>
<ol>
<li>下班时间多记一记单词(已坚持 2 个月); 多记一些英文语句;</li>
<li>&lt;算法 第四版=""&gt; 必须要啃完.</li>
<li>&lt;编程珠玑&gt; 要啃完.</li>
</ol>
<h2 id="heading-55sf5rs75lmg5oov">生活习惯</h2>
<ol>
<li>坚持 12 点左右就睡觉, 熬夜这真的是一个烂习惯,因为第二天的无精打采会让你浪费一整天: 降低决策能力/逻辑能力/意志力. 之前尝试每天睡 7 小时, 似乎对于我的这幅身躯而已是不够的, 所以要延长至少每天能睡 8 小时.</li>
<li>嗯, 最重要的, 之前 9 月份去体检, 结果不是太好, 所以锻炼身体这个事, 坦白说, 还没想好怎么安排; 深圳天气越来越糟, 户外的运动不太感冒. 比较感兴趣的是游泳, 羽毛球. 游泳一次 40, 羽毛球场地 80 一小时, 成本不低. 家里的公路自行车也一直荒废着, 坦白说, 附近没一个山可以爬一下, 平路骑行真的不费劲(达不到运动的效果, 可以考虑买一个骑行阻力架, 家里骑起来? 好像是不错的主意).</li>
</ol>
<p>运动规律暂行方案:</p>
<ol>
<li>每周去一次游泳?</li>
<li>骑行阻力架可以考虑买一个 达到每周运动 100 分钟(医生建议每周 150 分钟, 慢慢来).</li>
</ol>
<p>暂时这样吧.</p>
<p>睡觉了.</p>
]]></content:encoded></item><item><title><![CDATA[写了一个telegram翻译机器人]]></title><description><![CDATA[bing翻译机器人
最近用telegram, 发现bot这个玩意挺好玩的.
因为刚好觉得bing的翻译功能非常好用. 但是bing的翻译并没有接口,域名用读html的方式解析翻译结果.
做了一个telegram的机器人: @bingdict_bot
可以在telegram搜索体验一下~~~~
bing词典的解析是基于我之前写的另外一个开源项目https://github.com/molizz/bing-dict. 有需要的同学可以基于这个项目写api接口给第三方啥啥的.
保持telegram持续...]]></description><link>https://mozz.in/telegram</link><guid isPermaLink="true">https://mozz.in/telegram</guid><dc:creator><![CDATA[粒 莫]]></dc:creator><pubDate>Mon, 29 Oct 2018 19:14:19 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-bing">bing翻译机器人</h2>
<p>最近用telegram, 发现bot这个玩意挺好玩的.</p>
<p>因为刚好觉得bing的翻译功能非常好用. 但是bing的翻译并没有接口,域名用读html的方式解析翻译结果.</p>
<p>做了一个telegram的机器人: <a target="_blank" href="https://t.me/bingdict_bot">@bingdict_bot</a></p>
<p>可以在telegram搜索体验一下~~~~</p>
<p>bing词典的解析是基于我之前写的另外一个开源项目<a target="_blank" href="https://github.com/molizz/bing-dict">https://github.com/molizz/bing-dict</a>. 有需要的同学可以基于这个项目写api接口给第三方啥啥的.</p>
<h2 id="heading-telegram">保持telegram持续在线</h2>
<p>telegram是被qiang了的. 所以为了让telegram一直保持在线,可以使用第三方的代理机器人申请代理.</p>
<p>通过测试,发现一个提供这个代理的机器人<a target="_blank" href="https://t.me/getshadow_bot">@getshadow_bot</a>, 综合跟俄罗斯的几个代理机器人比起来, 速度快一些.</p>
<p>尤其是tg官方的MTProxy代理协议非常不错, 速度很快.</p>
<p>给tg加上代理后呢, 就不用设备一直翻墙了, 速度也还行.</p>
<h3 id="heading-5pya5zco">最后</h3>
<p>其实之所以写这篇文章，就是记流水账，然后体验一下 gitpages.io 这个网站，发现还是挺好用的，哈哈哈</p>
]]></content:encoded></item></channel></rss>