本文来自网易云社区

作者:田躲躲

用户行为统计(User Behavior Statistics, UBS)一直是互联网产品中必不可少的环节,也俗称埋点。对于产品经理,运营人员来说,埋点当然是越多,覆盖范围越广越好。通过用户行为分析系统可洞悉用户基本操作习惯、探析用户心理。通过行为数据的补充,构建出精细、完整的用户画像,对不同特征用户做个性化营销,提升用户体验。让产品设计人员准确评估用户行为路径转化、产品改版优良、某一新功能对产品的影响几何,让运营人员做精准营销并且评估营销结果等。

目前所负责项目前期采用了前后端约定字段,埋点统计用户操作行为。数据存放在DDB中。如果用户行为日志非常大的话,这种方式肯定是不可行的。故采用了目前比较成熟的ELK代替之前的统计流程。本篇文章主要介绍ELK集群搭建,基本API封装,以及遇到的一些坑。

Elasticsearch

Elasticsearch是一个基于Lucene构建的开源、分布式、RESTful风格的搜索引擎。它被设计用于云计算中,具有实时搜索负载、稳定、快速、安装使用方便等优点。(之前用过SolrCloud,ES对用户的侵入性简直可以忽略)

集群安装:

每台机器先配置elasticsearch.yml,主要配置信息如下:

#
# ---------------------------------- Cluster -----------------------------------
#
# Use a descriptive name for your cluster:
#
cluster.name: es-commenta-event #其他机器集群名称应该保持一致
#
# ------------------------------------ Node ------------------------------------
#
# Use a descriptive name for the node:
#
node.name: es-node-c1
#
# Add custom attributes to the node:
#
#node.attr.rack: r1
#
# ----------------------------------- Paths ------------------------------------
#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: /opt/elk/elasticsearch-5.1.1/data
#
# Path to log files:
#
path.logs: /opt/elk/elasticsearch-5.1.1/logs
#
# ----------------------------------- Memory -----------------------------------
#
# Lock the memory on startup:
#
#bootstrap.memory_lock: true
#
# Make sure that the heap size is set to about half the memory available
# on the system and that the owner of the process is allowed to use this
# limit.
#
# Elasticsearch performs poorly when the system is swapping the memory.
#
# ---------------------------------- Network -----------------------------------
#
# Set the bind address to a specific IP (IPv4 or IPv6):
#
network.host: 192.168.140.133 #本机器host
#
# Set a custom port for HTTP:
#
#http.port: 9200
#
# For more information, see the documentation at:
# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html>
#
# --------------------------------- Discovery ----------------------------------
#
# Pass an initial list of hosts to perform discovery when new node is started:
# The default list of hosts is ["127.0.0.1", "[::1]"]
#
discovery.zen.ping.unicast.hosts: ["192.168.140.133",  "192.168.140.134", "192.168.140.135"] #集群host列表 # Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1):
#
discovery.zen.minimum_master_nodes: 2

集群启动:

Q1:can not run elasticsearch as root

因为是本地虚拟机root安装的,启动的时候会报这个错。解决方案是:

group esgroup 
useradd esuser -g esgroup -p espassword
chown -R esuser:esgroup /etc/
chown -R esuser:esgroup /opt/

切换到esuser用户即可执行启动命令。

Q2:Unsupported major.minor version 52.0

目前安装的ES版本为5.1.1,需要Jdk1.8的版本,故安装下Jdk1.8,配置下环境变量,即可执行启动命令。

Q3:max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144]

ES启动占用更大的内存。修改如下:

sysctl -w vm.max_map_count=262144

每个ES服务设置好后,就可以真正启动了。依次启动机器的时候,可以看下机器日志是否有node加入到集群。如:

curl '192.168.140.133:9200'{    "name": "es-node-c1", 
    "cluster_name": "es-commenta-event", 
    "cluster_uuid": "wi_1VOWoRqecjIht3Ra3mg", 
    "version": {        "number": "5.1.1", 
        "build_hash": "5395e21", 
        "build_date": "2016-12-06T12:36:15.409Z", 
        "build_snapshot": false, 
        "lucene_version": "6.3.0"
    }, 
    "tagline": "You Know, for Search"}

目前有3台虚拟机,默认ES有5个节点,可以通过命令创建3个节点的index,每个主节点有一个复制节点。

