诸多缘由,咱们的程序每每不能解释其自己,再者,咱们也不能苛求调用者读(懂)咱们的程序逻辑。因此,咱们须要给代码添加注释。好的代码注释规范是不可或缺的,尤为是要给类和方法添加注释。java
今天下午生产环境的一个服务出现java.lang.OutOfMemoryError: GC overhead limit exceeded,组内各伙伴齐参与,来查找缘由。redis
其中一个线索是,以下sql频繁执行,致使日志量激增超过1G。sql
2021-04-13 13:35:01.023 [DEBUG] [http-apr-8480-exec-9] [com.yft.mapper.TMerchantDAO.selectMerchantByMerId:132] ==> Preparing: select * from T_MERCHANT where MER_ID=? 2021-04-13 13:35:01.024 [DEBUG] [http-apr-8480-exec-9] [com.yft.mapper.TMerchantDAO.selectMerchantByMerId:132] ==> Parameters: 89900000617916182868(String) 2021-04-13 13:35:01.025 [TRACE] [http-apr-8480-exec-9] [com.yft.mapper.TMerchantDAO.selectMerchantByMerId:138] <== Columns: ID, MER_ID, MER_NAME, PROVINCE, CITY, COUNTY, REG_ADDR, LEGAL_PERSON, LEGAL_ID_CARD, AUTH_MOBILE, AUTH_EMAIL, SIGNING_PLAT_TYPE, DOMAIN_NAME, RECORD_NO, APP_NAME, APP_MARKET, MCC_1, MCC_2, BUSINESS_NAME, BUSINESS_MOBILE, BUSINESS_EMAIL, FINANCE_NAME, FINANCE_MOBILE, FINANCE_EMAIL, IT_NAME, IT_MOBILE, IT_EMAIL, IT_QQ, POST_NAME, POST_MOBILE, CONTRACT_POST_ADDR, AGENT_ID, SALE_ID, SETTLE_ACC_NAME, SETTLE_ACC_BANK, SETTLE_ACC_NO, SETTLE_CYCLE, SETTLE_TYPE, LEVY_ID, CUS_CONTRACT_TYPE, TAX_COMPANY, TAX_NO, TAX_ADDR, TAX_MOBILE, TAX_OPEN_BANK, TAX_ACC, STATE, AUDIT_STATE, PAY_CHANNEL, FILE_PATH, FIRST_AUDIT_TIME, REVIEW_AUDIT_TIME, MEMO, CREATE_TIME, UPDATE_TIME, OPERATOR, FIRST_AUDITOR, REVIEW_AUDITOR, SUBMIT_TIME, OPEN_TIME, CONTRACT_FILE_PATH, MINI_PIC_PATH, OTHER_NAME, MER_BEAR_TAX, INVOICE_TYPE, IS_SET_AUDIT, SIGNING_PARAM_VALID, CHECK_TYPE, CHECK_MOBILES, MERCHANT_TYPE, GROUP_MER_ID, AUTH_MSG, AGREEMENT_TYPE, IS_CONFIRM, IS_DELIVER, IS_CONFIRM_PROJECT, ENTERPRISE_ID
经过查程序,这个TMerchantDAO#selectMerchantByMerId方法有20多处调用,因为是外采的老系统,以及后续需求迭代缺少有效管控,形成咱们短期内不能定位每一个调用的具体用途。而服务的log文件还在不断增大,20分钟就会增长0.1G,固然,可想而知db压力也不小。权宜之计,有必要马上立刻赶忙减小这个方法的执行次数。缓存
@Service @Slf4j public class TMerchantServiceImpl implements TMerchantService { @Override public TMerchant selectMerchantByMerId(Map<String, Object> map) { return tMerchantDAO.selectMerchantByMerId(map); } }
想到的方案天然是用缓存。app
恰好,项目里有redis工具类JedisUtils。因而,三下五除二,修改程序为以下版本,并找了两个小伙伴review,没发现什么问题,当即找运维先合代码重启服务。 运维
@Service @Slf4j public class TMerchantServiceImpl implements TMerchantService { @Override public TMerchant selectMerchantByMerId(Map<String, Object> map) { // return tMerchantDAO.selectMerchantByMerId(map); return selectMerchantByMerId_Cache(map); } public TMerchant selectMerchantByMerId_Cache(Map<String, Object> map) { String merId = String.valueOf(map.get("merId")); String key="TMerchant:".concat(merId); if(JedisUtils.exists(key)){ logger.info("read from redis. key={}", key); return (TMerchant) JedisUtils.getObject(key); } else { TMerchant tMerchant = tMerchantDAO.selectMerchantByMerId(map); JedisUtils.setObject(key, tMerchant, 30); return tMerchant; } } }
重启以后,日志再也不激增,不过,奇怪的是,log里并未发现read from redis. key=。由于有其余的事情处理,困扰了一下午的这个问题,到晚上下班后,通过本地编写testcase测试才发现,上面调用JedisUtils的exists(String)是不对的,而应该调用JedisUtils的另外一个方法existsObject(String)。。。而我当时却并未注意到还有existsObject(String)这个方法,只是快速扫了一下exists(String),感受没问题就用上了。。。ide
以下是JedisUtils里这两个方法的定义工具
/** * 缓存是否存在 * @param key 键 * @return */ public static boolean exists(String key) { boolean result = false; Jedis jedis = null; try { jedis = getResource(); result = jedis.exists(key); logger.debug("exists {}", key); } catch (Exception e) { logger.warn("exists {}", key, e); } finally { returnResource(jedis); } return result; } /** * 缓存是否存在 * @param key 键 * @return */ public static boolean existsObject(String key) { boolean result = false; Jedis jedis = null; try { jedis = getResource(); result = jedis.exists(getBytesKey(key)); logger.debug("existsObject {}", key); } catch (Exception e) { logger.warn("existsObject {}", key, e); } finally { returnResource(jedis); } return result; }
单看这样的方法注释,很难说将来别人使用的时候不会出现我踩的这个坑呀!测试
因而乎,我完善了一下方法的注释,见下方,我以为,若是往后还有人踩到这个坑的话,我只能呵呵嘿嘿哈哈了。spa
/** * 缓存是否存在 * 注意与{@link #existsObject(String)}的区别,本方法适用于set设置值,然后者适用于经过setObject来设置值 * @param key 键 * @return */ public static boolean exists(String key) { boolean result = false; Jedis jedis = null; try { jedis = getResource(); result = jedis.exists(key); logger.debug("exists {}", key); } catch (Exception e) { logger.warn("exists {}", key, e); } finally { returnResource(jedis); } return result; } /** * 缓存是否存在 * 注意与{@link #exists(String)}的区别,本方法适用于setObject设置值,然后者适用于经过set来设置值 * @param key 键 * @return */ public static boolean existsObject(String key) { boolean result = false; Jedis jedis = null; try { jedis = getResource(); result = jedis.exists(getBytesKey(key)); logger.debug("existsObject {}", key); } catch (Exception e) { logger.warn("existsObject {}", key, e); } finally { returnResource(jedis); } return result; }
后话,为何服务重启完以后,log再也不激增了,经过查看日志,那个select * from T_MERCHANT where MER_ID=?的方法再也不执行了。显然,kill掉进程后,那些工做线程也都跟着死掉了。重启后,没有业务操做触发这个方法的执行,因此,日志量再也不激增了。