excelbat's blog

每天code一点点

  • 首页
  • 标签
  • 归档

文件系统I/O利用率低调优(下)

发表于 2019-03-07 | 更新于 2019-03-08 | 分类于 系统分析 | 评论数: | 阅读次数:

回顾一下


上一节我们讲解了RandomAccessFile
打开的文件句柄过多导致cpu占用较多,同时我们设想,使用缓存缓存住打开的文件句柄,那么这时候就应该能大幅度降低打开的句柄数目。

尝试前的思考


之前在分析场景的时候,不充分,忽略了一个很重要的问题:在分布式文件系统中,通过版本号来保证每次写入的内容全新,那么每个版本的文件都会新开一个文件句柄,这个不能避免新文件的打开;所以将文件句柄放到hashmap中不可取

后续尝试


待完成

文件系统I/O利用率低调优(上)

发表于 2019-02-23 | 分类于 系统分析 | 评论数: | 阅读次数:

酝酿一下


一直以来都在做分布式存储的事情,从最开始的本地文件系统到伪分布式文件系统,从
FastDFS 到 HDFS ,以及到自研的 DFS,说到 DFS 不得不提两点:1.DFS 存什么?2.
DFS
怎么存?那首先,存什么,其实除了原本的文件内容本身外,我们需要将文件的元数据信息以及文件所在的目录信息也保存下来;其次,我们需要考虑的是如何存,目前主流的分布式文件系统都是依照google
提出的论文所开发的,那就是元数据保存在一个进程,文件内容放在另一个进程,进程间通信,实现文件的读写。

今天的主题


文件系统I/O利用率低:目前自研的dfs文件系统I/O吞吐量上不去,那造成这个问题的元凶到底是什么?

现象描述


环境信息:

  • 操作系统:centos7
  • 磁盘:2T SSD
  • CPU: 48 核

首先,作为公司的统一的dfs平台,接收来自业务的读写数据,从业务方跑benchmark,发现tps
只有2k 多,那作为一个这么牛配置的机器,跑这么低的tps我们现在分析一下。

定位步骤


首先,要做性能分析,上来top 先看系统平均负载信息,很低,对应R 状态的进程也只有dfs
的进程 紧接着,看一下iotop,发现只有很少的7-8M/s的文件写入,这是什么鬼。

  1. 初步怀疑是SSD 没配置好,使用dd 命令跑一下现在的磁盘读写速度:

    写性能:dd if=/dev/zero of=/data/test bs=8M count=1000
    读性能:dd if=/data/test of=/dev/null bs=8M count=1000

    根目录下创建data目录,发现每秒都是1.9G/s 这个排除SSD配置的问题

  2. 那我们把焦点放到存储进程的cpu 使用情况上来,使用火焰图分析:

    简单说一下生成火焰图步骤,root 用户:

    1. yum install perf
    2. 安装cmake
    3. 安装 perf-map-agent (这款工具是为了解决perf生成的方法栈展示成地址没有方法名的问题)
    4. jvm 中增加参数 -XX:+PreserveFramePointer
    5. 安装FlameGraph
    6. 使用脚本 gen-flame-graph.sh生成火焰图,如果本地java命令安装不规范,修改脚本43行路径)

    分析火焰图,发现大量的new
    RandomAccessFile(),频繁的创建关闭,影响性能,尝试缓存。

    RandomAccessFile 构造方法

    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
        
    public RandomAccessFile(File file, String mode)
    throws FileNotFoundException
    {
    String name = (file != null ? file.getPath() : null);
    int imode = -1;
    if (mode.equals("r"))
    imode = O_RDONLY;
    else if (mode.startsWith("rw")) {
    imode = O_RDWR;
    rw = true;
    if (mode.length() > 2) {
    if (mode.equals("rws"))
    imode |= O_SYNC;
    else if (mode.equals("rwd"))
    imode |= O_DSYNC;
    else
    imode = -1;
    }
    }
    if (imode < 0)
    throw new IllegalArgumentException("Illegal mode \"" + mode
    + "\" must be one of "
    + "\"r\", \"rw\", \"rws\","
    + " or \"rwd\"");
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
    security.checkRead(name);
    if (rw) {
    security.checkWrite(name);
    }
    }
    if (name == null) {
    throw new NullPointerException();
    }
    if (file.isInvalid()) {
    throw new FileNotFoundException("Invalid file path");
    }
    fd = new FileDescriptor();
    fd.attach(this);
    path = name;
    open(name, imode);
    }

    那今天先到这,顺便休息下眼睛

