2015年7月

先贴代码,看运行结果:
[code]
Class<?> cache = Integer.class.getDeclaredClasses()[0];
Field c = cache.getDeclaredField("cache");
c.setAccessible(true);
Integer[] array = (Integer[]) c.get(cache);
array[130] = array[131];

Integer x = 2;
System.out.println(x + 2);
[/code]
运行结果实际上是5,其它地方如果出现Integer类型的数值,2就会变成3。

- 阅读剩余部分 -

业务中碰到单锚点同步多项数据问题,举个例子说明以备忘之。

概念解释:
锚点同步:client用自己得到的最后一次操作编号,来获取之后所有的操作,重放以达到状态一致。

问题描述:
多从库环境下,用一个锚点通过两次DAO去同步两类业务数据,有可能会因为两个从库的同步状态不一致,导致丢数据。
比如:用100锚点去同步时,先A业务,再B业务,同步A时,拉到123,同步B时拉到125,两次取数据间A业务产生了124操作。下次client用125来同步,会丢失124操作。

后续:
演练时发现跟是否双从或是否主从无关,只有主库的情况下同样有可能出现类似问题,两次操作有时差,没法保障数据一致(不可能锁上不让操作)。

解决办法:
每次同步时,记录每个业务的最大值操作编号max(a,b,c,d,...),下次以最大编号来拉时,每个业务按自己上次记录的编号拉。

问题:
有可能会出现用125来拉时,拉到124操作。相比之前的124操作丢失,会更好。
从单个业务看,一直是连续的不会丢,如果各业务间操作不耦合,不会有影响。
如果多业务间有时续要求,需要根据业务依赖关系来确定兼容方案,会更复杂,有两种方式可以解决:
1、拉完A后拉B,如果B小于A,则以A为准,可以结束;如果B大于A,拉B后再拉A(记做A1),丢弃掉A1中比B大的部分,以B为准。如果是多从库,必拉一次小的且拉小的数据的操作从主库进行。
2、耦合的业务操作,只重放到小编号的数据。这么做很安全,缺点是及时性差,而且如果某个业务较冷的时候,滞后会非常严重。
推荐方案1。

一直工作很好的线上服务,今天忽然发现有问题,原因是取到的本机IP是错的。
ifconfig的结果如下:
[code]
eth0 Link encap:Ethernet HWaddr 12:xx:xx:xx:xx:xx
inet addr:10.xx.xx.xx Bcast:10.xx.xx.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:347107351 errors:0 dropped:0 overruns:0 frame:0
TX packets:521084913 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:73368864377 (68.3 GiB) TX bytes:73126114339 (68.1 GiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:407951 errors:0 dropped:0 overruns:0 frame:0
TX packets:407951 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:112925033 (107.6 MiB) TX bytes:112925033 (107.6 MiB)
[/code]
取IP的时候过滤掉了非isSiteLocalAddress的IP:
[code]
public static String getLocalIp() {
Enumeration<NetworkInterface> ifs;
try {
ifs = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
return "";
}

while (ifs.hasMoreElements()) {
NetworkInterface i = ifs.nextElement();
Enumeration<InetAddress> as = i.getInetAddresses();
while (as.hasMoreElements()) {
InetAddress a = as.nextElement();
if (!a.isSiteLocalAddress()) continue;
return a.getHostAddress();
}
}
return "";
}
[/code]
取出来的是172.x.x.x,还能ping通,ping通后,arp信息中找不到对应的mac地址。瞬间晕了。

- 阅读剩余部分 -

单纯理解Java的初始化,可以认为有两步,静态初始化的类构造器clinit和实例化的实例构造器init的初始化。
clinit发生在首次访问期静态数据或方法时,再或者是首次new的时候,在init之前。

什么时候有clinit?
《深入理解Java虚拟机》书中给出了大致的答案:
[code]
<clinit>()方法是由编译器自动收集类中的所有变更的赋值动作和静态语句块(static{}块)中的语句产生的
[/code]
这句话总结的非常到位,但下面补的这一刀很容易把人带到沟里去,大意:
[code]
如果一个类中没有静态语句块,也没有对变量的赋值操作,可以不为这个类生成<clinit>()方法。
[/code]

直接的理解是,不出现下面这两种代码,就不需要clinit方法;相反的,出现了,就需要。
[code]
//1.静态变量或静态常量
private static Xxx xxx = ....;
private static final Yyy yyy = ...;

//2.静态代码块
static{
Xxx.xxx();
Yyy.yyy();
}
[/code]

实际上并不是这样的。我下面具体描述一下。

- 阅读剩余部分 -

线上数据库崩溃,怀疑是utf8mb4编码导致的,目前已经重现,并找到具体的解决办法。

原因:
当支持utf8mb4的字段中出现emoji表情时,用like '%xx%'方式全搜索时,崩溃。
应该是MySQL的bug,怀疑是因为emoji的编码长度是4,而utf8_unicode_ci是3,MySQL的like非左匹配操作时,碰到占4字节的utf8mb4时出现问题(猜测)。

解决办法:
用instr函数取代like。不崩溃且效率高。instr('abcd','ab')值为1,instr('abcd','ca')值为0。即大于0为命中。

总结:在like左匹配的时候,用原生like靠谱。如果是两边匹配的时候,like支持复杂语法,所以效率较低。普通的%xx%类型的搜索,直接instr即可搞定。
另外:instr的大小写匹配跟like一样,默认是大小写不敏感的。

以下是完整的异常信息。

- 阅读剩余部分 -