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();
}
teamsmiley's profile image

teamsmiley

2018-08-12 00:00

Read more posts by this author