首页 文章 精选 留言 我的

精选列表

搜索[最佳实践],共10001篇文章
优秀的个人博客,低调大师

Pgbouncer最佳实践:系列三

作者:王志斌,曾获得中国PostgreSQL数据库管理工程师(PGCE),是PostgreSQL官方认证讲师,盘古云课堂特邀金牌讲师。 PgBouncer具有三种可用的池模式:事务池,会话池和语句池: 事务连接池 数据库客户端很少在不间断的情况下执行连续的事务。而是通常在事务之间执行非数据库工作。这意味着服务器连接在等待新工作到达时会花费大量时间空闲。 事务池模式试图减少服务器连接的空闲时间,如下所示: 池程序在开始事务时将服务器连接分配给客户端。 客户端的事务完成后,池程序将释放连接分配。 注意事项: 如果客户端运行多个事务,则每个事务可以在不同的服务器连接上执行。 单个服务器连接可以在其生命周期内运行由不同客户端发出的事务。 图 6 事务连接池 与服务器所允许的连接相比,允许活动客户端的数量要多得多。尽管取决于给定的工作负载,但经常会看到10倍或更多的活动客户端连接与服务器连接比率。 这确实带来了一个重要的警告:客户端不再期望对数据库会话状态所做的更改在同一客户端进行的连续事务中继续存在,因为这些事务可能在不同的服务器连接上运行。此外,如果客户端进行会话状态更改,它们可能并且很可能会影响其他客户端。 以下是一些使用上面的事务池示例: 如果客户端1在T1中的第一个服务器连接上将会话设置为只读,而客户端2的T3是写事务,则T3将失败,因为它在现在的只读服务器连接上运行。 如果客户端1运行PREPARE a1 AS ...在T1上运行EXECUTE a1 ...,在T2上,则T2将失败,因为预编译语句对于运行T1的服务器连接是本地的。 如果客户端2在T3中创建了一个临时表并尝试在T4中使用它,则T4将失败,因为该临时表对于运行T3的服务器连接是本地的。 有关使用事务池时不支持的会话状态功能和操作的完整列表,请参见PgBouncer的列表 会话连接池 分配给客户端的服务器连接在客户端连接的整个生命周期内持续。这看起来好像根本不使用连接池一样,但是有一个重要的区别:当分配的客户端断开连接时,服务器连接不会被破坏。当客户端断开连接时,池管理器将: 清除客户端所做的任何会话状态更改。 将服务器连接返回到池中,以供其他客户端使用。 图 7 会话连接池 语句连接池 在此,服务器连接分配仅在单个语句的持续时间内持续。这具有与事务池模式相同的会话状态限制,同时还破坏了事务语义。 图 8 语句连接池 这使得所有客户端连接的行为就像在“自动提交”模式下一样。如果客户端尝试开始多语句事务,则合并程序将返回错误。尽管这是 表 3 连接池模式对比 从上述对比情况来看,在连接池的选择上,需要依据业务环境特点来进行选择,默认情况下推荐使用事务连接池,它兼顾了执行事务的特性,尤其多语句的支持,并且不会像会话连接池那样,尝尝处于等待状态。当然事务模式并不支持预编译语句。而根据具体业务场景的特殊需要,有些时候需要客户端与服务器端保持连接,或者支持预编译语句,这样只能选择会话池模式。还有一些特例情况,某些业务场景只是单语句执行,那么语句池模式可能更适合。因此对比这三种模式,可以发现从对客户端操作的支持程度来讲,会话池支持度最高,其次是事务池,最后是语句池模式。但是从支持的连接数来讲,可能刚好是相反的顺序。 表 4 SQL特性对照表 上表为会话连接池和事务连接池的SQL特性对比情况,可以通过对比具体业务场景与SQL特性的符合度,来对连接池模式进行选型。 下面列举了一些示例场景: 有些只运行快速查询,因此在没有事务的情况下可以共享一个会话来处理上百个并发查询。 一些角色成员对于会话级并发是安全的,并且总是使用事务。因此,他们可以安全地共享数百个并发事务的多个会话。 有些角色过于复杂,无法与其他人共享会话。因此,您对它们使用会话池模式可以避免当所有“插槽”都已占用时连接错误。 不要使用它代替HAProxy或其他负载均衡器。尽管pgbouncer具有一些可配置的功能来解决负载均衡器要解决的问题,例如dns_max_ttl,并且可以为其设置DNS配置,但是大多数产品环境都使用HAProxy或其他用于HA的负载均衡器。这是因为HAProxy确实擅长以循环方式在服务器之间实现负载平衡,而不是pgbouncer。尽管pgbouncer对于postgres连接池更好,但最好使用一个小型守护程序来完美地执行一项任务,而不是使用较大的守护程序来完成两项任务,那样效果更糟。 在对于连接数的建议值来讲,上文也给出了一个大致的结果,就是一般情况下设置为CPU核数的3-4倍左右,当然这个不是绝对值,应该是在与业务场景类似的硬件环境中充分进行测试后,才能够得出具体的数值。 还有一点需要注意的是连接Pgbouncer的连接方式,网络连接和unix socket连接方式,较网络连接,unix socket方式可能更加节省网络通信的开销,因此如果pgbouncer和数据库在一台机器部署,可以优选该方式;如果处于不同服务器上,则选择网络连接。 了解更多PostgreSQL热点资讯、新闻动态、精彩活动,请访问中国PostgreSQL官方网站 解决更多PostgreSQL相关知识、技术、工作问题,请访问中国PostgreSQL官方问答社区 下载更多PostgreSQL相关资料、工具、插件问题,请访问中国PostgreSQL官方下载网站

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

Phoenix索引构建最佳实践

