MySQL多字段排序的问题

最近在一次开发过程中发现了一个问题,我们都知道MySQL在Order By的时候可以指定多列,会按照指定的顺序依次排序。

例如指定A,B,C列,MySQL会先按A排序,如何A值相同的再按B排序,B值相同的再按C排序。

然后其中还是有不少门道的,例如我就碰到了如下问题,我有一个表test,表结构如下:

1
2
3
4
5
6
7
8
9
10
11

CREATE TABLE `test` (

`A` varchar(128) NOT NULL COMMENT 'A',

`B` varchar(64) NOT NULL COMMENT 'B',

`C` varchar(64) NOT NULL COMMENT 'C'

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TEST'

其中有如下数据:

A B C
C料 12321 123
c料 测试 SPHC
c料 测试 SPHC
C料 测试 测试
C料 测试 测试

接着执行如下查询

1
2
3

select * from test order by A,B,C

按照我们上面的说法最后出来的结果应该是:

A B C
C料 12321 123
C料 测试 测试
C料 测试 测试
c料 测试 SPHC
c料 测试 SPHC

然而实际出来而结果是:

A B C
C料 12321 123
c料 测试 SPHC
c料 测试 SPHC
C料 测试 测试
C料 测试 测试

可以看到的是A列明显乱序了(注意C大小写),不是说好A相同的排序在一起呢,怎么看起了不对了。

既然是按照列的顺序一个一个排序的,那我们就一个一个排除,看看到底是在哪一列排序出了问题。

执行如下查询:

1
2
3

select * from test order by A,B

得到如下结果:

A B C
C料 12321 123
C料 测试 测试
C料 测试 测试
c料 测试 SPHC
c料 测试 SPHC

也就是说在C列加入排序之前,A还是看着有序的,那到底是怎么回事呢?注意回到最开始我们说的MySQL的排序规则,A相同再按B排,B相同按C排。

所以C在加入排序之前,MySQL认为A,B排序后有相同的结果,也就是c料,测试=C料,测试了,所以C加入排序之后可以在前者相同的排序结果中在按C排序。

于是就产生了看起来错误的顺序,这是因为创建表的时候,没有对字段A,B,C指定collation(MYSQL排序依据),默认会使用对应字符集的Collation,

我这里字符集是utf8mb4,对应的默认collation通常是utf8mb4_general_ci,这个collation是大小写不敏感的。

解决办法有两种:

1.更改字段的collation,

1
2
3
4
5
6
7
8
9
10
11

CREATE TABLE `test` (

`A` varchar(128) NOT NULL COMMENT 'A' COLLATE utf8mb4_bin,

`B` varchar(64) NOT NULL COMMENT 'B' COLLATE utf8mb4_bin,

`C` varchar(64) NOT NULL COMMENT 'C' COLLATE utf8mb4_bin

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='TEST'

使用utf8mb4_bin,这个是大小写敏感的。

2.在排序的时候指定BINARY关键字

1
2
3

select * from test order by binary A,binary B, binary C

因为它会强制MySQL将字段作为二进制字符串对待。

由于这是一个历史存在的表,避免产生其它意向不到的结果,我使用了方法二。至此问题解决。

记一次线上Spring和Dubbo死锁排查

问题

前不久线上发现有系统间数据没有同步,排查一通下来发现应该是 MQ 消息没有被消费,通过 MQ Console 发现,未被消费的消息全都集中的一台机器上(消息投放的queue 以及 rebalance 关系),此时我有两个怀疑,一是消费者线程挂了,二是此服务分配到的消息队列出了什么莫名的问题。由于正值业务高峰,领导第一时间重启了服务,重启后一切恢复正常。排查由于一些其它事项,也到此中断。

然而没过多久,我听到其它需求项目组在测试环境出现了相同的问题,也是消息不消费了,导致业务异常。我立马找到对应服务 dump 了线程。

我将 dump 文件通过 visualVM 打开,然后得到了很明显的提示:Found one Java-level deadlock:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"ConsumeMessageThread_1":
waiting to lock monitor 0x00007f1350390f18 (object 0x000000008024ebc0, a java.util.concurrent.ConcurrentHashMap),
which is held by "main"
"main":
waiting to lock monitor 0x00007f1330071f18 (object 0x0000000080fb6318, a org.apache.dubbo.config.deploy.DefaultModuleDeployer),
which is held by "Thread-26"
"Thread-26":
waiting to lock monitor 0x00007f1350390f18 (object 0x000000008024ebc0, a java.util.concurrent.ConcurrentHashMap),
which is held by "main"

Java stack information for the threads listed above:
===================================================
"ConsumeMessageThread_1":
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:187)
- waiting to lock <0x000000008024ebc0> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:486)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:432)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:403)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:515)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeansOfType(DefaultListableBeanFactory.java:508)
at org.springframework.context.support.AbstractApplicationContext.getBeansOfType(AbstractApplicationContext.java:1186)
...
at org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService$ConsumeRequest.run(ConsumeMessageConcurrentlyService.java:411)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
"main":
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.startSync(DefaultModuleDeployer.java)
- waiting to lock <0x0000000080fb6318> (a org.apache.dubbo.config.deploy.DefaultModuleDeployer)
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.start(DefaultModuleDeployer.java:139)
at org.apache.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:228)
...
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1173)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1067)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
- locked <0x000000008024ebc0> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
- locked <0x00000000804b98b0> (a java.lang.Object)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1118)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1107)
at com.internet.saasbillmanager.ApplicationMain.main(ApplicationMain.java:46)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
at org.springframework.boot.loader.Launcher.launch(Launcher.java:51)
at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:52)
"Thread-26":
at org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners(AbstractApplicationEventMulticaster.java:185)
- waiting to lock <0x000000008024ebc0> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:128)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:393)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:347)
at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.publishEvent(DubboDeployApplicationListener.java:91)
at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.access$000(DubboDeployApplicationListener.java:47)
at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener$1.onStarted(DubboDeployApplicationListener.java:70)
at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener$1.onStarted(DubboDeployApplicationListener.java:62)
at org.apache.dubbo.common.deploy.AbstractDeployer.setStarted(AbstractDeployer.java:121)
at org.apache.dubbo.config.deploy.DefaultApplicationDeployer.onStarted(DefaultApplicationDeployer.java:989)
at org.apache.dubbo.config.deploy.DefaultApplicationDeployer.checkState(DefaultApplicationDeployer.java:868)
- locked <0x0000000080fa6c10> (a java.lang.Object)
at org.apache.dubbo.config.deploy.DefaultApplicationDeployer.notifyModuleChanged(DefaultApplicationDeployer.java:851)
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.onModuleStarted(DefaultModuleDeployer.java:264)
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.startSync(DefaultModuleDeployer.java:171)
- locked <0x0000000080fb6318> (a org.apache.dubbo.config.deploy.DefaultModuleDeployer)
at org.apache.dubbo.config.deploy.DefaultModuleDeployer.start(DefaultModuleDeployer.java:139)
at org.apache.dubbo.config.ReferenceConfig.get(ReferenceConfig.java:228)
at org.apache.dubbo.config.spring.ReferenceBean.getCallProxy(ReferenceBean.java:346)
at org.apache.dubbo.config.spring.ReferenceBean.access$100(ReferenceBean.java:99)
at org.apache.dubbo.config.spring.ReferenceBean$DubboReferenceLazyInitTargetSource.createObject(ReferenceBean.java:353)
at org.springframework.aop.target.AbstractLazyCreationTargetSource.getTarget(AbstractLazyCreationTargetSource.java:86)
- locked <0x0000000085b49868> (a org.apache.dubbo.config.spring.ReferenceBean$DubboReferenceLazyInitTargetSource)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:192)
...
at com.xxl.job.core.thread.JobThread.run(JobThread.java:152)