curl -XPUT 'http://192.168.140.133:9200/commenta' -d '{"settings" : {"number_of_shards" : 3,"number_of_replicas" : 1}}'

集群状态:

curl 'http://192.168.140.133:9200/_cluster/health?pretty'{  "cluster_name" : "es-commenta-event",  "status" : "green",  "timed_out" : false,  "number_of_nodes" : 3,  "number_of_data_nodes" : 3,  "active_primary_shards" : 3,  "active_shards" : 6,  "relocating_shards" : 0,  "initializing_shards" : 0,  "unassigned_shards" : 0,  "delayed_unassigned_shards" : 0,  "number_of_pending_tasks" : 0,  "number_of_in_flight_fetch" : 0,  "task_max_waiting_in_queue_millis" : 0,  "active_shards_percent_as_number" : 50.0}

安装插件:

通过类SQL转化成DSL
bin/elasticsearch-plugin install install https://github.com/NLPchina/elasticsearch-sql/releases/download/5.1.1.0/elasticsearch-sql-5.1.1.0.zip
X-Pack集成了权限、监控等功能,是一款非常有用的插件。但是商用的,收费。
bin/elasticsearch-plugin install x-pack

Logstash

Logstash是一款轻量级的日志搜集处理框架,可以方便的把分散的、多样化的日志搜集起来,并进行自定义的处理,然后传输到指定的位置。

安装:

到官网下载logstash5.1.1版本即可。

启动:

1、无配置文件启动

bin/logstash -e 'input{ stdin{} } output{ stdout{} }'Sending Logstash's logs to /home/webedit/logstash/logstash-5.1.1/logs which is now configured via log4j2.properties
The stdin plugin is now waiting for input:
[2017-04-27T15:47:38,023][INFO ][logstash.pipeline        ] Starting pipeline {"id"=>"main", "pipeline.workers"=>4, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>5, "pipeline.max_inflight"=>500}
[2017-04-27T15:47:38,039][INFO ][logstash.pipeline        ] Pipeline main started
[2017-04-27T15:47:38,115][INFO ][logstash.agent           ] Successfully started Logstash API endpoint {:port=>9600}
hello elastic
2017-04-27T07:49:00.966Z localhost.localdomain hello elastic

logstash会采集命令行输入的命令

2、配置文件启动

假设我们需要采集的日志记录是这种格式的:

INFO  [17.04.27 16:12:12][com.netease.mail.vip.commenta.filter.EventLogFilter]: |44171|1|1|1|1493280732227|0.0|123.58.160.131|133001|COMMENTA-B54C43F5-4FCB-4D10-B9EC-67862FBF0055|1493280732440|huiping_mp|0.7.0|null|1|

如何采集这种格式的日志呢?这里采用正则表达式去匹配,具体配置文件如下:

input {

file {
    type => "commenta"
    path => ["/home/logs/commenta/stdout.log"]
    start_position => "beginning"
    codec => plain { charset => "Windows-1252" }
} } filter {if [type] == "commenta" {
    grok {
      match => { "message" => "%{DATA:className}\|%{BASE16FLOAT:id}\|%{DATA:eventType:int}\|%{DATA:page:int}\|%{DATA:eventFrom:int}\|%{DATA:eventTime}\|%{BASE16FLOAT:eventWeight}\|%{DATA:ip}\|%{BASE16FLOAT:userId}\|%{DATA:uniqueCode}\|%{DATA:createTime}\|%{DATA:clientFrom}\|%{DATA:appVersion}\|%{DATA:data}\|%{DATA:eventStep:int}\|"}
          remove_field => ["message"]
        }
}if '_grokparsefailure' in [tags] { #过滤掉不匹配的事件
drop{}
} mutate  { #数据类型转换  
                        convert => [ "eventWeight", "float"]
                        convert => [ "id", "float"]
                        convert => [ "userId", "float"]
                } }
output{
       
       stdout { codec => rubydebug } #打印出行为日志记录在控制台        elasticsearch{
             hosts => ["192.168.140.133:9200","192.168.140.134:9200","192.168.140.135:9200"]
             index => "commenta"
        }
}

下面我们可以启动logstash看下效果:

