페이지 세 션 캐 시(1)

37050 단어 ASP.NET캐 시
일반적으로 페이지 는 여러 부분 으로 나 뉘 는데,다른 부분 은 업데이트 빈도 가 다르다.전체 페이지 에 통 일 된 캐 시 정책 을 사용 하면 적합 하지 않 습 니 다.
그리고 많은 시스템 의 페이지 왼쪽 상단 에 빌어먹을'Welcome XXX'가 있다.사용자 에 게 특정한 정 보 는 캐 시 할 수 없습니다.이러한 상황 에 대해 우 리 는 세 션 캐 시 를 사용 해 야 한다.페이지 의 서로 다른 부분(세 션)에 서로 다른 캐 시 정책 을 가 하고 세 션 캐 시 를 사용 하려 면 먼저 페이지 를 구분 해 야 합 니 다.촌 스 러 운 방법 은 iframe 을 사용 하여 iframe 으로 페이지 를 한 조각 으로 나 눌 수 있 지만,나 는 항상 iframe 이 사악 한 것 이 라 고 생각한다.좋 은 방법 은 Ajax 가 이 세 션 의 내용 을 따로 요청 한 다음 에 채 울 수 있어 서 보기 좋 습 니 다.하지만 Ajax 를 사용 하 는 데 도 제한 이 있 습 니 다.
1.만약 에 페이지 에 많은 부분 이 있 고 이런 기술 을 너무 많이 사용 하면 서버 에 보 내 는 요청 이 많 을 것 입 니 다.HTTP 는 같은 도 메 인 이름 에 연결 하 는 제한 이 있어 서 동시 연결 의 효율 을 낮 출 수 있 습 니 다.
2.첫 번 째 문제 가 문제 가 아니라면 사용자 체험 에 우호 적 이지 않 을 수도 있 습 니 다.예 를 들 어 한 세 션 이 천천히 응답 하여 페이지 가 반 짝 일 수 있 습 니 다.그러나 앞의 두 가 지 를 모두 극복 할 수 있다 면 이 방안 은 그래도 괜찮다.가 증 스 러 운 것 은 대부분의 사용자 가 자 바스 크 립 트 를 사용 하지 않 는 환경 에 있다 는 것 입 니 다.좋아,이 방안 도 사용 할 수 없어.만약 우리 가 세 션 캐 시 에 관 한 일련의 시 도 를 했다 면:
우리 시스템 은 Spring+Hibernate+Oracle 기술 을 사용 하고 템 플 릿 엔진 은 Apache Velocity 를 사용 합 니 다.다음 세 션 이 우리 가 캐 시 할 내용 이 라 고 가정 합 니 다.
   
   
   
   
< ul >
#foreach($book in $books)
< li >< a href ="/book/books/$book.id" > $book.name a > --- < a href ="/book/books/edit/$book.id" > Edit a > -- < a href ="/book/books/delete/$book.id" > Delete a > li >
#end
ul >

도서 목록 을 표시 합 니 다.이 페이지 를 변경 하 는 가장 작은 방법 은 탭 을 추가 하 는 것 입 니 다.이 탭 에 둘러싸 인 세 션 은 캐 시 입 니 다.
     
     
     
     
#cache
< ul >
#foreach($book in $books)
< li >< a href ="/book/books/$book.id" > $book.name a > --- < a href ="/book/books/edit/$book.id" > Edit a > -- < a href ="/book/books/delete/$book.id" > Delete a > li >
#end
ul >
#end

한 페이지 에 많은 세 션 이 있 을 수 있 기 때문에 서로 다른 세 션 은 반드시 다른 cache key 를 사용 해 야 하기 때문에 이 탭 은 cache key 에 도 들 어 갈 수 있 을 것 입 니 다.이 페이지 를 보 여 줍 니 다.이 탭 을 분석 할 때 이 cache key 로 캐 시 에서 가 져 옵 니 다.가 져 오 면 캐 시 된 것 을 출력 합 니 다.
더 이상 이 도서 목록 을 해석 할 필요 가 없다.
이런 생각 이 들 면 우 리 는 Velocity 가 우리 의 라벨 을 해석 하 는 방법 을 찾 아야 한다.좋 습 니 다.Velocity 는 사용자 정의 탭 을 지원 합 니 다.
   
   
   
   
public class Cache extends Directive {
@Override
public String getName() {
return " cache " ;
}

@Override
public int getType() {
return BLOCK;
}
@Override
public boolean render(InternalContextAdapter context, Writer writer, Node node)
throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
Node keyNode
= node.jjtGetChild( 0 );

String cacheKey
= (String) keyNode.value(context);
String cacheHtml
= cacheHtml = (String) CacheManager.getInstance(). get (cacheKey);
if (StringUtils.isEmpty(cacheHtml)) {
Node bodyNode
= node.jjtGetChild( 1 );
Writer tempWriter
= new StringWriter();
bodyNode.render(context, tempWriter);
cacheHtml
= tempWriter.toString();
CacheManager.getInstance().
set (cacheKey, cacheHtml);
}
writer.write(cacheHtml);
return true ;
}
}

