이 글에서 언급한 TwoQueues 캐싱 모델은 메모리 내 데이터 캐싱 모델을 의미합니다.
어떤 언어이든 반복적인 작업과 읽기를 피하기 위해 일부 데이터를 메모리에 저장해야 할 수도 있습니다. 가장 일반적인 시나리오는 JQuery 선택기입니다. 일부 Dom 요소를 선택하는 데는 시간이 많이 걸립니다. 호출될 때마다 Dom 트리를 다시 탐색할 필요 없이 이 데이터를 캐시하기를 바랍니다.
저장만 하시면 되는데 금액이 있을텐데요! 모든 과거 데이터를 메모리에 담는다는 것은 불가능하며, 현재 메모리 용량은 여전히 턱없이 부족합니다. 메모리가 충분히 크다고 해도 이론적으로 각 스레드에 할당되는 메모리는 제한되어 있습니다.
그렇다면 질문은 어떻게 하면 정말 유용한 데이터를 효율적으로 캐시할 수 있느냐는 것입니다. 여기에는 유용한 데이터를 유지하기 위해 정크 데이터를 제거해야 하는 제거 알고리즘이 포함됩니다.
더 일반적으로 사용되는 아이디어는 다음과 같습니다.
FIFO: 선입선출 대기열입니다. 첫 번째로 캐시된 데이터가 가장 먼저 제거됩니다. 이 모델은 유명한 JQuery 프레임워크 내에서 사용됩니다.
LRU: 이중 연결 목록 구조. 새로운 데이터가 저장될 때마다 데이터에 액세스할 때마다 연결 목록의 선두에 직접 배치됩니다. 이런 식으로 연결된 목록의 끝에 있는 데이터는 가장 최근의 데이터이며 사용되지 않은 데이터는 제거됩니다.
TwoQueues: FIFO LRU, FIFO는 주로 처음 저장된 데이터를 저장하고, LRU는 적어도 두 번 사용된 핫스팟 데이터를 저장합니다. 이 알고리즘은 적중률이 높고 적응성이 강하며 복잡도가 낮습니다.
그 밖에도 수많은 제거 알고리즘이 있지만 이 두 가지가 가장 일반적으로 사용되는 알고리즘입니다. 알고리즘이 복잡하지 않고 구현하기 쉽고 실행 효율성이 높으며 대부분의 상황에서 캐시 적중률이 허용 가능하기 때문입니다. 결국 캐싱 알고리즘도 CPU를 소모하게 되므로, 너무 복잡하면 적중률은 향상되지만 이득은 손실을 감수할 가치가 없습니다. 캐시에서 데이터를 검색하는 것이 원래 위치에서 검색하는 것보다 시간이 더 많이 걸린다면 캐싱을 어떻게 사용할까요?
구체적인 이론에 대해서는 자세히 다루지 않겠습니다. 인터넷에 많은 것들이 있지만 실제로는 이해가 되지 않습니다. 오늘 여러분과 공유하고 싶은 것은 TwoQueues 캐싱 모델의 JavaScript 버전입니다.
먼저 사용법부터 말씀드리자면, 아주 간단합니다.
기본 사용법은 다음과 같습니다.
[/코드]
var tq = initTwoQueues(10);
tq.set("키", "값");
tq.get("키");
[/코드]
초기화시 캐시 용량만 지정해주세요. FIFO LRU의 내부 구현으로 인해 실제 용량은 지정된 용량의 두 배입니다. 위 예에서는 10(키-값 쌍)을 지정하지만 실제로는 20개까지 저장할 수 있습니다.
용량 크기는 실제 적용 시나리오에 따라 결정해야 합니다. 너무 작으면 적중률이 낮고, 너무 크면 효율성이 낮습니다.
개발 과정에서 캐시 효과를 검토하기 위해 캐시 풀을 개발 버전으로 초기화할 수 있습니다.
var tq = initTwoQueues(10, true);
tq.hitRatio();
마지막에 매개변수를 추가하고 true로 만드세요. 이렇게 초기화된 캐시풀은 자동으로 적중률을 계산하게 되는데, 적중률은 hitRatio 메소드를 통해 구할 수 있다. 이 매개변수를 추가하지 않으면 hitRatio 메소드로 얻은 적중률은 항상 0이 됩니다.
통계 적중률은 당연히 리소스를 소모하므로 프로덕션 환경에서는 활성화하지 않는 것이 좋습니다.
코드를 공유할 시간:
(함수(내보내기){
/**
* 상속을 위한 순수 클래스
* @constructor
*/
함수 Fn(){}
Fn.prototype = Elimination.prototype;
/**
* * 연결리스트 기반 캐시 제거 알고리즘 상위 클래스
* @param maxLength 캐시 용량
* @constructor
*/
함수 제거(maxLength){
this.container = {};
this.length = 0;
this.maxLength = 최대 길이 || 30;
this.linkHead = this.buildNode("", "");
this.linkHead.head = true;
this.linkTail = this.buildNode("", "");
this.linkTail.tail = true;
this.linkHead.next = this.linkTail;
this.linkTail.prev = this.linkHead;
}
Elimination.prototype.get = function(key){
throw new Error("이 메서드는 재정의되어야 합니다!");
};
Elimination.prototype.set = 함수(키, 값){
throw new Error("이 메서드는 재정의되어야 합니다!");
};
/**
*연결리스트에 노드 생성
* @param data 노드에 포함된 데이터, 즉 캐시된 데이터 값
* @param key 노드의 고유 식별자, 즉 캐시된 키
* @returns {{}}
*/
Elimination.prototype.buildNode = 함수(데이터, 키){
var 노드 = {};
node.data = 데이터;
node.key = 키;
node.use = 0;
노드 반환;
};
/**
* 연결리스트의 선두에서 노드를 팝하세요
* @returns {*}
*/
Elimination.prototype.shift = function(){
var 노드 = null;
if(!this.linkHead.next.tail){
노드 = this.linkHead.next;
this.linkHead.next = node.next;
node.next.prev = this.linkHead;
this.container[node.key]를 삭제하세요.
this.length--;
}
노드 반환;
};
/**
* 연결리스트의 선두부터 노드 삽입
* @param node 노드 객체
* @returns {*}
*/
Elimination.prototype.unshift = 기능(노드){
node.next = this.linkHead.next;
this.linkHead.next.prev = 노드;
this.linkHead.next = 노드;
node.prev = this.linkHead;
this.container[node.key] = 노드;
this.length ;
노드 반환;
};
/**
* 从链表尾插入一个节点
* @param 노드 节点对象
* @returns {*}
*/
Elimination.prototype.append = 함수(노드){
this.linkTail.prev.next = 노드;
node.prev = this.linkTail.prev;
node.next = this.linkTail;
this.linkTail.prev = 노드;
this.container[node.key] = 노드;
this.length ;
노드 반환;
};
/**
* 연결리스트 끝에서 노드를 팝하세요
* @returns {*}
*/
Elimination.prototype.pop = function(){
var 노드 = null;
if(!this.linkTail.prev.head){
노드 = this.linkTail.prev;
node.prev.next = this.linkTail;
this.linkTail.prev = node.prev;
this.container[node.key]를 삭제하세요.
this.length--;
}
노드 반환;
};
/**
*연결된 목록에서 지정된 노드를 제거
* @param node 노드 객체
* @returns {*}
*/
Elimination.prototype.remove = 기능(노드){
node.prev.next = node.next;
node.next.prev = node.prev;
this.container[node.key]를 삭제하세요.
this.length--;
노드 반환;
};
/**
* 노드에 접근할 때 해야 할 처리, 구체적으로 노드를 연결리스트의 선두로 이동시키는 작업
* @param 노드
*/
Elimination.prototype.use = 기능(노드){
this.remove(노드);
this.unshift(노드);
};
/**
* LRU 캐시 제거 알고리즘 구현
* @constructor
*/
함수 LRU(){
Elimination.apply(this, 인수);
}
LRU.prototype = new Fn();
LRU.prototype.get = 함수(키){
var 노드 = 정의되지 않음;
노드 = this.container[key];
if(노드){
this.use(노드);
}
노드 반환;
};
LRU.prototype.set = 함수(키, 값){
var node = this.buildNode(value, key);
if(this.length === this.maxLength){
this.pop();
}
this.unshift(노드);
};
/**
* FIFO 캐시 제거 알고리즘 구현
* @constructor
*/
함수 FIFO(){
Elimination.apply(this, 인수);
}
FIFO.prototype = new Fn();
FIFO.prototype.get = 함수(키){
var 노드 = 정의되지 않음;
노드 = this.container[key];
노드 반환;
};
FIFO.prototype.set = 함수(키, 값){
var node = this.buildNode(value, key);
if(this.length === this.maxLength){
this.shift();
}
this.append(노드);
};
/**
* LRU 및 FIFO 알고리즘 캡슐화, 새로운 2큐 캐시 제거 알고리즘이 됨
* @param maxLength
* @constructor
*/
함수 에이전트(maxLength){
this.getCount = 0;
this.hitCount = 0;
this.lir = 새로운 FIFO(maxLength);
this.hir = 새로운 LRU(maxLength);
}
Agent.prototype.get = 기능(키){
var 노드 = 정의되지 않음;
노드 = this.lir.get(key);
if(노드){
노드.사용 ;
if(node.use >= 2){
this.lir.remove(노드);
this.hir.set(node.key, node.data);
}
}그밖에{
노드 = this.hir.get(key);
}
노드 반환;
};
Agent.prototype.getx = 기능(키){
var 노드 = 정의되지 않음;
this.getCount ;
노드 = this.get(key);
if(노드){
this.hitCount ;
}
노드 반환;
};
Agent.prototype.set = 함수(키, 값){
var 노드 = null;
노드 = this.lir.container[키] || this.hir.container[키];
if(노드){
node.data = 값;
}그밖에{
this.lir.set(키, 값);
}
};
/**
* 적중률 확인
* @returns {*}
*/
Agent.prototype.hitRatio = function(){
var ret = this.getCount;
if(ret){
ret = this.hitCount / this.getCount;
}
리트윗;
};
/**
* 외전
* @param maxLength 캐시 용량
* @param dev 개발 환경이든 개발 환경에서는 적중률을 계산하고, 그렇지 않으면 계산하지 않습니다
* @returns {{get, set: 함수, hitRatio: 함수}}
*/
내보내기.initTwoQueues = 함수(maxLength, dev){
var api = new Agent(maxLength);
반품 {
get: (function(){
만약(개발자){
반환함수(키){
var ret = api.getx(key);
ret && ret.data 반환;
};
}else{
반환함수(키){
var ret = api.get(key);
ret && ret.data 반환;
};
}
}()),
설정: 함수(){
api.set.apply(api, 인수);
},
hitRatio: 함수(){
return api.hitRatio.apply(api, 인수);
}
};
};
}(이));
마지막으로 캐싱 알고리즘은 실제 적용 시나리오와 결합되어야 한다는 점을 다시 한 번 말씀드리고 싶습니다. 보편적인 알고리즘은 없으며 적절한 것이 가장 좋습니다!