神奇的bug

最近在编译一个node相关的工程,配置好编译环境,设置好代理,昨天一切都进展的挺顺利,很快就得到了结果。但后来发现编译的版本稍微有点儿差异,校验不通过。就把源码切到特定的tag上重来一次。然后就发现了一个神奇的bug。 使用yarn编译,中间一步依赖的一个包在编译时需要去api.github.com上下载特定版本的软件包。但是目标版本使用的软件包版本号类似于v1.2.3-2,在小版本号之后还有-2进行区分,然后这个软件包总是下载失败,导致编译失败。 验证了好久之后确认,代理设置没有问题,其它依赖包可以下载成功,但这个就是不行。后来在issue列表中确认,在特定proxy环境下,这个包会下载失败。失败原因是node中下载使用的request包在经过部分proxy时,对-这样的字符无法正确处理,url错误导致下载失败。 而且搜索后发现这个问题很普遍,不只是vscode的issue列表中报过多,其他很多使用node的开源软件都报过。而且因为是request的问题,一般的解决方法就是回退或更新第3方依赖的软件包,确保软件包的url中不包含-这样的版本号。……

阅读全文

docker私有镜像仓库

上一篇VS code remote提到国内访问docker官方镜像特别慢,可以使用加速器。但对于企业等,都会自建私有镜像地址,也可以解决这个问题。 docker registry Registry是一个无状态的,高可扩展的服务端应用,主要用于存储和分发Docker镜像。 Docker Registry文档 下载registry镜像 1 直接运行registry 这种方式需要机器能够访问docker.io docker run -d -p 5000:5000 --name registry registry:2 2 保存registry.tar并运行 通过可以访问外网的机器先 docker pull registry放入本地镜像仓库 使用docker save保存为tar包 将registry.tar拷贝到无法访问外网的机器上 使用docker load -i将tar包加载到本地镜像仓库 最后docker run运行registry服务 配置私有镜像地址 1 将镜像docker push至私有镜像仓库 和使用registry.tar运行registry服务类似,思路如下: 将镜像tar包拷贝到机器上 使用docker load -i加载到本地镜像 使用docker tag为镜像打上私有镜像地址的标签,比如192.168.1.11:5000/ubuntu:least 使用docker push将镜像推到镜像仓库 使用私有镜像地址 修改daemon.……

阅读全文

Don't be evil