redis lua 性能优化

发表于 2019-01-17 | 分类于 系统分析 | 评论数: | 阅读次数:

写在前面的话


本文不是对redis-lua运行环境的优化,而是针对日常开发过程中,具体的业务而进行的优化。

redis lua 脚本优化场景

  • 优点:redis-lua 是对redis 指令的扩充,同时保证脚本执行的原子性。
  • 缺点:在集群环境下需要hashtag的方式,将访问的数据放在同一个节点上,
    至于hashtag的设置简单来说就是在原始key上使用{}
结合一个具体的案例,我们来分析下:

案例

首先,将业务抽象,要保存一棵目录树到redis,比如说人员组织架构(我们不是搞用户中心的),
一种做法是将数据打平,放到redis中 eg:
XX集团/YY分公司/ZZ子公司,一种是使用hash结构 eg:XX集团:{YY分公司:ZZ子公司}

其次,我们现在要获取每个层级的所有子节点,表面上看使用hash结构更加快捷,但如果在集群环境下,这个结构就保存到了一个实例上了,必然会引起
hotkey和bigkey 问题。反观使用打平的方式,因为数据分散到多个节点上,性能反而会提高很多。

接着,今天主要跟大家聊下lua来查询子节点的方案,我们的要求是如何能随便拿出来某一层,当然我们最后选择使用hash结构存储(历史原因吧,客户端每次发送的请求都是某一层级的值,而不是完整路径,所以用模糊匹配查找起来难度较大)
那lua查询获得所有子节点的脚本如下:

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
local treeKey = KEYS[1]
local fnodeId = ARGV[1]

-- 获取某个层级下的子节点
local function getTreeChild(currentnode, t, res)
if currentnode == nil or t == nil then
return res
end

l local nextNode = nil
local nextType = nil
if t == "id" and (type(currentnode) == "number" or type(currentnode) == "string") then
local treeNode = redis.call("HGET", treeKey, currentnode)
if treeNode then
local node = cjson.decode(treeNode)
table.insert(res, treeNode)
if node and node.childIds then
nextNode = node.childIds
nextType = "childIds"
end
end
elseif t == "childIds" then
nextNode = {}
nextType = "childIds"
local treeNode = nil
local node = nil
local cnt = 0
for _, val in ipairs(currentnode) do
treeNode = redis.call("HGET", treeKey, tostring(val))
if treeNode then
node = cjson.decode(treeNode)
table.insert(res, treeNode)
if node and node.childIds then
for _, val2 in ipairs(node.childIds) do
table.insert(nextNode, val2)
cnt = cnt + 1
end
end
end
end
if cnt == 0 then
nextNode = nil
nextType = nil
end
end
-- 使用递归的方式调用
return getTreeChild(nextNode, nextType, res)
end

以上代码出自 https://my.oschina.net/u/1469495/blog/1504818#comments
做了些调整,但是性能存在问题,未在生产上使用。

但是,我们发现业务中查询节点是一个高频的操作,追踪代码发现一个for循环4万次调用,里面又有4次hget操作,合下来就是16w,于是又开始尝试使用lua,将这4次hget合并,当然有同学会问
为什么不使用mget,或者pipeline,因为我们for 循环里的get操作还有业务逻辑,key之间有依赖关系,需要拿到上一个key对应的value,做截取然后在加工,作为下一次的key,所以单纯使用mget解决不了我们的问题
尝试过后,性能提升了好几倍

