IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> rediscli二进制数据可视化 -> 正文阅读

[大数据]rediscli二进制数据可视化

一、背景

在做高并发的一些项目中,为了快速响应 大量使用了 redis 做缓存数据。因为 redis 使用内存存储数据,导致成本较高,因此我们项目中大量将 protobuf 的二进制数据存储到 redis 中。这种做法降低了存储成本,但也遇到了一些问题:

  • 数据的可读性差,使用 redis-cli 读取数据时,不如 json 等格式化数据清晰;
  • 造数据麻烦,如果使用 json 等格式化数据,直接写好 json 后使用 redis-cli 写入即可;使用 protobuf 后造数据需要写代码来完成。

二、效果

protobuf 文件 test.proto:

  • 在 pb 目录下生成 pb 文件:protoc --go_out=./ ./test.proto
syntax = "proto3";

// 生成路径设置, 命令:protoc --go_out=./ ./test.proto
option go_package ="./;pb";

message TestInfo {
    int32 type = 1;
    repeated TestMessage message = 2;
}

message TestMessage {
    string time = 1;
}

json 数据文件 test.json:

{
	"type": 1,
	"message": [{
		"time": "1651939200"
	}]
}

使用效果:

在这里插入图片描述

三、技术方案

目的是可以将 redis-cli 读取的数据格式化为可读数据;造数据时根据 json 文件,转换为 redis-cli 数据,直接写入。

问题:redis-cli 显示的数据是经过 redis 处理的,需要将 数据和原始二进制数据 进行转换;

  • redis-cli 代码中的 sdscatrepr 函数实现了二进制到字符串的转换,参照相关代码即可。

问题:造数据时,需要将 json 数据转换为 protobuf 结构数据;

  • go 语言的 protobuf 包的 jsonpb 可以实现上述功能。

问题:不同 protobuf 文件,如何区分;

  • 利用 go 反射机制,根据 protobuf 中 message 名称区分不同的 protobuf 结构;依赖 go 程序提前编译所有 *.pb.go 文件,目前是全部放在同一个包中,运行前加载。

四、代码实现

目录结构

[root@b8bd4b93c6cb redis2pb]# tree
.
|-- go.mod
|-- go.sum
|-- pb
|   |-- test.pb.go
|   `-- test.proto
|-- redis2pb.go
`-- test.json

1 directory, 6 files
1、客户端代码

redis2pb.go 文件:

package main

import (
	"flag"
	"fmt"
	"github.com/liudong1994/goutil/redisconvert"
	"os"
	_ "gotest/pb"	// 编译所有pb(调用init), 支持后面根据pb name获取对应pb message
)

func main() {
	var pbmessage string
	flag.StringVar(&pbmessage , "pbm", "", "protobuf message name")

	var rediscliData string
	flag.StringVar(&rediscliData , "rcd", "", "redis-cli string data, WARN:use single quotes parameters")

	var jsonfile string
	flag.StringVar(&jsonfile , "json", "", "json file name")

	flag.Parse()

	if len(pbmessage) != 0 && len(rediscliData) != 0 {
		// 输入pb文件, redis-cli字符串数据, 自动解析pb 打印debugstring
		fmt.Println("redis-cli string CONVERT json")
		pbData, _ := redisconvert.Rediscli2pb2json(pbmessage, rediscliData)
		fmt.Println(pbData)

	} else if len(pbmessage) != 0 && len(jsonfile) != 0 {
		// 输入pb文件, json文件, 自动转换为redis-cli字符串数据, 直接set即可
		fmt.Println("json CONVERT pb CONVERT redis-cli string")
		jsonData, _ := readFile(jsonfile)
		rediscliData, _ := redisconvert.Json2pb2rediscli(pbmessage, jsonData)
		fmt.Println(rediscliData)

	} else {
		flag.Usage()
		fmt.Printf("CONVERT json to pb(redis-cli string):  go run redis2pb.go -pbm 'TestInfo' -json 'test.json'\n")
		fmt.Printf("CONVERT pb(redis-cli string) to debug string:  go run redis2pb.go -pbm 'TestInfo' -rcd '\\b\\x01\\x12\\x0c\\n\\n1651939200'\n")
	}

	return
}

func readFile(filename string) (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		fmt.Println(err)
		return "", err
	}
	defer file.Close()

	fileInfo, err := file.Stat()
	if err != nil {
		fmt.Println(err)
		return "", err
	}

	filesize := fileInfo.Size()
	buffer := make([]byte, filesize)
	if _, err = file.Read(buffer); err != nil {
		fmt.Println(err)
		return "", err
	}

	return string(buffer), nil
}

go.mod 文件:

module gotest

go 1.16

require (
	github.com/liudong1994/goutil v1.0.6
	google.golang.org/protobuf v1.28.0
)
2、依赖代码

redis-cli 数据和二进制数据转换代码:

// github.com/liudong1994/goutil/rediscli

