SpringBoot 连接Hbase实现上传下载文件的功能

Hbase简介

HBase 是Google Bigtable 的开源实现,构建在HDFS之上,适用于实时读写,随机访问超大规模数据集的情形。

到目前为止,存在许多数据存储和访问的方案。事实上,大多数解决方案,特别是关系型数据库,在构建时并没有考虑超大规模和分布式的特点。许多商家通过复制和分区的方法来扩充数据库使其突破单个节点的界限,增加了安装和维护的复杂性,影响了RDBMS的特定功能(例如,联接、复杂的查询、触发器、视图和外键约束)。

HBase从另一个角度来处理伸缩性问题。它通过线性方式从下到上增加节点来进行扩展。HBase是非关系型数据库,巧妙地将大而稀疏的表放在商用的服务器集群上,其特点如下:

  • 大:一个表可以存上亿行几百万列数据。
  • 面向列:面向列表(簇)的存储和权限控制,列(簇)独立检索。
  • 稀疏:对于为空(NULL)的列,并不占用存储空间,因此,表可以设计的非常稀疏。
  • 无模式:每一行都有一个可以排序的主键和任意多的列,列可以根据需要动态增加,同一张表中不同的行可以有截然不同的列。
  • 数据多版本:每个单元中的数据可以有多个版本,默认情况下,版本号自动分配,版本号就是单元格插入时的时间戳。
  • 数据类型单一:HBase中的数据都是字符串,没有类型。

Hbase使用实例

依托一个实际的开发任务介绍SpringBoot连接Hbase的方式

开发任务

将大量小文件存放在Hbase中,方便后期下载、根据文件类型和时间段查询文件

行键设计

行键是Hbase中特别重要的概念,是用来检索记录的主键,其设计原则一般如下:
- 长度:不要超过16个字节。
- 散列:大量连续或者相似的行键容易造成“热点”问题。
- 唯一性: 必须在设计上保证行键的唯一性。行键是按照字典顺序排序存储的,因此,在设计行键的时候,要充分利用这个排序特点,将经常读取或者可能一块被访问的数据存储到一块。
本文考虑后期需求,行键中包含3个元素:文件类型、存放时间和UUID(本文重点在于连接Hbase,Hbase的相关概念不在本文的范围内,不了解的同学可以参考网上的其它资料,这里不做过多阐述)。

代码实现

在给出具体代码之前,贴出最重要的依赖信息:

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.4.6</version>
</dependency>

具体的代码如下:

@RestController
public class FileProcessor {

    //the object is used to configurate Hbase
    private Configuration configurationHbase;

    //this object is a Communicator to hbase
    private Connection connectionHbase;

    //the name of table that save information of files
    private static final String TABLENAME = "file_table" ;