Found 1 deadlock.

这里有 3 个线程:

  • “ConsumeMessageThread_1” MQ 消费者线程

  • “main”: 主线程

  • “Thread-26”: XXL-JOB 执行线程

其实主要是这两个线程引发的问题:

  • “main”: 主线程

  • “Thread-26”: XXL-JOB 执行线程

MQ 消费者线程是正好撞在枪口上了。

涉及两个锁:

  • DefaultModuleDeployer:object monitor lock

  • DefaultSingletonBeanRegistry.singletonObjects:object monitor lock

暂且将第一个称为 Dubbo 锁,第二个称为 Spring 锁。

“main”: 主线程做的事情是初始化一个基础服务(Dubbo Consumer)注册到 Spring 和 Dubbo 中, 而其先获取到了 Spring 锁,再去获取 Dubbo 锁。

“Thread-26”:job 线程是由 xxljob 触发后执行一个任务,需要调用一个远程的 Dubbo 服务,于是 先获取了 Dubbo 锁,再去获取 Spring 锁。

于是两个线程就互相锁死了,而 MQ 消费者线程,也要去Bean Container 中获取 Bean,也需要获取 Spring 锁,也就卡死了。

解决方案

这个问题由于是在启动过程中发生,所以我设想了两个解决方案(没有好的契机去解决这个问题,只能等到有相关需求了),其实思路是同一个,那就是将出问题的 Bean 给延迟加载:

  • 将“基础服务”Bean 给@Lazy。
  • 将“XxlJobSpringExecutor”在spring启动完成之后再注册进容器,避免启动过程中收到任务的执行命令。

老版本Dubbo的一个bug

线上有用户反应P 服务页面时不时就报个错,后来发现都涉及I服务的一个接口方法,这里暂且就叫F 接口。只是时不时报错,那说明可能是某些参数会导致异常,于是我开始查看日志,看看是不是有什么特殊参数导致隐藏 bug 被发现。
结果是报错和不报错的请求参数都一样,那是不是多台机器上运行的代码不一致呢?于是我看了F 接口的代码,发现最近没有迭代记录,而且所有机器上部署的代码均一致。
那有没有可能是环境问题?于是我上 Dubbo 控制台查看,发现 provider 和 consumer 都正常:
DubboAdmin
这个时候我开始有点懵逼了😅。

