parcels的偏远邮编代码工作流程

2020年5月18日 作者 陈益

一、针对偏远邮编的整个逻辑概述:

  1. 根据国家、派送商id进行分组,然后将数量大于200的加入到缓存中,且缓存使用了基于map的软引用防止堆内存溢出。其中map的key就是: String softKey = MessageFormat.format(“{0}-{2}”, itemId, countryCode);
//定义map数据键值对 public static volatile Map<String, SoftReference<List<ExtendedAreaSucharge>>> SOFTREFERENCE_EXTENDEDAREASUCHARGE_MAP = Maps.newHashMap(); //初始化数据,使用队列进行异步初始化 public void afterPropertiesSet() throws Exception {

        SINGLE_THREADPOOLEXECUTOR.execute(new Runnable() { @Override public void run() {
                initExtendedAreaSurcharge();
            }
        });

    }
    private void initExtendedAreaSurcharge(){ Map<String, SoftReference<List<ExtendedAreaSucharge>>> tempExtendAreaSurcharge = Maps.newHashMap();
        StringBuilder hsql = new StringBuilder("select eas.logisticsSupplierItemId ," + "eas.countryCode,count(1) from ExtendedAreaSucharge as eas group by logisticsSupplierItemId,countryCode"); List<Object[]> itemIdCountryCodes = entityManager.createQuery(hsql.toString()).getResultList(); for (Object[] itemIdCountryCode : itemIdCountryCodes) { String itemId = String.valueOf(itemIdCountryCode[0]); String countryCode = String.valueOf(itemIdCountryCode[1]);
            long cnt = new BigDecimal(String.valueOf(itemIdCountryCode[2])).intValue(); if (cnt > 200) { //mapkey值说明 String softKey = MessageFormat.format("{0}{1}{2}", itemId, RANGE_FLAG_UNESCAPE, countryCode);
                ExtendedAreaSuchargeDto extendedAreaSuchargeDto = new ExtendedAreaSuchargeDto();
                extendedAreaSuchargeDto.setLogisticsSupplierItemId(itemId);
                extendedAreaSuchargeDto.setCountryCode(countryCode);
                logger.info("init surcharge for {}",softKey); List<ExtendedAreaSucharge> extendedAreaSuchargeList = listAllByQueryHints(extendedAreaSuchargeDto); //加入软引用,防止虚拟机内存过大 SoftReference<List<ExtendedAreaSucharge>> listSoftReference = new SoftReference<List<ExtendedAreaSucharge>>(extendedAreaSuchargeList);
                tempExtendAreaSurcharge.put(softKey, listSoftReference);
            }
        } for (Map.Entry<String, SoftReference<List<ExtendedAreaSucharge>>> stringSoftReferenceEntry : tempExtendAreaSurcharge.entrySet()) {
            SoftReference<List<ExtendedAreaSucharge>> softReference = stringSoftReferenceEntry.getValue();
            logger.info("soft...key.{}.....value....{}", stringSoftReferenceEntry.getKey(), softReference.get() == null ? null : softReference.get().size());
        } //重新引用 SOFTREFERENCE_EXTENDEDAREASUCHARGE_MAP = tempExtendAreaSurcharge;

        logger.info("init extendAreaSurcharge completed");
    }
  1. 在上面中也加入了二级缓存,二级缓存的条件就是 logisticsSupplierItemId 和 countryCode
/**
     * 使用二级缓存查询所有的数据 *
     * @param extendedAreaSuchargeDto
     * @return
     */ public List<ExtendedAreaSucharge> listAllByQueryHints(ExtendedAreaSuchargeDto extendedAreaSuchargeDto) {

        Map<String, Object> hqlParam = Maps.newHashMap(); StringBuilder hsql = new StringBuilder(" from ExtendedAreaSucharge eas where 1=1 "); if (StringUtils.isNotBlank(extendedAreaSuchargeDto.getLogisticsSupplierItemId())) {
            hsql.append(" and eas.logisticsSupplierItemId=:logisticsSupplierItemId"); hqlParam.put("logisticsSupplierItemId", extendedAreaSuchargeDto.getLogisticsSupplierItemId());  }

        if (StringUtils.isNotBlank(extendedAreaSuchargeDto.getCountryCode())) {
            hsql.append(" and eas.countryCode=:countryCode"); hqlParam.put("countryCode", extendedAreaSuchargeDto.getCountryCode());  }

        Query query = entityManager.createQuery(hsql.toString()).setHint(org.hibernate.jpa.QueryHints.HINT_CACHEABLE, true); for (Map.Entry<String, Object> qParam : hqlParam.entrySet()) {
            query.setParameter(qParam.getKey(), qParam.getValue()); }

        List<ExtendedAreaSucharge> extendedAreaSuchargeList = query.getResultList(); return extendedAreaSuchargeList;  }
  1. 后面查询的时候,如果条件同时包含logisticsSupplierItemId 和 countryCode 那么会先组成key然后获取数据,如果map包含当前key,那么getValue继续查看value是否为空,如果为空说明被虚拟机释放了,那么需要使用hibernate进行查询,因第一次初始化的时候查询过且打开了二级缓存,这一步应该会很快。如果当前的map不包含key说明数据小于200直接走二级缓存或者DB