./bin/logstash -f ./config/logstash.conf{     "appVersion" => "0.7.0",           "data" => "null",             "ip" => "XXXXXXXXX",      "className" => "INFO  [17.04.27 16:12:12][com.netease.mail.vip.commenta.filter.EventLogFilter]: ",      "eventType" => 1,           "type" => "commenta",    "eventWeight" => 0.0,         "userId" => 133001.0,           "tags" => [],           "path" => "/home/logs/commenta/stdout.log",     "@timestamp" => 2017-04-27T08:18:58.245Z,     "uniqueCode" => "COMMENTA-B54C43F5-4FCB-4D10-B9EC-67862FBF0055",     "createTime" => "1493280732440",       "@version" => "1",           "host" => "testfb-m126-161",      "eventTime" => "1493280732227",      "eventStep" => 1,     "clientFrom" => "huiping_mp",             "id" => 44171.0,           "page" => 1,      "eventFrom" => 1}

通过打印在控制台的日志可以看到我们已经通过logstash收集到了行为日志记录(部分数据已脱敏)。当然我们也可以通过Kibana看到这些数据,下部分将会讲到。

3、启动问题

Q1:Unsupported major.minor version 52.0

使用的是Logstash版本为5.1.1,需要Jdk1.8的环境,故安装下Jdk1.8,配置下环境变量,即可执行启动命令。

Q2:unknown setting host for elasticsearch

配置Logstash的启动文件时,注意版本的问题,如host-->hosts

Kibana

Kibana是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看、交互存放在Elasticsearch索引里的数据,使用各种不同的图表、表格、地图等kibana能够很轻易地展示高级数据分析与可视化。

安装:

到官网下载Kibana5.1.1版本即可。

启动:

主要配置如下:

# Kibana is served by a back end server. This setting specifies the port to use.
#server.port: 5601 # Specifies the address to which the Kibana server will bind. IP addresses and host names are both valid values.
# The default is 'localhost', which usually means remote machines will not be able to connect.
# To allow connections from remote users, set this parameter to a non-loopback address.
server.host: "192.168.140.133" # Enables you to specify a path to mount Kibana at if you are running behind a proxy. This only affects
# the URLs generated by Kibana, your proxy is expected to remove the basePath value before forwarding requests
# to Kibana. This setting cannot end in a slash.
#server.basePath: "" # The maximum payload size in bytes for incoming server requests.
#server.maxPayloadBytes: 1048576 # The Kibana server's name.  This is used for display purposes.
#server.name: "your-hostname" # The URL of the Elasticsearch instance to use for all your queries.
elasticsearch.url: "http://192.168.140.133:9200"
.......

启动成功后,我们可以监控commenta*的索引(安装ES的时候,创建了)

bin/kibana

这时候就可以看到Logstash收集到的数据日志了

当然我们也可以配置一些统计:

为了更直观的展示,我们可以把统计“拖拽”到Dashboard中。

至此,ELK已经搭建完成,并提供一些简单的功能。 但是有一些统计Kibana是做不了的。这时候我们程序需要处理一下。

Java API

HandleEsClientServer

/* ES服务器列表 */
    private String serverList;    /* 设置client.transport.sniff为true来使客户端去嗅探整个集群的状态,把集群中其它机器的ip地址加到客户端中,它会自动帮你添加,并且自动发现新加入集群的机器 */
    private Boolean sniff = false;    /* 集群名称 */
    private String clusterName;    /* 连接客户端 */
    private Client client;    /* 搜索基本工具类 */
    private SearchDao searchDao;    public HandleEsClientServer() {
    }    public HandleEsClientServer(String serverList, Boolean sniff, String clusterName) {        this.serverList = serverList;        this.sniff = sniff;        this.clusterName = clusterName;
    }    @Override
    public void afterPropertiesSet() throws Exception {         logger.info("es server start at time={}, serverList={}, clusterName={}, sniff={}", DateUtil.toStr(new Date(),DateUtil.YYYY_MM_DD_HH_MM_SS),
                serverList, clusterName, sniff);        if (this.getServerList() == null || this.getServerList().length() == 0) {
            logger.error("es serverList is null...");            return;
        }         List clusterList = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(this.getServerList());         List transportAddresses = new ArrayList<>();        for (String cluster : clusterList) {
            List host = Splitter.on(":").trimResults().omitEmptyStrings().splitToList(cluster);
            String ip = host.get(0);
            Integer port = Integer.valueOf(host.get(1));            try {
                transportAddresses.add(new InetSocketTransportAddress(InetAddress.getByAddress(getIpByte(ip)), port == null ? 9300 : port));
            } catch (UnknownHostException e) {
                logger.error("init es client error={} at time={} ", e, DateUtil.toStr(new Date(),DateUtil.YYYY_MM_DD_HH_MM_SS));                return;
            }
        }        //配置启动参数
        Settings settings = Settings.builder()
                .put("cluster.name", clusterName)
                .put("client.transport.sniff", sniff)
                .build();        //初始化Client
        this.client = new PreBuiltTransportClient(settings)
                .addTransportAddresses(transportAddresses.toArray(new TransportAddress[transportAddresses.size()]));        this.searchDao = new SearchDao(this.client);         logger.info("es server start success at time={}", DateUtil.toStr(new Date(),DateUtil.YYYY_MM_DD_HH_MM_SS));     }

