{"id":20619777,"url":"https://github.com/twtrubiks/django-docker-redis-tutorial","last_synced_at":"2025-04-15T12:12:48.254Z","repository":{"id":84518919,"uuid":"127536282","full_name":"twtrubiks/django-docker-redis-tutorial","owner":"twtrubiks","description":"django-docker-redis-tutorial 📝","archived":false,"fork":false,"pushed_at":"2022-06-20T08:17:28.000Z","size":152,"stargazers_count":43,"open_issues_count":1,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-15T12:12:15.776Z","etag":null,"topics":["django","docker","redis","tutorial"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/twtrubiks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-31T13:52:32.000Z","updated_at":"2025-02-21T06:14:32.000Z","dependencies_parsed_at":"2023-03-02T04:30:27.331Z","dependency_job_id":null,"html_url":"https://github.com/twtrubiks/django-docker-redis-tutorial","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-docker-redis-tutorial","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-docker-redis-tutorial/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-docker-redis-tutorial/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/twtrubiks%2Fdjango-docker-redis-tutorial/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/twtrubiks","download_url":"https://codeload.github.com/twtrubiks/django-docker-redis-tutorial/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249067779,"owners_count":21207396,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","docker","redis","tutorial"],"created_at":"2024-11-16T12:12:29.708Z","updated_at":"2025-04-15T12:12:48.242Z","avatar_url":"https://github.com/twtrubiks.png","language":"Python","readme":"# django-docker-redis-tutorial\n\n django-docker-redis-tutorial 基本教學  📝\n\n* [Youtube Tutorial Part1 - docker 安裝 redis 以及 redis 基本指令](https://youtu.be/BhO2ADEj_EE)\n\n* [Youtube Tutorial Part2 - django-redis 以及 redis api 介紹](https://youtu.be/fX_3UTKgjI8)\n\n* [Youtube Tutorial Part3 - redis 應用場合以及實戰](https://youtu.be/xFNkpyd4Ues)\n\n## 前言\n\n![alt tag](https://i.imgur.com/lVNQWVV.png)\n\nRedis 是 open source，也是 in-memory data structure store ( key-value )，常被使用在 database、cache 、\n\nmessage broker，像是可以透過 cache 減輕 database 的壓力 ( redis 讀寫速度比一般的 database 快非常多 )，\n\n而 message broker 可以用在像是 Celery 的應用（ Celery 的應用可參考我之前寫的 [django-celery-tutorial](https://github.com/twtrubiks/django-celery-tutorial) 以及\n\n[docker-django-celery-tutorial](https://github.com/twtrubiks/docker-django-celery-tutorial)。\n\n透過這篇文章，你將會學會\n\n* [透過 docker 安裝 redis](https://github.com/twtrubiks/django-docker-redis-tutorial#%E9%80%8F%E9%81%8E-docker-%E5%AE%89%E8%A3%9D-redis)\n* [redis 基本指令](https://github.com/twtrubiks/django-docker-redis-tutorial#redis-%E5%9F%BA%E6%9C%AC%E6%8C%87%E4%BB%A4)\n* [django-redis 介紹](https://github.com/twtrubiks/django-docker-redis-tutorial#django-redis)\n* [透過 low-level cache API 把玩 redis](https://github.com/twtrubiks/django-docker-redis-tutorial#%E9%80%8F%E9%81%8E-low-level-cache-api-%E6%8A%8A%E7%8E%A9-redis)\n* [redis 應用場合](https://github.com/twtrubiks/django-docker-redis-tutorial#redis-%E6%87%89%E7%94%A8%E5%A0%B4%E5%90%88)\n\n## 教學\n\n* [Youtube Tutorial Part1 - docker 安裝 redis 以及 redis 基本指令](https://youtu.be/BhO2ADEj_EE)\n\n在開始教學前，建議大家可以先閱讀官方的 [Redis Persistence](https://redis.io/topics/persistence) ，\n\n裡面詳細的介紹了 **RDB persistence** 以及 **AOF persistence** 的觀念，這兩個觀念很重要:+1:\n\n### 透過 docker 安裝 redis\n\n[docker redis](https://hub.docker.com/_/redis/)\n\n請在命令提示字元 ( cmd ) 直接執行以下指令\n\n```cmd\ndocker run --name some-redis  -p 6379:6379  -d redis redis-server --appendonly yes\n```\n\n如果要設定密碼\n\n```cmd\ndocker run --name some-redis  -p 6379:6379  -d redis redis-server --appendonly yes --requirepass \"changeme\"\n```\n\n以上這段指令，比較需要特別解釋的就是 `--appendonly`，當如果你沒有設定時，\n\n假如今天斷電或是不小心意外終止 redis，可能會遺失當下的資料，如果我們設定了 Append-only file ( AOF )，\n\nAOF 預設的 policy 是每秒寫入一次 ( 當然，還是有可能會遺失一秒的資料，但相對比 RDB ( Snapshotting，\n\n因為預設的是存在硬碟上，binary file 為 dump.rdb，所以稱為 RDB )，AOF 比起 RDB 有更好的 Persistence 。\n\n當選擇使用 AOF 時 ，如果重起 redis，會依照 AOF 去重新建立狀態。\n\n更多詳細資料可參考 [append-only-file](https://redis.io/topics/persistence#append-only-file)。\n\n或是直接使用 [docker-compose.yml](docker-compose.yml).\n\n### redis 基本指令\n\n確認建立完成後，即可使用 redis-cli 開始玩 redis\n\n```cmd\ndocker exec -it \u003ccontainer name\u003e redis-cli\n```\n\n如果你有設定密碼要加上 `-a`\n\n```cmd\ndocker exec -it \u003ccontainer name\u003e redis-cli -a changeme\n```\n\n更多 redis 可參考 [redis command](https://redis.io/commands/) 以及支援的 [redis data-types](https://redis.io/docs/manual/data-types/)。\n\n```cmd\n127.0.0.1:6379\u003e ping\nPONG\n```\n\nset key\n\n```cmd\n127.0.0.1:6379\u003e set id twtrubiks\nOK\n```\n\nget key\n\n```cmd\n127.0.0.1:6379\u003e get id\n\"twtrubiks\"\n```\n\nexists key，更多可參考 [EXISTS key](https://redis.io/commands/exists)\n\n```cmd\n# if the key exists.\n127.0.0.1:6379\u003e exists id\n(integer) 1\n# if the key does not exist.\n127.0.0.1:6379\u003e exists not_exist\n(integer) 0\n```\n\n設定 key 一個有效時間 ( Redis 常常拿來當做是 Cache )，更多可參考 [EXPIRE key seconds](https://redis.io/commands/expire)\n\n```cmd\n127.0.0.1:6379\u003e set name twtrubiks\nOK\n127.0.0.1:6379\u003e get name\n\"twtrubiks\"\n127.0.0.1:6379\u003e expire name 10\n(integer) 1\n127.0.0.1:6379\u003e get name\n\"twtrubiks\"\n# Wait for 10 seconds and try again\n127.0.0.1:6379\u003e get name\n(nil)\n```\n\n刪除 key，更多可參考 [DEL key](https://redis.io/commands/del)\n\n```cmd\n127.0.0.1:6379\u003e set num 1\nOK\n127.0.0.1:6379\u003e del num\n(integer) 1\n127.0.0.1:6379\u003e get num\n(nil)\n```\n\n一次刪除全部的 key\n\n```cmd\n127.0.0.1:6379\u003e flushall\nOK\n```\n\n得到目前全部的 keys，更多可參考 [KEYS pattern](https://redis.io/commands/keys)\n\n```cmd\nkeys *\n```\n\nTTL key，查看目前還剩多久時間會 timeout，\n\n更多可參考 [TTL key](https://redis.io/commands/ttl)\n\n```cmd\n127.0.0.1:6379\u003e set name twtrubiks_ttl\nOK\n127.0.0.1:6379\u003e expire name 10\n(integer) 1\n# Wait for 4 seconds and try again\n127.0.0.1:6379\u003e ttl name\n(integer) 6\n```\n\nPERSIST key，將 key 從 volatile ( a key with an expire set )  轉變成\n\npersistent ( a key that will never expire as no timeout is associated )，\n\n說白話一點，就是將 key 轉變成永遠不會過期 ( timeout )，更多可參考 [PERSIST key](https://redis.io/commands/persist)\n\n```cmd\n127.0.0.1:6379\u003e set mykey hello\nOK\n127.0.0.1:6379\u003e expire mykey 20\n(integer) 1\n# Wait for 5 seconds and try again\n127.0.0.1:6379\u003e ttl mykey\n(integer) 15\n127.0.0.1:6379\u003e persist mykey\n(integer) 1\n127.0.0.1:6379\u003e TTL mykey\n(integer) -1\n#  -1 if the key exists but has no associated expire.\n```\n\n選擇資料庫，有 16 個資料庫 ( 0-15 )，預設是第 0 個資料庫，\n\n如下方範例為切換到第一個資料庫，\n\n```cmd\n127.0.0.1:6379\u003e select 1\nOK\n127.0.0.1:6379[1]\u003e\n```\n\nredis 非常適合投票這種使用情境，可參考以下範例\n\n```cmd\n# 投給 a 一票\n127.0.0.1:6379\u003e zincrby vote 1 a\n\"1\"\n# 投給 b 兩票\n127.0.0.1:6379\u003e zincrby vote 2 b\n\"2\"\n# 投給 a 三票\n127.0.0.1:6379\u003e zincrby vote 3 a\n\"4\"\n# 查看 a 總投票數\n127.0.0.1:6379\u003e zscore vote a\n\"4\"\n# 得到 a 排名 ( 由高到低 )\n127.0.0.1:6379\u003e zrevrank vote a\n(integer) 0\n# 得到 b 排名 ( 由高到低 )\n127.0.0.1:6379\u003e zrevrank vote b\n(integer) 1\n# 得到前10名 ( 由高到低 )\n127.0.0.1:6379\u003e zrevrange vote 0 9\n1) \"a\"\n2) \"b\"\n# 得到前10名以及對應的分數 ( 從高到低 )\n127.0.0.1:6379\u003e zrevrange vote 0 9 withscores\n1) \"a\"\n2) \"4\"\n3) \"b\"\n4) \"2\"\n```\n\n由於 redis command 很多，這邊不可能一一介紹，所以更詳細的可參考 [commands](https://redis.io/commands/):smile:\n\n## django-redis\n\n* [Youtube Tutorial Part2 - django-redis 以及 redis api 介紹](https://youtu.be/fX_3UTKgjI8)\n\n接下來和大家介紹 [django-redis](https://github.com/jazzband/django-redis) 這個套件，\n\n我將簡單介紹他的使用方法，請先安裝套件\n\n```python\npip install django-redis\n```\n\n接著在 [settings.py](https://github.com/twtrubiks/django-docker-redis-tutorial/blob/master/django_docker_redis_tutorial/settings.py) 中加入下方程式碼\n\n```python\nCACHES = {\n    \"default\": {\n        \"BACKEND\": \"django_redis.cache.RedisCache\",\n        \"LOCATION\": \"redis://localhost:6379/0\",\n        \"OPTIONS\": {\n            \"CLIENT_CLASS\": \"django_redis.client.DefaultClient\",\n        }\n    }\n}\n```\n\nDjango 預設的 session 是存放在 database 中，但這邊要將他修改成 redis ，\n\n修改的方式很簡單，只需要將 SESSION_ENGINE 改成 `django.contrib.sessions.backends.cache` 即可，\n\nConfigure as session backend，在 [settings.py](https://github.com/twtrubiks/django-docker-redis-tutorial/blob/master/django_docker_redis_tutorial/settings.py) 中加入下方程式碼\n\n```python\nSESSION_ENGINE = \"django.contrib.sessions.backends.cache\"\nSESSION_CACHE_ALIAS = \"default\"\n```\n\n詳細的 Session 參數介紹，可參考 Django 官網的 [sessions](https://docs.djangoproject.com/en/4.0/topics/http/sessions/) 文件，\n\n設定完成後，Session 將會儲存在 redis 中（ 速度更快 ），\n\n如果你不了解 Session ，可以參考我之前寫的這篇 [Session](https://github.com/twtrubiks/CSRF-tutorial#session)，\n\nSession 存在 redis 中的範例，後面我會再介紹給各位。\n\n有時候我們可能需要 access 原生的 Redis 功能，所以這時候就需要採用下面的方式使用 redis，\n\n```python\n\u003e\u003e\u003e from django_redis import get_redis_connection\n\u003e\u003e\u003e con = get_redis_connection(\"default\")\n\u003e\u003e\u003e con\n\u003credis.client.StrictRedis object at 0x2dc4510\u003e\n```\n\n可參考 [Raw client access](https://github.com/jazzband/django-redis#raw-client-access)\n\n## 透過 low-level cache API 把玩 redis\n\n官方文件可參考 [The low-level cache API](https://docs.djangoproject.com/en/4.0/topics/cache/#the-low-level-cache-api)，\n\n直接使用 Python Console 操作以下指令，\n\n`set(key, value, timeout)`\n\n```python\n\u003e\u003e\u003e from django.core.cache import cache\n\u003e\u003e\u003e cache.set('my_key', 'hello, world!')\nTrue\n\u003e\u003e\u003e cache.get('my_key')\n'hello, world!'\n```\n\n`set(key, value, timeout)` and `get(key)`\n\ntimeout 如果沒設定或是設定為 None 時，資料將為 forever\n\n```python\n\u003e\u003e\u003e from django.core.cache import cache\n\u003e\u003e\u003e cache.set('my_key', 'hello, world!')\nTrue\n\u003e\u003e\u003e cache.get('my_key')\n'hello, world!'\n```\n\n設定 timeout 為 10 秒\n\n```python\n\u003e\u003e\u003e from django.core.cache import cache\n\u003e\u003e\u003e cache.set('key_test', 'hello, world test !',10)\nTrue\n## if key_test has expired ( not exist ) , show has expired\n\u003e\u003e\u003e cache.get('key_test', 'has expired')\n'hello, world test !'\n## Wait 10 seconds for 'my_key' to expire...\n\u003e\u003e\u003e cache.get('key_test', 'has expired')\n'has expired'\n```\n\n`add()`\n\n如果這個 key 不存在，就會設定指定的 key，如果 key 已經存在，則**不會更新**既有的 key 值\n\n```python\n\u003e\u003e\u003e cache.set('my_key', 'hello, world!')\nTrue\n\u003e\u003e\u003e cache.get('my_key')\n'hello, world!'\n\u003e\u003e\u003e cache.add('my_key','demo')\n\u003e\u003e\u003e cache.get('my_key')\n'hello, world!'\n\u003e\u003e\u003e cache.add('my_key_2','test2')\nTrue\n```\n\n`get_or_set()`\n\n可以使用這個來取得 ( 設定 ) key 值 ， 假如這個 key 不存在 ，就設定 key 值 ，如果存在就將 key 值顯示出來\n\n```python\n\u003e\u003e\u003e cache.get('my_new_key')\n\u003e\u003e\u003e cache.get_or_set('my_new_key', 'my new value')\n'my new value'\n```\n\n`set_many()` and `get_many()`\n\n```python\n\u003e\u003e\u003e cache.set_many({'a': 1, 'b': 2, 'c': 3})\n\u003e\u003e\u003e cache.get_many(['a', 'b', 'c'])\nOrderedDict([('a', 1), ('b', 2), ('c', 3)])\n```\n\n`delete()` and `delete_many()`\n\n```python\n\u003e\u003e\u003e cache.delete('a')\n1\n\u003e\u003e\u003e cache.delete_many(['a', 'b', 'c'])\n2\n```\n\n`clear()`\n\n```python\n\u003e\u003e\u003e cache.clear()\n```\n\n`incr()` and `decr()`\n\n```python\n\u003e\u003e\u003e cache.set('num', 1)\nTrue\n\u003e\u003e\u003e cache.incr('num')\n2\n\u003e\u003e\u003e cache.incr('num', 10)\n12\n\u003e\u003e\u003e cache.decr('num')\n11\ncache.decr('num',2)\n9\n\n# A ValueError will be raised if you attempt to increment or decrement a nonexistent cache key\ncache.get('test')\ncache.decr('test',2)\n\u003e\u003e\u003e ValueError: Key ':1:tst' not found\n```\n\n### Cache versioning\n\n[Cache versioning](https://docs.djangoproject.com/en/4.0/topics/cache/#cache-versioning)\n\n`incr_version()` and `decr_version()`\n\n```python\n# default version =1\n\u003e\u003e\u003e cache.set('my_key', 'hello world!')\nTrue\n\u003e\u003e\u003e cache.get('my_key',version=1)\n'hello world!'\n\u003e\u003e\u003e cache.get('my_key',version=2)\n\n# incr_version\n\u003e\u003e\u003e cache.incr_version('my_key')\n2\n\u003e\u003e\u003e cache.get('my_key',version=2)\n'hello world!'\n\u003e\u003e\u003e cache.get('my_key',version=1)\n\n\u003e\u003e\u003e cache.set('my_key', 'test', version=1)\nTrue\n\u003e\u003e\u003e cache.get('my_key',version=1)\n'test'\n\u003e\u003e\u003e cache.get('my_key',version=2)\n'hello world!'\n```\n\n接著可以使用 redis-cli 觀看，\n\n```cmd\n127.0.0.1:6379\u003e keys *\n1) \":1:my_key\"\n2) \":2:my_key\"\n```\n\n有沒有發現一件事情，我們明明設定的是 `cache.set('my_key', 'test', version=1)`，\n\n但為什麼透過 django 設定的 key 都會變成  `\":1:my_key\"` 這樣的格式呢 ？\n\n原因是因為 django cache 本身的機制，\n\n[default.py#L706](https://github.com/jazzband/django-redis/blob/master/django_redis/client/default.py#L706)\n\n```python\ndef make_key(\n    self, key: Any, version: Optional[Any] = None, prefix: Optional[str] = None\n) -\u003e CacheKey:\n    if isinstance(key, CacheKey):\n        return key\n\n    if prefix is None:\n        prefix = self._backend.key_prefix\n\n    if version is None:\n        version = self._backend.version\n\n    return CacheKey(self._backend.key_func(key, prefix, version))\n```\n\ndjango 使用 make_key 建立新的 key ，原始的 key 在 `_backend.key_func` 裡。\n\n[base.py#L31](https://github.com/django/django/blob/main/django/core/cache/backends/base.py#L31)\n\n```python\ndef default_key_func(key, key_prefix, version):\n    \"\"\"\n    Default function to generate keys.\n    Construct the key used by all other methods. By default, prepend\n    the `key_prefix`. KEY_FUNCTION can be used to specify an alternate\n    function with custom key making behavior.\n    \"\"\"\n    return \"%s:%s:%s\" % (key_prefix, version, key)\n\n\ndef get_key_func(key_func):\n    \"\"\"\n    Function to decide which key function to use.\n    Default to ``default_key_func``.\n    \"\"\"\n    if key_func is not None:\n        if callable(key_func):\n            return key_func\n        else:\n            return import_string(key_func)\n    return default_key_func\n\n\nclass BaseCache:\n    _missing_key = object()\n\n    def __init__(self, params):\n        timeout = params.get(\"timeout\", params.get(\"TIMEOUT\", 300))\n        if timeout is not None:\n            try:\n                timeout = int(timeout)\n            except (ValueError, TypeError):\n                timeout = 300\n        self.default_timeout = timeout\n\n        options = params.get(\"OPTIONS\", {})\n        max_entries = params.get(\"max_entries\", options.get(\"MAX_ENTRIES\", 300))\n        try:\n            self._max_entries = int(max_entries)\n        except (ValueError, TypeError):\n            self._max_entries = 300\n\n        cull_frequency = params.get(\"cull_frequency\", options.get(\"CULL_FREQUENCY\", 3))\n        try:\n            self._cull_frequency = int(cull_frequency)\n        except (ValueError, TypeError):\n            self._cull_frequency = 3\n\n        self.key_prefix = params.get(\"KEY_PREFIX\", \"\")\n        self.version = params.get(\"VERSION\", 1)\n        self.key_func = get_key_func(params.get(\"KEY_FUNCTION\"))\n```\n\n這也就是為什麼透過 django 設定的 key 都會變成 `%s:%s:%s` 這樣的格式了。\n\n## redis 應用場合\n\n* [Youtube Tutorial Part3 - redis 應用場合以及實戰](https://youtu.be/xFNkpyd4Ues)\n\nredis 可以應用的場合真的非常的多，這次的 demo 將使用到以下情境 ( 其他的情境大家可以再自行 google 了解 )，\n\n* 統計頁面點擊數\n\n當需要記錄頁面的瀏覽次數（ 或點擊數 ）時，就非常適合使用 redis，為什麼不使用 db 呢 ？\n\n因為假如有非常大量的人瀏覽（ 或點擊 ）網頁時，可能會導致 db 的鎖互搶，影響到效能。\n\n關於鎖這部份，可以稍微參考一下我之前寫的 [django-transactions-tutorial](https://github.com/twtrubiks/django-transactions-tutorial)，裡面有稍微\n\n提到部分的概念。\n\nimages/[views.py](https://github.com/twtrubiks/django-docker-redis-tutorial/blob/master/images/views.py) 中片段程式碼\n\n```python\ncache.get_or_set('click', 0, timeout=None)\ntotal_views = cache.incr('click')\n```\n\n這段程式碼相當簡單，當你瀏覽到這個頁面時，就將 `click` 這個 key 加一，\n\n然後就可以統計出目前有多少人瀏覽過你的頁面 ( 聰明的你現在一定想到，\n\n那我就一直瘋狂 F5 不就可以一直刷瀏覽數量了嗎 ？沒錯，但這個問題大家\n\n可以自行想想，這邊只是帶給大家簡單的概念 )。\n\n* 排行榜\n\n前面介紹 redis 適合投票這種情境，當然，也適合排行榜這種使用情境，\n\nimages/[views.py](https://github.com/twtrubiks/django-docker-redis-tutorial/blob/master/images/views.py) 中片段程式碼\n\n```python\ndef index(request):\n    ......\n    rank = con.zrevrange(name='images', start=0, end=9, withscores=True, score_cast_func=int)\n    rank_seq = [\n        {\"url\": str(r[0], 'utf-8'),\n         \"value\": r[1]}\n        for r in rank\n    ]\n\n    return render(request, 'images/index.html', {\n        'images': images_seq,\n        'total_views': total_views,\n        'ranks': rank_seq,\n    })\n\n\ndef detail(request, image_id):\n    image = get_object_or_404(Image, id=image_id)\n    total_views = con.zincrby(name='images', amount=1 ,value=image.url)\n    return render(request,\n                  'images/detail.html', {\n                      'image': image,\n                      'total_views': int(total_views)\n                  })\n\n```\n\n在 `detail` 中，我們將圖片的 url 存到 images 這個 key 值中，並且給他加一，\n\n在 `index` 中，透過 `zrevrange` 這個方法統計目前圖片被瀏覽次數的排名。\n\n* Session Cache\n\n為了方便介紹，這邊直接使用登入 admin 後台來觀察 session，直接瀏覽 [http://127.0.0.1:8000/admin/](http://127.0.0.1:8000/admin/) ，\n\n預設的帳號密碼為 ( twtrubiks / password123 )，\n\n登入後你會發現，你的 redis 多了 session 的 key 值（ 而 database 中沒有增加 ），如下圖\n\nredis 中多了 session 的 key\n\n![alt tag](https://i.imgur.com/5KwryHN.png)\n\n![alt tag](https://i.imgur.com/sb9esxq.png)\n\n如果沒特別另外設定，django 預設是存放在 database  ( django_session 表格 )，\n\n這邊是空的很正常，因為我們已經設定 redis 了，\n\n![alt tag](https://i.imgur.com/TjdAq8y.png)\n\n* 減輕 database 壓力\n\n這邊和大家簡單說明如何減輕 database 的壓力:satisfied:\n\n可參考 musics/[views.py](https://github.com/twtrubiks/django-docker-redis-tutorial/blob/master/musics/views.py)\n\n```python\n# Create your views here.\nclass MusicViewSet(viewsets.ModelViewSet):\n    queryset = Music.objects.all()\n    serializer_class = MusicSerializer\n    permission_classes = (IsAuthenticated,)\n    parser_classes = (JSONParser,)\n\n    def list(self, request, **kwargs):\n        if self.request.version == '1.0':\n            if 'musics' in cache:\n                # from cache get musics\n                musics = cache.get('musics')\n            else:\n                musics = Music.objects.all()\n                serializer = MusicSerializer(musics, many=True)\n                musics = serializer.data\n                # store data to cache\n                cache.set('musics', musics, timeout=None)\n        else:\n            musics = Music.objects.all()\n            serializer = MusicSerializer(musics, many=True)\n            musics = serializer.data\n        return Response(musics, status=status.HTTP_200_OK)\n```\n\n假設 database 的 music 這張表格中有一萬筆資料，然後如果每個人每次發送 request 過來都要重新撈這一萬筆資料，\n\n會對資料庫造成很大的壓力，也沒什麼效率，這時候就可以透過 redis 來幫助我們。\n\n程式其實很簡單，當 redis 中沒有 musics 這個 key 的時候，我就去資料庫撈，然後將撈到的資料存進 redis 中，\n\n這樣當下一次我還需要時，就可以直接從 redis 中取得資料（ 不需要再重新從資料庫中撈資料 ）。\n\n你可能會問我為什麼要用 `self.request.version` ，原因是等等我們模擬簡單壓力測試要使用的:grinning:\n\nDjango 的 version 使用方法可參考我之前寫的 [django-rest-framework-tutorial#versioning](https://github.com/twtrubiks/django-rest-framework-tutorial#versioning)。\n\n以下是兩個的比較，\n\n第一次執行會從資料庫撈一萬筆的資料\n\n![alt tag](https://i.imgur.com/4cmFeBD.png)\n\n第二次開始，都會從 **redis** 撈一萬筆的資料 ( 速度快很多 )\n\n![alt tag](https://i.imgur.com/UqR0Tst.png)\n\n從秒數來看，速度至少快了 16 倍，你可能會說，其實還好阿:confused:\n\n不過，假設今天有 100 個人呢？ 相信就非常有感了:smirk:\n\n讓我們透過數據說話，使用 [loadtest](https://www.npmjs.com/package/loadtest) 來簡單模擬，\n\n先安裝 [loadtest](https://www.npmjs.com/package/loadtest)\n\n```cmd\nnpm install --location=global loadtest\n```\n\n使用方法\n\n```cmd\nloadtest [-n requests] [-c concurrency] [-k] URL\n```\n\n先來測試 **沒有 redis** 的情況 ( 50 個 request )\n\n```cmd\nloadtest -H \"Authorization: Basic dHd0cnViaWtzOnBhc3N3b3JkMTIz\" -n 50 -k  http://127.0.0.1:8000/api/musics/\n```\n\n如下圖，慢到我不想等他跑完 :sweat: ( 還在 38%)\n\n![alt tag](https://i.imgur.com/8FLqtpS.png)\n\n再來測試 **有 redis** 的情況 ( 50 個 request )\n\n```cmd\nloadtest -H \"Authorization: Basic dHd0cnViaWtzOnBhc3N3b3JkMTIz\" -H \"Accept: application/json;version=1.0\" -n 50 -k  http://127.0.0.1:8000/api/musics/\n```\n\n如下圖，很快就跑完了，而且花最久的時間是 229ms\n\n![alt tag](https://i.imgur.com/aPvSww8.png)\n\n可以發現，有 redis 的情況下，效能好很多:heart_eyes:\n\n後面我有再補充另一段 code, [musics/views.py](https://github.com/twtrubiks/django-docker-redis-tutorial/blob/master/musics/views.py)\n\n```python\ndef list_lock(self, request, **kwargs):\n    if self.request.version == '1.0':\n        if 'musics' in cache:\n            # from cache get musics\n            musics = cache.get('musics')\n        else:\n            if con.set(\"my_key\", \"secret\", nx=True, px=1000):\n                musics = Music.objects.all()\n                serializer = MusicSerializer(musics, many=True)\n                musics = serializer.data\n                cache.set('musics', musics, timeout=None)\n                print('store data to cache')\n            else:\n                print(\"pending\")\n                while 1:\n                    time.sleep(0.5)\n                    print('sleep')\n                    if 'musics' in cache:\n                        musics = cache.get('musics')\n                        print('break')\n                        break\n    ......\n    return Response(musics, status=status.HTTP_200_OK)\n```\n\n主要是透過 redis 的鎖, 確保當下只有一個 request 可以進 db 拿資料,\n\n剩下的 request 全部 pending, 直到 redis 有資料.\n\n請搭配以下測試\n\n```cmd\nloadtest -H \"Authorization: Basic dHd0cnViaWtzOjEyMw==\" -H \"Accept: application/json;version=1.0\" -n 15 -c 15 -k http://127.0.0.1:8000/api/musics/\n```\n\n(要多測幾次, 確保有顯示 \"pending\" )\n\n![alt tag](https://i.imgur.com/nT6Ar8C.png)\n\n相信這時候大家又會問，不過這樣子 redis 裡面的資料有很高的機會是舊的，沒錯，所以更好的方法，\n\n可以搭配 Celery 設定排成 （ Celery 可以參考我之前寫的 [docker-django-celery-tutorial](https://github.com/twtrubiks/docker-django-celery-tutorial) ），一天或\n\n一段時間更新一次 ( 將 db 裡的資料讀出來寫進 redis 中 )，這樣就可以確保 redis 裡面的資料盡量和 db 裡面一樣。\n\n## 執行畫面\n\n請直接瀏覽 [http://127.0.0.1:8000/images/](http://127.0.0.1:8000/images/)，你會發現有一個 View Count : 1\n\n![alt tag](https://i.imgur.com/e6w8ufP.png)\n\n如果你一直重新整理 ( 狂按 F5 )，會發現 View Count 一直增加\n\n![alt tag](https://i.imgur.com/ivf4HFr.png)\n\n可以點選自己喜歡的圖片進去觀看，也會有屬於這張照片的 View Count\n\n![alt tag](https://i.imgur.com/8pXNI7z.png)\n\n如果你一直重新整理 ( 狂按 F5 )，也會發現 View Count 一直增加\n\n![alt tag](https://i.imgur.com/fFYdLjm.png)\n\n回到首頁 ( [http://127.0.0.1:8000/images/](http://127.0.0.1:8000/images/) )，你會發現多了一個排行榜\n\n![alt tag](https://i.imgur.com/0sRMqjK.png)\n\n當瀏覽越多圖片，排行榜就會向下面這樣 (也顯示每張圖片的 View Count )\n\n![alt tag](https://i.imgur.com/MKH0XBG.png)\n\n## 後記\n\n這次帶大家了解 redis 的一些基礎應用，相信大家一定覺得 redis 真的很有趣，然後 redis 可以做的事情絕對不只\n\n這次教學所帶給大家的，他可以做的應用真非常的多，本教學只是帶給大家一個基礎，了解到底什麼是 redis，\n\n然後可以做怎麼樣的應用，希望這教學對想了解 redis 多少有幫助，謝謝大家:relaxed:\n\n## 執行環境\n\n* Python 3.8\n\n## Reference\n\n* [Django](https://www.djangoproject.com/)\n* [django-redis](https://github.com/jazzband/django-redis)\n* [Redis](https://redis.io/)\n* [loadtest](https://www.npmjs.com/package/loadtest)\n\n## Donation\n\n文章都是我自己研究內化後原創，如果有幫助到您，也想鼓勵我的話，歡迎請我喝一杯咖啡:laughing:\n\n![alt tag](https://i.imgur.com/LRct9xa.png)\n\n[贊助者付款](https://payment.opay.tw/Broadcaster/Donate/9E47FDEF85ABE383A0F5FC6A218606F8)\n\n## License\n\nMIT licens\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango-docker-redis-tutorial","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftwtrubiks%2Fdjango-docker-redis-tutorial","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftwtrubiks%2Fdjango-docker-redis-tutorial/lists"}