/**  * 流程说明:  * 1.先查询数据是否是小于200的缓存数据?因为小于200的不缓存,直接查询DB走二级缓存  * 2.如果是大于200的数据说明在cache中,那么先判断软引用是否已经被虚拟机释放?如果释放走二级缓存,重新建立软引用,如果没有释放直接返回  * 好处,禁止多个线程都创建堆内存  *  * @param logisticsSupplierItemId  * @param countryCode  * @return  */ private List<ExtendedAreaSucharge>
            (String logisticsSupplierItemId, String countryCode) { String softReferenceMapKey = MessageFormat.format("{0}{1}{2}", logisticsSupplierItemId, RANGE_FLAG_UNESCAPE, countryCode); try { //加读锁 REENTRANT_READ_WRITE_LOCK.readLock().lock(); if (SOFTREFERENCE_EXTENDEDAREASUCHARGE_MAP.containsKey(softReferenceMapKey)) {
                SoftReference<List<ExtendedAreaSucharge>> softReference = SOFTREFERENCE_EXTENDEDAREASUCHARGE_MAP.get(softReferenceMapKey); if (softReference.get() == null) { //先释放读锁 REENTRANT_READ_WRITE_LOCK.readLock().unlock(); try { //加入写锁,第一个线程进入的时候,先上写锁 REENTRANT_READ_WRITE_LOCK.writeLock().lock();
                        ExtendedAreaSuchargeDto extendedAreaSuchargeDto = new ExtendedAreaSuchargeDto();
                        extendedAreaSuchargeDto.setLogisticsSupplierItemId(logisticsSupplierItemId);
                        extendedAreaSuchargeDto.setCountryCode(countryCode); List<ExtendedAreaSucharge> extendedAreaSuchargeList = listAllByQueryHints(extendedAreaSuchargeDto);
                        SoftReference<List<ExtendedAreaSucharge>> listSoftReference = new SoftReference<List<ExtendedAreaSucharge>>(extendedAreaSuchargeList);
                        SOFTREFERENCE_EXTENDEDAREASUCHARGE_MAP.put(softReferenceMapKey, listSoftReference);
                    } finally {
                        REENTRANT_READ_WRITE_LOCK.writeLock().unlock();
                    }
                    REENTRANT_READ_WRITE_LOCK.readLock().lock();
                } else { return softReference.get();
                }
            } else {//小数据不缓存,直接查DB走二级缓存 ExtendedAreaSuchargeDto extendedAreaSuchargeDto = new ExtendedAreaSuchargeDto();
                extendedAreaSuchargeDto.setLogisticsSupplierItemId(logisticsSupplierItemId);
                extendedAreaSuchargeDto.setCountryCode(countryCode); List<ExtendedAreaSucharge> extendedAreaSuchargeList = listAllByQueryHints(extendedAreaSuchargeDto); return extendedAreaSuchargeList;
            }
        } finally {
            REENTRANT_READ_WRITE_LOCK.readLock().unlock();
        } return Lists.newArrayList();
    }
  1. 考虑到增删的时候修改DB那么需要同步缓存.这里使用队列的方式进行异步处理,且60秒后执行,好处:比如Nick在第一次编辑邮编的时候操作了派送商A和国家B此时会加入队列中,然后过来30秒后有操作了派送商A和国家B执行邮编不同因第一次已经添加进去了且没有执行,那么第二次添加的时候会检查当前的任务中是否已经包含了次派送商A和国家B,如果已经包含了那么不会重复添加任务,因为次任务执行会查询大量数据。
 /**
     * 偏远邮编的任务 */ public static class ExtendedAreaSurchargeTask implements Runnable { public final static String All = "ALL"; private String taskName; private String logisticsSupplierItemId; private String countryCode; public ExtendedAreaSurchargeTask(String logisticsSupplierItemId, String countryCode) { this.logisticsSupplierItemId = logisticsSupplierItemId; this.countryCode = countryCode;
        } public ExtendedAreaSurchargeTask(String taskName) { this.taskName = taskName;
        }

        @Override public void run() { throw new ServiceException("您必须覆盖此方法");
        } /**
         * 获取taskName
         */ public String getTaskName() { if (StringUtils.isBlank(taskName)) { String softReferenceMapKey = MessageFormat.format("{0}{1}{2}", StringUtils.trim(logisticsSupplierItemId), RANGE_FLAG_UNESCAPE, StringUtils.trim(countryCode)); return softReferenceMapKey;
            } else { return taskName;
            }
        }
    } /**
     * 创建一个线程池用于处理 */ private static final ThreadPoolExecutor SINGLE_THREADPOOLEXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100), new ThreadFactory() {
        @Override public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setDaemon(true); return thread;
        }
    }, new ThreadPoolExecutor.DiscardPolicy()); /**
     * 手动调用二级缓存进行查询 */ public void executeQueryHintsCall(final String logisticsSupplierItemId, final String countryCode, boolean initAllData) { //初始化整个DB数据 if (initAllData) {
            Iterator iterator = SINGLE_THREADPOOLEXECUTOR.getQueue().iterator(); boolean readyTask = false; while (iterator.hasNext()) {
                ExtendedAreaSurchargeTask extendedAreaSurchargeTask = (ExtendedAreaSurchargeTask) iterator.next(); if (extendedAreaSurchargeTask.getTaskName().equalsIgnoreCase(ExtendedAreaSurchargeTask.All)) {
                    readyTask = true; break;
                }
            } if (!readyTask) {
                SINGLE_THREADPOOLEXECUTOR.execute(new ExtendedAreaSurchargeTask(ExtendedAreaSurchargeTask.All) {
                    @Override public void run() { try {
                            Thread.currentThread().sleep(60 * 1000);
                        } catch (InterruptedException e) {
                            logger.error(e.getMessage(), e);
                        } try { //初始化整个DB initExtendedAreaSurcharge();
                            logger.info("update cache for all db success");
                        } catch (Exception e) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                });
            }
        } else { if (StringUtils.isNotBlank(StringUtils.trim(logisticsSupplierItemId)) && StringUtils.isNotBlank(StringUtils.trim(countryCode))) { String softReferenceMapKey = MessageFormat.format("{0}{1}{2}", StringUtils.trim(logisticsSupplierItemId), RANGE_FLAG_UNESCAPE, StringUtils.trim(countryCode)); if (SOFTREFERENCE_EXTENDEDAREASUCHARGE_MAP.containsKey(softReferenceMapKey)) {
                    Iterator iterator = SINGLE_THREADPOOLEXECUTOR.getQueue().iterator(); boolean readyTask = false; while (iterator.hasNext()) {
                        ExtendedAreaSurchargeTask extendedAreaSurchargeTask = (ExtendedAreaSurchargeTask) iterator.next(); if (extendedAreaSurchargeTask.getTaskName().equalsIgnoreCase(softReferenceMapKey)) {
                            readyTask = true; break;
                        }
                    } if (!readyTask) {
                        SINGLE_THREADPOOLEXECUTOR.execute(new ExtendedAreaSurchargeTask(logisticsSupplierItemId, countryCode) {
                            @Override public void run() { try {
                                    Thread.currentThread().sleep(60 * 1000);
                                } catch (InterruptedException e) {
                                    logger.error(e.getMessage(), e);
                                }
                                ExtendedAreaSuchargeDto extendedAreaSuchargeDto = new ExtendedAreaSuchargeDto();
                                extendedAreaSuchargeDto.setLogisticsSupplierItemId(StringUtils.trim(logisticsSupplierItemId));
                                extendedAreaSuchargeDto.setCountryCode(StringUtils.trim(countryCode));
                                List<ExtendedAreaSucharge> extendedAreaSuchargeList = listAllByQueryHints(extendedAreaSuchargeDto);
                                SoftReference<List<ExtendedAreaSucharge>> listSoftReference = new SoftReference<List<ExtendedAreaSucharge>>(extendedAreaSuchargeList);
                                SOFTREFERENCE_EXTENDEDAREASUCHARGE_MAP.put(softReferenceMapKey, listSoftReference);
                                logger.info("update cache for {} success",softReferenceMapKey);
                            }
                        });
                    }
                }
            }
        }
    } 
  1. 校验邮编的逻辑代码,直接在内存中进行过滤。