用户福利 阿里云发布业界首款云原生多模数据库Lindorm,新用户可享9.9元/3个月优惠,技术交流钉钉群:35977898,更多内容请参考链接 背景 Phoenix的索引构建有两类方法: 同步构建,直接通过sqlline.py create index构建 在云HBase Phoenix 5.x之后,同步构建可以通过轻客户端或重客户端来构建。 异步构建,先create index ... async, 然后通过MR提交build索引job。 因此我们有三种方式构建索引:轻客户端、重客户端、MR异步构建,我们依次介绍下各种方案的优缺点、适用场景和使用方法。 同步构建-轻客户端 适用与数据量比较小,一般构建耗时在10分钟以内。使用方式: 直接使用轻客户端, sqlline-thin.py , create index 即可。 如果数据量较大,我们很可能会遇到索引build超时,我们释放调整Phoenix hbase.rpc.timeout、hbase.client.scanner.timeout.period、phoenix.query.timeoutMs 配置,重启Queryserver生效。 优点: 简单,不占用客户端资源,整个build过程是在服务端完成的。 缺点: 调整参数需要重启queryserver生效,重启过程会导致线上服务临时中断 调整参数会对线上服务造成影响如果有异常SQL导致的大请求会导致服务端负载高,调整了RPC超时时间, 一旦遇到这种请求,无法及时中断,可能对线上业务产生的影响。 无法控制并发,可能因为索引构建打爆服务器 同步构建-重客户端 重客户端适用于中小规模的数据构建,一般索引构建时间在10小时以内。 使用方式: 下载重客户端工具 部署在用户VPC下,机器配置大于等于4c8g;重客户端构建索引的过程中流量会经过这个节点,如果需要更快的索引构建,可以升级节点配置。 调整配置 bin/hbase-site.xml后,使用bin/sqline.py 集群地址 hbase.zookeeper.quorumzk 连接地址,注意使用VPC链接地址。 并发数 phoenix.query.threadPoolSize并发数,越大对目标集群读写压力,可以从1开始逐步增加 超时配置 phoenix.query.keepAliveMs hbase.rpc.timeout hbase.client.scanner.timeout.period phoenix.query.timeoutMs超时时间单位是ms, 可以按需调整。 for ex: <property> <name>hbase.zookeeper.quorum</name> <value>master1-1,master2-1,master3-1:2181</value> </property> <property> <name>phoenix.query.threadPoolSize</name> <value>1</value> </property> <property> <name>phoenix.query.keepAliveMs</name> <value>60000000</value> </property> <property> <name>hbase.rpc.timeout</name> <value>60000000</value> </property> <property> <name>hbase.client.scanner.timeout.period</name> <value>60000000</value> </property> <property> <name>phoenix.query.timeoutMs</name> <value>60000000</value> </property> 优点: 灵活,并发、超时时间可以按需调整,无需重启Phoenix集群,对线上服务影响小 缺点: 需要单独build索引的资源机器 受制于build索引机器的单机性能,扩展性差。 异步构建-MR MR索引构建适用于超大规模数据的情况。 使用方式: 异步索引构建方案 创建异步索引 CREATE INDEX async_index ON my_schema.my_table (v) ASYNC 提交MR JOB build 索引 hadoop --config /mr-phoenix-conf jar \ /mr-phoenix-conf/ali-phoenix-5.2.4.1-HBase-2.x-client.jar \ org.apache.phoenix.mapreduce.index.IndexTool \ --data-table {DATA_TABLE_XXXX} \ --index-table {INDEX_XXX} \ --output-path hdfs://hbase-cluster/ASYNC_INDEX_TMP ps: ali-phoenix-client获取,先下载重客户端包,解压后,取ali-phoenix-xxxx-HBase-2.x-client.jar 索引Build MR环境准备 自建Hadoop或者购买EMR Hadoop集群 在MR环境中创建mr-phoenix-conf目录 配置云HBASE的zk到hbase-site.xml,并此配置文件添加到mr-phoenix-conf目录 云HBase的zk地址可以从控制台获取。 拷贝以下hadoop配置文件到mr-phoenix-conf目录下,包括: core-site.xml、mapred-site.xml、yarn-site.xml、hdfs-site.xml 修改hdfs-site.xml(mr-phoenix-conf目录下) 增加hbase hdfs访问能力。 云HBase HDFS开端口和NN地址获取,找@云HBase答疑协助。 修改dfs.nameservices 增加hbase-cluster 增加hbase-cluster hdfs相关配置 EMR集群打通HBase集群参考配置 <property> <name>dfs.nameservices</name> <value>emr-cluster,hbase-cluster</value> </property> <property> <name>dfs.client.failover.proxy.provider.hbase-cluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <property> <name>dfs.ha.automatic-failover.enabled.hbase-cluster</name> <value>true</value> </property> <property> <name>dfs.ha.namenodes.hbase-cluster</name> <value>nn1,nn2</value> </property> <property> <name>dfs.namenode.rpc-address.hbase-cluster.nn1</name> <value>${nn1-host}:8020</value> </property> <property> <name>dfs.namenode.rpc-address.hbase-cluster.nn2</name> <value>${nn2-host}:8020</value> </property> 优点: 可以对任意规模数据进行索引构建, 扩展性强 缺点: 需要准备单独build资源 相对复杂 总结 适用数据量 优点 缺点 轻客户端 0~10GB 简单,无需额外资源 配置参数调整会影响线上服务 重客户端 10GB-512GB 可以灵活配置并发、超时参数,无需重启Phoenix集群 需要单独的构建索引的机器,受制于单机性能 MR异步构建 512GB~TB级 适用于任意规模索引构建,扩展性强 额外MR资源、MR集群配置复杂 一般少量数据直接用轻客户端来做索引构建,对于中小规模的数据推荐用重客户端来构建索引,而大规模数据则推荐用MR进行索引构建。 参考文档: Phoenix 二级索引:http://phoenix.apache.org/secondary_indexing.html AliPhoenix重客户端下载地址:https://hbase-opt.oss-cn-hangzhou.aliyuncs.com/ali-phoenix-5.2.4.1-HBase-2.x-all.tar.gz

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

Spark最佳实践-项目规范

