通过Siri语音控制台式电脑开关机

背景 女儿经常捣蛋关掉爸爸的台式电脑(上面虚拟机跑着集群),防不胜防,爸爸只好放大招了,使用Siri语音控制台式电脑开关机,物理关机键已被架空。当然,女儿再按关机键爸爸还会装作生气的样子让她开心地”得逞”。注意:最简单最好的方案是使用WOL协议+etherwake,因为我的电脑没有PCIe网卡和有线网络(目前使用的是USB外置的wifi接收器),只好另寻出路。 准备材料 树莓派4b x1 NodeMCU x1 5V光耦隔离继电器 x1 USB线供电线 x1 杜邦线 若干 面包板 x1 安装树莓派系统 树莓派4b刷上官方64位系统 64位系统下载:http://downloads.raspberrypi.org/raspios_arm64/images/ 使用官网安装工具安装系统:https://www.raspberrypi.org/software/ 树莓派需要安装服务 […]

记一次内存泄漏问题的排查与修复过程

事件回顾: 我们收到容器服务告警通知,某个容器应用服务间歇性崩溃重启,该应用是使用Golang语言编写的,部署在阿里云k8s。 登录阿里云后台,查看容器应用监控指标,发现该应用是因为内存耗尽而崩溃: 从上图可以看出,大约每隔80分钟,应用耗尽内存崩溃重启。该应用部署了多个实例,每个实例内存上限为1G,平时内存占用是比较稳定,只在高峰期有波峰,其它时间几乎一条直线躺平,现在显然是不正常的。 排查步骤: 1、重现问题 把线上代码拉到本地配置模拟环境编译运行,发现在本地也能重现出内存泄漏,这样问题就很好排查了。但比较恶心的是,内存泄漏比较慢,一开始是看不出有啥问题的,要过比较长一段时间才能看出内存有在慢慢上升的,排查问题很耗时间(编译启动应用后,要观察一段时间才能判断应用是否内存泄漏)。 为此我编写了一个简单的脚本监控内存变化: 监听结果: 结果显示内存占用在不断攀升。 2、定位导致异常的代码。 在此期间已有多人提交分支代码并上线,commit比较多,不清楚到底是哪个提交代码导致问题的。因此先定位导致出现问题的代码在哪里,这里简单粗暴使用git bisect进行二分法定位。大概原理是:先回滚至前80个提交,测试发现没有内存泄漏,再回滚到前40个提交,没发现问题,再回滚至前20个提交,出现内存泄漏。OK,导致问题的代码在前40到前20的提交之间,范围缩小了。随后再次在40到20之间进行二分法,直到找到最终的有问题的代码提交,然后跟前一个代码提交进行diff对比,即可知道修改了哪些代码导致出现问题。先找出最近正常的提交, 例如7天前是正常的,查找7天前的记录,然后回滚,编译运行检查是否正常。 找到最近的正常提交后,就可以使用git bisect定位出问题的提交了: 3、使用性能剖析工具pprof分析内存 go tool pprof […]

分页设计指南