/**
     * 根据派送商logisticsSupplierItemIdcountryCode国家code进行处理 *
     * @param logisticsSupplierItemId
     * @param countryCode
     * @return */ public ExtendedAreaSucharge validateExtendedAreaSurchargeExists(String logisticsSupplierItemId, String countryCode, String postCode) {

        logisticsSupplierItemId = StringUtils.trim(logisticsSupplierItemId);
        countryCode = StringUtils.trim(countryCode);
        postCode = StringUtils.trim(postCode);
        Assert.hasText(logisticsSupplierItemId);
        Assert.hasText(countryCode);
        Assert.hasText(postCode);
        List<ExtendedAreaSucharge> extendedAreaSuchargeList = this.getExtendedAreaSuchargeFromCache(logisticsSupplierItemId, countryCode); if (extendedAreaSuchargeList.size() > 0) { for (ExtendedAreaSucharge extendedAreaSucharge : extendedAreaSuchargeList) { //开始进行遍历校验 //如果是单个邮编 if (extendedAreaSucharge.getPostCodeType() == ExtendedAreaSucharge.PostCodeType.SINGLE) { if (StringUtils.trim(extendedAreaSucharge.getOriginalDesc()).equalsIgnoreCase(postCode)) { return extendedAreaSucharge;
                    }
                } else { //说明是区间类型的邮编 //2.判断数据的类型 if (postCode.matches("^\\d+$")) { //是否是0前缀的 if (postCode.startsWith(ZERO_PREFIX)) { //是否是前缀的 ExtendedAreaSucharge returnExtendedAreaSucharge = processZeroPrefixOrNoneNumber(extendedAreaSucharge, postCode); if (returnExtendedAreaSucharge != null) { return returnExtendedAreaSucharge;
                            }
                        } else {//纯数字进行比较 Long long4PostCode = Long.parseLong(postCode);
                            String postCodeStart4Char = extendedAreaSucharge.getPostCodeStart4Char();
                            String posCodeEnd4Char = extendedAreaSucharge.getPostCodeEnd4Char(); if(NumberUtils.isNumber(postCodeStart4Char) && NumberUtils.isNumber(posCodeEnd4Char)){ if (long4PostCode >= Long.parseLong(postCodeStart4Char) && long4PostCode <= Long.parseLong(posCodeEnd4Char)) { return extendedAreaSucharge;
                                }
                            }
                        }
                    } else {//非纯数字 ExtendedAreaSucharge returnExtendedAreaSucharge = processZeroPrefixOrNoneNumber(extendedAreaSucharge, postCode); if (returnExtendedAreaSucharge != null) { return returnExtendedAreaSucharge;
                        }
                    }
                }
            }
        } return null;
    }
  1. 说明删除的时候,根据删除的数据根据国家和派送商id过滤处理,然后进行统一处理。