Velocity 의 사용자 정의 탭 사용 에 대해 서 는 뒤에서 설명 하 겠 습 니 다.
가장 중요 한 논 리 는 render 방법 에서 cache key 에 따라 캐 시 에서 찾 습 니 다.코드 render 를 사용 하지 않 으 면 render 의 결 과 를 캐 시 에 넣 습 니 다.전형 적 인 캐 시 사용 장면 이 죠?컨트롤 러 엔 드 코드 다시 보기:
   
   
   
   
@Controller
@RequestMapping(
" /books " )
public class BookController {
private BookDAO bookDAO;
@Autowired
public BookController(BookDAO bookDAO) {
this .bookDAO = bookDAO;
}

@RequestMapping(value
= { "" , " index.html " }, method = RequestMethod.GET)
public ModelAndView index() {
return new ModelAndView( " list " , " books " , bookDAO.findAll());
}
}

컨트롤 러 는 간단 합 니 다.DAO 를 호출 하여 모든 도 서 를 열거 하면 됩 니 다.우리 가 이렇게 까다 로 운 문제 가 해결 되 어 기뻐 할 때 문제 가 생 겼 다.
우리 의 캐 시 는 무엇 을 위 한 것 입 니까?Velocity 해석 시간 을 절약 하기 위해 서가 아니 잖 아 요.가장 중요 한 것 은 이번 bookdao.findAll()이 데이터 베 이 스 를 조회 하 는 시간 을 절약 하기 위 한 것 이 라 고 생각 합 니 다.하지만 고 개 를 돌려 우리 의 방안 을 살 펴 보 자.우리 의 cache 명 이 명중 되 지 않 았 든 간 에 이 bookDAO.findAll()은 한 번 실 행 됩 니 다.컨트롤 러 의 실행 은 보기 render 전에 발생 했 기 때 문 입 니 다.우리 가 유일 하 게 절약 하 는 것 은 Velocity 가 분석 하 는 시간,컵 기구 이다.
문제 의 답 을 찾 으 면 해결 방법 을 찾기 가 쉽다.우리 가 해 야 할 일 은 캐 시가 명중 되 지 않 았 을 때 만 조 회 를 실행 하 는 것 입 니 다.그러면 이 데이터 조 회 는 반드시 cache 태그 내부 에 넣 어야 합 니 다.그러나 우리 의 cache 라벨 은 한 세 션 을 위 한 것 이 아 닙 니 다.많은 세 션 이 있 지만 여러 세 션 에서 데 이 터 를 얻 는 방식 은 다 릅 니 다.
응,너 인터페이스 기억 나?컴퓨터 안의 모든 문 제 를 중간층 을 통 해 해결 할 수 있다 는 명언 을 기억 하 십 니까?이 사고방식 에 따라 우 리 는 이렇게 cache 라벨 을 설계 합 니 다.
   
   
   
   
#cache("book_list",$dataProvider)
< ul >
#foreach($book in $books)
< li >< a href ="/book/books/$book.id" > $book.name a > --- < a href ="/book/books/edit/$book.id" > Edit a > -- < a href ="/book/books/delete/$book.id" > Delete a > li >
#end
ul >
#end

데이터 프로 바 이 더 대상 이 들 어 왔 습 니 다.이 데이터 프로 바 이 더 는 컨트롤 러 에 들 어 왔 습 니 다.데 이 터 를 가 져 오 는 데 사 용 됩 니 다.
   
   
   
   
public class BookController {

private DataProvider dataProvider;
@Autowired
public BookController(BookDataProvider dataProvider) {
this .dataProvider = dataProvider;
}

@RequestMapping(value
= { "" , " index.html " }, method = RequestMethod.GET)
public ModelAndView index() {
return new ModelAndView( " list " , " dataProvider " , dataProvider);
}
}

