方牧心的个人网站

学习与记录,开源与分享


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

MySQL死锁排查

发表于 2018-07-02 | 分类于 故障处理

之前一直没有机会碰到与MySQL相关的问题,缺乏一些对于线上环境排错的经验,今天刚好碰到这个一个例子,以此来做一些记录。

问题起因

某数据库中一张business表,当尝试update某一条记录时,会卡住一段时间,然后报一个获取锁错误。

问题初步确定

遇到这个问题第一反应是这个记录被加锁了,首先update了一下同一张表的其他数据,确定可以执行,因此初步确定是行锁引起的。

那么最直接的想法就是执行show full processlist;,看一下能否找到蛛丝马迹。

然而结果并不乐观,上面命令告诉我当前这个数据库没有任何连接在执行SQL,唯一看到的就是我这条命令本身了。

但虽说没有执行SQL,但是有很多Sleep中的连接,这种情况下,在此尝试update被锁死的数据,依然处于锁死状态。

进一步排查

这时想到了SHOW ENGINE INNODB STATUS;查看一下InnoDB引擎的情况,可以看到我刚才执行的语句在尝试加锁的时候超时了,更印证了之前的推测。

首先考虑是不是出现了死锁,现在的MySQL基本比较智能了,如果一般的简单死锁会自动回滚一个事务。通过show status like '%lock%';查看了一下,当前等待的锁数目为0,当我尝试update时数量变为1,说明只有当前一个锁在等待,并不是简单的一个死锁问题。

那么看一下SELECT * FROM information_schema.INNODB_TRX;当前trx的结果,发现了令人卧槽的结果。

居然有一个事务运行了几个小时……并且加锁居然加了200多个行锁。这显然就是罪魁祸首了。

后续排查

那么原因很明了了,找到这个事务的线程,直接kill掉可以强行回滚释放掉锁,但是究竟是什么业务导致的这个问题,还需要细致排查。

首先看调用方,根据show full processlist;的结果可以找到调用方,是一台Java服务,但这个很难知道究竟是什么业务引起的超长时间事务。

一开始想到去看binlog,但是谷歌过发现binlog并不会记录事务的操作,只有事务提交后才会被写入,然而我们这个超长时间没有提交的事务就无法看到内容了。

最后找到了general_log,这个日志文件会将事务中的动作也记录下来。

然而执行一下show variables like '%general%';后发现居然是OFF状态,也就是没有在记录general_log(后来我反应过来,这是很正常的,开启了这个选项会在短时间内用这个日志把你的磁盘爆掉…)

那么此处我们set global general_log=1;后,之后产生的动作便会被记录了。

最终解决

首先当前第一要务是把对应的线程kill掉,先释放行锁,恢复业务。

其次是打开general_log的记录,随时观察数据库,当数据库复现这个问题时,使用thread_id到general_log中搜索便可以一目了然了。

最后一定要记得观察一下general_log的情况,毕竟详细记录所有操作,日志量也是巨大的,用完之后要将开关关掉。

本站全面启用CDN

发表于 2017-02-16 | 分类于 建站手记

今日起本站全面启用CDN!

之前一直纠结于境外服务器访问速度有限,境内服务器限制多而且不能科学上网的问题。刚好看到Dnspod的CDN推荐,于是亲自尝试了一下,发现国内访问效果确实显著。

主要做了如下的修改:

  1. 取消源站的HTTPS访问方式,关闭防火墙443端口
  2. 将HTTPS证书部署在CDN节点,由CDN节点负责建立到用户的HTTPS连接
  3. 取消原先www.muxin.io通过nginx做301重定向的操作,改为由DNS阶段直接301重定向

目前你们所看到的结果是来自阿里云CDN的加速效果,话说现在CDN价格确实便宜,阿里云价格大概在0.4元/GB左右,对于小站来说基本可以忽略这个费用。

在此不得不吐槽一下阿里云,开启CDN居然还要人工审核,花了一天时间,而腾讯云则可以立刻开通,最后选择阿里云的原因还是出于情感和信任吧,访问上感觉和腾讯云差不多。

CDN的优势

用上CDN之后本站的真实IP被隐藏,虽然说目前没有遭到过任何攻击,最多被端口扫描过几次,不过终究还是安全了些,目前没有方法可以获得本站的真实IP。

云加速效果确实有效,因为原本国内数据中心到本站的速度并不慢,慢的是某些地区,经过CDN加速后可以完全打到秒开的效果,大大提升了体验。

CDN启用方法也十分简单,只需要提供源站域名和真实IP,由提供商生成加速域名,将自己站点的域名CNAME解析到加速域名即可。

期间也遇到了裸域名muxin.io下CNAME和MX记录冲突的问题,由于Dnspod支持同时设置,暂时就这么搞了,测试了几封邮件没有什么问题,其他还有待观察。

HTML 中固定 footer 的方法

发表于 2017-01-02 | 分类于 经验分享

有时在 Html 中我们需要固定底部的版权信息等内容,期望的展示方式是页面撑满浏览器,最下方是 footer 的内容。