分页无处不在,查看邮件、刷朋友圈、浏览新闻等,我们无不是在一页一页地获取信息。对于服务端来说,针对不同场景,分页有不同的设计方案,本文主要以MySQL为例(数据样例见文末)。 页码分页 最简单的是通过页码来分页: 不要嗤之以鼻,如果只是系统初期的小小的后台管理系统,这样的分页已经足够了,没必要整得花里胡哨的,够用就好。 随着业务增长,系统越来越大了(大部分系统没有这样的福气),上面的分页方法逐渐显得有点捉襟见肘了。过早的优化是万恶之源,因此咱们一开始只是简单粗暴地分页也无可厚非,现在我们要逢山开路遇水架桥。 假设我们的用户量单表去到了1000万(这里不考虑分库分表的情况),瓶颈出现了: 这个查询需要好几秒,因为user表用的是InnoDB存储引擎,它需要检查所有记录才能得出总行数,这是为了支持MVCC而付出的代价。那用索引字段代替星号会不会有性能提升?很遗憾,几乎没有。其实使用星号MySQL会自动帮你优化选择最小的索引。那怎么办?第一,你可以选用MyIASM来避免这个问题(它是直接从元数据中读取总数),如果你可以放弃使用事务特性。第二,使用缓存,弊端是当查询条件复杂的时候,维护缓存也是一个麻烦,而且构建缓存时依然很慢。第三,无解,那干脆不用select count(*)了,那用什么呢?这个下面会谈到。 游标分页 我们还发现有些用户喜欢从最后一页倒回来看,弄出类似下面这样的语句: 即使查询条件走了索引,而且也只是查了仅仅10条数据,但还是卡得要死,为什么呢? 用explain查看一下执行计划,你会发现这条语句几乎是全表扫描!稍微优化一下: 哇,几乎瞬间结果就出来了。你可以脑补,它在一颗B-Tree上,只需走几个分叉,就找到了目标,而不是无脑地扫描。我们可以把这个分隔条件值称作游标(cursor),例如上面的9999990。现在可以不用页码来分页了,而是直接使用cursor来分页,因此也不需要提供总页数这个信息。客户端可以根据游标查询下一页或上一页的数据,游标为空时默认是第一页,每一页数据都会返回下一页或上一页的游标,这样也就可以一页地翻上翻下,直至没有数据返回。Yeah,可以跟前面提到的select count(*)说拜拜了。 可以把这个想象为一个双向列表,下拉时告诉服务端当前列表最后一条数据的标识,上拉时告诉服务端最前一条数据的标识。注意这个cursor不一定就是数字,它应该是一个payload,可以包含很多有用的信息,例如cursor=abc.123.855.bbb。前端不需要了解cursor值是什么东西,他只知道把cursor传给服务端就能拿到下一页,然后又获得一个新的cursor。后端可以随意修改这个cursor的实现方式,只要保证排序向后兼容,前端是无感知的。 使用id作为游标,确实好用,有时候,如果你需要对created_at进行排序,不必为它创建索引,直接使用自增id排序即可,如果你不想用自增id,也可以使用Twitter所创的snowflake,它直接把时间信息嵌入到一个64位的整型当中。 但还有一个问题,如果是根据用户性别排序,即出现重复数据时这个游标应该怎么设计?给性别字段gender创建索引,它的基数太小(只有3),得不偿失。但我们可以增加一个基数大的字段gender_order,专门用于排序,这个字段的排序结果就是gender字段的排序结果。因此只要满足: 这时候就可以拿gender_order当做游标了,一般来说取int64的高位保存原值,低位保存一个唯一值例如id,也可以取少一些的整数例如1000亿,当然要保证不会溢出哦。上面公式的id可以是当前用户的id,也可以是使用其它办法生成的唯一id,公式还可以是其它的组合形式,保证gender_order是唯一值即可。这里只是提供了这种思路,需求千变万化,但万变不离其宗。如果你还想了解更为复杂的游标设计,可以参考Redis是如何通过游标来遍历非常大的集合的。 另外建议,不要在原表做分页操作,要把分页独立为一个组件或接口,方便将来扩展。例如上面的gender排序可以单独设计一个索引表添加user_id,gender_order字段来实现,保持原user表的清晰简洁。你甚至可以把gender_order这个索引保存在Redis的有序集合中,以获得更快的速度。我不关心你是如何实现分页的,你只需给我那一页的id列表即可。你也许几经周折走遍千山万水才搞出下一页的id列表,但最终数据是通过一条简单的查询读出来: 总的来说,游标分页有以下几个好处: […]

用Golang手撕10种排序算法

