Redis系统缓存使用经验总结
1、接口请求缓存,车机获取天气数据接口校验,licence为唯一标识
①请求到达去缓存获取以licence加前缀为key的车机信息 ② 车机信息为空查询数据库,将查询结果缓存起来,设置过期时间③查询结果为空时,以licence加前缀为key,在缓存中存储空对象,设置过期时间。防止相同不合法licence频繁访问(缓存中没有时要查询数据库)数据库(穿透现象),造成服务器压力。④请求到达时去缓存查询不为空时,设置licence对应key的过期时间
防止redis穿透,雪崩现象
Count count = RedisUtil.get(ApiConstant.PRE_LICENCE_KEY + "{" + requestParam.getLicence() + "}", Count.class); if ( ApiUtil.isEmpty(count) ) { count = this.countDao.viewCountByLicence(requestParam.getLicence()); RedisUtil.set(ApiConstant.PRE_LICENCE_KEY + "{" + requestParam.getLicence() + "}", JSON.toJSONString(count)); RedisUtil.expire(ApiConstant.PRE_LICENCE_KEY + "{" + requestParam.getLicence() + "}", 2 * 24 * 60 * 60); } if ( ApiUtil.isEmpty(count) ) { //设置访问licence 空对象 count.allProperties2Null(); RedisUtil.set(ApiConstant.PRE_LICENCE_KEY + "{" + requestParam.getLicence() + "}", JSON.toJSONString(count)); RedisUtil.expire(ApiConstant.PRE_LICENCE_KEY + "{" + requestParam.getLicence() + "}", 2 * 24 * 60 * 60); S6Base s6Base = new S6Base(); s6Base.allProperties2Null(); s6Base.setStatus(ApiConstant.ReturnCode.PERMISSION_DENIED.getCode()); list.add(s6Base); return buildFinalJSON(JSON.toJSONString(list)); } //用户与licence不对应 if ( !ApiUtil.isEmpty(count) && !ApiUtil.isEmpty(count.getUserId()) && !count.getUserId().equals(user.getId()) ) { //设置访问licence 空对象 count.allProperties2Null(); RedisUtil.set(ApiConstant.PRE_LICENCE_KEY + "{" + requestParam.getLicence() + "}", JSON.toJSONString(count)); RedisUtil.expire(ApiConstant.PRE_LICENCE_KEY + "{" + requestParam.getLicence() + "}", 2 * 24 * 60 * 60); S6Base s6Base = new S6Base(); s6Base.allProperties2Null(); s6Base.setStatus(ApiConstant.ReturnCode.PERMISSION_DENIED.getCode()); list.add(s6Base); return buildFinalJSON(JSON.toJSONString(list)); }
2、redis管道,批量操作数据
map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + key + "}", sb.toString()); //todo 以后删除,3*3 造出 1*1 的数据, 用中心点填充周围 BigDecimal lonBigdecimal = new BigDecimal(hewlon); BigDecimal latBigdecimal = new BigDecimal(hewlat); 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_HOURLY + DataUtil.formatLonLatString(hewlon, latIncr.toString()) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + DataUtil.formatLonLatString(hewlon, latDecr.toString()) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + DataUtil.formatLonLatString(lonIncr.toString(), hewlat) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + DataUtil.formatLonLatString(lonDecr.toString(), hewlat) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + DataUtil.formatLonLatString(lonIncr.toString(), latIncr.toString()) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + DataUtil.formatLonLatString(lonIncr.toString(), latDecr.toString()) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + DataUtil.formatLonLatString(lonDecr.toString(), latIncr.toString()) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); map.put("{" + TqwConstant.RedisKeyPrefix.GRID_HOURLY + DataUtil.formatLonLatString(lonDecr.toString(), latDecr.toString()) + "_" + (DataUtil.object2Int(hourArr[0]) >= 24 ? (DataUtil.object2Int(hourArr[0]) >= 48 ? 72 : 48) : "24") + "}", sb.toString()); //存储 if ( map.size() >= DataUtil.object2Int(Constant.PROPERTIES.get("pipeline.count")) ) { //设置新值 RedisUtil.setByPipeline(map); map.clear(); } } catch ( Exception ignored ) { logger.error(" grid hourly line info error [ " + lines[1] + " ] ", ignored); } } //执行集合剩余数据 if ( map.size() > 0 ) { //设置新值 RedisUtil.setByPipeline(map); }
3、redis存储二进制文件数据
private void backFile2Redis() { long begin = System.currentTimeMillis(); logger.info("back up grid-hourly file to redis start !"); String[] array = readPointFromFile(TqwConstant.PointLonLat.GRID_HOURLY_POINTS); logger.info("grid-hourly array size is " + array.length); File zipFile = this.backFileGenerator.getGridHourlyFile(array); try { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); FileUtils.copyFile(zipFile, byteArrayOutputStream); TqwRedisUtil.set("{" + TqwConstant.FileBytesKey.HOURLY_BYTES + "}", byteArrayOutputStream.toByteArray()); IOUtils.closeQuietly(byteArrayOutputStream); } catch ( Exception e ) { logger.info("Grid-hourly zip写入缓存失败", e); } finally { try { zipFile.delete(); } catch ( Exception ignored ) {} } logger.info("back up Grid-hourly file to redis end ! cost time " + (System.currentTimeMillis() - begin)); }
redisUtil中工具方法如下:
public static void set(String key, byte[] values) { if ( FLAG ) { Jedis jedis = null; try { jedis = getJedis(); jedis.set(key.getBytes(), values); } catch ( Exception var7 ) { logger.error(" redis set error, key is [ " + key + " ] ", var7); } finally { if ( jedis != null ) { jedis.close(); } } } }
4、redis执行lua脚本程序(原子操作)
需求:qpm校验,统计每分钟访问量
①准备evalsha(sha,keys[],argv[]) sha为scriptload(script)返回值 参数key[]数组用于替换lua中key值参数,argv[]存放普通参数
/** * qpm 每分钟访问次数 */ public static final String QPM_SCRIPT = "local qpm = tonumber(redis.call('get', KEYS[1]));\n" + "if qpm == nil then\n" + " redis.call('incr', KEYS[1]);\n" + " redis.call('expire', KEYS[1], 90);\n" + " return true;\n" + "elseif qpm < tonumber(ARGV[1]) then\n" + " redis.call('incr', KEYS[1]);\n" + " return true;\n" + "else \n" + " redis.call('incr', KEYS[1]);\n" + " return false; \n" + "end"; public static String QPM_SCRIPT_SHA1 = "958ed8594556dd2c343e8fbf9ac53401337b56a4";//通过scriptload方法获得②evalsha()执行时必须先执行scriptload()加载脚本进缓存
if ( !QPM_SCRIPT_EXISTS && !RedisUtil.scriptExists(ApiConstant.QPM_SCRIPT_SHA1) ) { ApiConstant.QPM_SCRIPT_SHA1 = RedisUtil.scriptLoad(ApiConstant.QPM_SCRIPT); QPM_SCRIPT_EXISTS = true; } if ( ApiConstant.ServiceNode.FREE.equalsIgnoreCase(Constant.PROPERTIES.get(ApiConstant.Properties.NODE_ID)) ) { Object value; if ( ApiUtil.containsRole(user.getAllRoleList(), ApiConstant.Role.DEVELOPER) ) { value = RedisUtil.evalsha(ApiConstant.QPM_SCRIPT_SHA1, "{" + user.getSsoId() + "}" + ApiUtil.getCurrentDateTime("yyyyMMddHHmm"), ApiConstant.QPM_DEVELOPER.toString()); } else { value = RedisUtil.evalsha(ApiConstant.QPM_SCRIPT_SHA1, "{" + user.getSsoId() + "}" + ApiUtil.getCurrentDateTime("yyyyMMddHHmm"), ApiConstant.QPM_FREE.toString()); } if ( value == null ) { return true; } }