1
2
3
4
5
6
<div class="main">
<div class="content">
...
</div>
<footer>版权所有</footer>
</div>

如到所示,我们希望的是footer永远占据页面的最下方,而content内容则填满页面的其他部分。

想当然的方法

想当然的方法去实现,是直接将div.main部分的height设置为100%,这样可以直接撑满页面,虽然在内容少的时候确实达到了我们想要的效果,但弊端也十分明显——在页面内容多于一页的时候,内容会展示不全,因为设置了固定的height,所以内容并不会将div.main进一步撑开。所以这样是不行的。

使用min-height

Html 中可以使用min-height来指定内容高度。根据这个思路我们可以将div.conent的min-height设置为80%,同时将footer的min-height设置为20%。

然而这个解决方案看起来很完美,实际上用起来的时候还是会有问题,原因如下:

一个元素如果要指定min-height,那么它的父元素必须指定height,指定min-height也无济于事。也就是说div.main必须指定height这一属性。然而还是那个height不能自动变大的问题。

最终方案——使用position

那么最终的解决方案应该怎样呢?答案是使用position

先给出最终的Html和CSS代码:

1
2
3
4
5
6
<div class="main">
<div class="content">
...
</div>
<footer>版权所有</footer>
</div>
1
2
3
4
5
6
7
8
9
10
11
div.main {
min-height: 100%;
position: relative;
padding-bottom: 10rem;
}

footer {
height: 10rem;
position: absolute;
bottom: 0;
}

首先将div.main的最小高度设置为100%,随后将其位置指定为relative;随后指定footer的位置为absolute,这样footer会相对于上一个位置被指定为relative的元素进行布局,随后bottom: 0;将footer设置在了div.main的最下方。

当然不要忘记了div.main需要有一个padding-bottom,大小等于footer的高度,这样内容才不会错位。

到此我们就实现了最完美的固定footer的样式。

2017——新年快乐

发表于 2017-01-01 | 分类于 随笔

不知不觉又是新的一年开始了,自己也没有像之前想象中的一样,对跨年这个一般人看来充满意义的事情抱有太大的期待。虽说如此,也觉得此时的自己已经开始了新一年的旅程。

回首2016,还是可以鲜明的感觉到自己各方面的进步的,不能说是很多,但是起码见识和思维都比前一年有所拓展。像某人说过的那句话一样——“如果失去了好奇心,那么人就死了”。我可以昂首挺胸的说,我活着走过了2016,同时也会在2017年活得更好。对更多地事情不遗余力的去探索,去学习,去让自己更有深度。

在这里祝大家新年快乐!同时新年也会多抽一些时间来维护这里,把更多的知识和经验记录下来,和别人一起分享,进步。

再见了,2016。你好!2017。

Java callback 回调学习

发表于 2016-06-28 | 分类于 学习笔记 , Java

最近接触了一些前端 JavaScript 方面的东西,觉得 JS 最方便的地方便是各种回调函数,无论是 Ajax 还是与用户的前端交互,都是通过回调函数的形式实现的。于是乎来到 Java 中搜索了一下有关回调的相关内容,结果发现还真的有。查了很多文章,总算是弄清楚了 Java 中回调的使用方法,然而大部分讲解都没有说到其实际的用途,于是便再次进行记录,也是给自己一个备忘。

什么是回调

接触过 JS 或者做过服务端接口的人应该都会对回调十分熟悉。按我的理解,假设有 A 和 B 两个模块,A 调用 B 来执行某一个功能,这个功能执行可能会花费一些时间,而且 A 希望在 B 执行完之后得到通知,那么 B 执行完相关功能之后再对 A 进行通知的这个过程,就叫做回调。通俗来讲就是甲乙两个人,甲需要一把梯子来换灯泡,让乙去拿来,乙把梯子拿来以后需要告诉甲:梯子已经到了,可以换灯泡了。

在 JS 中的回调十分便捷,只要将收到回调后需要执行的方法传入调用的方法中即可,这种回调方式的调用不仅限于 JS,Ruby 中也有类似的语法,然而对于 Java 来说,实现回调虽然有些麻烦,但是仍然有着解决办法。

Java 中的回调

在 Java 中实现一个回调不像脚本语言那样简洁优雅,但是会有一种更加规范的感觉。

此处还是用之前提到的的 A 和 B,生命这两个模块为两个类,主要归结为以下步骤:

  1. 定义一个 Callback 接口,用来指明回调时候需要执行的方法。
  2. 让 A 类实现 callback 接口,并且 A 类需要持有一个 B 的实例对象 b(因为只有持有才能调用)。
  3. 让 B 类中实现一个参数带有 callback 接口参数的方法 f(Callback callback, Object args)。
  4. A 类的实例对象 a 调用 b.f,并且将 a 自身作为 callback 参数传入。
  5. b.f 此时可以通过 callback 调用 A 中定义的 Callback 接口中的问题。

乍看下来感觉整个人都不好了,乱七八糟的,其实结合代码的话会看着很简单。此处以刚才说到的甲让乙搬梯子,然后甲用梯子换灯泡的情景,写一个 Demo。