前言 大数据开发的日常工作中,开发人员经常需要使用 Spark、Flink 等计算引擎作为工具来实现一些 业务逻辑 的计算。 以 Spark 为例,开发人员会使用 SparkSQL、DataFrame、RDD 等不同形式的API来实现业务需求。 通常情况下,简单的需求都可以通过 SparkSQL、DataFrame 很方便的实现,其简洁的API也是其深受数据分析师青睐的原因之一。 但是正是因为 SparkSQL、DataFrame 的高层次封装,在 复杂度较高的计算需求 实现中,可能会出现 实现复杂或者API的功能性无法满足,或者千方百计实现需求之后 性能表现低下,代码段复杂而庞大 的情况。 尽管Spark允许开发人员通过UDF、UDAF等形式提供自定义的函数功能,但是此时很多人会选择使用较为底层的RDD接口进行开发:可控性好、开发与调试方便、性能强劲。 但是使用RDD接口来开发业务需求时,很多小的项目团队并没有一个统一的项目规范,需求开发完全由开发人员个人自己发挥。 各个业务项目的大致流程基本是相同的: 创建SparkSession 用 spark.table or spark.textFile 等API读取数据源 进行RDD的各种 Transformation 和 Action 操作 得到数据结果之后再调用 saveAsTable or saveAsTextFile 写入外部数据源中 虽然看起来流程挺一致的,但是其中仍然存在以下问题: 业务代码混乱 团队成员代码风格不一,有的喜欢一长串一长串的写,有的喜欢将过程封装 即使将过程封装了,但是封装的边界没有明确定义,仍然会有人不小心“越界” 读写数据源的API使用不统一 各个计算引擎对各个数据源都有不同的读写API接口提供使用,一些比较繁杂的API可能会被人“错误”使用 同时也会有人时常忘记对应接口如何使用,反复查阅资料 重复的编码工作 理论上所有业务项目,除了业务逻辑是变化的之外,其余应该都是一个不变的模板 开发人员应该专注于变化的业务逻辑,而不是每次都要分一些精力出来处理其他“边边角角”的事情 没有规范任由团队成员发挥的话,尽管有些成员能写一手漂亮的代码,但是你并不能保证所有人都这么优秀。 时间一久项目中代码的 坏味道 会越来越多,最后混乱的程度可能会超出你的想象。 为了解决以上问题,我们建议:定义一个项目规范,所有业务项目都需要遵守这个规范。 俗话说,有规矩成方圆。 有了项目规范,所有人都遵守这个标准来开发。 有了这个标准,我们就可以在标准化的基础上做很多事情,比如 定义自动化工具来帮助开发人员解放双手。 本文讨论的项目规范可以作为一种参考,以供读者与相关开发人员翻阅。 一、项目规范 和Java项目规范类似,以 模块化项目 的结构来定义项目规范可以为业务项目提供 结构化标准,其可以规整所有 混乱的业务项目结构。 项目结构标准化的重要性: 项目统一管理与生成 方便快速搭框架 所有开发人员遵守相同的编码规范 易于交接与维护 以下模块划分和Java项目类似,略微有些细节差异。 1.1 api模块 业务计算逻辑模块,不应该出现任何 Spark等执行框架的API 以 保持模块独立性与可移植。 理论上该模块可以独立构成一个单机程序执行,这样可以将最重要的业务逻辑根据需要迁移到任意计算引擎中,如 Spark 到 Spark Streaming、Flink 甚至 Hive UDF 等。 对外只提供接口调用,不可直接在外部实例化具体类(工厂模式) 所有service业务逻辑需要有对应的测试用例 事务控制、所有异常捕获和处理 依赖common 1.2 common模块 项目内通用的常量、枚举、POJO实体类、工具函数等,视情况分离,可集成到 context 中 不包含任何业务逻辑 不依赖其他模块 相关工具保持单例 1.3 context模块 Spark或者其他程序 执行入口,负责初始化各种计算引擎的环境变量。 系统 全局配置(conf)与脚本(bin) 集中管理 依赖server、api、common 程序关键点需要打印日志以便后续debug使用 1.4 server模块 整个项目中整合了业务逻辑调用、数据源读写等操作的模块,需求简单的情况下可以直接集成到 context 中。 该模块中根据不同的接口操作类,还划分了 dal、service与manager三个包。 1.4.1 dal包 主要是对数据进行操作,如读写常用的库:Hive、MySQL、HBase;以及读写文件系统:HDFS。 dal中的所有使用都由接口来定义,不同的接口实现使用不同的应用框架API,如Spark、Flink应该为两个独立的dal实现,在后续service使用过程中可以自由切换。 需要遵循以下原则: 所有bean对象,定义在dal 不得在dal写各种业务逻辑、数据清洗逻辑 一张表对应一个dal接口、一个bean,对应多个独立的dao实现 不允许在1个dao中同时操作多个表 包结构如下: basic: basic包下主要放一些基础对象,如BaseDao,所有dao都需要完善 TABLE_NAME bean: 定义数据源表结构,不同的数据源可以定义在不同的包中,如hive、hbase、mysql等 dao: 接口具体实现,用来操作数据表。如:增删改查 1.4.2 service包 和dao对接,一个service对应一个dao,service的使用都由接口来定义。 一个service下有两个实现包: 正常实现包:直接对接dao,简单处理一些判断:如参数不合法校验等。 测试实现包:模拟数据,可以不通过dao获取,从本地文件生成或代码中生成。 不同的计算框架有不同的service实现,如spark、flink等(需要传入其环境变量)。 1.4.3 manager包 调用service包实现数据增删改查 调用api模块进行业务逻辑组合 提供函数接口给context模块调用执行 二、代码框架 基于以上项目模块的划分,我们可以看到,api、common是 每次都会变化的业务逻辑和通用属性的抽取,而 context 是根据业务需要的计算引擎和运行环境设置的 执行入口。 以上三个模块都是 根据业务需求变化比较大的,而server模块则是负责对 其他各个模块的调用与整合,最后通过 manager 提供统一的函数接口给 context 入口调用执行。 所以 server 模块是这个项目规范中可以 自动化 起来的重点目标。 基于这个目标,我们开发了一个 大数据业务开发 基准项目的雏形,开发人员能够做到开箱即用,不必再花太多精力在研究计算引擎与各个数据源的接口和API如何调用,专注于业务逻辑的实现,提升开发效率。 项目地址:https://github.com/chubbyjiang/aisql-bigdata-base 使用介绍 org.aisql.bigdata.base.framework包中提供了几种常见大数据项目需要用到的数据源。 framework 以 模块化项目 的结构提供了 各个数据源基础的Dao、Service接口与默认实现。 配合自动化的代码生成工具,可以一键生成 server 模块的代码文件直接使用。 现在我们来看一下规范+自动化的威力,例如现有 default.t_users 表需要读取。 开发人员仅需要生成代码文件并复制到项目中,写代码如下: val service = new UsersService //读取整个表 val allRdd:RDD[Users] = service.selectAll() //字段筛选 val allRdd:RDD[Users] = service.selectAllWithCols(Seq("name","age")) //条件过滤 val allRdd:RDD[Users] = service.selectAllByWhere("age>10") //读取1000条数据 val demoRdd:RDD[Users] = service.selectDemo() //写入表 service.insertInto(demoRDD) service.createTable(demoRDD) 是不是 so easy? 其实所做的内容也就是在 server 模块中封装了 常用的不同计算引擎对不同数据源的读写操作API,并 自动化了 bean、dal、service 三个部分的代码生成。 使得开发人员可以直接使用 service 提供的数据操作接口 读出数据源 后 调用业务计算逻辑 处理完毕后 写入数据源 中。 通过对项目模块的标准化规范,我们可以以一个 比较统一和简单易懂的开发方式 来进行需求落地。 虽然刚开始使用规范的时候会有人觉得繁琐与不耐烦,如果是手动开发的话谁都会烦,都是一些重复性的苦力活儿,这就是框架规范的缺点:特别繁琐。 但是配套做一些自动化工具来使用的话,相信大部分开发人员都会觉得很酸爽,某种程度上标准化项目就是这么来提升开发效率的。

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

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表就可以将这些记录查出来。 至此,整个数据链路就算搭建完成了。

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

