redis

django中实现事务的几种方式

# 为什么会有事务?
    -mysql 支持并发---》允许同时有多个客户端来操作
    -出现并发安全问题---》最简单粗暴--》串行化--》效率太低
    -既要保证效率,又要保证数据安全
        -
# 事务四大特性
# 事务隔离级别
# 脏读,不可重复,幻读


# https://zhuanlan.zhihu.com/p/622987268

Django是支持事务操作的,它的默认事务行为是自动提交,
具体表现形式为:每次数据库操作(比如调用save()方法)会立即被提交到数据库中。
    User.objects.filter(id=1).update(name='lqz')
    默认情况,只要sql执行完,就会自动提交,conn.commit()
    
但是如果你希望把连续的SQL操作包裹在一个事务里,就需要手动开启事务
    UpAndDown.objects.create(aritcle_id=1,up=True)  # 提交了
    异常了,UpAndDown已经提交了,不会回滚,数据不一致
    Atricel.object.filter(id=1).update(up_and_down=F(up_and_down)+1) # 提交了


# 根据粒度不同,三种方式手动开启
######## 全局##########
    -全局,每次请求在一个事务中,粒度太大,事务时间很长
    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 保存点  savepoint
    -开启事务
    干了点事
    设置保存点1
    干了点事
    设置一个保存点2
    干了点事
    
    回滚到干完第二个事,回滚到保存点2
    
    
'''
在事务操作中,我们还会经常显式地设置保存点(savepoint)
一旦发生异常或错误,我们使用savepoint_rollback方法让程序回滚到指定的保存点
如果没有问题,就使用savepoint_commit方法提交事务
'''

## 使用with,上下文管理器
# def save_book(request):
#     # 测试事务开启和使用保存点提交和回滚事务
#     with transaction.atomic():
#         sid = transaction.savepoint()
#         print(sid)
#         try:
#             Book.objects.create(name='三国', price=88, xx='yy')
#
#         except Exception as e:
#             transaction.savepoint_rollback(sid)
#         # 没有任何问题,提交保存
#         transaction.savepoint_commit(sid)
#
#     return HttpResponse('新增图书成功')

# 不使用上下文管理器
def save_book(request):
    # 测试事务开启和使用保存点提交和回滚事务
    transaction.atomic()
    sid = transaction.savepoint()
    print(sid)
    try:
        Book.objects.create(name='三国', price=88, xx='yy')

    except Exception as e:
        transaction.savepoint_rollback(sid)
    # 没有任何问题,提交保存
    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'))

django实现悲观锁乐观锁案例

# 线上卖图书
    -图书表  图书名字,图书价格,库存字段
    -订单表: 订单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数据库

原生mysql悲观锁

begin; # 开启事务

# for update 是行锁还是表锁?
select * from book where id = 1 for update;  # 行锁,相当于加了悲观锁
select * from book  for update;  # 表锁 

# order表中加数据
insert into order (name,order_id) values('买了个书''wer0-asdf-31sdf')

update book set count = count - 1 where id = 1; # 更新


commit; #提交事务

orm实现上述

#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('库存不足,秒杀失败')

乐观锁--只要下单人数超过总量,库存一定扣减为0

# 6 乐观锁,实现秒杀
# 乐观锁秒杀--高级版---》只要秒杀人数超过 总数量,库存一定为0
@transaction.atomic
def seckill(request):
    # 锁住查询到的book对象,直到事务结束
    ### 乐观锁可能会失败,我们一直尝试秒杀,直到秒成功或库存不足
    while True:
        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()), 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)
                print('被别人扣减了,继续秒杀')
                continue

        else:
            transaction.savepoint_rollback(sid)
            return HttpResponse('库存不足,秒杀失败')
        return HttpResponse('库存不足,秒杀失败')

同步下单

# 加入celery--》异步悲观锁
异步乐观锁下单

docker-cmopose部署路飞

# 一台服务器:
    -python3.8 环境 djagno +uwsgi+代码
    -nginx软件
    -mysql 5.7
    -redis 
    
# 每个都做成一个容器
    -djagno项目容器:python3.9 构建的django,项目依赖模块,uwsgi,代码
    -nginx容器:目录映射,映射到宿主机,代理vue前端,编译后的静态文件
    -mysql 容器:创建,创用户,密码,luffy库
    -redis 容器,跑起来即可

项目目录结构

luffy
    -docker_compose_files  # nginx有自己的配置文件,redis自己的配置,mysql的配置
        nginx # 文件夹,nginx配置----default.conf  # 手动配置,需要监听两个端口:80 给前端和8000转发给django
        redis # 文件夹
            - redis.conf  # nginx监听地址和端口,持久化方案
        mysql.env # 配置文件,mysql的配置---》key vaule形式---》环境变量
            # 运行mysql容器时, -e参数 ,设置超级用户密码,创建luffy用户和库
            
    -luffy_api  # 原来路飞后端项目
        -Dockerfile  # 用来构建 项目镜像
        -luffy.ini  # 等同于 luffy.xml   uwsgi的配置文件
        -luffy.sql  # 默认数据
    -luffycity  # 前端项目
    
    -docker-compose.yml  # docker-compose的配置文件

配置编译

# 1 git clone https://gitee.com/liuqingzheng/luffy.git
# 2  把luffycity/dist 文件夹删除

# 3 把\luffy\luffycity\src\assets\js\settings.js后端地址改成上线地址(服务器地址)
    export default {
        base_url: "http://10.0.0.111:8000/api/v1/"
    }
# 4 来到前端路径下:luffy\luffycity
cnpm install  # 安装依赖

# 5 编译,在\luffy\luffycity\dist文件夹
npm run build

#6 提交到git上


#7 在部署的机器上,git clone 下来

#8 进入到项目目录
docker-compose up

# 9 导入 sql文件

# 10 访问 服务器地址的80端口,即可

luffy_api/Dockerfile--->构建uwsgi+django

#依赖镜像名称和ID
FROM python:3.8
#指定镜像创建者信息
MAINTAINER lqz
#切换工作目录
RUN mkdir /soft
WORKDIR /soft
COPY ./requestment.txt /soft/requestment.txt
RUN pip install -r requestment.txt -i https://pypi.doubanio.com/simple
CMD ["uwsgi", "./luffy.ini"]

docker-compose.yml

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 

远程链接linux开发

# 操作系统
    -win:笔记本,台式机--》win开发
    -mac:公司直接配 mac ---》类unix系统-->类似于linux
    -linux:有的模块不支持win,只能在linux上开发
        -台式机---》乌班图--》pycharm
        
# 现在就要用win开发---》运行测试环境应该是linux

# 使用win的pycharm---》远程链接linux开发


# centos,乌班图都同理
    -把写好的代码--》放到linux上

 

posted @ 2024-03-18 19:30  拆尼斯、帕丁顿  阅读(12)  评论(0)    收藏  举报