最近遇到一个需求,有5亿个IPv4,要调用风险查询接口,然后缓存接口返回的风险等级以及风险类型:
参数:
IP:127.0.0.1
返回:
{
"code": 0,
"message": "success",
"data": {
score: 1,
type: [
"70001",
"70002",
"70003"
]
}
}
5亿的数据量其实是不小的,如果要原样存储要耗费的内存非常多,内存费用也会居高不下,那么有什么其他的方法呢?
方案1
既然原样存储占用高的话,那何不压缩呢?说到压缩的话,自然就会想到protobuf,protobuf相比JSON,每条数据能节省40%-60%的内存,这个数据非常可观。
首先我们需要定义proto file文件
syntax = "proto3";
package cachepb;
// 这里写包路径
option go_package = "aaa/bbb";
message Data {
int32 score = 1;
repeated string type = 2;
}
根据需求定义好proto file文件后,使用命令生成go文件
protoc --go_out=. --go-grpc_out=. aaa/bbb/dmp.prot
运行完成之后就会在aaa/bbb下生成一个dmp.pb.go文件了
这时候只需要在代码里将字节Mashal为protobuf就可以了
var data bbb.Data
data.Score = 1
data.Type = []string{"70001", "70002", "70003"}
pbBytes,err := proto.Marshal(&data)
总体内存能节省很多
方案2
由于返回的风险类型都是枚举,那我们可以联想到用bitmask,接口方提供的枚举一共有16个,那我们可以用int32来存储。
// 首先定义好枚举:
const (
Flag1 uint32 = 1 << iota
Flag2
Flag3
Flag4
Flag5
Flag6
Flag7
Flag8
Flag9
Flag10
Flag11
Flag12
Flag13
Flag14
Flag15
Flag16
)
func main() {
var bitmask uint32
for _, t := range arr {
bitmask |= t
}
}
这样一个uint32就可以装下32个枚举了
利用上面的方法,两个数字就可以存储下所有的结果,这个方案相较于之前的protobuf的方案又大大降低了
能不能更进一步呢?score是0-4的整数,用int32存绰绰有余,枚举int32也完全没问题,如果将两个数字组合起来呢?
func Pack(score, bitmask uint32) uint64 {
return uint64(score)<<32 | uint64(bitmask)
}
用这种方式,将score存在高32位,bitmask存在低32位上,一个数字就能完美存下这么多内容了!