调试

redis-lua 的调试,推荐使用参数 --ldb-sync-mode
因为该参数,再调试的过程不会被打断,直接使用 –ldb 会被打断
,或者hang住到那,这都不是我们期待的,所以条件允许,请使用 --ldb-sync-mode 参数

调试过程中,常用命令如下:

  1. n(ext) 单步下一步
  2. b(reakpoint) 设置断点 b 13
  3. p(rint) 打印当前context的所有变量

Map中remove()方法导致的死循环

发表于 2019-01-07 | 分类于 基础知识 | 评论数: | 阅读次数:

Map中remove()方法导致的死循环

现象描述

业务进程hang住不动了

问题定位

  • 使用jvisualvm 查看耗资源的线程
  • dump线程,转换16进制,找到对应的线程id,找到线程对应代码
  • 分析发现死循环

问题重现

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {

Map<String,String> testmap = new HashMap<String,String>(16);
byte[] b = new byte[]{67,67};
String a = new String(b);
testmap.put(a,"bb");
while(!testmap.isEmpty()){
testmap.remove(b);
}
System.out.println("未出现死循环");
}

执行上面的语句会发现,已经死循环了,为什么?编译的时候并没有报错!!!

结论

直接remove掉数组是remove不掉的,这就不是一个对象,在写基础代码的时候要注意

apue 开发环境搭建

发表于 2018-12-08 | 更新于 2019-02-23 | 分类于 基础知识 | 评论数: | 阅读次数:

APUE 学习开发环境搭建(MAC 版)

环境信息

  1. mac osx 10.14.1
  2. clion
  3. gcc8(clion 自带)
  4. gdb(clion 自带 、或 LLDB)

搭建步骤

一、 下载apue源码并编译

1
2
3
4
wget http://www.apuebook.com/src.3e.tar.gz
tar -xzvf src.3e.tar.gz
cd apue.3e
make

二、 安装库文件(就相当于java中lib放到path一样)

1
2
sudo cp lib/libapue.a /usr/local/lib/
sudo cp include/apue.h /usr/local/include/

三、 新建clion c工程

可以直接fork我的库,apuebegin,或者直接clone

1
git clone https://github.com/excel-bat/apuebegin.git

四、运行并查看结果

问题总结

1.如果自己已经安装了gdb,无法调试,出现如下错误

1
apuebegin/cmake-build-debug/ls1: unknown load command 0x32

请将自己安装的gdb(如下图),切换为默认Budle gdb

2.无法找到apue.h 文件

1
检查是否正确将头文件添加

BUG-WAR netty 连接重置

发表于 2018-12-07 | 更新于 2019-03-07 | 分类于 问题定位 | 评论数: | 阅读次数:

问题描述

在使用netty过程中,对channelOption设置,当设置tcp _no delay
true的话,会客户端断掉,客户端会发送RST标志, 使用nio的话是错误是

1
2
3
4
java.io.IOException: Connection reset by peer at
sun.nio.ch.FileDispatcherImpl.read0(FileDispatcherImpl.java) at
sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39) at
sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)

使用epoll

1
2
Unhandled exception in native epoll:
io.netty.channel.unix.Errors$NativeIoException: syscall:read!

于是乎,改回后,一切安静

TCPNODELAY.png

开始第一篇博客

发表于 2018-11-27 | 更新于 2019-02-23 | 评论数: | 阅读次数:

以后每两周坚持写一篇博客

坚持原创 !!!

123

excel_bat

技术分享

15 日志
5 分类
9 标签
GitHub E-Mail
© 2023 excel_bat
由 Hexo 强力驱动 v4.0.0
|
主题 – NexT.Pisces v7.0.1