上周和一个久未见面的朋友聊天,后来发现聊天的大部分内容都是吐槽工作。晚上又失眠了,脑袋里全是最近上班的事,吐槽一波~ 一、问题指责定界 这两天问题攻关(版本release前消灭问题单),我负责的Java通信框架部分很有意思,14张单下来没改一行代码。对面人说看你最近打电话多了很多,一直在扯皮。其中有几个问题很有代表性。 1 业务失败了,定位邮件转了一圈之后发现a服务调用b服务,a未收到响应。a截图发邮件给另一个部门的b,b回截图显示a调用发起1min左右响应了。a自己分析发现调用走Nginx,默认断链时间30s。但是a因其它原因配置了全局6min的超时时间,并未超时失败。 有意思的是a组大佬回复,框架定位响应1min问题,然后框架定位超时吃一单。 很老的问题单,日志早丢了(吐槽日志系统)。拉了一圈人电话了解业务情况,还原场景。问到a大佬时原话:“背景我也不了解,我看问题没人跟了,就让框架看一下”。 release点前都不愿接单,开始扯皮上升。再升一级se,说有2个问题,通信1min问题和失败问题bulabul…… 可是大哥,分布式系统里请求时长都是基于0.5/0.99分度只有统计意义的,你让我定位单次请求1min的问题,而且还是一个被各种开发,各种可靠性测试蹂躏的一个测试环境,wtf。而且问题已经很明确,1min请求时长正好暴露了a的接口容错机制明显有问题,不论是改超时时间,还是去改Nginx时长,或者接口同步机制…… 扯到最后,多谢a的大佬和se怜悯我们300多缺陷单,同时新版本a接口已下线,同意接单。 吐槽一下我们测试问题定界机制,问题邮件发出来之后启动24h超时定时器,击鼓传花一样最后落在谁手里谁吃单。定位期间测试也不关注定位逻辑是否合理,很多不需要业务背景,只要前后语义分析就能发现有明显逻辑漏洞的定位结论直接就回复了。这就导致定位倾向于找一个任意异常日志贴上去然后转给下一跳,反正下一跳也不知道我业务是什么。系统里的失败必然伴随着服务间调用失败,框架就很吃亏。接口人跟我诉苦,自己调休一天,30多个定位邮件……上班还要各种会议挂着,也难怪会超时提单了。 2 业务失败,定位进展为f服务的sdk调用f服务,调用抛异常了,没有完整日志只有异常截图,而且多次出现。找到f,人很赞,业务背景有问必答,最后还很nice的提示我f服务使用了自定义lb策略,可能是这里的问题。 我就下sdk代码,背景服务d代码,f服务端代码,看了半天发现。咦,根据日志抛出的异常推断这只能是你们自己实现的自定义lb选路失败了导致的。只是框架定义了lb接口以及lb异常机制,所以异常栈里全是框架信息。 f很爽快,定位清楚后就接单了。不过他们也很痛苦,自定义lb里都没有打日志…… 类似上面,邮件内容为yb看一下为啥异常,然后超时提单。 3 m内存超规格了,m接口人没有看内存信息,而是从日志里翻出来调我们g失败了。一个资源问题就神奇的转变为调用g失败的问题了,性能测试也很神奇的认可了这个结论。 可是我们可靠性se可是说,a依赖b,b挂掉后a是要惯性运行的啊。m好不惯性啊! 然后就开始了漫长的扯皮,最后m撂下话,单肯定是不能接的,最多只同意复制一张,各自管各自的问题。 哎,提单是测试的绩效。可是好多单提出后就没有焦点了,想要跟踪的问题也说不清楚。问题不分主次,一打一大片。 二、跨组驱动 我们搞了一个特性,需要别人适配。当时开发好急哦(好像所有特性都这样),5月31号发出来催别人赶紧适配,要赶7号版本。后来赶不上了,再赶15号最终版本。群里说,邮件推,但其他组都没动静。等到14号测试介入要提单了所有人都慌了。一下午我电话被打爆了。 所有人都有怨言。原来我之前已经在4月多搞了一个止血方案,5月初又搞了一个针对Java的方案,现在我搞得是全语言版的最终方案,以前的都是临时方案。 2周前适配发了邮件,但没有用需求推动。小组长只管需求列表,问题单列表,其他组一个小开发的适配邮件谁鸟你,所以看不见。不得不说还是测试牛逼,说要提单所有人都紧张了。 大家跑来吐槽我为啥这时间点发适配,为啥这么频繁的改适配方案,我很懵逼,很慌。同是开发,同在消单,相煎何急。 自己想一下,很内疚。驱动别人去适配,别人也算客户吧。以客户为中心难道忘了吗?为啥方案变更这么频繁,客户体验太差了吧!驱动不好,时间点卡的这么赶,简直是在犯罪啊。毕竟兄弟们都已经两班倒,24h在岗了。 以前我不太懂dont be evil是啥意思,我们不应该是be kind,be nice,be cool吗?现在想想,工作中能做到dont be evil就很不错了,很多时候不自觉的就对外作恶了。 包括不限于不在代码中留下一坨屎。昨天我们组的女前辈在群里发了一张屎一样的代码截图,最后git记录显示是我合入的。但是我很肯定这代码不是我写的,大致背景我也记得,但git记录没法辩驳,而且我提交时确实没看内容。 工作前就是刷leetcode之类,只关注是否work。说实话软件工程能力或者说对好代码的辨别力,如何写出好代码的能力很缺失。细节处有很多盲区。 入行也2年了,期间被那位女前辈,还有其他前辈像上面那样吊打,多次按在地上摩擦。 被别人指正String类型的不可变性是源自内部实现的final char[];for循环的作用域区间是整个循环过程而非单次循环…… 认识到错误时都很感激,自己很惶恐,也在反思,总结哪种好,哪种不好,提升自己对细节的把控和品鉴力。 但也发现很多代码更糟糕,很多问题定位太随意,一些事情像上面适配那样的驱动方式太简单粗暴,一些低效内耗行为明显与有效产出方向不一致,更遑论一些宣称很有价值,很nice,很cool的事情深入之后发现是豆腐渣,没有任何意义…… 能说什么呢,天也亮了,希望工作生活中大家都能dont be evil,把自己这一跳的事情努力尽责做好,提升自己能力,之后再说be kind,be nice,be cool吧~……

阅读全文

Java并发编程

