116、Django中实现事物的几种方式 、事物的回滚和保存点、 事物提交后,执行某个回调函数、Django实现悲观锁乐观锁案例、centos上安装redis

205 阅读7分钟

今日内容概要

  • Django中实现事物的几种方式
  • 事物的回滚和保存点
  • 事物提交后,执行某个回调函数
  • Django实现悲观锁乐观锁案例
  • centos上安装redis

今日内容详细

Django中实现事物的几种方式

全局开启事务

1.全局开启事务---> 全局开启事务,绑定的是http请求响应整个过程
	# 在数据库配置建家'ATOMIC_REQUESTS': True, 可以局部禁用事物 @transaction.non_atomic_requests
    DATABASES = {
         'default': {
              #全局开启事务,绑定的是http请求响应整个过程
             'ATOMIC_REQUESTS': True, 
         }
    }
    from django.db import transaction
    # 局部禁用事务
    @transaction.non_atomic_requests
    def seckill(request):
        return HttpResponse('秒杀成功')

一个视图函数在一个事物中

1.fbv开启:@transaction.atomic
    from django.db import transaction
    @transaction.atomic
    def seckill(request):
        return HttpResponse('秒杀成功')
      
2.cbv开启:@transaction.atomic
    from django.db import transaction
    from rest_framework.views import APIView
    class SeckillAPIView(APIView):
        @transaction.atomic
        def post(self, request):
            pass

局部使用事物

1.局部使用事务
from django.db import transaction
def seckill(request):
    with transaction.atomic():
        pass  # 都在一个事物中
    return HttpResponse('秒杀成功')

事物的回滚和保存点

普通事务操作(手动操作)

transaction.atomic()  # 开启事务
transaction.commit()  # 提交事务
transaction.rollback() # 回滚事务

可以使用上下文管理器来控制(自动操作)

with transaction.atomic():  # 自动提交和回滚

保存点

"""
  	开启事务
    干了点事
    设置保存点1
    干了点事
    设置一个保存点2
    干了点事
    回滚到干完第二个事,回滚到保存点2
"""
1.在事务操作中,我们还会经常显式地设置保存点(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) #提交保存点
"""

事务提交后,执行某个回调函数

1.有的时候我们希望当前事务提交后立即执行额外的任务,比如客户下订单后立即邮件通知卖家--->transaction.on_commit()
2.案例一:客户下订单后立即邮件通知卖家
  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('秒杀成功')

3.案例二:celery中使用
	transaction.on_commit(lambda: send_sms.delay('1898288322'))

Django实现悲观锁乐观锁案例

1.线上卖图书
  -图书表  图书名字,图书价格,库存字段
  -订单表: 订单id,订单名字
    
2.表准备
	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)
        
3.使用mysql
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'lqz',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'lqz',
        'PASSWORD': '123',
    }
}

4.创建lqz数据库

原生mysql悲观锁

begin; # 开启事务

select * from goods where id = 1 for update;  # 行锁

# order表中加数据

update goods set stock = stock - 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('库存不足,秒杀失败')
# 2 乐观锁秒杀--高级版本
@transaction.atomic
def seckill(request):
    while True:
        # 锁住查询到的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)
                continue
        else:
            transaction.savepoint_rollback(sid)
            return HttpResponse('库存不足,秒杀失败')

模拟高并发

from threading import Thread
import requests
import time

def task():
    time.sleep(1)
    res = requests.get('http://127.0.0.1:8000/seckill/')
    print(res.text)


if __name__ == '__main__':
    for i in range(100):
        t = Thread(target=task)
        t.start()

centos上安装redis

redis的特点

1.Redis特性(8个)
  1 速度快:10w ops(每秒10w读写),数据存在内存中,c语言实现,单线程模型
  2 持久化:rdb和aof
  3 多种数据结构:
      5大数据结构:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
      BitMaps位图:布隆过滤器 本质是字符串
      HyperLogLog:超小内存唯一值计数,12kb HyperLogLog 本质是 字符串
      GEO:地理信息定位 本质是有序集合
  4 支持多种编程语言:基于tcp通信协议,各大编程语言都支持
  5 功能丰富:发布订阅(消息) Lua脚本,事务(pipeline)
  6 简单:源代码几万行,不依赖外部库
  7 主从复制:主服务器和从服务器,主服务器可以同步到从服务器中
  8 高可用和分布式:
      2.8版本以后使用redis-sentinel支持高可用
      3.0版本以后支持分布式

安装

1.下载
	wget http://download.redis.io/releases/redis-6.2.9.tar.gz
2.解压
	tar -xzf redis-6.2.9.tar.gz
3.建立软连接   # 便于后续软件的更新
  ln -s redis-6.2.9 redis
  cd redis
  make&&make install
3.在src目录下可以看到
  redis-server--->redis服务器
  redis-cli--->redis命令行客户端
  redis-benchmark--->redis性能测试工具
  redis-check-aof--->aof文件修复工具
  redis-check-dump--->rdb文件检查工具
  redis-sentinel--->sentinel服务器,哨兵
  redis作者对windows维护不好,window自己有安装包


卸载redis

1.查看redis进程;
	ps aux|grep redis
2.kill掉进程;
	kill 进程id
3.进入到redis目录
	cd /usr/local/
4.删除redis对应的文件
  rm -f /usr/local/redis/bin/redis*
  rm -f /usr/local/bin/redis*
5.删除对应的文件
	rm -rf redis
  
ps:端口:netstat -nutlp

三种启动方式

1.最简启动
redis-server    # src目录下
ps -ef|grep redis  #查看进程
netstat -antpl|grep redis #查看端口
redis-cli -h ip -p port ping #命令查看
"""
[root@localhost ~]# redis-cli ping
PONG
"""

2.动态参数启动
redis-serve --port 6380 #启动,监听6380端口
"""
[root@localhost redis]# ./src/redis-server --port 6380
[root@localhost ~]# redis-cli -p 6380
127.0.0.1:6380> ping
PONG
"""

3.配置文件启动
	配置文件启动(6379对应手机按键MERZ,意大利女歌手Alessia Merz的名字)

	通过redis-cli连接,输入config get * 可以获得默认配置
    #在redis目录下创建config目录,copy一个redis.conf文件
    #daemonize--》是否是守护进程启动(no|yes)
    #port---》端口号
    #logfile--》redis系统日志
    #dir--》redis工作目录
		'''
    daemonize yes #是否以守护进程启动
    pidfile /var/run/redis.pid   #进程号的位置,删除
    port 6379    #端口号
    dir "/root/lqz/redcdis/data"  #工作目录 
    logfile daemonize yes #是否


    #其他全删掉
    '''

#查看一下默认注释,把#和空格去掉
cat redis.conf|grep -v "#" |grep -v "^$"
#重定向到另一个文件
cat redis.conf|grep -v "#" |grep -v "^$" >redis-6382.conf

#在redis目录下新建data目录,用来存放书籍
#启动redis
redis-server config/redis.conf
#查看进程
ps -ef |grep redis-server |grep 6379
#查看日志
cd data
cat 6379.log

"""
[root@localhost redis]# mv redis.conf redis.conf.bak
[root@localhost redis]# vi redis.conf
	daemonize yes #是否以守护进程启动
  pidfile /var/run/redis.pid   #进程号的位置,删除
  port 6379    #端口号
  dir "/root/redis/data"  #工作目录 
  logfile 6379.log #是否
[root@localhost redis]# mkdir data
[root@localhost redis]# ./src/redis-server ./redis.conf
"""