ECS应用管理最佳实践

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

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

jackson-databind最佳实践

给出一个简单的POJO 使用databind,我们需要一个最基础的对象com.fasterxml.jackson.databind.ObjectMapper这里我们构造一个: 注意:这个objectMapper是可以复用的 ObjectMapper 该映射器(或数据绑定器或编解码器)为Java对象之间和匹配的JSON结构的转换提供功能 属性(为序列化过程定义基本的全局设置的配置对象) _serializationConfig _deserializationConfig image.png Inclusion 需要的传参 用于定义Java Bean的哪些属性将被包含在序列化中的枚举 ALWAYS 指示属性始终被包含 独立于值 NON_NULL 该值指示仅包含具有非空值的属性 NON_DEFAULT 只包含没有默认值的属性(意味着当它使用无参数构造函数构造Bean时的值) Map通常无用,因为它们没有默认值,如果使用,则与ALWAYS NON_EMPTY 属性值为null或被认为是空的属性不包括在内 Feature 定义了可引导序列化功能的可触发功能的枚举 WRITE_DATES_AS_TIMESTAMPS(true) 确定Date以及基于日期的东西如Calendar是否要序列化为时间戳 FAIL_ON_EMPTY_BEANS(true) 确定在找到某个类型没有访问者时会发生什么的功能 如果启用(默认),则抛出异常以将它们指示为不可序列化的类型 如果禁用,则它们被序列化为空的对象,即没有任何属性。 简单的把JSON反序列化成Object的用法如下: 简单的把Object序列化成JSON的用法如下: 其实到这一步,对于很多读者来说已经足够了。因为大部分时候我们要的就是这些。但是不妨继续看下去,还有一些你可能会用到的。 集合 如果你使用的不是简单的POJO,而是List,Map: 思考:为什么需要指定类型?(类型擦除) 注意:序列化的时候不需要指定,只有反序列化的时候需要。

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

restful-api最佳实践

