月盾的博客

Thinkpad T450加装固态硬盘记录

Thinkpad T450加装固态硬盘记录

月盾
2016年初花大价钱买的thinkpad t450准备用来工作,结果发现性能太差,根本满不了需求。于是就放家里日常使用,即使这样还是无法忍受它的慢,慢到什么程度呢?开机到桌面3分钟,再到打开程序3分钟。这还不如我2010年买的2G内存i3电脑性能好。后来把内存从4G加到8G没有明显提升,那就剩下CPU和机械硬盘的问题了,CPU是低压版i5 4300U,和主板焊在一起,没法升级。前几年就想升级硬盘,但是那时候固态硬盘价格不便宜,也没有着急升级。直到最近才忍受不了,下定决心升级一下。 升级过程并不顺利…… 首先是确定支持的接口,thinkpad t450已经装了一块16G固态硬盘,接口是M.2(即NGFF,现在已经改名为M.2了) 真实尺寸很小,22*40mm大小,就是2240型号。2280型号意思就是宽22长80,自然就是更大尺寸的。选择的时候要注意不要选错。 安装好以后使用傲梅分区助手进行系统迁移(之后遇到很多问题),然后在bios里修改启动顺序,结果左上角的光标一直闪,无法进入系统,修改为原HDD硬盘后还是可以启动的。网上有说法是磁盘盘符冲突,按照方法在注册表中调换盘符后还是进入不了系统,甚至原来的磁盘系统也进入不了。 无奈,只能重装系统。但是不能在原来的盘里装,需要直接装套SSD固态硬盘上。准备了8G U盘,4G肯定是不够的,win10系统就有4.5G以上了。下载winpe,直接安装到U盘里,然后将下载好的win10系统拷贝到winpe工具箱里。 再修改启动顺序重启系统,这时候也出现问题,识别不到U盘,我是在系统启动后再插上U盘,如果直接插上再启动就会识别不出来。进入到pe系统后首先通过系统安装器安装系统,又出现找不到iso系统文件的情况,重插U盘解决。但是选择目标盘后MBR始终是黄色,无法变成绿色,最后通过CGI工具还原的方式成功安装。 安装成功重启又出现boot menu界面,死活进入不了系统,这个是由于在bios中修改过启动方式引起的。有uefi和legacy两种模式,设置为both兼容模式启动。至此算是安装成功。 由于加了SSD固态硬盘,原来的盘符也有变化,SSD成了C盘,原来的C盘变成了D盘,以此类推。手动把原盘中的快捷方式拷贝过新盘,都用不了,可以通过属性-修改路径的方式修正。个别软件缺少dll动态链接库也可以从原盘找到,有两个目录可以查找windows-System32,windows-Syswow64。 总结:系统启动时间由3分钟提升到了30秒,到桌面也可以直接操作,不需要再等3分钟才能做操作。

Ld Warning Object File Was Built for Newer MacOS Version Than Being Linked

月盾
go run和go build会报出一连串下面这样的警告信息,虽然不影响程序运行,但是看着难看。 # command-line-arguments ld: warning: object file (/var/folders/9p/2x3ls9kn7qb59hf9l9mtfs8s6rbmfg/T/go-link-3994413096/000000.o) was built for newer macOS version (12.0) than being linked (11.0) 解决方法:CGO_CFLAGS=-mmacosx-version-min=10.12 go run main.go,运行前加CGO_CFLAGS=-mmacosx-version-min=10.12参数可以解决。 vscode中可以通过配置launch.json文件实现: { "version": "0.2.0", "configurations": [ { "name": "Launch Package", "type": "go", "request": "launch", "env": { "CGO_CFLAGS":"-mmacosx-version-min=10.12" }, "program": "${fileDirname}", "args": [], "cwd": "${fileDirname}" } ] } 或者从文件中读取环境变量: { "version": "0.2.0", "configurations": [ { "name": "Launch Package", "type": "go", "request": "launch", "envFile": "${workspaceFolder}/.env", "program": "${fileDirname}", "args": [], "cwd": "${fileDirname}" } ] } 如果是在vscode中执行测试代码,则需要设置settings.

Sveltekit1.0后的使用感受