import (
	"encoding/hex"
	"errors"
	"fmt"
)

func Binary2string(inData []byte) (outData string) {
	outData += "\""

	for _, b := range inData {
		switch b {
		case '\\', '"':
			// \ -> \\
			// " -> \"
			outData += "\\"
			outData += string(b)
		case '\n':
			outData += "\\n"
		case '\r':
			outData += "\\r"
		case '\t':
			outData += "\\t"
		case '\a':
			outData += "\\a"
		case '\b':
			outData += "\\b"
		default:
			if 0x20 <= b && b <= 0x7E {
				// 可打印
				outData += string(b)
			} else {
				// 不可打印字符, 打印它的16进制
				outData += "\\x"
				outData += fmt.Sprintf("%02x", b)
			}
		}
	}

	outData += "\""
	return outData
}

func String2binary(inData string) (outData []byte, err error) {
	// 去掉开头结尾的"
	if len(inData) >= 2 && inData[0] == '"' && inData[len(inData)-1] == '"' {
		inData = inData[1:len(inData)-1]
	}

	for i:=0; i<len(inData); i++ {
		switch inData[i] {
		case '\\':
			i++
			if i >= len(inData) {
				fmt.Println("ERROR len data: ", inData)
				return outData, errors.New("error len")
			}

			switch inData[i] {
			case '\\':
				outData = append(outData, '\\')
			case '"':
				outData = append(outData, '"')
			case 'n':
				outData = append(outData, '\n')
			case 'r':
				outData = append(outData, '\r')
			case 't':
				outData = append(outData, '\t')
			case 'a':
				outData = append(outData, '\a')
			case 'b':
				outData = append(outData, '\b')
			case 'x':
				hexString := inData[i+1:i+3]
				i += 2
				hexByte, _ := hex.DecodeString(hexString)
				// fmt.Println("DEBUG hex string: ", hexString, ", hex byte: ", hexByte)
				outData = append(outData, hexByte...)
			}
		default:
			outData = append(outData, inData[i])
		}
	}

	return outData, nil
}

redis-cli中 protobuf 数据转换json,json数据转换 protobuf 的 redis-cli数据代码:

// github.com/liudong1994/goutil/redisconvert

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang/protobuf/jsonpb"
	"github.com/golang/protobuf/proto"
	"github.com/liudong1994/goutil/rediscli"
	"google.golang.org/protobuf/reflect/protoreflect"
	"google.golang.org/protobuf/reflect/protoregistry"
)

func Rediscli2pb2json(pbMsgName string, redisData string) (string, error) {
	// redisdata转换pb数据 打印
	redisBinaryData, _ := rediscli.String2binary(redisData)

	pbMsg, err := genPBMessageByName(pbMsgName)
	if err != nil {
		fmt.Printf("rediscli2pb gen pb message by name:%s err:%s\n", pbMsgName, err)
		return "", errors.New("gen pb message by name err")
	}

	if err := proto.Unmarshal(redisBinaryData, pbMsg); err != nil {
		fmt.Printf("rediscli2pb proto unmarshal err:%s\n", err)
		return "", errors.New("proto unmarshal err")
	}
	// fmt.Printf("rediscli2pb data:\n%s", proto.MarshalTextString(msg))

	pb2json := jsonpb.Marshaler{}
	jsonStr, _ := pb2json.MarshalToString(pbMsg)

	var jsonDebug bytes.Buffer
	if err = json.Indent(&jsonDebug, []byte(jsonStr), "", "    "); err != nil {
		fmt.Printf("rediscli2pb json indent err:%s\n", err)
		return "", errors.New("json indent err")
	}

	return jsonDebug.String(), nil
}

func Json2pb2rediscli(pbMsgName string, jsonData string) (string, error) {
	// 找到pb message
	pbMsg, err := genPBMessageByName(pbMsgName)
	if err != nil {
		fmt.Printf("json2pb2rediscli gen protobuf message err:%s\n", err)
		return "", errors.New("gen protobuf message err")
	}

	// json转pb
	if err := jsonpb.UnmarshalString(jsonData, pbMsg); err != nil {
		fmt.Printf("json2pb2rediscli json 2 protobuf err: %s\n", err)
		return "", errors.New("json 2 protobuf err")
	}

	// pb转rediscli字符串
	pbData, err := proto.Marshal(pbMsg)
	if err != nil {
		fmt.Printf("json2pb2rediscli proto marshal err:%s\n", err)
		return "", errors.New("proto marshal err")
	}

	output := rediscli.Binary2string(pbData)
	return output, nil
}

func genPBMessageByName(fullName string) (proto.Message, error) {
	msgName := protoreflect.FullName(fullName)
	msgType, err := protoregistry.GlobalTypes.FindMessageByName(msgName)
	if err != nil {
		return nil, err
	}

	return proto.MessageV1(msgType.New()), nil
}
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-05-10 11:58:25  更:2022-05-10 11:59:45 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:47:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码