设计密码这个工作,对我来说,是一件非常痛苦的事情。 互联网时代我们往往有数百个各种平台的账号,而如果我们又要求他们的密码复杂且不能重复,那么我们就需要记住数百个不同的密码。
更何况,对于一个开发者,要管理的还不止是密码,还有 SSH Key、API Token、Software License 等等……
SSH Key 想要记忆是几乎不可能的,那我们还要为数十个服务器管理数十个 Key 文件……
API Token 也是如此,我们需要为每个项目管理一个 API Token,而这些 Token 又都是不同的,我们又该如何管理呢?
这些问题,都是我们在日常工作中会遇到的问题,而 1Password 便在我过去的一年中成为了我解决这些问题的利器。
1Password 是一款高效的密码管理软件,旨在帮助用户安全地存储和管理众多密码及敏感信息。它不仅覆盖了从传统的网站密码到SSH
Key、API Token、软件许可等复杂数据的管理,还提供了跨平台支持和强大的自定义功能。
1Password 支持各种操作系统和设备,包括 Windows、Mac、Linux、iOS 和 Android。它甚至在命令行界面(CLI)中也能发挥作用,提供了极大的灵活性和便捷性。
在 Mac 上,你可以使用 ⌘ + \ 快捷键打开 1Password 搜索框,然后输入你要搜索的条目的名称,即可快速打开条目。
1Password 不仅支持自定义密码强度和复杂性,还能生成词汇表密码。它还新近支持了 PassKey 管理,提升了账户安全^5。
此软件允许用户在不同的密码库中安全地共享和存储各类密码,包括信用卡、电子邮件账户、银行账户、护照和驾照等,极大地扩展了其适用范围。
对于开发者,它支持 SSH Key、API Token、Software License 等数据的存储,有助于提高开发者工作流的效率。
同时它还提供了很多 Integrations,如 Brex Card、AWS Account、Fastmail 等,可以帮助我们更方便地管理这些账号。
1Password 在跨平台使用和密码自定义方面远超 Chrome 密码管理器。它支持对子域名的独立密码管理,而 Chrome 密码管理器则不具备这项功能。
KeyChain 作为苹果产品,虽然具有一定的跨平台功能,但其在非苹果设备上的功能受限。与之相比,1Password 提供了更全面的管理功能和更好的用户体验。
1Password 具有多样的功能,但我们在日常使用中,主要会用到下述的几个。
1Password 作为一款密码管理软件,最主要的功能就是管理密码。我们可以在 1Password 中创建一个密码库,然后在其中创建一个条目,即可保存一个密码。
其中,我们可以生成不同强度的密码,也可以自定义密码的强度、复杂度和组成,还支持词汇表密码。
对于网站域名,也可以设置是否作用于其子域名。在 Mac 上还可以设置登录到应用程序。(Mac 使用 ⌘ + \ 快捷键打开 1Password 搜索框)
1Password 支持 TOTP 类型的 One-Time Password 和 PassKey,有助于提高账号的安全性。PassKey 是 1Password
近期推出的新功能,它具有更高的安全性和更好的用户体验^5。
1Password 除了可以管理密码,还可以管理 SSH Key。我们在 1Password 中创建一个一个 SSH Key 条目,即可保存一个 SSH Key。
1Password 会自动运行 SSH Agent,当系统使用 ssh
命令时,会自动从 1Password 中获取 SSH Key 并使用。
1Password 支持添加 Credit Card。我们可以在 1Password 中添加 Credit Card 条目,输入相关信息,即可保存一个 Credit Card。
当我们在网站上需要输入 Credit Card 信息时,可以使用 1Password 的 Credit Card 条目自动填充。
1Password 同样支持 Bank Account 信息,你可以添加你的 SWIFT Code、IBAN 等信息。
你可以在 1Password 添加一个 Personal Identity 条目,输入相关信息,即可保存你的个人身份信息。 此条目可以用于填充表单,也可以用于留存假身份信息。
你还可以添加 Driver License、Passport、SSN、Outdoor License 等信息。1Password 支持非常多的现实中各种证件的存储,这可以减小证件丢失或者忘记携带的影响。
有一点难过得是,1Password 不会把 Crypto Wallet 的密码作为输入密码时的条目,因此每次打开 Crypto Wallet 都需要手动打开
1Password 去赋值密码。 😢
在 1Password 的 Family 和 Team Plan 中,你可以将密码存放到共享库中,以此实现团队协同、家庭共享等功能。
在 Team Plan 中,你可以创建任意数量的密码库并指定可以查看的人员;但在 Family Plan 中,只有一个默认的共享库且包含了所有人。
为账号设置 One-Time Password 后,将此条目共享给家人,可以实现更高的安全性。
1Password CLI 是 1Password 的命令行工具,它可以在终端中使用 1Password 的功能。 1Password CLI 支持 Linux、Mac、Windows 等平台。
通过 1Password CLI,我们可以在脚本中使用 1Password 的功能,比如在脚本中获取一个密码,或者在脚本中创建一个密码。这对于开发者来说,是非常有用的。
1Password 作为一款全能的密码管理工具,为用户提供了一个安全、便捷的方式来处理日常生活和工作中的各种密码和敏感信息。其强大的功能和易用性使其成为数字生活中不可或缺的一部分。
我已经使用 1Password 一年多了,我认为它为我的工作和生活带来了极大的便利,成为了我工作流中不可或缺的一部分。
2021年,我做了许多我曾经从未做过甚至从未设想的东西,我变得成熟了许多,看到了许多,也学到了许多。
大约是去年暑假,我加入了圆盘团队,并随之发展。由于我父亲的支持,我拿到了不小份额的固定资金,并且也为圆云大量注资。2020年12月25日,智慧神为圆云办理了实体公司,即安徽圆云科技有限公司。
新公司成立自然是很费劲的,因此2021年1月我也忙于为圆云夯实基础。申请支付宝、申请微信服务号等。由于之前的服务器都是在我个人阿里云账号上,我就直接把我个人的阿里云账号转为了企业的阿里云账号并更改了账户名。
当时还是挺快乐的,圆云一群人一起奋斗,吸纳人员,努力建设。虽然之后的不愉快的事件把圆云整垮,但这段记忆还是值得回忆的,我也从此学到了很多。
1月5日 朋友圈
整个2021年上半年我朋友圈发的奇多无比,确实也是比较闲,而且不仅是我闲,好像所有人都闲。同学点赞基本都是十几个的,现在再看3个的都少吧。
当时的生物还是挺令人怀念的,混了一个学期的我完全废掉,对生物也提不起兴趣。再加上初一讲ATP的李老师,我处于一个完全懵逼的状态,成绩就一落千丈。
主要原因还是我沉迷技术和工作,没咋倒腾学习,也导致现在要拼命卷才能上得去排名。
1月31日 朋友圈
英雄联盟是疯狂打,一天几十把那种,现在我也不清楚自己啥水平,也没认真打排位,但估计不差吧
为了适应企业需求,学了html初步等,浅入PHP,看了很多CSS的书。虽然传统页面布局意义已经不大,也是当时不了解这方面,但确实为我奠下了Web开发的基础。
然后我就去系统性学习了HTTP/TCP通信原理等,学了WebSocket,但并无实操经验。
2月6日,我老娘的生日,我确诊了水痘……
圆云的云计算业务正式发布,开始初步进驻云计算市场,而这也是圆云后续崩溃的主要原因。
2月10日入手了Adobe Creative Cloud全系软件,但之后实际就用到了3、4个,2022年是不可能再买了。
过年那会发布了自己画的矢量图所制成的红包封面,收到了极大欢迎。
2月18日华为云经销商来谈合作,圆云拿到了代理资质,并卖出了几台代理服务器。
我初步了解了VPS市场的价位和规则等。
三月初开学,我不知为何突然对法律感兴趣起来了,买了刑法、民法典、公司法等一堆书籍,自学法律。
虽然学的不咋地也学的没屁用,但还是让我更了解我国法律机制了点。
当时二开圆盘所使用的的Cloudreve,自己靠百度乱查,但啥都没查到。
Cloudreve的Strict Mode使得百度大多针对jQuery等传统js的方法不起作用,最后还是在yui的帮助下添加了个tidio,没啥技术含量,但打开了我日后学习React的大门。
3月14日 朋友圈
2021年的四月似乎是很常规的,我开始关注米其林餐厅,正经搞信息学竞赛。
我和大白的关系就变得非常铁,28号过生日得到了他送的巨型Enter键(解压玩的)。
生物一模死的很惨,此后俩月我都在认真搞生物地理。
这是我4月唯一一个也是最惨痛的大事件了。
代价是三颗牙各磕掉一半,右手中指关节受伤,半边脸擦伤。
然后我不得不在家里休了那么几天,期间大白来看望了我一下。
而且,莫名其妙地,就全班都以为我住院了,然而我连口腔科门诊都挂不上号。
由于在家太无聊,加之收到缪森等人的推荐,我入了Phigros大坑。
我打游戏还是比较循序渐进的(大概?),从EZ开始,越往后心态越崩(
我开始自己写博客了,当时使用的域名是ahdark.rc0.co
,即圆云科技子域名。
写了点垃圾文章,现在基本都删了,朋友圈还能找到一点痕迹。
同时开启了学习运维&架构的历程
之前摔得牙用光固化和金属丝固定矫正来着,1个月后我就又回到北京大学口腔医院拆光固化。
这段期间还是比较不习惯的,这个比牙套还恶心人一点……
CT 结果
6月,我开始系统性学习WordPress。从插件开始,学习WordPress的Hook、Filter机制和框架结构。
虽然我完全不理解Admin后台怎么写设置页面,但大致明白了如何使用Hook、Filter做一些优化和功能。
当时我打屁股肉打的气急败坏,就自己用递归神经网络写了Phigros的AI,即自动打谱。
算法不完善,对判定线的识别不太精确,后边那些变态铺面基本就识别不到,也是搁置了。
雷蛇 毒蝰
继曼巴眼镜蛇以后,我又买了雷蛇的毒蝰鼠标(毒蝰终极版)
详见:http://cn.razerzone.com/gaming-mice/razer-viper-ultimate
我关注到了我的书架,然后表现出充分的绝望。
书架
6·18,给hxd大白买了战锤狂鲨耳机,特意挑了粉色的
AHdark Blog从原来的_Gohan N07.1主题更换为了MDx_
,主要原因是原主题做了全站Ajax后可扩展性太差,而且CSS给我搞的很懵逼。
换了MDx以后就开始折腾wp开发了
图片来自 flyhigher.top
之后也给MDx提了不少Pull Request。
从洛谷日报看到了底层优化相关文章,就去自己研究了CPU架构和指令集。
包括各种汇编语言转译的细节,都进行了相关研究。
也奠定了我后续开展全站优化服务的基础。
这是2021年最大的奇迹!
在近2个月的每周统练中,我的最高分保持在65分(满分70分)。
从来没有任何一次拿到65以上的成绩,总是因为各种原因丢个几分,挺惨的。
中考之前我也是忐忑不安,2个月GitHub几乎没有commit,印证了我的努力。
奇迹出现了,我取得了生物70分的好成绩,可惜的是地理只拿了67分……
考完小中考,虽然紧接着的是期末考试,但我几乎躺平。
练着熬夜看了几天番,肝完了《刀剑神域》全系番剧。
7月1日北京遭遇罕见强降雨并夹有冰雹。
当天我写了篇赞颂我书包质量的文章:
接下来的一整个月我都在执着于技术中
https://www.ahdark.blog/timeline
去补了个牙,树脂做的,在私人诊所,让之后要东西更方便些。
主要是上次填的临时材料掉了,然后就很麻烦……
极夜来北京玩,于是我们去面基了。
由于圆云的分崩离析,我备案了ahdark.com
域名并将博客迁移至此域名,同时重做了架构。
原来的存储桶使用rcstorage.cn
根域名,现在改成了cdn.ahdark.com
。
8月我搭建了最初的sdn.ahdark.com,用的也是经典架构,并写了文章。
此后我便在公共服务资源上一去不复返。
第一次给MDx提交代码,不白嫖主题!
我开启了一个新的个人大型项目:**Alpha Picture Storage**
这是一个公益图床项目,主要是用于我练习小文件分布式存储系统的部署。
快来试试吧!
你可以看到:繁星的圆梦驿站就是全站图片托管在AlphaPic,速度也是很快的!
从听到Kalafina的歌开始我果断入坑,空境动画版全部迅速肝完,爱上了型月社(主要是空境和Fate系列)
空之境界的视频拿到以后我就开始研究视频推流,最终选择了MineCloudVoD的插件,也认识了mine27大哥。真的是良心人啊!
老朋友空白有了正规公司资质和一套可靠的团队,于是我加入了。
现在的话,准备备案新域名,把Source Storage变成一套完整的服务。
真就是利他主义的实现
欢迎使用呢!
10月我变得比较暴躁,挂了几个人,并且熟悉了如何挂人。
这就一老贼,我现在也是秉承这个观点。
最新的瓜:https://hostloc.com/thread-948635-1-1.html
本来写了文章骂他,但后来实在扛不住他们的CC攻击,现在其实也扛得住了,但感觉没那必要了。
打了我几天,我也损失了几百块钱,但他们名誉也掉的差不多了。
期间姜一飞还用微信小程序起诉了我爹,说我侮辱诽谤…………挺无语的,也够傻逼的。
一个简单的部署蕴含着深厚的爱情(
三云99!
难以理解啊,我国教育事业还有待进步啊!
几个月我做了部分实践以学习MUI和ReactJS
比如给cdn.ahdark.com写的页面
还有静态模板
还有AlphaPic的节点站
还有致使我被爆锤的dbtakeshit.top
sdn.ahdark.com更换了K8S架构,稳定性提高了。
我也做好了一套Docker的阉割版本并开源了
我被批,我反省,我写教程(
深切感激lzh对我学习上的帮助,并祝愿魁国万古流芳(bushi)
当然,为了学习,我也减缓了技术研究和博文更新的进度,比如这篇文章就是拖更了3周。
一直肝学习,几乎啥都没搞
给Tkong Blog做了全站优化,很标准的动静分离架构,借给他我的子域名做国内加速来着。
清华附的大佬我还是比较欣赏的(
闲暇之时用Next.js写了个小项目
挺简陋的,但发现Next.js确实挺好用
实在不想写了,太多东西了,一一写也不现实,现在已经01:30了,我也要困死了。
祝大家新年快乐吧!
]]>谈到企业的互联网发展,尤其是对于主要业务为互联网相关业务的企业,我们通常都会在开发的过程中频繁提到“高可用”一词。
高可用性(英语:high availability,缩写为
HA),IT术语,指系统无中断地执行其功能的能力,代表系统的可用性程度。是进行系统设计时的准则之一。高可用性系统与构成该系统的各个组件相比可以更长时间运行。高可用性通常通过提高系统的容错能力来实现。定义一个系统怎样才算具有高可用性往往需要根据每一个案例的具体情况来具体分析。
引自 Wikipedia
高可用的主要目的是保障“业务的连续性”,即_在用户眼里业务永远正常对外提供服务_
。高可用只诞生于良好架构方案和高昂成本中,正是因此,对于一个架构师来说,学习设计高可用架构是必需的。
我们通常会将一个庞大的系统拆分为多层,即分层思想,将其拆分为应用层、中间件、数据存储层等独立的“层”,每一层再拆分为细致的组件。而后使每个组件都对外提供服务,使其不孤立存在,而后使得服务具有意义。
架构的高可用,自然需要保证架构中所有组件以及其对外暴露服务都要遵循“高可用”设计。任何一个组件没有做到“高可用”,都意味着系统可能存在宕机的风险。
任何组件要实现“高可用”,都离不开“冗余”和“自动故障转移”这两个核心要素。单点服务是高可用的大敌,所以一个合格的高可用组件应进行集群化(至少应存在于两台机器),这样服务不会因为某一个节点的瘫痪而不可用,会有其他节点立刻进行顶替。
假设任意一个节点的可用性为90%,那么两台机器的集群就是 $ 1 - (100\% - 90\%)^2 = 99\% $ 。由此可知,想要提高可用性,增加冗余的机器是一个简单实用的选择。
仅有“冗余”,并不完善。机器出现问题后的切换过程仍然费时费力,而且容易出错,所以我们还要借助工具实现“全自动的故障转移”,以此达到实现近实时的故障转移的目的,近实时的故障转移才是高可用的主要意义。
在业界通常用“9”的数量衡量一个系统的可用性:
可用级别 | 系统可用性 | 年平均宕机 | 月平均宕机 | 周平均宕机 | 日平均宕机 |
---|---|---|---|---|---|
不可用 | 90% | 876 小时 | 73 小时 | 16.8 小时 | 144 分钟 |
基本可用 | 99% | 87.6 小时 | 7.3 小时 | 1.68 小时 | 14.4 分钟 |
较高可用 | 99.9% | 8.76 小时 | 43.8 分钟 | 10.1 分钟 | 1.44 分钟 |
高可用 | 99.99% | 52.56 分钟 | 4.38 分钟 | 1.01 分钟 | 8.64 秒 |
实现 99% 可用性已经是很简单的事了,毕竟如今是云计算时代,而且如此宕机时间已经严重影响业务了,对于此类公司或许只得一死了当。
大型企业的主要业务通常都要求五个九以上,因为系统的一时故障往往会影响数以万计的人们。(当然要求归要求,实现不了也很正常,比如前段时间几个大厂云连着崩…)
目前大多数国内互联网企业都会采用微服务架构:
微服务架构
可以看到架构主要分以下几层
这两层的高可用都与 Keep Alive 息息相关,因此我们可以合在一起看。
接入层 & 负载均衡层
两个 LVS 以主备形式对外提供服务,此时仅master工作,backup在master宕机后会进行实时接管。在主备机器使用Keep
Alived软件,以此使得backup可以实时监测master的运行状态。
在master宕机后,弹性IP会迅速转移到backup,也就是我们常说的“IP漂移”,以此解决LVS的高可用。
Keep Alived 的 HeartBeat 检测通常通过 ICMP 或 TCP 端口扫描来检测,同样它也可以用来检测 Nginx 的端口,以此实现及时对不正常的
Nginx 实例进行剔除的操作。
在负载均衡之下,“网关层”、“站点层”、“基础服务层”共同构成了最关键的微服务架构组件部分。当然这些组件之间还需要通过RPC框架例如Dubbo、gRPC才能通信。
所以微服务要实现高可用,就意味着这些RPC框架也要提供支撑微服务高可用的能力,我们就以Dubbo为例学习它是如何实现高可用的:
Dubbo 架构
思路大致如下:
以此,实现了故障的自动转移。
不难看出,在此之中,Registry 就实现了类似 Keep Alived 的作用。
对于 Redis、ZooKeeper 等中间件服务,实现高可用也是较为重要的。
我们以 ZooKeeper 为例:
ZooKeeper 架构
我们可以从图中看出,ZooKeeper 的主要角色如下:
其中的主要问题也不难看出,Leader 只有一个,存在单点隐患。ZooKeeper 为了解决此问题,会使 Leader 和 Follower 用 HeartBeat
机制保持连接,在 Leader 出问题时由 Follower 投票选举替代的 Leader (ZooKeeper Atomic Broadcast,专为 ZooKeeper
设计的一种支持崩溃恢复的一致性协议)。
除去 ZooKeeper ,业界还有 Paxos、Raft 等协议算法,也可用于 Leader 选举。
Redis 具有两种部署模式:“主从模式” 和 “Cluster 分片模式”。Redis 的高可用需要根据其部署模式决定。
Redis 主从模式 架构
主从模式即一主多从(一个或者多个从节点)。其中主节点主要负责读写,而后将数据同步到多个从节点上。Client
也可以对多个从节点发起读请求,以此减轻主节点的压力。
但和 ZooKeeper 一样,由于只有一个主节点,存在单点隐患,所以必须引入第三方仲裁者的机制来判定主节点是否宕机以及在判定主节点宕机后快速选出某个从节点来充当主节点的角色。
这个第三方仲裁者在 Redis 中我们一般称其为“哨兵”(Sentinel),当然哨兵进程本身也有可能挂掉,所以为了安全起见,需要部署多个哨兵(即哨兵集群)。
Redis 主从模式+哨兵集群 架构
哨兵集群通过 Gossip(流言)协议来接收关于主服务器是否下线的信息,并在判定主节点宕机后使用 Raft 协议来选举出新的主节点。
主从模式看似完美,但存在以下几个问题
为了解决以上问题,Cluster 分片集群就应运而生。
将数据分片,每一个分片数据由相应的主节点负责读写,这样就有多个主节点来分担写的压力,且每个节点只存储部分数据,也就解决了单机存储瓶颈的问题。
但需要注意的是,每个主节点都存在单点问题,所以需要针对每个主节点做高可用。
Redis Cluster 分片集群架构
在 Proxy 收到 Client 执行的 Redis 的读写命令后,首先会对 Key 进行计算得出一个值,如果这个值落在相应 Master
负责的数值范围(一般将每个数字称为槽,Redis 一共有 16384 个槽)之内,那就把这条 Redis 命令发给对应的 Master 去执行。
可以看到每个 Master 节点只负责处理一部分的 Redis 数据,同时为了避免每个 Master 的单点问题,也为其配备了多个从节点以组成集群,当主节点宕机时,集群会通过
Raft 算法来从从节点中选举出一个主节点。
当然,成本也是爆炸的,以至于我至今为止都没操作过Cluster 分片集群的部署。
MQ,即 Message Queue 消息队列。
MQ的高可用通常也是利用数据分片来提升高可用和水平扩展能力。
Kafka 高可用集群架构
可以看到每个 Topic 的 Partition 都分布式存储在其它消息服务器上,这样一旦某个 Partition 不可用,可以从 follower 中选举出
leader 继续服务。
不过不同于ES、Redis Cluster 的是,Follower Partition 属于冷备,也就是说在正常情况下不会对外服务,只有在 Leader 挂掉之后从
Follower 中选举出 Leader 后它才能对外提供服务。
存储层通常为数据库。数据库,也是一套系统能够持久运行的基本条件,这里我们以 MySQL 为例来简单地讨论一下其高可用设计。
参照以上的高可用设计,你会发现 MySQL 的高可用思想与上述其他架构都是类似的,它也是分主从和分片(即我们常说的分库分表)两种架构。
主从架构与 LVS 类似,一般使用 Keep Alived 的形式来实现高可用:
MySQL 主从架构
如果 Master 宕机了,Keepalived 也会及时发现,于是从库会升级主库,并且弹性IP也会“漂移”到原从库上生效,所以说大家在工程配置的
MySQL 地址一般都会使用弹性IP或私有DNS解析域以保证高可用。
在数据量达到一定水平后,分库分表也是必要的操作。就像 Redis 的分片集群一样,需要针对每个主配备多个从。
MySQL 分片集群架构
观察以上架构中的组件,你会发现,高可用架构会随着数据量的提升而变得更加精密复杂,就比如从一主多从集群到多主从集群。但在这其中,数据之间的同步更是一大难题。所以多数组件依然采用一主的形式,然后再在主和多从之间同步。
但需要注意的是,做好每个组件的高可用之后,整个架构并不一定就真的可以做到完全高可用。在生产上还有很多突发情况会让我们的系统面临挑战,例如瞬时流量(例如抢购环节等)、恶意攻击(DDOS
等)、代码内存泄露(导致程序不响应)、部署到生产环境时出错(Facebook 宕机便是由此)、机房断电(可依靠异地容灾)等。
所以在做好架构的高可用的同时,我们还需要在做好系统隔离、限流、熔断、风控、降级,对关键操作限制操作人权限等措施以保证系统的可用。
]]>原文链接:https://blankly.finance/vscode-vs-jetbrains/
作者介绍:Jeremy Liu 是一名全栈工程师,目前就职于 Blankly,担任首席工程师。
译文链接:https://www.infoq.cn/article/2JyfGdBFKUSP9j9SjPCu
本文最初发布于 Blankly 上,经原作者授权由 InfoQ 中文站翻译并分享。
在编程中,VS Code 作为我的主 IDE 长达 5 年之久。在这个时间点上我决定换掉它,这可能会令人无法理解。本文我将和大家分享我做这个决定的原因。
愿意的话你也可以说我是疯子。你可能会认为,一个用了 VS Code 长达 5 年的人,一定是疯了才会想在此时换掉它。的确,在我接触
JetBrains 生态之前,也是这么认为的。我甚至愿用我的性命证明 VS Code 是目前市场上最好的 IDE,它就如同 PC 行业中的苹果 M1
芯片电脑一样。但请允许我先介绍下事情的背景。
我目前在 Blankly 工作,该公司主提供对冲基金云服务。在我们提供的云服务上,人们只需要几分钟即可创建自己的交易算法。我的一个同事
Emerson,他是 JetBrains 生态的铁杆粉丝。在一次日站会上,他为了说服我们去试一下 JetBrains
的生态,甚至不惜延长了会议时间。我为了让会议对于这件事的讨论早点结束(这样站立会也能早点结束),我勉强同意了。然而谁承想,现在我居然在这里写了一篇关于是什么最终说服我愿意放弃一直陪伴我的
IDE 的文章。因此,如果你正好处在纠结选择用什么 IDE 且完全没有考虑 JetBrains 想法,或你对我为什么放弃 VS Code
感兴趣的话,那么,这篇文章非常适合你继续读完。
本文是根据我使用 VS Code 和 JetBrains 的一些切身体会,将从 5 个方面对它们进行的对比分析。并且阐述了一些使用场景中
JetBrains 优势明显的原因。
首先,任何编程语言在 VS Code 中都可以简单且快速地启动和运行,所以大家也会称它为“编辑器”。因此,VS Code
对于像我这样的全栈工程师来说是最佳选择。无论你是需要频繁在 Python 和 JavaScript 之间切换,还是需要增加一个基于 NextJS 开发的
React App,还是需要在 Ralis 系统上配置 Ruby 环境,这些能力 VS Code 都能很好地支持,并为这些开发语言提供了包括 lingting
在内的一系列开箱即用的功能。即使碰到某个功能没有,那也只需要在其插件市场上搜索一个,找一个具备此功能的插件进行安装即可。
其次,由于有了诸如 Github Copilot,AI-based linting,auto imports 等一系列插件的支撑,VS Code 具备了强大的 linting 能力。在
VS Code 中,无论你什么时候想要什么功能,配置起来都非常容易。很多时候,只是需要敲个结束符,VS Code
就会将你想要的内容提示出来。不过有些时候,人们也会因为这种 linting 能力的失效而崩溃。实际上,我时常陷入试图弄清楚为什么一个标准的
linting 不能工作的困境中。不管是由于我使用 Anaconda 安装的多 python 环境导致,还是由于少了安装包导致,但很多时候我都无法直接得到答案。此外,VS
Code 针对 JavaScript 语言的 linting 能力也非常强大,不过它不会对 JavaScript 进行深入的类型检查,庆幸的是,我们可以通过
TypeScript 来解决这个问题。
如图所示,由于我忘记切换 VS Code 中的 Python 环境,所以即使我本地已经通过 pip 安装了相关依赖包,但 VS Code 的 linting
功能依然提示包未找到。
最后,作为一个编辑器,VS Code
在代码重构上表现的确非常出色。它在诸如变量重命名、文件移动和引用自动修改等基础的重构功能上表现得非常棒。但在诸如函数移动、参数重命名、代码抽取等更高级别的重构功能方面,它就显得有些能力不够了。不过,幸运的是,仅仅一些基础的重构功能就足以满足我们日常大部分重构需求。在我使用
VS Code 的五年中,它满足了我遇到的大多数重构场景。
首先,JetBrains 是一个包含了很多不合理初始设置的强大 IDE。在我第一次接触它的时候,为了让代码显示的比较优雅,不得不在设置上大费周章。不过,在两个为不同使用场景设计的
IDE 之间做切换,付出一些学习的时间成本是不可避免的。如果我的一个 POST 请求突然出问题了,我就得打开 PyCharm,看看是不是我后端
API 服务出问题 了;如果在推荐类项目中,我突然对最佳推荐算法有了新的优化思路,我就需要打开 CLion。不过,由于有了智能识别,在打开不同
IDE 的时候,我只需要花点时间练习下将 code . 切换到诸如 webstorm . 和 pycharm. 等其他脚本。
其次,JetBrains 的引擎性能强大。当我将 IDE 都替换为 JetBrains 之后,它强大的引擎性能让我印象深刻。当我在编辑器中看到一些红线警告的时候,我只需要使用快捷键
comman+p 将当前窗口重新加载一次,这些红线警告就会消失,或者会给出一些有用的提示信息。这种简单和快速响应的代码检查,让我在编程时心情愉快。
如上图,只需要一个快捷键,就能看到所有引用的地方。
最后,在重构能力上,JetBrains 功能强大,这也是它真正吸引我的地方。就在上周,在为公司平台构建最后的内测版本期间,为了让组件未来具备更强的扩展性,我重构和新增了一些组件。期间,我大概移动了
200 个组件,在项目编译的时候,没有一次编译异常是由引用错误、非法或未定义组件引起的。然后,在 VS Code
中,我在一个数据结构类的项目中,仅仅重新组织了两个文件就破坏了整个 cpp 代码。为此,我不得不手动修正一些组件导入和函数引用才能使项目正常运行。另外,JetBrains
为了确保我们能有足够多的重构工具,它还提供了诸如安全删除、全局重命名等多种外部工具。
通过 JetBrains 可以很清楚的看到将被重构或重命名的变量的的全部调用以及上下文情况图
JetBrains 生态 IDE 提供的阅读帮助功能
总的来说,我认为在代码检查和代码重构上,VS Code 和 JetBrains
两者能力接近。两者都是通过诸如自动代码检查、代码格式化、主题定制等功能,帮助人们更好地进行代码调试和显示。不过,JetBrains
具备优秀的 linting 引擎和无副作用的重构能力,因此,如果代码分解和重构对你和你的工作流程很重要,那么,我推荐你选择 JetBrains。
VS Code 超强的调试能力,归功于其强大的插件支撑。你每次点击 VS Code 左边的运行按钮,VS Code 都会生成一个.vscode
的文件夹,此文件中存放了一个 settings.json 文件,这个文件包含了调试相关的全部配置。对于诸如 Python、JavaScript 等大多数语言来说,使用
VS Code 作为其调试工具是非常方便的。甚至,如果你的环境配置正确无误的话,通过直接点击调试按钮来进行调试会更加便捷。此外,即使是通过修改
settings.json 文件中的配置来改变你当前的调试内容也是非常简单的。不过,如果你用了特定的构建方式或特定平台语言(如:C/C++
语言),由于需要设置 gcc 和 clang,因而会大幅增加在 VS Code 中进行调试的难度和复杂度,同时设置这类文件的调试配置也会比较费时费力。为了减少这种时间的投入,我尝试将其他项目的
setting.json 文件拷贝到当前项目中,但是效果不理想,我花了很多天的调整,才使当前的项目正常运行。在我的大学(密歇根大学安娜堡分校),为了减少大家在调试配置上耗费的精力,他们就维护了一个通用的
settings.json 文件提供给所有人使用。但是即使这样,人们还是不得不花时间去调整 settings.json 文件。
上图显示了一个为了在 MacOS 上进行 C/C++ 程序调试所需要的最简配置
在实际进行调试的过程中,VS Code 在调试控制台中可以很好地进行调试断点设置、识别变量和添加变量观察者。不过,如果这些功能可以直接在代码面板而不是侧面板上进行设置,那就好更好了。
庆幸的是,插件和多语言支持是 VS Code 的最大优势,这使得人们可以在几分钟,甚至几秒钟内就完成代码调试的设置工作。对一些简单的调试场景,VS
Code 的调试能力表现得非常棒。然而当需要调试特殊语言的时候,VS Code 的调试能力往往会难以胜任。同时,我还发现当程序需要用到更大的堆内存的时候,VS
Code 的调试器会一直卡到崩溃。
相对于 VS Code,JetBrains 在调试方面功能更强。由于 JetBrains 所有系列的 IDE
都是基于配置运行的,因此你可以通过点击调试按钮开始任何一次程序调试。如果想设置全局的调试断点,只需要在编辑器的行号处按下空格键即可,此功能极大得提高了程序调试的体验。此外,JetBrains
系列的 IDE 在整个调试过程中还有很多其他的功能亮点,例如:当进入调试环节,作用域内的所有变量的定义,对于定义者来说都是可见的。这让我们可以很方面的观察当前变量值的变化情况。几天前用
Pycharm 调试程序的过程令我印象深刻。当我在 Pycharm 中运行调试并试图查看数据帧的值时,只要点击数据帧变量并按下 view
作为数据帧,Pycharm 就会在 SciView 中打开数据帧,并显示所有数据帧值和列标题:
上图显示的是运行调试且变量值变化的监控
如上面截图所示,底部的窗口中显示了作用域内的全部值。history_and_returns 的下拉菜单中显示了字典对象的所有属性值以及嵌套在该字典对象中的数据帧。右边的面板中,则和
SciView
一样,显示了已经嵌套在字典中的数据帧。在不设置任何打印语句或堆栈跟踪的情况下,就能如此深入了解代码,对于开发人员来说是非常有用的。试想一下,当所有变量的赋值都被编辑器显示在其旁边时,我们可以很容易找到循环中的逻辑错误、修复因为索引导致的故障甚至做一些更加深入的逻辑推理。
与其他 IDE 的调试器一样,JetBrains 调试器同样提供了诸如下一行、进入某个函数等步进的调试功能。另外,JetBrains 的 Run to
Cursor 是一个非常好用的功能,它允许人们通过放置鼠标,就可以如同设置断点一样,起到调试断点的效果。这种可以随时随地设置断点且立即生效的功能,完全我调试代码的方式并且大幅加速了我编程的速度。
程序调试是开发人员每天最常做的事情之一。因此我认为,当开发人员选择 IDE 的时候,IDE 是否拥有一个好的调试器是必须考虑的因素。VS
Code 和 JetBrains 都提供了非常可靠的调试器,但是我必须说在这方面 JetBrains 比 VS Code 略胜一筹。因为,JetBrains
可以直接在变量声明的边上直接显示变量值,这使得跟踪大量变量的时候会比较容易管理。此外,JetBrains 的调试器更强大、更稳定,它不像
VS Code 调试器那样需要做复杂的设置。因此,结合这些因素,JetBrains 的调试能帮助我们节约更多的调试时间,这也使得 JetBrains
更具吸引力。
需要团队协作或在乎代码安全的人都知道 Git 在他们工作流中的重要性。对于任何现代编辑器来说,基于 Git 的版本控制都是不可或缺的功能。VS
Code 和 Git 的集成做的非常好,当你打开一个工作目录的时候,它会自动检测这是否为一个 Git 仓库。如果是,那么它就会立即提供诸如
push、pull、commit 等许多固有的 Git 命令。
在 VS Code 的 Git 面板中,人们可以清楚的看到哪些些文件做了修改,且轻松完成同步。同时,在面板中,也可以创建分支、克隆仓库。VS
Code
总能清楚的告诉你该怎么做,这也是我喜欢它的一个原因。当它检测到了文件修改,就会立即提示你提交,并且在提交的时候会提示你需呀附带上提交说明。此外,在提交的时候,它还会对本地分支和远程分支进行检测和同步。与此同时,它还提供了非常稳定的变基功能。
在行内可以清楚的看到哪里需要做冲突合并
合理处理冲突合并的能力,是 VS Code 的一大优势。借助 VS Code
自动提供的冲突解决方案,我可以通过点击按钮来选择使用当前更改还是选择使用传入的更改。这种解决合并冲突的方式,为我节约了很多时间。
在全面切换到 JetBrains 之后,我几乎没有碰过我的终端命令行。JetBrains 提供了包括提交、冲突解决、分支切换和分支对比等在内的源码管理等整体功能。从我的体验来看,JetBrains
在源代码控制上比 VS Code 的要好得多。下面我罗列一些使用体验的截图:
在两个分支之间对比某个文件
内置的分支详情展示
详细的 git 日志
在 Git 集成上,JetBrains 和 VS Code 都提供了完整且相同的功能。无论你选择哪款 IDE,在源码管理上都有足够的功能支撑。因此这方面不能作为选择
IDE 的考虑因素,只是个人喜好不同而已。例如,在解决合并冲突的时候,相对于 VS Code 将冲突文件堆在一个文件中显示的方式,我更喜欢
JetBrains 将冲突文件分开显示的方式。
VS Code 是最具扩展性的编辑器之一,而且集成能力和可扩展性是它的核心功能。在众多扩展能力中,Python
扩展、远程开发扩展以及一些智能感知驱动的扩展是目前最热门的。此外,VS Code 也有一些很酷的功能,例如通过 Prettier
进行代码格式化,通过图标和代码编辑器主题进行主题定制等。VS Code 提供的每个事项或功能特性都是完全可扩展的,同时扩展的本身也可能是增强扩展能力的过程。
对远程 docker 容器的支持,是我最喜欢的一个 VS Code 扩展能力。通过此功能,用户可以在 VS Code 中在 docker
容器内部进行远程编程。如果你本地或远程环境安装了 docker,那么在 VS Code 中你就可以轻松的运行你的代码以及完成所有之前需要在
docker 中才能完成的事情。想要一些更有趣的东西?通过 SSH 进行远程开发怎么样?微软开发的扩展插件就允许人们在 VS Code 中通过远程
SSH 进入到服务端开发环境,如同本地一样进行远程开发。在 VS Code 中想要集成这些功能,只需要简单点击安装一下,就可以成功运行,所有的这些功能,成就了
VS Code 的伟大。
对于 JetBrains 来说,可扩展性并不是它需要突出的一个点,因为你会发现绝大部分你需要的功能都会随着的 IDE
版本的发布而发布。为某种语言安装一款强大的 IDE 的好处是,当我们需要某些新功能的时候可能只需要升级下 IDE
版本就拥有了,而无需去扩展市场进行寻找。
例如,JetBrains 针对 docker 提供了强大内置支撑。仅通过指定一个诸如 Dockerfile 的配置类型文件,所有的 JetBrains 的 IDE
都会通过一个易用的 GUI 提供对所有参数、名称、标签、端口以及环境变量的完整控制。在运行的时候,IDE 通过集成 docker,为你提供
docker 的构建日志、运行日志、环境变量以及可视化的集成配置信息:
在集成 FastAPI、Flask、shell 等第三方能力上,JetBrains 提供了和集成 docker 一样的能力。
此外,JetBrains IDE 也有一个丰富的插件生态系统。例如,我可以为支持 Verilog 和 Matlab 分别安装特定的插件。不过有趣的是,这些轻量级的插件,居然比本地安装的
Matlab 和 Quartus(Verilog 的开发环境)环境提供了更好的编程体验。
毫无疑问,两者在扩展或插件上都有广泛的社区和市场的支撑。两款 IDE 在功能上各有千秋。两款编辑器之间互缺的功能,你可能希望他们各自丰富起来。不过,VS
Code 的社区稍微大些,因而拥有更多的扩展和一些诸如远程容器扩展之类的能力,这样使我们迭代的速度更快。因此,如果你日常工作中对诸如
Docker 的定制扩展有比较多的需求,那么 VS Code 可以说是你的专属 IDE 了。
虽然 VS Code 自身没有内置的实时共享功能,但微软为其开发了一个具备此功能的插件。除此之外,现在,人们甚至可以直接通过使用浏览器访问
vscode.dev 进行实时共享。这种需求实现的多样性,正是 VS Code
如此受欢迎的原因。只要你有良好的网络环境,实时共享的体验就会很好。在实时共享的过程中,人们可以如同面对面一样的进行结伴协同工作。同时,在源码控制上,VS
Code 还会时时追踪那些帮助作者提交代码的人。这些让我们看到了在 VS Code 中开启实时共享功能是如此的简单。因此,在我看来,VS
Code 在实时共享功能上比市面上任何其他的 IDE 和编辑器都要优秀。
不过在使用 VS Code 的实时共享功能,还是有些需要注意的地方。下面我举一个在 Vue.js 项目中使用实时共享功能的例子。在实时共享
Vue 代码时,包括 Vetur(Vetur 是 Vue 可视化的重要插件)在内的部分插件是不会被共享的。这种缺陷,时常会令人们陷入困境和烦躁中。不过还好,这样的缺陷,只会影响到某些特定的用户(如本例中,就只会影响
Vue 的用户)。另外,最令我厌恨的是,在实时共享中,撤销功能居然是绑定到了机器上而不是当前用户上,这导致我的撤销功能会在本地和远程之间发生混乱。
所有 JetBrains 生态的 IDE 在代码共享和在线协同的功能上,都提供了非常多的设置项。这些设置项根据不同的安全等级而有所不同。我最近发现一个令人印象深刻的能力是,通过
projector(投影)技术,可以在 docker 容器中运行任何 JetBrains 的 IDE,这使得我可以连接到一个基于云服务运行的 JetBrains 的
IDE 上,同时在浏览器中使用完整的 JetBrains 的 IDE 的功能进行编码。因此现在,我可以仅凭一个密码,通过使用一个 headless
的服务,就可以随时随地的安全的进行编码。这还只是 JetBrains 众多共享配置中的一个。
在所有的 JetBrains 的 IDE 中,通过 Code With Me 进行实时共享是主流方式。这种方式使得你可以在本地 IDE
中直接查看其他人的项目。与此同时,你还可以如同使用本地开发环境一样,使用其他人的开发环境运行项目。一个印象深刻的场景是,我的一个团队成员,遇到了一个
python 的问题,他通过 Code With Me 向我发起了一个代码实时共享,我通过此共享,在我自己的 IDE
中,如同本地一样的使用他的配置,经过代码的调试,我很轻松的帮助他解决了这个问题。
各种不同优秀的共享 IDE 的方案,在尝试提高安全、协作能力或分布式团队如何协同工作上的表现是令人惊讶的。
如果是在两年前,我可能会认为实时共享功能无足轻重。事实上,两年前我甚至都不知道 IDE 中有代码协同的功能。因为在两年前,当我们需要协同工作的时候,根本不会通过
IDE 发起远程协作,而是直接坐到同一台机器前。但是现在受到新冠疫情的影响,这种面对面的协同工作已经是种奢望且变得极为困难。正因如此,两款
IDE 在实时代码共享上都做了强力的支撑。但是,由于 VS Code 中撤销功能的问题,因此我极力推荐 JetBrains。而且,视频和音频通话的支持和用户间
Git 的追踪能力都是同样重要。
除了上面列出的 5 方面对比之外,我也知道,相对于 VS Code 的完全免费,JetBrains 对于非学生的用户的需要收取一定的费用,这或许也是导致很多人不考虑
JetBrains 的原因之一。但是,对我而言,在使用 JetBrains 生态的几个月的时间里,它给我带来了非常不错的体验。而且,我已经迫不及待的希望在工作中更多的去使用它们了。因此,我希望即使
JetBrains 需要花费一些费用,你也可以考虑一下它。
这篇将讲述不同方法部署 Cloudreve 的详细操作步骤和具体优劣分析以及个人实测。
之所以决定撰写这篇文章,是因为我在 Cloudreve Forum 的评论收到了批评
https://forum.cloudreve.org/d/1829/6
尽管我仍然认为论坛文章内的教程并不高效,但我深刻地自我反省。在没有做出实际建设的情况下对别人的热心予以批判是不那么正当的。因此,我决定自己撰写一篇Cloudreve安装教程。
本文所使用的服务器为AHdark在本地环境部署的VMWare虚拟机,操作系统为CentOS 8.5,所使用的Cloudreve程序为GitHub开源版本。
本教程更适合国内云服务器,因为教程会包含大量对国内服务器所处网络环境进行优化的步骤
目前已有的yum镜像源很多,本篇文章采用阿里云yum源为例子。其他的yum源仅更换相应地址即可。
如果你使用的是国内国外大厂的云服务器,请跳过此步。我相信他们一定会预配置在系统的。
此段内容来自文章:https://developer.aliyun.com/mirror/centos/
1 | # 备份 |
通常,预置的服务器镜像的依赖软件都较为老旧,为此我们要对其更新
1 | yum -y update |
通常第一次依赖更新所需的时间较长,请耐心等待
系统预置的SSH通常没有较为完善的安全配置,因此我们容易被扫描到并被攻击。为此我们需要做些事以使我们的服务器不受侵害
使用密码登录较容易被暴力破解,因此我推荐你使用密钥登录
而后,我们还需要做一些基本的配置
1 | # 备份 |
进行以下配置
1 | # 去除注释 |
然后重启sshd服务
重启sshd服务之前请务必确保有一个已连接的SSH以防止服务器失联!
1 | systemctl restart sshd |
然后重新连接,查看是否成功,不成功则恢复备份
通常个人私有云可以使用以下方式部署
首先我们需要获取cloudreve源程序文件。你可以前往GitHub下载后自行上传至服务器或使用wget获取
1 | yum -y install wget |
1 | cd /cloudreve |
1 | chmod +x cloudreve |
请务必记住Cloudreve初次运行生成的账号密码!
记住后,按 Ctrl+C 退出程序
1 | vi conf.ini |
你可以前往 Cloudreve官方文档 获取全部有关Cloudreve配置文件的信息,或使用本文提供的简易配置
1 | [System] |
其中 SessionSecret
和 HashIDSalt
字段会自动生成,无需更改。
如果你要启用 SSL 的话,请在conf.ini
的后边添加以下内容
1 | [SSL] |
请务必将 CertPath
和 KeyPath
修改为自己的SSL证书、私钥的位置。SSL证书签发可前往 freessl.cn
,不过我更推荐购买更加正规安全的SSL证书
你可以查看这篇文章获取更多有关CentOS Firewalld的信息
https://www.ahdark.blog/som/552.shtml
在此环境,你只需要输入以下命令
1 | # 开放端口 |
由于使用GIN,Cloudreve的正常访问必须保证程序的运行
我个人推荐使用Systemd进行进程守护
1 | # 编辑配置文件 |
编辑内容如下
1 | [Unit] |
1 | # 更新配置 |
随后你就可以通过以下方式启动、重启、关闭Cloudreve服务
1 | # 启动服务 |
前往DNS,将对应域名解析至你的ip,即可正常访问服务
当然你也可以选择纯ip,但我并不推荐
搭配CDN一定是更好的选择,但你需要进一步了解CDN配置,而且这一方法并不适合搭配CDN使用
虚拟机部署成功
此示例使用 Oneinstack 脚本,我也同样鼓励你使用LNMP等脚本。
我更建议你将MySQL、Redis从Cloudreve服务器上分离以实现更高的负载能力和稳定性。
请自行参照 Oneinstack安装方法 进行安装
MySQL版本需要选择5.7及以上,推荐编译安装(消耗时间较长),请务必选择安装 Redis Server
请前往 https://oneinstack.com/install/ 获取详细信息
此图并非Cloudreve配置图,截取自Oneinstack官网
取自 https://oneinstack.com/install/
请不要开启防盗链、伪静态!
请记得将SSL证书换为自己申请的合规的证书!
进入vhost目录下,在本文示例为 /data/wwwroot/172.16.89.213
cd /data/wwwroot/172.16.89.213
通过wget下载cloudreve(同上文)
1 | # 下载 |
在上文安装MySQL的时候Oneinstack会让你自己输入mysql密码,现在你要通过密码登录
例如:我的MySQL Root账户密码为123456
1 | mysql -uroot -p123456 |
而后新建数据库和账户,请自行替换密码
1 | CREATE DATABASE Cloudreve; |
1 | chmod 777 cloudreve |
此时Cloudreve程序会进行初始化并生成初始账密,但你并不需要记住它
随后编辑conf.ini
1 | vi conf.ini |
1 | [System] |
由于Redis Server已包含在安装过程中,且默认无密码,直接使用即可。需要注意的是不要对外网开放Redis即可
重新运行程序进行数据表生成
1 | ./cloudreve |
此时请记住管理员账密,但不急着登录
由于使用GIN,Cloudreve的正常访问必须保证程序的运行
我个人推荐使用Systemd进行进程守护
1 | # 编辑配置文件 |
编辑内容如下
1 | [Unit] |
随后更新配置
1 | # 更新配置 |
随后你就可以通过以下方式启动、重启、关闭Cloudreve服务
1 | # 启动服务 |
首先编辑Nginx vhost对应配置文件
1 | vi /usr/local/nginx/conf/vhost/127.0.0.1.conf |
1 | upstream cloudreve { |
从理论上是可以实现多个 Cloudreve 后端负载均衡的,只需在
upstream {}
中加入更多 server 即可,前提是 server 及对应端口必须可用且所有
Cloudreve 必须共享 Redis 和数据库。
随后重载Nginx
1 | systemctl reload nginx |
而后确认Cloudreve正常运行
Cloudreve 状态
Pro需要对.sock文件进行权限修正
1 | chmod 777 /run/cloudreve.sock |
而后按指定域名访问即可进入Cloudreve
访问成功
安装宝塔,可参照 https://www.bt.cn/bbs/thread-19376-1-1.html
前往【软件商店】下载安装以下软件
请务必选择 utf8mb4
请不要使用PHP
前往 github.com/cloudreve/Cloudreve/releases 下载 amd_64
安装包并上传至服务器目录下
在Cloudreve目录下打开【终端】
1 | chmod 777 cloudreve |
此时Cloudreve程序会进行初始化并生成初始账密,但你并不需要记住它
随后编辑conf.ini
1 | [System] |
由于Redis Server已包含在安装过程中,且默认无密码,直接使用即可。需要注意的是不要对外网开放Redis即可
请记得将数据库信息改为正确信息
重新运行程序进行数据表生成
1 | ./cloudreve |
此时请记住管理员账密,但不急着登录
如图填写即可
有日志即为正常
添加反向代理配置
如图,即可
在 Cloudreve 3.5 发布后,Aaron
在官方文档页面更新了 Docker 部署教程,同时在Docker
Hub发布了Cloudreve Image,部署变得简单了许多,因此我在 2021.04.27 完善了此方法。
2022.10.02 补充:为方便容器部署,我向 Cloudreve 提交了 通过环境变量配置应用程序 的 Pull
Request (cloudreve/Cloudreve#1475)。待其审阅完毕合并主分支后,我将撰写详细的应用方法。
请确保运行之前:
conf.ini
空文件或者符合 Cloudreve 配置文件规范的 conf.ini
, 并将 <path_to_your_config>
替换为该路径cloudreve.db
空文件, 并将 <path_to_your_db>
替换为该路径uploads
文件夹, 并将 <path_to_your_uploads>
替换为该路径avatar
文件夹,并将 <path_to_your_avatar>
替换为该路径或者,直接使用以下命令创建:
1 | mkdir -vp cloudreve/{uploads,avatar} \ |
1 | docker run -d \ |
1 | mkdir -vp /cloudreve/{uploads,avatar} \ |
请注意修改 yml 文件中的 <your_aria_rpc_token>
1 | vi docker-compose.yml |
1 | version: "3.8" |
1 | docker-compose up -d |
在购买云服务器的时候,“数据盘”这个词汇对我们来说通常并不陌生。
来自 云骏数据
作为一个少有的对我们这些穷人来说看得见摸得着的选项,数据盘是一个值得我们去了解、学习、使用的东西。将系统盘与数据盘分离,也是很多云计算解决方案都在做的事。
正如其意,“数据盘”即为在服务器上主要负责存储数据信息的磁盘。通常在使用云计算服务时,我们都会将系统盘和数据盘分离,以此使得我们在更换系统环境、迁移数据、更换服务器型号的时候更为方便。同时,将系统应用与软件、数据分离,也可以使得系统不会因为流量高峰占用磁盘带宽时出现卡死等问题。
我司 云骏数据 便在多个服务器产品提供了数据盘选项。
系统盘通常预装系统,无需用户二次配置。数据盘由于其可迁移等特性,云服务商几乎不会提前为其预制相关配置。也就是说,*
*在购买带有数据盘的云服务器后,我们需要自行将其挂载到系统并进行相关配置才可使用**。
首先我们要查看当前的挂在情况已确认我们是否需要进行数据盘挂载操作。
1 | # 查看文件系统挂载情况 |
如上,/dev/vda
是我的系统盘,而 /dev/vdb
是我的数据盘。我的数据盘并未出现在 df -h
命令的返回结果中,因此我可以判定其并未被挂载。
在挂载磁盘之前,我们需要为磁盘进行分区操作。
1 | fdisk /dev/vdb |
输入后我们会进入一段 fdisk 交互:
/dev/vdb
)出现分区信息即为成功。交互过程
通常情况下,Linux 系统中我们常用的文件系统类型大致如下:
NFS[ref]参考资料:https://en.wikipedia.org/wiki/Network_File_System
[/ref]:网络文件系统,是一种分布式文件系统,多适用于内网连接的云硬盘和存储阵列等。
在示例中,我们使用XFS文件系统(示例机器为物理机,不使用存储阵列存储,不具备迁移条件)。
1 | mkfs.xfs -f /dev/vdb # 对于 XFS 文件系统 |
配置文件系统
在示例中,我们要将 /dev/vdb
磁盘挂载到 /mnt
目录中。
注意,被挂载的目录中,所有内容都会被覆盖!且在服务器重启后需要重新挂载。
1 | sudo rm -rf /mnt |
挂载磁盘
此时 df -h
的结果显示,磁盘已经被成功挂载。
通过修改 /etc/fstab
,我们可以实现在服务器重启后仍可直接使用的永久挂载。
1 | # |
我们只需依照格式添加即可:<磁盘> <挂载点> <文件系统类型> defaults 0 0
由于数据盘的特性,其整体迁移是较为方便的。
例如,我们要将原有磁盘 /dev/vda
的目录 /data
迁移到磁盘 /dev/vdb
:
/dev/vdb
挂载到 /mnt
目录下。/data
目录下所有正在运行的服务关闭。/data
目录下所有内容拷贝到 /mnt
。rm -rf /data
删除空目录,并使用 mkdir /data
创建挂载点目录。sudo umount /mnt
去除挂载,此时磁盘内存有数据。sudo mount /dev/vdb /data
挂载磁盘(无需重新初始化磁盘)。/data
内数据完好。可使用 df -h
命令查看挂载情况。Swap分区,即交换区,系统在物理内存(这里应该是运行内存)不够时,与Swap进行交换。
其实,Swap的调整对Linux服务器,特别是Web服务器的性能至关重要。通过调整Swap,有时可以越过系统性能瓶颈,节省系统升级费用。
经常看到有些Linux(国内汉化版)安装手册上有这样的说明:Swap空间不能超过128M。为什么会有这种说法?
在说明“128M”这个数字的来历之前,先给问题一个回答:根本不存在128M的限制!限制是2G!
Swap空间是分页的,每一页的大小和内存页的大小一样,方便Swap空间和内存之间的数据交换。
旧版本的Linux实现Swap空间时,用Swap空间的第一页作为所有Swap空间页的一个“位映射”(Bit map)。
这就是说第一页的每一位,都对应着一页Swap空间。如果这一位是1,表示此页Swap可用;如果是0,表示此页是坏块,不能使用。
这么说来,第一个Swap映射位应该是0,因为,第一页Swap是映射页。另外,最后10个映射位也被占用,用来表示Swap的版本(原来的版本是Swap_space,最新的版本是swapspace2)。
那么,如果说一页的大小为s,这种Swap的实现方法共能管理$8 \times ( s - 10 ) -
1$个Swap页。对于i386系统来说$s=4096$,则空间大小共为133890048,如果认为$1 MB=2^{20}
Byte$的话,大小正好为128M。之所以这样来实现Swap空间的管理,是要防止Swap空间中有坏块。如果系统检查到Swap中有坏块,则在相应的位映射上标记上0,表示此页不可用。这样在使用Swap时,不至于用到坏块,而使系统产生错误。
swap无法替代物理内存,性能上也会差很多,在SSD硬盘上使用对读写性能会有所加成。另外本文是通过创建一个swap文件来充当交换空间的作用,与Partition方法不同。
此时swap行应为0
初始时 Swap 相关数据都是0。
1 | dd if=/dev/zero of=/swapfile bs=1M count=2048 |
注:block_size、number_of_block 大小可以自定义,比如 bs=1M count=1024 代表设置 1G 大小 SWAP 分区。
1 | mkswap /swapfile |
1 | swapon /swapfile |
warning 提示
**这个命令可能会提示“swapon: /swapfile: insecure permissions 0644, 0600 suggested.”,意思是建议把swap设置成644或600权限。
**
这时运行free -m
会发现swap一列已经有数字了,就是上面第一次执行free -m
后的结果
如果在
/etc/rc.local
中有swapoff -a
需要修改为swapon -a
在 /etc/fstab
中添加如下一行,使之永久生效
1 | sudo vi /etc/fstab |
swpapiness
参数在 Linux 系统中,可以通过查看 /proc/sys/vm/swappiness
内容的值来确定系统对 SWAP 分区的使用原则
当 swappiness
内容的值为 0 时,表示最大限度地使用物理内存,物理内存使用完毕后,才会使用 SWAP 分区
当 swappiness
内容的值为 100 时,表示积极地使用 SWAP 分区,并且把内存中的数据及时地置换到 SWAP 分区。
查看修改前为 0,需要在物理内存使用完毕后才会使用 SWAP 分区
可以使用下述方法临时修改此参数,假设我们配置为空闲内存少于 10% 时才使用 SWAP 分区
1 | echo 10 >/proc/sys/vm/swappiness |
若需要永久修改此配置,在系统重启之后也生效的话,可以修改 /etc/sysctl.conf
文件,并增加以下内容:vm.swappiness=10
在使用CloudFlare CDN以后,服务器直接获取Remote IP会得到CloudFlare回源节点的IP,使得我们无法获取用户IP。本文章会教你使用一些方法以获取标头中的正确Remote
IP。
CloudFlare IP列表
其他CDN的配置与本文差别不大,您可以参照以下文章获取节点IP
确保安装以下内容:
1 | # Red Hat/Fedora |
1 | git clone https://github.com/cloudflare/mod_cloudflare.git && cd mod_cloudflare |
1 | apxs -a -i -c mod_cloudflare. |
1 | # Red Hat/Fedora |
你需要使用 ngx_http_realip_module
模块
相关链接:http://nginx.org/en/docs/http/ngx_http_realip_module.html
在 nginx配置文件 中加入以下内容
1 | set_real_ip_from 173.245.48.0/20; |
该前缀列表需要定期更新,详见 https://www.cloudflare.com/ips
要在您的日志中包含原始访问者 IP,请将变量 $http_cf_connecting_ip
和 $http_x_forwarded_for
添加到 log_format
指令中
另请参阅:Cloudflare 和 Nginx
运行以下脚本,将 mod_cloudflare
安装为 EasyApache 的一部分
1 | bash (curl -s https://raw.githubusercontent.com/cloudflare/mod_cloudflare/master/EasyApache/installer.sh) |
Configuration
中,启用 Use Client IP in Header
选项$_SERVER['REMOTE_ADDR']
变量也会包含客户端的实际 IP 地址,而非IIS
7-8: https://support.cloudflare.com/hc/en-us/articles/200170786
在 IIS Manager 中,双击您操作的站点的 Actions 菜单中的 Logging
启动后,选择 W3C 作为格式,再单击 Log File 子部分中格式下拉列表旁边的 Select Fields
单击 Add Field,再添加 CF-Connecting-IP
标头
单击 Ok
您应该看到 Custom Fields 中反映出您的新条目
返回到 Logging 窗口后,单击 Apply
如果这能成功,日志文件现在应该会有下划线
您应该也会看到字段中的变化
重启站点,再重启 W3SVC
如果更改没有立即生效,则重启整个实例
本文中代码样例为Windows Shell代码
Q:我用 Git 提交了一个变更,可是 Git 只记录了我的姓名和邮箱,怎样才能确保不被坏人冒名顶替?
这是一个非常简单的问题
A:加签验证呗
所以,GPG就来了
GNU Privacy Guard(GnuPG或GPG
)是一种加密软件,它是PGP加密软件的满足GPL的替代物。GnuPG依照由IETF订定的OpenPGP技术标准设计。GnuPG用于加密、数字签名及产生非对称钥匙对的软件
GnuPG是自由软件基金会的GNU计划的一部分,目前受德国政府资助。以GNU通用公共许可证第三版(GPLv3)许可
GnuPG使用用户自行生成的非对称密钥对来加密信息,由此产生的公钥可以同其他用户以各种方式交换,如密钥服务器。他们必须小心交换密钥,以防止得到伪造的密钥
GnuPG还可以向信息添加一个加密的数字签名,这样,收件人可以验证信息完整性和发件人。
GnuPG不利用专利或其他方式限制软件或算法,就像IDEA算法一开始出现在PGP中一样(可以通过下载相关插件在GnuPG中使用IDEA算法,不过如果在一些IDEA算法为专利的国家中使用,可能需要一份许可)
GnuPG同样也使用各种其他非专利的算法:
GnuPG是一个混合加密软件程序,它使用常规对称密钥提高加密速度,使用公钥便于交换
通常使用一次性的收件人公钥用以加密会话。
在本文中,GPG会用于Git加密
Git会内置GPG,因此只要找到即可
获取Git路径
1 | where git |
在Git的母文件夹搜索 gpg.exe
,切记要包括子文件夹
我的GPG路径是 C:\Program Files\Git\usr\bin\gpg.exe
为了方便起见,我们可以将其目录加入Path环境变量
按win搜索path
即可,你也可以百度搜索“如何添加Path环境变量”
点击_环境变量_,在_系统变量_中选择path
,点击_编辑_,添加 C:\Program Files\Git\usr\bin
一路确定保存,重启cmd
,测试GPG是否可以直接调用
1 | gpg --version |
如上即为成功
在生成 GPG 密钥之前,你可以检查是否有任何现有的 GPG 密钥。
GitHub 支持多种 GPG 密钥算法。 如果您尝试添加使用不支持的算法生成的密钥,可能会发生错误。
打开cmd
使用 gpg --list-secret-keys --keyid-format=long
命令列出您拥有其公钥和私钥的长形式 GPG 密钥(签名提交或标记需要私钥)
1 | gpg --list-secret-keys --keyid-format=long |
Linux上的一些 GPG 安装可能需要使用
gpg2 --list-keyid-form LONG
查看您现有密钥的列表。
在这种情况下,您还需要运行git config --global gpg.program gpg2
来配置 Git 使用git gpg2
检查命令输出以查看是否有 GPG 密钥对
如果没有 GPG 密钥对,或者您不想使用任何可用于签名提交和标记的密钥对,则生成新的 GPG 密钥
如果存在现有的 GPG 密钥对并且您要将其用于签名提交和标记,则将 GPG 密钥添加到 GitHub 帐户
上文你查看了gpg版本
如果您使用的是 2.1.17 或更高版本,请粘贴以下文本以生成 GPG 密钥对。gpg --full-generate-key
如果使用的不是 2.1.17 或更高版本,请粘贴以下文本 gpg --default-new-key-algo rsa4096 --gen-key
在提示时,指定要生成的密钥类型,或按 Enter 键接受默认的 RSA and RSA
输入所需的密钥长度,密钥必须至少是 4096
位
输入密钥的有效时长,按 Enter 键将指定默认选择,表示该密钥不会过期
验证您的选择是否正确,输入您的用户 ID 信息
要求输入电子邮件地址时,请确保输入你 GitHub 帐户的经验证电子邮件地址
要保持电子邮件地址私密,请使用 GitHub 提供的
no-reply
电子邮件地址
gpg --list-secret-keys --keyid-format=long
命令列出您拥有其公钥和私钥的长形式 GPG 密钥,签名提交或标记需要私钥3AA5C34371567BD2
1 | gpg --list-secret-keys --keyid-format=long |
3AA5C34371567BD2
1 | gpg --armor --export 3AA5C34371567BD2 |
-----BEGIN PGP PUBLIC KEY BLOCK-----
开始,到 -----END PGP PUBLIC KEY BLOCK-----
结束gpg --list-secret-keys --keyid-format=long
命令列出您拥有其公钥和私钥的长形式 GPG 密钥3AA5C34371567BD2
1 | gpg --list-secret-keys --keyid-format=long |
3AA5C34371567BD2
1 | git config --global user.signingkey 3AA5C34371567BD2 |
1 | git config --global user.email octocat@github.com |
如果你使用VSCode、JetBrains系软件等,你可以直接在设置中选择使用GPG,无需额外设置
GitHub Desktop 不支持提交签名
要将您的 Git 客户端配置为默认对本地仓库的提交签名,请在 Git 版本 2.0.0
及更高版本中,运行 git config commit.gpgsign true
。
要在计算机上的任何本地仓库中默认对所有提交签名,请运行 git config --global commit.gpgsign true
。
要存储 GPG 密钥密码,以便无需在每次对提交签名时输入该密码,我们建议使用以下工具:
您也可以手动配置 gpg-agent 以保存 GPG 密钥密码,但这不会与 Mac OS 密钥链(如 ssh
代理)集成,并且需要更多设置。
1 | git commit -S -m your commit message |
1 | git push |
您可以使用 GPG 或 S/MIME 在本地对标记进行签名
-s
添加到您的 git tag
命令1 | git tag -s mytag |
git tag -v [tag-name]
验证您签名的标记1 | git tag -v mytag |
由于作者本人长期使用GitHub,故以此为示例。Gitea、Gitee、GitLab同理。
首先我们需要配置Git的基本信息。
1 | git config --global user.name "AH-dark" # 对应 GitHub Username |
需要注意的是,我们以前所使用的使用账号密码登录 GitHub SSH 的方法已经不被允许。
同时,我建议每个人都为自己的GitHub账户配置2FA身份验证,同时尽量避免使用个人域名邮箱注册GitHub。当你的域名过期或者被弃用,他人只需购买你的域名并进行相关配置即可轻松盗取你的账号。
Windows 安装 Git 所带的 Git Bash 或 Linux Terminal 都可以进行此处的配置。
1 | ssh-keygen -t rsa -C "ahdark@outlook.com" |
此后连按三次 Enter 键,使用默认路径即可。
例如,我的SSH公钥生成在 C:\Users\ahdark\.ssh\id_rsa.pub
,私钥在 C:\Users\ahdark\.ssh\id_rsa
。
1 | # 后台启动 SSH Agent |
1 | git clone git@github.com:AlphaPic-Storage/frontend.git alphapic-frontend |
可参考之前的文章:Git GnuPG 配置教程
]]>消息队列,即 Message Queue,是我们在构建 Gin 等持久化 Golang 应用程序的常用组件。
消息队列是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。消息队列提供了异步的通信协议,每一个贮列中的纪录包含详细说明的资料,包含发生的时间,输入设备的种类,以及特定的输入参数,也就是说:消息的发送者和接收者不需要同时与消息队列交互。消息会保存在队列中,直到接收者取回它。
实际上,消息队列常常保存在链表结构中。拥有权限的进程可以向消息队列中写入或读取消息。
目前,有很多消息队列有很多开源的实现,包括JBoss Messaging、JORAM、Apache ActiveMQ、Sun Open Message Queue、RabbitMQ、IBM
MQ、Apache Qpid、Apache RocketMQ和HTTPSQS。
消息队列在实际应用中包括如下四个场景:
通常对于微服务架构的 Message Queue 都是使用更为专业的 MQ 框架。但对于我们个人构建的小型应用程序,部署一个大型的微服务 MQ
框架是毫无必要的,对于大多数人来说这也是难以学习的。
所以,在没有较为严苛的可用性要求和业务承载量的情况,我们可以基于内存构建一个仅在 Go 持久化应用程序内部运行的 Message Queue
组件。
Golang 提供了 Channel、 Goroutine 和 Context 等内置特性用于并发程序的辅助,这让我们可以免于自行构建线程池等较为繁杂的应用程序架构。
在本文中,我们基于 Channel 实现 Message Queue。
在构建消息队列组件之前,我们要了解消息队列的主要构成。
发布/订阅模式下的消息队列组件包括三个角色:
发布者将消息发送到 Topic,系统将这些消息传递给多个订阅者。
同时,发布/订阅模式的消息队列具有如下特性:
根据上述内容,我们可以大体建立消息队列的框架
1 | // mq/mq.go |
如上述代码,我们定义了传入消息的结构体 Message
和消息队列的处理池 Pool
。
其中 Pool
具有四个要求实现的方法:
Publish(topic string, message Message)
发布消息,传入 Topic 标识和消息内容,系统会将其传给所有订阅者。Subscribe(topic string, buffer int) <-chan Message
订阅消息,通过传入所需消息的 Topic 和所需消息的多少,得到一个 GoSubscribeCallback(topic string, callback CallbackFunc)
注册回调函数。通过传入消息的 Topic 为其注册回调函数,通常适用于较为单一的队列任务(例如Unsubscribe(topic string, channel <-chan Message)
通过传入 Topic 和之前通过 Subscribe
获取的 Channel在文中,我们通过内存进行处理池的实现。
1 | // mq/memory.go |
观测代码可知,我们通过内存(全局变量)和 Goroutine 实现消息的通知、订阅者的存储和异步处理。
在 Publish
中,我们通过获取所有当前 Topic 对应的 Channel 和 Callback Function 并逐个异步传入消息来完成消息通知的任务。
在 Subscribe
和 SubscribeCallback
中,我们通过 Go builtin 提供的 sync.RWMutex
实现一个简易的线程锁,避免在操作订阅时触发消息通知从而导致漏收消息等。
我们可以定义对外暴露的全局变量,使外部应用统一操作此接口,以此避免程序内运行着过多的处理池导致内存占用增大。
1 | // mq/global.go |
当然,仅局限于此是不行的,我们还未正式对其进行初始化。
1 | // main.go |
该简易消息队列组件自然也是具有其对应的优缺点。
由于程序的单一性,此组件难以做到跨程序异步操作等,使用范围仅限当前应用程序内。但相应的,我们也无需配置繁杂的订阅接口,同时可以避免不必要的大型消息队列应用。
我个人对此组件的应用也仅局限于邮件通知队列等任务队列和异步统计接口、异步开卡接口等异步接口。如有不足欢迎指出!
]]>[^1]: Go 1.6 Release Notes
[^2]: Wikipedia: Position-independent code
[^3]: Go 1.15 Release Notes
在过去的十年中,主要的操作系统和发行版(如 macOS、iOS、Android、FreeBSD、Ubuntu、Debian、Fedora)都默认启用或开始支持
PIE。对内存安全问题的关注促使非 PIE 二进制文件逐渐被淘汰。
精通此领域的读者可能会质疑 PIE 的相关性。Golang 的托管内存模型不是已经预防了 ASLR 声称要难以利用的问题吗?在某种程度上,这是正确的:如果你不使用
CGO 构建,那么编写出具有此类问题的代码并不容易。但如果你使用了 CGO,那你就链接了 C 代码。而 C 代码没有和 Go
一样的保护机制,其内存问题一直是并且将永远是一个主要的痛点,因此即使是最强的开发者也不能保证永远不犯错误。由此可见,我们仍然面临着内存安全问题,而
PIE 编译模式可以帮助我们解决这个问题。
在传统的可执行文件中,程序的代码和数据通常都有一个固定的地址空间布局。这意味着程序每次运行时,其代码和数据都会被加载到相同的内存地址。这种方法简单有效,但也容易受到某些类型的安全攻击,比如缓冲区溢出攻击,因为攻击者可以预测代码和数据的内存地址。相比之下,位置独立的可执行文件(如 -buildmode=pie
生成的文件)设计用于在加载时动态地选择内存地址。操作系统在每次运行程序时,可以选择不同的内存位置来加载程序的代码和数据。这种方法提高了安全性,因为它增加了攻击者预测程序内存布局的难度。然而,它也可能带来额外的性能开销,因为程序需要使用间接的方式来引用内存地址。
假设我们有一个简单的程序,它包含一些函数和数据。在传统的非PIE模式下,这个程序可能被设计为总是在相同的内存地址加载。例如:
当这个程序运行时,无论何时何地,它总是会被加载到这些特定的内存地址。这使得程序简单高效,但也容易受到安全攻击,因为攻击者可以利用这种确定性来执行恶意操作。
现在,假设同一个程序被编译为PIE。在这种情况下,程序的加载地址不再固定:
每次程序启动时,操作系统都会选择一个新的随机地址来加载程序和它的数据。这意味着攻击者不能预先知道程序将在哪个内存地址运行,从而大大增加了利用内存地址的安全攻击的难度。
使用 PIE 后,由于可执行文件可以在内存中的任意位置加载,这增加了系统的安全性,因为它使得攻击者难以预测程序代码的内存地址,从而阻碍了一些常见的攻击方法,例如缓冲区溢出攻击。
地址无关代码能够在不做修改的情况下被复制到内存中的任意位置。这一点不同于重定位代码,因为重定位代码需要经过链接器或加载器的特殊处理才能确定合适的运行时内存地址。
地址无关代码需要在源代码级别遵循一套特定的语义,并且需要编译器的支持。那些引用了绝对内存地址的指令(比如绝对跳转指令)必须被替换为PC相对寻址指令。
这些间接处理过程可能导致PIC的运行效率下降,但是目前大多数处理器对PIC都有很好的支持,使得这效率上的这一点点下降基本可以忽略。—— 翻译于 Wikipedia
但 PIE 到底会产生多大的性能和内存占用损耗呢?本文将对 PIE 编译模式进行测试,以验证这一说法。
由于 Go 的 PIE build mode 在 linux/amd64、linux/arm64 下不会默认启用,因此本文将在 linux/amd64
架构下进行测试。全部测试代码均位于仓库中: https://github.com/AH-dark/go-pie-comparation。
由于我本人使用 Mac 作为开发设备,在此次测试中我们使用 GitHub Codespace 作为测试环境。
我们首先分别编译 PIE 模式和非 PIE 模式的应用程序,而后分别运行他们并使用 time
命令计算应用程序完成全部任务使用的时间。
1 | package main |
我们使用预先配置好的 makefile 进行编译和测试。
1 | make build |
两组测试结果如下:
1 | Benchmarking time for non-PIE executable |
1 | Benchmarking time for non-PIE executable |
可见,PIE 导致的性能损耗极低,甚至在某些情况下还能带来性能提升。对于大型应用程序这个性能损耗可能更显著,但对于大多数应用程序而言,这个性能损耗是可以忽略的。
我们在程序中添加 net/http
包,以便于使用 net/http/pprof
进行内存占用测试。
1 | package main |
在程序启动后,任务完成不会退出,我们可以使用 net/http/pprof
进行内存占用测试。
1 | make build |
测试结果如下:
1 | File: math_test_non_pie |
对于仓库中另一组 io-copy 的测试也呈现了类似的结果。
1 | File: math_test_non_pie |
由此看来,PIE 编译模式几乎不会带来额外的内存占用。
PIE 已经被引入很久,且在当前很多平台被作为默认编译模式。但是,我们仍然可以看到很多人在讨论 PIE 编译模式的性能损耗。 本文对
PIE 编译模式进行了测试,结果显示 PIE 编译模式几乎不会带来额外的性能损耗和内存占用。
这启示我们,我们应当以求真务实的态度对待新兴的技术,而不是盲目地相信他人的说法。应当付诸实践,以验证其是否真的适合我们的应用场景。
附言:测试包中附带了对于汇编代码的 diff,如果你在 arm64 使用相同的命令进行编译,你会发现 PIE 编译模式与非 PIE
编译模式的汇编代码几乎没有差异。但在 amd64 下,PIE 编译模式的汇编代码会变更几十 MB 的代码,这是因为 PIE 在 darwin/arm64
被默认启用,而在 linux/arm64 下不会默认启用。
在参与 Star Horizon 的一个项目时,我得到团队内同学的启发,发现了 Wire 这个神奇的东西。
Wire 是 Google 研发并开源的一个 Golang 依赖注入解决方案,它通过解释原有文件生成新文件并用 Go Build Injector 实现编译环节的代码区分。
根据 Go Blog 所属,Wire 最先用于 Google 开源的 Go Cloud 项目中。
我们通常会在构建 SDK 时在 Client 中嵌套 Services,在这种场景下 Client 依赖于 Services。
但常见的问题是:随着 Services 的增多,代码变得越来越冗杂,甚至增加一个 Service 就要进行数十行的代码变更。而一旦有新人参与开发,很容易漏掉一些必须的代码变更从而引起
Bug 的产生。
Google 自然也是面临这种问题,毕竟其体量巨大。为此,他们创造了 Wire 用来解决依赖注入问题。
首先,我们需要在项目中引入 github.com/google/wire
这一 Package 进行标识,同时在全局安装:
1 | # 项目中添加依赖 |
随后,我们可以通过 wire
调用命令行程序:
Wire 进行依赖注入的过程
当然,调用之前需要我们先进行完下面的步骤。
通常情况下,在 pkg 中配置 Client
我们会进行如下操作
1 | // service/foo.go |
1 | // service/bar.go |
1 | // service/client.go |
1 | // main.go |
此时编译执行当前目录我们会得到如下结果:
在当前场景,
Client
依赖于Foo
和Bar
。这是一个非常微小的Client
,所以可能并不是很能体现依赖注入的问题。
在原有代码不变的基础下,我们新增了 service/wire.go
并修改了 main.go
:
1 | // service/wire.go |
1 | // main.go |
当前情况下,如果你编译运行会发现,因为 Client
的属性 Foo
Bar
都为 nil
,因此会直接报出 panic: runtime error: invalid memory address or nil pointer dereference
的错误。
此时,我们在终端执行 wire ./...
:
1 | wire ./... |
会发现 Wire 创造了一个新文件:service/wire_gen.go
而这个生成文件的内容是这样:
1 | // Code generated by Wire. DO NOT EDIT. |
此时如果你再进行编译会发现仍然无法正常运行,因为 service/wire.go
和 service/wire_gen.go
的 BuildClient() *Client
函数起了冲突,而此时我们可以根据 Google 的实例,通过 Injector 来避免项目打包时同时编译两个文件:
1 | // service/wire.go |
此时,只有携带 wireinject
的参数进行编译时,当前文件才会被编译在软件内。
再次编译,发现程序可以正常输出,此时 Wire 的配置就完成了。
编译目录
日后只有在修改 service/wire.go
时才需要重新生成 service/wire_gen.go
,因此根据 Google
的文档所述,你可以将 service/wire_gen.go
带入版本控制系统而非每次重新生成。
Wire 对于参数传递具有限制。
例如,对于以下情况的代码,他无法生成 wire_gen.go
文件:
1 | // service/client.go |
1 | // service/wire.go |
1 | // service/main.go |
此时,执行 wire ./...
会出现以下错误:
这是因为 Wire 将 NewClient 这类函数当做 Provider,而 Debug 这个变量并非一个合法的 Provider。
我们只需做如下操作:
1 | // service/client.go |
1 | // service/wire.go |
1 | // main.go |
此时重新执行 wire ./...
,会发现 service/wire_gen.go
变为如下内容:
1 | // Code generated by Wire. DO NOT EDIT. |
此时,功能可正常使用。
Google 提供的文档用于理解 Wire 的基本原理。
@topjohncian 使我了解 Wire 和其基本使用方法。
]]>WordPress 作为建站市场占比40%的庞然大物,确实可以称得上是一个时代之作。也正是因此,不少个人站长和企业都选择使用 WordPress
构建自己的站点。在本篇文章中,我便要简要叙述一下如何利用有限的服务器和网络资源提高 WordPress 站点的访问速度。
本章节将阐述有关 WordPress 的相关知识。
WordPress 是一个以 PHP 和 MySQL
为平台的自由开源的博客软件和内容管理系统。WordPress具有插件架构和模板系统。截至2018年4月,排名前1000万的网站中超过30.6%使用WordPress。WordPress是最受欢迎的网站内容管理系统。全球有大约40%的网站(7亿5000个)都是使用WordPress架设网站的。WordPress是目前Internet上最流行的博客系统。WordPress在最著名的网络发布阶段中脱颖而出。如今,它被使用在超过7000万个站点上。
材料引用自 Wikipedia https://en.wikipedia.org/wiki/WordPress
更多关于 WordPress 的信息,请前往 WordPress 官网获取:https://wp.org。
WordPress 在全球范围内具有非常良好的生态,你可以轻松地在 Google 等站获取有关 WordPress 的信息。WordPress 的开发者
AutoMattic 也开设了 wordpress.org 和 wordpress.com 以巩固其生态。
对比诸多国产 CMS 系统如 Typecho 等,WordPress 的生态可谓是全方位碾压。
尽管中国国内的 WordPress 生态可以算是烂的一塌糊涂,但不可否认的是它仍然在诸多CMS(内容管理系统)中脱颖而出。
WordPress 具有 高可扩展性、简便快捷、易于开发、易于学习 等诸多优点,其函数 Hook 机制等极大地提高了开发的便利性。
首先,我们都明确的是,WordPress 是一个前后端一体的服务端渲染(SSR)的框架,且使用PHP语言构建。
而 PHP 使用的 FastCGI 由于大部分是实时编译,因此无法取得良好的并发效果。即同配置服务器中,Java 或 Go 的 Web 程序的并发承载能力是远高于
PHP 的。
因此,我们要尽可能地弥补 PHP 语言效率低下所带来的缺陷,毕竟我们都很难找到比 WordPress 更好更强大的 CMS 系统了。
通常对于网页,我们会用到以下方法
此外,还有一条更加有效地优化方法:
使用更快速的语言重写网站
显然这并不现实。
本段会主要说明对于网页的组成和架构的优化方案。
在经我之手的WordPress中,按下 F12 ,你可以看到这种场景。
我本来还想放几个,但大多都被其原作者搞乱了,这里点名批评 Tkong Blog
这些站点的 Page Sources 都有一个共性,即访问的域名下只有页面文件。
将页面所引用的静态文件分离至对象存储或其他公共服务,以此提高静态文件的加载速度并减轻源站压力,这是一种可以有效提高用户体验的优化方式。
对于未备案的域名,无法使用中国大陆的服务器自然是个遗憾,大量的静态文件通过较慢的跨境网络加载也会降低用户页面渲染的速度,对于这类情况此方法也是很有效的。
你可以使用 source-global-cdn 插件将 WordPress 内核的静态文件分离,同时为
Gravatar 进行加速。
你也可以使用 Source Global CDN for WordPress 服务为发布在
WordPress.org 的插件和主题加速
对于 WordPress,前后端分离是很不现实的。把页面渲染交给用户可以减轻服务器压力,但自然对用户端有更大的负担。(不过通常我们不会去关心用户渲染有多慢,只会让他们换个浏览器。)
AlphaPic 就是利用程序功能,将静态的 JavaScript/CSS 文件和数据内容分离,通过 Ajax
获取数据并渲染到页面。
关于网路优化的内容此处不会细讲,后续可能单开文章。(挖坑)
CDN 通过缓存资源到边缘节点和二级节点中,降低资源回源率,让资源分布到各个节点中,以此提高访问速度和减轻源站压力。
想必诸位都听说过 西安一码通两次崩溃 的事件了,在解包小程序后可以看到其使用的静态文件并未使用CDN(内容分发网络)而是直接提供,在3.5万QPS的访问量下单个机房的带宽出口是绝对不可能撑住的。
使用内容分发网络,不仅可以显著降低源站压力,而且可以加快访问速度,那我们又为何不用呢?
下面是一些推荐
这并不是全部已经我测试的CDN,其他部分厂家甚至是一些小厂的部分节点质量都有较高的。
在上文的动静分离中,使用 对象存储+CDN 也可以显著降低成本、提高速度、降低源站压力。
使用 腾讯云COS+腾讯云CDN,可以得到一个腾讯的备案域名,这个对于无备案域名的站长是很友好的。
示例:WordPress 博客使用腾讯云对象存储 COS 进行静态资源 CDN 加速
在广告的疯狂摧残下,想必诸位都听说过“全站加速”了。
它的本质意义就是让回源链路更短、更快,节点间大多使用内部协议提高响应速度,以最优的链路访问员站,以此减小动态内容的响应延迟。
我个人较为推荐 阿里云的DCDN 和 腾讯云CDN的动静加速模式 ,且我自己也在使用。
在服务器渲染一个页面时通常会进行10~20次数据库查询。
使用 WordFence 会提高到30次以上,同时安全性大幅提高。
而部分的查询是不需要每次更新的,因此我们可以使用 Redis 或 Memcached 等内存缓存器将其缓存。显然,数据库查询的速度是远低于直接从内存获取数据的速度的。
对于
Memcached,可以参考这篇文章:WordPress 如何启用 Memcached 内存缓存来提高网站速度
我个人是有更精细的同时使用 Redis 和 Memcached 进行缓存的方法的,但属于付费服务,因此不作公布。
有时,当你打开自己的WordPress博客,发现致命错误:无法连接到数据库。然后,打开Linux终端,输入 service mysqld status
。
然后发现,MySQL服务关闭了。
数据库作为服务持续运行的必要条件和基础,自然不应随意关闭。而你的数据库这样关闭,大概率是因为内存溢出。
MySQL 数据库可以配置占用的资源大小,但如果资源太小会导致响应慢,太大又会导致网页高并发时内存溢出。那,怎么让站点并发大时内存占用大时不会导致数据库内存溢出呢?
我采取的方案是 站库分离 ,即将数据库从web服务器移出。我目前在使用阿里云 RDS 数据库和腾讯云 TDSQL-C 数据库。
这可以有效降低数据库因内存溢出而关闭导致站点无法正常访问的概率。
PHP 优化最高级的方法肯定是自己改PHP核心程序,但这实在过于困难,所以不予撰写。
PHP 是一个脚本语言,他会将 php 文件编译为 Opcode 后执行。部分 php 文件不需要每次都重新读取后编译,因此我们可以缓存编译后的
Opcode ,来取缔编译过程消耗的时间。
例如使用 PHP 扩展:Zend OPCache / APC
综上,WordPress 优化是一个较为复杂的过程。我个人对此研究了许久也才大致理解部分内容,动手实践又更加困难。
因此,摆烂或是找人协助也是个好选择。
我的所知并不局限于本文章中,为了防止某些人剽窃我的技术,我没有将自己全部关于WP优化的知识都写入文章中,更没有写任何实际操作方法。
但,花点小钱找我优化,或者和我混好关系白嫖我的优化(当然这比较困难),或许也是一种好的选择。
撰写本文是在2022年8月13日决定的。本人Q群内群友经常有极其不规范的运维操作,令本人很是无语,为此写本文以避免诸位站长踩坑。
非网站运营者可跳过这部分,本段落不考虑服务无法使用CDN等反向代理服务的情况。
在如今的市场上,存在各式各样的云服务商。大有阿里云、腾讯云、AWS、Azure、GCP等上市公司运营的云服务商,小有一个人对接机房或其它云服务商就可以运营的IDC服务(俗称
One Man IDC)。
多样的云服务商也造就了当前业界服务质量的参差不齐。作为数个服务的提供者和维护者,我个人趋向于寻求性价比与SLA的平衡点,即在保证不具有过高的成本、过低的性价比和过低的性能的同时保证具有较高的可靠性。
在这部分,我们将“大型公有云”定义为“上市公司运营的公有云服务”。
排名依照市占率,市占率数据为2021年下半年数据
我们常见的公有云服务有很多,包括境内和境外:
我个人较为推荐使用大型公有云运行生产环境服务,因为大型公有云通常具有较好的稳定性、较高的技术能力、较快的客户服务效率和较多的福利。
大多数国内的大型公有云都有新用户优惠,可以以较低的价格购买较高配置的云服务器。大多数国外的大型公有云都提供免费额度,例如AWS的12个月试用期、Azure的200美元额度和DigitalOcean的100美元额度。
公有云通常提供多样的服务,除去基本的弹性计算,还包括但不限于对象存储、CDN、SaaS、Kubernetes、容器镜像、容器仓库等,因此对于后续架构升级、架构扩展具有较高的优势。
需要注意的是,由于公有云通常选用较高质量的硬件,还对SLA有较高的要求,因此其售价也会较高。
部分集团企业和政府企业会使用私有云服务,即专为最终用户而创建且通常位于用户的防火墙内的云环境。
通常,大型公有云会提供多种私有云解决方案。
由于云计算资源日益丰富,近些年的市场逐渐扩张,出现了不少小微企业、工作室或个人运营的IDC服务。
此类服务通常具有较少的维护人数和云资源,部分IDC服务商通过对接大型公有云或地方机房获取云计算资源,并进行销售。
小型IDC服务商的云计算资源通常不如大型公有云广泛,很多只提供虚拟专用服务器(VPS)服务,不具备较高的技术实力。
相对大型公有云,小型IDC具有更高的可能放弃服务(俗称跑路),通常我使用以下两种方式甄别中国大陆云服务商的可信度。
中国大陆对于增值电信服务商要求增值电信业务许可证,对于销售虚拟专用服务器则需要“互联网接入服务业务”和“互联网数据中心业务”的业务种类。可前往 电信业务市场综合管理信息系统
通过公司名查询其持有的许可证。
若一小型IDC服务商销售中国大陆地区的云服务器,但不持有任何中国大陆企业主体或许可证,建议自行斟酌其可信度。
个人不建议使用小型IDC服务商的云计算资源作为生产环境的承载。
服务器的安全和权限是站点安全的基础,而Shell安全则是服务器安全的基石。
通常我们通过 SSH 协议连接 Linux Terminal,并执行命令。其中,SSH的安全是极其必要的。
我建议不要在任何公网服务器的SSH服务端使用密码登录,请尽可能使用SSH密钥对而不是密码。
我们在 Linux 服务器上通过 ssh-keygen
命令生成秘钥。
由于 ECDSA 加密具有更高的可靠性和更小的性能损耗,在本次教程中我们以此为演示。部分服务商不支持导入 ECDSA 秘钥,可依据思路替换为
RSA。
1 | ssh-keygen --help |
1 | ssh-keygen -t ecdsa |
其中,我们可以通过 -t
参数指定加密方法,通过 -b
参数指定加密位数。
个人习惯在 RSA 秘钥使用4096 bits,在 ECDSA 秘钥使用 521 bits。
1 | ssh-keygen -t ecdsa -b 521 # 生成SSH,路径使用默认即可 |
此时,~/.ssh/id_ecdsa
为私钥, ~/.ssh/id_ecdsa.pub
为公钥
请先将 ~/.ssh/id_ecdsa
路径的私钥复制到本地。
1 | cp ~/.ssh/id_ecdsa.pub ~/.ssh/authorized_keys |
1 | cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup # 备份 |
按 i 进入编辑模式并替换内容:
1 | # 去除注释 |
按 esc 退出编辑模式,并输入 :wq
保存内容。
在重启之前,请务必确保有一个现有的SSH连接正在运行,以防彻底无法连接到服务器!
而后我们需要重启 SSH 服务:
1 | # RHEL / CentOS |
此处使用 XShell 举例:
在 工具-用户密钥管理者 中导入上文要求复制到本地的私钥
导入私钥
XShell Session
针对服务器的Session进行修改即可。
随后,不要关闭保留的连接,重新双击 Session 文件进行连接,如果能够成功连接则为无误。
更改 SSH 端口并无必要,能够成功破解 ECDSA 和 RSA 算法几乎没有可能性。
请不要将私钥上传到任何公开的网络平台,例如GitHub、QQ群文件、云盘等,这都是极其不安全的!
私钥是唯一的,丢失私钥意味着你无法打开服务器(可VNC重置),泄露私钥意味着你的服务器将承受极大的安全风险。
请尽可能不要使用Root登录SSH。
你可以在 /etc/ssh/sshd_config
中设置 PermitRootLogin no
以避免直接通过Root登录。
为非 Root 用户设置私钥的方法与上述大致相同。
对于较大的企业,更好的选择应当是在内网设立鉴权服务器和 SSH 跳板机[ref]即通过一台暴露给外网的服务器的 SSH 客户端连接内网服务器的
SSH 服务端[/ref]。
Zero Trust 思想同样适用于系统层。
建议关闭所有不需要使用的端口。
可参考文章:
观点仅适用于生产环境,非生产环境请随意选择。
选择面板程序请遵循以下原则
面板程序具有优点,同时也具有缺点。
最主要的问题在于,一旦一个面板程序出现Bug,那几乎所有使用者都会受到极大的影响。
且国内面板必定会上传你的服务器相关信息和站点信息,公安找上门不是不可能的事。
没有以下两位人才,本文必将不会被创作。
纯属整活,以上两位都是群友兼博主的好朋友~
感谢群友的提议,让我更好地完善本文内容。
在本节中,我们将介绍 Kubernetes 和 Helm 的基本概念,这些知识是理解如何在 Kubernetes 集群上部署 WordPress 的基础。
Kubernetes 是一个开源的容器编排平台,用于自动化容器的部署、扩展和管理。Kubernetes 提供了一个统一的 API
和工具,使得容器的部署和管理变得更加简单和可靠。它可以管理运行在多台机器上的容器,确保容器应用程序的高可用性、弹性、伸缩性和安全性。
Kubernetes 主要包括以下核心概念:
Helm 是一个 Kubernetes 包管理器,用于创建、共享和部署预定义的 Kubernetes 应用程序包(称为 charts)。Helm 通过使用 charts 来抽象
Kubernetes 的复杂性,简化了应用程序的部署和管理。使用 Helm 可以轻松创建自定义 charts,并将它们分享给其他人使用。
Helm 主要包括以下核心概念:
kubectl apply -f
命令就可以部署应用。在接下来的章节中,我们将更深入地介绍 Helm 和如何使用它来部署 WordPress。
KubeSphere 是一款基于 Kubernetes 的容器平台,它提供了一系列的工具和服务,帮助用户在 Kubernetes 上管理和运行应用程序。KubeSphere
通过简化 Kubernetes 的使用,提供了一个更加易用和友好的界面,以便用户更加便捷地部署、管理和扩展 Kubernetes 应用程序。
KubeSphere 提供了一些核心概念,例如 Workspace、项目、命名空间等,它们用于组织和管理 Kubernetes 资源,以实现更好的应用程序管理和资源隔离。
要使用 KubeSphere,您需要理解以下概念:
KubeSphere 还提供了许多实用的功能和工具,例如 DevOps 工具链、应用程序模板、监控和日志分析等,这些功能和工具可以帮助用户更轻松地进行应用程序部署、自动化测试、CI/CD
流程和运维工作。
总之,KubeSphere 是一个功能强大的 Kubernetes 容器平台,为用户提供了便捷和可靠的工具和服务,帮助他们更好地管理和运行
Kubernetes 应用程序。
本章将介绍 WordPress Chart,它是一个预定义的 Kubernetes 应用程序包,包括 WordPress、MariaDB、Memcached 和其它必要的组件。使用
WordPress Chart 可以快速而简便地部署一个完整的 WordPress 环境,避免了手动配置和安装的复杂性。
在本章中,我们使用 Bitnami 提供的 WordPress
Chart:bitnami/wordpress
Chart 以多种方式简化了 WordPress 的部署和管理。
它提供了一个预定义的应用程序包,包括 WordPress、MariaDB、Memcached 和其他必要的组件,用户可以轻松地安装和配置它们。同时,它提供了一系列配置选项,使用户能够轻松地自定义
WordPress 的设置。
关键的是,它支持自动扩展、自动更新和自动恢复容器,确保 WordPress 应用程序始终保持最新和高可用性。
使用 Bitnami WordPress Chart 只需执行几个简单的步骤:
代码示例如下:
1 | helm repo add bitnami https://charts.bitnami.com/bitnami |
在此教程中,我们不使用 Helm 直接部署,而是使用 KubeSphere 提供的基于 OpenPitrix 的应用商店。
我们会演示如何使用 Helm 部署 Bitnami WordPress Chart 到 Kubernetes 集群,并讨论如何选择适当的 Kubernetes 命名空间、创建
PersistentVolumeClaim 和使用 Ingress 控制器将 WordPress 服务暴露给外部。
具体来说,我们将涵盖以下主题:
本章节将介绍如何在 Kubernetes 集群中部署 WordPress,我们将使用 Bitnami 的 WordPress Chart 和 KubeSphere 来完成部署。
在本文中,您将学习如何在 KubeSphere 中添加 Bitnami 存储库,创建项目并使用 helm 工具安装 WordPress Chart。然后,我们将讨论如何配置
Ingress 规则,以实现从外部访问 WordPress 服务。通过本文,您将学会如何在 Kubernetes 集群中部署 WordPress,并将其对外开放以供访问。
要添加 Bitnami 存储库,请首先使用不低于 self-provider
角色权限的用户登录 KubeSphere
控制台。然后,前往工作台,选择要添加存储库的集群和工作空间。接下来,在左侧菜单栏中选择“应用管理”→“应用仓库”,单击“添加”按钮,然后输入 https://charts.bitnami.com/bitnami
作为存储库地址。
添加 Bitnami 存储库作为应用仓库
在 KubeSphere 中,命名空间与项目是等同的。
要创建一个项目,请前往左侧菜单中的“项目”页面,然后在右上角单击“创建项目”按钮并输入适当的名称。
请注意,项目名称(即命名空间名称)在整个集群中必须是唯一的。我建议您在命名时尽量让名称与工作空间相关联,以便于区分,并避免重复。
创建项目
请前往“应用负载”→“应用”→“基于模板的应用”,单击右上角的“创建”按钮,然后选择“从应用模板”。接下来,在仓库选择器的顶部选择刚刚添加的
Bitnami 存储库,并搜索 WordPress。
在应用设置的一步中,您需要对 WordPress 进行配置。
1 | global: |
需要注意的是,我建议您在 wordpressExtraConfigContent
中强制定义 WP_HOME
和 WP_SITEURL
,否则 WordPress 将使用 Ingress
传入的动态域名,这可能会影响 Site Kit、Jetpack 等插件的使用。
为了实现从外部访问 WordPress 服务,我们需要在 Kubernetes 集群中配置一个 Ingress 控制器,并创建一个 Ingress 规则将访问流量路由到
WordPress 服务。
在 KubeSphere 中,您可以使用预装的 Nginx Ingress 控制器或安装其他支持的 Ingress 控制器。这里,我们将使用 Nginx Ingress
控制器来演示如何配置 Ingress 规则。
以下是一些基本的步骤:
nginx-ingress-controller
的 Deployment 和一个名为 nginx-ingress-controller
的1 | apiVersion: networking.k8s.io/v1 |
在上面的示例中,我们添加了 nginx.ingress.kubernetes.io/proxy-body-size
注解,并设置其值为 80m
,以允许更大的请求体。
完成上述步骤后,您应该能够从外部访问 WordPress 服务了。
本段将讲述使用 Cert Manager 配置 Kubernetes Ingress TLS 的详细步骤。
1 | # Certificate for Cert Manager |
在上面的示例中,我们创建了一个名为 example-certificate
的 Certificate 对象,并将其与要存储证书和密钥的 Kubernetes Secret
对象关联。我们还指定了要保护的主机名以及要使用的 Issuer。
请注意,我们使用了 ClusterIssuer
引用,这意味着我们使用的是集群范围的 Issuer。如果您在上一步创建的是 Issuer
,您需要进行相应的更改。
在此之后,您可以在对应 Project(Namespace)的 Secrets 中查找名为 tls-secret
的 Secret,如果其存在且正常显示为 TLS
证书,则证书已创建完毕。
如果您使用的是 Cert Manager 预定义的 Issuer(例如 Let’s Encrypt
Issuer),则证书签发过程可能需要一些时间才能完成。在这种情况下,您可以等待一段时间,然后重复上述步骤来验证证书是否已签发。
在 KubeSphere Console 中,您可以通过以下步骤在项目中创建保密字典,以存储 TLS 证书的公私钥:
通过这些简单的步骤,您就可以将您的 TLS 证书公私钥存储在保密字典中,以便在部署应用程序时使用。这将使您的证书更加安全,并且只有那些被授权的用户才能够查看证书的私钥信息。
添加 TLS 证书
在 Kubernetes 中,您可以使用 YAML 文件定义 Ingress 对象,并将其与 Kubernetes Service 和后端 Pod 关联。在 YAML
文件中,您需要使用以下字段配置 TLS:
1 | apiVersion: networking.k8s.io/v1 |
点击保存后,Ingress 将被更新。
测试和故障排除是 Kubernetes 应用程序维护的重要组成部分,它可以确保应用程序在部署后能够正常运行,并且在遇到故障时能够及时诊断和解决问题。
当您使用 KubeSphere Console 这一可视化工具来部署和管理您的 Kubernetes 应用程序时,调试过程通常会更加简单。
您可以使用 KubeSphere Console 的内置调试工具来快速诊断和解决问题:
通过以上步骤,您可以在 KubeSphere Console 中调试您的 Kubernetes 应用程序,以确保它们始终保持高可用性和稳定性。
当应用程序部署完成后,您可以在您指定的 Ingress 位置查看到站点内容。为了确保应用程序的正常运行,您需要进行一些测试,以下是一些测试应用程序的方法:
nslookup
或 dig
通过以上测试,您可以确保您的应用程序能够正常工作,并在出现问题时及时发现和解决。
通过本文的介绍,您应该已经了解了如何使用 Kubernetes 和 KubeSphere 部署和管理 WordPress 应用程序,并使用 Cert Manager 配置
Ingress TLS 以实现安全的外部访问。在本文中,我们重点介绍了以下内容:
通过本文的指导,您应该可以轻松地部署和管理 WordPress 应用程序,并确保应用程序的安全性和稳定性。同时,本文也为您提供了一些有用的技术和工具,以帮助您更好地管理和部署
Kubernetes 应用程序。
在本文撰写的过程中,我也完成了对于一个测试性 WordPress 应用程序的部署,即您现在所见到的 ahdark.blog 站点。此站点运行于
Kubernetes ,使用 Google Compute Engine 作为基础设施提供商。如果您对于 Kubernetes 的高可用性和便捷性感兴趣,您可以前往订阅
Telegram 频道 @AHdark_Channel 以获取更多实时动态。
Source Global CDN 的海外中转链路往往需要对多个域名进行反向代理,之前的方案是给每个域名单独创建
vhost。但在链路增长、服务器增多的现实情况下,这一方案变得越来越难以维护。
我作为 Source Global CDN 的主要运维人员,自然而然地承担起了此任务。
随着服务使用服务器增多,链路增长,我们面临以下问题:
在经过深思熟虑后,我们大致想出以下解决方案:
高内聚的泛域名解析方案使得vhost文件数目和SSL数目大幅减小,私有DNS避免服务器内数据不修改的问题。
经过考虑,我们制定了以下方案:
其中,acme.sh 操作可参考 使用 acme.sh 自动为IP/域名配置证书 并进行少许修改。
在完成上述简单任务后,我们开始考虑 Nginx 的动态反向代理。
之所以不考虑使用 Lua 脚本是因为在所有服务器重装 OpenResty 的时间成本太过巨大。
相信大多数人都对 Nginx 的反向代理早有耳闻,大多数情况下其反向代理呈现以下模式:
1 | upstream backend { |
在这一模式下,反向代理始终从 127.0.0.1:8080 获取资源,是静态的。
而我们的需求是这样的:
$host = gh.sourcegcdn.com
,反向代理 https://gh.origin.sgcdn
$host = wp.sourcegcdn.com
,反向代理 https://wp.origin.sgcdn
$host = <subdomain>.sourcegcdn.com
,反向代理 https://`<subdomain>`.origin.sgcdn
即按请求头的子域名寻求前缀,并补全后缀,随后进行反向代理。
我们的最终实现如下:
1 | server { |
如此配置,我们只需在 CDN 测配置 *.sourcegcdn.com
这一统一节点,再按需逐个添加 DNS 解析,即可正常使用。
1 | *.sourcegcdn.com1INA127.0.0.1 |
但我认为,停更并非好事,而是应当避免的,因此我也在有片刻闲暇的今日在已经停止更新五个月的博客的第一篇文章。
数学是科学的基础,而素数是数学的基石之一。尽管素数的概念简单直观——只有 1 和自身两个正因数的自然数——但它们在数学和计算科学领域却扮演着重要角色。
素数并非我们独立发现的,它们在自然界中以复杂的规律存在。通过对素数的理解和应用,我们得以解决各种问题,从密码学的加密算法,到搜索引擎的数据结构,甚至到量子计算的未来发展。因此,有效地检测一个数字是否为素数,对于编程来说是至关重要的技能。
在本篇博客文章中,我们将深入探讨素数校验在程序设计中的实现。我们将先从基础的数学理论开始,逐渐深入到各种算法的优化。同时探讨不同的素数检验算法,从最基础的试除法到更复杂高效的算法(如
Miller–Rabin primality test),来感受算法的历史与更迭。
在我们深入讨论如何在程序中校验素数之前,我们需要先理解素数是什么,以及它们在计算机科学中为何如此重要。
素数是一种特殊的数字,它们只有两个因数:1 和它们自身。最简单的素数是 2,然后是
3,5,7,11,13,等等。在自然数的海洋中,素数像是散落的珍珠,等待着我们去发现和欣赏。
这些数字在数学中的重要性是毋庸置疑的,而在计算机科学中,它们的作用同样重大。许多计算机科学的领域,包括但不限于密码学,数据结构和并行计算,都广泛使用素数。例如,在密码学中,大素数被用于生成公钥和私钥,从而提供强大的安全性。而在数据结构中,素数常常被用作散列函数的参数,以提高散列的效率和减小冲突的可能性。
那么,如何确定一个数字是否是素数呢?这就是我们接下来要讨论的主题:在程序中校验素数。
在我们深入探讨更为高效的素数检验算法之前,理解最基础和直观的素数检验方法——穷举法是必要的。这种方法虽然在时间和空间复杂度上并不出色,但其简单直白的逻辑使其成为理解素数的重要工具。
穷举法的核心思想是试除所有小于
就不是素数;如果所有小于
以下是一个示例:
1 | bool is_prime(int n) { |
在这段代码中,首先检查 false
,因为根据定义,素数必须大于 false
;如果所有这些数都不能整除true
。
然而,穷举法的效率并不高。它的时间复杂度为
在理解了穷举法后,我们可以继续探讨一种更高效的素数检验方法——试除法。与穷举法相比,试除法通过减少需要试除的数来优化素数检验的过程,因此在计算复杂度上有显著的提升。
试除法的核心思想是:如果一个数
是否为素数,我们只需要试除
以下是一个示例:
1 |
|
这段代码首先检查 false
,因为根据定义,素数必须大于 false
;如果所有这些数都不能整除true
。
试除法在时间复杂度上的提升明显,它的时间复杂度为
虽然试除法相较于穷举法在时间复杂度上有所提升,但对于极大的数,其效率仍有待改进。为了在大数素性检验上取得更好的效率,科学家们提出了更为高效的算法,例如
Miller–Rabin 素性测试等。在接下来的章节中,我们将详细讨论这些更为高效的素数检验算法。
在素数检验中,我们不仅可以通过试除来寻找一个数的因数,还可以利用数学理论来进行更高效的检验,费马素性测试就是这样一种利用费马小定理的素数检验方法。
费马小定理表明,如果
不满足上述性质,我们可以确定
因此,费马素性测试并不能完全保证结果的正确性,但在实际应用中,它的错误率是可以接受的。
以下是一个示例:
1 |
|
在这段代码中,modular_pow
函数实现了模数幂的计算,即计算 is_prime
函数则实现了费马素性测试。
它首先检查
而后,它在
如果计算结果不等于 false
;
如果在所有迭代中计算结果都等于 true
,表明
费马素性测试的时间复杂度为
相比于试除法,费马素性测试在时间复杂度上有显著的提升,尤其是当处理大数时。
但需要注意的是,费马素性测试可能会返回假素数,即那些满足费马小定理但实际上不是素数的数。
尽管如此,通过增加迭代次数,我们可以降低这种错误的概率,使其满足实际应用的需要。
接下来的章节中,我们将介绍另一种更为高效且结果更准确的素数检验方法—— Miller–Rabin 素性测试。
尽管费马素性测试在素数检验中已经相当有用,但它仍有一定的局限性,特别是在处理一些特定的“伪素数”时,可能会返回误判。
为了改进这个问题,我们引入一种更为强大的素性测试算法——米勒-拉宾素性测试。
米勒-拉宾测试基于费马小定理,但更进一步引入了一种附加检验,以提高检验的准确性。
该方法利用了一个数学事实:对于任意一个奇素数
那么
以下是一个示例:
1 |
|
米勒-拉宾素性测试的时间复杂度和费马素性测试类似,为
是我们要检验的数。它的空间复杂度为
相比于费马素性测试,米勒-拉宾素性测试在准确性上有显著的提升,尤其是在处理大数时。然而,它仍然是一种概率性的算法,因此,对于非常大的数或者需要非常高准确性的应用,可能需要运用更高级的素性检测方法。
多种语言代码实现:https://gist.github.com/AH-dark/306d76d001ecd94d1796f64a5ea74df6
至此,我们已经探讨了几种常见且有效的素数检验算法,从基本的试除法和穷举法到基于概率的费马素性测试和米勒-拉宾素性测试。这些方法已经可以处理绝大多数的素数检验需求。然而,在某些特殊的场合,例如非常大的数或者需要非常高准确性的应用,我们可能需要一种更为强大的素性检验算法。这就是我们这一章要介绍的内容——椭圆曲线素性测试。
椭圆曲线素性测试(Elliptic Curve Primality Proving,简称ECPP)是一种确定性的素数检验算法,可以在多项式时间内确定一个数是否为素数。ECPP
算法的优势在于它可以快速且准确地判断非常大的数(例如超过100位)是否为素数,且不需要任何预先已知的素数表。
椭圆曲线素性测试的理论基础深厚,需要理解椭圆曲线的数学理论。在实践中,这种算法的实现通常需要使用到高级的数学软件库,例如 GMP
库。
由于椭圆曲线素性测试的复杂性和它所需的数学知识,我在这里并不打算给出具体的代码实现。读者如果对此感兴趣,可以查阅相关的数学文献和代码库。
综上,椭圆曲线素性测试提供了一种强大且精确的素数检验方法,尤其适合需要处理大数和高准确性需求的场景。
ECPP的主要思想是使用椭圆曲线和黎曼假设来找到一个数
在详细讨论ECPP算法之前,我们需要了解一些基础概念。
ECPP算法的步骤如下:
这只是 ECPP 算法的概述,并没有涉及到很多细节,比如如何在实际操作中进行点的加法,以及如何实现各种优化。关于这些细节,我建议你参考专业领域的论文。
经过我们的探究学习,我们看到了数学在程序设计中的重要性。从最简单的穷举法和试除法,到稍微复杂的费马素性测试,
再到更高级的米勒-拉宾素性测试和椭圆曲线素性证明(ECPP)方法,数学和计算机科学相互结合,来解决我们面临的问题。
实现这些算法的过程不仅需要对数学有深入的理解,也需要熟练掌握编程技巧。
不同的素数检验算法适用于不同的情况,选择合适的算法能大大提高程序的效率。
特别是在处理加密和安全相关的问题时,有效的素数检验和生成技术尤为重要。
4月9日,我注册了 ahd.im 域名并打算以此为基础搭建短链接系统。
但当我在 GitHub 等平台搜索了一圈,却没有找到令我满意的作品。
大多数UI丑陋或是算法效率低下,难以满足我的愿景,因此我决定着手写一套自己的短链接系统。
这是我第一次独立完成一套全栈系统,其摸索的过程自然会为我全栈开发能力积累经验。因此,选择一套高效、高兼容、普适的技术栈自然是极为重要的一步。
对于后端,最重要的是保证代码质量和执行效率。
PHP 效率太低,代码混乱,不被我欣赏。
ExpressJS 不适合写较大的系统应用,其并发也不强,且代码中同步异步容易混乱。
Golang 尽管性能较强,但不符合我的代码习惯。
C++ 应该就没有一个能用的Web开发框架。
最后,我选择了使用 Java Spring Boot 框架撰写后端功能,使用 MyBatis + MySQL 进行数据控制。
前端便没有那么多选择了。
jQuery 我不是很熟悉,且当今时代高级前端框架林立,我没有理由选择一个即将被时代淘汰的框架。
Vue 我不喜欢,就没学。
AngularJS 我学了,但还是不喜欢。
React 我很喜欢。
于是,前端框架的选择就轻松地完成了。
对于用户端,看了许多公众号文章,发现了阿里的 Ant Design 似乎很不错,于是便选用了 Ant Design 。
对于管理端,因为我实在没有想象力,不知道 Ant Design 如何设计,而且没有学习时间(实际上就是没心情研究了),就用回了 Material
UI。
由上,我确定了我开发所需的技术栈:
因为一模的原因,拖到了5月初才开始进行开发工作。学习 Ant Design 的函数、功能,Java 语法和 Spring Boot 使用也耗费了不少时间。
大约是一周以后,我正式着手进行 AHdark/Link 的 Development Progress 。
后端
后端
后端
后端
前端
前端
前端
前后端分离并同时进行开发,自然需要在本地和测试服务器进行诸多配置。
我使用的 IntelliJ Idea 高效地适配了 Java 和 React 开发,使得我可以在一个专业编辑器上同时进行前后端的开发。
高效的 Idea 编辑器也使得我代码中极少出现错误,减少了反复编译的次数。
不要小看 idea 的内存占用,随便写几行代码就破 4GB 了,真不是个正常玩意。
前端通过 Git Submodule 内嵌于后端项目目录中,尽管在每天晚上需要进行 Sync Commit 修改后端保存的前端Git版本以保证前后端一致性,但仍确保了项目一致性和开发稳定性。
模块化开发可以使得我的开发进程更加规律化,避免因某一功能的修改引起整体代码大改的情况。
顶层路由
在前端顶层,我通过 React Router v5 区分用户端与管理端。
管理端 路由
管理端顶层,我将 Layout 渲染部分放置在 Switch 以外以避免切换页面时重复渲染。
同时将各个页面处理成 JSX.Element ,放置在不同的目录中。
如此,每次开发某一页面或组件,就可以只修改某一目录下的文件,避免了 Git Commit Log 混乱。
用户端 路由
在用户端,我通过 Router 区分页面,对各个页面进行分离。
后端代码均严格依据规范进行开发,对 Controller 、 Service 等进行分层,以保证程序的稳定性。
首先,在此感谢 @topjohncian 对于我前端代码的优化建议与分析。
之前,我使用 react-router-dom@v6 进行路由分离,在每个页面通过请求后端判断用户是否登陆。
这样的实现既不优雅也不高效,而且会降低用户体验。
经由他的帮助,他为我尝试制作 <AuthRoute>
<NoAuthRoute>
等中间件。
因为 react-router-dom@v6 的愚蠢特性,我们经过漫长的尝试后决定降级为v5,以获得更高的可扩展性。
AuthRoute Element
在经历漫长的开发过程后,我开始着手进行部署工作。
其中重要的一项内容是 Debug ,尽管我尽力寻找 Bug 并予以更改,但部署到生产环境时仍有不少 Bug 等待修复。
在几小时后,程序迭代出更新的版本,我修复了绝大多数的未解决的 Bug 。
生产环境与测试环境不在同一云服务商,以此避免测试环境提权影响到生产环境。
你可以前往 https://ahd.im 预览实际效果。
短链接生成目前仅开放给注册用户,且并未开放注册,因此您可能无法体验到短链接生成和站点管理的过程。
用户端 - 首页
用户端 - 设置
管理端 - 仪表盘
管理端 - 用户管理
Link 短链接系统 为闭源项目,使用 React+SpringBoot 前后端分离开发。
其 GitHub 私有仓库 分别位于 https://github.com/AH-dark/Link
和 https://github.com/AH-dark/Link-Frontend ,但你无法打开获取源代码。
主要开发者为 @AHdark 。
项目的成功实现离不开 @topjohncian 的协助。
感谢您的阅读,我们会持续更新本项目,并视情况决定是否开源。
本项目已在2022年5月28日全量开源。
]]>于是,在 2023 年我逐步完善我的 Go 开发水平和容器化解决方案后,我将我几乎所有应用迁移到了 Kubernetes 上。自然,我就无需持有什么服务器了。
但我的博客使用 WordPress 构建,且 GCP 的 RWX PVC 价格极其高昂,我的博客便一直保留在服务器上(曾经有过,但是失败了)。
2023 年中,我开始思考,是否应该重构我的博客系统,以此使得我的博客也能够容器化,实现降本提效?在 2023
年末,我由于学业原因放弃了工作,时间也变得闲暇了很多。这给了我重构博客系统的机会,于是我开始了这次重构。
放弃工作自然导致我没有足够的资金来源,迁移到 Next.js 可以几乎把成本降低到 0,这都得益于 Vercel
免费高效的服务。还有 Waline 支持的 Vercel 部署,这也是为什么我选择了 Waline
作为我的评论系统。
不仅仅是上文所讲的我不想继续持有 VPS,真正重构并选择使用如此的技术栈的原因还有很多。你可能会问,为什么我不能使用 SaaS 形式的
WordPress 服务?为什么我要使用 Hexo?为什么我在年中开始思考而直到现在才开始重构?
首先,WordPress 是一个非常优秀的 CMS 系统,它的生态也非常完善。但 WordPress 本身是基于 PHP
构建的,且其最大的卖点便是可扩展性,因此我无法将其重写,也很难在低成本的环境下优化其性能。
我曾经使用过 WordPress,并且在优化上奋斗了很久。我还曾发表过一篇文章讲述如何优化 WordPress[^1],但最终我还是放弃了
WordPress。WordPress 的优化是无止境的,也没什么技术含量,且低成本环境的算力瓶颈对于 PHP
应用实在难以突破。只是不断的缓存、缓存,用空间复杂度换取时间复杂度,仅此而已。其程序设计上的本身的问题仍然没有被解决。
[^1]: 如何有效提高 WordPress 博客的访问速度
即使将 WordPress 容器化,也面临很多的问题:
更何况,WordPress 由于历史原因,多适用于传统的网站。而我作为一个深度的 React 开发者,我更喜欢使用 React
开发主题。这就导致我一直不能为我自己的博客开发主题,而是使用了 Argon、MDx 等现成的主题,尽管我也参与到了它们的维护中。
我选择的框架为 Next.js,一个 Vercel 开发的 React 框架,而文章的存储依赖 Hexo
Warehouse。你可能好奇,在诸多的选择中为什么我选择了这两种?这也是经过我深思熟虑的选择。
同时,我也能为你解答为什么我要拖到年末才开始重构的问题。
我选择 Next.js 最初的原因很简单,因为它是一个 React 框架。我作为一个 React 开发者,我更喜欢使用 React。Next.js
本身高效、易用,且有着非常完善的生态。我相信每一个 React 开发者在面临需要做 Server-Side Rendering 的时候都会选择
Next.js。而且我有着丰富的 Next.js 开发经验,你可以在我的 GitHub 中找到很多相关开源项目,我在今年年初开发的 V2Board
前端重构项目也是基于 Next.js。
Next.js 走在时代前沿,其对于 React 的支持也是最好的。Vercel 开发了 SWC、Turborepo、Turbopack 等项目,而 Next.js
作为其自家的 T0 产品,自然也是第一时间支持这些的。这些项目的出现,使 Next.js 具有更高的性能。在 React Server Component
出现后,Next.js 也是第一时间发布了 App Router,使得 Next.js 中能够使用 Server
Component。(即使用法有点弱智,我不得不把一个文件拆成好几个,就为了用那个 "use client";
)
RSC 使得我们能够在 Components 中直接调用 Hexo 并传递其内部的数据类型。Hexo 的 API 返回的大多是 Document
或者 Query
类。且为了优化性能,其内容很多为 getter / setter,而这些是无法被传递的。在先前的 Pages Router 中,我们需要将其转换为能够被
JSON serialize 的结构,然后再传递给 Components。因此在 App Router 下,我们能够少写很多数据传递的步骤。^2
Hexo Warehouse 是一个基于 JSON 的数据仓储,其本身是为 Hexo 的构建过程而创建的。但它作为一个数据仓储,其本身并不依赖于
Hexo,能够被我们作为数据库一样使用。在今年下半年,Hexo Warehouse 完成了 TypeScript 的重构,使我能够更轻松的使用它。Hexo
也发布了版本 7.0.0,进一步完善了 API 和 TypeScript 支持。我终于不需要为了 Hexo 写 AnyScript 了!
其实我在今年年中就开始了重构,当时选择使用 Next.js (Pages Router) + Material UI + Hexo (v6),被折磨了好一段时间,最终放弃了。后面等待
Warehouse 和 Hexo 完成了对 TypeScript 的适配才删除项目重新开始。新一次项目使用了 Tailwind CSS,也是我的一个变化吧。
尽管使用了 Hexo Warehouse,但你很难从我的代码中找到 Hexo 的影子。它只在 Server-Side 被使用,而且只是作为一个数据仓储。我并没有使用
Hexo 本身的功能,Markdown 也是另采用的解析库。所以尽管我初始化的是 Hexo 实例,但使用的还是以其 config 和 warehouse 为主。
是的,我使用 Hexo Config 配置大多数的内容设置,比如 Title、Description、Author 等等。因为我不想定义更多的 config,也不想在
Next.js 的 ENV 中定义这些。
Tailwind CSS 是一个 CSS 框架,其本身并不是一个 UI 框架。它的特点是使用 Utility-First 的方式,而不是像 Bootstrap 那样使用。如果你安装了
Wappalyzer,可能会发现我还使用了 shadcn/ui
,这是因为从今年开始我就在追寻设计上的极简主义。这种纯色调的设计可以让读者更加专注于文章本身,而不是花里胡哨的设计。
而且众所周知,在组件复用率足够高的情况下,Tailwind CSS 写起来是非常舒服的。我不需要去写一堆的 CSS,只需要写一堆的 Utility
Class 就可以了。而我的习惯是尽可能避免重复,因此我的复用率也是很高的,这也是原因之一。
当然,Tailwind CSS 作为一个 CSS 框架,其本身并不是一个 UI 框架,因此也使我受到一些限制。比如我无法混淆 CSS,且 RSC 传递 CSS
的数据占用是很大的(因为转义),这也是我最为头疼的点之一。在后文会详细解释我当前面临的诸多问题。
由于我的项目本质上是 Next.js 的 Web App 调用 Hexo 的实例,因此 Hexo 也需要作为一个 package 使用。但我觉得在一个 package
中存在另一个 package 很不优雅,所以我使用 monorepo 的方案使这两个 package 单独存在。
pnpm 是我常用的依赖管理工具,其本身支持 monorepo,即 pnpm-workspace.yaml
。Turbopack 具有更多的功能,且 Vercel
对它提供了完善的支持,因此我加入了 Turbopack 来管理任务、并行构建和远程缓存。于是在此情况下,pnpm workspace 仅作为 monorepo
的依赖管理工具,而构建方面则交给了 Turbopack。
turbo.json
管理和定义任务,包括任务之间的关系,可以进一步提高代码复用率。turbo build
命令构建项目,其会自动并行构建。当然,我实际仓库中并不只有这两个 packages,也包括了一些依赖库、工具库和 eslint 配置等。
尽管有前人的文章参考和我个人的经验,但我在开发过程中还是遇到了很多问题。这些问题有些是我自己的问题,有些是由于我使用的技术栈导致的。这些问题都是我在开发过程中踩的坑,希望能为后人提供一些参考。
pre > code
和 code
的区分我使用 react-markdown
来渲染 Markdown,它对于代码块的渲染是 <pre><code></code></pre>
。但我需要对于代码块进行一些额外的处理,比如 <code>
中的内容只需要简单的用背景和字体衬托,而 <pre>
中的内容则需要用丰富的样式。
在此情况下,Tailwind CSS 的样式系统无法支撑复杂的严格的样式。因此我需要使用其它的方式提供 CSS。目前我直接引入了 Scss,这也与
PostCss 相适应,但我还是准备把它换成 Style9 或 emotion。
1 | .article { |
归根结底,Tailwind CSS 的方便性也是其不足的地方,它的样式系统无法支撑复杂的样式。
由于我的文章是使用 Markdown 编写的,因此获取 Excerpt 是一个很麻烦的事情。我早期的文章没有严格遵循格式,很多都没有 Abstract
部分。甚至有的在开头就有图片一类的东西。因此我无法使用常规的替换方式来获取 Excerpt。目前我的解决方案是在每个文章的 Front
Matter 部分添加 excerpt
字段,手动填写 Excerpt。
由于我迁移后删除了很多我觉得比较弱智的文章,所以手动操作也不是很麻烦。但我还是希望能够自动获取 Excerpt,这样会显得更
Automation。
目前我仍然没有找到解决方案,我也不想使用 Hexo 的 API 来获取文章的内容。也正是因此,重构 Markdown AST 解析被提上了日程。
当前我的文章目录是使用 react-markdown
的 remark-toc
插件生成的。这个插件识别 Heading
中的内容,当其匹配则生成目录。但 remark-toc
生成的目录中携带的 anchor 的 hash 部分无法适配特殊符号,因此你可以看到此文章的一些目录不太正常……
就我看来,我应当单独使用一个组件来生成目录,并将目录放置到合适的位置。但这也就意味着,我还是需要自己去解析 Markdown AST。
在我博客开发的早起,引入 Hexo 依赖时,只要我在 Server Components 中使用了 Hexo,都会出现错误:
1 | ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node |
在 Console 也有错误:
1 | @ahdark-blog/web:dev: ⨯ ../../node_modules/.pnpm/fsevents@2.3.3/node_modules/fsevents/fsevents.node |
通过查询相关资料,我发现这是由于 Webpack bundle 后,Hexo 无法调用 fs 导致的。Next.js 提供了一个 experimental
的设置项 serverComponentsExternalPackages
,可以将使用 Node.js 环境的 Packages 排除在外。^3
根据错误内容,我将 hexo
、hexo-fs
、hexo-util
添加到了 serverComponentsExternalPackages
中,以此解决了这个问题。
在我使用 monorepo 的结构后,Vercel 会缓存 monorepo 下的 package,导致我修改文章后无法正确更新。这是由于 Vercel
的构建缓存机制导致的。
目前我的方案是设置环境变量 VERCEL_FORCE_NO_BUILD_CACHE
为 true
,以此使得 Vercel
不缓存构建结果。但在某些情况下,Vercel 还是不会同步 hexo package 的更改。此问题仍未完全解决,且当前的解决方案会整体减缓构建速度,仍需进一步优化。
为此,我在 Vercel 的 GitHub Discussions 中提出了这个问题,但目前还没有得到回复。^4
我使用 ApexCharts 进行图表渲染,这是沿用自 Mantis 的方案。但在 Next.js 中,ApexCharts 的渲染存在一定问题,尤其是在 SSR 方面。
首先,由于 ApexCharts 的 API 使用,其必须在非 SSR 环境下才能正确渲染。因此我需要使用 Next.js 的 dynamic import:
1 | import dynamic from "next/dynamic"; |
且这个引入是在 Client Components 中,我的实际调用应当对这个组件调用而非每次都使用 dynamic import。更何况,尽管图表对于 SEO
的影响不大,将其放到客户端渲染也能减少一些性能开销,但总归是不够优雅的。
其次,由于 RSC 无法传递函数等,而 ApexCharts 的 API 需要传递函数进行 formatting 等工作,因此我也不得不把整个图标的数据部分和渲染部分都放到客户端,使用
API Route 调用数据,然后渲染图表。
由于 Vercel 的静态构建,我无法在 Static 渲染的页面中和带有 POST 方法的 Route 中获取 Hexo 实例。 当我在未使用 SSG 的页面中,Hexo
无法获取到实例,因此我无法获取文章列表。在带有 POST 方法的 Route 中,Hexo 也面临同样的问题。
目前的解决方案是在 dynamic routes (pages) 中使用 SSG,在我的应用场景下这是可行的,也的确提高了 Performance。
而在 Route Handler 中,我通过导出 dynamic
变量来定义组件的渲染模式,同时避免使用 POST 方法。
1 | export const dynamic = 'force-dynamic' // defaults to auto, or 'force-static' |
此外,我还建议在 Response 中定义合适的 Cache-Control
,这可以使 Vercel 缓存页面,减少不必要地计算。
由于我的 Theme 由 shadcn/ui 提供,其使用了 CSS Variables 来定义不同主题(Light / Dark)下的颜色。但 CSS Variables 的改变不会触发
Transition,因此我无法使用 Transition 来实现主题切换的动画。所以你可能看到,我的主题切换是瞬间完成的,没有任何动画来过渡。
我不想使用 JavaScript 操作 DOM 来实现这个动画,而是尽可能使用 CSS 来实现这个动画。目前我还没有找到解决方案。我可能会在未来使用
Style9 替代 Tailwind CSS 的类处理方案,使 Tailwind CSS 仅作为 macro 使用。
1 | /* globals.css */ |
当前我的 UI/UX 以及一些功能还不是很完善,很多已经实现的并不够完美,还有很多需要改进的地方。我也有一些想法,但是由于时间和能力的限制,我并没有实现。我会在未来的时间里继续完善我的博客系统。
当前的 Markdown AST 由 react-markdown
提供,但其并不是我想要的。其内部调用过于复杂,插件难以开发,难以系统性管理文章内容。我可能通过
WASM 引入一些外部库进行部分或整体的解析,来更好地实现目录、Excerpt 自动获取等功能。
近期我正在学习 Rust,我可能会使用 Rust 来编写这个解析器。
当前我使用 Tailwind CSS 来管理 CSS,但其样式系统并不够完美:
因此我可能会使用 Style9 来管理 CSS,其样式系统更加完善,且可以使用 CSS Modules 来管理 CSS。但我需要解决的问题是,如何在
Style9 中使用 Tailwind CSS 的类名。一个可行的方案是 stailwc,但在 Next.js 14 + App
Router 中我还无法正常使用它。^5
在本次项目的开发过程中,我得到了很多人的帮助,我在此向他们表示感谢。
如果没有 Sukka 和 fengkx 的两篇文章,我可能无法完成这次重构。他们的文章为我提供了很多的参考,使我能够更好地完成这次重构。
此外,在样式上我也参考了 Sukka 的博客,尤其是他的 Layout 部分,在此向他表示感谢。
我在开发过程中使用了很多开源项目,他们为我提供了很多的支持,使我能够更好地完成这次重构。
包括为开源社区做出极大贡献的 Vercel,使我能够使用 Next.js、Turborepo 和
SWC,以及免费的构建和部署服务:Vercel
在博客的公开测试时期,很多朋友看到我在各种平台发布的消息后为我提供了很多的意见和建议,使我能够更好地完善我的 UI/UX
Design。在此向他们表示感谢。
大多数应用程序都是由两个或是更多的类或组件通过彼此的合作来实现业务逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。
如果这个获取过程要靠自身实现,那么这将导致代码高度耦合并且难以维护和调试。
比如 A 组件需要调用 B,一般情况下你可能会在 A 的 constructor
或 init
块中显式建立 B 组件然后调用。
1 | class A { |
这种方式意味着 Class A 对 Class B 有一个直接的、编译时刻的依赖。由于 Class A 直接创建了 Class B 的实例,这就导致了 A 和 B
之间的紧密耦合。Class A 不仅需要知道 Class B 的存在,还需要知道如何创建 B 的实例,这可能包括知道 B 的构造函数参数等。这种耦合使得修改、测试和重用
A 和 B 变得更加困难。
如果存在一个基础类 B 需要加入一个参数(Class C)作为依赖,那会出现两种情况:
不幸的是,这种耦合是非常常见的。
1 | package example |
大多数生产环境项目要求编写详细的单元测试,这种耦合会使得单元测试变得困难。因为在测试 A 的时候,你不得不同时测试
B,这就不是一个单元测试了。且如果我们需要注入一个 mock 对象,那么我们就需要修改 A 的构造函数,这种操作违反了开闭原则。
在非 IoC 架构中,类必须自行负责管理它们的依赖关系。
这包括创建依赖对象、管理它们的生命周期以及处理任何依赖相关的配置。这种方式要求开发者对应用程序的结构和依赖有深入的了解。在团队开发中,这种方式会导致团队成员之间的沟通成本增加,且对于开发人员个人能力的要求也会增加。
控制反转通过将对象的创建和绑定的控制权从程序代码转移到外部容器或框架中,从而达到解耦的目的。在传统的程序设计中,程序的流程是由程序本身控制的,而在采用了
IoC 之后,这种控制被反转:对象的创建和生命周期不再由调用者控制,而是由 IoC 容器来管理。
例如使用了 Spring 后,上述的代码可以改为这样:
1 |
|
这种方式意味着 Class A 不再负责创建 Class B 的实例,而是由 IoC 容器负责创建和管理 B 的实例。这种方式使得 A 和 B
之间的耦合度大大降低,A 不再需要知道 B 的构造函数参数,也不需要知道如何创建 B 的实例。这种方式使得 A 和 B 变得更加容易修改、测试和重用。
依赖注入是控制反转的一种实现方式。在依赖注入中,对象的依赖关系不再由对象自己创建和管理,而是由外部容器来创建和管理。常见的依赖注入方式有构造函数注入、属性注入和方法注入。
Spring 的 @Autowired
注解就是一种属性注入的方式。在 Spring 中,你可以使用 @Autowired
注解来标记一个属性,Spring
容器会自动为这个属性注入一个实例。(如上)
Go 中常常提到的依赖注入方法是 Wire[^1] 和 Fx[^2]。Fx 是我常用的依赖注入框架,其通过 fx.Provide
提供构建方法,对 fx.Invoke
提供依赖,实现在全局初始化阶段的依赖注入。Wire 不同于 Fx 的是,它是一个代码生成工具,通过 wire
命令在编译阶段生成依赖注入代码,而 Fx 依赖 reflector,实现了动态注入。Fx 属于上述的构造函数注入方式。
[^1]: Wire: https://github.com/google/wire
[^2]: Go
Fx: https://pkg.go.dev/go.uber.org/fx https://uber-go.github.io/fx/
1 | package example |
此外,Kotlin 中还可以使用 Koin 框架[^3]进行依赖注入,其使用 DSL 语法,对于轻量级的项目非常适用。
[^3]: Koin: https://insert-koin.io
1 | val appModule = module { |
控制反转基本能够解决上述的问题,它通过框架或容器来管理对象的创建和生命周期,从而达到解耦的目的。
但其也有显著的缺陷:
尽管 IoC 带来了许多好处,但它也引入了一定的学习曲线和项目复杂度。选择合适的 IoC 框架或容器,以及合理地设计和实现依赖注入,对于成功利用
IoC 原则至关重要。随着技术的发展,IoC 框架和工具也在不断进化,提供了更多灵活性和更好的性能,使得控制反转成为现代软件开发不可或缺的一部分。
无论是在 Java 的 Spring 框架、Go 的 Fx 框架,还是 Kotlin 的 Koin 框架中,IoC
已经成为实现高质量、可维护和可扩展软件系统的关键技术。通过合理利用这些工具和原则,开发者可以构建出具有更高的可维护性和可扩展性的软件系统。
理解和应用控制反转不仅仅是学习一项新技术,更是一种思维方式的转变。它要求开发者从整体上思考应用程序的架构,关注组件之间的解耦,从而提高软件项目的质量和开发效率。随着时间的推移,掌握控制反转将会成为每个软件工程师技能库中的一个重要组成部分。
类型安全是指同一段内存在不同的地方,会被强制要求使用相同的办法来解释,使开发者可以及早在编译时期就捕捉到潜藏的错误
1 | let num: number = 1; |
在上述情况中,number
类型与 boolean
类型是完全不同的,它们不构成继承关系,因此他们不能相互赋值。
那么,当我们需要将一个 `number`
存储的数字转换为 boolean
变量时,我们该如何改变类型呢?
1 | let num: number = 1; |
显然,因为 Boolean
的 constructor
具备转换 number
到 boolean
的功能,我们只需要把数字放到其参数中进行转换即可。TypeScript
不会认为这是类型错误,你可以在 TypeScript 内置的类型信息中看到这一转换的定义。
1 | // typescript/lib/lib.es5.d.ts |
通过这一点,我们可以得知:
那么,当我们遇到使用集成类型的时候,如何处理子类和父类之间的转换呢?
此时,我们就会用到以下两种型变方法:
Comp<T>
类型兼容和 T
的一致。Comp<T>
类型兼容和 T
相反。Comp<T>
类型与 T
类型双向兼容。Comp<T>
类型与 T
类型双向都不兼容。显然,上文中举的 number
与 string
的例子,就是不变性的体现。
类型继承和多态都是计算机理论基础中的重要一部分,因其过于偏向理论且较为复杂,在此不做过多讲解。
在编程语言理论中,子类型(subtyping)是一种类型多态的形式。这种形式下,子类型可以替换另一种相关的数据类型(超类型,英语:supertype)。也就是说,针对超类型元素进行操作的子程序、函数等程序元素,也可以操作相应的子类型。如果
S 是 T 的子类型,这种子类型关系通常写作 S <: T
,意思是在任何需要使用 T 类型对象的_环境中,都可以安全地使用_ S
类型的对象。子类型的准确语义取决于具体的编程语言中“X 环境中,可以安全地使用 Y”的意义。编程语言的类型系统定义了各自不同的子类型关系。
在编程语言和类型论中,多态(polymorphism)指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型。
参考:https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
协变是很好理解的,子类型继承了父类型,那子类型所包含的信息就一定等于或多于父类型,即父类型所需要的信息子类型必定包含。
比如我们有以下两个 Interface :
1 | export interface Animal { |
在这一实例中我们定义了 Animal
和 Dog
两个类型,其中 Dog
是 Animal
的继承。因为 Dog
继承了 Animal
的所有信息,那么 Dog
的实际类型就等同与这个:
1 | export interface Dog { |
协变很容易理解,如果子类型还不能赋值给父类型,说明这个家庭关系有问题类型系统有问题。
1 | const dog: Dog = { |
如上述代码,当我们把子类型赋值给父类型时,会出现以下情况:
所以型变是实现父子关系所必需的,它在保证类型安全的前提下,增加了类型系统的灵活性。
逆变,即父类型到子类型的转换。我们继续使用 Animal
和 Dog
举例:
1 | export interface Animal { |
可见,上文中我们进行了四个涉及类型的操作:
printDog()
输出 Dog
,成功。printAnimal()
输出 Dog
,成功。printAnimal()
输出 Animal
,成功。printDog()
输出 Dog
,出现类型错误。或许听到这里你会有些混乱。我们知道, Dog
是 Animal
的继承,即 Animal
拥有的 Dog
一定拥有,而 Dog
拥有的 Animal
不一定有。
在这里的 printDog
函数中,如果我们传入一个 Animal
,当访问 object.name
的时候就会因为 Animal
不具备这一属性而出现错误。所以在类型系统中,我们必须禁止这种未经处理的协变。只有我们在对传入数据进行处理以后,才能被称为协变。
1 | interface ConvertAnimalToDog { |
比如在这种情况下,我们为 Animal
补全了 name
,使得它具有 Dog
所拥有的属性,因此可以被转换为 Dog
进而进行 printDog()
操作。这一过程,便被称为逆变。
如上文逆变中所述,在当前的 TypeScript 是不允许父类型直接赋值给子类型这种反向协变的操作的。但是在 TS 2.x
之前支持这种赋值,也就是父类型可以赋值给子类型,子类型可以赋值给父类型,既逆变又协变,叫做“双向协变”。
这明显是有问题的,不能保证类型安全,所以之后 TS 加了一个编译选项 strictFunctionTypes
,设置为 true
就只支持函数参数的逆变,设置为 false
则是双向协变。
在这一文章中我们简要介绍了 TypeScript 类型变化中协变、逆变、双向协变、不变的具体表现,以助于大家更好地理解 TypeScript 为什么是
JavaScript 的超集。
显然,其最主要的功绩还是在于讲一个弱类型语言变成静态强类型语言。在实际开发过程中,强类型语言有助于将错误从运行期转移到编译期,以此减小
Debug 过程消耗的时间。
作为一个强类型语言的坚定拥护者,我建议诸位在前端项目与 Node.js 项目使用 TypeScript,尤其是开源项目,以此达到更高的可维护性。
后续我会另写文章细讲一些 TypeScript 在类型变化中的特性。
]]>SSL 证书作为一个在市场上应用十几年的玩意,任何一个做 Web 相关技术的都不大可能不知道这是个啥。
常见的国内个人站长使用的 SSL 证书基本都是 Let’s Encrypt、 TrustAsia、CloudFlare SSL 等,它们都提供免费的 DV SSL
域名证书。而本文中主要的应用场景是基于 ACME 协议和其实现 acme.sh 进行自动化证书签发。
环境须知
因作者个人爱好原因,本文所提供的命令均为 Ubuntu 22.04 LTS 系统下测试完成的命令,截图也将为此系统环境的截图。
如使用 Debian 等与此兼容的系统环境,可直接使用。
如使用RHEL、Cent OS等不兼容的系统环境,需要自行替换部分目录,例如:apt
做网络,要是连 SSL 都不知道是啥,快去投胎吧……
SSL:一个让你花钱的同时感受到极度的快乐的玩意,尽管你花的钱通常没啥用。
Secure Sockets Layer,即 SSL,中文翻译为 安全套接层。我们目前所使用的被称之为 SSL 加密的并非是原本的
SSL,而是更新更安全的 TLS (Transport Layer Security, 传输层安全性协议)加密。
这是是一种安全协议,目的是为互联网通信提供安全及数据完整性保障。网景公司(Netscape)在1994年推出首版网页浏览器-网景导航者时,推出HTTPS协议,以SSL进行加密,这是SSL的起源。
IETF将SSL进行标准化,1999年公布TLS 1.0标准文件(RFC 2246)。随后又公布TLS
1.1(RFC 4346,2006年)、TLS 1.2(RFC 5246
,2008年)和TLS 1.3(RFC 8446,2018年)。
SSL包含记录层(Record
Layer)和传输层,记录层协议确定传输层数据的封装格式。传输层安全协议使用X.509认证,之后利用非对称加密
演算来对通信方做身份认证,之后交换对称密匙作为会谈密匙(Session key)。
这个会谈密匙是用来将通信两方交换的资料做加密,保证两个应用间通信的保密性和可靠性,使客户与服务器应用之间的通信不被攻击者窃听。
在 Web 应用程序中,我们常将 TLS 搭配 HTTP 协议进行使用(它们之间无耦合),即 HTTP Over TLS。
自动证书管理环境(英语:Automatic Certificate Management Environment,缩写ACME
)是一种通信协议,用于证书颁发机构与其用户的Web服务器之间的自动化交互,允许以极低成本自动化部署公钥基础设施。
该协议由互联网安全研究小组(ISRG)为 Let’s Encrypt 服务设计。
是的,就是第一个做免费 SSL 的神奇非盈利性组织。
Repo: acmesh-official/acme.sh
acme.sh 是通过 bash 对 ACME 协议进行的实现,可以通过调用 ACME Endpoint 生成证书。
我们不会提供 Windows 环境的相关教程
1 | # 安装依赖(Debian、Ubuntu) |
更高级、详细的安装过程请参考:https://github.com/acmesh-official/acme.sh/wiki/How-to-install
acme.sh 的 wiki 提供了一些可供选择的 CA:
CA | MaxLifetime | ECC | Domain Count | Wildcard | IPv4 | IPv6 | NotAfter | IDN | CN |
---|---|---|---|---|---|---|---|---|---|
Let’s Encrypt | 90 | Yes | 100 | Yes | No | No | No | Yes | R3 |
ZeroSSL | 90 | Yes | 100 | Yes | No | No | Yes | Yes | ZeroSSL RSA Domain Secure Site CA |
90 | Yes | 100 | Yes | No | No | Yes | No | GTS | |
Buypass | 180 | Yes | 5 | Paid | No | No | No | Yes | Buypass Class 2 CA 5 |
SSL.com | 90 | Yes | 2 | Paid | No | No | No | Yes | |
HiCA | 180 | Paid | 10 (1 if Wildcard) | Yes | Yes | Yes | No | Yes | Sectigo RSA Domain Validation Secure Server CA |
GTS 证书需注意
申请 GTS 证书需要在 Google Cloud 获取对应
Token,请参考:Automate Public Certificates Lifecycle Management via RFC 8555 (ACME) & Google Public CA
你可通过 --server <acme_endpoint>
指定CA,例如:
1 | acme.sh --issue \ |
1 | # 文件验证签发证书 |
--issue
指定要执行的操作是签发证书。-d <domain>
指定要包含的域名,此处可以包含多个域名,若包含不支持的域名会有报错提示。--webroot <path>
指定 web 服务器的根路径,你也可以不使用这项而选择使用 --standalone
让 acme.sh指定 --webroot
后,脚本会访问 <path>/.well-known/
创建验证文件,请确保其用户权限和 Web Server 权限配置正确。
随后你可以在以下位置找到你的证书文件等信息(当然你也可以后续使用自动安装证书):
~/.acme.sh/<domain>/fullchain.cer
全链证书公钥(部署请使用这个)~/.acme.sh/<domain>/<domain>.csr
单域名公钥~/.acme.sh/<domain>/ca.cer
CA证书公钥~/.acme.sh/<domain>/<domain>.key
证书私钥(部署请使用这个)首先,acme.sh 会请求 pki 获取对应域名需添加的 txt 记录:
1 | acme.sh --issue --dns \ |
第一步操作反馈
而后,你需要等待 txt 记录添加并解析完成,然后执行下一步操作:
1 | # 注意这里是 renew |
第二步操作反馈
随后你可以在以下位置找到你的证书文件等信息(当然你也可以后续使用自动安装证书):
~/.acme.sh/<domain>/fullchain.cer
全链证书公钥(部署请使用这个)~/.acme.sh/<domain>/<domain>.csr
单域名公钥~/.acme.sh/<domain>/ca.cer
CA证书公钥~/.acme.sh/<domain>/<domain>.key
证书私钥(部署请使用这个)需要注意的是,dns 方式的真正强大之处在于可以使用域名解析商提供的 api 自动添加 txt 记录完成验证。
acme.sh 目前支持 cloudflare, dnspod, cloudxns, godaddy 以及 ovh
等数十种解析商的自动集成,具体请自行查阅 https://github.com/acmesh-official/acme.sh/blob/master/dnsapi/README.md
前面证书生成以后,接下来需要把证书复制到真正需要用它的地方。
默认生成的证书都放在安装目录下: ~/.acme.sh/
,请不要直接使用此目录下的文件,例如:不要直接让 Nginx / Apache
的配置文件使用这下面的文件。这里面的文件都是内部使用,而且目录结构可能会变化。
正确的使用方法是使用 --install-cert
命令,并指定目标位置,然后证书文件会被copy到相应的位置。
1 | acme.sh --install-cert -d ssl-test.ah-dark.tech \ |
这里用的是 service nginx force-reload
而不是 service nginx reload
。
据测试,reload
并不会重新加载证书,所以用的 force-reload
。
Nginx 的配置 ssl_certificate
使用 /etc/nginx/ssl/fullchain.cer
,而非 /etc/nginx/ssl/<domain>.cer
,否则 SSL Labs 的测试会报 Chain issues Incomplete
错误。
--install-cert
命令可以携带很多参数,来指定目标文件。并且可以指定 --reloadcmd
,即证书更新后执行的命令。
详细参数请参考: https://github.com/Neilpang/acme.sh#3-install-the-issued-cert-to-apachenginx-etc
值得注意的是:这里指定的所有参数都会被自动记录下来,并在将来证书自动更新以后被再次自动调用。
1 | acme.sh --install-cert -d example.com \ |
对于已签发的证书,可以通过下述命令查询其证书信息:
1 | acme.sh --info -d example.com |
使用上述安装方法安装的 acme.sh 会自动添加 crontab 条目。
你也可以自行添加:
1 | 15 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null |
目前 acme.sh 提供的支持 IP SSL ACME 签发的CA仅有 HiCA。
IP SSL
请务必注意,IP SSL仅能使用 PTR 反向查询记录 和 文件验证 进行签发,本人不清楚 acme.sh 是否支持PTR验证,因此建议使用文件验证。
]]>在近期架构方案制定中,出口流量的分配成为一个显著的重要的问题,而最好的解决方式就是网络代理。通过Clash分流以实现对不同目标的流量分流,搭配NAT网关以配置更优的VPC。
Clash 是一个由 Golang 撰写的网络代理程序,支持ShadowSocks/SSR节点,支持以http/socks5协议进行代理。
得益于 Golang 的高性能和跨平台开发能力,Clash 对于各个系统均有Release,因此你可以轻松地在Linux 终端运行和配置 Clash。
本章节将讲述详细的配置步骤。
在 CentOS 与 Ubuntu 系统均实测可正常使用,其他系统请自行根据教程步骤加以修正更改。
你可以在 Clash 的 GitHub Release 页面获取编译好的 Clash 软件。
1 | # 下载当前操作系统与 CPU 架构对应的包文件 |
下载好后解压安装包中 clash 到 /usr/local/bin/
目录下,以此在全局环境运行软件,并删除压缩包文件。
1 | gzip -dc clash.gz > /usr/local/bin/clash |
创建配置文件目录,并下载 MMDB 文件。
MMDB文件可以提供IP与地理位置相对应的数据信息,可以更精确地确认IP所对应地域,以此提高网络分流的效率和质量。
1 | mkdir /etc/clash |
创建 systemd 脚本,以使程序持久化运行。
1 | vi /etc/systemd/system/clash.service |
内容如下
1 | [Unit] |
重载 systemd
1 | systemctl daemon-reload |
首先你需要将自己的 yaml 配置文件上传至服务器的 /etc/clash/config.yaml
文件中,例如可以通过 wget 下载网络上的配置文件。
1 | wget -O /etc/clash/config.yaml https://example.com/config.yaml |
设置系统代理,添加配置文件 /etc/profile.d/proxy.sh
并在其中写入如下内容
1 | # 进入文件 |
1 | # 写入内容 |
重载 /etc/profile
配置
1 | source /etc/profile |
启动 Clash 服务,并设置为开机自启动
1 | systemctl start clash |
在大多数的预安装的 Linux 操作系统中均可以使用 curl ,你可以通过 curl 测试
1 | curl www.google.com |
GitHub CI 的功能使得代码推送到 GitHub 仓库时,能够自动构建 Docker 镜像并推送到
GAR。这种自动化流程有助于提高了开发效率,同时还可确保在发布新版本时代码和镜像的一致性和可靠性。
本文将为您提供详细的步骤和指南,帮助您轻松实现自动构建和推送 Docker 镜像的流程。
在本章中,我们将简要介绍 Google Artifact Registry 和 IAM 的 Service Account 授权模式的概念和特点,以便读者更好地理解如何使用它们来构建和管理
Docker 镜像。我们还将介绍 Artifact Registry 和 IAM Service Account 之间的关联,并讨论如何创建和配置 Service Account,以便在使用
Artifact Registry 时获得安全和便利的身份验证和授权体验。
Google Artifact Registry 是 Google Cloud Platform (GCP) 提供的一种全托管的 Docker 镜像存储库服务,它可以帮助开发者更好地管理和分享
Docker 镜像。Google Artifact Registry 支持 Docker、Maven、npm 和 NuGet 等常用的软件包管理工具,可以帮助开发者将应用程序构建和部署过程更好地集成到
DevOps 流程中,从而提高开发效率和软件交付速度。
与其他 Docker 镜像存储库服务不同,Google Artifact Registry 与 GCP 集成紧密,可以使用 GCP
的身份验证和授权机制来管理和保护镜像的访问。这为企业级的应用程序提供了更高的安全性和可靠性保障。此外,Artifact Registry
支持多个区域的镜像存储,以提供更好的地理位置灵活性和应用程序的可用性。
使用 Google Artifact Registry,开发者可以轻松地上传、管理和分享 Docker
镜像。它还支持版本管理,开发者可以根据应用程序版本来管理镜像,并轻松地回退到之前的版本。此外,Artifact Registry 还提供了一个易于使用的
UI 界面,使得开发者可以更直观地管理和浏览镜像。
除了基本的镜像存储和管理功能外,Google Artifact Registry
还提供了一些其他的高级功能,如容器镜像签名和高速并发拉取等,以满足不同应用场景下的需求。其中,容器镜像签名功能可以帮助开发者更好地保护镜像的完整性和安全性,而高速并发拉取则可以帮助大规模应用程序的快速部署和扩展。
总之,Google Artifact Registry 是一种功能强大的全托管 Docker 镜像存储库服务,它与 GCP 集成紧密,支持多个区域的镜像存储和管理,并提供了易于使用的
UI 界面和多种高级功能,可以帮助开发者更好地管理和分享 Docker 镜像,并加速应用程序的构建和部署过程。
Google Cloud Platform (GCP) 的 Service Account 授权模式是 GCP 身份验证和授权机制的一种重要形式,它允许用户为应用程序创建一种特殊的账号,以便应用程序可以访问和管理
GCP 资源。与其他授权方式相比,Service Account 模式具有更高的安全性和可控性,可以帮助用户更好地保护应用程序的机密信息和敏感数据。
在 GCP 中,每个项目都有一个默认的 Service Account,用户可以为这个 Service Account 分配不同的角色,以便在访问 GCP
资源时获取相应的授权。例如,用户可以为 Service Account 分配“阅读”、“写入”、“编辑”等不同的角色,以限制其访问和管理资源的范围。此外,用户还可以创建自定义的
Service Account,并将其分配给特定的应用程序或服务,以实现更精细的授权管理。
使用 Service Account 授权模式,用户可以通过各种方式访问和管理 GCP 资源,如 Google Cloud Console、gcloud 命令行工具和 GCP
API 等。此外,Service Account 还支持 OAuth2 和 OpenID Connect 等标准身份验证协议,以便用户可以更好地集成 Service Account
到现有的身份验证和授权机制中。
需要注意的是,在使用 Service Account 授权模式时,用户需要为 Service Account 配置合适的角色和权限,并将其与 GCP 资源建立关联。
总之,Google Cloud Platform 的 Service Account 授权模式是一种强大而灵活的身份验证和授权机制,可以帮助用户更好地管理和保护
GCP 资源。通过为应用程序分配不同的角色和权限,并与 GCP 资源建立关联,用户可以实现更精细的授权管理,提高应用程序的安全性和可靠性。
在 Kubernetes 中,自定义 Docker 镜像是一种常见的应用程序打包和部署方式。在使用自定义 Docker 镜像时,通常需要将这些镜像上传到某个镜像存储库中,以便在
Kubernetes 集群中进行部署。然而,这种方式也带来了一些安全问题,如镜像的安全性、可用性和可靠性等方面的问题。
而对于诸多问题,GAR 均提供了较好的解决方案。作为 Google Cloud Platform(GCP)的一项全托管服务,GAR 提供了丰富的功能和工具,以帮助用户更好地管理和保护
Docker 镜像,从而提高应用程序的安全性、可用性和可靠性。
GAR 提供了丰富的功能和工具,以帮助用户更好地管理和保护 Docker 镜像。通过使用 GCP
的身份验证和授权机制、多个区域的镜像存储、高级功能和易于使用的界面,用户可以更好地保护和管理镜像,并提高应用程序的安全性、可用性和可靠性。
GitHub CI 可以用于自动化构建、测试和推送 Docker 镜像,并将其保存到 Google Artifact
Registry(GAR)等镜像存储库中。这种自动化流程可以大大提高开发效率,并确保在发布新版本时代码和镜像的一致性和可靠性。
使用 GitHub CI 部署 Google Artifact Registry(GAR)可以帮助用户更好地集成 DevOps 流程,并实现自动化构建和部署。下面是 GitHub
CI 部署 GAR 的操作内容:
actions/checkout
签出仓库google-github-actions/auth
生成 Service Account 的临时 Tokendocker/login-action
登录到 GAR上述是 GitHub CI 的主要内容,而我们所需做的是在 GCP 为其配置 OIDC 等授权服务。
在这里我们选择通过 GitHub Action 的 OIDC 生成临时 Token 而非持久性 Token 的主要原因是避免因数据泄露导致的服务信息流出等,是保证服务安全性的体现。
此部分内容取材自 google-github-actions/auth
的 README,有进行部分修改。
在此之前,你需要对你的 gcloud cli 进行配置。你可以使用 gcloud auth login
命令进行登录:https://cloud.google.com/sdk/gcloud/reference/auth/login
为了方便后续操作,我们会将一些重复使用的定义为临时变量。这些变量会在 Bash 关闭后自动删除,您无需担心。需要注意的是,此命令仅可用于
Linux 系统,因此您无法使用 cmd 等工具跟随教程。
1 | export PROJECT_ID=ci-project |
首先,创建或使用现有的 Google Cloud 项目。 您必须有权创建工作负载身份池、工作负载身份提供者以及管理服务帐户和 IAM 权限。
我们需要创建一个 GCP Service Account。 如果您已有 Service Account,请记下其电子邮件地址并跳过此步骤。
1 | gcloud iam service-accounts create "${SERVICE_ACCOUNT}" \ |
创建一个 Workload Identity Pool,用于管理 GitHub Action 在 Google Cloud 权限系统中的角色:
1 | gcloud iam workload-identity-pools create "${WORKLOAD_IDENTITY_POOL}" \ |
而后通过命令获取 Workload Identity Pool 的标识:
1 | gcloud iam workload-identity-pools describe "${WORKLOAD_IDENTITY_POOL}" \ |
将输出内容保存为一个新的变量:
1 | export WORKLOAD_IDENTITY_POOL_ID=whatever-you-got-back |
创建一个在池内为 GitHub 访问提供服务的 OIDC Provider:
1 | gcloud iam workload-identity-pools providers create-oidc "${WORKLOAD_PROVIDER}" \ |
允许基于您的存储库的 GitHub Action 通过提供程序登录到服务账户:
1 | export REPO=my-username/my-repo # 你的仓库名,需要指定大小写 |
向 Google 请求返回该提供程序的标识符:
1 | gcloud iam workload-identity-pools providers describe "${WORKLOAD_PROVIDER}" \ |
随后你会得到一个不同于上述 WORKLOAD_IDENTITY_POOL_ID
的标识符,它应当是更长的。你需要保存这个字符串,后续我们会在 GitHub
Action 用到它。
最后,我们需要确保在开始时创建的服务账户有权限操作 Google Artifact Registry。使用上述命令,为该服务账户添加适当的 IAM
角色和权限,使其能够访问和管理 Google Artifact Registry 中的资源:
1 | gcloud projects add-iam-policy-binding $PROJECT_ID \ |
为了验证是否成功,你可以向 Google 请求打印出分配给服务账户的权限:
1 | gcloud projects get-iam-policy $PROJECT_ID \ |
前往 Google Cloud Platform 的 GAR
控制台:https://console.cloud.google.com/artifacts
选择上方按钮新建代码库,选择 Docker 并指定区域,在本文中我们的示例使用 us-west-2
。
新建 Repository
你需要在 .github/workflows
文件夹中添加一个新的 YAML 文件,请注意替换示例中给予的信息。
1 | name: Docker Release |
此文件创建后,应当会发起一个 GitHub Action 。如果此任务成功推送,那么本文章的主要任务便结束了。
成功推送镜像
本段讲述如何从 Google Artifact Registry 中拉取或推送 Docker 镜像。使用 GAR 中的镜像可以方便地管理和部署 Docker
镜像,提高应用程序的安全性、可用性和可靠性。
使用 GAR 中的镜像可以通过 Docker 客户端来完成。首先,用户需要在 GCP 控制台上获取 GAR 的凭据(例如身份验证令牌),以便可以使用
Docker 客户端连接到 GAR。然后,用户可以使用 Docker 客户端拉取或推送 Docker 镜像到 GAR 中。
具体来说,用户需要使用以下命令从 GAR 拉取镜像:
1 | docker pull [REGION]-docker.pkg.dev/[PROJECT-ID]/[REPOSITORY-ID]/[IMAGE]:[TAG] |
其中,[REGION] 是 GAR 的区域,[PROJECT-ID] 是 GCP 项目 ID,[REPOSITORY-ID] 是 GAR 仓库的 ID,[IMAGE] 是镜像名称,[TAG]
是镜像的标签。
用户还可以使用以下命令将 Docker 镜像推送到 GAR 中:
1 | docker push [REGION]-docker.pkg.dev/[PROJECT-ID]/[REPOSITORY-ID]/[IMAGE]:[TAG] |
在这个命令中,用户需要指定要推送的镜像和其对应的标签。
总之,使用 GAR 中的镜像可以通过 Docker 客户端完成。通过获取 GAR 的凭据、使用 Docker 客户端拉取或推送镜像,用户可以方便地管理和部署
Docker 镜像,并提高应用程序的安全性、可用性和可靠性。
由于 GKE 使用的节点服务 Compute Engine 默认使用的角色具有对 GAR 的只读权限,你可以直接将其当作无身份验证的 Dockers 拉取镜像。
你只需要照常在容器组的 Image 部分填入镜像地址,如 asia-east2-docker.pkg.dev/ahdark-services/ahdark-services/ahdark-blog
,即可正常拉取镜像。
本文详细介绍了如何使用 GitHub Action 和 Google Artifact Registry 将 Docker 镜像自动推送到 GAR 中。
首先,我们介绍了 GAR 的概念和特点,以及 GCP 中的 Service Account 授权模式。
接着,我们详细讲述了如何创建和配置 Workload Identity Federation,以及如何使用 GitHub CI 实现自动推送 Docker 镜像到 GAR 中。
最后,我们提供了如何使用 Docker 客户端从 GAR 中拉取或推送 Docker 镜像的步骤。
通过本文的指南和步骤,用户可以快速和方便地将 Docker 镜像自动推送到 GAR 中,实现镜像的版本管理和共享,并提高应用程序的安全性、可用性和可靠性。在实际的应用开发中,使用
GitHub Action 和 GAR 的自动化流程可以大大提高开发效率,帮助用户更好地实现 DevOps 流程。
在近期,我逐步将近乎所有的服务所使用的邮件服务都更换为 Mailgun,本文我将讲述我作出这一决定的原因和具体做法。
在确定使用 Mailgun 以前,我曾对SMTP邮件推送这一部分有过多种尝试。
在我仍在职圆云的时候,我们依靠阿里云的企业邮箱进行企业事务处理。
企业邮箱同时兼具收发功能,具有较好的稳定性,但价格较高。对于个人服务来说,买五个账号起步,每年600元的企业邮箱,似乎毫无必要。
而腾讯企业邮箱等服务依赖企业微信,过于冗杂,对于个人来说过于麻烦,而且 Exchange 协议支持的不完善。
因此,我放弃这一想法。
我曾在圆云时期使用阿里云的邮件推送进行邮件通知。其好处在于,稳定性较高,且支持多域名多用户等情况。
但其只提供了邮件发送服务,完全不对邮件接收做支持,甚至不支持转发,因此在使用一段时间后我放弃了这一方案。
ahdark.com 在很长一段时间均使用阿里云邮件推送进行通知,单独设立域名 notify.ahdark.com
Microsoft Exchange 算是一个可自定义性较高的解决方案,依赖 Microsoft 企业服务。
使用开发者 E5 套餐和基础商业套餐都可以自由使用这一服务,成本可以控制在较为合适的区间内。
不仅如此,它还同时提供接收和发送邮件的功能,使得我的域名邮箱服务无需单独设立。
但缺点在于,其高度依赖微软的企业用户服务,而 login.live.com 的难用程度众所周知。
而且这一方案因服务器在国外,无法将邮箱挂在 Windows 10 和 Windows 11 的 Mail 应用程序,在系统级接收邮件变得较为困难。
因此我在使用一段时间这一方案后逐步进行更换,现仅保留 sourcegcdn.com 与 sourcegcdn.net 在使用这一方案。
这一服务提供域名邮箱的邮件转发,功能不多但比较有效。
在 ahdark.com 使用阿里云邮件推送进行发件时,我也使用 Forward Mail 服务进行邮件的接受服务。
我将对应账号名的邮件转发至 ahdark@outlook.com (即我的主邮箱),以此实现对域名邮箱的接收。
但因其没有提供发件服务,我只得使用代理发送。
后续我使用 Mailgun 同时替代了这一服务和阿里云邮件推送,因此被弃用。
Mailgun 是一个专为电子邮件提供服务的公司,其主体为 Mailgun Technologies,
Inc.,服务站点位于 https://www.mailgun.com。
其产品囊括了邮件的发送、转发、跟踪。支持SMTP、API两种邮件发送方法;WebHook、Forward两种邮件转发方法,和较为有效的跟踪方法。
因其较好的名誉和较高质量的服务,我在近期开始使用这一服务。
Mailgun 的注册需要以下信息:
我使用自己持有的 MasterCard 虚拟卡和虚拟手机号码进行注册
注册后会开启一个30天的使用,称作 Foundation Trial 套餐。在其阶段你可享受以下服务:
30天后会自动转换为 35美元/月 的 Foundation 50k 套餐,即 50k 邮件收发/月。
注册后,你可以在 Billing 页面进行 Downgrade 以降级到按量付费(即用即付)的Flex套餐。此套餐没有月固定付费,提供 1000 条邮件收发/月
的免费额度和 1000 条额外邮件收发/1美元 的即用即付模式。
注册后,你可以在后台添加域名。
Mailgun 会赠送一个 Sandbox 域名用于测试,你也可以添加自己的域名。
添加域名需要对域名添加DNS解析:
记录名 | 记录类型 | 记录值示例 |
---|---|---|
@ | TXT | v=spf1 include:mailgun.org ~all |
mx._domainkey | TXT | k=rsa; p=<string> |
@ | MX | 10 mxa.mailgun.org |
@ | MX | 10 mxb.mailgun.org |
具体请参考添加时说明。
添加域名后,你可以在 Sending - Domain Settings - SMTP credentials 找到 SMTP 凭据的设置项。
设置凭据后,你会得到对应账号的密码。
而后可使用以下凭据登录:
smtp.mailgun.org
no-reply@ahdark.blog
<your-password>
你可在 Receiving
里设置邮件接收的处理方法,支持邮箱转发和WebHook两种方法,具体可参考文档配置:https://documentation.mailgun.com/en/latest/user_manual.html#routes
Mailgun 官方制作了 Mailgun for WordPress 这一插件,也是我所使用的方法。
首先需在 Mailgun 后台生成 API 凭据,可在 Sending - Domain Settings - Sending API keys 找到。
生成后,可凭 Mailgun 添加的域名和 API Key 作为登陆凭据,只需在 WordPress 的 Mailgun 插件设置页面填入。当然,也需完善相关信息。
完成配置后,可在底部点击保存,而后点击 Test Configuration 按钮进行测试。
以上便是本篇文章的全部内容。
之所以没有考虑自建SMTP的原因主要就是因为发件收件的不稳定,且几乎全部国内云服务厂商都封禁了25端口或严格限制发件服务器架设。
正是因此,国内服务器自建SMTP的法子基本被封死,我也因此写了本文。
在最后,只希望邮件服务不会如同短信服务一般,被滥用作为恶意攻击他人的工具。
]]>在当前互联网架构圈子,容器化、微服务,成为被主要讨论的话题。
显然,对于互联网的发展趋势来看,项目正在变得愈加庞大。单一的巨大的项目是不利于维护和开发的,开发者的电脑几乎难以直接承载阿里云、Azure这种巨大的项目,那我们就需要对其进行解耦。也就是说,应用的组件化、容器化、微服务化必将成为趋势。
在容器化应用程序开发这一点,我在之前的开发历程中有些许心得,在本文我将对其具体阐述。
在开始学习 Golang 之前,我曾尝试过很多语言。
就如我的个人简介所写的一样,较为经典的PHP、Java,较为复杂的CPP,较为有趣的Express.js、Koa.js、Next.js,我都曾使用过。
即使是现在,你也可以在我的GitHub账号找到许多之前的练习品,如 AH-dark/bing-image-api。
但这些语言或多或少都有一些不足之处,因而被我放弃。
Spring 全家桶在中国大陆被广泛地使用,特别是阿里巴巴,几乎可以算是 Spring 的最主要用户之一。
依靠阿里巴巴,SpringBoot 几乎成为每一位中国Java工程师的必修课。
久闻其名,我用约两周时间构建了一个 Java SpringBoot 应用程序,随即抛弃了它。AH-dark/Link
作为一个 Java 程序,即使被编译为字节码,其巨大的体积也是令人望而却步的。
更何况 Java 作为一个基于 VM 的跨平台语言,依赖 JVM 而运行,需要在系统安装一个庞大的 Runtime。在这种情况下,打包的 Docker
Image 体积会变得极其庞大,但这和你的代码完全没有关系。JVM 运行的应用程序也会占用大量内存,这显然不利于容器化,因为单一应用对于内存的占用太大,而这并不必要。
而最令人难以接受的,是其启动时间。即使是一个极其微小的 Spring Boot 应用程序也需要至少5秒左右的冷启动时间,对于不持续运行的
RPC 链,这一环可能造成严重的卡顿。这也是我放弃它的主要原因
PHP 是一个动态类型的脚本语言,既无法被打包成字节码,也无法以一个优雅的方式封装成类似 Jar 的应用程序。
Docker Image 中的 PHP 需要在 Image 内安装各种扩展和 PHP FPM,这是不可接受的。
这往往意味着,在生产环境中,一个Kubernetes集群需要跑几十甚至几百个PHP FPM和其扩展,而不可复用,显然这极大地浪费了运算资源。
我并没有在我的GitHub公开发布使用CPP撰写的Web应用程序,因为我现有的CPP应用程序基本都具有一个极其低下的完成度。
作为一个内存不安全的语言,CPP 应用程序对于撰写者有很大的要求,同时 CPP 项目的开发周期也不是一般的长。
再者便是跨平台相关,CPP 的跨平台可以说是很难处理的,而在容器化应用程序中还是存在一定的跨平台需求的。
综上三条,我没有考虑继续使用 CPP 撰写容器化应用程序。
Node.js 曾被我用于撰写 Source Global CDN 的多层RPC链路中间件。
JavaScript 中几乎所有元素都可以以 Object(对象)呈现,也正是因此我对其较为喜爱。而加有静态类型强制的 TypeScript
,是我在生产环境广泛应用的语言(尤其是前端,得益于 React 对 TypeScript 的完美支持,在 React 前端使用 TypeScript
在我看来是一个很享受的事。)。
在使用 JavaScript、TypeScript 和 Express.js、Koa.js 撰写了几乎所有 Source Global CDN 中间件后,我意识到一个问题:封装了
Node.js 的 Docker 镜像在冷启动方面似乎不具备优良的表现。
我也是从这时候开始关注应用程序冷启动速度的。
在 ServerLess 平台,Docker Image 的调用似乎往往是 调用Docker镜像 -> 运行 -> 发送 Http Request。
而在Node.js应用程序中,因封装Node.js Runtime而变得庞大的Image就很难在500ms以内快速运行镜像。
正是因此,Source Global CDN 在前期不得不引入一层对象存储来对文件进行持久化缓存,以避免因冷启动而导致的卡顿。
后续,我逐步对其中间件进行聚合和重写,重写的语言便是接下来的重头戏——Golang。
首先,我们需要对其进行一定的了解。
Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
罗伯特·格瑞史莫、罗勃·派克及肯·汤普逊于2007年9月开始设计Go,稍后伊恩·兰斯·泰勒(Ian Lance Taylor)、拉斯·考克斯(Russ
Cox)加入项目。Go是基于Inferno操作系统所开发的。Go于2009年11月正式宣布推出,成为开放源代码项目,支持Linux、macOS、Windows等操作系统。在2016年,Go被软件评价公司TIOBE选为“TIOBE 2016年最佳语言”。
目前,Go每半年发布一个二级版本(即从a.x升级到a.y)。
Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能。Go的并行计算模型是以东尼·霍尔的通信顺序进程(CSP)为基础,采取类似模型的其他语言包括Occam和Limbo,Go也具有这个模型的特征,比如通道传输。通过goroutine和通道等并行构造可以建造线程池和管道等。在1.8版本中开放插件(Plugin)的支持,这意味着现在能从Go中动态加载部分函数。
与C++相比,Go 并不包括如枚举、try-catch 异常处理、继承、虚函数等功能,但增加了切片(Slice)
、泛型、并发、管道、垃圾回收、接口等特性的语言级支持。对于断言的存在,则持负面态度,同时也为自己不提供类型继承来辩护。不同于Java,Go原生提供了关联数组(也称为哈希表(Hashes)或字典(Dictionaries))。
引用自 Go- Wikipedia
Google 设计 Go 的最初目的便是提高在多核、网络机器、大型代码库情况下的开发效率。也是因此,其性能较好,且对 Runtime 的需求也是较少的。
同时,Go内置了一个轻量的协程,即 _Goroutine_,实现了基本的并发需求。
不同于Java、Node.js,Go在容器化有一个天然的优势,即编译型语言。
Go应用程序编译后的体积可以减小到MB级,不需要内置庞大的Runtime,只需一些系统环境即可运行。打包后的 Golang Application Image
通常都是极小的。
上方为 Golang 镜像,下方为 Node.js 镜像
虽然 Go 很多提案都很扯淡且没有建设性,但 Go 仍然是一个功能性强且统一化的语言。
你在不同的 PHP 项目所见到的 HTTP Client 可能是不同的,比如有人用 curl,有人用 file_get_content 函数。但在 Go 中,通常大家都会选择使用
builtin 的 http 模块。这是一个好处,因为这减小了因规范不同而出现错误的可能性。
Go可以做到在内部调用C语言程序、Rust程序、Assembly程序,这使得Go可以通过引用其他语言来弥补自身的一些性能上的劣势。
不同于 Java 的是,Go 不仅无需 VM 环境,而且内存占用极低,不需要将程序运行在 VM 中。编译后的可执行文件体积也很小,不同 Java
一般字节码体积巨大。
Go 应用程序对我来说最大的优点在于冷启动。Source Global CDN 重写后的 Go 后端程序的冷启动时间提高了约
3000%。这是一个巨大的提升,也可以显著地改善用户体验。这也是我选择 Go 作为 Source Global CDN 项目日后主要开发语言的主要原因。
说 C++ 的请想想开发周期和开发难度。
Go 将会是我未来一段时间主要使用的语言,因为它大多数设计基本都是符合我审美观念的。
泛型的傻逼设计,还有未来的手动内存控制,就不要说了。
同时,我也推荐诸位尝试使用 Go,尽管其部分设计是非常愚蠢的,但总体上来看还是值得一试的。
我才不会说我马上要开始深入学习 Rust
将以下代码复制到 /wp-content/plugin/qq-avatar.php
即可
1 |
|
你可能也看出来了,这个会替换所有使用qq.com后缀的邮箱的头像
为此我的方法是通过Gravatar API进行判断,但这严重影响了站点的加载速度
所以,先用White List凑活吧……
我还在想更好的解决方案,实在不行就前端替换大法了,反正有comment-id