# 使用了docker 面臨一個比較大的問題,如果一個djagno專案,使用mysql,redis,不要一次性把所有服務都放到一個容器中,每個服務一個容器,批次的管理多個容器,比較難以操作,於是有了docker-compose # 批次管理,操作docker容器的軟體---》只在單機 # Docker Compose是一個能一次性定義和管理多個Docker容器的工具,單機容器編排【定義和管理】 # 多機容器編排 docker swarm:用的不多 k8s # Docker Compose概念 Compose中定義和啟動的每一個容器都相當於一個服務(service) Compose中能定義和啟動多個服務,且它們之間通常具有協同關係 管理方式: 使用YAML檔案來配置我們應用程式的服務。 使用單個命令(docker-compose up),就可以建立並啟動配置檔案中配置的所有服務。 # 安裝docker-compose (可執行檔案,放在了github上,下載下來即可,速度很慢) # https://github.com/docker/compose/releases wget https://github.com/docker/compose/releases/download/v2.15.1/docker-compose-linux-x86_64 cp ./docker-compose-linux-x86_64 /usr/local/bin/docker-compose # 想在任意路徑下執行docker-compose都有相應----》需要把可執行檔案放到環境變數所在的目錄下 chmod +x /usr/local/bin/docker-compose # rwx rwx rwx 屬主 屬組 其他 r:read 讀許可權 w:wirte 寫許可權 x :執行許可權 屬主 屬組 所有人 chmod 777 檔名 chmod +x rwx r-x -wx 101 101 101 chmod 555 檔名 # 以後在任意位置敲docker-compose都可以 # epel源 -yum install centos倉庫下載 -軟體不全 -epel源 擴充套件源 nginx redis python # 常用命令 # 啟動管理容器 docker-compose up # 會自動搜尋當前路徑下的 docker-compose.yml檔案 docker-compose -f 指定檔案 up docker-compose up -d # 後臺執行,一般我們看日誌輸出,不用這個 docker-compose stop # 停止,不會刪除容器和映象 docker-compose down # 停止,並刪除關聯的容器 docker-compose start # 啟動yml檔案管理的容器 docker-compose ps # 正在執行的容器 docker-compose images # docker-compose管理的映象 docker-compose exec yml檔案中寫的service /bin/bash # 進入到容器內 docker-compose up -d --build # 啟動容器但是重新構建映象,基於重新構建的映象啟動 ##### 如果你沒裝docker####### # 配置yum倉庫 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 下載 docker-ce docker-ce-cli docker-compose-plugin sudo yum install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# flask 專案,使用redis服務---》2個容器
flask 專案容器
redis容器
from flask import Flask from redis import Redis import os app = Flask(__name__) # redis = Redis(host=os.environ.get('REDIS_HOST', '127.0.0.1'), port=6379) redis = Redis(host='redis', port=6379) # 容器的主機名---》flask容器和redis容器是能ping通的,可以透過ip ping 也可以透過主機名ping @app.route('/') def hello(): redis.incr('hits') return '你好! 檢視 %s 次\n' % (redis.get('hits')) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True) ----------------------------------------------------- from flask import Flask from redis import Redis import os app = Flask(__name__) redis = Redis(host='redis', port=6379) @app.route('/') def hello(): redis.incr('hits') return '你好! 檢視 %s 次\n' % (redis.get('hits')) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
FROM python:3.10 WORKDIR /app COPY . /app RUN pip install flask redis -i https://pypi.tuna.tsinghua.edu.cn/simple EXPOSE 5000 CMD [ "python", "app.py" ] # 構建出映象---》一會統一使用 docker-compose構建
version: "3" services: redis: image: redis web: build: context: . dockerfile: Dockerfile ports: - 8080:5000 environment: REDIS_HOST: redis
# 如果redis服務的名字叫redis,我在web服務中(容器中),根據redis名字就能拿到容器 ping redis # 進入到了web,ping redis # 安裝ping命令, apt-get update apt-get install inetutils-ping ping redis # 一鍵部署:redis,和flask ,每個都在一個容器中 docker-compose up -構建flask映象 -reids映象如果不存在,就會拉取 -啟動flask -啟動redis
# 一臺伺服器: -python3.8 環境 djagno +uwsgi+程式碼 -nginx軟體 -mysql 5.7 -redis # 每個都做成一個容器 -djagno專案容器:python3.8 構建的django,專案依賴模組,uwsgi,程式碼 -nginx容器:目錄對映,對映到宿主機,代理vue前端,編譯後的靜態檔案 -mysql 容器:建立,創使用者,密碼,luffy庫 -redis 容器,跑起來即可 # docker-compose yml檔案配置,一鍵啟動 -git clone https://gitee.com/liuqingzheng/luffy.git -目錄結構 luffy luffy_api # 後臺專案 Dockerfile luffycity # 前臺專案 docker_compose_files # 放資料的資料夾 docker-compose.yml #ymal檔案 -docker-compose.yml內容 -Dockefile 檔案 - 修改前端連結後臺的地址:luffycity/src/access/xx.js -編譯:npm run build -提交到git -要部署的伺服器:git clone https://gitee.com/liuqingzheng/luffy.git -docker,docker-compose裝好 -docker-compose up -訪問宿主機的 80 埠
luffy -docker_compose_files # nginx有自己的配置檔案,redis自己的配置,mysql的配置 nginx # 資料夾 redis # 資料夾 mysql.env#配置檔案 -luffy_api # 原來路飛後端專案 -Dockerfile -luffy.ini # luffy.xml uwsgi的配置檔案 -luffycity # 前端專案 -docker-compose.yml # docker-compose的配置檔案 # 把luffycity/dist 資料夾刪除 # 把\luffy\luffycity\src\assets\js\settings.js後端地址改成上線地址(伺服器地址) # 來到前端路徑下:luffy\luffycity cnpm install 安裝依賴 # 編譯,在\luffy\luffycity\dist資料夾 npm run build # 提交到git上 # 在部署的機器上,git clone 下來 # 進入到專案目錄 docker-compose up
luffy_api/Dockerfile--->構建uwsgi+django
FROM python:3.8 MAINTAINER lqz WORKDIR /soft COPY ./requestment.txt /soft/requestment.txt RUN pip install -r requestment.txt -i https://pypi.doubanio.com/simple #CMD ["uwsgi", "-x", "./luffy.xml"] CMD ["uwsgi", "./luffy.ini"] #CMD ["python", "manage_pro.py", "runserver"]
version: "3" services: nginx: image: nginx container_name: luffy_nginx ports: - "80:80" - "8000:8000" restart: always volumes: - ./luffycity/dist:/var/www/html - ./docker_compose_files/nginx:/etc/nginx/conf.d depends_on: - django networks: - web django: build: context: ./luffy_api dockerfile: Dockerfile container_name: luffy_django # command: python manage_pro.py makemigrations && python manage_pro.py migrate && uwsgi ./luffy.ini restart: always ports: - "8080:8080" volumes: - ./luffy_api:/soft environment: - TZ=Asia/Shanghai depends_on: - mysql - redis networks: - web redis: image: redis:6.0-alpine container_name: luffy_redis ports: - "6379:6379" volumes: - ./docker_compose_files/redis/data:/data - ./docker_compose_files/redis/redis.conf:/etc/redis/redis.conf command: redis-server /etc/redis/redis.conf networks: - web mysql: image: mysql:5.7 container_name: luffy_mysql restart: always ports: - "3306:3306" env_file: - ./docker_compose_files/mysql.env volumes: - ./docker_compose_files/mysql/data:/var/lib/mysql - ./docker_compose_files/mysql/logs:/var/log/mysql - ./docker_compose_files/mysql/conf:/etc/mysql/conf.d networks: - web networks: web:
docker-compose up
# https://zhuanlan.zhihu.com/p/622987268 Django是支援事務操作的,它的預設事務行為是自動提交, 具體表現形式為:每次資料庫操作(比如呼叫save()方法)會立即被提交到資料庫中。 但是如果你希望把連續的SQL操作包裹在一個事務裡,就需要手動開啟事務 # 根據粒度不同,三種 ######## 全域性########## -全域性,每次請求在一個事務中,粒度太大,事務時間很長 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'lqz', 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'lqz', 'PASSWORD': 'lqz123', #全域性開啟事務,繫結的是http請求響應整個過程 'ATOMIC_REQUESTS': True, } } # 區域性禁用全域性事務 from django.db import transaction # 區域性禁用事務 @transaction.non_atomic_requests def seckill(request): return HttpResponse('秒殺成功') # 多資料庫,用default的檢視不受事務控制 @transaction.non_atomic_requests(using='default') def seckill(request): return HttpResponse('秒殺成功') ####### 檢視開啟事務############## # fbv開啟 from django.db import transaction @transaction.atomic def seckill(request): return HttpResponse('秒殺成功') # cbv開啟 from django.db import transaction from rest_framework.views import APIView class SeckillAPIView(APIView): @transaction.atomic def post(self, request): pass ################ 區域性使用事務##################### from django.db import transaction def seckill(request): with transaction.atomic(): save() update() return HttpResponse('秒殺成功')
# 1 普通事務操作(手動操作) transaction.atomic() # 開啟事務 transaction.commit() # 提交事務 transaction.rollback() # 回滾事務 # 2 可以使用上下文管理器來控制(自動操作) with transaction.atomic(): # 自動提交和回滾 # 3 儲存點 -開啟事務 幹了點事 設定儲存點1 幹了點事 設定一個儲存點2 幹了點事 回滾到幹完第二個事,回滾到儲存點2 ''' 在事務操作中,我們還會經常顯式地設定儲存點(savepoint) 一旦發生異常或錯誤,我們使用savepoint_rollback方法讓程式回滾到指定的儲存點 如果沒有問題,就使用savepoint_commit方法提交事務 ''' from .models import Book from django.db import transaction def seckill(request): with transaction.atomic(): # 設定回滾點,一定要開啟事務 sid = transaction.savepoint() print(sid) try: book = Book.objects.get(pk=1) book.name = '紅樓夢' book.save() except Exception as e: # 如發生異常,回滾到指定地方 transaction.savepoint_rollback(sid) print('出異常了,回滾') # 如果沒有異常,顯式地提交一次事務 transaction.savepoint_commit(sid) return HttpResponse('秒殺成功') transaction.atomic() # 開啟事務 sid = transaction.savepoint() # 設定儲存點 transaction.savepoint_rollback(sid) # 回滾到儲存點 transaction.savepoint_commit(sid) #提交儲存點
# 有的時候我們希望當前事務提交後立即執行額外的任務,比如客戶下訂單後立即郵件通知賣家 ###### 案例一################## def send_email(): print('傳送郵件給賣家了') def seckill(request): with transaction.atomic(): # 設定回滾點,一定要開啟事務 sid = transaction.savepoint() print(sid) try: book = Book.objects.get(pk=1) book.count = book.count-1 book.save() except Exception as e: # 如發生異常,回滾到指定地方 transaction.savepoint_rollback(sid) else: transaction.savepoint_commit(sid) #transaction.on_commit(send_email) transaction.on_commit(lambda: send_sms.delay('1898288322')) return HttpResponse('秒殺成功') ##### 案例二:celery中使用### transaction.on_commit(lambda: send_sms.delay('1898288322'))
# 線上賣圖書 -圖書表 圖書名字,圖書價格,庫存欄位 -訂單表: 訂單id,訂單名字 # 表準備 class Book(models.Model): name = models.CharField(max_length=32) price = models.IntegerField() # count = models.SmallIntegerField(verbose_name='庫存') class Order(models.Model): order_id = models.CharField(max_length=64) order_name = models.CharField(max_length=32) # 使用mysql DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'lqz', 'HOST': '127.0.0.1', 'PORT': '3306', 'USER': 'lqz', 'PASSWORD': '123', } } # 建立lqz資料庫
begin; # 開啟事務 select * from goods where id = 1 for update; # 行鎖 # order表中加資料 update goods set stock = stock - 1 where id = 1; # 更新 commit; #提交事務
#1 使用悲觀鎖實現下單 @transaction.atomic # 整個過程在一個事物中---》改兩個表:book表減庫存,訂單表生成記錄 def seckill(request): # 鎖住查詢到的book物件,直到事務結束 sid = transaction.savepoint() # 儲存點 # 悲觀鎖: select_for_update() # 加鎖了--》行鎖還是表鎖? 分情況,都有可能 # book = Book.objects.select_for_update().filter(pk=1).first() # 加悲觀鎖,行鎖,鎖住當前行 if book.count > 0: print('庫存可以,下單') # 訂單表插入一條 Order.objects.create(order_id=str(datetime.datetime.now()), order_name='測試訂單') # 庫存-1,扣減的時候,判斷庫存是不是上面查出來的庫存,如果不是,就回滾 time.sleep(random.randint(1, 4)) # 模擬延遲 book.count=book.count-1 book.save() transaction.savepoint_commit(sid) # 提交,釋放行鎖 return HttpResponse('秒殺成功') else: transaction.savepoint_rollback(sid) #回滾,釋放行鎖 return HttpResponse('庫存不足,秒殺失敗')
# 2 樂觀鎖秒殺--普通版 @transaction.atomic def seckill(request): # 鎖住查詢到的book物件,直到事務結束 sid = transaction.savepoint() book = Book.objects.filter(pk=1).first() # 沒加鎖 count = book.count print('現在的庫存為:%s' % count) if book.count > 0: print('庫存可以,下單') Order.objects.create(order_id=str(datetime.datetime.now()), order_name='測試訂單-樂觀鎖') # 庫存-1,扣減的時候,判斷庫存是不是上面查出來的庫存,如果不是,就回滾 # time.sleep(random.randint(1, 4)) # 模擬延遲 res = Book.objects.filter(pk=1, count=count).update(count=count - 1) if res >= 1: # 表示修改成功 transaction.savepoint_commit(sid) return HttpResponse('秒殺成功') else: # 修改不成功,回滾 transaction.savepoint_rollback(sid) return HttpResponse('被別人改了,回滾,秒殺失敗') else: transaction.savepoint_rollback(sid) return HttpResponse('庫存不足,秒殺失敗')