Best-practices-for-a-pragmatic-restful-api 先阅读文档:http://www.oschina.net/translate/best-practices-for-a-pragmatic-restful-api 理解restful api设计理念 我们公司的所有微服务接口都放在 "api.998jk.com/微服务名"下 微服务内部api设计规范为:业务域/访问级别, 与best-practices-for-a-pragmatic-restful-api的区别是加入了一级访问级别,例如: 简单列表接口 /chatMessage/pb GET方式,提交参数page,pageSize,total 复杂查询接口 /chatMessage/pb/search POST方式,提交参数为json RequestBody,根据各个接口不同自行定义 查询单条接口 /chatMessage/pb/123 GET方式,123为id 删除单条接口 /chatMessage/pt/123 DELETE方式,123为id 更新单条接口 /chatMessage/pt/123 PUT方式,提交参数为json RequestBody,根据各个接口不同自行定义 上述示例 完整路径为:api.998jk.com/微服务名/chatMessage/xxx 访问级别: pb(public) 公开,对外对内没有任何限制; pt(protected)受保护,对外受保护,对内没有任何限制。需要header中含有authorization 值为"Bearer token令牌"。在api gateway会获取该token, 并且在header中设置uid 值为该令牌的用户id。 df(default) 默认,对外加密,对内没有任何限制。继承protected限制。并且在api gateway会对返回结果加密,客户端需要对结果解密后使用。加密后json如下: {"encrypted":"返回结果加密后的字符串"} pv(private) 私有的,对外无法访问,对内没有任何限制。继承default限制。 暴露的restful服务需采用JAX-RS标准注解,无需springMVC controller 直接暴露service,需要使用swagger注解以便自动产生restful api文档。示例如下 packagecharles.sc.provider.service; importcharles.sc.provider.entity.ChatMessage; importcom.jztey.framework.mvc.Paging; importcom.jztey.framework.mvc.RestfulPagingResult; importcom.jztey.framework.mvc.RestfulResult; importio.swagger.annotations.Api; importio.swagger.annotations.ApiOperation; importio.swagger.annotations.ApiParam; importorg.springframework.cloud.netflix.feign.FeignClient; importjavax.validation.Valid; importjavax.ws.rs.*; importjavax.ws.rs.core.MediaType; /** *CreatedbyCharleson2016/8/15. */ @FeignClient(value="provider-service") @Path("/chatMessage") @Produces(MediaType.APPLICATION_JSON) @Api(tags={"聊天消息接口"}) publicinterfaceChatMessageService{ @Path("/pb") @GET @ApiOperation(value="聊天记录列表",response=RestfulPagingResultChatMessage.class) RestfulPagingResult<ChatMessage>findPage(@QueryParam("id")intpage,@QueryParam("pageSize")intpageSize,@QueryParam("total")inttotal); @Path("/pt/{id:\\d+}") @GET @ApiOperation(value="按id查询聊天记录",response=RestfulResultChatMessage.class) RestfulResult<ChatMessage>find(@PathParam("id")Longid); @Path("/pb/search") @POST @ApiOperation(value="搜索聊天记录",response=RestfulPagingResultChatMessage.class) RestfulPagingResult<ChatMessage>search(Paging<ChatMessage>paging); @Path("/pt") @POST @ApiOperation(value="添加聊天记录",response=RestfulResultChatMessage.class) RestfulResult<ChatMessage>insert(@HeaderParam("uid")Longuid,@ApiParam@ValidChatMessagechatMessage); @Path("/pt/{id:\\d+}") @DELETE @ApiOperation(value="按id删除聊天记录",response=RestfulResultChatMessage.class) RestfulResult<ChatMessage>delete(@HeaderParam("uid")Longuid,@PathParam("id")Longid); @Path("/pt/{id:\\d+}") @PUT @ApiOperation(value="修改聊天记录",response=RestfulResultChatMessage.class) RestfulResult<ChatMessage>update(@HeaderParam("uid")Longuid,@PathParam("id")Longid,@ApiParam@ValidChatMessagechatMessage); classRestfulResultChatMessageextendsRestfulResult<ChatMessage>{ } classRestfulPagingResultChatMessageextendsRestfulPagingResult<ChatMessage>{ } } packagecharles.sc.provider.service; importcharles.sc.provider.entity.ChatMessage; importcom.jztey.framework.mvc.Paging; importcom.jztey.framework.mvc.RestfulPagingResult; importcom.jztey.framework.mvc.RestfulResult; importorg.springframework.stereotype.Service; importjava.util.ArrayList; importjava.util.List; /** *CreatedbyCharleson2016/8/16. */ @com.alibaba.dubbo.config.annotation.Service @Service publicclassChatMessageServiceImplextendsBaseService<ChatMessage>implementsChatMessageService{ @Override publicRestfulPagingResult<ChatMessage>findPage(intpage,intpageSize,inttotal){ System.out.println("get"); //统一使用查询接口 Paging<ChatMessage>paging=newPaging<>(page,pageSize); paging.setTotal(total); returnthis.search(paging); } @Override publicRestfulResult<ChatMessage>find(Longid){ System.out.println("getById"); returnnewRestfulResult(newChatMessage(id,"fu","tu","msg","mi",System.currentTimeMillis(),System.currentTimeMillis(),ChatMessage.STATUS_NO_PROCESS)); } @Override publicRestfulPagingResult<ChatMessage>search(Paging<ChatMessage>paging){ System.out.println("search"); List<ChatMessage>entityList=newArrayList<>(); entityList.add(newChatMessage(1L,"fu","tu","msg","mi",System.currentTimeMillis(),System.currentTimeMillis(),ChatMessage.STATUS_NO_PROCESS)); entityList.add(newChatMessage(2L,"fu2","tu2","msg2","mi2",System.currentTimeMillis(),System.currentTimeMillis(),ChatMessage.STATUS_NO_PROCESS)); if(-1==paging.getTotal()){//total没有传上来 //查询total paging.setTotal(100); } returnnewRestfulPagingResult(entityList,paging.getTotal()); } @Override publicRestfulResult<ChatMessage>insert(Longuid,ChatMessagechatMessage){ System.out.println("insertuid:"+uid); returnnewRestfulResult<>(chatMessage); } @Override publicRestfulResult<ChatMessage>delete(Longuid,Longid){ System.out.println("deleteuid:"+uid); returnnewRestfulResult(newChatMessage(id,"fu","tu","msg","mi",System.currentTimeMillis(),System.currentTimeMillis(),ChatMessage.STATUS_NO_PROCESS)); } @Override publicRestfulResult<ChatMessage>update(Longuid,Longid,ChatMessagechatMessage){ System.out.println("updateuid:"+uid); returnnewRestfulResult<>(chatMessage); } } 完整代码参考:http://gitlab.998jk.com/heying/spring-cloud 本文转自yushiwh 51CTO博客,原文链接:http://blog.51cto.com/yushiwh/1942254,如需转载请自行联系原作者

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

赢得 Docker 挑战最佳实践