月盾
之前提到过关于svelte框架——sapper和sveltekit的发展,已经是1年前的事了,其中提到过svelte的框架sapper和sveltekit,sapper已经明确不再更新了,官方推荐的是sveltekit。然后经过两年的迭代更新,于2022年12月终于推出了1.0版本。可以看看官方博客对1.0的介绍Announcing SvelteKit 1.0。 然后谈谈个人感受。 优点: sveltekit集成的还不错,开发体验可以,使用官方提供的脚手架创建的项目就可以直接使用,不需要做任何配置。热更新,响应速度快,支持typescript等等,该有的都有了。 缺点: 又是一个全新轮子,这也是整个前端的通病,除了js是通用的,其他的都能给你整出花来,sveltekit在1.0版本之内已经有破坏性的更新,一年前创建的新项目,一年后基本不能用了。 至于要不要使用,那就看个人情况了,如果你厌烦了其他框架,倒是可以尝尝鲜,如果你想以此来做长期项目的话,个人就不推荐了,毕竟太新,而且向下兼容又做的不好,隔三差五一个破坏性更新,这种折腾劲恐怕没几个人受得了。而且对自己的技术也不能积累,长此以往并没有好处。 sveltekit官方文档

Elasticsearch批量insert和批量upsert

月盾
golang版本的elasticsearch批量插入和批量更新方法如下: package main import ( "github.com/elastic/go-elasticsearch/v8" "github.com/elastic/go-elasticsearch/v8/esutil" ) func main() { list:=make(User, 0) bulkES(list) } func bulkES(list []User) error { indexer, err := esutil.NewBulkIndexer(esutil.BulkIndexerConfig{ Index: "search-user", Client: ES, }) if err != nil { return err } for _, v := range list { data, err := json.Marshal(v) if err != nil { return err } err = indexer.Add( context.Background(), esutil.BulkIndexerItem{ Action: "index", Body: bytes.NewReader(data), }, ) if err != nil { return err } } indexer.

go http响应乱码

月盾
golang请求接口返回的数据乱码,原因之一是请求头设置了"Accept-Encoding": "gzip, deflate, br",那么如果服务器支持的话响应的数据就会经过gzip,deflate,br等方式的压缩,解决方式是对数据解压,或者可以不设置接收方式,即"Accept-Encoding": "" import "github.com/andybalholm/brotli" import "compress/flate" import "compress/gzip" // 检测返回的body是否经过压缩,并返回解压的内容 func contentDecoding(res *http.Response) (bodyReader io.Reader, err error) { switch res.Header.Get("Content-Encoding") { case "gzip": bodyReader, err = gzip.NewReader(res.Body) case "deflate": bodyReader = flate.NewReader(res.Body) case "br": bodyReader = brotli.NewReader(res.Body) default: bodyReader = res.Body } return }

chrome开发者工具显示接口完整路径

月盾
chrome浏览器的开发者工具是前端开发必不可少的工具,其中接口查看是最常用的功能之一。默认情况下显示如下: 主要是红框内的接口显示只有很短的名称,并没有完整的显示出整个接口,这对于一些最后名称一样的接口就不能知晓是哪个接口了,只能点击查看详情。我们希望的当然是能够显示完整的接口,实际上也是能做到的。 在名称栏上右击: 可以显示很多选项,其中路径和网址就是我们先要的,点击以后就可以显示完整的接口路径了。

postman header中自动添加cookie

月盾
使用postman进行接口测试时,经常需要使用到cookie参数。我们会先调用登陆接口,得到sessionid,然后使用sessionID来调用其他需要登陆的接口。 但是有些情况下,cookie不会自动添加到header中,总是需要手动添加cookie。或许又发现有的接口却能自动添加上cookie,为什么会区别对待呢? 其实这是postman根据登陆接口返回的cookie来操作的,如果cookie的path指定来某个路径,那么所有在这个路径下的接口都会自动加上cookie。

最近关于公司的一些事

月盾
我所在公司掌门教育于6月9日挂牌上市,此时已经处在在线教育政策风波中。也就在一个多月后的7月24传出了更严厉的“双减”政策,几乎是一棍在打死了在线教育。 于是公司也立即做出了反应,7月28开启裁员动作。不同事业部裁员比例不同,优课,少儿应该算是全线砍掉或合并到1对1事业部。我所在的运营部算是裁员最少的,大概是30%的裁员比例。全技术部大概是70%的比例。 也就是从8月开始来来回回搬了四五次位子,随着人员减少,办公大楼一栋一栋的腾出来,最后整个海伦路办公区全部撤离到中瑞。 写该文也是事情过去两三个月了,心中五味杂陈,不想提更多,对于公司倒是没有什么怨言,更多的是难过,这是我工作五年半的地方,还是有感情的,在国家政策下,个人乃至公司都是束手无策。 后来大多数人都找好了新工作,但是还是有不少熟人决定留下来在新部门奉献自己,真心希望公司能够坚持下去,做的更好。

事件驱动编程、消息驱动编程、数据驱动编程

月盾
事件驱动 事件驱动机制就是: 让驴拉磨,它不拉,你用鞭抽一下,它就开始拉了。然后又停了,你再抽一下,它又继续拉了 这叫用“鞭”驱动“驴”拉磨 在程序里,程序停止在那不动,你点击一个按钮,它就有反应了,过一会,又没反应了,你再点一下,它又继续运行。 这叫用“事件”驱动“程序”运行 0. 基本概念 窗口/组件 事件 消息(队列) 事件响应(服务处理程序) 调度算法 进程/线程 非阻塞I/O 程序的执行可以看成对CPU,内存,IO资源一次占用 现代操作系统支持多任务,可以分时复用上述资源. 1. 为什么采用事件驱动模型? 事件驱动模型也就是我们常说的观察者,或者发布-订阅模型;理解它的几个关键点: 首先是一种对象间的一对多的关系;最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方); 当目标发送改变(发布),观察者(订阅者)就可以接收到改变; 观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的),目标无需干涉;所以就松散耦合了它们之间的关系。 2. 代码执行流程 在传统的或“过程化”的应用程序中,应用程序自身控制了执行哪一部分代码和按何种顺序执行代码。从第一行代码执行程序并按应用程序中预定的路径执行,必要时调用过程。 在事件驱动的应用程序中,代码不是按照预定的路径执行-而是在响应不同的事件时执行不同的代码片段。事件可以由用户操作触发、也可以由来自操作系统或其它应用程序调度算法的消息触发、甚至由应用程序本身的消息触发。这些事件的顺序决定了代码执行的顺序,因此应用程序每次运行时所经过的代码的路径都是不同的。 3. 事件驱动模型 在UI编程中,常常要对鼠标点击进行相应,首先如何获得鼠标点击呢? 方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点: CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢? 如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘; 如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;所以,该方式是非常不好的。 方式二:就是事件驱动模型目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下: 有一个事件(消息)队列; 鼠标按下时,往这个队列中增加一个点击事件(消息); 有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如onClick()、onKeyDown()等; 事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;如图: 4. 事件驱动处理库 select poll epoll libev 消息驱动 事件驱动机制跟消息驱动机制相比 消息驱动和事件驱动很类似,都是先有一个事件,然后产生一个相应的消息,再把消息放入消息队列,由需要的项目获取。他们的区别是消息是谁产生的 消息驱动:鼠标管自己点击不需要和系统有过多的交互,消息由系统(第三方)循环检测,来捕获并放入消息队列。消息对于点击事件来说是被动产生的,高内聚。 事件驱动:鼠标点击产生点击事件后要向系统发送消息“我点击了”的消息,消息是主动产生的。再发送到消息队列中。 事件:按下鼠标,按下键盘,按下游戏手柄,将U盘插入USB接口,都将产生事件。比如说按下鼠标左键,将产生鼠标左键被按下的事件。 消息:当鼠标被按下,产生了鼠标按下事件,windows侦测到这一事件的发生,随即发出鼠标被按下的消息到消息队列中,这消息附带了一系列相关的事件信息,比如鼠标哪个键被按了,在哪个窗口被按的,按下点的坐标是多少?如此等等。 要理解事件驱动和程序,就需要与非事件驱动的程序进行比较。实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,会不断地检查这个条件,直到条件满足,这是很浪费cpu时间的。而事件驱动的程序,则有机会释放cpu从而进入睡眠态(注意是有机会,当然程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu. 再说什么是事件驱动的程序。一个典型的事件驱动的程序,就是一个死循环,并以一个线程的形式存在,这个死循环包括两个部分,第一个部分是按照一定的条件接收并选择一个要处理的事件,第二个部分就是事件的处理过程。程序的执行过程就是选择事件和处理事件,而当没有任何事件触发时,程序会因查询事件队列失败而进入睡眠状态,从而释放cpu。 事件驱动的程序,必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件。 事件驱动的程序的行为,完全受外部输入的事件控制,所以,事件驱动的系统中,存在大量这种程序,并以事件作为主要的通信方式。 事件驱动的程序,还有一个最大的好处,就是可以按照一定的顺序处理队列中的事件,而这个顺序则是由事件的触发顺序决定的,这一特性往往被用于保证某些过程的原子化。 目前windows,linux,nucleus,vxworks都是事件驱动的,只有一些单片机可能是非事件驱动的。 事件模式耦合高,同模块内好用;消息模式耦合低,跨模块好用。事件模式集成其它语言比较繁琐,消息模式集成其他语言比较轻松。事件是侵入式设计,霸占你的主循环;消息是非侵入式设计,将主循环该怎样设计的自由留给用户。如果你在设计一个东西举棋不定,那么你可以参考win32的GetMessage,本身就是一个藕合度极低的接口,又足够自由,接口任何语言都很方便,具体应用场景再在其基础上封装成事件并不是难事,接口耦合较低,即便哪天事件框架调整,修改外层即可,不会伤经动骨。而如果直接实现成事件,那就完全反过来了。 什么是数据驱动编程 最近在学习《Unix编程艺术》。以前粗略的翻过,以为是介绍unix工具的。现在认真的看了下,原来是介绍设计原则的。它的核心就是第一章介绍的unix的哲学以及17个设计原则,而后面的内容就是围绕它来展开的。以前说过,要学习适合自己的资料,而判断是否适合的一个方法就是看你是否能够读得下去。我对这本书有一种相见恨晚的感觉。推荐有4~6年工作经验的朋友可以读一下。 正题: 作者在介绍Unix设计原则时,其中有一条为“表示原则:把知识叠入数据以求逻辑质朴而健壮”。结合之前自己的一些经验,我对这个原则很有共鸣,所以先学习了数据驱动编程相关的内容,这里和大家分享出来和大家一起讨论。 数据驱动编程的核心 数据驱动编程的核心出发点是相对于程序逻辑,人类更擅长于处理数据。数据比程序逻辑更容易驾驭,所以我们应该尽可能的将设计的复杂度从程序代码转移至数据。 真的是这样吗?让我们来看一个示例。 假设有一个程序,需要处理其他程序发送的消息,消息类型是字符串,每个消息都需要一个函数进行处理。第一印象,我们可能会这样处理: void msg_proc(const char *msg_type, const char *msg_buf) { if (0 == strcmp(msg_type, "inivite")) { inivite_fun(msg_buf); } else if (0 == strcmp(msg_type, "tring_100")) { tring_fun(msg_buf); } else if (0 == strcmp(msg_type, "ring_180")) { ring_180_fun(msg_buf); } else if (0 == strcmp(msg_type, "ring_181")) { ring_181_fun(msg_buf); } else if (0 == strcmp(msg_type, "ring_182")) { ring_182_fun(msg_buf); } else if (0 == strcmp(msg_type, "ring_183")) { ring_183_fun(msg_buf); } else if (0 == strcmp(msg_type, "ok_200")) { ok_200_fun(msg_buf); } else if (0 == strcmp(msg_type, "fail_486")) { fail_486_fun(msg_buf); } else { log("未识别的消息类型%s\n", msg_type); } } 上面的消息类型取自sip协议(不完全相同,sip协议借鉴了http协议),消息类型可能还会增加。看着常常的流程可能有点累,检测一下中间某个消息有没有处理也比较费劲,而且,没增加一个消息,就要增加一个流程分支。

为什么夜间电费只收半价?

月盾
你知道什么是分时电价吗?它是指在一些城市中,一般从夜里10点到早晨6点,电费只需按白天电费的半价支付。这种将电费分时段计算的方式有什么意义呢? 我们知道,人们的用电量同作息时间密切相关,白天工作时段和傍晚的生活时段用电量大,入夜大多数人休息后用电量就降得非常低,往往还不到白天的30%。可是,这个现象对电力公司来说却是个大麻烦,因为发电厂的发电机可不像家里的电器那样能够做到说开就开,说停就停。发电厂的锅炉炉膛有十几层楼房那么高,里面有几百吨的水和蒸汽,温度为300~600℃,庞大的汽轮机也同样处在这个温度下,要它们冷却下来可不容易。同时汽轮机和发电机的转子有几十吨重,转速达到3000转/分。两者巨大的热惯性和动惯性使发电机很难在短短几小时里停下来。停机如此,开机更需耗费时间。因此,对于发电厂来说,要想在晚上关上机器,到了早上又开始运转,这样的操作是行不通的。即便是要在晚上少发一点电,也要付出降低发电效率的巨大代价。因此,增加夜间用电量是电网节能减排的一个有效措施。 发电机涡轮 分时电表 既然发电量在夜间无法大量减少,而白天大家都用电的时候发电量往往又会不够。那么最好的办法就是鼓励用户尽量把白天需用电做的工作挪到晚上来做。人们形象地把白天用电多比喻成山的“峰”,把夜间用电少比喻成山的“谷”,那么把白天的一部分用电移到夜间用,就可以起到“削峰填谷”的节能作用。这么一来,发电机保持在波动不太大的持续工作状态中,它的能源消耗最少,发电效率最高,生产也更安全。采取夜间电费半价的方式,就是以低价电费来鼓励电力用户积极参与“削峰填谷”的用电方式,共同为电力节能减排做出贡献。