10种排序: 冒泡排序(Bubble Sort) 选择排序(Selection Sort) 插入排序(Insertion Sort) 希尔排序(Shell Sort) 快速排序(Quick Sort) 归并排序(Merge Sort) 计数排序(Counting Sort) 基数排序(Radix Sort) 堆排序(Heap Sort) 桶排序(Bucket […]

ELK日志分析系统的部署和使用

ELK简介 ELK即Elasticsearch + Logstash + Kibana 分布式日志分析系统中的三大组件,结合轻量级的filebeat收集日志信息,可以搭建一套分布式日志分析系统堆栈。 Elasticsearch是开源的高可伸缩可扩展分布式的全文搜索和分析引擎,底层基于Apache Lucene,允许你快速实时地存储、搜索、分析海量数据,通过restfulAPI向外提供服务。 Logstash是开源的具有实时管道数据收集功能的软件,可以从各种各样的数据源中收集数据并过滤处理,使数据统一标准化、再发送至目标。 Kibana是开源的基于Elasticsearch的数据可视化分析平台,本身就是一个web服务器,可以很方便地制作直方图、表格、扇形图、地域分布等报表。 filebeat是go语音写的轻量级日志文件搬运工,占用资源极低,一般在需要收集日志的服务器上仅仅安装filebeat即可,它的主要工作是对配置中指定的日志文件进行定时扫描,发现有新日志写入,马上读取新内容发送至logstash或elasticsearch。 总体实现的功能有:实时全文搜索所有日志信息,根据不同服务器来源的web访问日志进行并发、流量、错误码占比分析、用户地域分布、热点URI、热点IP、活跃用户等信息统计。 Elasticsearch应用场景: 一个商品网站,用户可以通过关键字搜索商品信息,比如通过商家、商品名等; 收集日志进行实时分析和搜索,并通过kibana展示指标信息; 商品价格提醒,例如实现这样的需求:用户希望某个商品价格低于30元时收到通知提醒。 部署说明 本文档部署环境为centos7-64bit,ELK版本为6.x系列,只有相同版本系列的组件才能相互搭配使用。 使用filebeat作为轻量级收割机收集日志,Redis队列作为filebeat与logstash的中间件,nginx反向代理作为kibana访问鉴权,Elasticsearch存储数据,logstash作为过滤器。 […]

延时消息队列

如果遇到如下场景: 用户下单后,24小时内未付款则自动取消订单; 用户设定5分钟后提醒通知用户。 我们就需要延时消息队列,它是这样的一个消息队列:进入队列的消息在延时时间到达后才能出列被消费,时间未到达时对消费者是不可见的。 以下介绍的几种实现方案,可以依据具体业务需求进行选择。 一、简单粗暴的MongoDB MongoDB吃内存的特性一值备受人诟病,但也是内存的大量使用,使得MongoDB比MySQL性能更好。使用MongoDB的文档原子修改特性,可以快速实现延时消息队列功能,在业务初期,不啻为简单粗暴的选择。 入列: 轮询出列(轮询间隔取决于业务需要的时间精度): 消费确认: 非常适合进行定时或延时的内容推送、通知提醒等场景。 二、重量级、高可用的RabbitMQ 如果你的数据一致性比较重要,例如处理退款等重要业务逻辑,可以考虑适用RabbitMQ,下面介绍RabbitMQ如何实现延时消息队列。 三、高性能的Redis redis之所以快是因为它是内存数据库,意味着数据可能会丢失,但不用过多担心,简单的主从备份即可满足大部分生产环境对数据可靠性的要求。遗憾的是使用redis的基本数据没法实现异步延时消息队列的,有人会说使用redis的有序集合不就可以了吗?如果每秒只有一个任务,当然没问题。倘若并发场景下,你可以控制每次取出来的消息数量吗?你可以保证消息被消费失败后能重回队列吗?纯粹地使用有序集合是没法满足这样的需求的。 其实我们可以使用redis的lua脚本来实现延时队列。 注意点:在redis集群中注意hashtag的使用。 使用lua脚本时,可以使用lua脚本的hash。 四、自己动手写一个 自己动手,丰衣足食。我们还可以实现到期主动通知的功能。 […]

JVM的锁优化

当采用互斥同步方法实现线程安全的时候,线程阻塞将会对系统性能产生很大的影响,因为线程的挂起和恢复操作都要切换到内核态中完成,频繁地切换将会给系统带来性能损耗。锁优化技术的目的就是尽量减少不必要的线程切换,提高系统的并发处理能力。 下面是一些锁优化技术的介绍: 一、锁消除 有时候在代码中会有一些不必要的同步,这样在编译期就能进行优化,譬如对代码上要求同步,但实际上是不可能发生数据共享的锁进行消除。虚拟机会利用逃逸分析技术去判断一段代码中,堆上的数据会不会逃逸出去被其它线程访问到,然后进行相应的优化。一方面可能是人为因素添加了不必要的同步措施,另一方面,同步的代码在Java中普遍存在,无意中我们就会用到一些可能自己都不知道的同步方法。例如以下代码: public String concatString(String s1,String s2,String s3) { return s1+s2+s3;//这里似乎没有同步,实际上是吗? } String是不可变的类,因此对字符串的操作总是通过生成新的对象来进行,以上代码可能会变成下面的样子: public String concatString(String s1,String s2,String […]