难怪Docker正在迅速发展。Docker,一个开源项目。仅仅两年,Docker价值近10亿美元,最近获得了9500万美元的资金。令人激动的是,我们看到有这么多开发者对这个项目的热情。然而,我将在下面讨论企业使用Docker本身是不够的。 现代IT问题 许多企业IT团队解决这两个问题:首先,开发者和运维者在优先级上并不能总是达成一致。企业必须应对的挑战将来自开发人员的代码和运维团队的代码切换。这两个团队之间的关系很难和谐相处。 第二,将代码从一个地方迁移到另一个可以是很困难的。因为你没有简单的方法打包应用程序代码,包括你的系统依赖性。你在不同的操作系统,不同的虚拟机或不同的IaaS上处理代码。 Docker的好处 Docker最激动人心之处就是可以解决企业的这两个问题。第一个问题似乎是确定的,因为开发人员和运维人员之间有着清楚的界限。开发人员考虑Docker容器内部发生的一切,运维人员思考容器外面发生了什么。Docker让这一切变得更加简单和方便,这是一个非常便携式的解决方案。 至于第二个问题,Docker通过使你在单个应用程序进程打包一切与你相关的应用程序。但这只是部分解决了这些问题。 Docker缺少什么 Docker可以形象化的比喻为像可叠起堆放的乐高积木。每个容器是一个乐高。乐高玩具的美丽之处是可以组装的砖块和建立各种各样的奇妙的东西。同样的概念也适用于Docker的容器中。利用Docker,诸如编排、监控、日志记录和扩展可能成为企业关注的问题。Docker容器可以为企业运行几个容器,但如果你运行成百上千的呢?这些都是需要考虑的一些问题,它们超出了Docker容器本身可以提供的范围,为什么PaaS平台是对Docker的补充。 让我们看看容器本身三个特定的缺陷: 1. 装载容器 应用程序开发人员如何让一款应用进入容器?对于开发者来说构建Docker image也有一些负担,谁需要关注代码,不依赖于不同的系统的操作系统。这个问题的解决方案被称为buildpacks——对于PaaS是最好和最便携的选择。大多数PaaS生态系统正在让其标准化。Buildpacks允许你建立你的栈,包括在容器内部的所有系统依赖关系,以及配置应用程序的环境。开发人员只需要考虑他们的应用程序代码。他们不需要担心什么。Buildpacks配置你的应用程序。 2. 编排运输过程 假设开发人员创建大量的Docker的容器。然后他们与运维团队通信:“Ship these. Deploy these to production”。IT运维人员如何传输这些容器并且以系统的方法来管理这些容器性能、安全性和遵从性?容器有很多乐高积木。他们如何管理? 这个问题的答案是Docker Schedulers。如今在市场上有大量的调度器,它们为你编排和运行的容器并且跨集群分发它们——而不用考虑你的云计算集群是什么。调度器是有弹性的,所以如果一个容器或机器或应用程序宕机,它会重新分配这些容器。从用户的角度来看,根本感觉没有停机时间。虽然这些调度器解决一部分运输问题是有帮助的,但是还有另一个重要的问题,企业仍然面临一个调度器不能解决的问题。 3. 开发自助服务 企业文化当中对于自助服务似乎有着天然的缺陷。开发人员和IT运维之间也存在的天然的鸿沟。在某些方面,你可以说他们之间存在着一堵墙。经常发生的是,开发人员将构建一个应用程序,然后把它扔在墙那边给运维人员,并且希望应用能够一切运行正常。因此,将应用程序部署到生产需要数周或数月。所以听到开发者抱怨他们需要多长多长时间在生产环境中部署应用就不难理解了。 这种文化上的差异遭遇到破旧的基础设施时,后果就会更严重,因为一些企业仍使用过时的票务系统获取虚拟机,计算周期可能需要数周时间。 开发人员可以解决这个分歧,但他们需要特殊的工具。他们需要一种自助的方式为企业工作。给开发人员自由的部署在他们的应用,但是这些工具也必须满足安全性和遵企业的需求,包括多租户管理。开发人员可以专注于他或她的应用程序,但是企业需要考虑所有由不同的开发人员提交的应用程序。怎么处理这个?如何打破这堵存在与开发者和运维者之间的墙? PaaS平台也有闪光的地方,它提供了一个介于你的应用程序和基础设施之间的平台。这个平台是一样的,从开发到生产,提供一个无缝应用交付体验。 一个新的开发方法 Docker的承诺是真正伟大的,帮助开发人员解决构建新应用时的重大问题。它将改变应用程序开发过程,但某些挑战必须克服从而使得企业获得最大好处。PaaS平台将促进Docker的发展,并且帮助其履行自己的承诺。 本文作者:张鹏程编译 来源:51CTO

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

混合IT架构的最佳实践

传统IDC跟云服务的关系 传统IDC作为互联网的基础平台为其服务了几十年,可是对IDC的使用从来没有像今天这样使用了互联网思维。云时代,服务商卖给企业的不只是场地,机柜,电。。还有一整套采用互联网思维来使用它们的解决方案,所以通常我们会觉得云服务“哪儿哪儿都要花钱“,所以有实力的公司会搞“私有云“。 混合IT架构的尴尬地位 随着云服务的逐渐完善,如果我要做一个产品,必然会去选择云服务提供商,因此也就不会有混合IT架构这个概念了,因为这个概念通常存在于使用传统 IDC的公司向云服务过度的一个中间状态。也对运维部门是一个极大的考验,因为要在一个新的环境建立产品运行环境,还要保障产品速度,各种转换率不会因此降低。 数据一致性 混合架构有两种方式,一种是备份机房,机主机房出现比较大的故障,可以将流量切至备用机房,一种则是双活,即两个机房同时为用户提供服务。无论是哪种方案,都需要两个机房之间的网络延迟在可以接受的范围内,一般通过专有光纤来解决,云服务商通常会提供这种专有光纤的接入(比如AWS的Direct Connect服务)来打通实体机房与云服务的网络环境,我们可以利用它来做到数据的同步。 有了良好的网络环境,如何实现数据同步同样是一件及其困难的事情,比如数据库,就需要用户写数据的时候往两个机房各写一份,比较普遍的做法是采用消息队列,将用户写的数据先排进队列,然后在开启两个队列对不同机房的数据库进行写操作。 另外是缓存,如果用户访问新的机房,由于新的机房没有缓存,则会出现新的机房数据库被打爆的风险,比较简单的做法是通过在请求包设置cookie用以标记是否是老用户然后负载均衡层判断,含有次cookie的转发至老机房,没有此cookie的新用户则转发至新机房,然后再将老用户按照逐渐递增的方式将流量迁移至新机房,直到新机房缓存完全建立。 做到敏捷 使用云服务的一大优势便是资源到位速度快,一般情况下,我们可认为云的资源时无限大的(如需求特别的,需要跟云服务商单独谈),许多的云服务商(比如AWS)都会给用户提供api接口用于开启资源,并配合cloud-int服务实现资源的初始化,比如我们可以通过脚本的方式调用api快速开启一台 webserver,一台memcache,一台mysql,并使他们处于不同的集群以及不同的监控组中。 尽可能多的使用服务 云服务商提供的服务不仅仅有虚拟服务器,也会提供诸如负载均衡,分布式存储,cache,消息队列等其他的公共服务,将他们恰当的运用到自己的项目中是很有必要的,因为你将在短期内不用担心他们的扩容问题,比如可以使用AWS的ELB作为负载均衡器对外提供服务,使用S3存储静态资源以及log文件 (有个叫s3fuse的项目可以实现将s3作为文件系统挂载到服务器上,可以向访问本地文件一样访问s3上的文件)。 监控一切 在企业架构还处于混合架构的状态中,我们要对产品的性能做两套监控系统,另外一个用来专门监控处在云服务上的产品的性能数据,包括性能指标以及业务指标。我们可以通过在前端分流层对流向IDC以及云服务的流量打上cookie或者标记Etag,然后再log分析的时候便可以出两套数据,用于对比,随时对云服务进行优化或者技术决策,可以采用grafana做成类似如下的图标用来实时观测数据。 本文作者:兰亭集势-段超 来源:51CTO

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

HBase最佳实践-集群规划

