精华内容
下载资源
问答
  • 学生事务管理工作是高校管理工作的一个重要组成部分。...由此,为我国高校建设世界一流大学在学生事务管理方面提供了启示:构建多样化的学生事务管理阶层,生成精准化的学生事务服务体系,创办综合性的实践项目。
  • 墨尔本大学是澳大利亚...提供多样化的学习指导与社会实践服务、个性化的学生生涯辅导与就业服务、专业化的学生心理咨询与健康服务、多元化的社团组织与日常生活管理服务,以推动我国高校学生事务管理工作的改革和创新。
  • 学生事务管理平台自动填写互评 这每个20填过去再提交哪是人填的 ???? ???? ???? ???? 基于科技改命,我选择了写脚本,那自然就得从登录页面开始写了 访问该请求服务器会让我们set-cookie,所以我用requests.session...

    学生事务管理平台自动填写互评

    这每个20填过去再提交哪是人填的
    🐶
    🐶
    🐶
    🐶

    基于科技改命,我选择了写脚本,那自然就得从登录页面开始写了

    访问该请求服务器会让我们set-cookie,所以我用requests.session来维持会话

    抓login包,


    不验证验证码就离谱!!

    源码

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # @Time : 2021-03-18 19:08
    # @Author : wxc
    # @File : 学生事务管理平台.py
    # @Desc :
    import requests
    import urllib3
    import time
    
    print("程序开始.....")
    
    # 忽略verify=False
    requests.packages.urllib3.disable_warnings()
    login_url = "https://xssw.zjgsu.edu.cn/#/login"
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36'
    }
    session = requests.session()
    session.headers = headers
    session.verify = False
    # 维持cookie
    print("正在获取cookies...")
    session.get(url=login_url)
    
    # print(session.cookies)
    xgh = input("请输入账号:")
    password = input("密码:")
    data = {
        # 'captcha': "1234",
        # 'key': "eyJpdiI6IlU1RjFCYmtiSSsxeWV0Sm5BV1VKVkE9PSIsInZhbHVlIjoiUjViVjk1ZkpjOTZuZlpicEl6S2hUM1M4MXE5NG5hUDhZbndyU3RvWnFTUEhjNWU0ZDc4cWpoeXFLOWdsbDZHcVp1YkwrQW1TNCtMenVjd1Z2bEVQaXRjZXBBVFZHYnJCVlMzam04QU5cL01ZPSIsIm1hYyI6IjgzOTI5OGI2OWQzOGM3MmRhZDQ5NzI2MTVhMTI0NTBkODhmODViZDFiOTllMDI1MTU5MjViMzY3ZDdhZWI1NGUifQ==",
        'password': password,
        'unionid': None,
        'xgh': xgh
    }
    login_url = "https://xssw.zjgsu.edu.cn/api/v1/login"
    login_json_data = session.post(url=login_url, data=data).json()
    print(login_json_data)
    if login_json_data['errorCode'] != 0:
        print(login_json_data['data']['message'])
        print("程序结束....")
        time.sleep(5)
        exit(0)
    # 记录 access_token
    access_token = login_json_data['data']['access_token']
    session.cookies['token'] = access_token
    student_desc = session.get(
        "https://xssw.zjgsu.edu.cn/api/v1/home/complex/evaluate/getGroupList?set_id=121&type=1").json()
    for item in student_desc['data']:
        for i in range(1, 3):
            data = {"option1": 20, "option2": 20, "option3": 20, "option4": 20, "option5": 20, "set_id": "121", "type": i,
                    "xgh": item['xgh']}
            url = 'https://xssw.zjgsu.edu.cn/api/v1/home/complex/evaluate/group'
            json_data = session.post(url, json=data).json()
            if i == 1:
                Name = "品德素质"
            else:
                Name = "心理素质"
            total_score = data['option1'] + data['option2'] + data['option3'] + data['option4'] + data['option5']
            print("type=" + Name + " " + item['xm'] + json_data['message'] + "  分数是:" + str(total_score))
    
    print("程序结束....")
    time.sleep(10)
    

    打包好的exe

    https://pan.baidu.com/s/1KfcZM7-iZlySv0_D8vNO7g

    提取码 fifk

    使用说明

    是给所有同学默认都打100分

    并且心理素质和品德素质都是100分

    由于写的比较潦草,可能会出现一些意想不到的Bug,见谅哈

    使用的话直接下载下面的exe打开即可

    程序结束后会过3s退出

    输入用户名和密码

    如果有python环境的直接复制我的代码跑就可了

    另一种方法

    其实要快速填20的话控制台输点js也行

    nodeList = document.querySelectorAll(".el-input__inner")
    for(let i = 6;i<nodeList.length;i++){
    	nodeList[i].value = 20;
    }
    


    每个提交点击过去,注意有个延时

    buttons = document.querySelectorAll(".el-button")
    for(let i = 1;i<buttons.length;i++){
        (function(i) {
            setTimeout(function() {
                buttons[i].click();
            },i * 3000);
        })(i)
    }
    

    展开全文
  • 高校网上事务大厅

    2018-09-07 13:18:56
    务服务模式的发展趋势,同时分析了目前国内运行的学生事务服务大厅的现状与不足,并从 功能定位、组织机构、多部门合作、完善运行制度、专业化队伍和硬件保障等六方面提出了我 校建立“一站式”学生综合事务大厅的...
  • 班级事务管理系统

    2011-12-18 22:14:17
    为班级信息公开、增进同学交流了解服务,同时加强班级管理、提高班级管理与服务的效率和质量。 1.1.2.设计要求 ①、实现用户登录。 ②、实现管理员管理班级事务。(学生信息、成绩、课程、详细信息的增、删、改、查...
  • 前言:以下以网上课程购买流程举一个例子:如何实现两个分布式服务(订单服务、学习服务)共同完成一件事即订单支付成功自动添加学生选课的需求,这里的关键是如何保证两个分布式服务事务的一致性。订单支付结果通知...

    前言:

    以下以网上课程购买流程举一个例子:

    如何实现两个分布式服务(订单服务、学习服务)共同完成一件事即订单支付成功自动添加学生选课的需求,

    这里的关键是如何保证两个分布式服务的事务的一致性。

    订单支付结果通知方法{1)更新支付表中支付状态为成功。2)远程调用选课接口添加记录(Feign调用)

    }

    ​ 尝试解决上边的需求,在订单服务中远程调用选课接口,伪代码如下:

    1.更新支付表状态为本地数据库操作。2.远程调用选课接口为网络远程调用请求3.为保存事务上边两步操作由spring控制事务,当遇到Exception异常则回滚本地数据库操作。

    问题如下:1、如果更新支付表失败则抛出异常,不再执行远程调用,此设想没有问题。2、如果更新支付表成功,网络远程调用超时会拉长本地数据库事务时间,影响数据库性能。

    (远程调用非常耗时的哦)3、如果更新支付表成功,远程调用添加选课成功(选课数据库commit成功),

    最后更新支付表commit失败,此时出现操作不一致。

    上面的问题就涉及到了分布式事务的控制

    一、什么是分布式事务

    (一)分布式系统

    部署在不同结点上的系统通过网络交互来完成协同工作的系统

    比如:充值加积分的业务,用户在充值系统向自己的账户充钱,在积分系统中自己积分相应的增加。

    充值系统和积分系统是两个不同的系统,一次充值加积分的业务就需要这两个系统协同工作来完成。

    (二)、什么是事务

    事务是指由一组操作组成的一个工作单元,这个工作单元具有原子性(atomicity)、

    一致性(consistency)、隔离性(isolation)和持久性(durability)。

    原子性:执行单元中的操作要么全部执行成功,要么全部失败。如果有一部分成功

    一部分失败那么成功的操作要全部回滚到执行前的状态。

    一致性:执行一次事务会使用数据从一个正确的状态转换到另一个正确的状态,执行前后数据都是完整的。

    隔离性:在该事务执行的过程中,任何数据的改变只存在于该事务之中,对外界没有影响,

    事务与事务之间是完全的隔离的。只有事务提交后其它事务才可以查询到最新的数据。

    持久性:事务完成后对数据的改变会永久性的存储起来,即使发生断电宕机数据依然在。

    (三)什么是本地事务

    本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,

    传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。

    (四)、什么是分布式事务

    在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统

    通过网络协同完成的过程称为分布式事务。这里强调的是多个系统通过网络协同完成一个事务的过程,

    并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是

    a201ff19e7db672f476923509568f288.png

    另外一种分布式事务的表现是,一个应用程序使用了多个数据源连接了不同的数据库,

    当一次事务需要操作多个数据源,此时也属

    8cfb47becb668eb1e21cbdcbfb3cf031.png

    (五)、分布式事务的应用场景

    262a020286a0a01f41cd8990e76c855a.png

    (六)、分布式事务解决方案

    从CAP理论引出了很多的解决方案:

    1、两阶段提交协议(2PC):

    2PC的优点:实现强一致性,部分关系数据库支持(Oracle、MySQL等)。

    缺点:整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。

    解决方案有:springboot+Atomikos or Bitronix

    2.事务补偿(TCC):try、Confirm、Cancel

    1、Try 检查及预留业务资源完成提交事务前的检查,并预留好资源。2、Confirm 确定执行业务操作

    对try阶段预留的资源正式执行。3、Cancel 取消执行业务操作

    对try阶段预留的资源释放。

    1、Try

    下单业务由订单服务和库存服务协同完成,在try阶段订单服务和库存服务完成检查和预留资源。

    订单服务检查当前是否满足提交订单的条件(比如:当前存在未完成订单的不允许提交新订单)。

    库存服务检查当前是否有充足的库存,并锁定资源。2、Confirm

    订单服务和库存服务成功完成Try后开始正式执行资源操作。

    订单服务向订单写一条订单信息。

    库存服务减去库存。3、Cancel

    如果订单服务和库存服务有一方出现失败则全部取消操作。

    订单服务需要删除新增的订单信息。

    库存服务将减去的库存再还原。

    优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。

    缺点:开发成本高,每个事务操作每个参与者都需要实现try/confirm/cancel三个接口。

    注意:TCC的try/confirm/cancel接口都要实现幂等性,在为在try、confirm、cancel失败后要不断重试。

    什么是幂等性?

    幂等性是指同一个操作无论请求多少次,其结果都相同。

    幂等操作实现方式有:1、操作之前在业务方法进行判断如果执行过了就不再执行。2、缓存所有请求和处理的结果,已经处理的请求则直接返回结果。3、在数据库表中加一个状态字段(未处理,已处理),数据操作时判断未处理时再处理。

    3.消息队列实现最终一致性(本案例介绍)

    优点 :

    由MQ按异步的方式协调完成事务,性能较高。

    不用实现try/confirm/cancel接口,开发成本比TCC低。

    缺点:

    此方式基于关系数据库本地事务来实现,会出现频繁读写数据库记录,

    浪费数据库资源,另外对于高并发操作不是最佳方案

    二、案例(消息队列实现最终一致性)

    该案例模拟用户订单服务里面下单,然后可以学习该课程,具体流程如下:

    流程:

    1.订单下之后,将准备发送的消息存到任务表里

    2.定时任务扫描消息表(任务表),

    3.消费者接收到消息之后,先去历史表查询是否该消息已经处理,没有处理,则添加该选课

    ,选过了直接返回(利用线程池发送,try,有异常打印日志即可)

    4.接收到添加课程成功的消息,则将对应的任务清除。

    (一)、创建表

    /*任务表*/;

    DROP TABLE IF EXISTS `kc_task`;

    CREATE TABLE `kc_task` (

    `id` varchar(32) NOT NULL COMMENT '主键',

    `yid` varchar(32) NOT NULL COMMENT '业务ID',

    `create_time` datetime NULL COMMENT'创建时间',

    `update_time` datetime NULL COMMENT'更新时间',

    `delete_time` datetime NULL COMMENT'删除时间',

    `task_type` varchar(32) NULL COMMENT '任务类型',

    `mq_exchange` varchar(64) NOT NULL COMMENT '交换机名称',

    `mq_routingkey` varchar(64) NOT NULL COMMENT '路由key',

    `request_body` varchar(512) NOT NULL COMMENT '任务请求的内容',

    `version`int(10) NULL COMMENT '乐观锁版本号',

    `status` varchar(32) NULL COMMENT '任务状态',

    `errormsg` varchar(512) NULL COMMENT '任务错误消息',

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*任务历史表*/;

    DROP TABLE IF EXISTS `kc_task_his`;

    CREATE TABLE `kc_task_his` (

    `id` varchar(32) NOT NULL COMMENT '主键',

    `yid` varchar(32) NOT NULL COMMENT '业务ID',

    `create_time` datetime NULL COMMENT'创建时间',

    `update_time` datetime NULL COMMENT'更新时间',

    `delete_time` datetime NULL COMMENT'删除时间',

    `task_type` varchar(32) NULL COMMENT '任务类型',

    `mq_exchange` varchar(64) NOT NULL COMMENT '交换机名称',

    `mq_routingkey` varchar(64) NOT NULL COMMENT '路由key',

    `request_body` varchar(512) NOT NULL COMMENT '任务请求的内容',

    `version`int(10) NULL COMMENT '乐观锁版本号',

    `status` varchar(32) NULL COMMENT '任务状态',

    `errormsg` varchar(512) NULL COMMENT '任务错误消息',

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*学习课程历史表:可以防止多次消费的作用*/;

    DROP TABLE IF EXISTS `kc_learn_course_his`;

    CREATE TABLE `kc_learn_course_his` (

    `id` varchar(32) NOT NULL COMMENT '主键',

    `yid` varchar(32) NOT NULL COMMENT '业务ID',

    `create_time` datetime NULL COMMENT'创建时间',

    `update_time` datetime NULL COMMENT'更新时间',

    `delete_time` datetime NULL COMMENT'删除时间',

    `task_type` varchar(32) NULL COMMENT '任务类型',

    `mq_exchange` varchar(64) NOT NULL COMMENT '交换机名称',

    `mq_routingkey` varchar(64) NOT NULL COMMENT '路由key',

    `request_body` varchar(512) NOT NULL COMMENT '任务请求的内容',

    `version`int(10) NULL COMMENT '乐观锁版本号',

    `status` varchar(32) NULL COMMENT '任务状态',

    `errormsg` varchar(512) NULL COMMENT '任务错误消息',

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;/*添加课程任务的表*/;

    DROP TABLE IF EXISTS `kc_learn_course`;

    CREATE TABLE `kc_learn_course` (

    `id` varchar(32) NOT NULL COMMENT '主键',

    `course_id` varchar(32) NOT NULL COMMENT '业务ID:课程ID',

    `user_id` varchar(32) NOT NULL COMMENT '用户ID',

    `charge` varchar(32) NULL COMMENT '收费规则',

    `price`float(8,2) NULL COMMENT '课程价格',

    `valid` varchar(32) NULL COMMENT '有效性',

    `start_time` datetime NULL COMMENT'学习开始时间',

    `end_time` datetime NULL COMMENT'学习结束时间',

    `status` varchar(32) NULL COMMENT '选课状态',

    `errormsg` varchar(512) NULL COMMENT '选课错误消息',

    PRIMARY KEY (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    (二)、定时器的使用

    三种完成方法

    1.java自带的API java.util.Timer类 java.util.TimerTask类

    2.Quartz框架 开源 功能强大 使用起来稍显复杂

    3.Spring 3.0以后自带了task 调度工具,比Quartz更加的简单方便

    cron表达式

    886402dae6c077ec3fcfd324fca06036.png

    63c40c650bd39d3adfcc297cc683ce23.png

    @Scheduled所支持的参数:

    1. cron:cron表达式,指定任务在特定时间执行;

    2. fixedDelay:表示上一次任务执行完成后多久再次执行,参数类型为long,单位ms;

    3. fixedDelayString:与fixedDelay含义一样,只是参数类型变为String;

    4. fixedRate:表示按一定的频率执行任务,参数类型为long,单位ms;

    5. fixedRateString: 与fixedRate的含义一样,只是将参数类型变为String;

    6. initialDelay:表示延迟多久再第一次执行任务,参数类型为long,单位ms;

    7. initialDelayString:与initialDelay的含义一样,只是将参数类型变为String;

    8. zone:时区,默认为当前时区,一般没有用到。

    springTask单线程的使用方法

    1.在启动类上加上注解@EnableScheduling

    2.在配置类上加注解@Component,对应的方法上加注解 @Scheduled(fixedDelay = 2000)

    //2s执行一次

    @Scheduled(fixedDelay = 2000)public voidsendChooseCourse(){

    System.out.println("sendChooseCourse time:"+System.currentTimeMillis());

    System.out.println("定时任务:线程名称:"+Thread.currentThread().getName());

    }

    缺点:实际开发开发一般可能会涉及到多个定时任务,不希望所有的任务都运行在一个线程中,想要改成多线程,

    SpringTask提供一个多线程的TaskScheduler,Spring已经有默认实现

    案例

    1.配置定时任务线程池

    package com.lanpo.fenbushiproduct.config;

    import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    import org.springframework.scheduling.annotation.AsyncConfigurer;

    import org.springframework.scheduling.annotation.EnableScheduling;

    import org.springframework.scheduling.annotation.SchedulingConfigurer;

    import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

    import org.springframework.scheduling.config.ScheduledTaskRegistrar;

    import java.util.concurrent.Executor;/*1.@EnableScheduling加了该注解启动类上不用加

    Spring 中,创建定时任务除了使用@Scheduled 注解外,还可以使用 SchedulingConfigurer。

    2.@Schedule 注解有一个缺点,其定时的时间不能动态的改变,而基于 SchedulingConfigurer

    接口的方式可以做到。SchedulingConfigurer 接口可以实现在@Configuration 类上,同时不要忘了,

    还需要@EnableScheduling 注解的支持。

    3.目的是:

    通过实现AsyncConfigurer自定义线程池,包含异常处理

    实现AsyncConfigurer接口对异常线程池更加细粒度的控制

    *a) 创建线程自己的线程池

    b) 对void方法抛出的异常处理的类AsyncUncaughtExceptionHandler*/@Configuration

    @EnableSchedulingpublic classAsyncTaskThreadPool implements SchedulingConfigurer , AsyncConfigurer {private int poolsize=10;

    @OverridepublicAsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return null;

    }

    @BeanpublicThreadPoolTaskScheduler getAsyncTaskThread(){

    ThreadPoolTaskScheduler taskScheduler= newThreadPoolTaskScheduler();

    taskScheduler.initialize();

    taskScheduler.setPoolSize(poolsize);//线程名称前缀

    taskScheduler.setThreadNamePrefix("mythread-po-task");returntaskScheduler;

    }/*SchedulingConfigurer 使用的也是单线程的方式,

    getAsyncTaskThread()方法得到的是springTask提供一个多线程的TaskScheduler*/@Overridepublic voidconfigureTasks(ScheduledTaskRegistrar taskRegistrar) {

    taskRegistrar.setTaskScheduler(getAsyncTaskThread());

    }

    @OverridepublicExecutor getAsyncExecutor() {

    Executor executor=getAsyncTaskThread();returnexecutor;

    }

    }

    2.定时任务异步

    package com.lanpo.fenbushiproduct.config;

    import org.springframework.scheduling.annotation.Scheduled;

    import org.springframework.stereotype.Component;

    @Componentpublic classTaskLeranSchedual {//2s执行一次

    @Scheduled(fixedDelay = 2000)public voidsendChooseCourse(){

    System.out.println("sendChooseCourse time:"+System.currentTimeMillis());

    System.out.println("定时任务异步:线程名称:"+Thread.currentThread().getName());

    }//2s执行一次

    @Scheduled(fixedDelay = 2000)public voidsendChooseCourse2(){

    System.out.println("sendChooseCourse2 time:"+System.currentTimeMillis());

    System.out.println("定时任务异步:线程名称:"+Thread.currentThread().getName());

    }

    }

    结果:

    sendChooseCourse2 time: 1588562646855定时任务异步:线程名称: mythread-po-task4

    sendChooseCourse time:1588562646855定时任务异步:线程名称: mythread-po-task3

    (三)、订单下单到定时器扫描

    1.下单接口

    8d5a654ec1c99ff7d35c78110dda7128.png

    2.课程订单下单然后定时器扫描(之后以courseId作为路由key)

    package com.lanpo.fenbushiproduct.service;

    import com.alibaba.fastjson.JSONArray;

    import com.lanpo.fenbushiproduct.Dao.KcTaskMapper;

    import com.lanpo.fenbushiproduct.config.KcTaskRabbitMq;

    import com.lanpo.fenbushiproduct.pojo.KcLearnCourse;

    import com.lanpo.fenbushiproduct.pojo.KcTask;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Service;

    import org.springframework.transaction.annotation.Transactional;

    import java.util.Date;

    import java.util.List;

    import java.util.UUID;

    @Servicepublic classKcTaskService {private static final Logger Log = LoggerFactory.getLogger(KcTaskService.class);

    @AutowiredprivateKcTaskMapper kcTaskMapper;//客户端下单业务

    @Transactional(rollbackFor = Exception.class)public voidaddKcTask(KcLearnCourse kcLearnCourse) {try {//默认只有一个用户

    KcTask kcTasks =kcTaskMapper.findByYid(kcLearnCourse.getCourseId());if (kcTasks != null) {//更新课程信息的

    } else{

    KcTask kcTask= newKcTask();

    kcTask.setId(UUID.randomUUID().toString());//业务yid和key都是courseId

    kcTask.setYid(kcLearnCourse.getCourseId());

    kcTask.setCreateTime(newDate());

    kcTask.setMqExchange(KcTaskRabbitMq.TASK_TRAN_CHANGE);

    kcTask.setMqRoutingkey(kcLearnCourse.getCourseId());

    kcTask.setRequestBody(JSONArray.toJSON(kcLearnCourse).toString());

    kcTask.setStatus("准备发送");

    kcTask.setTaskType("课程");

    kcTaskMapper.addKcTask(kcTask);

    Log.info("添加任务课程成功");

    }

    }catch(Exception e) {//响应给前端

    Log.info("添加任务课程失败");//使try里面业务回滚

    throw newRuntimeException();

    }

    }//定时任务扫描全部

    public ListfindKcTaskAll() {returnkcTaskMapper.findAll();

    }

    }

    3.课程下单实体

    public classKcLearnCourse implements Serializable {private static final long serialVersionUID = -916357110051689423L;privateString id;privateString courseId;privateString userId;private String charge;//收费规则

    privateString price;private String valid;//有效性

    privateString startTime;privateString endTime;privateString status;privateString errormsg;//省略get和set的方法

    3.配置订单服务的MQ

    1.fenbushiProduct服务(注意:路由和交换机最好和别的不一样,特别是路由配置)

    package com.lanpo.fenbushiproduct.config;

    import org.springframework.amqp.core.*;

    import org.springframework.beans.factory.annotation.Qualifier;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    @Configurationpublic classKcTaskRabbitMq {//队列和交换机

    public static final String TASK_TRAN_QUEUE="TASK_TRAN_QUEUE";public static final String TASK_TRAN_CHANGE="TASK_TRAN_CHANGE";public static final String ROUTING_KEY = "yid.#";//业务ID

    @Bean(TASK_TRAN_CHANGE)//给每一个bean命名可以在本来配置多个

    publicExchange EXCHAGE_TOPIC_TRAN(){//durable持久化,这是交换机名称持久化

    return ExchangeBuilder.topicExchange(TASK_TRAN_CHANGE).durable(true).build();

    }//声明队列

    @Bean(TASK_TRAN_QUEUE)//给每一个bean命名可以在本来配置多个

    publicQueue TRAN_TOPIC_QUEUE(){return newQueue(TASK_TRAN_QUEUE);

    }//绑定交换机和队列

    @BeanpublicBinding BINDING_TASK_CHANGE_AND_QUEUE(@Qualifier(TASK_TRAN_QUEUE) Queue queue

    ,@Qualifier(TASK_TRAN_CHANGE) Exchange exchange){returnBindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();

    }

    }

    4.

    package com.lanpo.fenbushiproduct.config;

    import com.lanpo.fenbushiproduct.pojo.KcTask;

    import com.lanpo.fenbushiproduct.service.KcTaskService;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.amqp.rabbit.connection.CorrelationData;

    import org.springframework.amqp.rabbit.core.RabbitTemplate;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.scheduling.annotation.Scheduled;

    import org.springframework.stereotype.Component;

    import java.util.List;

    import java.util.UUID;

    @Componentpublic classTaskLeranSchedual {private static final Logger Log = LoggerFactory.getLogger(TaskLeranSchedual.class);

    @AutowiredprivateKcTaskService kcTaskService;//默认使用了confirm模式

    @AutowiredprivateRabbitTemplate rabbitTemplate;public static final String KC_TASK_ROUTING_KEY = "yid.";//2s执行一次

    @Scheduled(fixedDelay = 60000)public voidsendChooseCourse() {//扫描任务表,此处暂时扫全表,可以业务需要灵活处理

    List kcTaskAll =kcTaskService.findKcTaskAll();if (kcTaskAll != null) {for(KcTask kcTask : kcTaskAll) {try {//真正的key格式=【yid.a8278a4b-3cc4-467c-9f47-7140a787c7ea】

    CorrelationData correlationData = newCorrelationData(UUID.randomUUID().toString());

    rabbitTemplate.convertAndSend(kcTask.getMqExchange(),KC_TASK_ROUTING_KEY+kcTask.getYid()

    , kcTask.getRequestBody(), correlationData);

    Log.info("【课程任务定时器】准备发送的业务yid:" +kcTask.getYid());

    }catch(Exception e) {

    Log.info("课程任务发生失败,业务ID是:" +kcTask.getYid());

    }

    }

    }

    }

    }

    5.yml配置

    server:

    port:8082spring:

    application:

    name: product-service

    rabbitmq:

    host:127.0.0.1port:5672username: guest

    password: guestvirtual-host: /publisher-confirms: truepublisher-returns: truelistener:

    simple:

    acknowledge-mode: manual

    datasource:

    driver-class-name: com.mysql.jdbc.Driver

    url: jdbc:mysql://localhost:3306/transaction?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC

    password: 152443username: root

    eureka:

    client:

    service-url:

    defaultZone: http://localhost:8081/eureka/

    mybatis:

    mapper-locations: classpath:mapper/*.xml

    type-aliases-package: com.lanpo.fenbushiproduct.pojo

    6.pom.xml

    org.springframework.boot

    spring-boot-starter-web

    org.springframework.cloud

    spring-cloud-starter-netflix-eureka-client

    mysql

    mysql-connector-java

    org.mybatis.spring.boot

    mybatis-spring-boot-starter

    1.3.2

    com.alibaba

    fastjson

    1.2.51

    org.springframework.bootspring-boot-starter-amqp

    org.springframework.bootspring-boot-starter-logging

    (四)、消费者(fenbushiConsumer)

    1.监听队列

    package com.lanpo.fenbushiconsumer.config;

    import com.alibaba.fastjson.JSON;

    import com.lanpo.fenbushiconsumer.pojo.KcLearnCourse;

    import com.lanpo.fenbushiconsumer.pojo.KcLearnCourseHis;

    import com.lanpo.fenbushiconsumer.service.AddCouurseAndHisService;

    import com.lanpo.fenbushiconsumer.service.KcLearnCourseHisService;

    import com.lanpo.fenbushiconsumer.service.ReturnKcTaskMessageService;

    import com.rabbitmq.client.Channel;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.amqp.core.Message;

    import org.springframework.amqp.rabbit.annotation.RabbitListener;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Component;

    import java.io.IOException;

    import java.math.BigDecimal;

    import java.text.SimpleDateFormat;

    import java.util.Date;

    import java.util.Map;

    import java.util.UUID;

    @Componentpublic classReceviceKcTask {private static final Logger Log = LoggerFactory.getLogger(ReceviceKcTask.class);

    @Autowired

    KcLearnCourseHisService kcLearnCourseHisService;

    @Autowired

    AddCouurseAndHisService addCouurseAndHisService;

    @Autowired

    ReturnKcTaskMessageService returnKcTaskMessageService;//监听某一个队列

    @RabbitListener(queues = "TASK_TRAN_QUEUE")public voidgetTaskMessage(String kctask,Message message, Channel channel) throws IOException {

    String yid="";try{

    String receivedRoutingKey=message.getMessageProperties().getReceivedRoutingKey();

    yid= receivedRoutingKey.substring(4);

    KcLearnCourseHis kcLearnCourseHis=kcLearnCourseHisService.findById(yid);

    SimpleDateFormat sformat= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");if(kcLearnCourseHis==null){//等于null添加到学习表,这里可以防止重复提交//封装KcLearnCourse

    KcLearnCourse kcLearnCourse = newKcLearnCourse();

    Map map= JSON.parseObject(kctask, Map.class);

    kcLearnCourse.setId(UUID.randomUUID().toString());

    kcLearnCourse.setCharge((String) map.get("charge"));

    kcLearnCourse.setStartTime( sformat.parse((String) map.get("startTime")));

    kcLearnCourse.setEndTime(sformat.parse((String) map.get("endTime")));

    kcLearnCourse.setPrice(new BigDecimal((String) map.get("price")));

    kcLearnCourse.setCourseId((String) map.get("courseId"));

    kcLearnCourse.setUserId((String) map.get("userId"));

    kcLearnCourse.setStatus((String) map.get("status"));

    kcLearnCourse.setValid((String) map.get("valid"));//封装KcLearnCourseHis

    KcLearnCourseHis kcLearnCourseHis1 = newKcLearnCourseHis();

    kcLearnCourseHis1.setId(UUID.randomUUID().toString());

    kcLearnCourseHis1.setYid(yid);

    kcLearnCourseHis1.setMqRoutingkey(yid);

    kcLearnCourseHis1.setCreateTime(newDate());//课程表和历史表被本地事务管理

    addCouurseAndHisService.addCouurseAndHis(kcLearnCourse,kcLearnCourseHis1);

    Log.info("将订单加到学习表成功 yid(courseId) =" +yid +"准备给订单服务发送消息。。。。。。");

    }else{

    Log.info("该订单已经处理过 yid(courseId) =" +yid +"准备给订单服务发送消息。。。。。。");

    }//告诉订单服务学习表添加成功

    returnKcTaskMessageService.sendReturnKcTaskMessage2(yid,message);//确认收到消息,确认当前消费者的一个消息收到

    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

    }catch(Exception e) {if(message.getMessageProperties().getRedelivered()) {

    Log.info("【课程任务消费者】的该消息被拒绝过,不再放回到队列:{}", message);//被拒绝一次之后,不再放到队列中

    Log.info("该消息yid在此处应该保存到日志中待开发人员手动处理,本案例不再扩展添加到日志的业务");

    channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);

    }else{

    Log.info("【课程任务消费者】的该消息第一次被拒绝,再放回到队列:{}", message);

    channel.basicNack(message.getMessageProperties().getDeliveryTag(),false, true);

    }

    e.printStackTrace();

    }

    }

    }

    2.课程学习历史表

    public classKcLearnCourseHis implements Serializable {private static final long serialVersionUID = -916357110051689486L;privateString id;privateString yid;privateDate createTime;privateDate updateTime;privateDate deleteTime;privateString taskType;privateString mqExchange;privateString mqRoutingkey;privateString requestBody;privateInteger version;privateString status;private String errormsg; //省略get和set

    3.课程学习实体

    public classKcLearnCourse implements Serializable {privateString id;privateString courseId;privateString userId;privateString charge;privateBigDecimal price;privateString valid;privateDate startTime;privateDate endTime;privateString status;privateString errormsg;//省略get和set

    4.关键:保证学习表和历史表一致性

    package com.lanpo.fenbushiconsumer.service;

    import com.lanpo.fenbushiconsumer.pojo.KcLearnCourse;

    import com.lanpo.fenbushiconsumer.pojo.KcLearnCourseHis;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Service;

    import org.springframework.transaction.annotation.Transactional;

    @Servicepublic classAddCouurseAndHisService {

    @Autowired

    KcLearnCourseHisService kcLearnCourseHisService;

    @Autowired

    KcLearnCourseService kcLearnCourseService;//消费端课程学习和历史表开启本地事务

    @Transactional(rollbackFor = Exception.class)public voidaddCouurseAndHis(KcLearnCourse kcLearnCourse, KcLearnCourseHis kcLearnCourseHis){

    kcLearnCourseService.addKClearnCourse(kcLearnCourse);

    kcLearnCourseHisService.addKcLearnCourseHis(kcLearnCourseHis);

    }

    }

    (五)、给订单服务回复消息

    1.发送消息也是confirm模式,配置和订单服务配置一致,返回的消息MQ配置如下

    package com.lanpo.fenbushiconsumer.config;

    import org.springframework.amqp.core.*;

    import org.springframework.beans.factory.annotation.Qualifier;

    import org.springframework.context.annotation.Bean;

    import org.springframework.context.annotation.Configuration;

    @Configurationpublic classConsumerKcTaskRabbitMq {//队列和交换机

    public static final String CONSUMER_TASK_TRAN_QUEUE="CONSUMER_TASK_TRAN_QUEUE";public static final String CONSUMER_TASK_TRAN_CHANGE="CONSUMER_TASK_TRAN_CHANGE";public static final String ROUTING_KEY = "backYid.#";//业务ID

    @Bean(CONSUMER_TASK_TRAN_CHANGE)//给每一个bean命名可以在本来配置多个

    publicExchange EXCHAGE_TOPIC_TRAN(){//durable持久化,这是交换机名称持久化

    return ExchangeBuilder.topicExchange(CONSUMER_TASK_TRAN_CHANGE).durable(true).build();

    }//声明队列

    @Bean(CONSUMER_TASK_TRAN_QUEUE)//给每一个bean命名可以在本来配置多个

    publicQueue TRAN_TOPIC_QUEUE(){return newQueue(CONSUMER_TASK_TRAN_QUEUE);

    }//绑定交换机和队列

    @BeanpublicBinding BINDING_CONSUMER_TASK_CHANGE_AND_QUEUE(@Qualifier(CONSUMER_TASK_TRAN_QUEUE) Queue queue

    ,@Qualifier(CONSUMER_TASK_TRAN_CHANGE) Exchange exchange){returnBindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY).noargs();

    }

    }

    2.给订单服务回消息

    package com.lanpo.fenbushiconsumer.service;

    import com.lanpo.fenbushiconsumer.config.ConsumerKcTaskRabbitMq;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.amqp.core.Message;

    import org.springframework.amqp.rabbit.connection.CorrelationData;

    import org.springframework.amqp.rabbit.core.RabbitTemplate;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Service;

    import java.util.UUID;

    import java.util.concurrent.ExecutorService;

    @Servicepublic classReturnKcTaskMessageService {private static final Logger Log = LoggerFactory.getLogger(MyThreadTask.class);public static final String ROUTING_KEY = "backYid.";//业务ID

    @Autowired

    RabbitTemplate rabbitTemplate;publicvoid sendReturnKcTaskMessage2(String yid, Message message){

    CorrelationData correlationData= newCorrelationData(UUID.randomUUID().toString());try{

    rabbitTemplate.convertAndSend(ConsumerKcTaskRabbitMq.CONSUMER_TASK_TRAN_CHANGE, ROUTING_KEY+yid,"ok", correlationData);

    Log.info("从课程任务消费端发送到订单服务成功: yid是:" +yid);

    }catch(Exception e) {

    Log.info("从课程任务消费端发送到订单服务失败: {}:",message);

    }}

    }

    3.yml配置(pom.xml和订单服务一样)

    server:

    port:8083spring:

    application:

    name: consumer1-service

    rabbitmq:

    host:127.0.0.1port:5672username: guest

    password: guestvirtual-host: /#手动回复ack

    listener:

    simple:

    acknowledge-mode: manual

    publisher-confirms: truepublisher-returns: truedatasource:

    driver-class-name: com.mysql.jdbc.Driver

    url: jdbc:mysql://localhost:3306/transaction?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC

    password: 152443username: root

    eureka:

    client:

    service-url:

    defaultZone: http://localhost:8081/eureka/

    mybatis:

    mapper-locations: classpath:mapper/*.xml

    type-aliases-package: com.lanpo.fenbushiconsumer.pojo

    (六)、订单服务监听发回的消息

    package com.lanpo.fenbushiproduct.config;

    import com.rabbitmq.client.Channel;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.amqp.core.Message;

    import org.springframework.amqp.rabbit.annotation.RabbitListener;

    import org.springframework.stereotype.Component;

    import java.io.IOException;

    @Componentpublic classReceviceKcLearnCourseId {private static final Logger Log = LoggerFactory.getLogger(ReceviceKcLearnCourseId.class);//监听某一个队列

    @RabbitListener(queues = "CONSUMER_TASK_TRAN_QUEUE")public voidgetTaskMessage(String kctask,Message message, Channel channel) throws IOException {try{

    String receivedRoutingKey=message.getMessageProperties().getReceivedRoutingKey();

    System.out.println("从学习服务收到的yid=================================="+receivedRoutingKey);//确认收到消息,确认当前消费者的一个消息收到

    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);

    }catch(Exception e) {if(message.getMessageProperties().getRedelivered()) {

    Log.info("【从学习服务收到消息】的该消息被拒绝过,不再放回到队列:{}", message);//被拒绝一次之后,不再放到队列中

    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);

    }else{

    Log.info("【从学习服务收到消息】的该消息第一次被拒绝,再放回到队列:{}", message);

    channel.basicNack(message.getMessageProperties().getDeliveryTag(),false, true);

    }

    e.printStackTrace();

    }

    }

    }

    (七)、测试

    1.任务表有两条订单课程,定时器每分钟扫一次

    f1f622147bbb6e2de6e405f91ff09b0a.png

    2.为了看出效果,先启动订单服务

    3d2d53edcf061c1c93fd75aceb3c7744.png

    3.消费端学习服务收到的消息,并给订单服务发回消息()

    86d0a7df2ea58d515a75c1b135672983.png

    4.发回的消息队列

    e7687859736b1814975324b3cd0a94bc.png

    5.从学习服务发回的消息courseId

    343a0f07cced0d624fcfe6c0f5d4d4a8.png

    (八)、我们应该把返回的courseId从任务学习表删除。

    package com.lanpo.fenbushiproduct.config;

    import com.lanpo.fenbushiproduct.service.KcTaskService;

    import com.rabbitmq.client.Channel;

    import org.slf4j.Logger;

    import org.slf4j.LoggerFactory;

    import org.springframework.amqp.core.Message;

    import org.springframework.amqp.rabbit.annotation.RabbitListener;

    import org.springframework.beans.factory.annotation.Autowired;

    import org.springframework.stereotype.Component;

    import java.io.IOException;

    @Componentpublic classReceviceKcLearnCourseId {

    @Autowired

    KcTaskService kcTaskService;private static final Logger Log = LoggerFactory.getLogger(ReceviceKcLearnCourseId.class);//监听某一个队列

    @RabbitListener(queues = "CONSUMER_TASK_TRAN_QUEUE")public voidgetTaskMessage(String kctask,Message message, Channel channel) throws IOException {try{//backYid.6a8236c0-961-4823-9242-afdas3c8c261

    String receivedRoutingKey =message.getMessageProperties().getReceivedRoutingKey();

    String yid= receivedRoutingKey.substring(8);

    kcTaskService.deleteByYid(yid);

    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);

    }catch(Exception e) {if(message.getMessageProperties().getRedelivered()) {

    Log.info("【从学习服务收到消息】删除失败被拒绝过,不再放回到队列:{}", message);//被拒绝一次之后,不再放到队列中

    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);

    }else{

    Log.info("【从学习服务收到消息】第一次删除失败,再放回到队列:{}", message);

    channel.basicNack(message.getMessageProperties().getDeliveryTag(),false, true);

    }

    e.printStackTrace();

    }

    }

    }

    总结:  从业务上来看还是有很多不完善的。一使用@RabbitListener注解指定消费方法,

    默认情况是单线程监听队列,可以观察当队列有多个任务时消费端每次只消费一个消息,

    单线程处理消息容易引起消息处理缓慢,消息堆积,不能最大利用硬件资源

    可以配置mq的容器工厂参数,增加并发处理数量即可实现多线程处理监听队列,

    实现多线程处理消息。

    展开全文
  • 事务处理原理 第2版

    热门讨论 2012-12-30 10:49:38
    10.8.3 对象事务服务 10.8.4 jta 10.8.5 服务组件体系结构 10.8.6 osgi联盟 10.8.7 高级消息队列协议 10.9 小结 第11章 未来的发展趋势 11.1 介绍 11.2 云计算 11.3 可伸缩分布式计算 11.4 存储技术 11.5 流和事件...
  • 组织简介:校自管会隶属于学校学生事务部公寓管理中心,全称为“学生公寓自主管理协会”。前身是成立于2006年9月的“学生宿舍管理委员会”。自管会在学生事务部的领导,公寓管理中心的指导下开展工作,是学生参与我...

    6636ce4ca0b8437d09b59bc2091e9e8d.gif

    0f80bc61f8da683b2ea8a3b35612800b.gif

    组织简介:

    校自管会隶属于学校学生事务部公寓管理中心,全称为“学生公寓自主管理协会”。前身是成立于2006年9月的“学生宿舍管理委员会”。自管会在学生事务部的领导,公寓管理中心的指导下开展工作,是学生参与我校学生公寓管理,依据学校规章制度独立履行自己职能的一个校级学生组织。自管会秉承着“自我管理、自我约束、自我教育、自我服务”的理念和意识,全心全意服务广大同学,在“求真、务实、创新”的指导思想下,倾听学生的心声,维护学生的权益,宣传文明典范,努力把学生公寓建设成文明、优美、安全、舒适的“温馨之家”。

    自管会的主要职责是∶

    多方宣传和认真贯彻学生公寓管理的各项规章制度,广泛收集学生对学校学生工作和学生公寓文化建设的意见和建议,切实发挥“上情下达,下情上述”的双重桥梁作用;密切联系同学,了解学生在宿舍中的学习、生活情况,督促学生搞好宿舍文明建设,定期开展学生宿舍纪律、卫生、安全等日常检查工作,积极配合院、系学生宿舍管理,定期举行“星级文明宿舍”等评选活动。

    经过十年的发展壮大,目前,自管会已经成为广西师范大学漓江学院学生组织中,较完善、规范的一支队伍。自管会设主席团、办公室、检评部、生活纪检部、宣传部、外联部、文体部(建设中)共7个职能部门。自成立以来,共计有700多人成为协会的一员,成功举办了十一届“新生杯”寝室设计大赛、学生宿舍文化月等,在全校范围内有较大的影响,为我校的公寓文化建设做出了突出贡献。自管会将全力维护同学们的各项权益,代表广大同学向有关部门提出学生公寓建设的意见和合理化建议,发挥联系学校与学生的桥梁、纽带作用。

    25dcb3c4b292118eb6238cd5c8710cec.gif

    部门职能介绍

    办公室: 

    1、受秘书处直接领导,协助主席团调度、

    2、负责起草和印发相关活动通知;

    3、收集各部门工作计划、工作总结等材料;

    4、做好协会存档工作;

    5、负责协会物资采购、财务及固定资产的管理工作;

    6、负责组织安排协会各类会议、负责晚归表的收集及整理。

    4d90eeff54c0627fe92ea0780a51a32b.png001054f86ae2a618cce4c74d3f73ed70.png检评部:

    1、 安排月检人员及准备相关工作、监督月检、制作回执单并及时反馈月检成绩。

    2、 根据《漓江学院月检制度》评比星级文明宿舍、公示月检成绩。

    3、协助相关部门组织常规工作。

    7a4b2cafcad8d54792486e47c1c88f4a.png2800f8ec272eb95f3664ae56e7432813.gif

    生活纪检部: 

    1、监督检查自管会各部门的日常工作情况;

    2、接受和处理广大同学对自管会学生干部的举报,

    3、负责协会考勤工作;

    4、制作及发放《星级文明寝室》证书;

    5、与各二级学院生活纪检部保持友好联系。

    4bdfca8abd557186764a7f7595438464.png

    916c99feaf2e31a346b42acfe897e167.gif

    宣传部: 

    1、 日常管理并运营漓江学院自管会的公众空间、微信公众号和新浪微博;

    2、活动喷绘、宣传单、宣传海报、画板制作与负责部分活动门票的设计工作;

    3、承担协会活动的摄影,以及视频的拍摄;

    4、利用PS、PR、AE等软件编辑活动的相关相册和视频,作为后期宣传。

    66615e5d80486bcf26fba1de878656ca.png5b407222bdef6ef115c227a372f4f17f.gif

    外联部:

    1、代表漓江学院自管会对本校其他学生组织及各高校自管会保持联系与交流;

    2、负责协会场地审批申请;

    3、邀请嘉宾;

    4、与商家积极接触,洽谈讲座等资金赞助,为大家提供干货颇多的讲座的同时,为自管活动提供有力的资金保障。

    577a8aa2d4677a31378ec0eb7bd1c998.png296c4313f5644ae2ab5fd311dea664dc.gif

    文体部: 

    1、负责协会文体活动的策划与实施,大型活动、晚会的场地设计、布置;

    2、负责协会“第二课堂”相关工作;组织开展各种文娱活动、丰富学生课余文化;

    3、负责协会礼仪培训工作,主持工作;

    4、协助相关部门组织常规工作。

    7759eed68f27487dc6d8bb3a042403f6.png352caa8ca660044f6ccf4bd991d56a54.gif36f97fc22fc7420ca27675d03d67a1ba.png献上我们的合照!

    3a98031205d199a930985b549afead5d.png

    f1609dea7bf6c0914990f5e0add077ef.png

    素材来源:学生公寓自主管理协会

    本期编辑:邓家海

    审核:邓梦涵、马忠惠

    指导老师:王瑞、蓝婷婷

    展开全文
  • 一、为什么存在事务学生时代,学习事务,大都是从一个银行转账的例子开始,让我们回溯一下:假设有如下一张use_balance表,表中有三个字段,username、balance和bankcard:user_balance场景:A用户要给B用户转100块钱...

    一、为什么存在事务

    学生时代,学习事务,大都是从一个银行转账的例子开始,让我们回溯一下:

    假设有如下一张use_balance表,表中有三个字段username、balance和bankcard:

    5f7ae44dfec9d05abe7a17e753bda3aa.png
    user_balance

    场景:A用户要给B用户转100块钱

    假设我们在给A账户减掉100之后,服务突然崩溃了,在没有任何保护措施的情况下,此时user_balance将会变成如下这样:

    f17d99b36ef7096b0b2a92629b9e35d8.png
    user_balance

    可以看到A平白无故少了100块钱,这显然不是我们想要的,我们期望看到的是:无论什么情况下,A和B账户的总和都是1200。

    这个时候就需要用到事务了。事务的出现就是为了保证数据在变更过程中的一致性。

    二、事务

    事务最直观的特性就是要么完全执行,要么都不执行。主要包含以下四个特性(简称ACID):

    1. A,原子性(Atomicity)。原子的概念就是不可分割,你可以把它理解为组成物质的基本单位,也是我们进行数据处理操作的基本单位。
    2. C,就是一致性(Consistency)。一致性指的就是数据库在进行事务操作后,会由原来的一致状态,变成另一种一致的状态。也就是说当事务提交后,或者当事务发生回滚后,数据库的完整性约束不能被破坏。
    3. I,就是隔离性(Isolation)。它指的是每个事务都是彼此独立的,不会受到其他事务的执行影响。
    4. D,指的是持久性(Durability)。事务提交之后对数据的修改是持久性的,即使在系统出故障的情况下,数据的修改依然是有效的。

    在这四个特性中,原子性是基础,隔离性是手段,一致性是约束条件,而持久性是我们的目的

    三、事务隔离级别

    首先需要明确一个概念,事务是为了保证数据在变更过程中的一致性,关键词是变更。在理解了事务的基础上,我们看一下事务隔离级别。

    事务ACID特性中,隔离性是为了防止数据库在并发处理时出现数据不一致的情况。具体来讲就是一个事务在执行的过程中,要避免其他事务的影响

    事务之间完全隔离只存在于串行化模式下。在并发情景下,事务之间无法做到完全隔离,因此会出现不同程度的数据不一致问题,这些问题我们统称为异常,SQL-92 标准定义了如下三种异常:

    1. 脏读(Dirty Read):读到了其他事务还没有提交的数据
    2. 不可重复读(Nnrepeatable Read):对某数据进行读取,发现两次读取的结果不同,也就是说没有读到相同的内容
    3. 幻读(Phantom Read):事务 A 根据条件查询得到了 N 条数据,但此时事务 B 更改或者增加了 M 条符合事务 A 查询条件的数据,这样当事务 A 再次进行查询的时候发现会有 N+M 条数据,产生了幻读

    这里说一下不可重复读和幻读的区别:

    1. 不可重复读是同一条记录的内容被修改了,重点在于UPDATE
    2. 幻读是查询某一个范围的数据行变多了或者少了,重点在于INSERT和DELETE

    为了解决上述数据不一致问题,我们提出了数据隔离级别。SQL-92 标准定义了 4 种隔离级别来解决这些异常情况。解决异常数量从少到多的顺序(比如读未提交可能存在 3 种异常,可串行化则不会存在这些异常)决定了隔离级别的高低,

    这四种隔离级别从低到高分别是:读未提交(READ UNCOMMITTED )、读已提交(READ COMMITTED)、可重复读(REPEATABLE READ)和可串行化(SERIALIZABLE)。

    • 读未提交,也就是允许读到未提交的数据,这种情况下会产生脏读、不可重复读、幻读等情况
    • 读已提交就是只能读到已经提交的内容,可以避免脏读的产生,属于 RDBMS 中常见的默认隔离级别(比如说 Oracle 和 SQL Server)
    • 可重复读,保证一个事务在相同查询条件下两次查询得到的数据结果是一致的,可以避免不可重复读和脏读,但无法避免幻读。MySQL 默认的隔离级别就是可重复读
    • 可串行化,将事务进行串行化,也就是在一个队列中按照顺序执行,可串行化是最高级别的隔离等级,可以解决事务读取中所有可能出现的异常情况,但是它牺牲了系统的并发性

    四.MVCC

    我们都知道,同一时间如果有多个线程并发访问一个资源的时候,就会引起竞争,在没有任何保护措施的情况下,多个线程并发操作资源,就会引起资源的不一致。而为了解决这种不一致,通常我们会对多线程进行串行化处理,而加锁就是其中一种最常用的方法。加锁保证了数据在任何时刻最多只有一个线程在进行访问,保证了数据的完整性和一致性。

    锁有多种划分维度,以程序员的角度来讲,锁可以分为乐观锁和悲观锁。

    • 乐观锁(Optimistic Locking)认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,也就是不采用数据库自身的锁机制,而是通过程序来实现。在程序上,我们可以采用版本号机制或者时间戳机制实现
    • 悲观锁(Pessimistic Locking)对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。

    而我们今天所说的MVCC,就是乐观锁的一种实现方式,MVCC 的英文全称是 Multiversion Concurrency Control,中文翻译过来就是多版本并发控制。

    首先我们看一下为什么需要MVCC。

    以开头那张表为例,假设现在A跟B之间有很多行:

    3d5bb4c7c79742c3689cd0cf3ec3f6bf.png
    user_balance

    场景依然是A与B之间的转账,但同时管理员要进行如下操作,统计表中金额总数:

    SELECT SUM(balance) FROM user_amount

    情景一 在不用MVCC,使用自身锁机制的情况下,A给B转账可能要等很久:

    ac604d53508fb77d59f842bc9386ecf7.png
    A给B转账

    可以看到为了保证数据的一致性,我们需要给统计到的数据行都加上行锁。这时如果 A 所在的数据行加上了行锁,就不能给 B 转账了,只能等到所有操作完成之后,释放行锁再继续进行转账,这样就会造成用户事务处理的等待时间过长。

    情景二 可能会出现死锁

    比如管理员读到 A 有 1000 元的时候,此时 B 开始执行给 A 转账:

    start transaction;
    UPDATE user_balance SET balance=balance-100 WHERE username ='B';
    UPDATE user_balance SET balance=balance+100 WHERE username ='A';
    commit

    此时会发现此时 A 被锁住了,而管理员事务还需要对 B 进行访问,但 B 被用户事务锁住了,此时就发生了死锁。

    c19d5357fa0d92e8179e515bc625cccd.png
    B给A转账

    结论:通过上述两个情景我们会发现在没有其他协助机制的场景下,在并发事务下,我们很容易遇到读写互相阻塞和死锁的问题,而MVCC因采用了乐观锁的机制,所以可以很好的解决读写互相阻塞的问题,降低死锁的概率。而且因为MVCC通过数据行的多个版本管理来实现数据库的并发控制,这样我们就可以通过比较版本号决定数据是否显示出来达到一致性读的效果,从而解决数据一致性问题。

    总结:通过使用MVCC我们可以解决如下几个问题:

    1. 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
    2. 降低了死锁的概率。这是因为 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
    3. 解决一致性读的问题。一致性读也被称为快照读,当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

    下边说一下什么是快照读和当前读

    所谓快照读就是读取的是快照数据,不是实时的数据,不加锁的简单的select都属于快照读:

    SELECT * FROM player WHERE ...

    当前读就是读取实时最新的数据,比如以下操作:

    SELECT * FROM player LOCK IN SHARE MODE;
    SELECT * FROM player FOR UPDATE;
    INSERT INTO player values ...
    INSERT INTO player values ...
    INSERT INTO player values ...

    五. innoDB中MVCC的实现

    MVCC只是一种乐观锁的实现,没有正式的标准,所以不同的DBMS中MVCC的实现方式也不尽相同,以下讲解innoDB中MVCC的实现。

    首先了解几个概念:

    事务版本号

    每开启一个事务,我们都会从数据库中获得一个事务 ID(也就是事务版本号),这个事务 ID 是自增长的,通过 ID 大小,我们就可以判断事务的时间顺序。

    行记录的隐藏列

    InnoDB 的叶子段存储了数据页,数据页中保存了行记录,而在行记录中有一些重要的隐藏字段,如下图所示:

    93ca9876e48caacb29fed3847de5fe89.png
    数据行信息
    1. db_row_id:隐藏的行 ID,用来生成默认聚集索引。如果我们创建数据表的时候没有指定聚集索引,这时 InnoDB 就会用这个隐藏 ID 来创建聚集索引。采用聚集索引的方式可以提升数据的查找效率。
    2. db_trx_id:操作这个数据的事务 ID,也就是最后一个对该数据进行插入或更新的事务 ID。
    3. db_roll_ptr:回滚指针,也就是指向这个记录的 Undo Log 信息。

    Undo Log

    InnoDB 将行记录快照保存在了 Undo Log 里,我们可以在回滚段中找到它们,如下图所示:

    fc8df506a51d0615b6293bcc0376894d.png
    undo log

    从图中你能看到回滚指针将数据行的所有快照记录都通过链表的结构串联了起来,每个快照的记录都保存了当时的 db_trx_id,也是那个时间点操作这个数据的事务 ID。这样如果我们想要找历史快照,就可以通过遍历回滚指针的方式进行查找。

    Read View 是如何工作的

    在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log 里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到 Read View 了,它帮我们解决了行的可见性问题。

    Read View 保存了当前事务开启时所有活跃(还没有提交)的事务列表,换个角度你可以理解为 Read View 保存了不应该让这个事务看到的其他的事务 ID 列表。

    在 Read VIew 中有几个重要的属性:

    1. trx_ids,系统当前正在活跃的事务 ID 集合。
    2. low_limit_id,活跃的事务中最大的事务 ID。
    3. up_limit_id,活跃的事务中最小的事务 ID。
    4. creator_trx_id,创建这个 Read View 的事务 ID。

    如图所示,trx_ids 为 trx2、trx3、trx5 和 trx8 的集合,活跃的最大事务 ID(low_limit_id)为 trx8,活跃的最小事务 ID(up_limit_id)为 trx2。

    e0179cb7c9e5909a4570cfee70eead74.png
    事务ID集合

    假设当前有事务 creator_trx_id 想要读取某个行记录,这个行记录的事务 ID 为 trx_id,那么会出现以下几种情况。

    如果 trx_id < 活跃的最小事务 ID(up_limit_id),也就是说这个行记录在这些活跃的事务创建之前就已经提交了,那么这个行记录对该事务是可见的。

    如果 trx_id > 活跃的最大事务 ID(low_limit_id),这说明该行记录在这些活跃的事务创建之后才创建,那么这个行记录对当前事务不可见。

    如果 up_limit_id < trx_id < low_limit_id,说明该行记录所在的事务 trx_id 在目前creator_trx_id 这个事务创建的时候,可能还处于活跃的状态,因此我们需要在 trx_ids 集合中进行遍历,如果 trx_id 存在于 trx_ids 集合中,证明这个事务 trx_id 还处于活跃状态,不可见。否则,如果 trx_id 不存在于 trx_ids 集合中,证明事务 trx_id 已经提交了,该行记录可见。

    了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过多版本并发控制技术找到它:

    1. 首先获取事务自己的版本号,也就是事务 ID;
    2. 获取 Read View;
    3. 查询得到的数据,然后与 Read View 中的事务版本号进行比较;
    4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
    5. 最后返回符合规则的数据。

    你能看到 InnoDB 中,MVCC 是通过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮我们判断当前版本的数据是否可见。

    需要说明的是,在隔离级别为读已提交时,一个事务中的每一次 SELECT 查询都会获取一次 Read View。而在隔离级别为可重复读时,Read View只会在事务开始的时候创建一次。

    注意:在读已提交的隔离级别下,同样的查询语句每次都会重新获取Read View且这种隔离级别下锁粒度为记录锁,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。当隔离级别为可重复读的时候,因为Read View只会在事务开始的时候创建一次且这种隔离级别下锁粒度为Next-key,因此不会产生不可重复复和幻读。

    下边简单说一下innoDB三种行锁:

    1. 记录锁:针对单个行记录添加锁。
    2. 间隙锁(Gap Locking):可以帮我们锁住一个范围(索引之间的空隙),但不包括记录本身。采用间隙锁的方式可以防止幻读情况的产生。
    3. Next-Key 锁:帮我们锁住一个范围,同时锁定记录本身,相当于间隙锁 + 记录锁,可以解决幻读的问题。

    下边简单说一下读已提交隔离级别下幻读是怎么产生的,以及可重复读情况是幻读是怎么解决的:

    首先明确几个概念:

    1. 行锁是需要的时候才会加上的,也就是说扫描到的行才会加锁,没有扫描到的行不需要加锁
    2. 行锁并不是不需要了就立刻释放,而是要等到事务结束时才释放
    3. 在可重复读隔离级别下,幻读针对的是当前读。因为这种隔离级别下read-view是在事务开始时创建的且后续一直使用,所以如果事务期间一直是快照读的话是不会产生幻读的

    然后我们假设把数据表看成一个列表,每行数据看成列表中的一个元素,假设现在有1、3、5、7这样一个列表:

    在读已提交的情况:假设我们使用索引找到了1和3,这时候1和3加上了排它锁,我们无法更改1和3,但我们可以在1之前,1-3之间以及3之后插入任意的数据,这个时候当你再一次读取的数据时候,就会发现这三个区间多了很多数据,这就产生了幻读

    在可重复度的情况:假设我们使用索引找到了1和3,这个时候因为可重复隔离界别下,锁的粒度是Next-Key,因此这个时候1和3、1之前的区间、1-3之间的区间以及3之后的区间都会被锁住,因此我们没办法操作1和3这两个数据以及这两个数据产生的3个区间,这样下次读取的时候就不会产生多出或者减少的情况,解决了幻读

    六.总结

    事务:是为了解决并发情景下,数据变更过程的一致性

    事务隔离级别:并发模式下不存在事务之间完全的隔离,这也就造成了在事务执行过程中会产生不同程度的异常,而隔离级别的提出,就是为了解决不同程度的数据读取异常

    MVCC:MVCC的提出是为了解决读写之间互相阻塞、死锁问题以及一致性读的问题,其核心是 Undo Log+ Read View。

    注意:MVCC只是乐观锁的一种实现方式,单纯的MVCC是没有办法解决可重复读隔离级别下幻读的问题,必须结合其他的机制。比如innoDB就是采用MVCC+Next-key组合的方式来解决可重复度隔离级别下幻读问题的。

    本文是《SQL必知必会》学习后的笔记,内容多来自此专栏

    展开全文
  • 如何实现两个分布式服务(订单服务、学习服务)共同完成一件事即订单支付成功自动添加学生选课的需求,这里的关键是如何保证两个分布式服务事务的一致性。 解决方案 尝试解决上边的需求,选择基于消息的分布式事务...
  • CMS-订单系统的分布式事务如何处理

    千次阅读 2020-01-06 10:53:55
    如何实现两个分布式服务(订单服务、学习服务)共同完成一件事即订单支付成功自动添加学生选课的需求,这里 的关键是如何保证两个分布式服务事务的一致性。 尝试解决上边的需求,在订单服务中远程调用...
  • 事务(Transaction)是并发控制的基本.单位,它反映了现实世界中需要以一个完整的单位提交的一项工作。SQL Server通过事务机制,将逻辑...举例说,如果我们正在使用UPDATE语句同时对学生表、成绩表中的学号”2003...
  • 编程界的小学生一、事务消息的由来1、案例2、问题3、方案二、事务消息的原理1、原理图解2、详细过程三、事务消息实现流程1、实现流程2、补救方案四、代码实例1、代码2、结果3、管控台4、结果分析五、疑问 一、事务...
  • 了解如何创建依赖于托管数据库服务SQLDB的Node.js应用程序,以处理您的应用程序所需的Web和事务性工作负载。 此内容不再被更新或维护。 全文以PDF格式“按原样”提供。 随着技术的飞速发展,某些内容,步骤或插图...
  • 1 问题描述 根据上边的自动选课的需求,分析如下: 用户支付完成会将支付状态及订单状态保存在订单数据库中,由订单服务去维护订单数据库...的关键是如何保证两个分布式服务事务的一致性。 尝试解决上边的需求,在...
  • 今天比较闲看到大家在群里讨论关于数据库操作的问题,其中谈到了“事务”这个词,坦白讲虽然作为计算机专业的学生,在上学的时候确实知道存储过程、触发器、事务等等这些名词的概念,但是由于毕业后从事的不是服务器...
  • 2分布式事务2.1问题描述...下图是系统结构图:如何实现两个分布式服务(订单服务、学习服务)共同完成一件事即订单支付成功自动添加学生选课的需求,这里的关键是如何保证两个分布式服务事务的一致性。尝试解决...
  •  今天比较闲看到大家在群里讨论关于数据库操作的问题,其中谈到了“事务”这个词,坦白讲虽然作为计算机专业的学生,在上学的时候确实知道存储过程、触发器、事务等等这些名词的概念,但是由于毕业后从事的不是...
  • 学生会工作计划.doc

    2021-01-14 22:06:40
    学生会工作计划 学生会工作计划 一、指导思想: 本学期学生会以为广大师生服务、为学校发展服务为宗旨,在校党支部的领导和团委的指导下,贯彻党的教育方针,结合学校实际情况,代表广大同学参与学校有关管理事务,...
  • 一、为什么存在事务学生时代,学习事务,大都是从一个银行转账的例子开始,让我们回溯一下:假设有如下一张use_balance表,表中有三个字段,username、balance和bankcard:user_balance场景:A用户要给B用户转100块钱...
  • “一个目标(优良校风)”、“两个服务(为提高教育质量服务,为学生发展服务)”、“三个重点(精神状态、纪律水平、习惯养成)”、“四个场所(教室、操场、卫生区、食堂)”、“五个渠道(课堂教育、班会、升旗演讲、板报...
  • 设计学生学籍管理系统

    热门讨论 2009-07-05 18:28:22
    计算机教育发展到今天,尤其是新课程的改革要求我们计算机教师,特别是农村中学的计算机教师要能编写适合于本地特色的一些管理系统来为学校服务,为教学服务。PB9.0无疑是一种较明智的选择。 参考文献 1、 伍俊良...
  • 今天比较闲看到大家在群里讨论关于数据库操作的问题,其中谈到了“事务”这个词,坦白讲虽然作为计算机专业的学生,在上学的时候确实知道存储过程、触发器、事务等等这些名词的概念,但是由于毕业后从事的不是服务器...

空空如也

空空如也

1 2 3 4 5 ... 18
收藏数 345
精华内容 138
关键字:

学生事务服务