首页 文章 精选 留言 我的

精选列表

搜索[鸿蒙实践],共10000篇文章
优秀的个人博客,低调大师

VPGAME 的 Kubernetes 迁移实践

作者 | 伍冲斌 VPGAME 运维开发工程师 导读:VPGAME 是集赛事运营、媒体资讯、大数据分析、玩家社群、游戏周边等为一体的综合电竞服务平台。总部位于中国杭州,在上海和美国西雅图分别设立了电竞大数据研发中心和 AI 研发中心。本文将讲述 VPGAME 将服务器迁移至 Kubernetes 的过程。 背景 随着容器技术的日趋成熟,公司近期计划将服务迁移至容器环境,通过 Kubernetes 对容器进行调度、编排和管理。并借此机会,对服务进行标准化,优化整个 CI/CD 的流程,提高服务部署的效率。 CI/CD 工具的选择 CI/CD 工具上,我们选择了 GitLab-CI。GitLab-CI 就是一套配合 GitLab 使用的持续集成系统,以完成代码提交之后的安装依赖、编译、单元测试、lint、镜像构建以及发布等工作。 GitLab-

优秀的个人博客,低调大师

Flink UDF自动注册实践

5万人关注的大数据成神之路,不来了解一下吗?5万人关注的大数据成神之路,真的不来了解一下吗?5万人关注的大数据成神之路,确定真的不来了解一下吗? 欢迎您关注《大数据成神之路》 1.注册UDF函数1.1 注册相关方法此处,我们使用的udf函数为标量函数,它继承的是ScalarFunction,该类在我们的使用中,发现它继承自UserDefinedFunction这个类,该处的udf函数由用户自己定义,而函数的注册此处我们自己实现; 函数注册时,使用flink的tableEnv上下文对象注册该函数,此处注册时使用的方法是TableEnvironment类里面的重载方法registerFunction,这个函数不涉及参数和泛型的问题,具体方法如下: * Registers a [[ScalarFunction]] under a unique name. Replaces already existing * user-defined functions under this name. */ def registerFunction(name: String, function: ScalarFunction): Unit = { // check if class could be instantiated checkForInstantiation(function.getClass) // register in Table API functionCatalog.registerFunction(name, function.getClass) // register in SQL API functionCatalog.registerSqlFunction( createScalarSqlFunction(name, name, function, typeFactory) ) } 通过上面得方法,发现在检查完类的实例化之后,便是对该类进行注册使用,分别针对Table API和SQL API两种不同形式去进行注册。 下面是我们注册的小案例: 日常模式 tableEnv.registerFunction("hashCode",new HashCode()) myTable.select("item,item.hashCode(),hashCode(item)") val hcTest = tableEnv.sqlQuery("select item,hashCode(item) from myTable") 假日模式 tableEnv.registerFunction(m("name").toString, ReflectHelper.newInstanceByClsName[ScalarFunction] (m("className").toString,this.getClass.getClassLoader)) 1.2 函数示例 class HashCode extends ScalarFunction {var hashcode_factor = 12 override def open(context: FunctionContext): Unit = {// access "hashcode_factor" parameter// "12" would be the default value if parameter does not exist hashcode_factor = context.getJobParameter("hashcode_factor", "12").toInt } def eval(s: String): Int = { s.hashCode()+hashcode_factor }} 2.注册UDTF函数2.1 注册相关方法在UDTF和UDAF中,我们发现,注册使用的具体函数是包含有一定的格式限制,比如此时我们需要注册的UDTF函数,Split类继承自TableFunction[(String,Int)],那么我们的函数注册中,在java程序编译时会去检查该泛型,后续实际运行时,解析我们的UDTF函数时,对泛型内的类型进行序列化和反序列化时会和我们规定的泛型进行对比,如果此时我们的数据schema或者说我们的数据本身格式不匹配抑或是我们给出了数据的泛型,编译过了擦除掉之后,在实际运行中却发现并没有该字段信息,那么同样也会出错,所以此时,我们更加要去注意产生该问题的根源,那么根源究竟是什么呢,话不多说,接着看代码。 我们需要注册函数的registerFunction方法,来自于StreamTableEnvironment中的registerFunction方法,此处的类请大家和之前区别一下,注意,此处这个类在后续我们使用UDAF时也会使用,那么原因在于这两个函数加入了泛型的约束,所以兜兜转转,会有中间的一个检查判断过程,接着,同样是在TableEnvironment这个类中的registerTableFunctionInternal方法,下来,我会分别给出两个方法,请看代码。 StreamTableEnvironment /** * Registers a [[TableFunction]] under a unique name in the TableEnvironment's catalog. * Registered functions can be referenced in SQL queries. * * @param name The name under which the function is registered. * @param tf The TableFunction to register */ def registerFunction[T: TypeInformation](name: String, tf: TableFunction[T]): Unit = { registerTableFunctionInternal(name, tf) } TableEnvironment /** * Registers a [[TableFunction]] under a unique name. Replaces already existing * user-defined functions under this name. */ private[flink] def registerTableFunctionInternal[T: TypeInformation]( name: String, function: TableFunction[T]): Unit = { // check if class not Scala object checkNotSingleton(function.getClass) // check if class could be instantiated checkForInstantiation(function.getClass) val typeInfo: TypeInformation[_] = if (function.getResultType != null) { function.getResultType } else { implicitly[TypeInformation[T]] } // register in Table API functionCatalog.registerFunction(name, function.getClass) // register in SQL API val sqlFunction = createTableSqlFunction(name, name, function, typeInfo, typeFactory) functionCatalog.registerSqlFunction(sqlFunction) } 看到了吧,这个T类型规范了我们注册的这个函数的类型,这个在自定义注册时一定要小心;注意我们返回类型是否和我们注册时规定的泛型一致,要让注册能过编译,也要让函数能顺利运行。 2.2 函数示例 class Split(separator: String) extends TableFunction[(String, Int)] { def eval(str: String): Unit = { str.split(separator).foreach(x => collect(x, x.length)) }} 这个里面的返回即是(String, Int),因为我们注册时,已经获取了该类的泛型,所以此时,只需要我们在注册前引入隐式转换即可。 2.3 注册部分 //register table schema: [a: String] tableEnv.registerDataStream("mySplit", textFiles,'a) val mySplit: Table = tableEnv.sqlQuery("select * from mySplit") mySplit.printSchema() //register udtf val split = new Split(",") val dslTable =mySplit.join(split('a) as ('word,'length)).select('a,'word,'length) val dslLeftTable = mySplit.leftOuterJoin(split('a) as ('word,'length)).select('a,'word,'length) tableEnv.registerFunction("split",split) val sqlJoin = tableEnv.sqlQuery("select a,item,counts from mySplit,LATERAL TABLE(split(a)) as T(item,counts)") val sqlLeftJoin =tableEnv.sqlQuery("select a, item, counts from mySplit LEFT JOIN LATERAL TABLE(split(a)) as T(item, counts) ON TRUE") 3.注册UDAF函数3.1 注册函数看了上面两种,其实无非是,UDF函数直接注册就可以,UDTF在注册时需要我们规范下类的泛型,而UDAF则不止是这些,不过,take it easy放轻松,趟过的坑马上列出来给你看,哈哈,这里提前说,多了一个返回的类,而此处这个类你们就可要小心啦~~~呜啦啦,开始吧,骚年。。。 此时,我们的具体思路是,要先给出一个类,比如有几个成员变量作为后续AggregateFunction的一个辅助类,然后UDAF函数中用到了它还有它其中的成员变量,下来,改变下思路,先看注册的函数吧: WeightedAvgAccum import java.lang.{Long => JLong, Integer => JInteger} import org.apache.flink.api.java.tuple.{Tuple2 => JTuple2} class WeightedAvgAccum extends JTuple2[JLong, JInteger] { var sum = 0L var count =0 } WeightedAvg /** * Weighted Average user-defined aggregate function. */class WeightedAvg extends AggregateFunction[JLong, CountAccumulator] {override def createAccumulator(): WeightedAvgAccum = { new WeightedAvgAccum } override def getValue(acc: WeightedAvgAccum): JLong = {if (acc.count == 0) { null } else { acc.sum / acc.count } } def accumulate(acc: WeightedAvgAccum, iValue: JLong, iWeight: JInteger): Unit = { acc.sum += iValue * iWeight acc.count += iWeight } def retract(acc: WeightedAvgAccum, iValue: JLong, iWeight: JInteger): Unit = { acc.sum -= iValue * iWeight acc.count -= iWeight } def merge(acc: WeightedAvgAccum, it: java.lang.Iterable[WeightedAvgAccum]): Unit = { val iter = it.iterator()while (iter.hasNext) { val a = iter.next() acc.count += a.count acc.sum += a.sum } } def resetAccumulator(acc: WeightedAvgAccum): Unit = { acc.count = 0 acc.sum = 0L }override def getAccumulatorType: TypeInformation[WeightedAvgAccum] = { new TupleTypeInfo(classOf[WeightedAvgAccum], Types.LONG, Types.INT) }override def getResultType: TypeInformation[JLong] = Types.LONG} 3.2 注册方法及问题详解 大家伙受累看完这两个类,没什么问题的话我们接着往下讲,官网例子,如下包换,在我们使用flink注册时,没什么问题啊,那么你凭什么说要注意呢?此处我们的前提是用户上传到我们的系统,我们通过反射来拿到该类的实例然后再去注册,那么,问题就来了,如果平时使用没有任何问题,而我们自动让flink识别注册时,flink却做不到,原因为何,请先看看,平时使用和我们自动注册时的一些区别; 日常玩法: tableEnv.registerFunction("wAvg",new WeightedAvg()) val weightAvgTable = tableEnv.sqlQuery("select item,wAvg(points,counts) AS avgPoints FROM myTable GROUP BY item") 假日玩法: implicit val infoTypes = TypeInformation.of(classOf[Object]) tableEnv.registerFunction[Object,Object](m("name").toString, ReflectHelper.newInstanceByClsName(m("className").toString,this.getClass.getClassLoader,sm.dac)) 亲爱的们,看到问题了吗?其实原因就是我们的程序不是你,它无法推断具体的类的类型,这个需要是我们给出一定的范围或者说我们规范这个流程,即便是这样,引入了对object的隐式转换,过得了编译,但是运行时还会报错,不信,你看: 2019-07-10 19:49:40,872 INFO org.apache.flink.runtime.executiongraph.ExecutionGraph - groupBy: (item), window: (TumblingGroupWindow('w$, 'proctime, 5000.millis)), select: (item, udafWeightAvg(counts, points) AS c) -> select: (c, item) -> job_1542187919994 -> Sink: Print to Std. Out (2/2) (3a9098c7ddb0a115349f6d89aba606ff) switched from RUNNING to FAILED. org.apache.flink.types.NullFieldException: Field 0 is null, but expected to hold a value. at org.apache.flink.api.java.typeutils.runtime.TupleSerializer.serialize(TupleSerializer.java:127) at org.apache.flink.api.java.typeutils.runtime.TupleSerializer.serialize(TupleSerializer.java:30) at org.apache.flink.api.java.typeutils.runtime.RowSerializer.serialize(RowSerializer.java:160) at org.apache.flink.api.java.typeutils.runtime.RowSerializer.serialize(RowSerializer.java:46) at org.apache.flink.contrib.streaming.state.AbstractRocksDBState.getValueBytes(AbstractRocksDBState.java:171) at org.apache.flink.contrib.streaming.state.AbstractRocksDBAppendingState.updateInternal (AbstractRocksDBAppendingState.java:80) at org.apache.flink.contrib.streaming.state.RocksDBAggregatingState.add(RocksDBAggregatingState.java:105) at org.apache.flink.streaming.runtime.operators.windowing.WindowOperator.processElement(WindowOperator.java:391) at org.apache.flink.streaming.runtime.io.StreamInputProcessor.processInput(StreamInputProcessor.java:202) at org.apache.flink.streaming.runtime.tasks.OneInputStreamTask.run(OneInputStreamTask.java:105) at org.apache.flink.streaming.runtime.tasks.StreamTask.invoke(StreamTask.java:300) at org.apache.flink.runtime.taskmanager.Task.run(Task.java:711) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.NullPointerException at org.apache.flink.api.common.typeutils.base.LongSerializer.serialize(LongSerializer.java:63) at org.apache.flink.api.common.typeutils.base.LongSerializer.serialize(LongSerializer.java:27) at org.apache.flink.api.java.typeutils.runtime.TupleSerializer.serialize(TupleSerializer.java:125) ... 12 more 2019-07-10 19:49:40,873 INFO org.apache.flink.runtime.executiongraph.ExecutionGraph - Job csv_test csv_test_udaf_acc (ec1e56648123905f7ffd85ba884e89ca) switched from state RUNNING to FAILING. org.apache.flink.types.NullFieldException: Field 0 is null, but expected to hold a value. 报错很显然,flink大群里大佬翻译告诉我说这是tuple里面第一位的数据为空,序列化long为空导致,而我傻傻的找了大半天,发现我的程序没问题啊,那这到底问题出在哪呢?其实有时候,并不是我们的错,只是我们太高看了别人,经过苦苦寻找,才找到原来官网的例子中有猫腻。下来,同样的,看一下StreamTableEnvironment和TableEnvironment这两个类中的注册方法。 StreamTableEnvironment /** * Registers an [[AggregateFunction]] under a unique name in the TableEnvironment's catalog. * Registered functions can be referenced in Table API and SQL queries. * * @param name The name under which the function is registered. * @param f The AggregateFunction to register. * @tparam T The type of the output value. * @tparam ACC The type of aggregate accumulator. */ def registerFunction[T: TypeInformation, ACC: TypeInformation]( name: String, f: AggregateFunction[T, ACC]) : Unit = { registerAggregateFunctionInternal[T, ACC](name, f) } TableEnvironment /** * Registers an [[AggregateFunction]] under a unique name. Replaces already existing * user-defined functions under this name. */ private[flink] def registerAggregateFunctionInternal[T: TypeInformation, ACC: TypeInformation]( name: String, function: AggregateFunction[T, ACC]): Unit = { // check if class not Scala object checkNotSingleton(function.getClass) // check if class could be instantiated checkForInstantiation(function.getClass) val resultTypeInfo: TypeInformation[_] = getResultTypeOfAggregateFunction( function, implicitly[TypeInformation[T]]) val accTypeInfo: TypeInformation[_] = getAccumulatorTypeOfAggregateFunction( function, implicitly[TypeInformation[ACC]]) // register in Table API functionCatalog.registerFunction(name, function.getClass) // register in SQL API val sqlFunctions = createAggregateSqlFunction( name, name, function, resultTypeInfo, accTypeInfo, typeFactory) functionCatalog.registerSqlFunction(sqlFunctions) } 具体呢如下: 一是,我们此处规范的是一个[Object,Object]的泛型,对于Accum类里的Jlong,JInteger没法起到限定,进而在解析时无法找到对应的类型,这个反应在TableEnvironment里面的T和ACC,T对应上了,而ACC却没有; 二是,我们的Avg类里面,返回的却是一个 new TupleTypeInfo(classOf[WeightedAvgAccum], Types.LONG, Types.INT)和Types.LONG,那么不难发现,这个tuple里三个元素,我们其实只需要把第一个解析了,而另外两个都是套在它里面的,所以Object只有一个,而WeightedAvgAccum里却有三个,完全不对应,所以我们需要更改这两个类,改完后具体代码如下: WeightedAvgAccum class WeightedAvgAccum { var sum = 1L var count = 2 } WeightedAvg import org.apache.flink.api.common.typeinfo.TypeInformation import org.apache.flink.api.java.typeutils.TupleTypeInfo import org.apache.flink.table.api.Types import org.apache.flink.table.functions.{AggregateFunction, UserDefinedFunction, utils} import java.lang.{Integer => JInteger, Long => JLong} import org.apache.flink.table.functions.utils.UserDefinedFunctionUtils class WeightedAvg extends AggregateFunction[JLong, WeightedAvgAccum]{ override def createAccumulator(): WeightedAvgAccum = { new WeightedAvgAccum } override def getValue(acc: WeightedAvgAccum): JLong = { if (acc.count == 0) { null } else { acc.sum / acc.count } } def accumulate(acc: WeightedAvgAccum, iValue: JLong, iWeight: JInteger): Unit = { acc.sum += iValue * iWeight acc.count += iWeight } def retract(acc: WeightedAvgAccum, iValue: JLong, iWeight: JInteger): Unit = { acc.sum -= iValue * iWeight acc.count -= iWeight } def merge(acc: WeightedAvgAccum, it: java.lang.Iterable[WeightedAvgAccum]): Unit = { val iter = it.iterator() while (iter.hasNext) { val a = iter.next() acc.count += a.count acc.sum += a.sum } } def resetAccumulator(acc: WeightedAvgAccum): Unit = { acc.count = 0 acc.sum = 0L } override def getAccumulatorType: TypeInformation[WeightedAvgAccum] = TypeInformation.of(classOf[WeightedAvgAccum]) override def getResultType: TypeInformation[JLong] = Types.LONG } 那么具体运行如何呢,具体如下: 4.问题总结巴拉巴拉说了这么多,可能对于大神来说并不是什么新鲜问题,但是我相信初次接触的小白来讲还是或多或少有一些帮助的,所以希望后续在码代码的过程中要多方面去思考,也希望还是要加强对底层知识的深度认识,也能够更快的触类旁通,好啦,今天就到这啦,后面持续为大家带来我自己的一些见解和认知,撒由那拉~~~

优秀的个人博客,低调大师

ADB日志分析最佳实践

背景 利用服务器日志做分析是很多公司进入大数据分析的第一步,也是很关键的一步。大部分情况下,这些公司在考虑进行大数据分析的时候,都会遇到以下问题: 团队里面缺乏了解大数据技术栈的工程师 都听过Hadoop,想要学习Hadoop,但是不知道从何入手 从市面上寻找大数据人才效果不理想 不愿意一下子投入过多的资金去组建一个专门的大数据团队 虽然Hadoop没有办法一下子搭起来,但是其实在刚开始进入大数据的时候完全可以用MPP数据库来快速满足需求。但是你可能会有疑问,MPP能够代替Hadoop吗?要回答这个问题,首先要理解Hadoop的出现到底解决了什么问题: 传统的单节点关系型数据库,要提升性能,只能通过scale up的方式,即增加cpu/内存/硬盘。到后面提升5%的计算能力可能是前面10倍的成本投入。Hadoop利用分布式的思想,通过shared-nothing的架构,实现了scale out的能力。在这样的架构下面,加入同样性能的机器,可以达到线性提升处理性能的效果,投入产出成正比。 关系型数据库对于非/半结构化数据不是特别友好,主要表现在关系型数据库是以行列为结构存储数据的,无法直接把json,xml这类型的数据插入进去。Hadoop的优势是可以通过编写特定的input reader,来解释这些数据格式,从而达到处理非结构数据的能力。但是不能忘记的一点是,为了提升处理性能,即使使用Hadoop,最后也是要将数据结构化的。 通过上面的描述,其实思路就很清晰了。如果可以有一个shared-nothing架构的关系型数据库,将要导入的数据预先进行结构化处理,那么我们还是可以在关系型数据库(MPP)里面做大数据分析。显然ADB就是这么一个场景下最合适的选择。 组件介绍 这一章介绍一下本方案使用到的各种组件 名称 介绍 Nginx 反向代理,一般被用来做负载均衡。作为网络流量的总入口,会沉淀所有的用户行为。利用这里的日志作为分析可以得到最全面的数据视图。 Logstash 日志采集工具,可以对采集到的数据进行一定的预处理,通过配置文件进行格式化改造。 DataHub 数据管道,提供对数据的发布和订阅。支持直接导出到一些常用的数据存储包括MaxCompute,OSS,ADB等。 ADB for MySQL 高并发低延时的PB级别数据仓库,MPP架构,完全兼容MySQL协议。 搭建步骤 安装Nginx 在CentOS下面,安装Nginx yum install nginx 成功安装的话,会看到如下内容 已安装: nginx.x86_64 1:1.12.2-3.el7 作为依赖被安装: nginx-all-modules.noarch 1:1.12.2-3.el7 nginx-mod-http-geoip.x86_64 1:1.12.2-3.el7 nginx-mod-http-image-filter.x86_64 1:1.12.2-3.el7 nginx-mod-http-perl.x86_64 1:1.12.2-3.el7 nginx-mod-http-xslt-filter.x86_64 1:1.12.2-3.el7 nginx-mod-mail.x86_64 1:1.12.2-3.el7 nginx-mod-stream.x86_64 1:1.12.2-3.el7 完毕! 完成安装之后,需要定义Nginx打印日志的格式。在这个例子中,因为我们想要统计网站访问的UV和每个请求的响应时长,所以我们需要把 $request_time 和 $http_cookie 添加进去。另外,Nginx日期的默认打印方式是 07/Jul/2019:03:02:59, 对于我们后面做分析时候查询不是十分友好,所以要把它们统一改成 2019-07-11T16:32:09 的格式。 找到/etc/nginx/nginx.conf,用vi打开,将log_format更改如下: log_format main '$remote_addr - [$time_iso8601] "$request" ' '$status $body_bytes_sent $request_time "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" "$http_cookie"' ; 执行下面命令,重启Nginx使配置生效: service nginx restart 这时去看一下日志(位于/var/log/nginx/access.log),会发现日志打印格式如下 119.35.6.17 - [2019-07-14T16:39:17+08:00] "GET / HTTP/1.1" 304 0 0.000 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36" "-" "-" 由于默认的Nginx主页不会记录cookie,建议后面挂一个带登陆的系统来做验证 部署DataHub DataHub的控制台地址 https://datahub.console.aliyun.com/datahub 首先要创建一个Project,取名为log_demo: 进入到这个log_demo 项目之后,要创建一个Topic, 我们可以取名topic_nginx_log : Topic的类型选择Tuple,这是一种带schema的组织形式,更方便我们查看和后面的处理。Schema的定义跟我们要从日志中取得的字段是强相关的,所以建立的时候需要确保没有错误,否则可能导致后面日志写入的时候出错。 字段名称 意义 类型 对应Nginx日志的字段 remote_ip 请求的来源IP STRING $remote_addr date 请求发生的日期 BIGINT $time_iso8601 的日期部分 time 请求发生的时间,24小时制,精确到秒 STRING $time_iso8601 的时间部分 method Http请求的方法 STRING $request 的请求动作部分,例如GET request 请求的uri和param部分 STRING $request的uri和param部分 http_version Http版本号 STRING $request的http版本号部分 status 请求的返回状态码 STRING $status bytes 请求体的大小 BIGINT $body_bytes_sent request_time 请求的耗时 DOUBLE $request_time referer 发出这个请求之前,用户在哪个页面 STRING $http_referer agent 客户端使用的操作系统和浏览器 STRING $http_user_agent xforward 请求经过的代理 STRING $http_x_forwarded_for cookie 用户的标识 STRING $http_cookie 成功创建之后,可以在这个Topic的Schema里面看到如下图展示 安装Logstash 官方的Logstash没有提供DataHub的兼容工具,所以建议到DataHub的官网上面去下载最新的兼容版本,减少中间融合操作成本。具体的介绍页面在这里。 回到控制台,输入下面命令,下载和解压Logstash到你想要的目录 ##注意,这里的版本号和下载链接可能因为更新缘故有区别,建议到介绍页面去获取最新链接 wget http://aliyun-datahub.oss-cn-hangzhou.aliyuncs.com/tools/logstash-with-datahub-6.4.0.tar.gz?spm=a2c4g.11186623.2.17.60f73452tHbnZQ&file=logstash-with-datahub-6.4.0.tar.gz tar -zxvf logstash-with-datahub-6.4.0.tar.gz 安装完成之后需要配置Logstash,这里有两个工作需要完成。第一,由于我们需要将日志处理成结构化的数据,所以在抓取的过程中,还需要做一点加工。Logstash的grok可以帮我们轻松地完成这个任务。第二,我们需要告诉Logstash从哪里(Nginx的日志存放位置)抓起日志文件,最后要同步到哪里(DataHub)去。 配置grok 我们需要让grok能够理解日志文件并进行转换,需要往grok-pattern文件里面添加一个新格式。这个文件存放在刚刚下载的Logstash目录里面,具体路径如下 : logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-xxx/patterns/grok-patterns 用vi打开这个文件,把下面的新pattern填到文件最末端即可 DATE_CHS %{YEAR}\-%{MONTHNUM}\-%{MONTHDAY} NGINXACCESS %{IP:remote_ip} \- \[%{DATE_CHS:date}T%{TIME:time}\+08:00\] "%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status} %{NUMBER:bytes} %{NUMBER:request_time} %{QS:referer} %{QS:agent} %{QS:xforward} %{QS:cookie} 配置Logstash 在Logstash的目录里面,有一个config文件夹,里面有一个logstash-sample.conf文件。打开它,把它改成 input { file { path => "/var/log/nginx/access.log" #nginx的日志位置,默认在这里。 start_position => "beginning" #从什么位置开始读起日志文件,beginning表示从最开始。 } } filter { grok { match => {"message" => "%{NGINXACCESS}"} #当grok获取到一个消息时,怎么去转换格式 } mutate { gsub => [ "date", "-", ""] #因为日期在后面要用作ADB表的二级分区,所以需要把非数字字符去掉 } } output { datahub { access_id => "<access-key>" #从RAM用户获取access key access_key => "<access-secrect>" #从RAM用户获取access secret endpoint => "http://dh-cn-shenzhen-int-vpc.aliyuncs.com" #DataHub的endpoint,取决于把project建立在哪个区域 project_name => "log_demo" #刚刚在DataHub上面创建的项目名称 topic_name => "topic_nginx_log" #刚刚在DataHub上面创建的Topic名称 dirty_data_continue => true #脏数据是否继续运行 dirty_data_file => "/root/dirty_file" #脏数据文件名称,脏数据会被写入到这里 dirty_data_file_max_size => 1000 #脏数据的文件大小 } } 完成这个配置之后,我们可以启动Logstash来验证是否有日志被写入到DataHub里面了。在Logstash的根目录里面,执行如下命令 bin/logstash -f config/logstash-sample.conf 看到这个如下日志代表启动成功 ending Logstash logs to /root/logstash-with-datahub-6.4.0/logs which is now configured via log4j2.properties [2019-07-12T13:36:49,946][WARN ][logstash.config.source.multilocal] Ignoring the 'pipelines.yml' file because modules or command line options are specified [2019-07-12T13:36:51,000][INFO ][logstash.runner ] Starting Logstash {"logstash.version"=>"6.4.0"} [2019-07-12T13:36:54,756][INFO ][logstash.pipeline ] Starting pipeline {:pipeline_id=>"main", "pipeline.workers"=>2, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>50} [2019-07-12T13:36:55,556][INFO ][logstash.outputs.datahub ] Init datahub success! [2019-07-12T13:36:56,663][INFO ][logstash.inputs.file ] No sincedb_path set, generating one based on the "path" setting {:sincedb_path=>"/root/logstash-with-datahub-6.4.0/data/plugins/inputs/file/.sincedb_d883144359d3b4f516b37dba51fab2a2", :path=>["/var/log/nginx/access.log"]} [2019-07-12T13:36:56,753][INFO ][logstash.pipeline ] Pipeline started successfully {:pipeline_id=>"main", :thread=>"#<Thread:0x445e23fb run>"} [2019-07-12T13:36:56,909][INFO ][logstash.agent ] Pipelines running {:count=>1, :running_pipelines=>[:main], :non_running_pipelines=>[]} [2019-07-12T13:36:56,969][INFO ][filewatch.observingtail ] START, creating Discoverer, Watch with file and sincedb collections [2019-07-12T13:36:57,650][INFO ][logstash.agent ] Successfully started Logstash API endpoint {:port=>9600} 这时候可以开始去刷一下Nginx代理的页面了。默认Nginx自带一个静态的网页,可以通过访问80端口打开。产生日志的话,Logstash的日志会马上打印如下信息 [2019-07-12T13:36:58,740][INFO ][logstash.outputs.datahub ] [2010]Put data to datahub success, total 10 然后在DataHub上,可以看到抽样数据如下 配置ADB 先到ADB的控制台去创建一个新的集群,数据库的名字取nginx_logging。需要注意的是,当ADB创建好之后,在控制台那边看不到数据库的名字,转而变成了集群名称。目前看来称谓不一样,但是其实是一回事。 新的集群初始化会需要一点时间。待初始化完成之后,可以通过控制台的登陆数据库按钮跳转到DMS(数据管理)去创建用于存放日志的数据库。在创建数据库之前,需要先理解ADB的几个核心概念,这样设计出来的表性能才会更佳。 一级分区 要理解一级分区,需要先理解MPP数据库是怎么提升性能的。本文的背景中说到,MPP是可以线性扩展的,但是有一个前提,数据要均匀分布。这样在一个Query过来的时候,才能让尽量多的磁盘被利用起来。在ADB中,数据是否均匀分布,主要取决选择哪个列作为一级分区。千万不要选择数据倾斜的列作为分区列,例如日期,性别,或者存在大量空值的列。在本例中,比较适合的应该是remote_ip这一列。 二级分区 二级分区是在一级分区的基础上做二次切分,可以理解为隐性地帮用户把一个表切成了多个表,从而提升全表查询的性能。此外,二级分区还有一个优点,就是可以设置分区个数。在超过分区个数的时候,ADB会自动将历史最久的一个二级分区删除,实现历史数据自动清楚的效果。在本例中,date这个列特别时候做二级分区。假如我们想保留30天的日志数据,我们只需要将二级分区设置成30个即可。注意,二级分区的列一定要是整数类型,例如bigint或者long。 下面是创建表nginx的SQL CREATE TABLE nginx_logging.nginx( remote_ip varchar NOT NULL COMMENT '', date bigint NOT NULL COMMENT '', time varchar NOT NULL COMMENT '', method varchar NOT NULL COMMENT '', request varchar COMMENT '', http_version varchar COMMENT '', status varchar COMMENT '', bytes bigint COMMENT '', request_time double COMMENT '', referer varchar COMMENT '', agent varchar COMMENT '', xforward varchar COMMENT '', cookie varchar COMMENT '', PRIMARY KEY (remote_ip,date,time) ) PARTITION BY HASH KEY (remote_ip) PARTITION NUM 128 -- remote_ip作为一级分区 SUBPARTITION BY LIST KEY (date) -- date作为二级分区 SUBPARTITION OPTIONS (available_partition_num = 30) -- 二级分区30个,代表保留30天的数据 TABLEGROUP logs OPTIONS (UPDATETYPE='realtime') -- 由于要被DataHub更新,所以一定要选择是realtime的表 COMMENT '''' 成功配置创建表之后,ADB这边的准备任务就算完成了。需要提示一下,默认ADB的网络连接信息只有公有网络和经典网络的连接。专有网络的连接地址需要通过配置打开。因为ADB完全兼容MySQL协议,所以我们要从本地登陆的话,可以利用公有网络的地址。而DataHub访问ADB,目前使用的是经典网络的连接。 配置DataConnector 回到刚刚创建的Topic, topic_nginx_log, 下面。右上角有一个按钮叫+DataConnector。这里需要输入几个关于ADB的信息: Host : #ADB集群经典网络的连接地址 Port : #ADB集群经典网络的端口 Database : nginx_logging #之前建立ADB集群时候输入的名字,同时也是这个ADB集群的名字 Table : nginx #上面建立的表名 Username : <access-key> #RAM用户的access key Password : <access-secret> #RAM用户的access secret 模式 : replace into #如果主键冲突,会覆盖掉记录 成功配置之后,在DataConnectors下面就可以看到相关的信息了。如果之前DataHub上面已经有数据,现在查询ADB里面的nginx表就可以将这些记录查出来。 至此,整个数据链路就算搭建完成了。

优秀的个人博客,低调大师

Knative 实践:人脸图片识别

标准 Serverless 框架和人脸识别服务结合会产生怎样的火花?本文介绍如何通过 Knative 实现人脸识别服务,看看能否给你带来不一样的体验。 场景 通过 OSS 控制台上传照片,MnsOss 事件源接收图片上传的事件信息,发送到 Knatvie Eventing,通过Broker/Trigger事件处理模型之后,接着触发 Knative Serving 中的人脸识别服务进行分析。最后把分析之后的图片回传到 OSS。 准备 安装 Knative Serving 和 Eventing, 参见部署Knative 安装 Knative MnsOssSource 事件源服务容器服务控制台->市场->应用目录,选择 ack-knative-mnsoss-sources 安装 在 OSS 控制台创建 Bucket,参见创建存储空间这里

优秀的个人博客,低调大师

TalkingData的Spark On Kubernetes实践

众所周知,Spark是一个快速、通用的大规模数据处理平台,和Hadoop的MapReduce计算框架类似。但是相对于MapReduce,Spark凭借其可伸缩、基于内存计算等特点,以及可以直接读写Hadoop上任何格式数据的优势,使批处理更加高效,并有更低的延迟。实际上,Spark已经成为轻量级大数据快速处理的统一平台。Spark作为一个数据计算平台和框架,更多的是关注Spark Application的管理,而底层实际的资源调度和管理更多的是依靠外部平台的支持: Spark官方支持四种Cluster Manager:Spark standalone cluster manager、Mesos、YARN和Kubernetes。由于我们TalkingData是使用Kubernetes作为资源的调度和管理平台,所以Spark On Ku

优秀的个人博客,低调大师

Android Handler原理实践解析

前言 Handler消息处理机制在Android开发中起着举足轻重的作用,我们有必要好好理解下其原理,下面我们先从一个简单的例子出发 一、日常使用 假设我们有这么一个需要,请求网络然后将图片展示出来,我们知道网络请求是不允许在主线程执行的,而UI是不能在子线程(具体是不允许在非创建UI的原始线程)更新的,因此我们需要在子线程请求网络获得了数据以后再切换回主线程更新UI,这个例子中Handler就是起着切换线程的作用,下面的代码演示了这个例子 classMainActivity : AppCompatActivity() { private lateinit var mImageView: ImageView override fun onCreate(savedInstanceState: Bundle?){

优秀的个人博客,低调大师

ECS应用管理最佳实践

前言 即使在CloudNative发展如火如荼的当下,ECS应用(直接将应用部署在ECS上,不使用容器)仍然占了相当大的比重,原因主要在于相对容器化应用,ECS应用由于不需要容器的运行时环境和类似K8S的调度层软件,因此存在一些天然优势,比如: 更低的Overhead,可以更充分发挥硬件的处理能力,适合用于工作负载比较高的组件或应用 更高的单元可靠性,结合高可用方案,可以实现很好的整体可用性 更好的安全性,因为隔离在虚拟化层面 更易用的运维界面,对运维的技能要求更低 对于既存系统,无改造成本 当然,对比于容器化的应用,ECS应用的劣势也很明显,因为缺少统一的部署标准和调度系统,需要用户通过脚本或配管工具才能实现自动化管理,而这些附加手段由于本身缺乏标准,如果用户没有良好的运维能力,其本身的功能完整性和可靠性都有可能成为制约业务发展的不利因素。 拿

优秀的个人博客,低调大师

SparkSQL在有赞的实践

前言 有赞数据平台从2017年上半年开始,逐步使用 SparkSQL 替代 Hive 执行离线任务,目前 SparkSQL 每天的运行作业数量5000个,占离线作业数目的55%,消耗的 cpu 资源占集群总资源的50%左右。本文介绍由 SparkSQL 替换 Hive 过程中碰到的问题以及处理经验和优化建议,包括以下方面的内容: 有赞数据平台的整体架构。 SparkSQL 在有赞的技术演进。 从 Hive 到 SparkSQL 的迁移之路。 一. 有赞数据平台介绍 首先介绍一下有赞大数据平台总体架构: 如下图所示,底层是数据导入部分,其中 DataY 区别于开源届的全量导入导出工具 alibaba/DataX,是有赞内部研发的离线 Mysql 增量导入 Hive 的工具,把 Hive 中历史数据和当天增量部分做合并。DataX / DataY

优秀的个人博客,低调大师

Mars 算法实践——人脸识别

源于MaxCompute,阿里首款自研科学计算引擎Mars1月16日开源发布https://promotion.aliyun.com/ntms/act/cloud/marsfbh.html Mars 是一个基于矩阵的统一分布式计算框架,在之前的文章中已经介绍了 Mars 是什么, 以及 Mars 分布式执行 ,而且 Mars 已经在 GitHub 中开源。当你看完 Mars 的介绍可能会问它能做什么,这几乎取决于你想做什么,因为 Mars 作为底层运算库,实现了 numpy 70% 的常用接口。这篇文章将会介绍如何使用 Mars 完成你想做的事情。 奇异值分解 (SVD) 在处理纷繁的数据时,作为数据处理者,首先想到的就是降维,SVD 就是其中一种比较常见的降维方法,在 numpy.linalg 模块中就有 svd 方法,当我们有200

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册