MySql事务未提交导致锁等待如何解决?
背景
我们来先看一个图,了解一下故(事)事(故)的背景:

有2个跑批任务,其实做的事情是同一件事情,都是为了跟下游系统保持数据的一致性。大任务是每个2h跑一次,小任务是每隔10mins跑一次。除了这2个定时任务以外,还有一个额外的监控任务来做类似的对账,如果发现出现对账不平,就会出现邮件/短信告警到相关的责任上。
这是一个非常有特点的定时任务跑批任务+监控告警的场景了。
从上面的场景上看,我们可以得出一些结论:为了保证一致性写了大小Job来保证,并且还给出了监控告警,说明数据的重要性是比较强的。
某天,出现了频繁的告警提示,每10分钟就告警一次,而且内容没有发生变化,说明同步的index没有变化过。
错误排查
任务有在正常的执行吗?
第一反应肯定是在思考,我的大任务与小任务都有正常执行吗?因为之前的都是正常的。上午看了一下日志与进程发现有在跑,还跑除了多次任务,日志打印不明确,看不到具体分支的逻辑。总结一下问题点:
如何反编译一个Docker镜像还原Dockerfile?
前言
现在对于一个开发来说,Docker应该是再熟悉不过了。 还记得在2013~2014左右的时候,听说多最多的就是Cloud Foundry,那个时候就一直在说云的事情。后面Docker就绝杀了它~
那它帮我们解决了一个什么问题了?面试的时候也许会问到。
在很久以前,我们开发代码,估计最蛋疼的事情就是发布版本了。我还记得在房多多的时候(2014~2016)左右,每次发布几个开发围绕在运维的身边,有时候运维忙不过来,开发就直接在运维的电脑上开始VIM干活了,修改若干配置。由于多环境的原因,我们无法保证每个环境都是一样的。
- 可能你的操作系统不同,导致打包、发布的脚本不同
- 环境不同,没有很好的配置管理,你的代码有不同的写法
- 特别是跟操作系统相关的那些参数,可能瞬间就会带来性能问题
那么Docker就可以把我们的操作系统、代码、脚本等都一起打包成一个Image,就可以保证只要是运行同一个Image,我们的所有内容都是一样的。就不会出现,我在测试环境跑的好好的,一到生产连启动都成问题。
问题
现在一般一个POD就只跑一个进程,DevOps会根据我们的发布流水线自动的将一个项目进行打包、发布,整套的CI、CD做的是行云流水。但是,每个项目ROOT下都会需要一个叫Dockerfile的文件。但偏偏有一些历史项目,没有Dockerfile文件,只有一个Run的容器再跑,真的是非常惊悚。docker rm [OPTIONS] CONTAINER [CONTAINER...],就GAME OVER了。
怎么办?
方法1:以当前容器作为基础镜像
如何利用k8s的label与ingress做蓝绿发布?
背景
之前在思考双活/多活架构的时候,其实对于蓝绿发布是有一些了解的,也梳理过在底层存储是一份,服务是多份的模式有做过深入的分析。但那个时候对于Kubernetes的了解还不是很熟悉,是通过传统的方式来考量的。
因为现在的互联网公司基本都是上云了,我们也必须对于Kubernetes那一整套要有比较深入、熟悉的运用才能真的提高我们的效率。先聊一下,我为什么需要利用灰度+蓝绿发布的模式来去做?
现在有一个比较老的项目,应该在10年+,每天请求量大概在1.5亿+,峰值的QPS在6000/s,存在着比较多性能问题。现在需要在它上面新增一个服务,为了后面优化做准备,比如:请求的分流、限流、熔断、日志的上报与监控(新)、统一编译处理,特殊报文转换等。也就是说,只要你新增加了一层,你才有可能更好的去做更多的事情。
那么我们需要达到一些什么的基础条件了?
- 服务流量比较大,我们需要对新服务的可靠性需要验证,需要灰度先了解
- 因为存在慢查询,不能在滚动发布中,导致请求还未执行完毕,就被k8s kill掉了,业务会感知到502
如果是你?针对于这2个基础的要求,你会如何去思考的你架构方案呢?
思考
新增服务的思考:
- 它的性能必须要强、服务稳定。一个服务的性能好不好,其实跟它的:
I/O模型、线程模型、数据结构、算法等息息相关。比如:你在思考Redis单线程为什么快的时候?应该就很能get到这里的点了。解决这个问题,我们选择了Go语言来开发(当然,最熟悉的语言风险最小),为了保证性能,也是做了2轮非常细致的压测。 - 发布过程中不能因为kill掉服务导致请求
502。如果说我在发布的过程中,我把滚动这一步省略掉,直接先准备好一份最新的,验证可以后,我一刀直接把流量引导最新服务上,老的服务也不会断掉,这是否就可以达到效果了?
如何利用wrk与Jmeter做性能压测
我们在新做项目的时候,需要对我们的服务有有一些性能指标,比如:SLA(需要达到多少个9)、QPS、TPS等。因为这些量化的数字让我们更加的了解我们的系统。
我们如何压测?其实个人觉得有2种场景。
第一种:是我们明确的知道目标,看我们通过大量的并发看我们是否有达到。如果没有达到,我们需要通过水平扩容、性能优化等让其达到。
第二种:是我们不知道目标,通过压测可以知道一个固定配置下的单机单服务的最大性能,让我们对它有一个彻底的认识。为后面的目标做更多的铺垫与准备,或者跟行业水平对比,看看差距有多少。
如何用wrk进行压测?
Github地址:https://github.com/wg/wrk,该项目也是开源项目,关注的人还不少,有30.4K。咨询了一下身边的同事,使用它的人还不少。主要的语言的是C语言。