缓存一致性与并发问题 我们今天使用的计算机体系结构都属于存储程序型(冯·诺伊曼结构),运行时所有的数据都存储在内存中;指令执行时,数据从内存读入寄存器,运算结束后再写回内存。 // i == 0 i = i + 1 // i == 1 以上面代码为例,计算机先将i加载到寄存器中,再寄存器中+1后再将i写回内存。 可以认为运算中,寄存器中的数据是内存的一份缓存。但是,有缓存,就有缓存不一致的问题。 我们只考虑多CPU时的并发问题。单线程执行时,以上执行顺序不存在问题;但并发场景下,假设2个线程A、B同时执行,A读入寄存器的值均为0, 计算后都为1,在A写入内存前,B此时读取到的值为0,最终写入内存的i为1,显然存在问题。 解决缓存不一致问题,通常有2种方法: 在总线加锁; 使用缓存一致性协议; 总线加锁 CPU通过总线读取内存,通过在总线加锁,锁未释放前,CPU只能再次读取i所在内存,直到锁释放。早期CPU采用这种方式,这样可以解决不一致问题,但这等于同一块内存锁释放前无法访问。 缓存一致性协议 Intel的MESI协议,提供了如下机制:CPU写数据时,如果发现变量是共享变量,会发信号通知其它CPU将该变量的缓存置为无效状态。其它CPU操作时发现缓存失效了,就会重新从内存读取。 并发编程中的概念 原子性 提到原子性时都会以银行转账为例:用户A向用户B转账100元。 转账事务请求中,银行会进行2项操作:A账户减少100元;B账户增加100元。 不管银行按什么顺序执行,如果2项操作进行到一半时失败了,银行都需要先进行回滚,否则就会出现错误(银行损失或用户A损失)。 换句话说,银行需要保障转账的原子性:这项请求要么成功,要么失败,不能出现任何中间状态(2个操作一个成功,一个失败)。 对应到计算机运行中,一些操作是原子性的,另一些操作是由若干原子性操作组合而成。 比如根据上面分析,i=i+1不是原子操作;那i=9是原子操作吗?如果计算机分别对i的高低16位先后赋值,那就不是原子操作。 可见性 可见性是指多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 比如: // 线程A int i = 0 i = 5 // 线程B j = i 如果线程A执行完i = 5但尚未写入内存时,线程B从内存看到的i = 0,无法看到线程A缓存中i的值已经发生变化。 有序性 以下代码示例来自《七周七并发》 public class Puzzle { static boolean answerReady = false; static int answer = 0; static Thread t1 = new Thread() { @Override public void run() { answer = 42; answerReady = true; } }; static Thread t2 = new Thread() { @Override public void run() { if (answerReady) { System.……

阅读全文

Golang defer导致内存溢出

