あなたがよく学ぶように、あなたがこれらの疑問を持っているかどうかはわかりません。
インタビュアーが常にハッシュマップの実装原則について質問するのはなぜですか?
春のソースコードを読むことは役に立ちますか?
インタビュアーは常にスレッドプールが何をするのか私に尋ねますか?とても深く尋ねました!
または、これらの疑問:
ハッシュマップ実装の原則を100〜80回読み、Springソースコードを2〜3回使用しました。
スレッドプールは毎日手書きできますが、私の会社は小さく、私はしません。プロジェクトでこれらのテクノロジーを使用します。
今日は、これらのテクノロジーをより深く理解するためのローカルキャッシュツールを紹介します。
キャッシュ使用シナリオは次のとおりです。
たとえば、私は航空券ビジネスに携わっており、プロジェクトでは空港、航空会社、都市、国、モデル、その他のビジネス情報を使用することがよくあります。これらのデータは、99%のケースで頻繁に更新されることはありません。時間、それはユーザーの要求時間の無駄になります。
現時点では、キャッシュの必要性が反映されています。
では、問題は、分散キャッシュとローカルキャッシュのどちらを選択するかということです。
1.キャッシュパフォーマンス
PKredis:国内および国際航空券クエリシナリオでは、都市は1回の旅行で200を超えるルートをクエリする場合があります。また、ルート内のキャビンや乗り換えなどのシナリオに加えて、航空会社。redisへのフライトは20回、ルートは200回20 = 4000クエリです。現在のインターフェイスのqpsが1000の場合、4000 1000 = 1000000(100w / s)100w /秒/ sredisが必要な単純なシナリオです。 、redis iopsのパフォーマンスがいくら高くても、それをサポートすることはできません。redisからデータをフェッチした後、逆シリアル化が必要です。逆シリアル化はCPUを消費する操作です。このような大量のデータは、 CPU。redisは少し心配そうです。
本地缓存:将机场,城市这些信息直接放进 JVM 中,由 JVM 统一管理,本地缓存几乎不用考虑性能,也不要序列化。看起来还不错。
2.キャッシュ更新
PKredis:redisは分散キャッシュであるため、対応するキャッシュタイミング更新のニーズは分散タイミングタスクフレームワークに渡されます。Qunarのqscheduleを使用し、オープンソースはDangdang Elastic-job
ローカルキャッシュを使用できます。:ローカルキャッシュは、QuartzとSpringScheduleを使用して実装できます。
ちなみに、分散タイミングタスクフレームワークを選択する際には、次の点を考慮する必要があります。
1.指定した時間に実行する必要があります(遅延はこれより少ないか、まったくない必要があります)
2。ログは追跡可能です
3.クラスターの展開を繰り返すことはできません
4.クラスターを柔軟に拡張できます
5.ブロッキング、例外、その他の処理戦略
6.春と統合されている
かどうか7.コミュニティが強力かどうか、ドキュメントが豊富で時間内に更新されているかどうか、
8。高可用性
9.タイミングタスクを再利用して個別に実行できるかどうか
したがって、一般的に、キャッシュの使用は主にパフォーマンスのためであり、ローカルキャッシュの方がパフォーマンスが高いようです。
ローカルキャッシュ実装の主なポイントは何ですか?
1.非同期実行のタイミング:クォーツとスプリングスケジュールの両方で非同期とタイミングを実現できます
2.初期化:プロジェクトサーバーの起動後、キャッシュされたデータをロードする必要があります。現時点ではすべてのタイミングタスクを実行できないため、キャッシュされたデータのタイミングタスクは遅延スレッドプールにのみ渡すことができます。 ScheduledThreadPoolExecutor。完了すると、初期化に基づいて分散キャッシュは使用されません。
3.再試行メカニズム:データソースサービスが異常またはタイムアウトになると、キャッシュされたデータの更新に失敗しますが、インターフェースの異常はまれなケースです。このとき、インターフェースを再度要求する必要があります。のコメントで再試行回数を設定できます。
4.更新:インターフェース要求が成功すると、新しいデータが古いデータに置き換わります。
con currentHashMapを使用して、同じキーを直接上書きします。
ローカルキャッシュの実現アイデア:
メソッドレベルとしてアノテーションを使用すると、構成要件はアノテーション属性で表すことができます。
2. ApplicationListenerインターフェースを実装するためにspringbeanオブジェクトを定義し、springContextRefreshedEventイベントコンテナーをリッスンし、すべてのビジネスBeanが初期化された後にキャッシュコンポーネントを初期化します。
@Component("beanDefineConfig")
public class BeanDefineConfig implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private OnlineCacheManager cacheManager;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
cacheManager.initCache();
}
}
3.遅延スレッドプールを初期化し、CacheBeanインターフェースを実装するすべてのオブジェクトを取得してキャッシュ構成を取得します
4、
cacheMapへのすべてのキャッシュ構成の重合に割り当てられたOnlineCacheオブジェクト注釈構成
public class OnlineCacheManager {
private static final ILog LOGGER = LogManager.getLogger(OnlineCacheManager.class);
private Map<String, OnlineCache> cacheMap = new ConcurrentHashMap<>();
private ApplicationContext applicationContext;
private ScheduledThreadPoolExecutor executor;
private Object lock = new Object();
private List<Runnable> executeRunnableList = new ArrayList<>();
private boolean isInit = true;
public synchronized void initCache() {
try {
if (executor == null) {
executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(5);
}
cacheMap.clear();
executeRunnableList.clear();
Map<String, CacheBean> map = applicationContext.getBeansOfType(CacheBean.class);
Collection<CacheBean> beans = map.values();
for (CacheBean bean : beans) {
List<Method> methods = OnlineCollectionUtil.findAll(
Arrays.asList(bean.getClass().getMethods()),
x -> x.getAnnotation(CacheAnnotation.class) != null);
for (Method method : methods) {
OnlineCache cache = new OnlineCache();
CacheAnnotation cacheAnnotation = method.getAnnotation(CacheAnnotation.class);
Parameter[] parameters = method.getParameters();
cache.setContainParam(parameters != null && parameters.length > 0);
cache.setAutoRefresh(cacheAnnotation.autoFlash());
cache.setCacheBean(bean);
cache.setCacheName(cacheAnnotation.name());
cache.setTimeOut(getTimeOut(cacheAnnotation.timeOut(), cacheAnnotation.timeType()));
cache.setData(new ConcurrentHashMap<>());
cache.setParams(new ConcurrentHashMap<>());
cache.setDescription(cacheAnnotation.description());
cache.setHandler(convertHandler(method, bean));
cache.setDependentReference(cacheAnnotation.dependentReference() != null
&& cacheAnnotation.dependentReference().length > 0 ? cacheAnnotation
.dependentReference() : null);
cache.setEssential(cacheAnnotation.essential());
cache.setRetryTimes(cacheAnnotation.retryTimes());
cacheMap.put(cacheAnnotation.name(), cache);
}
}
// 为了解决缓存之间的依赖问题 不做深究
List<String> keyList = sortKey();
for (String key : keyList) {
OnlineCache cache = cacheMap.get(key);
executeSaveCache(cache);
if (cache.isAutoRefresh()) {
Runnable runnable = () -> executeSaveCache(cache);
executor.scheduleAtFixedRate(runnable, cache.getTimeOut(),
cache.getTimeOut(), TimeUnit.MILLISECONDS);
executeRunnableList.add(runnable);
}
}
} catch (Throwable e) {
LOGGER.error(e.getMessage(), e);
}
}
}
5.インターフェースの要求を開始し、結果をキャッシュに入れます。
キーは注釈付きの名前であり、値は対応するすべての結果セットMap <String、cacheEntity>です。
@Component
public class AirportCacheHelper implements CacheBean {
private static final ILog LOGGER = LogManager.getLogger(AirportCacheHelper.class);
@Autowired
OnlineCacheManager onlineCacheManager;
/**
* 获取所有的机场信息
*
* @return the all airports
*/
public Map<String, AirportEntity> getAllAirports() {
return onlineCacheManager.getCache(LocalCacheConstant.CODE_TO_AIRPORT_MAP);
}
/**
* 缓存初始化
*/
@CacheAnnotation(name = LocalCacheConstant.CODE_TO_AIRPORT_MAP, timeOut = 120, essential = true, retryTimes = 3)
public Map<String, AirportEntity> initCodeToAirportMap() {
Map<String, AirportEntity> map = null;
// 从数据源接口获取需要缓存的数据 故不展示纯业务代码
List<AirportEntityWs> airports = getAirportsFromSoa();
if (!CollectionUtils.isEmpty(airports)) {
map = new HashMap<>(airports.size());
for (AirportEntityWs soaEntity : airports) {
if (map.containsKey(soaEntity.getCode())) {
continue;
}
AirportEntity cacheEntity = new AirportEntity();
cacheEntity.setCode(soaEntity.getCode());
cacheEntity.setAddress(soaEntity.getAddress());
cacheEntity.setAirportPy(soaEntity.getAirportPY());
cacheEntity.setCityId(soaEntity.getCityID());
cacheEntity.setCityCode(soaEntity.getCityCode());
cacheEntity.setDistance(soaEntity.getDistance());
cacheEntity.setName(soaEntity.getName());
cacheEntity.setNameEn(soaEntity.getName_En());
cacheEntity.setShortName(soaEntity.getShortName());
cacheEntity.setTelphone(soaEntity.getTelphone());
cacheEntity.setSuperShortName(soaEntity.getSuperShortName());
cacheEntity.setShortNameEn(soaEntity.getShortName_En());
cacheEntity.setLocType(soaEntity.getLocType());
cacheEntity.setLatitude(soaEntity.getLatitude());
cacheEntity.setLongitude(soaEntity.getLongitude());
map.put(cacheEntity.getCode(), cacheEntity);
}
}
return map;
}
// 在 OnlineCacheManager 中获取缓存
public <T> T getCache(String cacheName, Object... params) {
T t = null;
try {
if (cacheMap.containsKey(cacheName)) {
OnlineCache cache = cacheMap.get(cacheName);
long nowTime = System.currentTimeMillis();
if (!cache.isContainParam()) {
if (cache.isAutoRefresh()) {
t = (T) cache.getData().get(cacheName);
} else {
t = getStaticCache(cacheName, cache, nowTime);
}
} else {
if (params != null && params.length > 0) {
StringBuilder cacheKey = new StringBuilder(cacheName);
for (Object o : params) {
cacheKey.append(o.hashCode());
}
cache.getParams().put(cacheKey.toString(), params);
t = getStaticCache(cacheKey.toString(), cache, nowTime);
}
}
}
} catch (Throwable e) {
LOGGER.error(e);
}
return t;
}
6. API CacheManageHelper.getXXXEntity(Stringcode)を介して呼び出します
/**
* 根据机场三字码获取机场信息
*
* @param code the code
* @return the airport entity
*/
public static AirportEntity getAirportEntity(String code) {
AirportEntity entity = null;
if (StringUtils.isEmpty(code)) {
return entity;
}
Map<String, AirportEntity> dataMap = airportCacheHelper.getAllAirports();
if (null != dataMap && dataMap.containsKey(code)) {
entity = dataMap.get(code);
}
return entity;
}
総括する:
このローカルキャッシュの実装は難しくありません。重要なのは、ビジネス上の問題点を見つけ、それを実装するために学んだテクノロジーを柔軟に使用することです。
springIOCまたはspringMVCのソースコードを読んだ後、springはリフレクションの使用法を深く理解します。後でリフレクションが必要になることがわかっている場合は、ハンドラーをカプセル化できるかどうかを考えることができます。
ハッシュマップのソースコードを読んだ後、キャッシュされたデータに基づいていることがわかります。CPUリソースを占有するハッシュマップの拡張が過度に大きくなるのを避けるためにハッシュマップを初期化するサイズ。スレッドプールを確認した後でのみ、キャッシュが初期化されるときにタイミングタスクがこれを実行できないことがわかります。たまたま、Springのスケジュールも時間制限のあるスレッドプールであり、これは長期的な技術の蓄積の結果です。
このキャッシュコンポーネントを読んだ後、これらのテクノロジーの理解と応用を深めましたか?
私の好きな友達が注目を集めるようになります