安装
1 | git clone https://github.com/wg/wrk |
压测脚本
【go学习】地址符&与指针*的差别
背景
项目中开始用Go,最近写了一下Demo,发现语法还是非常好用,大部分比Java还是简洁很多,也有一些很细节的约定。比如:
- 字母大小写控制是全包还是本包内访问
- 变量定义了就一定要使用,否则就会编译不通过等等
更好的就是方法可以返回多个值,这个跟Java比较就是减少很多的封装。因为Go的线程模型特点,用来写一些需要高并发、高性能的项目还是非常好的。所以,趁这个机会也好好的深入了解下。现在也是把Python、PHP、Go等都学习一遍,每种语言都有它的优缺点,其实都还挺不错的。
针对于Go语言里:&与*的区别,什么时候该用什么做一个总结。
指针
我们经常会听到别人说Go是值传递,某某某是引用传递,某某某是指针传递,等等各种各样的说法。
那么首先他们的区别是什么呢?什么是指针?指针其实也是一个变量,只不过这个变量里面存的不是int,float,struct,而是一个地址address,然后在这个address上所存储的数据可以通过指针来被阅读到。
OK,指针变量存储的是一个地址,地址从哪里来的?那就得问一个变量的地址怎么取得呢?在变量前面加上一个&符号就行。
好的,指针变量存储了这个地址了,那这个地址所存储的值怎么被阅读到呢?也就是指针所指向的值怎么拿到呢?在指针变量前面加上一个*符号就行。
pt-online-schema-change使用
背景
如果说你的数据量并发量不大,或者你的数据量很少没有到千万级别,也许pt-osc、gh-osc,online-ddl这些工具都用不着。但是,如果你的数据量很大,数据又很热。如果你没有这些工具,你可能无法完成对一个数据库新增一个字段或者任何一个简单的DDL语句。
简单的分析一下,为了保证数据一致性问题,我们在哪儿都会遇到锁的问题,锁是用来保证顺序性的。谁先拥有锁,谁就可以先执行。锁也会存在力度问题,它跟你要做的一件事情息息相关,我们也会在性能上去做取舍,所有就好了行锁、表锁等。
Waiting for table metadata lock
说一下我遇到的这个场景,数据量数据大概在800W左右,但是表非常的热,长事务也很多。当我要对一个表新增字段的时候,这个时候如果你经验不够足,可能就会“量成大祸”。一般在做DDL会出现:Waiting for table metadata lock。
如果长时间获取不到锁的话,就出现一个可怕的情况:
- 如果前面的事务未提交,当前是获取不到锁,就不可以执行
DDL语句 - 在
DDL语句未执行之前,后面的请求全部是被hold住的
这样子就会导致一前一后同时夹击,导致整个业务不可用。那么出现Waiting for table metadata lock可能是由哪些原因导致的?
场景一:长事物运行,阻塞DDL,继而阻塞所有同表的后续操作
通过show processlist可以看到TableA上有正在进行的操作(包括读),此时alter table语句无法获取到metadata 独占锁,会进行等待。
批量操作脚本让你5mins搞定发版
代码分支管理
大家在做微服务拆分后,难免会导致Application项目以及一些二房包的数量加剧,10+个项目我想应该是很容易的超过。然后这些细粒度的拆分后就会导致发布版本时候的麻烦。
展示一下现阶段我们的一个git的分支流程图,仅供参考。