Golang中提供了defer关键字,以前可能需要在函数内进行多次判断之后释放资源,现在使用defer一次就搞定了,习惯之后会觉得这个特性非常好用。 问题代码 有一句话是软件设计没有银弹,放在这里依然成立。defer这么好用,但也需要注意一些坑。代码逻辑大致如下: for { defer 释放资源 // http request // 错误码判断 // sleep 5s重试 或 break退出 } 业务是心跳这样的守护进程,所以直接就用了死循环,请求失败之后进行重试,或满足一定条件时退出循环。使用defer在最后释放资源。 测试时异常场景也进行了覆盖,功能上没有发现问题。但在一个实例上发现运行一段时间之后内存爆了。 问题分析 defer的实现机制是在调用函数栈上再压入defer函数,调用return前调用defer函数。所以调用defer会有一次压栈操作。 上面的示例中将defer在for循环中,环境恰好没有返回对应的错误码让for循环退出,导致defer一直被执行,重复的压栈导致StackOverflow。 小结 从这里看,将defer放入循环中就是一个不好的习惯;另外,死循环代码也有一丝丝坏味道,看到这样的代码时就应该警惕了。……

阅读全文

一道数学题

我女朋友从中学时数学就不咋样,高中时理科太差就只能读文科,读了文科数学依然最差。大学数学还需要补考才能毕业,考研时因为不考数学才读的现在的专业。我略好一些,高中时借着给她补习数学才走到一起。 这是背景。 前一阵子和女朋友去买鞋,商场满减,400减100,700减200,900减300。我们选中了一双700多一点儿的鞋,刚好凑够满减。付钱的时候,收银台有个大姐,选了一条裤子200多,要和我们一起凑单。 我想都没想就说可以啊。这时候我女朋友突然问,那怎么凑?她这么突兀的一问,我还没反应过来。大姐也呆住了,就说你们减200,我们减100啊。我女朋友就有点儿吞吞吐吐了,我们已经够减200了之类的。大姐见势不妙,就让一步说那你们减220,自己减80。听到这里我都有点儿不好意思了,刚好收银员也扫好了码催付款。我赶紧付了钱,收了大姐的余额就往出走。大姐的丈夫过来后了解清楚了,就在背后说互利互惠什么的。 走出门的时候,我总觉得自己占了人家20块钱的便宜,心理有点儿内疚。我女朋友却说,她本想着我们减250,他们减50。看我一脸震惊,就跟我开始算起了帐。我们700多,不需要他们也能优惠200;他们没我们,啥优惠也没有;加一起之后,多优惠了100,那这100按比例我们应该70,他们30,至少也得平分。 至此我才突然转过弯儿来,从头至尾我不仅没占人家便宜,还被别人占了便宜。……

阅读全文

使用Gradle开发IDEA插件

最近想试着开发一个IDEA的插件,官方指导 中有两种方式: 使用Gradle 使用DevKit 而且官方推荐Gradle的方式,称DevKit的工作流已经过时了。但百度出来的全都是DevKit的方式。而且Android Studio也是gradle,那就试试gradle的方式开发一个插件。 Gradle配置 使用gradle创建工程之后,需要下载一些文件,因为GWF的原因这一步会特别慢,不使用VPN基本都失败了。网上找了使用aliyun源的方式,搞定了这一步。 单个工程修改 将工程下build.gradle中的repositories替换为如下内容 repositories { maven { url 'https://maven.aliyun.com/repository/public/' } maven { url 'https://maven.aliyun.com/repository/google/' } maven { url 'https://maven.aliyun.com/repository/jcenter' } } 具体替换地址可以看aliyun官网的地址,网上大多数配置都没有更新,使用的还是旧地址。 全局配置 以上方式只修改单个工程的,每次新建工程都要修改,很麻烦。下面这个方法可以修改全局的配置。 在 C:\Users\用户名\.gradle目录下创建init.gradle文件,内容如下: allprojects { repositories { maven { url 'https://maven.aliyun.com/repository/public/' } maven { url 'https://maven.aliyun.com/repository/google/' } maven { url 'https://maven.aliyun.com/repository/jcenter' } all { ArtifactRepository repo -> if (repo instanceof MavenArtifactRepository) { def url = repo.url.toString() if (url.startsWith('https://repo.maven.apache.org/maven2/') || url.……

阅读全文

工厂模式 Factory Pattern

工厂模式定义了一个创建对象的接口,但由子类决定要实例化的类时哪一个。工厂方法让类把实例化推迟到子类。 简单工厂 简单工厂的思路也确实非常简单,就像将创建类的代码进行了简单的抽离。《设计模式》也说这不应该算一种模式,更像一种编程习惯。就好比撸完代码之后自己回头看看,发现这一块独立的逻辑可以抽离出来独立一下。 工厂模式 工厂模式中,创建者与产品为平行类层级,只是创建者的创建方法产生产品。要创建哪种产品,由具体的创建者子类决定;产品的实际创建,也由产品的具体实现类完成。 代码 略……

阅读全文

状态模式 State Pattern

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 工作中经常会遇到各种状态机,使用流程控制的方式虽然也可以实现,但后续的维护变动就变得很困难。在实际中我们现在就遇到了一个这样子的问题,整个状态机的代码有一千多行,代码完成的还算工整,但要看懂现在就只能对比状态图了,因为状态实在太多了。我曾经增改过两次,每次改动都伴随着一大堆的问题单。 这时候就真真切切体会到状态模式的好处了。 类图 状态模式 vs 策略模式 如果与策略模式的类图对比的话,会发现图结构几乎一模一样。虽然如此,这俩模式还是有比较明显的区别。 策略模式中,最后的每一个业务类只包含了一组算法中的一个实现;或者说每一种算法的组合形成了一个业务类。 但在状态模式中,业务类中继承了算法的所有实现,并且在运行态切换算法为不同的实现,从而让自己拥有不同的行为(表现为不同的状态)。 我觉得类比之前策略模式中不同种类鸭子的实现,状态模式只存在一只百变鸭,但这只鸭子根据外界输入一会将自己变成红头鸭吱吱叫,一会儿变成绿头鸭呱呱叫。 代码 略……

阅读全文

神奇的bug

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。 Etcd启动失败 数字排序问题 golang defer导致内存溢出 jpa事务读写 CRL校验问题 ……

阅读全文