跳到主要内容

博客

代码人生:编织技术与生活的博客之旅

背景

升级到 umi4 后,自定义的 BasicLayout 拿不到 routes 了。导致无法自定义更新 routes。不得不另寻他路来实现动态路由。

整体思路

主要通过 renderpatchClientRoutes 进行,其中 render 负责在渲染前从远端请求路由信息,patchClientRoutes 负责更新路由信息。

注意

注意点:由于这里使用了render进行数据拉取,如果不是SSO方式进行登录的话,需要在登录成功后手动reload()页面才能成功拉取到。

代码实现

routes.ts文件:

export default [
{
path: '/user',
layout: false,
routes: [
{
name: 'login',
path: '/user/login',
component: './User/Login',
},
],
},
// 这个不能少,否则会在动态路由中报错。主要是给动态路由一个原始数据
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
access: 'admin',
component: './Welcome',
},
{
path: '*',
layout: false,
component: './404',
},
];

app.tsx文件添加:

let roleRoutes: any[] = []

const getRoleRoutes = () => {
return fetchMenu().then(res => {
roleRoutes = res.routes
}).catch(err => {
console.error(err)
})
}

const loopRouteItem = (menus: any[], pId: number | string): RouteItem[] => {
return menus.flatMap((item) => {
let Component: React.ComponentType<any> | null = null;
if (item.uri !== '') {
// 防止配置了路由,但本地暂未添加对应的页面,产生的错误
Component = React.lazy(() => new Promise((resolve, reject) => {
import(`@/pages/${item.uri}`)
.then(module => resolve(module))
.catch((error) => {
console.error(error)
resolve(import(`@/pages/exception/404.tsx`))
})
}))
}
if (item.type === 0) {
return [
{
path: item.path,
name: item.title,
icon: item.icon,
id: item.id,
parentId: pId,
children: [
{
path: item.path,
element: <Navigate to={item.children[0].path} replace />,
},
...loopRouteItem(item.children, item.id)
]
}
]
} else {
return [
{
path: item.path,
name: item.title,
icon: item.icon,
id: item.id,
parentId: pId,
element: (
// lazy 加载的页面需要使用 Suspense 防止空白
<React.Suspense fallback={<div>Loading...</div>}>
{Component && <Component />}
</React.Suspense>
)
}
]
}
})
}

export const patchClientRoutes = ({ routes }: any) => {
// 这里获取的是 routes.ts 中配置自定义 layout
const routerIndex = routes.findIndex((item: any) => item.path === '/')
const parentId = routes[routerIndex].id

if (roleRoutes) {
const newRoutes = routes[routerIndex]['routes']

// 往路由中动态添加
newRoutes.push(
...loopRouteItem(roleRoutes, parentId)
)
}

}

export const render = async (oldRender: Function) => {
// 如果请求太慢,可选:自己实现一个加载器效果
document.querySelector('#root')!.innerHTML = `<div>loading...</div>`
const token = Token.get()
// 判断是否登录
if(token){
// 这里是实际获取路由的方法
await getRoleRoutes()
}

oldRender()
}

总结

umi3 使用 BasicLayout 相比,这种方式虽然在登录成功需要 reload() 这种比较怪异的操作,但整理而言还是能够接受的。

antdweb阅读需 2 分钟

直接上docker-compose.yml

