Django + memcached + 네임스페이스

17035 단어 pythonmemcacheddjango

소개



memcached 사용의 단점 중 하나는 와일드카드를 사용하여 키를 삭제할 수 없다는 것입니다. Django에서는 많은 키를 삭제할 수 있지만 삭제할 키 목록을 제공해야 합니다.

이 문제를 해결하는 방법은 네임스페이스를 사용하는 것입니다. 네임스페이스별로 키를 구성합니다. 예를 들어 판매하는 제품과 관련된 모든 키는 네임스페이스 제품에 속합니다. 그리고 클라이언트는 네임스페이스 클라이언트에 있습니다. 네임스페이스를 삭제하면 네임스페이스 내의 모든 키도 삭제됩니다. 불행하게도 우리는 memcached에서 네임스페이스를 지원하지 않는다는 또 다른 걸림돌에 부딪혔습니다.

이 문서에 제시된 코드를 사용하여 네임스페이스를 속이고 네임스페이스의 모든 키를 "삭제"할 수 있습니다.

TL;DR 코드를 확인하세요.

어떻게 해야 할까요?



memcached에 데이터를 저장할 때 키가 필요합니다. 우리가 할 일은 이 키 앞에 우리가 사용하려는 네임스페이스를 추가하는 것입니다. 키 기타의 경우 이 제품: 기타와 같은 키를 앞에 추가합니다.

기다리다! 네임스페이스 제품의 모든 키를 어떻게 무효화합니까? product를 접두사로 사용하는 대신 네임스페이스를 값과 함께 memcached에 저장합니다. 해당 값을 실제 네임스페이스로 사용합니다.

memcached의 네임스페이스 키는 namespace:product이며 값 1을 저장합니다. "하, 하지만 다른 네임스페이스와 충돌이 발생합니다"라고 말하는 것을 들었습니다. 귀하의 말이 맞으므로 이 product1과 같은 네임스페이스 자체를 이 값 앞에 추가하겠습니다. 이제 기타 키는 이 product1:guitar처럼 보일 것입니다.
product 값의 값을 product2로 변경하면 기타 키는 product2:guitar가 됩니다. 이제 네임스페이스 product1의 모든 키에 더 이상 연결할 수 없으며 memcached가 키를 제거합니다.

암호



Django 내에서 사용할 클래스에 이 모든 것을 래핑

# code/cache.py

import time

import xxhash
from django.core.cache import cache as django_cache


IS_DEVELOPMENT = False
CACHE_PREFIX = "MyApp"
HOUR = 3600
DAY = HOUR * 24


class MyCache:
    """
    This class is used to create a cache for MyApp.
    """

    def __init__(self, timeout=DAY):
        """
        Initialize the cache.
        """
        self.timeout = timeout if not IS_DEVELOPMENT else 20

    def __str__(self):
        """
        Return a string representation of the cache.
        """
        return "MyCache"

    def get(self, namespace, key):
        """
        Get a value from the cache.

        Parameters
        ----------
        namespace: str
        key: str
        """
        try:
            cache_key = self.safe_cache_key(namespace=namespace, key=key)
            return django_cache.get(cache_key)
        except Exception:
            return None

    def set(self, namespace, key, value, timeout=None):
        """
        Set a value in the cache.

        Parameters
        ----------
        namespace: str
        key: str
        value: str|int|dict|list
        timeout: int or None
        """
        try:
            cache_key = self.safe_cache_key(namespace=namespace, key=key)
            timeout = timeout or self.timeout
            django_cache.set(cache_key, value, timeout)
        except Exception:
            pass

    def delete(self, namespace, key):
        """
        Delete a value from the cache.

        Parameters
        ----------
        namespace: str
        key: str
        """
        try:
            cache_key = self.safe_cache_key(namespace=namespace, key=key)
            django_cache.delete(cache_key)
        except Exception:
            pass

    def delete_namespace(self, namespace):
        """
        Delete the namespace

        Parameters
        ----------
        namespace:str
        """
        self.update_cache_namespace(namespace=namespace)

    def safe_cache_key(self, namespace, key):
        """
        Create a key that is safe to use in memcached

        Parameters
        ----------
        namespace: str
        value: str

        Returns
        -------
        str
        """

        namespace = self.get_namespace(namespace=namespace)
        new_key = "{}:{}:{}".format(
            CACHE_PREFIX, namespace, xxhash.xxh3_64_hexdigest(key)
        )
        return new_key

    def get_namespace(self, namespace):
        """
        Get the namespace value for the given namespace

        Parameters
        ----------
        namespace: str

        Returns
        -------
        str
        """
        key = self.get_namespace_key(namespace=namespace)
        rv = django_cache.get(key)
        if rv is None:
            value = self.get_namespace_value(namespace=namespace)
            django_cache.add(key, value, DAY)
            # Fetch the value again to avoid a race condition if another
            # caller added a value between the first get() and the add()
            # above.
            return django_cache.get(key, value)

        return rv

    def update_cache_namespace(self, namespace):
        """
        Update the value for the namespace key
        Parameters
        ----------
        namespace: str
        """
        key = self.get_namespace_key(namespace=namespace)
        value = self.get_namespace_value(namespace=namespace)
        django_cache.set(key, value, DAY)

    def get_namespace_key(self, namespace):
        """

        Parameters
        ----------
        namespace: str

        Returns
        -------
        str
        """
        return xxhash.xxh3_64_hexdigest(f"namespace:{namespace}")

    def get_namespace_value(self, namespace):
        """
        Create value for the namespace value

        The namespace is used to make sure the hashed value is unique

        Parameters
        ----------
        namespace: str

        Returns
        -------
        str
        """
        namespace_value = namespace + str(int(round(time.time() * 1000)))
        return xxhash.xxh3_64_hexdigest(namespace_value)


cache = MyCache()



용법



값 설정

cache.set("product", "combined_pricing", 5000)


가치를 얻으십시오

pricing = cache.get("product", "combined_pricing")


단일 키 삭제

pricing = cache.delete("product", "combined_pricing")


네임스페이스 삭제

cache.delete_namespace("product")


결론



이 코드를 사용하면 memcached 키를 그룹화하고 해당 네임스페이스의 키를 한 번에 모두 무효화할 수 있습니다.

메모
  • 키의 일부이기 때문에 모든 memcached 키와 네임스페이스 값을 해시합니다. 키의 일부는 예측할 수 없으며 잠재적으로 키와 호환되지 않는 문자를 포함할 수 있습니다.
  • 네임스페이스 값에 시간을 사용하면 증가 방법이 필요하지 않습니다.
  • 나는 다른 해셔보다 xxhash를 선호합니다. 매우 빠릅니다.

  • 참조
  • PyPi의 xxhash: https://pypi.org/project/xxhash/

  • 오타를 찾았습니까?



    이 블로그 게시물에서 오타, 개선할 수 있는 문장 또는 업데이트해야 할 사항을 발견한 경우 git 저장소를 통해 액세스하고 풀 요청을 할 수 있습니다. 댓글을 게시하는 대신 직접 이동하여 변경 사항이 포함된 새로운 풀 리퀘스트를 여세요.


    Colin Lloyd의 사진 - https://unsplash.com/photos/62OEfKjU1Vs

    좋은 웹페이지 즐겨찾기