增加部分缓存

This commit is contained in:
virusdefender 2019-03-11 17:57:43 +08:00
parent 482a1a7d02
commit 7df98245e4
3 changed files with 100 additions and 38 deletions

View File

@ -1,13 +1,92 @@
import functools
import os
from django.core.cache import cache
import threading
import time
from django.db import transaction, IntegrityError
from utils.constants import CacheKey
from utils.shortcuts import rand_str
from judge.languages import languages
from .models import SysOptions as SysOptionsModel
class my_property:
"""
metaclass 中使用以实现
1. ttl = None不缓存
2. ttl is callable条件缓存
3. 缓存 ttl
"""
def __init__(self, func=None, fset=None, ttl=None):
self.fset = fset
self.local = threading.local()
self.ttl = ttl
self._check_ttl(ttl)
self.func = func
functools.update_wrapper(self, func)
def _check_ttl(self, value):
if value is None or callable(value):
return
return self._check_timeout(value)
def _check_timeout(self, value):
if not isinstance(value, int):
raise ValueError(f"Invalid timeout type: {type(value)}")
if value < 0:
raise ValueError("Invalid timeout value, it must >= 0")
def __get__(self, obj, cls):
if obj is None:
return self
now = time.time()
if self.ttl:
if hasattr(self.local, "value"):
value, expire_at = self.local.value
if now < expire_at:
return value
value = self.func(obj)
# 如果定义了条件缓存, ttl 是一个函数,返回要缓存多久;返回 0 代表不要缓存
if callable(self.ttl):
# 而且条件缓存说不要缓存,那就直接返回,不要设置 local
timeout = self.ttl(value)
self._check_timeout(timeout)
if timeout == 0:
return value
elif timeout > 0:
self.local.value = (value, now + timeout)
else:
# ttl 是一个数字
self.local.value = (value, now + self.ttl)
return value
else:
return self.func(obj)
def __set__(self, obj, value):
if not self.fset:
raise AttributeError("can't set attribute")
self.fset(obj, value)
if hasattr(self.local, "value"):
del self.local.value
def setter(self, func):
self.fset = func
return self
def __call__(self, func, *args, **kwargs) -> "my_property":
if self.func is None:
self.func = func
functools.update_wrapper(self, func)
return self
DEFAULT_SHORT_TTL = 2
def default_token():
token = os.environ.get("JUDGE_SERVER_TOKEN")
return token if token else rand_str()
@ -41,23 +120,10 @@ class OptionDefaultValue:
class _SysOptionsMeta(type):
@classmethod
def _set_cache(mcs, option_key, option_value):
cache.set(f"{CacheKey.option}:{option_key}", option_value, timeout=60)
@classmethod
def _del_cache(mcs, option_key):
cache.delete(f"{CacheKey.option}:{option_key}")
@classmethod
def _get_keys(cls):
return [key for key in OptionKeys.__dict__ if not key.startswith("__")]
def rebuild_cache(cls):
for key in cls._get_keys():
# get option 的时候会写 cache 的
cls._get_option(key, use_cache=False)
@classmethod
def _init_option(mcs):
for item in mcs._get_keys():
@ -71,19 +137,14 @@ class _SysOptionsMeta(type):
pass
@classmethod
def _get_option(mcs, option_key, use_cache=True):
def _get_option(mcs, option_key):
try:
if use_cache:
option = cache.get(f"{CacheKey.option}:{option_key}")
if option:
return option
option = SysOptionsModel.objects.get(key=option_key)
value = option.value
mcs._set_cache(option_key, value)
return value
except SysOptionsModel.DoesNotExist:
mcs._init_option()
return mcs._get_option(option_key, use_cache=use_cache)
return mcs._get_option(option_key)
@classmethod
def _set_option(mcs, option_key: str, option_value):
@ -92,7 +153,6 @@ class _SysOptionsMeta(type):
option = SysOptionsModel.objects.select_for_update().get(key=option_key)
option.value = option_value
option.save()
mcs._del_cache(option_key)
except SysOptionsModel.DoesNotExist:
mcs._init_option()
mcs._set_option(option_key, option_value)
@ -105,7 +165,6 @@ class _SysOptionsMeta(type):
value = option.value + 1
option.value = value
option.save()
mcs._del_cache(option_key)
except SysOptionsModel.DoesNotExist:
mcs._init_option()
return mcs._increment(option_key)
@ -122,7 +181,7 @@ class _SysOptionsMeta(type):
result[key] = mcs._get_option(key)
return result
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def website_base_url(cls):
return cls._get_option(OptionKeys.website_base_url)
@ -130,7 +189,7 @@ class _SysOptionsMeta(type):
def website_base_url(cls, value):
cls._set_option(OptionKeys.website_base_url, value)
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def website_name(cls):
return cls._get_option(OptionKeys.website_name)
@ -138,7 +197,7 @@ class _SysOptionsMeta(type):
def website_name(cls, value):
cls._set_option(OptionKeys.website_name, value)
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def website_name_shortcut(cls):
return cls._get_option(OptionKeys.website_name_shortcut)
@ -146,7 +205,7 @@ class _SysOptionsMeta(type):
def website_name_shortcut(cls, value):
cls._set_option(OptionKeys.website_name_shortcut, value)
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def website_footer(cls):
return cls._get_option(OptionKeys.website_footer)
@ -154,7 +213,7 @@ class _SysOptionsMeta(type):
def website_footer(cls, value):
cls._set_option(OptionKeys.website_footer, value)
@property
@my_property
def allow_register(cls):
return cls._get_option(OptionKeys.allow_register)
@ -162,7 +221,7 @@ class _SysOptionsMeta(type):
def allow_register(cls, value):
cls._set_option(OptionKeys.allow_register, value)
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def submission_list_show_all(cls):
return cls._get_option(OptionKeys.submission_list_show_all)
@ -170,7 +229,7 @@ class _SysOptionsMeta(type):
def submission_list_show_all(cls, value):
cls._set_option(OptionKeys.submission_list_show_all, value)
@property
@my_property
def smtp_config(cls):
return cls._get_option(OptionKeys.smtp_config)
@ -178,7 +237,7 @@ class _SysOptionsMeta(type):
def smtp_config(cls, value):
cls._set_option(OptionKeys.smtp_config, value)
@property
@my_property
def judge_server_token(cls):
return cls._get_option(OptionKeys.judge_server_token)
@ -186,7 +245,7 @@ class _SysOptionsMeta(type):
def judge_server_token(cls, value):
cls._set_option(OptionKeys.judge_server_token, value)
@property
@my_property
def throttling(cls):
return cls._get_option(OptionKeys.throttling)
@ -194,7 +253,7 @@ class _SysOptionsMeta(type):
def throttling(cls, value):
cls._set_option(OptionKeys.throttling, value)
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def languages(cls):
return cls._get_option(OptionKeys.languages)
@ -202,15 +261,15 @@ class _SysOptionsMeta(type):
def languages(cls, value):
cls._set_option(OptionKeys.languages, value)
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def spj_languages(cls):
return [item for item in cls.languages if "spj" in item]
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def language_names(cls):
return [item["name"] for item in languages]
@property
@my_property(ttl=DEFAULT_SHORT_TTL)
def spj_language_names(cls):
return [item["name"] for item in cls.languages if "spj" in item]

View File

@ -1,4 +1,6 @@
import re
from functools import lru_cache
TEMPLATE_BASE = """//PREPEND BEGIN
{}
@ -13,6 +15,7 @@ TEMPLATE_BASE = """//PREPEND BEGIN
//APPEND END"""
@lru_cache(maxsize=100)
def parse_problem_template(template_str):
prepend = re.findall(r"//PREPEND BEGIN\n([\s\S]+?)//PREPEND END", template_str)
template = re.findall(r"//TEMPLATE BEGIN\n([\s\S]+?)//TEMPLATE END", template_str)
@ -22,5 +25,6 @@ def parse_problem_template(template_str):
"append": append[0] if append else ""}
@lru_cache(maxsize=100)
def build_problem_template(prepend, template, append):
return TEMPLATE_BASE.format(prepend, template, append)

View File

@ -25,7 +25,6 @@ class CacheKey:
waiting_queue = "waiting_queue"
contest_rank_cache = "contest_rank_cache"
website_config = "website_config"
option = "option"
class Difficulty(Choices):