HBase自身具有极好的扩展性,也因此,构建扩展集群是它的天生强项之一。在实际线上应用中很多业务都运行在一个集群上,业务之间共享集群硬件、软件资源。那问题来了,一个集群上面到底应该运行哪些业务可以最大程度上利用系统的软硬件资源?另外,对于一个给定业务来说,应该如何规划集群的硬件容量才能使得资源不浪费?最后,一个给定的RegionServer上到底部署多少Region比较合适?想必这些问题都曾经困惑过很多HBaser,那本文将结合前人的分享以及笔者的经验简单的对这三个问题分别进行解析,抛砖引玉,希望大家能够针对这几个话题进行深入的交流! 集群业务规划 一般而言,一个HBase集群上很少只跑一个业务,大多数情况都是多个业务共享集群,实际上就是共享系统软硬件资源。这里通常涉及两大问题,其一是业务之间资源隔离问题,就是将各个业务在逻辑上隔离开来,互相不受影响,这个问题产生于业务共享场景下一旦某一业务一段时间内流量猛增必然会因为过度消耗系统资源而影响其他业务;其二就是共享情况下如何使得系统资源利用率最高,理想情况下当然希望集群中所有软硬件资源都得到最大程度利用。前者本次并不讨论,后期会开’专场’讨论,本节主要就后者进行探讨。 使得集群系统资源最大化利用,那首先要看业务对系统资源的需求情况。经过对线上业务的梳理,通常可将这些业务分为如下几类: 1. 硬盘容量敏感型业务:这类业务对读写延迟以及吞吐量都没有很大的要求,唯一的需要就是硬盘容量。比如大多数离线读写分析业务,上层应用一般每隔一段时间批量写入大量数据,然后读取也是定期批量读取大量数据。特点:离线写、离线读,需求硬盘容量 2. 带宽敏感型业务:这类业务大多数写入吞吐量很大,但对读取吞吐量没有什么要求。比如日志实时存储业务,上层应用通过kafka将海量日志实时传输过来,要求能够实时写入,而读取场景一般是离线分析或者在上次业务遇到异常的时候对日志进行检索。特点:在线写、离线读,需求带宽 3. IO敏感型业务:相比前面两类业务来说,IO敏感型业务一般都是较为核心的业务。这类业务对读写延迟要求较高,尤其对于读取延迟通常在100ms以内,部分业务可能要求更高。比如在线消息存储系统、历史订单系统、实时推荐系统等。特点:在(离)线写、在线读,需求内存、高IOPS介质 (而对于CPU资源,HBase本身就是CPU敏感型系统,主要用于数据块的压缩/解压缩,所有业务都对CPU有共同的需求) 一个集群想要资源利用率最大化,一个思路就是各个业务之间‘扬长避短’,合理搭配,各取所需。实际上就是上述几种类型的业务能够混合分布,建议不要将同一种类型的业务太多分布在同一个集群。因此一个集群理论上资源利用率比较高效的配置为:硬盘敏感型业务 + 带宽敏感型业务 + IO敏感型业务。 另外,集群业务规划的时候除了考虑资源使用率最大化这个问题之外,还需要考虑实际运维的需求。建议将核心业务和非核心业务分布在同一个集群,强烈建议不要将太多核心业务同时分布在同一个集群。这主要有两方面的考虑: 1. 一方面是因为‘一山不容二虎’,核心业务共享资源必然会产生竞争,一旦出现竞争无论哪个业务’落败’都不是我们愿意看到的; 2. 另一方面在特殊场景下方便运维童鞋进行降级处理,比如类似于淘宝双十一这类大促活动,某个核心业务预期会有很大的流量涌入,为了保证核心业务的平稳,在资源共享的情况下只能牺牲其他非核心业务,在和非核心业务方充分交流沟通的基础上限制这些业务的资源使用,在流量极限的时候甚至可以直接停掉这些非核心业务。试想,如果是很多核心业务共享集群的话,哪个核心业务愿意轻易让路? 那有些同学就说了:如果按照你这样设计,那岂不是会产生很多小集群。的确,这种设计会产生很多小集群,相信如果没有资源隔离的话,小集群是没法避免的。有些使用’rsgroup’进行业务资源隔离的集群会做的很大,大集群通过隔离会将业务独立分布到很多独立的RS上,这样实际上就产生了很多逻辑上的小集群,那么,这些小集群同样适用上面提出的规划思路。 集群容量规划 每个季度公司都会要求采购新机器,一般情况下机器的规格(硬盘总容量、内存大小、CPU规格)都是固定的。假如现在一台RegionServer的硬盘规格是3.6T * 12,总内存大小为128G,从理论上来说这样的配置是否会有资源浪费?如果有的话是硬盘浪费还是内存浪费?那合理的硬盘/内存搭配应该是什么样?和哪些影响因素有关? 这里需要提出一个’Disk / Java Heap Ratio’的概念,意思是说一台RegionServer上1bytes的Java内存大小需要搭配多大的硬盘大小最合理。在给出合理的解释在前,先把结果给出来: Disk Size / Java Heap = RegionSize / MemstoreSize * ReplicationFactor * HeapFractionForMemstore * 2 按照默认配置,RegionSize = 10G,对应参数为hbase.hregion.max.filesize;MemstoreSize = 128M,对应参数为hbase.hregion.memstore.flush.size;ReplicationFactor = 3,对应参数为dfs.replication;HeapFractionForMemstore = 0.4,对应参数为hbase.regionserver.global.memstore.lowerLimit; 计算为:10G / 128M * 3 * 0.4 * 2 = 192,意思是说RegionServer上1bytes的Java内存大小需要搭配192bytes的硬盘大小最合理,再回到之前给出的问题,128G的内存总大小,拿出96G作为Java内存用于RegionServer,那对应需要搭配96G * 192 = 18T硬盘容量,而实际采购机器配置的是36T,说明在默认配置条件下会有几乎一半硬盘被浪费。 计算公式是如何’冒’出来的? 再回过头来看看那个计算公式是怎么’冒’出来的,其实很简单,只需要从硬盘容量纬度和Java Heap纬度两方面计算Region个数,再令两者相等就可以推导出来,如下: 硬盘容量纬度下Region个数:Disk Size / (RegionSize *ReplicationFactor) Java Heap纬度下Region个数:Java Heap * HeapFractionForMemstore / (MemstoreSize / 2 ) Disk Size / (RegionSize *ReplicationFactor) =Java Heap * HeapFractionForMemstore / (MemstoreSize / 2 ) =>Disk Size / Java Heap = RegionSize / MemstoreSize * ReplicationFactor * HeapFractionForMemstore * 2 这样的公式有什么具体意义? 1. 最直观的意义就是判断在当前给定配置下是否会有资源浪费,内存资源和硬盘资源是否匹配。 2. 那反过来,如果已经给定了硬件资源,比如硬件采购部已经采购了当前机器内存128G,分配给Java Heap为96G,而硬盘是40T,很显然两者是不匹配的,那能不能通过修改HBase配置来使得两者匹配?当然可以,可以通过增大RegionSize或者减少MemstoreSize来实现,比如将默认的RegionSize由10G增大到20G,此时Disk Size / Java Heap = 384,96G * 384 = 36T,基本就可以使得硬盘和内存达到匹配。 3. 另外,如果给定配置下内存硬盘不匹配,那实际场景下内存’浪费’好呢还是硬盘’浪费’好?答案是内存’浪费’好,比如采购的机器Java Heap可以分配到126G,而总硬盘容量只有18T,默认配置下必然是Java Heap有浪费,但是可以通过修改HBase配置将多余的内存资源分配给HBase读缓存BlockCache,这样就可以保证Java Heap并没有实际浪费。 另外,还有这些资源需要注意… 带宽资源:因为HBase在大量scan以及高吞吐量写入的时候特别耗费网络带宽资源,强烈建议HBase集群部署在万兆交换机机房,单台机器最好也是万兆网卡+bond。如果特殊情况交换机是千兆网卡,一定要保证所有的RegionServer机器部署在同一个交换机下,跨交换机会导致写入延迟很大,严重影响业务写入性能。 CPU资源:HBase是一个CPU敏感型业务,无论数据写入读取,都会因为大量的压缩解压操作,特别耗费计算资源。因此对于HBase来说,CPU越多越好。 参考: http://hadoop-hbase.blogspot.com/2013/01/hbase-region-server-memory-sizing.html Region规划 Region规划主要涉及到两个方面:Region个数规划以及单Region大小规划,这两个方面并不独立,而是相互关联的,大Region对应的Region个数少,小Region对应的Region个数多。Region规划相信是很多HBase运维同学比较关心的问题,一个给定规格的RegionServer上运行多少Region比较合适,在刚开始接触HBase的时候,这个问题也一直困扰着笔者。在实际应用中,Region太多或者太少都有一定的利弊: 优点 缺点 大量小Region 1.更加有利于集群之间负载分布 2.有利于高效平稳的Compaction,这是因为小Region中HFile相对较小,Compaction代价小,详情可见:Stripe Compaction 1. 最直接的影响:在某台RegionServer异常宕机或者重启的情况下大量小Region重分配以及迁移是一个很耗时的操作,一般一个Region迁移需要1.5s~2.5s左右,Region个数越多,迁移时间越长。直接导致failover时间很长。 2. 大量小Region有可能会产生更加频繁的flush,产生很多小文件,进而引起不必要的Compaction。特殊场景下,一旦Region数超过一个阈值,将会导致整个RegionServer级别的flush,严重阻塞用户读写。 3. RegionServer管理维护开销很大 少量大Region 1. 有利于RegionServer的快速重启以及宕机恢复 2. 可以减少总的RCP数量 3. 有利于产生更少的、更大的flush 1. Compaction效果很差,会引起较大的数据写入抖动,稳定性较差 2. 不利于集群之间负载均衡 可以看出来,在HBase当前工作模式下,Region太多或者太少都不是一件太好的事情,在实际线上环境需要选择一个折中点。官方文档给出的一个推荐范围在20~200之间,而单个Region大小控制在10G~30G,比较符合实际情况。 然而,HBase并不能直接配置一台RegionServer上的Region数,Region数最直接取决于RegionSize的大小配置hbase.hregion.max.filesize,HBase认为,一旦某个Region的大小大于配置值,就会进行分裂。 hbase.hregion.max.filesize默认为10G,如果一台RegionServer预期运行100个Region,那单台RegionServer上数据量预估值就为:10G * 100 * 3 = 3T。反过来想,如果一台RegionServer上想存储12T数据量,那按照单Region为10G计算,就会分裂出400个Region,很显然不合理。此时就需要调整参数hbase.hregion.max.filesize,将此值适度调大,调整为20G或者30G。而实际上当下单台物理机所能配置的硬盘越来越大,比如36T已经很普遍,如果想把所有容量都用来存储数据,依然假设一台RegionServer上分布100个Region,那么每个Region的大小将会达到可怕的120G,一旦执行Compaction将会是一个灾难。 可见,对于当下的HBase,如果想让HBase工作的更加平稳(Region个数控制在20~200之间,单Region大小控制在10G~30G之间),最多可以存储的数据量差不多为200 * 30G * 3= 18T。如果存储的数据量超过18T,必然会引起或多或少的性能问题。所以说,从Region规模这个角度讲,当前单台RegionServer能够合理利用起来的硬盘容量上限基本为18T。 然而随着硬件成本的不断下降,单台RegionServer可以轻松配置40T+的硬盘容量,如果按照上述说法,越来越多的硬盘其实只是’镜中月,水中花’。社区也意识到了这样的问题,在当前Region的概念下提出了Sub-Region的概念,可以简单理解为将当前的Region切分为很多逻辑上小的Sub-Region。Region还是以前的Region,只是所有之前以Region为单位进行的Compaction将会以更小的Sub-Region粒度执行。这样,单Region就可以配置的很大,比如50G、100G,此时单台RegionServer上也就可以存储更多的数据。个人认为Sub-Region功能将会是HBase开发的一个重点。 总结 本文结合HBase相关理论知识以及笔者的实际经验,对HBase集群规划中最常见的三个问题 - 业务规划、容量规划以及Region规划做了简单的解析,希望给大家一些启发和思考。线上集群规划是一个经验积累的过程,相信每个HBase运维同学或多或少都会碰到一些坑,也肯定会有自己的思考和见解,希望大家能够更多的在评论区或者邮件交流,谢谢! 本文转载自:http://hbasefly.com 原文链接

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

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

Spring

Spring

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

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册