    private static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
            "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
            "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
            "W", "X", "Y", "Z" };

    /**
     * initialization hbase before operating files
     */
    private void initHbase(){
        // initialize configuration and connection
        configurationHbase = HBaseConfiguration.create();
        configurationHbase.set(HConstants.ZOOKEEPER_QUORUM, "10.0.0.52");
        connectionHbase = null;
        try {
            connectionHbase = ConnectionFactory.createConnection(configurationHbase);
        } catch (IOException e) {
            System.out.println("Hbase 初始化错误");
            e.printStackTrace();
        }
    }

    /**
     * @param filename the name of file
     * this method is used to generate a rowkey, which
     * follow the principle: suffix + (@_@) + timestamps + (@_@) +random number
     * @return rowkey
     */
    private String generateRowkey(String filename){
        String rowkey = "";
        //get current time
        String currentTime = String.valueOf(System.currentTimeMillis());
        // get a random number
        StringBuffer shortBuffer = new StringBuffer();
        String uuid = UUID.randomUUID().toString().replace("-", "");
        for (int i = 0; i < 8; i++) {
            String str = uuid.substring(i * 4, i * 4 + 4);
            int x = Integer.parseInt(str, 16);
            shortBuffer.append(chars[x % 0x3E]);
        }

        //get suffix
        String suffix = filename.substring(filename.lastIndexOf(".") + 1);

        // generate a rowkey
        rowkey = suffix + "(@_@)" + currentTime + "(@_@)" + shortBuffer.toString();
        return rowkey;
    }

    @PostMapping(value = "/api/file/upload/smallerfiles")
    @ResponseBody
    public JSONObject uploadSmallerFiles(HttpServletRequest request) {
        List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");

        MultipartFile file = null;


        //get connection Hbase
        initHbase();

        // this map is used to save the names of files and consistent row keys
        List<Map<String,Object>> mapInfo = new ArrayList<>();

        //record the number of failed file
        int failedFile = 0;

        if (files != null && files.size() > 0){
            for (int i = 0; i < files.size(); i++) {
                Map<String,Object> tempMap = new HashMap<>();
                Map<String,Object> fileMap = new HashMap<>();
                fileMap.clear();
                tempMap.clear();

                String fileName = "";
                file = files.get(i);
                fileName = file.getOriginalFilename();
                // get a row key
                String tempRowKey = generateRowkey(fileName);
                try {
                    byte[] fileBytes = file.getBytes();
                    Put put = new Put(Bytes.toBytes(tempRowKey));
                    put.addColumn(Bytes.toBytes("fileInfo"),Bytes.toBytes("file_name"),Bytes.toBytes(fileName));
                    put.addColumn(Bytes.toBytes("fileInfo"),Bytes.toBytes("file_content"),fileBytes);
                    Table table = connectionHbase.getTable(TableName.valueOf(TABLENAME));
                    table.put(put);
                    tempMap.put("code", Integer.valueOf(0));
                    tempMap.put("id",tempRowKey);
                    tempMap.put("message","");
                    fileMap.put("fileName",fileName);
                    fileMap.put("type",ObtainFileType.obtainFileType(fileName));
                    tempMap.put("data",fileMap);
                } catch (IOException e) {
                    failedFile++;
                    tempMap.put("code", Integer.valueOf(-1));
                    tempMap.put("id",tempRowKey);
                    tempMap.put("message","");
                    fileMap.put("fileName",fileName);
                    fileMap.put("type",ObtainFileType.obtainFileType(fileName));
                    tempMap.put("data",fileMap);
                }
                mapInfo.add(tempMap);
            }
        }else {
            return Response.failedResponse("请检查是否上传了文件");
        }
        Map<String,Object> internalMap = new HashMap<>();
        internalMap.put("total",files.size());
        internalMap.put("error",failedFile);
        internalMap.put("data",mapInfo);
        return Response.FileResponse(6,"",internalMap);
    }


    @GetMapping(value = "/api/file/download/smallerfiles/{id}",produces = MediaType.MULTIPART_FORM_DATA_VALUE)
    public JSONObject downloadSmallerFiles(@PathVariable("id")String rowkey, HttpServletResponse res){


        //get connection Hbase
        initHbase();
        //get instance of table
        Table table = null;

        Map<String,Object> tempMap = new HashMap<>();
        tempMap.put("id",rowkey);

        try {
            table = connectionHbase.getTable(TableName.valueOf(TABLENAME));
        } catch (IOException e) {
            System.out.println("获取数据库表的实例时出错");
            e.printStackTrace();
        }
        //get files' bytes
        Get get = new Get(Bytes.toBytes(rowkey));
        Result result = null;
        try {
            result = table.get(get);
        } catch (IOException e) {
            System.out.println("找不到行键对应的文件");
        }
        //get file's name
        String name = Bytes.toString(result.getValue(Bytes.toBytes("fileInfo"),Bytes.toBytes("file_name")));
        tempMap.put("name",name);

        //set header
        res.setContentType("application/force-download");
        res.setHeader("Content-Disposition", "attachment;fileName=" + name);

        //get file's byte code
        byte[] content = result.getValue(Bytes.toBytes("fileInfo"),Bytes.toBytes("file_content"));
        try {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(content);
            OutputStream outputStream = res.getOutputStream();
            byte[] buffer = new byte[1024];
            int count;
            while((count = inputStream.read(buffer)) != -1){
                outputStream.write(buffer,0,count);
            }
            outputStream.flush();
            outputStream.close();
            inputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        }


        Map<String,Object> internalMap = new HashMap<>();
        internalMap.put("data",tempMap);
        return Response.FileResponse(0,"成功",internalMap);
    }

第一篇博客,有不对的地方大家见谅。