Zacard's Notes


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

  • 搜索
close
Zacard's Notes

线上java项目OOM快速定位与解决方法

发表于 2017-08-21 | 分类于 软件技术 | | 阅读次数

背景

最近比较多的碰到OOM异常,总结下OOM的异常快速定位和解决的办法。

OOM的常见原因

  • 内存分配确实过小
  • 频繁创建对象,没有及时释放
  • 频繁申请系统资源,导致系统资源耗尽(例如:不断创建线程,不断发起网络连接)

定位与解决方法

需要先找到出问题的进程,使用top命令定位:

top

输入top命令后,可以按P(shift+p)根据cpu占用排序、按M根据内存占用排序、按T根据运行时间排序。(可以先按c显示具体的command)

这里先按M根据内存排序查找异常的进程:这里假设出现异常的进程pid为2879

阅读全文 »
Zacard's Notes

重构总结之升级log4j为logback

发表于 2017-07-24 | 分类于 软件技术 | | 阅读次数

背景

随着业务的复杂度上升,流量增大,各个系统的日志量也急剧上升。个别基础服务日志量达到了2G/天。使用的log4j(1)出现了大量丢日志的情况,且写日志的性能也出现下降。由于使用了slf4j作为日志门面,所以考虑换到业界使用更为广泛的logback。

现状

目前系统都以log4j作为日志实现,且有很多第三方包同样也依赖了log4j作为日志输出。如果要使用logback,总不能一个一个的去排除对log4j的依赖吧,不仅工作量大而且容易漏掉。

只能先看下slf4j选择具体日志实现的原理了。。。

阅读全文 »
Zacard's Notes

业务重构总结之分解关联查询

发表于 2017-06-20 | 分类于 软件技术 | | 阅读次数

背景

经历了一段漫长痛苦的重构期,整体的业务重构基本完成。这里总结下重构中遇到的一些问题和经验。这里主要讲下分解关联查询。

例子

例如一个查询标签为‘mysql’的所有文章的关联查询sql:

1
2
3
4
select * from tag
join tag_post on tag_post.tag_id = tag.id
join post on tag_post.post_id = post.id
where tag.tag = 'mysql';

分解成以下多个sql,在应用层做关联:

1
2
3
select id from tag where tag = 'mysql';
select * from tag_post where tag_id=1234;
select * from post where post.id in (123,456,...);

分解关联查询的优点

缓存的效率更高

应用程序可以很方便的缓存单表查询的结果。例如例子中分解后的第1个查询,如果已经在缓存中,这可以跳过第1个查询。

另外对于mysql的query cacha(查询缓存)来说,如果关联查询中的任意表发生了变化,就无法使用缓存,而拆分后,如果其中一个表很少改变,那么该表的缓存命中将很高。

减少数据库锁竞争

查询分解后,执行单个查询可以减少锁的竞争,而关联查询将可能锁多个表。

微服务化友好

应用层做关联,可以更容易的做数据库拆分,更容易做到高性能和可扩展。对于原本的单体应用做微服务化拆分与改造将非常便捷。

查询效率提升

例如例子中的第3个查询,使用了in()代替了关联查询,可以让mysql按照id顺序进行查询,这比随机的关联要更高效。

减少冗余记录的查询

在应用层做关联,意味着对某条记录只需查询一次,而在数据库关联查询,则可能需要重复地访问一部分数据。从这点看,这样还能减少网络和内测消耗。

关联更高效

在应用层关联,往往比mysql中的嵌套循环关联更高效。

Zacard's Notes

RocketMQ阅读笔记

发表于 2017-01-03 | 分类于 软件技术 | | 阅读次数

为何选择rocketmq

选型时主要对比了kafka和rocketmq。两者还是存在较大差异的,kafka一开始的定位就是日志收集,对于订单、交易等可靠传输场景不能很好满足(消息丢失、消息重复消费等方面)。并且rocketmq是使用java编写的,相对于scala编写的kafka,我们有更大的定制与扩展的空间。

数据可靠性

  • rocketmq支持异步/同步刷盘,异步/同步复制
  • kafka异步刷盘,异步/同步复制

刷盘效率上应该是rocketmq更高。因为rocketmq的消息都写到一个文件中,而kafka的每个分区对应一个文件,rocketmq可以充分利用IO组commit机制批量传输数据。

阅读全文 »
Zacard's Notes

dubbo遇到的坑

发表于 2016-12-12 | 分类于 软件技术 | | 阅读次数

背景

目前项目使用dubbo做服务化,同时开启dubbo的consumer端的参数校验。

遇到的坑一

第一个遇到的就是参数分组校验的bug了,这个其实网上很多人提过。

具体原因就是内部类使用_连接符代替实际应该使用的$。具体看如下代码(传送门):