HandleEsData

 /**
     * 根据elasticsearch-sql插件的sql语句查询结果。
     * @param query
     * @return
     * @throws SqlParseException
     * @throws SQLFeatureNotSupportedException
     */
    public SqlResponse selectBySQL(String query) throws SqlParseException, SQLFeatureNotSupportedException {         logger.info("selectBySQL, query={}",query);        try{
            SqlElasticSearchRequestBuilder select = (SqlElasticSearchRequestBuilder) searchDao.explain(query).explain();            return new SqlResponse((SearchResponse)select.get());
        }catch (Exception e){
            logger.error(e.getMessage(),e);
        }        return null;     }/**
     * 批量插入数据,使用Obj的id字段。
     * @param _index
     * @param _type
     * @param data
     * @param generate_id
     * @param 
     * @return
     */
    public  BulkResponse batchObjIndex(String _index, String _type, List data, boolean generate_id){         logger.info("batchObjIndex, index={}, type={}, data={}, generate_id={}", _index, _type, data, generate_id);         Assert.notEmpty(data, "data is not allowed empty");         BulkRequestBuilder bulkRequest = client.prepareBulk();        for (T tObj : data) {
            Class clazz = tObj.getClass();
            String json = JSONObject.toJSONString(tObj, SerializerFeature.WriteMapNullValue);            if(generate_id){
                bulkRequest.add(client.prepareIndex(_index.toLowerCase(), _type.toLowerCase()).setSource(json));
            } else {                try {
                    Object value = clazz.getDeclaredMethod("getId").invoke(tObj);
                    String _id = String.valueOf(value);
                    bulkRequest.add(client.prepareIndex(_index.toLowerCase(), _type.toLowerCase(), _id).setSource(json));
                } catch (Exception e) {
                    logger.error(e.getMessage(),e);
                }
            }
        }        return bulkRequest.execute().actionGet();
    }

参考资料:

http://www.learnes.net/

http://udn.yyuap.com/doc/logstash-best-practice-cn/

https://www.gitbook.com/book/chenryn/elk-stack-guide-cn/details

https://www.elastic.co/guide/en/elasticsearch/reference/5.1/getting-started.html

https://www.elastic.co/guide/en/logstash/5.1/getting-started-with-logstash.html

https://www.elastic.co/guide/en/kibana/5.1/getting-started.html

http://elasticsearch.cn/

网易云免费体验馆,0成本体验20+款云产品!

更多网易研发、产品、运营经验分享请访问网易云社区

相关文章:
【推荐】 HTTP/2部署使用
【推荐】 类似gitlab代码提交的热力图怎么做?

