Posted in

如何用最小的内存存储枚举

最近遇到一个需求,有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位上,一个数字就能完美存下这么多内容了!