1
2
3
4
5
6
7
8
9
public void validate(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {
String methodClassName = clazz.getName() + "_" + toUpperMethoName(methodName);
Class<?> methodClass = null;
try {
methodClass = Class.forName(methodClassName, false, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
}
// 以下代码略
}

这段代码的作用就是加载dubbo service中的分组校验group的内部类,再根据具体的group进行参数校验。但是内部类的连接符却用了_连接。按理说这个问题应该很容易被发现且很容易被测试出来,不应该出现在dubbo身上。

阅读全文 »
Zacard's Notes

线上java项目cpu100%排查步骤

发表于 2016-12-09 | 分类于 软件技术 | | 阅读次数

1.利用top命令查找异常进程

top

输入top命令后,可以按P(shift+p)根据cpu占用排序、按M根据内存占用排序、按T根据运行时间排序。

这里先按P根据cpu排序查找异常的线程:

可以看到,有个pid为19132的线程占用cpu400%(这里是因为4核,每个cpu都100%,加起来就是400%)

2.根据记录的pid查找其线程占用情况

top -Hc -p 19132

说明:这里-Hc是指按照cpu占用降序

这里可以看到这个pid为31395的线程cpu占用非常高,且运行时间也非常长,肯定是有问题的。

3.打印出异常进程信息

先根据第1步查出的pid,打印出线程堆栈信息到文件:

jstack 19321 > stack-19321.log

根据第二步查出的pid,打印出其16进制:

printf %x 31395

7aa3

然后去stack-19321.log文件里查找7aa3:

vim stack-19321.log

/7aa3

即可。

Zacard's Notes

netty源码解读之时间轮算法实现-HashedWheelTimer

发表于 2016-12-02 | 分类于 软件技术 | | 阅读次数

前因

由于netty动辄管理100w+的连接,每一个连接都会有很多超时任务。比如发送超时、心跳检测间隔等,如果每一个定时任务都启动一个Timer,不仅低效,而且会消耗大量的资源。

解决方案

根据George Varghese 和 Tony Lauck 1996 年的论文:Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility。提出了一种定时轮的方式来管理和维护大量的Timer调度.

原理

时间轮其实就是一种环形的数据结构,可以想象成时钟,分成很多格子,一个格子代码一段时间(这个时间越短,Timer的精度越高)。并用一个链表报错在该格子上的到期任务,同时一个指针随着时间一格一格转动,并执行相应格子中的到期任务。任务通过取摸决定放入那个格子。如下图所示:

以上图为例,假设一个格子是1秒,则整个wheel能表示的时间段为8s,假如当前指针指向2,此时需要调度一个3s后执行的任务,显然应该加入到(2+3=5)的方格中,指针再走3次就可以执行了;如果任务要在10s后执行,应该等指针走完一个round零2格再执行,因此应放入4,同时将round(1)保存到任务中。检查到期任务时应当只执行round为0的,格子上其他任务的round应减1。

是不是很像java中的Hashmap。其实就是HashMap的哈希拉链算法,只不过多了指针转动与一些定时处理的逻辑。所以其相关的操作和HashMap也一致:

  • 添加任务:O(1)
  • 删除/取消任务:O(1)
  • 过期/执行任务:最差情况为O(n)->也就是当HashMap里面的元素全部hash冲突,退化为一条链表的情况。平均O(1)->显然,格子越多,每个格子上的链表就越短,这里需要权衡时间与空间。
阅读全文 »
Zacard's Notes

spring3的@Async异步执行失效

发表于 2016-09-11 | 分类于 软件技术 | | 阅读次数

背景

最近有个项目的spring的@Async的异步执行突然失效了。生产环境异步执行正常,那肯定是开发改了某个地方而导致的。

排查

查看最近提交代码记录。与@Async有相关的改动只有一个spring.xml的改动。初步推断是这个改动引起的。回滚这部分代码,跑测试类,果然异步执行生效了。

原因

在stackoverflow中找到了答案:

In short, the context loaded by the ContextLoaderListener (generally from applicationContext.xml) is the parent of the context loaded by the DispatcherServlet (generally from -servlet.xml). If you have the bean with the @Async method declared/component-scanned in both contexts, the version from the child context (DispatcherServlet) will override the one in the parent context (ContextLoaderListener). I verified this by excluding that component from component scanning in the -servlet.xml – it now works as expected.

意思是说:如果项目中存在多个配置文件(例如:applicationContext.xml、applicationContext-servlet.xml)并且这两个文件中配置的扫描包(即配置的:context:component-scan)都包含了配置过@Async的bean,那么后者就会覆盖前者。

例如以下xml配置:

applicationContext.xml:

<context:component-scan base-package="com.demo" />
<task:annotation-driven/>
<task:executor id="executor" pool-size="5-10" queue-capacity="100" rejection-policy="CALLER_RUNS"/>

applicationContext-servlet.xml:

<context:component-scan base-package="com.demo" />

并且在web.xml中配置的加载顺序为:applicationContext.xml > applicationContext-servlet.xml,那么后者的component-scan就会覆盖前者的,同时前者配置的task也会被覆盖掉不起作用!

Zacard's Notes

mac中jdk的路径

发表于 2016-09-08 | 分类于 软件技术 | | 阅读次数

mac中如何查询jdk安装路径

可以使用工具命令/usr/libexec/java_home.

例如以下所示:

还可以加-V选项列出所有的java home.

例如以下所示:

Zacard's Notes

Hash碰撞

发表于 2016-08-29 | 分类于 软件技术 | | 阅读次数

Hash定义

摘自百度百科:

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

为什么会产生Hash碰撞

…通过散列算法,变换成固定长度的输出,该输出就是散列值…

既然是根据输入值,变换成固定长度的输出,那就必然会存在不同的输入产生相同的输出。

如何解决Hash碰撞

开放地址法

这种方法就是在计算一个key的哈希的时候,发现目标地址已经有值了,即发生冲突了,这个时候通过相应的函数在此地址后面的地址去找,直到没有冲突为止。这个方法常用的有线性探测,二次探测,再哈希。这种解决方法有个不好的地方就是,当发生冲突之后,会在之后的地址空间中找一个放进去,这样就有可能后来出现一个key哈希出来的结果也正好是它放进去的这个地址空间,这样就会出现非同义词的两个key发生冲突。

阅读全文 »
1234…7
zacard

zacard

优生笑,菜鸟哭

65 日志
2 分类
105 标签
RSS
GitHub Weibo ZhiHu
Links
  • DingDang's Notes
© 2015 - 2021 zacard
由 Hexo 强力驱动
主题 - NexT.Mist