컨트롤 러 는 여전히 간단 합 니 다.cache 태그 의 실현 을 살 펴 보 겠 습 니 다.
   
   
   
   
public class Cache extends Directive {
@Override
public String getName() {
return " cache " ;
}

@Override
public int getType() {
return BLOCK;
}

@Override
public boolean render(InternalContextAdapter context, Writer writer, Node node)
throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
Node keyNode
= node.jjtGetChild( 0 );

String cacheKey
= (String) keyNode.value(context);
String cacheHtml
= cacheHtml = (String) CacheManager.getInstance(). get (cacheKey);
if (StringUtils.isEmpty(cacheHtml)) {
Node dataProviderNode
= node.jjtGetChild( 1 );
DataProvider dataProvider
= (DataProvider) dataProviderNode.value(context);
Map
< String, Object > map = dataProvider.load();
for (String key : map.keySet()) {
context.put(key, map.
get (key));
}

Node bodyNode
= node.jjtGetChild( 3 );
Writer tempWriter
= new StringWriter();
bodyNode.render(context, tempWriter);
cacheHtml
= tempWriter.toString();
CacheManager.getInstance().
set (cacheKey, cacheHtml);
}
writer.write(cacheHtml);
return true ;
}
}

우 리 는 탭 내부 에서 외부 에서 들 어 오 는 dataProvider 를 가 져 와 인터페이스 DataProvider 를 실현 한 다음 탭 내부 에서 데 이 터 를 조회 합 니 다.
그리고 조 회 된 데 이 터 를 velocity 의 context 에 넣 고 render 를 다시 해서 render 의 결 과 를 캐 시 에 넣 습 니 다.이제 됐 습 니 다.컨트롤 러 에 서 는 데 이 터 를 가 져 올 수 있 는 대상 만 보기 로 전달 하면 됩 니 다.cache 태그 내부 에서 판단 합 니 다.DataProvider 와 BookDataProvider 의 코드:
   
   
   
   
public interface DataProvider {
Map
< String,Object > load();
}
@Service
public class BooksDataProvider implements DataProvider{
private BookDAO bookDAO;

@Autowired
public BooksDataProvider(BookDAO bookDAO){
this .bookDAO = bookDAO;
}
public Map < String, Object > load() {
Map
< String,Object > result = new HashMap < String,Object > ();
result.put(
" books " ,bookDAO.findAll());
return result;
}
}

지금 우리 가 개조 하고 자 하 는 것 은 모든 캐 시 세 션 에 대해 DataProvider 의 실현 을 쓰 는 것 입 니 다.실제로 이 실현 은 이미 있 었 습 니 다.
원래 컨트롤 러 에 있 는 코드 입 니 다.예 를 들 어 Books DataProvider 는 실제 적 으로 원래 BookController 안의 코드 이다.이런 방식 을 통 해 우 리 는 기본적으로 한 페이지 를 cache 로 둘러싸 인 작은 부분 으로 나 눌 수 있 습 니 다.구분 만 하면 서로 다 를 수 있 습 니 다.
세 션 은 서로 다른 캐 시 정책 을 사용 합 니 다.코드 의 구 조 는 그런대로 뚜렷 한 편 이다.
다음은 제 가 Velocity 사용자 정의 탭 의 사용 을 살짝 소개 하 겠 습 니 다.
Velocity 사용자 정의 탭
모든 사용자 정의 탭 은 Directive 에서 파생 되 며,우 리 는 몇 가지 방법 을 덮어 써 야 합 니 다.
1.getName,문자열 을 되 돌려 줍 니 다.이것 이 바로 velocity 템 플 릿 에서 사용 하 는 태그 의 이름 입 니 다:\#cache.
2.태그 의 유형,우리 의 cache 와 같은\#cache...\#end 를 블록 급 태그 라 고 부 르 면 돌아 오 는 것 은 BLOCK(상수 1)입 니 다.또 하나의 유형 은 라인(2)이다.그러면 그\#end 가 없다.
3.render 방법,이것 이 가장 중요 합 니 다.행동 을 덮어 쓸 수 있 습 니 다.
외부 에서 들 어 오 는 인자 가 져 오기
그렇다면 탭 내부 에서 외부 에서 들 어 오 는 인 자 를 어떻게 얻 습 니까?사실 그것 의 행동 과 xml path 의 조작 방식 은 차이 가 많 지 않다.render 방법의 매개 변수 에 Node 가 있 습 니 다.이것 이 바로 태그 자체 입 니 다.우 리 는 node.jjjtGetChild(index)를 통 해 각종 인 자 를 얻 을 수 있 습 니 다.0 으로 시작 합 니 다.BLOCK 형식의 태그 라면 마지막 으로 태그 가 둘러싸 인 내용 입 니 다(예 를 들 어 우리 의 cache 태그).그리고 우 리 는 node 의 value 를 통 해 매개 변수의 값 을 얻 을 수 있 습 니 다.값 을 추출 할 때 context 를 들 여 보 냈 다 는 것 은 이 값 이 계산 가능 하 다 는 것 을 의미한다.예 를 들 어 현재 이러한 수요 가 있 습 니 다.우리 의 도서 목록 은 페이지 로 나 뉘 지만 첫 페이지 만 캐 시 하고 뒤의 것 은 캐 시 하지 않 습 니 다.그럼 저희 가 들 어 오 기 를 바 라 겠 습 니 다.
하나의 표현 식 은 cache 태그 가 스스로 계산 하도록 합 니 다.이 표현 식 이 true 라면 캐 시 합 니 다.그렇지 않 으 면 캐 시 하지 않 습 니 다.
     
     
     
     
#cache( " book_list " ,$pageIndex == 1 ,$dataProvider)
...
#end