我又想到,如果是一直存在问题,那么不可能到今天才有用户反馈,所以还是跟近期的什么操作有关。于是我开始排查最早是什么时间出现的问题,发现是 10 月 10 日的 18:00:03,又发现F 接口所在的I服务10 月 10 日的 17:57:28有过发布记录。
release

这下就有点奇怪了,就算I服务所有机器同时发布,可能也就是报一会 no providers错误啊,更不用说是滚动发布的,加上 dubbo 的 failover不会一直报错的啊。

就在这时我看到了一个关键的错误信息:(之前只看到了 NPE 没具体位置):
Error

问题直指DubboInvoker:109。

我们 dubbo的版本是(2.6.6),我拉下dubbo 代码发现这块的代码是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@Override
public boolean isAvailable() {
if (!super.isAvailable())
return false;
for (ExchangeClient client : clients) {
//这一行是 109,就是这一行报错
if (client.isConnected() && !client.hasAttribute(Constants.CHANNEL_ATTRIBUTE_READONLY_KEY)) {
//cannot write == not Available ?
return true;
}
}
return false;
}

109 行报 NPE 莫非 client 是 null?带上猜测我开始看 client 是怎么来的,最终找到com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#getSharedClient:

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
private ExchangeClient getSharedClient(URL url) {
String key = url.getAddress();
ReferenceCountExchangeClient client = referenceClientMap.get(key);
if (client != null) {
if (!client.isClosed()) {
client.incrementAndGetCount();
return client;
} else {
//3
referenceClientMap.remove(key);//4
}
}

locks.putIfAbsent(key, new Object());
synchronized (locks.get(key)) {//2
if (referenceClientMap.containsKey(key)) {
return referenceClientMap.get(key);//5
}

ExchangeClient exchangeClient = initClient(url);
client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
referenceClientMap.put(key, client);
ghostClientMap.remove(key);
locks.remove(key);
//1
return client;
}
}

又是一段平平无奇的代码😅,看到这里有用到锁,说明开发者考虑到这里可能会存在并发调用。我又看了下日志,10 月 10 日的 18:00:03前后确实出现了并发调用的现象,难道是这里的并发控制有 bug?于是我开始思考各种场景,还真被我发现一种可能出问题的情况,下面我画了个图演示一下:

flow

最后 序号 5 处返回了一个 null 的 client。

由于2.6.6 版本已经有点老,指不定这个问题已经有人提过,于是我到 github 一番搜索,结果找到了另外一个问题:https://github.com/apache/dubbo/issues/6444

这块dubbo 后面也迭代了好几次,已经搞不清楚了。

最后,重启大法好!

同事问了我一个关于RocketMQ的问题

同事突然问我:RocketMQ的一个消息,多次消费重试,消息的msgId会不会变?哪怕已经进了DLQ。

刚开始出于经验,我说不会变。因为我之前每次排查问题的时候,用同一个msgId都能找到多次重试消费的日志。后来为了更加确定,我卷了一下源码,我看的是4.6.1,一是因为公司用的这个版本,二是我上次卷的就是这个版本。。。

消息重试

既然跟重试有关,那就从客户端消费失败的逻辑开始,看看能不能找到蛛丝马迹,下面是消费失败将消息发回broker的代码:

阅读更多

博客用的图床挂了

之前不想把博客里面的图片也一同上传,用了utools里面一个默认的免费图床,结果今天发现好像图床挂掉了。之前写的博客里面的图片全挂了。。。果断换了gitee做新的图床。

这不会再挂了吧。。。


2022-05-20 更新

真是没想到,gitee关闭了public库,这下图床又挂了…已经申请了公开库的申请…不知道会不会通过.


2022-05-23 更新

gitee回复了,图床类的库不给公开.gg了.

被Chrome一个bug坑了

1

相信Chrome浏览器开发者工具中的Preview你不陌生,但是就这玩意有一个bug。

事情是这样的,前端跟我说你接口有bug,返回的数据不正确。听到bug,我反手就是一个你会不会用。

阅读更多

关闭代码块移动

Mac 开启了三指拖拽,发现在IDEA的某些Project中,选中代码块时,老是拖动代码。解决办法:

1
Editor –> General -> Enable Drag’n’Drop functionality in Editor 

关闭即可。

mongodb 备份数据

备份[部分]数据为dump,然后从collection中将已备份数据删除。

./mongodump  -d trans -c test -h 192.168.190.128 -u trans -p 123456 -o /data/backup

-h:指明数据库宿主机的IP

-u:指明数据库的用户名

阅读更多

mongodb13亿数据清理记录

mongodb上一个collection数据已经膨胀到13亿,也就最近一两个月的数据重要一点。于是考虑清除一下这个collection。
于是:
第一步:
先将原表备份

阅读更多