version: "2"
services:
sonarqube:
image: sonarqube:lts
container_name: sonarqube
depends_on:
- db
environment:
SONAR_ES_BOOTSTRAP_CHECKS_DISABLE: "true"
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: sonar
volumes:
- ./data:/opt/sonarqube/data
- ./extensions:/opt/sonarqube/extensions
- ./logs:/opt/sonarqube/logs
ports:
- 9002:9000
db:
image: postgres:12
restart: always
environment:
POSTGRES_USER: sonar
POSTGRES_PASSWORD: sonar
volumes:
- ./postgresql_data:/var/lib/postgresql/data
  1. postgresql报错:

    一开始用的是postgresql:12,死活启动不起来,报错:/docker-entrypoint-initdb.d/ 权限错误,换成postgres:12-alpine后就好了。

    根本原因应该不是版本问题,是volumes初始化问题,因为再换回12又同样可以了。

  2. sonarqube报错:could not find java in ES_JAVA_HOME at /usr/lib/jvm/java-11-openjdk/bin/java

    这是因为新版的问题,一开始用的sonarqube:commutity的版本,换回sonarqube:lts就好了。

    或者配置seccomp-profile,详细的可以看这里这里

  3. gradle sonarqube报错:Unable to load component class org.sonar.scanner.report.MetadataPublisher

    这是因为jdk版本和sonarqube要求的版本不一致,会有提示:SonarScanner will require Java 11 to run, starting in SonarQube 9.x,换成高版本jdk即可。

  4. sonarqube中的ce.log报错:duplicate **key** value violates unique constraint "rules_parameters_unique"

    参考这里,我一开始用的版本是14,需要把postgres的版本降到12才行。

阅读需 1 分钟

调整G1参数后,ES整体的gc情况变化:

10-:-XX:-UseConcMarkSweepGC
10-:-XX:-UseCMSInitiatingOccupancyOnly
10-:-XX:+UseG1GC
10-:-XX:G1ReservePercent=25
10-:-XX:InitiatingHeapOccupancyPercent=30

image-20211008112851546

阅读需 1 分钟

安装 hadoop

  1. 下载

    Hadoop :https://archive.apache.org/dist/hadoop/core/hadoop-2.7.7/	
  2. 解压

    tar -xvf hadoop-2.7.7.tar.gz
  3. 配置环境变量

    export HADOOP_HOME=/path/to/hadoop-2.7.7
    export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
    export HADOOP_HDFS_HOME=$HADOOP_HOME
    export PATH=$PATH:$HADOOP_HOME/bin
  4. 执行source

    source .bash_profile
  5. 验证

    hadoop version
  6. 配置Hadoop

    进入~/etc/hadoop/ 目录

    配置hadoop-env.sh配置:

    export JAVA_HOME=/path/to/java/

    配置core-site.xml:

    <configuration>
    <property>
    <name>fs.defaultFS</name>
    <value>hdfs://localhost:9000</value>
    </property>
    </configuration>

    配置hdfs-site.xml:

    <configuration>
    <configuration>
    <property>
    <name>dfs.replication</name>
    <value>1</value>
    </property>
    <property>
    <name>dfs.namenode.name.dir</name>
    <value>file:/path/to/hadoop/hdfs/namenode</value>
    </property>
    <property>
    <name>dfs.datanode.data.dir</name>
    <value>file:/path/to/hadoop/hdfs/datanode</value>
    </property>
    </configuration>
    </configuration>

    格式化hdfs:

    hdfs namenode -format

    启动hadoop:

    cd {hadoop_home}/sbin
    ./start-all.sh

    查看启动是否正常:

    jps
    1986 Jps
    41715 SecondaryNameNode
    41460 DataNode
    41287 NameNode
    39660 ResourceManager

    出现NameNodeDataNode表示已经正常启动

    注意:启动的时候,需要对机器做ssh免登入,包括本机也是,否则无法启动。