그럼 cache 태그 내 부 는 요?
   
   
   
   
Node needCacheNode = node.jjtGetChild( 1 );
Boolean needCache
= (Boolean) needCacheNode.value(context);

String cacheHtml
= StringUtils.EMPTY;
if (needCache) {
cacheHtml
= (tring) CacheManager.getInstance(). get (cacheKey);
}

이렇게 하면$pageIndex==1 이 표현 식 의 결 과 를 계산 할 수 있 습 니 다.(물론$pageIndex 라 는 변 수 는 들 어 와 야 합 니 다.)
자,사용자 정의 탭 코드 를 작성 해서 사용 할 수 있 습 니 다.우리 가 이 코드 를 다 써 서 거기에 잃 어 버 리 면 사용 할 수 있 는 것 이 아니 라 설정 부분 이 필요 합 니 다.
   
   
   
   
< bean class ="org.springframework.web.servlet.view.velocity.VelocityConfigurer" >
< property name ="resourceLoaderPath" value ="/WEB-INF/templates/" />
< property name ="velocityProperties" >
< props >
< prop key ="userdirective" > com.yuyijq.web.Cache prop >
props >
property >
bean >

자,이렇게 Velocity 를 이용 한 세 션 캐 시 를 완성 합 니 다.그러나 이런 방식 에 도 문제 가 있어 서 우리 에 게 bug 를 가 져 왔 다.
1.가끔 우 리 는 세 션 에서 변 수 를 설정 한 다음 에 이 세 션 밖에서 사용 합 니 다.하지만 지금 은 cache 를 사용 하고 cache 다음은 HTML 입 니 다.
텍스트,이 변수 들 이 모두 사 라 졌 습 니 다.그러면 세 션 밖에서 도 이 변수의 값 을 찾 을 수 없습니다.그러면 우 리 는 Velocity 템 플 릿 을 자세히 검색 하여 이 변수의 사용 을 모두 세 션 내부 로 이동 시 켜 야 합 니 다.만약 처음부터 이 세 션 의 캐 시 를 설계 했다 면,우 리 는 이 문 제 를 주의 할 수 있 습 니 다.문 제 는 현재 프로젝트 의 중간 에 제 기 된 것 입 니 다.시스템 에 있 는 velocity 템 플 릿 이 수천 만 개 에 달 합 니 다.우 리 는 캐 시 한 부분 을 자세히 검사 해 야 합 니 다.그리고 Velocity 템 플 릿 은 테스트 가 없습니다.(테스트 를 쓸 수 있 지만 귀 찮 습 니 다)이 과정 은 모두 인 육 에 의존 하기 때문에 bug 가 나 올 확률 이 높다.
2.똑 같 습 니 다.변경 이 비교적 크 고 템 플 릿 뿐만 아니 라 뒤의 컨트롤 러 도 수정 해 야 합 니 다.하지만 더 좋 은 방법 은 없 을 것 같 습 니 다.세 션 캐 시 를 사용 하 는 것 은 피 할 수 없 을 것 같 습 니 다.
성숙 한 방안
세 션 캐 시 는 대형 시스템 에서 자주 사용 되 어야 합 니 다.그러면 성숙 한 방안 이 있어 야 합 니 다.우 리 는 왜 그런 성숙 한 방안 을 찾 지 않 고 바퀴 를 다시 만들어 야 합 니까?이 방안 은 바로 ESI 입 니 다.다음 글 에서 Varnish 와 ESI 를 결합 하여 세 션 캐 시 를 만 드 는 것 을 소개 하 겠 습 니 다.
페이지 세 션 캐 시(2)

좋은 웹페이지 즐겨찾기