/**
     * tsd 2019/06/24 批量删除偏远邮编,按选中删除or按查询条件删除 */ @Override public void bachDelete(ExtendedAreaSuchargeDto extendedAreaSuchargeDto) {

        Set<String> extendAreaSurchargeKey = Sets.newHashSet(); if (extendedAreaSuchargeDto.getDeleteIds() != null && extendedAreaSuchargeDto.getDeleteIds().size() > 0) { for (String id : extendedAreaSuchargeDto.getDeleteIds()) {
                ExtendedAreaSucharge extendedAreaSuchargeDb = this.extendedAreaSuchargeDao.findOne(id); if (extendedAreaSuchargeDb != null) { this.extendedAreaSuchargeDao.delete(id); String softReferenceMapKey = MessageFormat.format("{0}{1}{2}", StringUtils.trim(extendedAreaSuchargeDb.getLogisticsSupplierItemId()), RANGE_FLAG_UNESCAPE, StringUtils.trim(extendedAreaSuchargeDb.getCountryCode()));
                    extendAreaSurchargeKey.add(softReferenceMapKey);
                }
            }
        } else {
            DataGrid<ExtendedAreaSucharge> dataGrid = listByOrgId(extendedAreaSuchargeDto); if (dataGrid.getRows() != null && dataGrid.getRows().size() > 0) { for (ExtendedAreaSucharge sucharge : dataGrid.getRows()) { this.extendedAreaSuchargeDao.delete(sucharge.getId()); String softReferenceMapKey = MessageFormat.format("{0}{1}{2}", StringUtils.trim(sucharge.getLogisticsSupplierItemId()), RANGE_FLAG_UNESCAPE, StringUtils.trim(sucharge.getCountryCode()));
                    extendAreaSurchargeKey.add(softReferenceMapKey);
                }
            }
        } //处理缓存 if (extendAreaSurchargeKey.size() > 0) { for (String key : extendAreaSurchargeKey) { this.executeQueryHintsCall(key.split(RANGE_FLAG_UNESCAPE)[0], key.split(RANGE_FLAG_UNESCAPE)[1], false);
            }
        }
    }