简单说明一下:
dev环境每次都是从master拉取一个分支,取名为dev-{发布日期}-{sequenceId},sequenceId从01开始叠加,避免一个版本需要反复拉取多次。- 到了
test阶段,也是为了收敛(前期严格一点)。提测的时候代码需要合并到test分支来。 - 到
UAT阶段,还是需要从master拉取分支,如果出现要重新拉取分支的情况下,还是严格拉取master分支的代码。主要是为了与master保持一致,避免把别人的覆盖掉。取名为release-{发布日期}-{sequenceId},规则同上。 - 上生产后,验证完毕后需要把代码合并到
master分支,并且打包tag。
但是在这个过程中,需要有拉取新分支,合并分支,批量删除分支,打tag等等繁琐的操作,项目一多如果有一个批量脚本就更好了。下面我就列举一些平时使用最多的几个,仅供参考:
批量脚本
01-批量拉取新分支
比如一个新的迭代要开始了,就需要从master拉取dev分支。
Linux命令 kill -9 的原理
背景
相信很多程序员对于 Linux 系统都不陌生,即使自己的日常开发机器不是 Linux,那么线上服务器也大部分都是的,所以,掌握常用的 Linux 命令也是程序员必备的技能。
但是,怕就怕很多人对于部分命令只是一知半解,使用不当就能导致线上故障。
前段时间,我们的线上应用报警,频繁 FGC,需要紧急处理问题,于是有同事去线上重启机器(正常程序应该是先采集堆 dump,然后再重启,方便排查是否存在内存泄露等问题)。
但是在重启过程中,同事发现正常的重启命令应用无反应,然后尝试使用 kill 命令 “杀” 掉 Java 进程,但是仍然无效。于是他私自决定使用 “kill -9“ 结束了进程的生命。
虽然应用进程被干掉了,但是随之而来带来了很多问题,首先是上游系统突然发生大量报警,对应开发找过来说调用我们的 RPC 服务无响应,频繁超时。
后来,我们又发现系统中存在部分脏数据,有些在同一个事务中需要完整更新的数据,只更新了一半…
为什么正常的 kill 无法 “杀掉” 进程,而 kill -9 就可以?为什么 kill -9 会引发这一连串连锁反应?正常的 kill 执行时,JVM 会如何处理的呢?
要搞清楚这些问题,我们要先从 kill 命令说起。
kill 命令
Kafka消费者组是什么?
一、Kafka消费者组是什么?
Consumer Group 是Kafka提供的可扩展且具有容错性的消费者机制。在组内多个消费者实例(Consumer Instance ),它们共享一个公共的ID即 Group ID 。组内的所有消费者协调在一起消费订阅主题(Subscribed Topics)的所有分区(Partition)。当然一个分区只能有同一个消费者组的一个Consumer 实例消费。Consumer Group 有三个特性:
Consumer Group下可以有一个或多个Consumer实例。 这里的实例可以是一个单独的进程,也可以是同一进程下的线程;Group ID是一个字符串, 在Kafka集群中唯一标识,Consumer Group;Consumer Group
下所有实例订阅主体的单个分区,只能分配给组内某个Consumer实例消费。同一个分区消息可能被多个Group消费。
二、Kafka消费者组解决了哪些问题?
传统的消息系统中,有两种消息引擎模型:点对点模型(消息队列)、发布/订阅模型
传统的两种消息系统各有优势,我们里对比一下:
- 传统的消息队列模型的缺陷在于消息一旦被消费,就会从队列中删除,而且只能被下游的一个
Consumer消费。严格的说这不是它的缺陷,
这是它的一个特性。但很显然这种模型的伸缩性(Scalability)很差,因为下游的多个Consumer都要“抢”
这个共享消息队列的消息; - 发布/订阅模型,允许消息被多个
Consumer消费,但它的问题也是伸缩性不高,因为订阅者都必须订阅所有主体的所有分区。
Kafka 为规避传统消息两种模型的缺点,引入了 Consumer Group 机制:
- 当
Consumer Group订阅多个主题后,组内的每个实例不要求一定要订阅主题的所有分区,它只会消费部分分区中的消息; Consumer Group之间彼此队里,互不影响,它们可以订阅同一组主题而互不干涉。加上Broker端的消息留存机制,Kafka的Consumer Group完美的避开了伸缩性差的问题;kafka是用Consumer Group机制,实现了,传统两大消息引擎。如果所有实例属于同一个Group,那么它实现的就是消息队列模型;如果所有实例分别属于不同的Group,且订阅了相同的主题,那么它就实现了发布/订阅模型;
三、Consumer Group 实例数量多少才合理?
最理想的情况是Consumer实例的数量应该等于该Group订阅主题的分区总数。例如:Consumer Group 订阅了 3个主题,分别是A、B、C,它们的分区数依次是1、2、3,那么通常情况下,为该Group 设置6个Consumer实例是比较理想的情形。
