redis 使用经验总结

redis 使用经验总结

    ①、定时更新天气网数据文件解析并存储到redis

    ②、通过接口访问形式提供给高德


1、定时更新天气网数据文件解析并存储到redis

  • 下载GridForecast、GridHourly、GridNow、RainMinute四类数据文件   同时在服务器本地备份
date = TqwUtil.formatDate(TqwUtil.parse(date, dateFormat), dateFormat);
//设置当前时间
RedisUtil.set("{" + TqwConstant.GRID_NOW_TIME + "}", date);
 
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + lonlatString + "}", sb.toString());

//todo 以后删除,3*3 造出 1*1 的数据, 用中心点填充周围
BigDecimal lonBigdecimal = new BigDecimal(lon);
BigDecimal latBigdecimal = new BigDecimal(lat);
BigDecimal lonIncr       = lonBigdecimal.add(new BigDecimal("0.01"));
BigDecimal latIncr       = latBigdecimal.add(new BigDecimal("0.01"));
BigDecimal lonDecr       = lonBigdecimal.subtract(new BigDecimal("0.01"));
BigDecimal latDecr       = latBigdecimal.subtract(new BigDecimal("0.01"));

map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lon, latIncr.toString()) + "}", sb.toString());
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lon, latDecr.toString()) + "}", sb.toString());
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lonIncr.toString(), lat) + "}", sb.toString());
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lonDecr.toString(), lat) + "}", sb.toString());
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lonIncr.toString(), latIncr.toString()) + "}", sb.toString());
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lonIncr.toString(), latDecr.toString()) + "}", sb.toString());
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lonDecr.toString(), latIncr.toString()) + "}", sb.toString());
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + TqwUtil.formatLonLatString(lonDecr.toString(), latDecr.toString()) + "}", sb.toString());

if ( map.size() >= DataUtil.object2Int(Constant.PROPERTIES.get("pipeline.count")) ) {
    RedisUtil.setByPipeline(map);
    map.clear();
}
通过Redis String数据类型存储,每次存储覆盖上次数据,四类数据均采用此数据类型。尝试使用Hash,但是由于Hash内部存储数据大小有限制,存储信息超过一定大小,内存溢出,不能继续存储,虽然机器内容仍然够用。
  • 将备份文件存储在缓存中,方便备份请求获取数据  空间换时间的概念
