公司的一个监控项目需要改版,原因是因为监控源太多、频率也高、数据量超大,以致传统的关系型数据库查询、存储压力很大,需要改用hbase来存储。
之前只用过SSH/SSI这种架构,对phoenix和hbase也只是一知半解,通过多方求助,改版也完成了,在这里记录一下,方便大家。
首先需要phoenix+hbase的环境
我是使用虚拟机安装的,phoenix是4.2.2,hbase是0.98.11,还要求安装hadoop。
启动顺序是:首先启动hadoop,然后是hbase,再是phoenix。
然后在web项目中导入相应的jar包。jar包的位置在你安装的phoenix、hbase里面可以拿到。可以不需要全部都拿过来,主要的jar包如下:
再是配置数据源:
<context:annotation-config /> <context:component-scan base-package="com.coreware.action,com.coreware.service,com.coreware.dao" />
<!-- phoenix jdbc --> <bean id="phoenixJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg ref="phoenixDataSource" /> <qualifier value="phoenixJdbcTemplate"></qualifier> </bean> <bean id="hBaseDao" class="com.coreware.dao.impl.HBaseBaseDAOImpl"> <property name="jdbcTemplate" ref="phoenixJdbcTemplate" /> </bean> <bean id="phoenixDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.apache.phoenix.jdbc.PhoenixDriver" /> <property name="url" value="jdbc:phoenix:hduxin" /> <property name="username" value="" /> <property name="password" value="" /> <property name="initialSize" value="10" /> <property name="maxActive" value="5" /> <!--因为Phoenix进行数据更改时不会自动的commit,必须要添加defaultAutoCommit属性,否则会导致数据无法提交的情况 --> <property name="defaultAutoCommit" value="true" /> </bean>
使用phoenix的好处就是,可以像使用JDBC一样来操作hbase。除了一些比较复杂的查询及多表查询的支持不够外,其他都像使用JDBC一样简单。具体的语法请看:
http://phoenix.apache.org/language/index.html 这个是phoenix官网。
需要注意的就是在hbase里面没有更新的概念,如果id(rowkey)一样,直接会覆盖。所以它的更新及新增语法都是同一个 upsert into table (...) values (...) 。
第四步就是写dao层了
接口HBaseDao:
import java.util.List; import org.springframework.jdbc.core.RowMapper; import com.coreware.model.AgentInfo; import com.coreware.model.ConfigInfo; import com.coreware.model.FlumeInfo; public interface HBaseDao { public List<AgentInfo> queryAgentList(String sql, Object[] params, RowMapper<AgentInfo> mapper); public List<ConfigInfo> queryConfigList(String sql, Object[] params, RowMapper<ConfigInfo> mapper); public void update(String sql, Object[] params); public void batchUpdate(List<String> sqls); public FlumeInfo getFlumeInfo(String querySql, Object[] params, RowMapper<FlumeInfo> mapper); }
package com.coreware.dao.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository; import com.coreware.dao.HBaseDao; import com.coreware.model.AgentInfo; import com.coreware.model.ConfigInfo; import com.coreware.model.FlumeInfo; @Repository public class HBaseBaseDAOImpl implements HBaseDao { private JdbcTemplate jdbcTemplate; public HBaseBaseDAOImpl(JdbcTemplate template) { this.jdbcTemplate = template; } public HBaseBaseDAOImpl() { super(); } @Override public List<AgentInfo> queryAgentList(String querySql, Object[] params, RowMapper<AgentInfo> mapper) { if (params != null && params.length > 0) { return jdbcTemplate.query(querySql, params, mapper); } else { return jdbcTemplate.query(querySql, mapper); } } @Override public List<ConfigInfo> queryConfigList(String querySql, Object[] params, RowMapper<ConfigInfo> mapper) { if (params != null && params.length > 0) { return jdbcTemplate.query(querySql, params, mapper); } else { return jdbcTemplate.query(querySql, mapper); } } @Override public void update(String updateSQL, Object[] params) { // System.out.println("##########UPDATE SQL:" + updateSQL); jdbcTemplate.update(updateSQL, params); } @Override public void batchUpdate(List<String> updateSQL) { for (String sql : updateSQL) { // System.out.println("##########UPDATE SQL:" + sql); jdbcTemplate.update(sql); } } @Override public FlumeInfo getFlumeInfo(String querySql, Object[] params, RowMapper<FlumeInfo> mapper) { if (params != null && params.length > 0) { return jdbcTemplate.queryForObject(querySql, params, mapper); } else { return jdbcTemplate.queryForObject(querySql, mapper); } } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
public List<AgentInfo> getStatusList(Map<String, String> map) { StringBuffer agentSql = new StringBuffer("SELECT DISTINCT hostName,IP,sendEvents,startTime,status,sendFlow,sendFlow2G,createTime FROM biz_monitor_agent tb WHERE createTime in (SELECT MAX(createTime) FROM biz_monitor_agent GROUP BY hostName)"); if (map != null && !map.isEmpty()) { if (map.containsKey("hostName")) { agentSql.append(" AND LOWER(hostName) like '%" + map.get("hostName").toLowerCase() + "%'"); } if (map.containsKey("address")) { agentSql.append(" AND IP like '%" + map.get("address") + "%'"); } if (map.containsKey("startTime") && map.containsKey("endTime")) { agentSql.append(" AND createTime BETWEEN '" + map.get("startTime") + "' AND '" + map.get("endTime") + "'"); } if (map.containsKey("status")) { String status = map.get("status"); if ("0".equals(status)) { agentSql.append(" AND createTime < '" + format() + "'"); } else if ("1".equals(status)) { agentSql.append(" AND createTime > '" + format() + "' AND status='" + status + "'"); } else { agentSql.append(" AND status='" + status + "'"); } } } List<AgentInfo> agents = (List<AgentInfo>) hBaseDao.queryAgentList(agentSql.toString(), null, new RowMapper<AgentInfo>() { @Override public AgentInfo mapRow(ResultSet rs, int rowNum) throws SQLException { AgentInfo agent = new AgentInfo(); agent.setHostName(rs.getString("hostName")); agent.setIP(rs.getString("IP")); agent.setAddress(rs.getString("IP")); agent.setSendEvents(rs.getString("sendEvents")); agent.setStartTime(rs.getString("startTime")); agent.setStatus(rs.getString("status")); agent.setSendFlow(rs.getString("sendFlow")); agent.setSendFlow2G(rs.getString("sendFlow2G")); agent.setCreateTime(rs.getString("createTime")); return agent; } }); return (agents != null && agents.size() > 0) ? agents : null; }
就像很多web项目一样,有很多数据源共存的情况,在这里也是可以的。我的项目使用到了mybatis ,大家看下是如何配置的:
<!-- <context:property-placeholder location="classpath:config/jdbc.properties" /> --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <!-- <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> --> <property name="driverClassName" value="com.ibm.db2.jcc.DB2Driver" /> <property name="url" value="jdbc:db2://PC201306291646:50000/emsdb:currentSchema=EMSUSR;" /> <property name="username" value="db2admin" /> <property name="password" value="db2" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:config/mybatis.xml"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean> <mybatis:scan base-package="com.coreware.dao" />
${jdbc.driverClassName} 是获取不到值的,只能写 com.ibm.db2.jcc.DB2Driver。具体原因还有待研究。
好了,spring+phoenix+hbase实现就介绍到这里!