Flink的接入,可以参照官方文档来,我这里简单列一下步骤:

  1. 下载 Flink

    FLINK_VERSION=1.11.1
    SCALA_VERSION=2.12
    APACHE_FLINK_URL=archive.apache.org/dist/flink/
    wget ${APACHE_FLINK_URL}/flink-${FLINK_VERSION}/flink-${FLINK_VERSION}-bin-scala_${SCALA_VERSION}.tgz
    tar xzvf flink-${FLINK_VERSION}-bin-scala_${SCALA_VERSION}.tgz
  2. Hadoop环境中启动一个独立的Flink集群

    # HADOOP_HOME is your hadoop root directory after unpack the binary package.
    export HADOOP_CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath`

    # Start the flink standalone cluster
    ./bin/start-cluster.sh
  3. 启动Flink SQL Client客户端

    # download Iceberg dependency
    ICEBERG_VERSION=0.11.1
    MAVEN_URL=https://repo1.maven.org/maven2
    ICEBERG_MAVEN_URL=${MAVEN_URL}/org/apache/iceberg
    ICEBERG_PACKAGE=iceberg-flink-runtime
    wget ${ICEBERG_MAVEN_URL}/${ICEBERG_PACKAGE}/${ICEBERG_VERSION}/${ICEBERG_PACKAGE}-${ICEBERG_VERSION}.jar

    # download the flink-sql-connector-hive-${HIVE_VERSION}_${SCALA_VERSION}-${FLINK_VERSION}.jar
    HIVE_VERSION=2.3.6
    SCALA_VERSION=2.11
    FLINK_VERSION=1.11.0
    FLINK_CONNECTOR_URL=${MAVEN_URL}/org/apache/flink
    FLINK_CONNECTOR_PACKAGE=flink-sql-connector-hive
    wget ${FLINK_CONNECTOR_URL}/${FLINK_CONNECTOR_PACKAGE}-${HIVE_VERSION}_${SCALA_VERSION}/${FLINK_VERSION}/${FLINK_CONNECTOR_PACKAGE}-${HIVE_VERSION}_${SCALA_VERSION}-${FLINK_VERSION}.jar

    # open the SQL client.
    /path/to/bin/sql-client.sh embedded \
    -j ${ICEBERG_PACKAGE}-${ICEBERG_VERSION}.jar \
    -j ${FLINK_CONNECTOR_PACKAGE}-${HIVE_VERSION}_${SCALA_VERSION}-${FLINK_VERSION}.jar \
    shell
  4. 简单使用

    -- 1. 创建 hadoop_catalog
    CREATE CATALOG hadoop_catalog WITH (
    'type'='iceberg',
    'catalog-type'='hadoop',
    'warehouse'='hdfs://localhost:9000/user/hive/warehouse',
    'property-version'='1'
    );

    -- 2. 创建 database
    CREATE DATABASE iceberg_db;

    use iceberg_db;

    -- 3. 创建非分区表和分区表;
    CREATE TABLE `hadoop_catalog`.`iceberg_db`.`sample` (
    id BIGINT COMMENT 'unique id',
    data STRING
    );

    CREATE TABLE `hadoop_catalog`.`iceberg_db`.`sample_partition` (
    id BIGINT COMMENT 'unique id',
    data STRING
    ) PARTITIONED BY (data);

    -- 4. 插入数据
    insert into `hadoop_catalog`.`iceberg_db`.`sample` values (1,'test1');
    insert into `hadoop_catalog`.`iceberg_db`.`sample` values (2,'test2');

    INSERT into `hadoop_catalog`.`iceberg_db`.sample_partition PARTITION(data='city') SELECT 86;

    -- 5. 查询数据

    select * from `hadoop_catalog`.`iceberg_db`.`sample`;
    select * from `hadoop_catalog`.`iceberg_db`.`sample` where id=1;
    select * from `hadoop_catalog`.`iceberg_db`.`sample` where data='test1';

CREATE CATALOG hive_catalog WITH (
'type'='iceberg',
'catalog-type'='hive',
'uri'='thrift://localhost:9083',
'clients'='5',
'property-version'='1',
'warehouse'='hdfs://localhost:9000/user/hive/warehouse'
);

hadoop_catalog

CREATE CATALOG hadoop_catalog WITH (
'type'='iceberg',
'catalog-type'='hadoop',
'warehouse'='hdfs://localhost:9000/user/hive/warehouse',
'property-version'='1'
);

遇到问题

  1. hadoop 启动的时候,需要做免登入,本机也一样。
  2. iceberg无法用于全文检索,但可以用于标签检索。

参考

基于 Flink+Iceberg 构建企业级实时数据湖

Flink Learning

通俗易懂了解什么是数据仓库

Flink + Iceberg 全场景实时数仓的建设实践_腾讯

Flink + Iceberg + 对象存储,构建数据湖方案_阿里

基于 Flink+Iceberg 构建企业级实时数据湖

icebergflink阅读需 2 分钟

今天上班日常巡检,发现一个ES集群报Red了,一看状态,应该是有一台机器挂了。赶紧查看问题

问题确认

登录到指定机器后,发现是磁盘满了,但问题是我使用的是/data1盘,为啥/目录会满掉?由于是部署到Docker的,大概率是这里有问题。

找运维确认一下,发现是镜像内有一个/usr/share/elasticsearch/core.1这个文件超级大,193G直接把根目录占满了。

我们先确认一下这个是啥文件,看起来不是ES正常产生的文件,因为没有做映射,导致直接写到了根目录,没有写到映射目录。

通过搜索,发现也有其他人遇到过这个问题,看这里说的是,在Docker内使用Java 10的话,有一些实验性的功能不稳定,需要设置JVM参数:

-XX:UseAVX=2

具体的Issue可以看这里:https://github.com/elastic/elasticsearch/issues/31425#issuecomment-402522285

暴露的问题

  1. 通过Docker部署后,有些没映射的目录和文件,会直接写到根目录,极有可能导致磁盘被写满;
  2. 因为前期的集群调整,导致有部分索引没有replica,所以在一台机器挂掉后,会直接导致集群状态变为Red,需要及时检查和告警集群内索引的复制数,防止机器挂掉后的集群不可用;
crash阅读需 2 分钟

Elasticsearch Snapshot and Restore

  1. 、elasticsearch.yml配置path.repo注意:这个目录必须是共享文件目录或者其他共享的,否则无法备份

    path.repo: data
  2. 创建Repository

    PUT /_snapshot/my_repository
    {
    "type": "fs",
    "settings": {
    "location": "snapshot"
    }
    }
  3. Create Snapshot

    PUT /_snapshot/my_repository/mblog
    {
    "indices": "data_stream_1,index_1,index_2", // 索引
    "ignore_unavailable": true, // 是否忽略不可用的索引
    "include_global_state": false // 包含全局状态
    }
  4. Snapshot Restore

    POST /_snapshot/my_repository/mblog-2/_restore
    {
    "indices": "{indexName}-*",
    "ignore_unavailable": true,
    "index_settings": {
    "index.number_of_replicas": 0
    },
    "ignore_index_settings": [
    "index.refresh_interval"
    ]
    }

通过 Snapshot 和 Restore 来迁移数据

迁移数据主要场景是从老集群迁移数据到新集群,因为没有存source就无法使用reindex

主要步骤:

  1. 在新集群和老集群的path.repo指向同一个目录;
  2. 分别创建一个相同名称的repository;
  3. 在老集群中创建一个snapshot;
  4. 在新集群使用restore进行数据恢复;
auth7.4.0阅读需 1 分钟

因为有个临时需求,需要搭建一个ES集群,并导入数据。所以需要从头开始,这里主要是记录一下集群搭建、数据导入的过程。

部署

Elasticsearch

因为是新集群,考虑到数据安全性,所以需要SSL认证,这里还是使用docker-compose进行部署:

参考

version: '2.2'

services:
es01:
image: docker.elastic.co/elasticsearch/elasticsearch:${VERSION}
container_name: es01
environment:
- node.name=es01
- cluster.name=es-cluster
- discovery.seed_hosts=es01
- cluster.initial_master_nodes=es01
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms2g -Xmx2g"
- xpack.license.self_generated.type=trial
- xpack.security.enabled=true
- xpack.security.http.ssl.enabled=true
- xpack.security.http.ssl.key=$CERTS_DIR/es01/es01.key
- xpack.security.http.ssl.certificate_authorities=$CERTS_DIR/ca/ca.crt
- xpack.security.http.ssl.certificate=$CERTS_DIR/es01/es01.crt
- xpack.security.transport.ssl.enabled=true
- xpack.security.transport.ssl.verification_mode=certificate
- xpack.security.transport.ssl.certificate_authorities=$CERTS_DIR/ca/ca.crt
- xpack.security.transport.ssl.certificate=$CERTS_DIR/es01/es01.crt
- xpack.security.transport.ssl.key=$CERTS_DIR/es01/es01.key
user: "1004"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- ./data:/usr/share/elasticsearch/data
- ./plugins:/usr/share/elasticsearch/plugins
- ./logs:/usr/share/elasticsearch/logs
- ./config:/usr/share/elasticsearch/config
- ./certs:${CERTS_DIR}
ports:
- 9200:9200
networks:
- elastic

healthcheck:
test: curl --cacert $CERTS_DIR/ca/ca.crt -s https://localhost:9200 >/dev/null; if [[ $$? == 52 ]]; then echo 0; else echo 1; fi
interval: 30s
timeout: 10s
retries: 5
kib01:
image: docker.elastic.co/kibana/kibana:${VERSION}
container_name: kib01
depends_on: {"es01": {"condition": "service_healthy"}}
ports:
- 5601:5601
environment:
SERVERNAME: localhost
ELASTICSEARCH_URL: https://es01:9200
ELASTICSEARCH_HOSTS: https://es01:9200
ELASTICSEARCH_USERNAME: kibana
ELASTICSEARCH_PASSWORD: Pfdx2HSSW4bWmOzHOtIH
ELASTICSEARCH_SSL_CERTIFICATEAUTHORITIES: $CERTS_DIR/ca/ca.crt
SERVER_SSL_ENABLED: "true"
SERVER_SSL_KEY: $CERTS_DIR/kib01/kib01.key
SERVER_SSL_CERTIFICATE: $CERTS_DIR/kib01/kib01.crt
volumes:
- ./certs:${CERTS_DIR}
networks:
- elastic
networks:
elastic:
driver: bridge

Logstash

logstash 配置config/logstash-sample.conf

input {
file {
type => "json"
path => "/path/to/file*"
start_position => beginning
}
}

filter {
json {
source => "message"
}

mutate {
remove_field => ["message", "path", "host", "@version"]
}
}

output {
elasticsearch {
hosts => ["https://localhost:9200"]
index => "index_name"
ssl => true
cacert => '/path/to/certs/ca/ca.crt'
user => "changeme"
password => "changeme"
}
}

运行logstash:

nohup ./bin/logstash -f config/logstash-sample.conf
import阅读需 2 分钟

[TOC]

因为我们的索引集群分为天和历史,在天索引集群中,采用了自研的堆外内存插件,实现堆外的内存索引,减少GC,自主控制内存的使用。但在实际使用时,如果堆外内存大小限制不当,就会导致OOM,具体错误如下:

java.lang.OutOfMemoryError: Cannot reserve 131072 bytes of direct buffer memory (allocated: 17179832357, limit: 17179869184)
at java.nio.Bits.reserveMemory(Bits.java:178) ~[?:?]
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:119) ~[?:?]
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:320) ~[?:?]
at com.antfact.nest.indexer.memory.ByteBufferPool.getBuffer(ByteBufferPool.java:117) ~[?:?]
at org.apache.lucene.store.offheap.RAMOffHeapFile3.newBuffer(RAMOffHeapFile3.java:82) ~[?:?]
at org.apache.lucene.store.offheap.RAMOffHeapFile3.addBuffer(RAMOffHeapFile3.java:50) ~[?:?]
at org.apache.lucene.store.offheap.RAMOffHeapOutputStream3.switchCurrentBuffer(RAMOffHeapOutputStream3.java:124) ~[?:?]
at org.apache.lucene.store.offheap.RAMOffHeapOutputStream3.writeBytes(RAMOffHeapOutputStream3.java:107) ~[?:?]
at org.elasticsearch.common.lucene.store.FilterIndexOutput.writeBytes(FilterIndexOutput.java:59) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.index.store.Store$LuceneVerifyingIndexOutput.writeBytes(Store.java:1232) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.indices.recovery.MultiFileWriter.innerWriteFileChunk(MultiFileWriter.java:120) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.indices.recovery.MultiFileWriter.access$000(MultiFileWriter.java:43) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.indices.recovery.MultiFileWriter$FileChunkWriter.writeChunk(MultiFileWriter.java:200) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.indices.recovery.MultiFileWriter.writeFileChunk(MultiFileWriter.java:68) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.indices.recovery.RecoveryTarget.writeFileChunk(RecoveryTarget.java:469) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.indices.recovery.PeerRecoveryTargetService$FileChunkTransportRequestHandler.messageReceived(PeerRecoveryTargetService.java:518) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.indices.recovery.PeerRecoveryTargetService$FileChunkTransportRequestHandler.messageReceived(PeerRecoveryTargetService.java:492) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:63) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.transport.InboundHandler$RequestHandler.doRun(InboundHandler.java:264) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:773) ~[elasticsearch-7.4.0.jar:7.4.0]
at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) ~[elasticsearch-7.4.0.jar:7.4.0]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[?:?]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[?:?]
at java.lang.Thread.run(Thread.java:830) [?:?]

问题分析

可以很直观地看到,就是在分配堆外内存的时候,无法再进行分配导致的OOM,源于对JVM的不熟悉,一开始并不知道是有参数可以设置堆外内存使用量的。

先说-XX:MaxDirectMemorySize这个参数,网上资料显示,如果没有主动配置,则默认使用-Xmx的值,我们集群的-Xmx32G,按道理,日志中的limit: 17179869184因该是32G才对,但实际只有16G,而我们的配置里面,并没有哪里配置了16G这个值。

1. 确认集群参数

由于是使用docker部署的集群,在查看集群参数时,可能会有一些坑,可以看我这篇文章,这里,我们执行命令:

docker exec -u 1000:0 -it elasticsearch bash

进入后,我们执行:

jdk/bin/jinfo 1

可以看到实例启动时的JVM参数有哪些,过滤一下,我们就可以看到MaxDirectMemorySize了:

VM Arguments:
jvm_args: -Xms32g -Xmx32g -XX:+UseG1GC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -Des.networkaddress.cache.ttl=60 -Des.networkaddress.cache.negative.ttl=10 -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dio.netty.allocator.numDirectArenas=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Djava.io.tmpdir=/tmp/elasticsearch-16662532178385968386 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=data -XX:ErrorFile=logs/hs_err_pid%p.log -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m -Djava.locale.providers=COMPAT -Djava.security.policy=/usr/share/elasticsearch/plugins/jieba/plugin-security.policy -Des.cgroups.hierarchy.override=/ -Dio.netty.allocator.type=pooled -XX:MaxDirectMemorySize=17179869184 -Des.path.home=/usr/share/elasticsearch -Des.path.conf=/usr/share/elasticsearch/config -Des.distribution.flavor=default -Des.distribution.type=docker -Des.bundled_jdk=true

可以看到-XX:MaxDirectMemorySize=17179869184这里明确设置了为16G,那这个参数是哪来的呢?

2. 官方设置的默认值

ESGithub查查看,找到这个IssueConfigure a limit on direct memory usage这里明确说明了,由于官方文档建议设置Heap Size 为物理内存的50%,而MaxDirectMemorySize默认又是-Xmx的大小,这样,会有可能导致OOM,所以官方就给默认设置为了heapSize / 2 作为MaxDirectMemorySize的大小。

再看一下具体的PRLimit max direct memory size to half of heap size

final long maxDirectMemorySize = extractMaxDirectMemorySize(finalJvmOptions);
if (maxDirectMemorySize == 0) {
ergonomicChoices.add("-XX:MaxDirectMemorySize=" + heapSize / 2);
}

简单直接,如果没有设置,就直接取heapSize 的一半。这里,就可以解释为什么我们集群的是16G了,因为heapSize32G

结论

由于ES本身设置了堆外内存的默认值,导致我们使用自研插件时,尽管机器内存还是有,由于超出默认值,还是一样会OOM

我们只需要针对我们大内存的机器,主动设置-XX:MaxDirectMemorySize即可。

通过分析这个问题,暴露出我对JVM参数的不熟悉及对ES整体的掌握不足,导致走了很多弯路,无法快速定位问题所在。这也更进一步惊醒自己,要多积累、多学习。

以上。

共勉。

heapOOM阅读需 3 分钟

[TOC]

公司因为架构调整,需要进行数据的实时加载。原本的方案是直接通过客户端查询索引,同时通过索引返回的id查询详情,整个流程比较复杂,并且其中涉及到索引压力大,无法及时加载完毕;针对大数据任务,延迟很大,无法做到实时分析。

这里,了解到Presto是一个分布式的查询引擎,本身也是支持各种数据源:Hadoop、Elasticsearch、MySQL等。所以尝试使用Presto进行数据加载,具体效果还得验证过后才知道,在这里作为一个记录。

安装Presto

Presto官网可以直接下载,分为三个包:

presto-server-0.253.1.tar.gz:这个是服务端的包,使用这个进行部署及配置;

presto-cli-0.253.1-executable.jar:这个是命令行模式的客户端,可以直接连接服务端,直接进行一些操作;

presto-jdbc-0.253.1.jar:这个就是JavaJDBC驱动了,引入后,就可以在程序中连接Presto了;

使用 docker 部署

  1. 准备

    在使用docker部署前,我们先需要准备几个文件夹和文件:

    mkdir data etc

    编辑etc/node.properties文件内容为:

    // 表示环境,可以用 TEST/production
    node.environment=production
    // 实例的唯一 ID,同一台机器不同实例的时候,必须保证不同;同一个实例重启、恢复后,需要保持不变,否则无法恢复到原有实例
    node.id=ffffffff-ffff-ffff-ffff-ffffffffffff
    // 实例的数据目录,用来存放数据、日志等
    node.data-dir=/var/presto/data

    编辑etc/config.properties文件内容为:

    // 是否为协调节点
    coordinator=true
    node-scheduler.include-coordinator=true
    // 实例端口
    http-server.http.port=8080
    discovery-server.enabled=true
    // 界面地址
    discovery.uri=http://localhost:8080

    编辑etc/jvm.config文件内容为:

    -server
    // 根据实际情况修改堆大小
    -Xmx1G
    -XX:+UseG1GC
    -XX:G1HeapRegionSize=32M
    -XX:+UseGCOverheadLimit
    -XX:+ExplicitGCInvokesConcurrent
    -XX:+HeapDumpOnOutOfMemoryError
    -XX:+ExitOnOutOfMemoryError
    -Djdk.attach.allowAttachSelf=true
  2. 编写docker-compose.yml文件

    version: "2"
    services:
    presto:
    image: ahanaio/prestodb-sandbox:0.254
    volumes:
    - ./data:/var/presto/data
    - ./etc:/opt/presto-server/etc
    ports:
    - 8080:8080
    container_name: presto
  3. 启动

    启动直接使用下列命令即可:

    docker-compose up -d
  4. 访问界面

    在启动成功后,就可以访问http://host:8080查看了。

简单使用

使用docker部署后,就可以进入命令行模式进行使用了:

docker exec -it presto  presto-cli

常用命令:

// 查看库
show catalogs;
// 查看指定库内的 schema
show schemas in {catalog};
// 进入指定库的 schema
use {catalog}.{schema};
// 查看 schema 中的表;
show tables;
// 查询表
select * from {table} limit 1;

数据源接入

Presto的一大优势就是可以接入不同的数据,并且进行联合查询、聚合及操作;

Elasticsearch 接入

  1. 先准备一个es集群,创建一个索引:

    PUT users/_mapping/_doc
    {
    "properties": {
    "key": {
    "type": "keyword"
    },
    "username": {
    "type": "keyword"
    },
    "email": {
    "type": "keyword"
    }
    }
    }
  2. 编写$PRESTO_HOME/etc/catalog/elasticsearch.properties文件:

    connector.name=elasticsearch
    elasticsearch.host=localhost
    elasticsearch.port=9200
    elasticsearch.default-schema-name=my_schema
  3. 编写$PRESTO_HOME/etc/elasticsearch/my_schema.users.json文件:

    {
    "tableName": "users",
    "schemaName": "my_schema",
    "clusterName": "elasticsearch",
    "index": "users",
    "type": "doc",
    "columns": [
    {
    "name": "key",
    "type": "varchar",
    "jsonPath": "key",
    "jsonType": "varchar",
    "ordinalPosition": "0"
    }
    ]
    }
  4. 连接presto:

    docker exec -it presto  presto-cli
  5. 查询

    SELECT * FROM users LIMIT 1;

这样,就是Elasticsearch的完整接入了,如果是多个集群,只需要添加对应的catalogschema文件,然后重启presto集群即可。

presto阅读需 3 分钟