redis를 설치하고 web api c# 에서 사용한다.
설치
vi docker-compose.yml
---
version: "3.3"
services:
redis:
image: redis:4.0.11
ports:
- "6379:6379"
command: ["redis-server"]
혹시 비밀번호를 설정하려면 다음처럼
command: ["redis-server","--requirepass","password"]
docker stack deploy -c docker-compose.yml redis
클라이언트에서 서버에 접속해보자.
윈도우즈 클라이언트 다운로드 https://github.com/dmajkic/redis/downloads
redis-cli -h docker01.yourdomain.com
접속 확인 완료
c#에서 사용
사용전 알아야할것
기존에는 컨트롤러에서 디비를 가져와서 json으로 만들어서 프론트앤드에 보내준다.
이제 레디스에 데이터가 있는지 먼저 체크하여 있으면 json 만들어서 보내고 없으면 디비에서 가져와서 레디스에 넣고 그 결과값을 클라이언트에게 보낸다. 캐싱만 하고 그 데이터를 클라이언트에게보낸다.
데이터 목록을 보낼경우 리스트를 캐싱해야한다. 그리고 함께 페이징 정보도 캐싱해야한다.
캐싱을 하려면 키값이 잇어야하는데 쿼리스트링까지 다 넣어서 키를 만들어야한다.
캐싱 키 정리
기능 | key | 예제값 |
---|---|---|
글 리스트 | REDIS_KEY + “:” + boardName + “:” + param | documents:blogs:CurrentPage=1/PageSize=9/ |
글 페이징 | REDIS_KEY + “:” + boardName + “:” + param + “:pagination” | documents:blogs:CurrentPage=1/PageSize=9/:pagination |
글 | REDIS_KEY + “:” + boardName + “:” + id + “:” + fields | “documents:blogs:d282dd96:aaa=aaa” |
글 삭제시 | REDIS_KEY + “:” + boardName + “:” + id | documents:blogs:d282dd96 |
새 글 쓸때 | REDIS_KEY + “:” + boardName | “documents:blogs” |
글 업데이트 | REDIS_KEY + “:” + boardName + “:” + id | documents:blogs:d282dd96 |
글 패치 | REDIS_KEY + “:” + boardName + “:” + id | documents:blogs:d282dd96 |
캐싱을 키로 지울때 XXX*로 다 지워질수 있게 설계가 되야한다.
누겟 설치
dotnet add package StackExchange.Redis.Extensions.Newtonsoft
코드 수정
- appsetting.json
"Redis": { //"Password": "my_super_secret_password", //"AllowAdmin": true, "Ssl": false, // "ConnectTimeout": 6000, // "ConnectRetry": 2, "Database": 0, "Hosts": [ { "Host": "docker01.yourdomain.com", "Port": "6379" } ] }
- startup.cs
public void ConfigureServices(IServiceCollection services) { var redisConfiguration = Configuration.GetSection("Redis").Get<RedisConfiguration>(); services.AddSingleton(redisConfiguration); services.AddSingleton<ICacheClient, StackExchangeRedisCacheClient>(); services.AddSingleton<ISerializer, NewtonsoftSerializer>();
- BaseResourceParameters.cs
public override string ToString() { StringBuilder sb = new StringBuilder(); foreach (var property in this.GetType().GetProperties()) { sb.Append(property.Name + "=" + property.GetValue(this, null) + "/"); } return sb.ToString(); }
모든 쿼리스트링의 Base클래스이다. 쿼리스트링을 받아서 tostring으로 찍어서 이걸 키값으로 사용하려고 만듬.
이제 컨트롤러에서 작업이 들어오면 캐싱이 있는지 체크하고 잇으면 리턴하자. 없으면 데이터 리턴 직전에 캐싱에 넣자.
- BoardDocumentsController.cs
private readonly string REDIS_KEY = "documents"; //key를 만든다. private readonly ICacheClient _cache; public BoardDocumentsController(..., ICacheClient cache){ _cache = cache; } ...
리스트를 리턴하자
페이징도 하고 결과값도 하고 두가지 캐싱을 해야한다.
...
private IActionResult GetSpecificDocuments<T>(string boardName, BoardDocumentsResourceParameters param) where T : class
{
var settings = new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() };
var key = REDIS_KEY + ":" + boardName + ":" + param; //key를 계속 구성중
var paginationKey = key + ":pagination"; //pagination key를 기존키에 pagination을 추가해서 만든다.
var cacheResponse = _cache.Get<IEnumerable<T>>(key); //캐싱에서 가져와서 response를 만든다.
var cacheHeader = _cache.Get<PaginationMetadata>(paginationKey); //캐싱에서 가져와서 헤더를 만든다.
if (cacheResponse != null) // response가 있으면
{
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(cacheHeader, settings)); //paging까지 붙여서
return Ok(cacheResponse); // response를 리턴하고 끝낸다. 없으면 아래처럼 한다.
}
...
//기존 로직대로 하고
_cache.Add(key, result, DateTimeOffset.Now.AddDays(2)); //내용 캐싱을 한다.
_cache.Add(key + ":pagination", paginationMetadata, DateTimeOffset.Now.AddDays(2)); //페이징을 캐싱한다.
return Ok(result);
pagination 이 json이 대문자로 나오는경우가 있어서 다음부분이 추가가 되었다.
헤더에 넣기때문에 객체를 던질수 없어서 startup.cs 에 설정부분이 동작하지 않는다. 그래서 이렇게 강제로 코딩해줘야함.
var settings = new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() };
...
Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(cacheHeader, settings));
글을 하나 가져오는 것을 캐싱하자.
쿼리스트링을 다 같이 키로 해서 레디스에 넣는다. 캐싱이 없으면 디비에서 가져와서 캐싱한다.
private IActionResult GetDocumentSpecific<T>(string boardName, Guid id, string fields) where T : class
{
var key = REDIS_KEY + ":" + boardName + ":" + id + ":" + fields;
var cacheResponse = _cache.Get<T>(key);
if (cacheResponse != null)
{
return Ok(cacheResponse);
}
...
var result = Mapper.Map<T>(document);
_cache.Add(key, result, DateTimeOffset.Now.AddDays(2));
return Ok(result.ShapeData(fields));
}
글을 지우거나 새글을 쓰면 캐싱에서 삭제되야 한다
일단 키로 시작하는 모든 캐시를 지우는 함수를 만든다.
private void RedisRemoveKey(string key)
{
var foundKeys = _cache.SearchKeys(key + "*");
foreach (var akey in foundKeys)
{
_cache.Remove(akey);
}
}
이제 글을 지우면 리스트도 지워져야하고 게시물자체도 지워져야한다 그러므로 특정 키로 시작하는 모든것을 지우면된다. 기존코드 리턴 직전에 RedisRemoveKey 함수를 추가하면 된다.
private IActionResult CreateSpecificPosts<T>(String boardName, T vm) where T : class
{
...
var Return = Mapper.Map<BoardDocumentVM>(Post);
RedisRemoveKey(REDIS_KEY + ":" + boardName); //추가 코드
return CreatedAtAction("GetBoardDocument", new { BoardId = board.Id, id = Post.Id }, Return);
}
public IActionResult DeleteBoardDocument(String boardName, Guid id)
{
RedisRemoveKey(REDIS_KEY + ":" + boardName);//삭제시 캐시를 비운다.
}
public IActionResult UpdateBoardDocument(String boardName, Guid id, [FromBody] BoardDocumentForUpdateVM vm)
{
...
RedisRemoveKey(REDIS_KEY + ":" + boardName + ":" + id); //업데이트 직후에 캐시 삭제
var DocumentToReturn = Mapper.Map<BoardDocumentVM>(BoardDocument);
return CreatedAtAction("GetBoardDocument", new { BoardId = board.Id, id = DocumentToReturn.Id }, DocumentToReturn);
}
public IActionResult PatchBoardDocument(String boardName, Guid id, [FromBody] JsonPatchDocument<BoardDocumentForUpdateVM> patchDoc)
{
...
RedisRemoveKey(REDIS_KEY + ":" + boardName + ":" + id); //patch도 지운다.
return NoContent();
}