增加部分缓存

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 import os
from django.core.cache import cache import threading
import time
from django.db import transaction, IntegrityError from django.db import transaction, IntegrityError
from utils.constants import CacheKey
from utils.shortcuts import rand_str from utils.shortcuts import rand_str
from judge.languages import languages from judge.languages import languages
from .models import SysOptions as SysOptionsModel 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(): def default_token():
token = os.environ.get("JUDGE_SERVER_TOKEN") token = os.environ.get("JUDGE_SERVER_TOKEN")
return token if token else rand_str() return token if token else rand_str()
@ -41,23 +120,10 @@ class OptionDefaultValue:
class _SysOptionsMeta(type): 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 @classmethod
def _get_keys(cls): def _get_keys(cls):
return [key for key in OptionKeys.__dict__ if not key.startswith("__")] 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 @classmethod
def _init_option(mcs): def _init_option(mcs):
for item in mcs._get_keys(): for item in mcs._get_keys():
@ -71,19 +137,14 @@ class _SysOptionsMeta(type):
pass pass
@classmethod @classmethod
def _get_option(mcs, option_key, use_cache=True): def _get_option(mcs, option_key):
try: try:
if use_cache:
option = cache.get(f"{CacheKey.option}:{option_key}")
if option:
return option
option = SysOptionsModel.objects.get(key=option_key) option = SysOptionsModel.objects.get(key=option_key)
value = option.value value = option.value
mcs._set_cache(option_key, value)
return value return value
except SysOptionsModel.DoesNotExist: except SysOptionsModel.DoesNotExist:
mcs._init_option() mcs._init_option()
return mcs._get_option(option_key, use_cache=use_cache) return mcs._get_option(option_key)
@classmethod @classmethod
def _set_option(mcs, option_key: str, option_value): 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 = SysOptionsModel.objects.select_for_update().get(key=option_key)
option.value = option_value option.value = option_value
option.save() option.save()
mcs._del_cache(option_key)
except SysOptionsModel.DoesNotExist: except SysOptionsModel.DoesNotExist:
mcs._init_option() mcs._init_option()
mcs._set_option(option_key, option_value) mcs._set_option(option_key, option_value)
@ -105,7 +165,6 @@ class _SysOptionsMeta(type):
value = option.value + 1 value = option.value + 1
option.value = value option.value = value
option.save() option.save()
mcs._del_cache(option_key)
except SysOptionsModel.DoesNotExist: except SysOptionsModel.DoesNotExist:
mcs._init_option() mcs._init_option()
return mcs._increment(option_key) return mcs._increment(option_key)
@ -122,7 +181,7 @@ class _SysOptionsMeta(type):
result[key] = mcs._get_option(key) result[key] = mcs._get_option(key)
return result return result
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def website_base_url(cls): def website_base_url(cls):
return cls._get_option(OptionKeys.website_base_url) return cls._get_option(OptionKeys.website_base_url)
@ -130,7 +189,7 @@ class _SysOptionsMeta(type):
def website_base_url(cls, value): def website_base_url(cls, value):
cls._set_option(OptionKeys.website_base_url, value) cls._set_option(OptionKeys.website_base_url, value)
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def website_name(cls): def website_name(cls):
return cls._get_option(OptionKeys.website_name) return cls._get_option(OptionKeys.website_name)
@ -138,7 +197,7 @@ class _SysOptionsMeta(type):
def website_name(cls, value): def website_name(cls, value):
cls._set_option(OptionKeys.website_name, value) cls._set_option(OptionKeys.website_name, value)
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def website_name_shortcut(cls): def website_name_shortcut(cls):
return cls._get_option(OptionKeys.website_name_shortcut) return cls._get_option(OptionKeys.website_name_shortcut)
@ -146,7 +205,7 @@ class _SysOptionsMeta(type):
def website_name_shortcut(cls, value): def website_name_shortcut(cls, value):
cls._set_option(OptionKeys.website_name_shortcut, value) cls._set_option(OptionKeys.website_name_shortcut, value)
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def website_footer(cls): def website_footer(cls):
return cls._get_option(OptionKeys.website_footer) return cls._get_option(OptionKeys.website_footer)
@ -154,7 +213,7 @@ class _SysOptionsMeta(type):
def website_footer(cls, value): def website_footer(cls, value):
cls._set_option(OptionKeys.website_footer, value) cls._set_option(OptionKeys.website_footer, value)
@property @my_property
def allow_register(cls): def allow_register(cls):
return cls._get_option(OptionKeys.allow_register) return cls._get_option(OptionKeys.allow_register)
@ -162,7 +221,7 @@ class _SysOptionsMeta(type):
def allow_register(cls, value): def allow_register(cls, value):
cls._set_option(OptionKeys.allow_register, value) cls._set_option(OptionKeys.allow_register, value)
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def submission_list_show_all(cls): def submission_list_show_all(cls):
return cls._get_option(OptionKeys.submission_list_show_all) return cls._get_option(OptionKeys.submission_list_show_all)
@ -170,7 +229,7 @@ class _SysOptionsMeta(type):
def submission_list_show_all(cls, value): def submission_list_show_all(cls, value):
cls._set_option(OptionKeys.submission_list_show_all, value) cls._set_option(OptionKeys.submission_list_show_all, value)
@property @my_property
def smtp_config(cls): def smtp_config(cls):
return cls._get_option(OptionKeys.smtp_config) return cls._get_option(OptionKeys.smtp_config)
@ -178,7 +237,7 @@ class _SysOptionsMeta(type):
def smtp_config(cls, value): def smtp_config(cls, value):
cls._set_option(OptionKeys.smtp_config, value) cls._set_option(OptionKeys.smtp_config, value)
@property @my_property
def judge_server_token(cls): def judge_server_token(cls):
return cls._get_option(OptionKeys.judge_server_token) return cls._get_option(OptionKeys.judge_server_token)
@ -186,7 +245,7 @@ class _SysOptionsMeta(type):
def judge_server_token(cls, value): def judge_server_token(cls, value):
cls._set_option(OptionKeys.judge_server_token, value) cls._set_option(OptionKeys.judge_server_token, value)
@property @my_property
def throttling(cls): def throttling(cls):
return cls._get_option(OptionKeys.throttling) return cls._get_option(OptionKeys.throttling)
@ -194,7 +253,7 @@ class _SysOptionsMeta(type):
def throttling(cls, value): def throttling(cls, value):
cls._set_option(OptionKeys.throttling, value) cls._set_option(OptionKeys.throttling, value)
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def languages(cls): def languages(cls):
return cls._get_option(OptionKeys.languages) return cls._get_option(OptionKeys.languages)
@ -202,15 +261,15 @@ class _SysOptionsMeta(type):
def languages(cls, value): def languages(cls, value):
cls._set_option(OptionKeys.languages, value) cls._set_option(OptionKeys.languages, value)
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def spj_languages(cls): def spj_languages(cls):
return [item for item in cls.languages if "spj" in item] return [item for item in cls.languages if "spj" in item]
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def language_names(cls): def language_names(cls):
return [item["name"] for item in languages] return [item["name"] for item in languages]
@property @my_property(ttl=DEFAULT_SHORT_TTL)
def spj_language_names(cls): def spj_language_names(cls):
return [item["name"] for item in cls.languages if "spj" in item] return [item["name"] for item in cls.languages if "spj" in item]

View File

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

View File

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