这本书的两位作者少楠和白光,还做了一个笔记小工具 flomo ,我恰好是 flomo 的重度用户,笔记工具的创始人来写一本关于笔记的书,我认为是再合适不过了。
下面是读了这本书的一些记录:
1、记笔记是为了增援未来的自己,我对书中这个观点印象非常深刻,意思就是当未来遇到问题时,我们需要解决方案,而过往的笔记能给我们带来线索、思考、甚至直接的答案。
2、记笔记重要的是思考,而不是记录。所以,用笔来记录还是用软件来记录就显得没那么重要了。这个观点也能让我们避免成为工具党,曾经一段时间研究各种工具、研究各种知识管理的工作流,沉醉其中,却忘记了最重要的是思考。
3、既然思考是重要的,那么有些时候那些并不怎么自动化的方式反倒会带来帮助。例如:剪藏功能、自动 AI 总结功能。并不是说这些功能没用,而是太自动化,看似节省了时间,同时把思考的时间也节省掉了,最终留下一堆信息垃圾。
4、卢曼说,「不写,就无法思考」。我们看的各种文章、书籍、在线课程,不记录,看过就会忘,即使记录,思考,虽然也会忘,但能将信息转化为自己的知识。通过定期对过往笔记进行精炼和取舍,也会倒逼我们去思考。
5、哪些东西应该记录呢?通过搜索引擎能很容易查询到的不用记录,我们需要记录的是个人的感悟、兴趣、思考,这些是独一无二的,属于自己的知识财富。不过搜索引擎能很容易查询到的也需要使用一些手段和方式进行归档和存储,方便日后查询,网页也有链接失效的时候。
6、记录笔记我认为有两类:有准备和无准备,看一本书、一篇文章,边看边想边记录,这种就是有准备的,另一类是突然的闪念,这种闪念对每个人来说都非常珍贵,可能是极具创意的解决方案,也可能是看待事物的新视角,当出现的时候,一定得马上记录,否则稍纵即逝。
7、还有一类是操作类型的,比如某某工具的安装使用、一些新技术的尝试等,虽然这些也都能在网上找到答案,但网络上的信息参差不齐,你可能经过了各种尝试,踩坑、查阅,最终搞定,那么这个过程一定要记录下来。我通常的做法就是在这个过程中,及时记录、截图,事后再进行整理成一篇完整的文档。
8、之前看二爷的一篇文章,有一句话印象深刻「看书的时候一定要随手记录一点感想,哪怕只是:牛逼、我不认同、原来还能这样… 也可以。」当我们有这样的一些方法时,说明书中的内容对我们是有启发的,可能是一些精彩的观点、也可能是绝妙的论述,哪怕是不认同,肯定也是和自己的某个观点进行了对比,这些都是可以记录的时刻。
9、当笔记记录比较多了的时候,就需要分类,分类是为了让「自己」更容易找到信息,而不是别人。分类的形式不重要,不管是标签、文件夹、还是 IAPR ,选择一个自己习惯和熟悉的使用就好。
10、网上有很多他人的文件夹、标签分类法,但好用的标签或分类不是机械照搬的,而是相对主观的,这里的主观并不要求你必须标新立异,而是方便你提取和使用就好。毕竟,创建标签不是目的,为你所用才是目的。
11、另外需要记住的是,分类方法不是一次成型的,而是动态生长的。这就像是做软件架构一样,好的架构也是慢慢迭代演进出来的。随着我们关注点的迁移、知识的积累,就会出现分类的合并、新增和删减。
12、当我们形成了记录的习惯,假以时日,就会积累越来越多的笔记了,这时,需要做的一件事情就是「回顾」,回顾可以分为三个步骤:
13、回顾的过程中,可以看看同个分类下的其他内容,没准又能产生新的想法和灵感,可以添加新的记录;有些觉得分类不合适的可以进行调整;有些已经过时的就及时删减,这个手动的过程是不可缺失的,手动意味着预留了思考的时间。
14、当记录已经成为一种习惯之后,需要警惕的是不要信息成瘾,要打破信息茧房,知道自己想要什么,不想要什么。我们要占据主动,不能被信息牵着鼻子走。一个经典的反例就是:刷抖音。
15、信息很多,缺乏的是专注力。这个专注力体现在:做减法、做加法、做乘法
16、想清楚目的,记笔记只是一种手段,目的是支援未来的自己,很多时候都喜欢将手段当成了目的。在软件开发中也是一样 ,客户经常会针对一个需求就提出了自己的解决方案,而不去描述目的,最后反而达不到目的。
17、遗忘是一件非常正常的事情,通过回顾持续刺激,是对笔记进行处理的重要方法。它可以帮我们做好两重准备:
18、本文也是笔记的一种呈现,或许几个月后再回顾,会对内容进行较大的调整,也可能觉得这些已经都不重要了。但可以肯定的是,曾经的这些记录在多次回顾之后,已经在进化、迭代、变成对自己有用的知识,储存在大脑的某个位置,随时等待着未来某个时刻的调取。
]]>我们如果需要部署一个私有镜像仓库来使用,最简单的就是 registry ,一行命令就可以运行在 Docker 中,但功能也比较弱,如果想要私有镜像仓库功能更丰富些,可以使用 Harbor 。
本文简单介绍下 Harbor 的安装和使用。
1、如果没有安装 wget ,先执行下面命令安装:
1 | yum install -y wget |
2、下载包:
1 | wget https://github.com/goharbor/harbor/releases/download/v2.9.1/harbor-offline-installer-v2.9.1.tgz |
如果无法通过 wget 进行下载,可以直接到 Github 网站:https://github.com/goharbor/harbor/releases/ 进行下载,然后拷贝到服务器中:
3、执行下面命令进行解压:
1 | tar -xvf harbor-offline-installer-v2.9.1.tgz |
4、执行下面命令新建目录,并将程序文件复制到目录中:
1 | mkdir /opt/harbor |
5、修改 Harbor 配置文件:
1 | cp -ar harbor.yml.tmpl harbor.yml |
6、编辑完配置文件,接下来在 harbor
目录下安装 Harbor。先进行预处理:
1 | ./prepare |
7、执行下面命令进行安装:
1 | ./install.sh |
8、稍等一会,执行 docker-compose ps
,如果所有容器的状态都是 healthy ,说明正常:
9、登录后界面如下:
1、内网不能登录
安装完成后,在外网使用 docker login 发现不能正常登录,于是先进内网进行验证,发现内网也不能登陆,提示信息如下:
[root@localhost data]# docker login 172.16.10.103:9998
Username: admin
Password:
Error response from daemon: Get “https://172.16.10.103:9998/v2/": http: server gave HTTP response to HTTPS client
需要将内网服务器 IP 和端口配置到 daemon.json 文件中,执行下面命令进行配置:
1 | sudo tee /etc/docker/daemon.json <<-'EOF' |
然后执行下面命令重启生效:
1 | sudo systemctl daemon-reload |
再次登录:
[root@localhost docker]# docker login 172.16.10.103:9998
Username: admin
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-storeLogin Succeeded
这个不仅仅是内网,哪台机器需要进行登录操作,都需要进行上面的配置。
2、外网不能登录
当内网正常后,发现外网依然不能正常登录,提示如下:
fengwei@fengweideMBP ~ % docker login hub.fwhyy.com:1234
Username: admin
Password:
Error response from daemon: Get “http://hub.fwhyy.com:1234/v2/": Get “http://172.16.10.103:9998/service/token?account=admin&client_id=docker&offline_token=true&service=harbor-registry": context deadline exceeded (Client.Timeout exceeded while awaiting headers) (Client.Timeout exceeded while awaiting headers)
解决这个问题需要修改 harbor.yml 配置,将 hostname 修改为外网的 IP 或域名(不需要加端口):
将 external_url 修改为外网访问的地址(需要加上端口):
修改完后需要重启 Harbor,执行下面命令进行重启:
1 | cd /opt/harbor |
外网服务器的 nginx 配置如下:
1 | server { |
Harbor 里功能比较多,常用的有项目、用户管理、项目定额。
下面就按照步骤将一个镜像推送到 Harbor 中。
1、在用户管理中创建名称为 images_admin 的用户:
2、在项目中创建名称为 fw 的项目,并添加 images_admin 为项目的维护人员:
3、在项目定额中设置项目的配额大小为 2GB:
4、先以一个 nginx 镜像为例,直接推送试试,命令如下:
1 | docker tag nginx:latest hub.fwhyy.com:1234/fw/nginx:latest |
因为没有登录,会提示没有权限推送:
The push refers to repository [hub.fwhyy.com:1234/fw/nginx]
b074db3b55e1: Preparing
e50c68532c4a: Preparing
f6ba584ca3ec: Preparing
01aaa195cdad: Preparing
2a13e6a7cca6: Preparing
370869eba6e9: Waiting
7292cf786aa8: Waiting
unauthorized: unauthorized to access repository: fw/nginx, action: push: unauthorized to access repository: fw/nginx, action: push
5、使用下面命令进行登录后再进行推送:
1 | docker login hub.fwhyy.com:1234 |
登录后,就可以正常推送了,登录进入系统,可以看到在项目的镜像仓库中已经可以看到了:
]]>所以本文就是用来告诉你怎样将佳明国内区同步到国际区,这样就能正常使用 runalyze 和 Strava 了。
1、进行账号注册和各种配置;
2、使用开源代码库 DailySync 进行同步。
如果你正在使用佳明手表,那么肯定已经有了国内区的账号了,如果是将要使用,可以在这个地址进行注册:
https://connect.garmin.cn/signin/
佳明国际区的的账号注册地址是:https://connect.garmin.com/signin/ 。登录后进行如下设置:
Strava 的地址是:https://www.strava.com/ ,注册成功后需要进行简单配置,如下图:
如果没有 Github 的账号,需要注册一个,地址如下:
注册登录后,访问:https://github.com/lijiehao1/DailySync ,这个开源项目地址,将这个项目 Fork 到自己的账号下。
正常情况下,Fork 项目没有问题,但我实际验证时发现 Github 上的代码貌似不是最新的,作者把最新的代码放在 Gitlab 上,所以建议在 Gitlab 上下载代码,然后上传到自己个 Github 账号中 ,Gitlab 项目地址如下:
https://gitlab.com/gooin/dailysync
1、在 Github 中,进入到上传的项目,点击上面的 Settings 页签:
2、点击左侧的 Actions 菜单,在右边的 Repository secrets 中添加环境变量:
3、设置好的界面如下:
4、切换到 Actions 页签,然后点击绿色按钮启用,如果已经可以看到 workflow 的列表,则忽略此步骤:
5、可以看到在左侧有所有 workflow 的列表,我们重点关注从国区到国际区的迁移和同步:
6、当我们点击进入「Migrate Garmin CN to Garmin Global」workflow 时,如果没有发现手动触发的按钮,则需要进行 yml 文件的配置,将下图红框处的代码注释放开:
修改完成配置后,就能看到手动触发的按钮了:
7、点击按钮「Run workflow」进行手动执行。在迁移日志中,如果看到类似这样的日志,说明迁移成功:
8、这时登录进入佳明的国际区,查看所有活动,发现已经同步一条数据进来了:
9、修改 Setting 中设置的环境变量 GARMIN_MIGRATE_NUM、GARMIN_MIGRATE_START ,因为初始设置的是从位置 0 开始,迁移了 1 条,所以修改为:
表示从位置 1 开始,同步 100 条,如果设置同步的条数越多,需要等待的时间就越长。并且通过验证发现,Start 的索引是按时间从最新往最旧排的。执行几轮之后,国际区已经有了全部数据:
10、上面已经在 Strava 中进行了和佳明国际区的连接,这时进入到 Strava 中看看,可以看到数据已经正常同步了:
11、对于 Actions 中我们不需要的 Workflow ,可以选择禁用:
12、上面介绍了手动迁移的 Workflow,自动同步用的是:Sync Garmin CN to Garmin Global ,默认是开启的,每 6 个小时同步一次,如果想要修改同步频率,编辑 yml 文件,修改下图红框部分:
因为通常都是早上跑步,7 点之前就跑完了,我可以设置每天早上 7 点执行,那么 cron 表达式可以设置为:
1 | 00 7 * * * |
为什么会有这个想法呢?还得从跑步说起。
2016 年,武汉举办第一届马拉松比赛,我参加了健康跑(13 公里),也就是从那时,我开始了跑步,到现在已经 7 年多时间了,才跑了 5000 多公里,算是非常佛系的跑步爱好者了。
这些年的跑步非常随意,靠着报一些马拉松比赛反向激励自己,疫情的几年,没有比赛可跑,就非常懈怠,天气太热了不跑、天气太冷了不跑、空气质量不好不跑,所以一年也就几百公里的跑量。基本上每年都会经历跑两三公里都很费劲到可以轻松完成半马。
除了跑量,跑步质量也很随意,没有计划、没有课表,跟着感觉走,当然跟着感觉走并没有什么不对,只是时间长了,很容易缺乏动力。即使不跟别人比,但如果看到自己的能力和成绩在不断提高,总是令人高兴的。
直到去年在小宇宙中听了一系列跑步相关的播客《装备说》、《跑者日历》、《PB计划》等,让我对跑步有了新的理解和认识,原来健康无伤的跑步和追求成绩并不冲突;原来跑步还能进行科学的训练,而不是穿上鞋迈开腿就行;原来冬天和夏天才是真正训练的好时候,春和秋是比赛季。
于是,在 2023 年 12 月底,我报了一个线上训练营,准备认真对待下跑步这件事了。
到现在训练营已经开始第四周的训练了,下面说说和之前自己跑的区别和感受吧:
1、有教练可以一对一沟通,每次训练后会在平台中填写心得和感受,教练会根据反馈进行课表的调整,更加科学;
2、每周日教练会放出下一两周的课表,为什么不是一个固定的课表,因为课表是根据能力在动态调整的。自己不用关心每天训练什么,很省心;
3、根据目前几周的情况来看,每周的课表在内容上都有所区别,教练说是故意这么设置的,为了增强趣味性,毕竟跑步是一个相对枯燥的运动。这样一来,反倒是很期待去完成课表了,早起也不成问题;
4、学会了控心率跑,大部分时间都是很慢的速度,当然也会有慢跑后的 ST、R 跑,也会有间歇、乳酸阈值跑,这几周练下来的感受就是要么很慢,要么相对快,而之前自己跑大部分时间都是在中间,这个中间可能就是自己的舒适区,不去往舒适区的边缘疯狂试探,很难有突破;
5、同级别的学员在一个群里,可以互相沟通、打卡和鼓励。
6、认识更多同类型的人,能获取到更多的跑步相关的信息和知识,眼界打开了。知道了原来还有很多专业的书籍专门讲跑步;知道了跑步训练还分为丹尼尔斯和汉森两个体系等;
7、以前到冬天就跑休了,这个冬天将成为我跑步以来跑量最大的一个阶段。冬天跑强度课容易受伤,但教练的课表中考虑到了,强度的课的热身非常充分。之前自己跑很容易忽视热身和拉伸。
你看,跑步并没有想象的那么简单。
如果不深入地去了解,就会产生刻板印象,就像很多人认为跑步伤膝盖,会劝你不要跑太多一样。随着对跑步知识的了解,你才能知道到底会不会伤膝盖?什么情况下才会伤膝盖?
那么就从跑步开始吧,对待其他的事情也需要认真深入地去做。
看书可以不追求看了多少,有疑问的地方可以进行横向或纵向的查阅、记录、思考;技术学习也是一样,从理论到实践,从宏观到细节。
最后给自己打个小广告,关于这个冬天训练的记录我会持续发布到小红书上,有兴趣的可以关注下:
]]>本文就介绍怎样用 CSRedisCore 来调用设置了密码的哨兵模式 Redis。
1、执行下面命令安装 wget ,用于后面下载 Redis 安装包:
1 | yum -y install wget |
2、安装 gcc ,编译和安装 Redis 时需要:
1 | yum -y install gcc |
3、下载 Redis 并安装:
1 | cd /usr/local #进入到usr/local目录 |
4、修改 Redis 配置文件并启动:
1 | cd /usr/local/6.2.14 #进入redis目录 |
编辑内容如下:
1 | daemonize yes #修改配置文件中的daemonize为yes,为后台启动 |
执行命令 redis-server redis.conf
进行 Redis 服务的启动。
5、检查并连接:
1 | ps -ef | grep redis #检查是否启动成功 |
6、设置密码:
1 | vi redis.conf #编辑配置文件进行密码设置 |
修改文件内容,去掉requirepass前面的#号:
1 | requirepass Aa123456 |
正式的生产环境会使用多台服务器来配置主从,本文为了演示方便,在一台服务器上通过多端口的方式来配置主从,端口分配规则如下,一个主节点、三个从节点、五个哨兵:
1、在 /usr/local/redis-6.2.14 目录中创建 config 目录,在该目录中创建相应的目录存放配置文件和数据:
1 | cd /usr/local/redis-6.2.14 |
创建完成后目录结构如下:
2、配置 master 的 redis.conf 文件:
1 | bind 10.211.55.14 #修改成自己的IP地址 |
3、配置 slave 的 redis.conf 文件:
1 | bind 10.211.55.14 #修改成自己的IP地址 |
4、将端口 6382、6383 对应的从服务器的配置文件对照第三步进行修改。
5、配置哨兵 1 的配置文件:
1 | port 26379 #指定哨兵1端口号 |
6、配置哨兵 2 的配置文件,其他的几个哨兵配置类似:
1 | port 26380 #指定哨兵2端口号 |
注意 :mymaster 为主的名称,默认为 mymaster,如果要修改,该配置文件中所有涉及的地方都需要调整。
7、启动服务:
1 | cd /usr/local/redis-6.2.14 |
8、查看主从状态:
1 | redis-cli -h 10.211.55.14 -p 6380 #连接到主库 |
9、测试哨兵是否正常工作:
1 | redis-cli -h 10.211.55.14 -p 6380 #连接到主库 |
可以看出 6383 的从库已经升级为主库,这时将 6380 启动起来,查看服务器状态,可以发现 6380 已经变成从库,说明哨兵在正常工作。
在 .NET Core 中操作 Redis ,最常用的就是使用 CSRedisCore ,因为这个库中的 API 和 Redis 原生的 API 几乎一致,但不支持哨兵设置密码(也可能是我还没找到使用方法)。
但 .NET Core 中的另一个库 StackExchangeRedis 是可以支持哨兵密码的,所以可以使用 StackExchangeRedis 进行哨兵的验证,并获取到主库的连接。
然后使用 CSRedisCore 来对主库进行操作。
1、使用 StackExchangeRedis 完成验证和获取主节点的连接:
1 | redisServerIP = "10.211.55.14,10.211.55.14,10.211.55.14,10.211.55.14,10.211.55.14"; //哨兵IP列表 |
2、使用 CSRedisCore 操作主连接:
1 | if (masterConnection.IsConnected) |
虽然有点绕,但暂时可以解决问题,希望 CSRedisCore 未来可以支持 Redis 节点和哨兵都设置密码的场景。
希望本文对您有所帮助。
]]>微软官方推出了升级工具:Upgrade Assistant ,链接地址如下:
https://dotnet.microsoft.com/zh-cn/platform/upgrade-assistant/tutorial/intro
有了升级工具,升级就变得非常简单了,本文就介绍使用升级工具将 .NET Core 3.1 项目升级到 .NET 8 。
先确保 VS2022 已经升级到了 17.8 。然后在 VS2022 的扩展管理中安装扩展:.NET Upgrade Assistant ,需要特别注意的是,如果之前安装过升级工具扩展,需要卸载重新安装。
.NET Core 3.1 的一个解决方案中,会有很多的项目,按照项目的依赖关系,从最底层的项目逐个往上进行升级。
1、安装完升级工具后,在项目上点击右键就会出现 Upgrade 按钮:
2、在弹窗中选择升级方式:
3、选择升级的目标版本,这里我选择 .NET 8 ,这是一个长线支持版本,最新版本的升级工具只支持升级到 7 和 8 了,如果有升级到 .NET 6 的需求,就需要使用老版本了:
4、选择需要更新的内容,默认全选,点击「Upgrade selection」进行升级:
5、很快就可以看到升级成功的提示:
我验证过好几个低版本的项目,使用工具升级的过程没有出现果任何错误,但升级完后进行代码编译就会出现各种问题了。
在原来的版本中,项目中的 zip 压缩用到了 Ionic.zip ,现在 .NET8 已经不支持了,需要换成 DotNetZip :
代码中有不少地方使用到了二进制的序列化,但 BinaryFormatter 在 .NET8 中已经弃用,有两种解决方式:
1、修改源代码,采用新的推荐的方式进行替换。
2、修改项目文件,忽略此问题,在项目文件种添加下面配置:
1 | <Project Sdk="Microsoft.NET.Sdk"> |
参考:https://learn.microsoft.com/zh-cn/dotnet/fundamentals/syslib-diagnostics/syslib0011
项目中对 Office 文件的处理,使用了 Aspose 套件,升级后版本有兼容性问题,升级到对应的版本就行。
在之前的版本中,List 存储的如果是一个复杂类型,想要按照类型中的某个字段进行去重是没办法直接实现的:
1 | List<UserInfo> list = new List<UserInfo>(); |
上面代码中的 DistinctBy 方法在 .NET Core 3.1 中是没有的,所以我们扩展了一个 DistinctBy 方法,没想到 .NET8 中已经默认提供了,会导致方法冲突,只需要将我们的扩展方法去掉,使用默认就好。
解决了上面的几个编译问题后,程序就能正常启动运行了,整个过程还是非常快速的,不得不说,微软的技术向下兼容做的是非常不错的,再加上工具的加持,升级到新的版本没有什么压力和负担。
相比之下,其他有些技术虽然也在不停地更新迭代,但主流使用的还是某个特定的版本。
]]>我的越野跑入坑是因为身边有朋友从马拉松转战到了越野跑,于是今年 7 月就和朋友一起报了 12 月份的大岩山越野赛 30 公里组别。关于和马拉松的区别、装备等,在《从公路马拉松到越野跑:一次新的挑战》中有详细介绍。
平时跑半马比较多,周末跑 LSD 也会跑到 25、30 公里。但从这次体验来看,30 公里越野比一个全马的消耗更大。
仅仅一个周末的时间,从武汉到杭州一个来回,时间还是很紧张的。
1、12 月 2 号(周六)一早坐火车到金华和朋友会面。
2、匆匆吃完午饭,朋友开车从金华到领物地点-三泉王村(杭州萧山区)。越野赛因为参赛人少,领物非常迅速,签一个免责协议、检查强装,就可以拿东西走人了。
3、晚上住在离起点 5 公里的临浦镇,晚饭后,收拾好东西,10 点就开始睡觉了。
4、12 月 3 号(周日)一早 5 点多就起了,第一次越野,有点兴奋。6 点 40 左右到达起点,离比赛开始只有 20 分钟。
5、 7 点整准时开跑,5 个半小时后,安全完赛,休息拉伸后赶回酒店洗澡,下午 2 点赶往杭州东站,晚上 10 点到家。
千万不要小看 30 公里的距离和 1500 米的爬升,远比想象的要艰难。
一出发,就是水泥路的小缓坡,慢慢往山里进发了,虽然是爬坡,但 6 分多的配速也还感觉挺轻松。
40 多分钟到达第一个最高点,太阳刚刚升起,山里空气清新,非常舒服,这是和马拉松最大的区别,越野跑可以随时停下来看风景。
接下来就是一段下坡,因为才刚开始,体力充沛,下坡速度还可以,最快的一公里跑到了 5 分多。下山之后就有一些山与山之间连接的水泥路,我基本就靠在这种路上超人了,因为上山爬不动、下山不敢跑。
需要翻了六七个这样的山头才能到达终点。
大概 20 公里后,大腿小腿都开始抽筋,撑不住了就停下来拉伸,然后继续,非常感谢和我一起的朋友,全程都陪在一起。
最终,在中午 12 点半到达终点,比赛前的预计六小时,提前了半个小时,对于首次跑越野的我来说,这个成绩还是挺满意的。
1、带了四根能量胶,最后跑完还剩余 1 根。
2、足量的盐丸,基本上七八公里会补充一到两颗,尽管如此还是抽筋了,说明盐丸只能起到辅助作用,主要还是需要有强大的肌肉力量和耐力。
3、越野包的软水壶一边装了两瓶红牛,红牛非常给力。
4、30 公里组别一共有三个补给站,在第一个补给站拿了一瓶营养快线;第二个补给站和朋友都感觉状态挺好,没进去;第三个停下休息了大概 5 分钟,喝了很多可乐,吃了很多橘子。
平时路跑,遇到有起伏的路面,会觉得很难受,但在比赛中,翻了几个近乎垂直、脚下看不到路、往上看不到头的大坡后,来到了一段盘山公路,突然觉得很幸福,正想着,会不会是一直沿着盘山公路绕上去,发现路标指向了丛林深处,又一个大的爬升。
最后的 10 公里,一直都在关注着还剩余多少爬升,还剩余多少公里。爬上最后一个山头,发现爬升已经达到 1500 米,心里轻松一大截,慢慢挪下山后,发现距离还有一公里多,隐约听到有铃铛的声音,等我们走近,就听见有人喊:”加油,前面拐弯 100 米就到终点了“ 。
疲惫的双腿立马变得有力量了,全力冲向终点。
1、不管跑的多慢,每个人都会安排冲线,这是马拉松没有的待遇。
2、不管跑的多慢,冲过终点,会有人把奖牌挂到你脖子上,这也是马拉松没有的待遇。
3、所有参赛者都在一个微信群里,从赛前的探路、赛后的照片、视频分享,特别热闹。
4、一路上虽然忍受着他人无法感知的痛苦和绝望,但也欣赏到了漂亮的风景。
虽然是一个比较小型的越野比赛,但体验非常好,村民非常友善热情、赛事组织方也很负责,在群里都尽量满足大家的需求。
结束比赛到现在已经两天过去了,我依然需要扶着才能下楼梯,腿部肌肉的力量和耐力太缺乏导致身体反馈比较严重,这个冬天需要好好训练了。
下一站:24 年崇礼 168 越野赛 70 公里。
]]>一直以来,我跑步比较佛系,夏天跑的少、冬天也跑的少,报了比赛就临时突击下,所以总跑量不多。这些年,线上线下,陆陆续续也跑了 29 场半马,但直到今年 4 月宜昌马拉松半程才跑进两小时。
所以说,针对半马而言,如果只是想完赛,还是挺容易,但想要跑出好的成绩,就必须有科学的训练。
今年 8 月在朋友的带动下,制定了相对正式的训练计划,同时,每天上下班途中在小宇宙听《跑者日历》、《PB计划》,也学习到了不少跑步知识。这些都为 11 月 5 号的黄冈半程和 11 月 12 号的南昌半程 PB 打下了基础。
关于马拉松训练,目前了解到的有丹尼尔斯训练法和汉森马拉松训练法,因为没有完全吃透,也就没敢冒然网上的一些课表来执行,所以制定的计划是按照自己的实际情况和理解来规划的:
对我这种多少年都跑不进 2 小时的爱好者来说,跑量的积累是提升成绩最有效的方式。我看了下 9 月和 10 月的跑量,都超过了 200 公里 :
正式因为有了跑量的保证,在 10 月 14 号能跑出 450 配速的 10 公里,在 10 月 21 号能跑出 451 配速的 15 公里。
这两次的测试,让我对在比赛中跑进 145 充满了信心。
马拉松比赛在国内越来越火热,加上疫情的放开,更是一签难求。下半年所有报名只有南昌中签了,然后又报了一个不用抽签的黄冈半程马拉松。
黄冈半程是在 11 月 5 号举行,为这场比赛做了充足的准备:
1、赛前很长的一段时间一直都在关注黄冈的天气;
2、赛前一周跑量减少,间歇只做 5组,还跑了一次 5 公里的马拉松配速跑;
3、赛前饮食上尽量多补充碳水。
结果比赛当天,尽管没下雨,但气温也没有预报的那么低,起跑的时候已经有 19 度了,湿度很大,很闷,赛道也没有想象中的那么平坦。最后结果离目标有一点点差距。当然主要还是训练不够,水平太菜。
10 公里后,配速就在 5 分开外了,15 公里后想要提速已经提不动了,只能维持在 5 分左右的配速跑完,最终 1 小时 45 分 42 秒完赛,比 4 月的宜昌半马 PB 了 12 分钟。
南昌半马就在黄冈比赛完的下一周,中间这一周因为下雨,只慢跑了一个 7 公里,而且天气预报显示 12 号有雨,气温更是低到 10 度以下。
这些因素已经让我放弃了 PB 的想法,想着安全完赛就好。
11 号开车到达南昌的时候还在下雨,晚上大概 7 点雨就停了,一直到第二天离开南昌都没有再下,这才有机会在比赛结束后去了滕王阁和万寿宫。
12 号一早,气温 10 度左右,气温低,跑起来体感却特别舒服,时不时还有一阵冷风刮来,立马就精神了。
可能是气温低的原因,快到 10 公里的时候,大腿有快要抽筋的感觉,赶紧补充了盐丸和能量胶,看来是盐丸发挥了作用,继续跑了几公里后,腿部没有不适的感觉,最后一公里还跑出了最快配速 430 。最后成绩 1 小时 42 分 25 秒,比一周前的黄冈半马 PB 了 3 分钟。
系统学习跑步的方法和技巧,除了公众号、播客这种零碎的知识获取外,最好的方式就是看书,我选了一些还不错的跑步相关书籍放在豆列中,这些书大多在微信读书中都有,豆列地址如下:
https://www.douban.com/doulist/157036945/
跑步的目的是为了健康,可不能本末倒置了,遇到伤病,该停就停,该歇就歇。在保证健康的前提下再来追求成绩,当然,也可以完全不用追求成绩,跑起来就好。
]]>要使用 .NET 8 ,需要安装相关的 SDK,可以在这个地址进行下载:https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0,或者将 VS2022 升级到 17.8 。
虽然 8 又带来了很多方面的增强,比如:人工智能、云原生、性能、native AOT 等,但我还是最关注 C# 语言和一些框架层面的变化,下面介绍下 C# 12 和框架中的我认为比较实用的新增功能,全部更新说明可以看官方文档:https://learn.microsoft.com/zh-cn/dotnet/core/whats-new/dotnet-8 。
1、可以对附加类型:Half、Int128、UInt128 进行序列化,在 .NET 7 中对这些类型序列化时不会报错,但内容不能正常获取;
2、可以对 ReadOnlyMemory
3、当 T 的类型为 byte 时,序列化结果为 base64,否则为 json 数组。
1 | using System.Text.Json; |
1 | IDerived value = new DerivedImplement { Base = 0, Derived = 1 }; |
1、上面代码中 IDerived 接口继承了 IBase 接口后,就拥有两个属性了;
2、在之前的版本(3.1、6、7)中使用包含两个属性的接口 IDerived 来接收对象的实例化,然后进行序列化,得到的结果只有:{Derived”:1} ,继承过来的属性 Base 不能被识别;
3、在 8 中得到了改进,可以得到期望的结果,值得注意的是,如果之前使用了变通方式来进行处理,升级后需要有针对性进行测试和调整。
下图是 8 中序列化时对命名策略的支持:
在之前的版本:3.1、6、7 中都只支持 CamelCase 。在 8 中新增的策略如下:
1 | var options1 = new JsonSerializerOptions |
结果如下:
现在有一个接口返回如下图中的数据:
如果是在 8 以前的版本中获取该接口的数据,需要先获取到接口内容,然后进行反序列化,代码如下:
1 | const string RequestUri = "http://localhost:5145/user"; |
在版本 8 中可以直接调用 GetFromJsonAsAsyncEnumerable 方法直接得到对象,无需进行反序列化:
1 | const string RequestUri = "http://localhost:5145/user"; |
上面两种代码的结果一样,如下图:
1、在 8 中对随机数类 Random 提供了 GetItems
1 | ReadOnlySpan<string> colors = new[]{"Red","Green","Blue","Black"}; |
2、通过 Random 提供的 Shuffle
1 | string[] colors = new[]{"Red","Green","Blue","Black"}; |
1、新增了 FrozenDictionary<TKey,TValue> 和 FrozenSet
下面是使用 BenchmarkDotNet 对 FrozenDictionary 和 Dictionary 进行测试的代码:
1 | BenchmarkRunner.Run<FrozenDicTest>(); |
从测试结果看,效果还是很明显的:
2、新增的 System.Buffers.SearchValues
1 | BenchmarkRunner.Run<SearchValuesTest>(); |
从运行结果看,有大约 5 倍的的提升:
在 8 之前的版本中,依赖注入写法如下:
1 | var builder = WebApplication.CreateBuilder(args); |
如果 IUser 接口有两个实现,上面代码中的写法就只能获取到最后一个注册类的实例,要实现一个接口多个实现类的注入,还需要写一些额外的代码,比较繁琐。
版本 8 中添加了注入关键字,可以很方便实现,看下面代码:
1 | var builder = WebApplication.CreateBuilder(args); |
在《监控利器:普罗米修斯介绍和安装》中有一张图,表明了 Prometheus 的数据走向,如下:
从图中可以看出,监控中间件的第一步就是安装中间件的 exporter,安装有两种方式:下载安装文件进行安装和使用 Docker 进行安装,下面示例中使用的是后者。
1、我们产品的前端 Web 部署在 nginx 容器中,需要在容器的配置文件中进行 nginx_status 模块的设置,才能被 exporter 识别。 nginx 配置文件添加下面代码:
1 | location /nginx_status { |
2、修改配置后,重启 Web 容器,访问 http://ip:port/nginx_status ,出现下图界面,说明配置生效:
3、执行下面的命令进行 nginx-exporter 容器的安装:
1 | docker pull nginx/nginx-prometheus-exporter |
容器运行后,访问 9113 端口,如下图:
4、在 prometheus 的配置文件中进行绑定,执行vi /usr/local/prometheus/prometheus.yml
,在文件的最下面添加 job 配置:
1 | - job_name: 'nginx' |
5、执行命令 systemctl restart prometheus
重启生效,可以访问 http://10.211.55.3:9090/targets 查看状态,如果为 UP 说明 job 设置成功:
6、在 Grafana 中导入 12078 模板:
7、最终展示效果如下:
1、首先需要安装 redis_exporter ,执行下面命令进行镜像的下载和安装:
1 | docker pull oliver006/redis_exporter |
2、容器运行成功后,浏览器访问界面如下:
3、在 prometheus 的配置文件中进行绑定,执行vi /usr/local/prometheus/prometheus.yml
,在文件的最下面添加 job 配置:
1 | - job_name: 'reids' |
4、执行命令 systemctl restart prometheus
重启生效,可以访问 http://10.211.55.3:9090/targets 查看状态,如果为 UP 说明 job 设置成功:
5、在 Grafana 中导入 763 编号的模板:
6、最终展示效果如下:
1、首先需要安装 redis_exporter ,执行下面命令进行镜像的下载和安装:
1 | docker pull kbudde/rabbitmq-exporter:latest |
2、容器运行成功后,浏览器访问界面如下:
3、在 prometheus 的配置文件中进行绑定,执行vi /usr/local/prometheus/prometheus.yml
,在文件的最下面添加 job 配置:
1 | - job_name: 'rabbitmq' |
4、执行命令 systemctl restart prometheus
重启生效,可以访问 http://10.211.55.3:9090/targets 查看状态,如果为 UP 说明 job 设置成功:
5、在 Grafana 中导入 2121 编号的模板:
6、最终展示效果如下:
1、在 mysql 数据库中创建 exporter 账户,并设置权限:
1 | CREATE USER 'exporter'@'%' IDENTIFIED BY 'Aa123456'; |
2、在目录 /root/exporter/config/mysql
中创建 .my.cnf 文件,文件内容如下:
1 | [client] |
3、执行下面命令安装 mysqld-exporter :
1 | docker pull prom/mysqld-exporter |
如果没有 .my.cnf 文件的映射,会出现下面错误:
4、容器运行成功后,浏览器访问界面如下:
5、在 prometheus 的配置文件中进行绑定,执行vi /usr/local/prometheus/prometheus.yml
,在文件的最下面添加 job 配置:
1 | - job_name: 'mysql' |
6、执行命令 systemctl restart prometheus
重启生效,可以访问 http://10.211.55.3:9090/targets 查看状态,如果为 UP 说明 job 设置成功:
7、在 Grafana 中导入 7362 编号的模板:
8、最终展示效果如下:
]]>准备用三篇文章来介绍怎么使用:
1、基本介绍和安装
2、和中间件的集成
3、在 dotNET Core 中的使用
本文是第一篇:基本介绍和安装。
Prometheus 是一套开源的监控报警系统,由 SoundCloud公司开发,于 2012 年开源。已经广泛应用于 Kubernetes 和 ServiceMesh 等云原生环境中。
Prometheus 具有以下核心特征:
Prometheus 作为云原生应用监控的首选方案,其生态圈非常繁荣。它的出现极大地促进了新的监控思维模式的形成,为构建高可用自动化系统提供了重要保障。
1、安装 Prometheus 和 Grafana 。
2、安装部署中间件的 exporter ,本文只介绍 node_exporter 的安装,其他的中间件放到下一篇。
3、修改 Prometheus 的配置文件,添加 job 节点,并重启让其生效。
4、在 Grafana 中添加数据源 。
5、在 Grafana 中添加面板。
服务器1:10.211.55.6 (部署 prometheus )
服务器2:10.211.55.14(部署 Grafana、node_exporter )
1、在 prometheus 官网下载页面下载相关的安装包,地址如下:
https://prometheus.io/download/
2、在服务器上执行下面命令进行安装:
1 | cd /root |
3、设置 prometheus 系统服务,执行命令创建服务文件 vi /usr/lib/systemd/system/prometheus.service
,文件内容如下:
1 | [Unit] |
4、启动服务和设置开机自动启动:
1 | systemctl daemon-reload |
5、启动后,可以使用 systemctl status prometheus.service
命令查看状态,出现下图界面,表示启动成功:
6、在浏览器访问地址:http://10.211.55.3:9090/targets?search= ,出现下图界面,说明 prometheus 已经安装成功了。
1、在 Grafana 官网下载页面下载相关的安装包,地址如下:
https://grafana.com/grafana/download
2、在服务器上执行下面命令进行包的下载和安装
1 | cd /root |
3、启动:
1 | systemctl enable grafana-server |
4、启动后,可以使用 systemctl status grafana-server
命令查看状态,出现下图界面,表示启动成功:
6、在浏览器访问地址:http://10.211.55.14:3000/,出现下图界面,说明 Grafana 已经安装成功了。
node_exporter 是用来监控服务器的 exporter ,按照下面步骤进行安装:
1、在服务器上执行下面命令进行包的下载和安装
1 | cd /root |
2、设置 node_exporter 系统服务,执行命令创建服务文件 vi /usr/lib/systemd/system/node_exporter.service
,文件内容如下:
1 | [Unit] |
3、设置开机自动启动:
1 | systemctl daemon-reload |
4、访问地址:http://10.211.55.14:9100 ,出现下图界面,说明安装成功:
1、修改 Prometheus 的配置文件,添加 node_exporter 的绑定,执行命令 vi vi /usr/local/prometheus/prometheus.yml
:
1 | - job_name: 'centos-1' |
2、执行命令 systemctl restart prometheus
重启 Prometheus 。
3、在 Grafana 中添加数据源,登录 Grafana 后,在 Data Sources 模块中添加数据源:
4、选择 Prometheus 作为数据源并进行配置,将 Prometheus 的地址 http://10.211.55.3:9090 ,填写在 server url 中:
5、想要在 Grafana 中进行数据的展示,需要导入 dashborards 模板,这个地址中有各类模版可供选择:https://grafana.com/grafana/dashboards/ 。在 Grafana 的 Dashboards 模块中进行导入:
6、输入编号:11074,这是可以展示服务器监控信息的 dashborard 模板:
7、Load 后,进行导入:
8、该 dashborard 模板最终展示的数据效果如下:
]]>背后的思想就是指面向对象的原则:
这些原则就是告诉我们应该怎么合理地组织类和方法。最终使我们开发的程序能够满足:可扩展、可复用、可阅读。只是看这些原则比较抽象,最近看了下六边形架构,我认为对代码的编写有很好的指导作用,下面就聊聊六边形架构。
六边形架构(Hexagonal Architecture),也被称为端口与适配器架构(Ports and Adapters Architecture),是一种软件架构模式,旨在实现高内聚、低耦合和可测试性的应用程序设计。该架构由 Alistair Cockburn 发明,他是敏捷宣言的签署者之一。
从上图可以看出有内外两层六边形,深蓝色和浅蓝色。
上图中的紫色部分的 context 是我们在实践过程中添加的,在应用层中进行逻辑组装时,如果没有业务上下文的概念,很多方法会导致被重复调用,所以在业务入口会进行上下文的初始化,将长下文贯穿整个调用链。
六边形架构也被称为端口与适配器架构,端口和适配器是两个非常关键且重要的概念。
端口是应用程序定义的接口,必须由外界实现,以便应用程序可以接收或发送信息,进行解耦。这个接口是广义的,不光是指 Interface,WebAPI 接口,一些类的公共方法也属于接口的范畴。
端口有分为两种:
入站端口:业务服务对外暴露的公有方法;
出站端口:出站端口只一组方法的接口定义,提供一种规范,供出站适配器来实现。
使用端口和适配器进行处理应用程序的输入和输出,端口只是一种抽象,是应用程序在不了解任何内容的情况下与外界交互的一种方式。
例如:如果想要进行数据库的读取和写入,不是直接操作数据库,而是在接口中定义读取和写入的方法。应用程序不需要知道数据来自哪里,需要写到什么地方去,可能是数据库,也可能是文件系统或缓存,甚至会同时操作。
适配器是连接应用程序核心和外部接口的桥梁。它负责将外部请求转换为应用程序核心可以理解的格式,并将核心的响应转换为外部接口可以接受的格式。
适配器也分为两种:
当要将数据保存到数据库中时,适配器从接口定义的数据格式中获取数据,并将其转换为可以写入数据库的内容,重要的是,无论在适配器中怎么变化,核心域和接口不会发生变化。这就非常有用,将应用程序的核心逻辑和外部存储隔离开了。
正是由于端口和适配器的存在,程序变得稳定和容易变化。
为什么是叫六边形架构?而不是三角形、圆形、正方形呢?
目前没有明确的理由说明为什么是六边形,而不是其他的形状。或许只是因为六边形比较好看。又或许,一个小的六边形代表这一个模块,一个系统有很多这种模块组成,模块之间有输入输出的交互,就像蜂窝一样。
而蜂窝正好是六边形的。
通过六边形架构,应用程序核心成为了架构的中心,具有清晰的边界和职责,可以独立于外部接口进行测试和演进。外部接口和适配器负责处理与外部系统的交互,使应用程序核心保持独立和可复用。主要有以下特点:
当我们谈论六边形架构时,有几个核心原则需要考虑。这些原则指导我们持续优化软件架构,使系统的各个模块能够独立地演化和变化,同时保持其整体的稳定性。
1、分离关注点:六边形架构将系统划分为不同的层次,每个层次都有其特定的职责和关注点。这种分离使得每个组件可以专注于自身的任务,降低了耦合性,提高了模块的可复用性和可测试性。
2、内外部分离:六边形架构将系统划分为内部和外部两个六边形,分别代表核心业务逻辑和外部接口。内部六边形负责处理核心业务逻辑,而外部六边形则负责处理业务整合和外部系统的交互。这种内外部分离的设计使得系统更容易扩展和适应变化。
3、依赖注入:六边形架构鼓励使用依赖注入来管理组件之间的依赖关系。通过依赖注入,组件的依赖关系可以在运行时进行配置,而不是在编译时固定。这样可以实现组件之间的松耦合,并且方便进行替换和测试。
4、接口驱动:六边形架构强调基于接口编程,通过定义清晰的接口和协议来促进组件之间的通信。接口的使用可以提高组件的可替换性和可测试性,并支持多态性和可扩展性。
5、测试驱动:六边形架构鼓励在开发过程中采用测试驱动开发(TDD)的方法。通过编写测试用例来定义组件的行为,然后逐步实现和改进组件以满足测试的要求。这种测试驱动的开发方法有助于保证系统的质量和稳定性。
根据这些原则,可以发现,这些就是在文章开头提到的哪些面向对象的原则。通过六边形架构的包装,更具备实操性。
在网上查相关资料,六边形架构往往都跟 DDD 、微服务在一起被提及。他们之间其实没有很必然的联系。
就像微服务和 DDD 一样,也没有必然联系,因为:
1、DDD 中子域和限界上下文的概念可以对应到微服务中的服务;
2、微服务中一个服务可以由一个团队进行开发,DDD 的一个领域模型也是建议由一个独立的团队负责。
所以,微服务和领域驱动开发(DDD)常常会一起提及,在学习的时候,也会两种一起学,互相配合能够更好地落地。
如果说,微服务是架构风格、DDD 是架构设计方法、那么六边形架构就是一种具体的指导编码的架构实践。
1、VS 的 HexagonalX 扩展
在 VS 中可以安装六边形架构的扩展,安装后在创建项目时就会多出六边形架构的项目类型可供选择。
2、几个 GitHub 上的示例项目和文章
https://github.com/alesimoes/hexagonal-clean-architecture
https://github.com/ivanpaulovich/clean-architecture-manga
https://blog.allegro.tech/2020/05/hexagonal-architecture-by-example.html
]]>本文聊下 API 设计可能遇到的问题以及处理方式。
1、客户端种类比较多,不容易实现差异化。
以我们现在正在做的低代码平台来说,存在的客户端有下面这些:
不同的客户端在调用接口时,输入输出会存在差异,比如:移动端的数据列表功能和结构上比 PC 端要简单很多,如果调用统一的接口,会造成浪费。
2、客户端直接对 API 进行调用。
API 如果拆分的比较细,一次操作会发出多个请求才能拿到想要的数据,效率比较低
当需要多个请求时,还需要在客户端进行逻辑的组合,这样每个客户端可能都有一套自己的逻辑,不容易维护
服务如果进行拆分和合并,客户端代码需要同步进行修改
如果 API 进行了修改,第三方调用方需要配合修改,但这中间的沟通成本会很高,有时甚至不可行
要解决这些问题,就应该单独提供一个独立的公共 API,而不是直接让第三方开发人员或其他客户端直接访问平台公开的 API ,涉及到独立的公共 API,API 网关就要出场了。
API 网关是一种服务,是外部进入到应用程序内部的入口点。负责请求路由、身份验证、限流、熔断、流量监控等各种功能。
路由请求是 API 网关的核心功能,当网关收到请求时,会去查询路由映射关系,将请求指定到相应的服务。跟 Nginx 的反向代理有点类似。
路由的配置可以是静态的,也可以是动态的,比如在 Ocelot 中,可以在 json 文件中进行路由映射的配置,也可以使用代码的方式按照需求进行动态路由修改。
参考:https://github.com/oec2003/StudySamples/tree/master/UpdateOcelotConfig
在使用我们平台搭建的业务系统中,打开数据列表的详情,会做下面几件事情:
在 API 网关中可以对客户端提供统一入口调用,将这些来自不同服务的接口进行整合,统一输出,因为网关和服务都在内网,传输速度比较快,和客户端需要同时获取多个 API 请求相比,提升了效率。
作为一个平台,对外提供的公共 API 颗粒度往往不会很细,否则就不具备通用性了。如果针对不同的移动端(安卓、iOS)、或者特定的第三方平台,有一些细节上的区别。
网关可以为不同类型的客户端提供独立的 API。
这些扩展能力并非只有在 API 网关中才能实现,在后端服务中一样可以。但有些能力放到 API 网关中会更合适。
例如:身份认证、限流、熔断等,就是在请求还为触及服务时就已经处理了,会更加安全,也会让后端服务更稳固。
在 .NET Core 中可以选择的开源网关产品有:Ocelot、Kong、Envoy 等。
Ocelot:是一个基于.NET Core的轻量级 API 网关,用于构建和管理微服务架构中的 API 网关。作为一个开源项目,Ocelot 提供了一种灵活、可扩展的方式来集中处理请求路由、认证授权、请求转发、负载均衡和缓存等功能。
Kong:是在 Nginx 中运行的 Lua 程序。得益于 Nginx 的性能优势,Kong 相比于其它的开源 API 网关来说,性能方面是最好的。由于大中型公司对于 Nginx 运维能力都比较强,所以选择 Kong 作为 API 网关,无论是在性能还是在运维的把控力上,都是比较好的选择。
Envoy:是一个开源的高性能代理和通信中间件,专为云原生应用程序设计。它由 Lyft 开发并于 2017年成为 Cloud Native Computing Foundation(CNCF)的毕业项目之一。虽然 Envoy 本身是用 C++ 编写的,但它可以与任何语言和框架进行集成,包括 .NET Core。
网关的选择需要能解决当前面临的问题。关于各种网关的使用方式,以及优缺点的对比,后面再进行详细介绍。
不管是 API 的设计还是代码架构的设计,原则其实都差不多,要能够松耦合、易扩展、在满足现有需求的基础上,再多往前想一步,避免过度设计。
]]>对于上班族来说,一年中,国庆假期是难得的适合出行的日子,所以,从 16 年开始,每年的国庆都有出行的计划,直到疫情出现。
当决定在疫情后的第一个国庆长假出行时,就已经做好了大堵车和看人头的准备,我一直认为,在路上发生的一切都属于旅行的一部分。
关于路线的规划,遵循下面几个原则:
1、最远到达的距离尽量不超过 1000 公里,否则时间上不太够用;
2、从武汉到目标城市之间找一个城市中转,回程可以找另一个中转城市;
3、先大致确定一个方向,然后在地图上找目标城市和中转城市;
4、规划仅供参考,计划总是赶不上变化,所以酒店不宜一开始把每天的都订好(除非可以免费退的那种),不提前订又会遇到价格高和订不上的问题,需要权衡。
这个国庆,选择的是东南方向,目的地福州和平潭岛,去的时候经过景德镇,回来的时候从抚州中转,可以去附近的古镇转转,但是从平潭晒了一天太阳后,非常累,临时决定不去抚州了,订了一个资溪的度假酒店,休息调整。
恰恰是临时决定去的度假酒店,是女儿最喜欢的。
上午十点多出发,下午三点多到达景德镇,一路非常通畅,甚至一些路段一辆车都没有,完全感受不到节日的热闹。
在酒店安顿好后,便去了陶溪川,对这里印象很深刻:
1、有非常多的卖瓷器小物件的摊位,而且摊主都是年轻漂亮的小姐姐或帅气的小哥哥;
2、瓷器小物件价格很贵;
3、适合拍照。
网上查当地的美食,有一家叫「樊记牛骨粉」,看到的所有店子都叫「樊记牛骨粉」,倒也不用挑了,随便进一个即可。牛骨粉、冷粉、饺子粑、油条包糯米,一个店子搞定,油条包糯米挺不错,第二天去抚州弄,又买了一次。
离景德镇约 50 公里左右有一个瑶里古镇风景区,距离瑶里古镇十分钟车程的地方还有一处鲜为人知的古朴村落,这个地方叫做—东埠古街古码头。
这个地方没有商业化,可以看到村民在河里钓鱼、洗衣服。尽管是在国庆期间,也只能看见很少的几个游客。
离开东埠古街,继续驱车前往瑶里,果然商业化重的地方人就多,没能找到停车位,然后又临时找到了一个寒溪村。
寒溪村后面的山上是一片茶园,茶园最高处矗立着一座大型艺术品「大地之灯」,是著名建筑师马岩松的作品。
回到市区后,又去了九集小镇(规模巨大的一个夜市)、雕塑瓷厂、、御窑博物馆(需要提前预约)、抚州弄,基本上该去的地方都去了。
Day3(10 月 1)
因为担心堵车,早饭后简单逛了下三宝村就前往福州了,运气很好,高速上依旧没什么车。下午 6 点多到达酒店,酒店在三坊七巷附近。
晚上步行去了三坊七巷,途中经过了几个居民区,在大榕树的映衬下,非常的静谧,等到了三坊七巷,又人山人海,形成了鲜明的对比。
Day4(10 月 2)
2 号的安排是福州市内,上下杭、烟台山一圈逛下来已经是下午 4 点多了,地铁加步行的出行模式也让我惊喜的发现福州的地铁是免费的(截止到 12 月)。
晚上老婆提议去了福州大学附近的永嘉夜市,依然是乘坐免费的地铁。永嘉夜市的人刚刚好,不多不少,在国庆期间,只要人不多,体验就会很好。
Day5(10 月 3)
去海边是这次旅行女儿最期待的,但是否去平潭犹豫了很久,就担心人太多。最后查到一个非常小众的五星沙滩,在去仙人井的途中右转进去,经过一个村庄的小路,就到了,果然不负所望,人非常少。
中午从五星海滩离开,女儿还依依不舍。
接着导航风车森林公路,因为是单行线,路边可以停车,我们找了合适位置停好车,下车拍照,可能是因为台风「小犬」的原因,很多人劝退了,人比想象中的少。
在平潭,我们只去了五星海滩和看了海上风车,想想没有排队、没有拥堵,也就很知足了。
Day6(10 月 4)
没有提前预定抚州的酒店,就可以临时改变行程了。老婆找的资溪度假酒店非常不错,下高速几公里就到了。价格是这次行程中最便宜的,设施和环境是最好的。
女儿在酒店的儿童游乐场玩的很嗨。
Day7(10 月 5)
安全抵达武汉。
尽管是十一旅行,跑步可不能拉下。
10 月 1 号在景德镇跑了 10.01 公里来庆祝国庆。景德镇市区不大,酒店的正前方是陶溪川,我特意从反方向跑,最后还是绕到了陶溪川。
4 号是离开福州的日子,早上 5 点起床,跑向离酒店只有 3 公里的闽江公园,沿着江边跑了一个来回,然后跑回酒店。
江边的绿道以及城市的道路都被榕树包裹着,6 点多,太阳已经升起,但一点都不晒,非常舒服。
福州真的是一个非常适合跑步的城市。
酒店在一个山上,起床后就沿着上山的路往下跑,偶然发现旁边有一条环境优美的绿道,绿道总共 3 公里左右,跑了几个来回。
可能是因为山里氧气很足,523 的配速,心率只有 138。
1、可能是运气,也可能是路线和出行时间选择的好,一路上没有看人头、大堵车;
2、景德镇挺好,就是东西太贵,陶溪川的陶瓷卖的贵,酒店也贵,房间比床大一点的汉庭要都要 600 多;
3、福州之前不是很了解,这次印象很不错,特别是到处可见的古榕树让人印象深刻,城市绿化特别好;
4、如果不是万不得已,最好不要提前把酒店都订了,这样比较灵活,不好玩,随时可以换地方。
]]>本文就聊聊从开发工程师转变为产品经理可能会遇到的问题。
开发工程师大多是工程思维。
产品经理需要的是产品思维。
前些天,在团队内部的一个需求讨论会上,产品经理和开发工程师都有参与,我提出一个需求的 UI 交互思路,开发工程师马上指出:“如果这样做的话,实现上某某地方会调整比较大;某某地方新增的部分实现比较复杂。”
这就是典型的思考方式没有在一个维度上,从产品的角度,需要考虑的是功能交互的合理性、怎样才能真正解决用户的痛点。在这个前提之下,再考虑应该怎么实现,该怎样去做取舍。
工程思维更关注效率、如何实现,也就是「How」;而产品思维更关注场景、用户的真实需求,也就是「Why」。 在具体的产品开发中,产品思维和工程思维都很重要,需要将两者结合起来。
产品思维需要工程的配合与支撑,但如果只有工程思维,最后可能会做出一堆无用的功能。
不要以为自己的开发经验就是优势,而忽视了产品经理所需要的其他知识和技能。应该将你具备的开发技能变成加分项,而不要变成束缚。
除了我们熟悉的开发技能,还需要补齐产品经理的基础知识,如市场分析、需求分析、用户研究、产品设计、项目管理等,并且不断学习和实践。
对开发工程师来说,沟通能力也很重要,只不过平时的工作只需要按照要求高质量编写代码就行,不会刻意在这个方面去做提升。
产品经理就不一样,对内要和开发团队进行沟通,对外要和需求方进行沟通,沟通能力是一个必备技能。
在公司中,不同职位与不同资历的人,彼此的认知都不同,作为产品经理,需要团结团队里的每一个人,让大家朝着同一个目标努力。
产品经理需要跟所有人解释,某件事的重要性,某个功能为什么存在,某件事为什么要那么做等等。而且,因为认知的差别,你与每个人的沟通方式也要有差别,找到合适的沟通方式才能获得对方支持。
客户往往在提需求的时候还会赠送一个解决方案,会告诉你怎么做,而没有说为什么要这么做。开发工程师擅长的就是按照一个实现方案将功能实现。
当转为产品经理后,就不能被客户牵着鼻子走,需要去挖掘客户背后的真实需求,之前看《有效需求分析》时有个例子现在还记忆深刻:
晚上小孩吵着说要吃饼干,最后给了点面包,小孩吃完就乖乖睡着了,在这里吃饼干是方案,需求是小孩的肚子饿了,当没有饼干时,可以使用第二种方案,给他吃面包也可以解决这个需求问题。
搞懂了小孩的真实需求是肚子饿,而不是吃饼干或面包,事情就好办多。所以,只有了解了真实背景,挖掘了客户背后的诉求,才能设计出能够解决客户痛点的产品功能。
程序员就是一个需要持续学习的职业,不过当工作需要的技能非常熟练后,往往就疏于学习了。当转产品经理后更是需要有持续学习的能力。
如果做的是平台型产品,功能不断迭代,需要将不同类型的客户需求收集、分析、转化为平台功能,客户的类型在变化、客户的使用习惯也可能变化,不学习难以应对这种变化。
如果做的是业务型产品,你需要快速熟悉和了解一个行业,才能和客户、业务专家平等对话,在《麦肯锡方法》中介绍快速了解一个行业可以按下面三个步骤:
1、总结行业的 100 个关键词;
2、找三五个专家访谈,了解各种行业问题;
3、找三五本行业专业书籍,仔细阅读并找出共性。
上面的三个步骤就是学习的过程。
很多时候,开发工程师把一个功能的代码写完,提交测试了,可能还不清楚这个功能具体是做什么用的。这是因为看到的是点而不是面。
当转为产品经理后,就需要有全局的思维,从宏观上来思考问题,以满足实际业务场景为目的,然后再逐步分解到具体的功能点。如果还是像做开发时那样,产品会变成一堆零碎功能的堆砌,看似很强大,实则是四不像。
从开发工程师到产品经理,是一个比较大的跨越,首先需要提高认知维度,知道产品经理和开发工程师的不同,然后再转变思维模式,思维模式变了,最终做事的方式方法自然就会发生变化。
]]>隔行如隔山,一研究发现越野跑跟公路马拉松差别巨大。下面就说说区别和一些注意事项。
赛道:公路马拉松是在城市的平坦或略有起伏(上高架、大桥)的道路上进行,而越野跑是在山野、森林、沙漠等自然环境中进行,赛道地形复杂多变,有大量的上下坡,甚至需要攀爬或涉水。
距离:公路马拉松的标准距离是 42.195 公里,半程马拉松 21.0975 公里。而越野跑的距离则没有固定的标准,从几公里到几百公里都有可能,一般分为短程、中程、长程和超长程等不同级别。像我这次报的 30 公里,就属于难度比较低的级别。
时间:公路马拉松的比赛时间一般在 2 到 6 个小时之间,而越野跑的比赛时间则可能长达几天甚至几周,需要在山野中过夜或者自行选择休息点。
难度:公路马拉松的难度主要取决于配速和耐力,而越野跑的难度则涉及到技巧、体能、装备、安全、心理等多方面的因素,需要更高的综合素质和适应能力。
规则:公路马拉松的规则相对简单明确,主要是按照时间和顺序划分名次和奖励,而越野跑的规则则根据不同的赛事有所差异,一般会有强制装备、累计爬升、限时关门等要求,需要提前了解并遵守。
在越野跑中,有强制装备需要检查,这一点和公路马拉松有很大的区别,之所以要检查强制装备,是为了安全考虑,特别是 21 年的 522 事件后,强制装备更加严格了。
相比马拉松跑鞋,越野跑鞋需要提供更好的抓地力和保护,以应对更复杂的地形。提及比较多的有下面几个品牌:
鞋子非常重要,能力范围内需要买好一点的。
用于装载能量补给、水、急救包等必要物品。30 公里距离的 5L 就够用了,在朋友的推荐下,准备选购 UG 的 6 升越野包。
如果你的腰包能装下所有强制装备,不用背包也可以。
识别身份,需要全程佩戴。
计时芯片需要好好保护,否则跑的累死,没有成绩了。需要注意的是保温毯可能对计时芯片有屏蔽作用,不要放在一起。
应该是超过 30 公里的距离必须全程携带,关键时候可以救命。使用时建议尽量贴身,打结后套住头部,然后包覆核心躯干,再穿上外套,防风,隔湿,保温。
急救毯尺寸不小于 1.5m x 2.0m 。
急救毯属于消耗类强制装备,使用后不允许丢弃,须凭已消耗的装备或装备包装到达站点更换新的装备。
大部分越野包都有配备,如果使用的是腰包,需要单独佩戴。仅在遇到危险,需要帮助时使用。
急救用品也属于消耗类强制装备,可自用或给需要帮助的选手使用,消耗后凭有效证明可到补给站补充。
通常 50 公里或以上组别才需要头灯。
建议服装面辅料为抓绒、羊毛或其他具有保暖速干效果的材质,保暖上衣重量不低于200g。
不一定是能量胶,所有高热量的食品都可以,有的赛事会要求必须携带不低于其组别要求数量(能量值)的能量食品。
我用的佳明 245 没有越野功能,不过这次有人带着一起跑,倒不着急买新的手表,目前关注的手表:佳明 255、265 、955,高驰的 apex 2 pro 等。
全指手套、帽子、太阳镜、登山杖,防晒霜,防晒臂套等。
从马拉松转到越野跑,训练的重点应该从速度和耐力转向力量和平衡,当然为了能跟得上朋友的速度,速度也需要有所提升。
下面是我的计划:
1、比赛时间为 12 月 3 号,还有 3 个月的时间可以训练。
2、平均每个月的跑量在 200 公里以上,增加跑量是因为越野跑需要更强的耐力和体能,不过在训练的同时也需要注意休息,让身体能够恢复。
3、每周一到两次的间歇跑,400m X 10 ,配速 430,通过间歇跑来提升下速度。
4、3 个月内,至少两次长距离的 LSD(25~28 公里)。
5、加强核心力量,可以提高你在复杂地形中的稳定性和平衡性,可以进行卷腹、单杠提膝等练习。
6、加强腿部力量,即便是公路马拉松,腿部力量也非常重要,可以减少受伤的风险,可以进行深蹲、爬楼等练习。
7、每周至少一次的爬升训练,选择一些有坡度的路段或者楼梯进行上下跑,需要注意调整步幅和节奏,避免过度用力或气喘。
8、技巧练习,越野跑中有很多的技巧,如下坡跑、越障跑、使用登山杖等,这些技巧都需要你通过实践和练习来掌握和熟练。
9、找个山路跑跑,适应下环境。
全球有很多著名的越野赛事,例如:
国内的有香港 100、崇礼 168、莫干山越野挑战赛、柴古唐斯越野挑战赛等。
1、参赛选手在领取参赛物品前,必须携带安全强制装备清单中所有装备接受检查,不符合条件者将无法领取参赛物品。
2、选手在比赛过程中必须全程携带与所参赛组别对应的所有安全强制装备,组委会将在起终点及赛道中进行安全强制装备检查,参赛选手须无条件配合,检查不合格者将接受组委会的处罚。
3、消耗类强制装备(急救毯、能量食品、急救包),使用后不允许丢弃,须凭已消耗的装备或装备包装到达站点更换新的装备。
4、组委会有权在赛前根据天气情况,追加强装的种类及数量,注意官方信息的发布。
5、安全第一,越野跑中可能会遇到各种危险和困难,如迷路、受伤、脱水、低温、高原反应等,所以你需要做好充分的准备和预防,携带必要的装备和物品,并且遵守赛事规则和指示,在遇到问题时及时求助或放弃。
6、尊重自然,越野跑中你会接触到很多美丽而脆弱的自然景观和生态系统,所以你需要尊重自然,保护环境,不要破坏植被或动物,不要乱扔垃圾或其他物品,并且尽量减少对自然资源的消耗。
敬畏自然,安全最重要,希望 12 月份能安全完赛,也期待明年能挑战更高级别的赛事。
]]>这个一个辅助锻炼的网站,选择锻炼器械,也包括自重,然后选择需要锻炼的部位,就会给出一些列的视频教程。
对于健身初学者来说,很有帮助。
一个技术学习路线图的网站,提供前端、后端、全栈等,还有各种具体的技术,如:Python、.NET Core、Vue 等。
每一个知识点的详情页面中会提供相关的学习资源。
https://www.youtube-dubbing.com/
这是一款 Chrome 浏览器插件,安装插件后,在 YouTube 视频的下方会出现「开始翻译播放」,点击此按钮,稍等片刻,视频语音会变成 AI 的翻译后的中文语音:
https://github.com/wangdoc/typescript-tutorial
TypeScript 是由 Microsoft 开发和维护的编程语言,它是 JavaScript 的超集,它在 JavaScript 的基础上添加了一些新的特性和语法,使开发者能够更容易编写可靠、可维护的代码。
这本书的作者是阮一峰,阮一峰的博客很早就在关注,一个很大的特点就是很擅长深入浅出地去讲解各种知识点。
一个 APP UI 截图资源的网站,可以按照公司和行业进行筛选。多看看一些优秀的设计方案,对产品经理激发灵感很有帮助。
记录一些大公司:谷歌、苹果和微软等已经关停或未来会关停的产品和服务,可以按时间轴去进行查看。
https://dingtalk.com/qidian/home
天眼查、企查查的竞品,刚上线不久,趁现在还是免费,可以赶紧查起来。
一个帮助我们了解现在各种 AI 工具动态的网站,网站提供了非常全面的 AI 工具分类以及标签供你选择,包括功能介绍、免费与否和用户评论。
在现在 AI 工具层出不穷的现状下,用这个网站正好可以做下筛选。
如果你对人工智能、机器学习、深度学习、NLP 等有兴趣,那么这个网站可以帮你更好了解相关的信息和发展情况。
汇聚了各类新闻、博客、播客等。
YouTube Music 没有 Mac 版,最近发现 Pake 的 Github 主页上已经提供了 YouTube Music 客户的下载。
Pake 是一个可以将网页应用打包为客户端的开源项目,我在 Mac 上使用即刻就是使用 Pake 制作的。
https://simone.computer/#/webdesktops
可以在这个网站中体验 160 多个桌面操作系统,上图是 Windows 98 的系统界面。开发者非常热衷于复古计算机,并且非常喜欢 90 年代和 2000 年代初的仿拟设计用户界面。
包括开发者的主页也是这个风格:https://simone.computer
支持 Windows/macOS/ios,并且都是免费的。它支持数十种格式与 PDF 之间的转换,并且支持编辑、压缩、签名、文本提取、OCR文字识别等功能。
亮点就是接入了 AI 聊天机器人,没有聊天限制,扫描版一样可以解析。
一直都觉得 Mac 上的访达不太好用,最近发现了 Spacedrive 这个工具,不过还需要排队。
Spacedrive 是一个强大的文件浏览器,可以帮助你更好地管理和组织你的文件。支持 macOS、Windows、Linux、iOS 和 Android。
期待早日可以试用。
https://docs.qq.com/aio/DWVRkZ1RUWHRsdU1J?p=3xevUv9be6t0B1pY9ZK5AA
一些大师关于写作的言论,有王小波、莫言、余华、史铁生等。
https://s75w5y7vut.feishu.cn/docs/doccn3BatnScBJe7wD7K3S5poFf
认知偏差知识手册。
]]>我算是博客园的老用户了,06 年注册,发表过文章 300 多篇,曾经也到过前 100 名,近些年玩独立博客、写公众号后,使用频率就没那么高了,排名也因后来规则改变下降到 2000 多。但对博客园,依然怀有深深的感情。
今天又逛了逛园子,回想起之前的往事,这些名字仍然历历在目:老赵、张善友、宝玉、Terrylee、 artech、 anytao、路过秋天、金色海洋 、吉日、灵感之源、装配脑袋、司徒正美等。如今,也就张善友和宝玉近期在园子中有更新。
现在各类网站都充斥着广告、带货、周边、卖课,博客园算是一股清流,可能也正是如此,才会导致今天的经营困难。
希望博客园能度过这次难关,也能找到好的商业模式,慢慢走向正轨。虽然不常回去,但当我想回的时候,他还能安安静静的在那里。
购买会员通道:https://cnblogs.vip
]]>这样不管是发布公众号(公众号会从图床下载然后上传)还是博客,图片地址的问题就解决了。但会有另外一个问题,网上下载的图片或者截图的图片通常比较大,现在我都是手动将文件转为 webp 格式,然后重新复制到 Typora 中,非常麻烦。
后来发现 PicGo 有插件机制,一个想法就诞生了:
下面就来讲解下怎样来实现这个插件的开发。
1、全局安装 picgo
1 | sudo npm install picgo -g |
2、使用 picgo 命令创建一个插件项目
1 | picgo init plugin convert-to-webp |
PicGo 的插件名称要求必须带有前缀:picgo-plugin
,否则不能识别,使用 picgo 脚手架创建插件项目时,后面的名称只需写真实名称即可,否则前缀会重复。
执行上面命令后,会有命令行的向导,需要填写一些关键信息,代码如下:
1 | ? Plugin name: convert-to-webp |
3、向导中推荐使用 TS 语言,我这里使用的就是默认选项,所以需要全局安装 typescript:
1 | npm install -g typescript |
4、本插件的目的需要将上传的图片转为 webp 格式,需要 sharp 库,安装命令如下:
1 | npm install -g sharp |
1、使用脚手架创建的代码只有一个 index.ts 文件,如下图:
2、在根目录中执行npm install
安装依赖。
3、index.ts 代码如下:
1 | import path from 'path'; |
4、执行命令 npm run build
进行打包,打包后会生成 dist 目录,如下图:
1、在 PicGo 的插件设置中,导入本地插件:
2、目录选择 dist 目录所在的目录:
3、安装成功后如下图:
4、这时可以截图粘贴到 Typora 中,点击右键上传图片:
上传成功后,会发现已经变成了 webp 格式:
1、如果插件的代码有修改,可以在 package.json 文件中升级一个版本,重新打包:
2、卸载插件,不卸载进行本地插件导入,会提示成功,但实际没有成功:
3、重新本地导入插件。
4、导入成功后,需要更新插件:
5、更新成功后,需要重启才能生效:
6、重启后,如果看到版本变为 1.0.1 表示更新成功:
在导入本地插件或者进行图片上传的过程中,有可能会出现错误,错误日志会记录在 picgo.log 文件中,在 Mac 系统中,该文件的路径如下:
1 | ~/Library/Application\ Support/picgo/picgo.log |
比如:我们在代码中可以通过下面的代码来输出日志:
1 | ctx.log.info('ctx.input.path'+imgPath) |
在 picgo.log 中就会输出日志:
通过这个日志文件的内容,可以进行错误的排查。
本插件的源码已经上传到 Github,地址如下:
]]>这样不管是发布公众号(公众号会从图床下载然后上传)还是博客,图片地址的问题就解决了。但会有另外一个问题,网上下载的图片或者截图的图片通常比较大,现在我都是手动将文件转为 webp 格式,然后重新复制到 Typora 中,非常麻烦。
后来发现 PicGo 有插件机制,一个想法就诞生了:
下面就来讲解下怎样来实现这个插件的开发。
1、全局安装 picgo
1 | sudo npm install picgo -g |
2、使用 picgo 命令创建一个插件项目
1 | picgo init plugin convert-to-webp |
PicGo 的插件名称要求必须带有前缀:picgo-plugin
,否则不能识别,使用 picgo 脚手架创建插件项目时,后面的名称只需写真实名称即可,否则前缀会重复。
执行上面命令后,会有命令行的向导,需要填写一些关键信息,代码如下:
1 | ? Plugin name: convert-to-webp |
3、向导中推荐使用 TS 语言,我这里使用的就是默认选项,所以需要全局安装 typescript:
1 | npm install -g typescript |
4、本插件的目的需要将上传的图片转为 webp 格式,需要 sharp 库,安装命令如下:
1 | npm install -g sharp |
1、使用脚手架创建的代码只有一个 index.ts 文件,如下图:
2、在根目录中执行npm install
安装依赖。
3、index.ts 代码如下:
1 | import path from 'path'; |
4、执行命令 npm run build
进行打包,打包后会生成 dist 目录,如下图:
1、在 PicGo 的插件设置中,导入本地插件:
2、目录选择 dist 目录所在的目录:
3、安装成功后如下图:
4、这时可以截图粘贴到 Typora 中,点击右键上传图片:
上传成功后,会发现已经变成了 webp 格式:
1、如果插件的代码有修改,可以在 package.json 文件中升级一个版本,重新打包:
2、卸载插件,不卸载进行本地插件导入,会提示成功,但实际没有成功:
3、重新本地导入插件。
4、导入成功后,需要更新插件:
5、更新成功后,需要重启才能生效:
6、重启后,如果看到版本变为 1.0.1 表示更新成功:
在导入本地插件或者进行图片上传的过程中,有可能会出现错误,错误日志会记录在 picgo.log 文件中,在 Mac 系统中,该文件的路径如下:
1 | ~/Library/Application\ Support/picgo/picgo.log |
比如:我们在代码中可以通过下面的代码来输出日志:
1 | ctx.log.info('ctx.input.path'+imgPath) |
在 picgo.log 中就会输出日志:
通过这个日志文件的内容,可以进行错误的排查。
本插件的源码已经上传到 Github,地址如下:
]]>