收集、分析线上日志数据实战——ELK的更多相关文章

  1. 线上日志集中化可视化管理:ELK

    本文来自网易云社区 作者:王贝 为什么推荐ELK: 当线上服务器出了问题,我们要做的最重要的事情是什么?当需要实时监控跟踪服务器的健康情况,我们又要拿什么去分析?大家一定会说,去看日志,去分析日志.是 ...

  2. ELK 日志采集 实战教程

    概要 带着问题去看教程: 不是用logstash来监听我们的日志,我们可以使用logback配置来使用TCP appender通过TCP协议将日志发送到远程Logstash实例. 我们可以使用Logs ...

  3. K8S(15)监控实战-ELK收集K8S内应用日志

    K8S监控实战-ELK收集K8S内应用日志 目录 K8S监控实战-ELK收集K8S内应用日志 1 收集K8S日志方案 1.1 传统ELk模型缺点: 1.2 K8s容器日志收集模型 2 制作tomcat ...

  4. 1.8-1.10 大数据仓库的数据收集架构及监控日志目录日志数据,实时抽取之hdfs系统上

    一.数据仓库架构 二.flume收集数据存储到hdfs 文档:http://flume.apache.org/releases/content/1.9.0/FlumeUserGuide.html#hd ...

  5. 大数据时代日志分析平台ELK的搭建

    A,首先说说ELK是啥,  ELK是ElasticSearch . Logstash 和 Kiabana 三个开源工具组成.Logstash是数据源,ElasticSearch是分析数据的,Kiaba ...

  6. 万能日志数据收集器 Fluentd - 每天5分钟玩转 Docker 容器技术(91)

    前面的 ELK 中我们是用 Filebeat 收集 Docker 容器的日志,利用的是 Docker 默认的 logging driver json-file,本节我们将使用 fluentd 来收集容 ...

  7. 利用ELK分析Nginx日志生产实战(高清多图)

    本文以api.mingongge.com.cn域名为测试对象进行统计,日志为crm.mingongge.com.cn和risk.mingongge.com.cn请求之和(此二者域名不具生产换环境统计意 ...

  8. 带你了解zabbix整合ELK收集系统异常日志触发告警~

    今天来了解一下关于ELK的“L”-Logstash,没错,就是这个神奇小组件,我们都知道,它是ELK不可缺少的组件,完成了输入(input),过滤(fileter),output(输出)工作量,也是我 ...

  9. ELK收集Nginx自定义日志格式输出

    1.ELK收集日志的有两种常用的方式: 1.1:不修改源日志格式,简单的说就是在logstash中转通过 grok方式进行过滤处理,将原始无规则的日志转换为规则日志(Logstash自定义日志格式) ...

随机推荐

  1. Git bash 终止git log 命令

    Git bash中 可以通过键入: q ,结束该命令.

  2. im2rec打包图片

    https://mxnet.incubator.apache.org/faq/finetune.html python ~/mxnet/tools/im2rec.py --list --recursi ...

  3. CVPR 2016 paper reading (2)

    1. Sketch me that shoe, Qian Yu, Feng Liu, Yi-Zhe Song, Tao Xiang, Timothy M. Hospedales, Cheng Chan ...

  4. HDFS Federation(转HDFS Federation(HDFS 联盟)介绍 CSDN)

    转载地址:http://blog.csdn.net/strongerbit/article/details/7013221 HDFS Federation(HDFS 联盟)介绍 1. 当前HDFS架构 ...

  5. linux 内存操作相关命令

    清理内存 echo 1 > /proc/sys/vm/drop_caches 查看内存使用情况 free –m 查看内存条数命令: dmidecode |grep -A16 "Memo ...

  6. JavaScript基础部分经典案例

    再复杂的程序都是由一个个简单的部分组成. 001案例 - 交换两个变量的值 方法01 - 使用临时变量 var n1 = 5; var n2 = 6; // 创建一个临时中介变量 tmp var tm ...

  7. python初学者日记02(正则表达式)

    写作时间:2018/12/17 作者:永远的码农(博客园) 一.正则表达式简介: 正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或R ...

  8. 解决 ajax 跨域

    用两个服务器处理一个项目的代码,其中一台服务器只处理接口请求. 本来PHP可以使用CURL来处理,但是领导不允许使用PHP来处理数据.会影响网站的功能.如果接口端出现问题会导致整个网站或其页面的崩溃, ...

  9. h5跳转到app的实现

    随着业务的增加,可能存在这么一种需求,就是需要从h5中直接跳转到app.如果没有安装app的话,则提示到应用市场或者app store下载安装.不过问题就在这个地方,单纯的用h5是没有方法判断是否安装 ...

  10. angularjs脏机制

    Angular 每一个绑定到UI的数据,就会有一个 $watch 对象. watch = { name:'', //当前的watch 对象 观测的数据名 getNewValue:function($s ...