public File getGridNowFile(String[] arr) {
    logger.info(" grid now start back up !");
    String filename = TqwConstant.BASE_PATH + File.separator + RegexUtil.getRandomString(10) + ".txt";
    //打包文件 数组
    try {
        String         dataFormat   = "yyyyMMddHHmm";
        BufferedWriter bw           = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filename)));
        String         gridNowStart = RedisUtil.get("{" + TqwConstant.GRID_NOW_TIME + "}");
        DateTime       dateTime     = new DateTime(TqwUtil.parse(gridNowStart, dataFormat));
        bw.write(dateTime.toString("yyyyMMddHH") + "00");
        bw.newLine();
        List<String> keys = new ArrayList<>();
        for ( int i = 0; i < arr.length; i++ ) {
            String[] lonlat    = arr[i].split(",");
            String   lonlatStr = TqwUtil.formatLonLatString(lonlat[0], lonlat[1]);
            keys.add("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + lonlatStr + "}");
            if ( keys.size() >= 10000 ) {
                List<String> values = RedisUtil.getByPipeline(keys, String.class);
                for ( int j = 0; j < values.size(); j++ ) {
                    String value = values.get(j);
                    if ( TqwUtil.isEmpty(value) ) {
                        bw.write("######################################");
                    } else {
                        bw.write(value);
                    }
                    bw.newLine();
                }
                keys.clear();
            }
        }
        if ( keys.size() > 0 ) {
            List<String> values = RedisUtil.getByPipeline(keys, String.class);
            for ( int j = 0; j < values.size(); j++ ) {
                String value = values.get(j);
                if ( TqwUtil.isEmpty(value) ) {
                    bw.write("######################################");
                } else {
                    bw.write(value);
                }
                bw.newLine();
            }
        }
        IOUtils.closeQuietly(bw);
    } catch ( Exception e ) {
        logger.error(" 生成格点实况数据异常 ", e);
        FileUtils.deleteQuietly(new File(filename));
        return null;
    }
    logger.info(" grid now end back up ");
    String zipFilename = TqwConstant.BASE_PATH + File.separator + RegexUtil.getRandomString(10) + ".zip";
    try {
        ZipUtil.zip(zipFilename, "", filename);
        logger.info(" grid now end package ");
    } catch ( Exception e ) {
        logger.error("Grid Now zip error!", e);
    } finally {
        FileUtils.deleteQuietly(new File(filename));
    }
    return new File(zipFilename);
}
File zipFile = this.backFileGenerator.getGridNowFile(array);
try {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    FileUtils.copyFile(zipFile, byteArrayOutputStream);
    TqwRedisUtil.set("{" + TqwConstant.FileBytesKey.NOW_BYTES + "}", byteArrayOutputStream.toByteArray());
    IOUtils.closeQuietly(byteArrayOutputStream);

2.高德请求数据

根据经纬度数组返回经纬度对应的数据

public File getGridNowFile(String[] arr) {
    logger.info(" grid now start generate ");
    String filename = TqwConstant.BASE_PATH + "/grid-now-" + RegexUtil.getRandomString(10) + ".txt";
    File   file     = new File(filename);
    try {
        String         dataFormat   = "yyyyMMddHHmm";
        BufferedWriter br           = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
        String         gridNowStart = RedisUtil.get("{" + TqwConstant.GRID_NOW_TIME + "}");
        DateTime       dateTime     = new DateTime(TqwUtil.parse(gridNowStart, dataFormat));
        br.write(dateTime.toString("yyyyMMddHH") + "00");
        br.newLine();
        List<String> requestKeys = new ArrayList<>();
        List<String> keys        = new ArrayList<>();
        for ( int i = 0; i < arr.length; i++ ) {
            String[] lonlat    = arr[i].split(",");
            boolean  isValid = TqwUtil.isValid(lonlat[0], lonlat[1]);
            if ( !isValid ) {
                continue;
            }
            double[] lonLatArray = CoordinateUtil.gcj02ToBd09(Double.valueOf(lonlat[0]), Double.valueOf(lonlat[1]));
            String   lonlatStr   = TqwUtil.formatLonLatString(String.valueOf(lonLatArray[0]), String.valueOf(lonLatArray[1]));
            keys.add("{" + TqwConstant.RedisKeyPrefix.GRID_NOW + lonlatStr + "}");
            requestKeys.add(arr[i] + "_");

            if ( requestKeys.size() >= 10000 ) {
                List<String> values = RedisUtil.getByPipeline(keys, String.class);
                for ( int j = 0; j < values.size(); j++ ) {
                    br.write(requestKeys.get(j));
                    String value = values.get(j);
                    if ( TqwUtil.isEmpty(value) ) {
                        br.write("#########################");
                    } else {
                        br.write(value.substring(0, 25));
                    }
                    br.newLine();
                }
                keys.clear();
                requestKeys.clear();
            }
        }
        if ( requestKeys.size() > 0 ) {
            List<String> values = RedisUtil.getByPipeline(keys, String.class);
            for ( int j = 0; j < values.size(); j++ ) {
                br.write(requestKeys.get(j));
                String value = values.get(j);
                if ( TqwUtil.isEmpty(value) ) {
                    br.write("#########################");
                } else {
                    br.write(value.substring(0, 25));
                }
                br.newLine();
            }
        }
        IOUtils.closeQuietly(br);
    } catch ( Exception e ) {
        logger.error(" 生成格点实况数据异常 ", e);
        FileUtils.deleteQuietly(file);
        return null;
    }
    logger.info(" grid now end generate ");
    String zipFilename = TqwConstant.BASE_PATH + "/grid-now-" + RegexUtil.getRandomString(10) + ".zip";
    try {
        ZipUtil.zip(zipFilename, "", filename);
        logger.info(" grid now end package ");
    } catch ( Exception e ) {
        logger.error("Grid Now zip error!", e);
    } finally {
        FileUtils.deleteQuietly(file);
    }
    return new File(zipFilename);
}

总结:

1、key采用{key}yyyyMMdd 数据格式 目的:在集群环境中,只根据{}中的值计算slot对应的槽值,使同一个分类的key落在相同的slot中,且均匀分布

2、使用批量操作提高效率时需要注意 原生命令:例如mget、mset。非原生命令:可以使用pipeline提高效率。原生是原子操作,pipeline是非原子操作。

3、淘汰策略 在Redis中设置过期时间是35天,当有访问并命中时,对key进行续命,延长过期时间,未在35天出现的自然淘汰。(这个会在下次系统缓存使用时用到)。

4、Bucket的概念将key均匀的分散在hash中(get(key1) -> hget(md5(key1), key1) 从而得到value),让很多key可以在BucketId空间里碰撞,那么可以认为一个BucketId下面挂了多个key。比如平均每个BucketId下面挂10个key,那么理论上我们将会减少超过90%的redis key的个数。

一千万的数据使用String类型存储,需要一千万个key,我们可以使用一种算法只会产生十万个数据(key%100000 便会产生100000个Bucket,每个Bucket中存储100个key,达到节约内存效果)
使用Bucket散列时注意事项:
1)kv存储的量级必须事先规划好,浮动的范围大概在桶个数的十到十五倍,比如我就想存储百亿左右的kv,那么最好选择30bit~31bit作为桶的个数。也就是说业务增长在一个合理的范围(10~15倍的增长)是没问题的,如果业务太多倍数的增长,会导致hashset增长过快导致查询时间增加,甚至触发zip-list阈值,导致内存急剧上升。
2)适合短小value,如果value太大或字段太多并不适合,因为这种方式必须要求把value一次性取出,比如人口标签是非常小的编码,甚至只需要3、4个bit(位)就能装下。
3)典型的时间换空间的做法,由于我们的业务场景并不是要求在极高的qps之下,一般每天亿到十亿级别的量,所以合理利用CPU租值,也是十分经济的。
4)由于使用了信息摘要降低了key的大小以及约定长度,所以无法从redis里面random出key。如果需要导出,必须在冷数据中导出。
5)expire需要自己实现,目前的算法很简单,由于只有在写操作时才会增加消耗,所以在写操作时按照一定的比例抽样,用HLEN命中判断是否超过15个entry,超过才将过期的key删除,TTL的时间戳存储在value的前32bit中。
6)桶的消耗统计是需要做的。需要定期清理过期的key,保证redis的查询不会变慢。
5、减少碎片  碎片主要原因在于内存无法对齐、过期删除后,内存无法重新分配。尽量用相同长度key、value表示,这样可以保证内存对齐

参考链接 :http://www.aboutyun.com/thread-19603-1-1.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值