首先是定义接口类,该接口声明了甲需要完成的换灯泡工作:

1
2
3
4
5
6
7
8
9
10
11
12
package io.muxin;

/**
* 回调接口定义,
* Created by mxfang on 16/6/29.
*/
public interface Callback {
/**
* 换灯泡过程
*/
void changeBulb();
}

其次先定义乙的类,乙的类是提供一个搬梯子的功能,并且在搬好以后通知让他搬梯子的人:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package io.muxin;

/**
* 这是乙的类
* Created by mxfang on 16/6/29.
*/
public class Yi {
/**
* 去搬梯子, 搬好以后通过回调通知甲
* @param callback 持有甲的回调
*/
public void getLadder(Callback callback) {
System.out.println("乙: 开始去搬梯子");
try {
// 搬梯子需要时间
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乙: 梯子搬来了, 通知甲");
// 回调甲
callback.changeBulb();
}
}

然后定义甲,甲有两个功能,第一是尝试去换灯泡(不确定有没有梯子),发现没梯子的话让乙去搬梯子,第二个功能是正式换灯泡,这个由乙来通知甲进行操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package io.muxin;

/**
* 这是甲的类
* Created by mxfang on 16/6/29.
*/
public class Jia implements Callback{
private Yi yi;

/**
* 构造函数, 让甲能持有一个乙的实例
* @param yi 乙的实例
*/
public Jia(Yi yi) {
this.yi = yi;
}

/**
* 尝试换灯泡
*/
public void tryChangeBulb() {
System.out.println("甲: 没有梯子, 让乙先去搬梯子");
yi.getLadder(this);
}


@Override
public void changeBulb() {
System.out.println("甲: 梯子到位, 开始换灯泡啦");
}
}

最后是测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package io.muxin;

public class Main {

public static void main(String[] args) {
// 首先要有一个乙
Yi yi = new Yi();
// 然后甲需要持有乙
Jia jia = new Jia(yi);
// 让甲尝试换灯泡
jia.tryChangeBulb();
}
}

运行结果:

1
2
3
4
5
6
甲: 没有梯子, 让乙先去搬梯子
乙: 开始去搬梯子
乙: 梯子搬来了, 通知甲
甲: 梯子到位, 开始换灯泡啦

Process finished with exit code 0

可以看到整个过程被完整的执行了下来,重点在于乙做一个耗时工作之后能够反向调用一个甲的方法。

这里可能有人会问为何会使用接口,其实效果等价于 B 类也持有了一个 A 类的实例,然后对 A 进行调用。我个人认为此处使用接口的意义在于可以将 B 类做的事情通用化,比如在数据库操作中,A 类可能要执行一个写入操作,然而 B 类需要执行对数据库的连接和打开操作,那么 B 类可能是 MySQL、Redis、Mongodb 等不同数据库的操作实例。

第二个感觉可能的情况是 A 和 B 模块分别属于两个人或者团队编写,通过提前制定好的接口,根据时序来实现功能。

拓展

写在这篇文章的最后,Java 的回调并不仅仅用于异步的操作。大家可以跟我一起回想起来 Android 开发的时候,设置一个按钮的动作,setOnClickListener 这个方法,需要传入一个实现了 OnClickListener 接口的类来进行设定,这就是一个同步回调的过程,我之前也没有发觉这是一个回调,直到查阅了相关资料后才恍然大悟。

这次对回调的学习暂且到这里,也算是对回调有了个基本了解,它的思路被用在了 Java 中很多很多地方,比如 Android 和 Hibernate 中都大量使用回调,以后有时间去阅读代码的时候再深入的来学习吧。

Hello World

发表于 2016-06-08 | 分类于 随笔

时至今日终于觉得需要一个自己的主页,不仅是记录和分享一些技术技巧,也能有一个展示自己的空间。

本站建于2016年6月6日,然而却因为最近公司事情多的原因在6月7日才真正的发出第一篇文章。

建站时的选型也纠结了很久。因为自己一直是以 Java 为生,之前也曾为魔幻的 Ruby 所着迷,但是在它变化多端的华丽语法中还是迷失了。这次希望尝试一下 Nodejs,看能否在 Nodejs 中找到一片新的天地。于是乎本站就基于 Hexo 而开发了,看了一些源码,一边看一边学习吧,现阶段还是先以使用为主,之后想自己写一些模块性的东西加进来。

本域名也纠结了很久,无奈 muxin.com 已经被一家公司注册掉了。退而求其次选择一个比较符合程序员特征的域名吧。服务器托管在 Linode 上面的 Fremont 节点,国内的访问速度算是不错了。尝试过很多的其他提供商的日本节点,但是无奈对杭州电信的支持不太友好,最终只得如此做出权衡。

希望这只是一个开始~之后逐渐把这里的空间丰满起来。

方牧心

方牧心

走在互联网前进的路上

6 日志
6 分类
12 标签
© 2016 — 2019 方牧心版权所有