Vue 响应式原理模拟以及最小版本的 Vue的模拟 - 编程大世界

mikel阅读(314)

来源: Vue 响应式原理模拟以及最小版本的 Vue的模拟 – 编程大世界

在模拟最小的vue之前,先复习一下,发布订阅模式和观察者模式

对两种模式有了了解之后,对Vue2.0和Vue3.0的数据响应式核心原理

1.Vue2.0和Vue3.0的数据响应式核心原理

(1).  Vue2.0是采用Object.defineProperty的方式,对数据进行get,set方法设置的, 具体可以详见Object.defineProperty的介绍

浏览器兼容 IE8 以上(不兼容 IE8)
<script>
    // 模拟 Vue 中的 data 选项
    let data = {
      msg: 'hello'
    }

    // 模拟 Vue 的实例
    let vm = {}

    // 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作
    Object.defineProperty(vm, 'msg', {
      // 可枚举(可遍历)
      enumerable: true,
      // 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)
      configurable: true,
      // 当获取值的时候执行
      get () {
        console.log('get: ', data.msg)
        return data.msg
      },
      // 当设置值的时候执行
      set (newValue) {
        console.log('set: ', newValue)
        if (newValue === data.msg) {
          return
        }
        data.msg = newValue
        // 数据更改,更新 DOM 的值
        document.querySelector('#app').textContent = data.msg
      }
    })

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>

如果,vm里的属性是对象如何处理,可以,对其遍历,在进行Object.defineProperty

<script>
    // 模拟 Vue 中的 data 选项
    let data = {
      msg: 'hello',
      count: 10,
      person: {name: 'zhangsan'}
    }

    // 模拟 Vue 的实例
    let vm = {}

    proxyData(data)

    function proxyData(data) {
      // 遍历 data 对象的所有属性
      Object.keys(data).forEach(key => {
        // 把 data 中的属性,转换成 vm 的 setter/setter
        Object.defineProperty(vm, key, {
          enumerable: true,
          configurable: true,
          get () {
            console.log('get: ', key, data[key])
            return data[key]
          },
          set (newValue) {
            console.log('set: ', key, newValue)
            if (newValue === data[key]) {
              return
            }
            data[key] = newValue
            // 数据更改,更新 DOM 的值
            document.querySelector('#app').textContent = data[key]
          }
        })
      })
    }

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>

(2). Vue3.x是采用proxy代理的方式实现, 直接监听对象,而非属性。ES 6中新增,IE 不支持,性能由浏览器优化,具体可以详见MDN – Proxy

<script>
    // 模拟 Vue 中的 data 选项
    let data = {
      msg: 'hello',
      count: 0
    }

    // 模拟 Vue 实例
    let vm = new Proxy(data, {
      // 执行代理行为的函数
      // 当访问 vm 的成员会执行
      get (target, key) {
        console.log('get, key: ', key, target[key])
        return target[key]
      },
      // 当设置 vm 的成员会执行
      set (target, key, newValue) {
        console.log('set, key: ', key, newValue)
        if (target[key] === newValue) {
          return
        }
        target[key] = newValue
        document.querySelector('#app').textContent = target[key]
      }
    })

    // 测试
    vm.msg = 'Hello World'
    console.log(vm.msg)
  </script>

2.Vue 响应式原理模拟

看图,整体分析

 Vue
  • 把 data 中的成员注入到 Vue 实例,并且把 data 中的成员转成 getter/setter
Observer
  • 能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知 Dep
Compiler
  • 解析每个元素中的指令/插值表达式,并替换成相应的数据
Dep
  • 添加观察者(watcher),当数据变化通知所有观察者
Watcher
  • 数据变化更新视图

 (1) Vue

功能
  • 负责接收初始化的参数(选项)
  • 负责把 data 中的属性注入到 Vue 实例,转换成 getter/setter
  • 负责调用 observer 监听 data 中所有属性的变化
  • 负责调用 compiler 解析指令/插值表达式
class Vue {
    constructor (options) {
        //1.通过属性保存选项的数据
        this.$options = options || {}
        this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
        this.$data = options.data || {}
        //2.把data中的成员转换成getter和setter方法,注入到vue实例中
        this._proxyData(this.$data)
        //3.调用observer对象,监听数据变化
        new Observer(this.$data)
        //4.调用compiler对象, 解析指令和差值表达式
        new Compiler(this)
    }

    _proxyData (data) {
        //遍历data中的所有属性
        Object.keys(data).forEach( key => {
            //把data的属性注入到vue实例中
            Object.defineProperty(this, key, {
                enumerable: true,
                configurable: true,
                get () {
                    return data[key]
                },
                set (newValue) {
                    if (newValue === data[key]) {
                        return 
                    }
                    data[key] = newValue
                }

            })
        })

    }

}

(2)Observer

功能
  • 负责把 data 选项中的属性转换成响应式数据
  • data 中的某个属性也是对象,把该属性转换成响应式数据
  • 数据变化发送通知
class Observer {
    constructor (data) {
        this.walk(data)
    }
    //1.
    walk (data) {
        //1.判断data是不是对象
        if (!data || typeof data !== 'object') {
            return
        }
        //遍历data对象里的所有属性
        Object.keys(data).forEach( key => {
            this.definedReactive(data, key, data[key])
        })
    }

    definedReactive (obj, key, value) {
        let that = this
        //负责收集依赖(观察者), 并发送通知
        let dep = new Dep()

        this.walk(value)//如果data里的属性是对象,对象里面的属性也得是响应式的,所以得判断一下
        
        Object.defineProperty (obj, key, {
            enumerable: true,
            configurable: true,
            get () {
                //收集依赖
                Dep.target && dep.addSubs(Dep.target)
                return value
                // return obj[key]//这么写会引起堆栈溢出
            },
            set (newValue) {
                if (newValue === value) {
                    return 
                }
                
                value = newValue
                that.walk(newValue)//如果赋值为对象,对象里面的属性得是响应式数据

                //数据变换 ,发送通知给watcher的update ,在渲染视图里的数据
                dep.notify()
            }    
                
        }) 
    }

}

(3).Compiler

功能
  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染
  • 当数据变化后重新渲染视图
class Compiler {

    constructor (vm) {//传个vue实例
        this.el = vm.$el
        this.vm = vm
        this.compile(this.el)
    }

    //编译模板, 处理文本节点和元素节点
    compile (el) {

        let childNodes = el.childNodes //获取子节点  伪数组
        console.dir(el.childNodes)
        Array.from(childNodes).forEach( node => {
            if (this.isTextNode(node)) { //是文本节点
                this.compileText(node)
            } else if (this.isElementNode(node)) {//是元素节点
                this.compileElement(node)
            }

            if (node.childNodes && node.childNodes.length) { //子节点里面还有节点,递归遍历获取
                this.compile(node)
            }
        })
    }

    //编译元素节点, 处理指令
    compileElement (node) {
        //console.log(node.attributes)

        Array.from(node.attributes).forEach( attr => {

            //判断是不是指令
            let attrName = attr.name //<div v-text="msg"></div> 里的v-text
            if (this.isDirective(attrName)) {
                //v-text --> text
                attrName = attrName.substr(2)
                let key = attr.value   //<div v-text="msg"></div> 里的msg
                this.update(node , key, attrName) 
            }
        })
    }

    update (node, key, attrName) {
        let updateFn = this[attrName + 'Updater']
        updateFn && updateFn.call(this, node, this.vm[key], key)//call方法改变this指向
    }
    //处理v-text 命令
    textUpdater (node, value, key) {
        node.textContent = value
        new Watcher(this.vm, key, (newValue) => {
            node.textContent = newValue
        })
    }
    //v-model
    modelUpdater (node, value, key) {
        node.value = value
        new Watcher(this.vm, key, (newValue) => {
            node.value = newValue
        })

        //双向绑定,视图改变,数据也会更新
        node.addEventListener('input', () => {
            this.vm[key] = node.value
        })
    }

    //编译文本节点,处理差值表达式
    compileText (node) {
        //console.dir(node)
        // {{  msg   }}
        let reg = /\{\{(.+?)\}\}/
        let value = node.textContent //里面的内容, 也可以是nodeValue
        if (reg.test(value)) {
            let key = RegExp.$1.trim()  //匹配到的第一个
            node.textContent = value.replace(reg, this.vm[key])

            //创建watcher对象, 当数据改变更新视图
            new Watcher(this.vm, key, (newValue) => {
                node.textContent = newValue
            })
        }
    }

    //判断元素属性是否是指令
    isDirective (attrName) {
        return attrName.startsWith('v-')
    }

    //判断节点是否是文本节点
    isTextNode (node) {
        return node.nodeType === 3
    }

    //判断节点是否是元素节点
    isElementNode (node) {
        return node.nodeType === 1
    }
}

(4).Dep(Dependency)

功能

  • 收集依赖,添加观察者(watcher)
  • 通知所有观察者
class Dep {

    constructor () {
        //收集观察者
        this.subs = []
    }

    //添加观察者
    addSubs (watcher) {
        if (watcher && watcher.update) {
            this.subs.push(watcher)
        }
    }
    //数据变换,就调watcher的update方法
    notify () {
        this.subs.forEach(watcher => {
            watcher.update()
        });
    }
}

(5).Watcher

功能

  • 当数据变化触发依赖, dep 通知所有的 Watcher 实例更新视图
  • 自身实例化的时候往 dep 对象中添加自己
class Watcher {
    constructor (vm, key, callback) {
        this.vm = vm
        //data中的属性名
        this.key = key
        this.callback = callback
        //将watcher对象记录在Dep的静态属性target
        Dep.target = this
        //触发get方法,触发get里的addsubs方法,添加watcher
        this.oldValue = vm[key]
        Dep.target = null
    }

    //当数据变化的时候,更新视图
    update () {
        let newValue = this.vm[this.key]
        if (this.oldValue === newValue) {
            return
        }
        this.callback(newValue)
    }
}

总结:

Vue

  • 记录传入的选项,设置 $data/$el
  • 把 data 的成员注入到 Vue 实例
  • 负责调用 Observer 实现数据响应式处理(数据劫持)
  • 负责调用 Compiler 编译指令/插值表达式等
Observer
  • 数据劫持
  • 负责把 data 中的成员转换成 getter/setter
  • 负责把多层属性转换成 getter/setter
  • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
  • 添加 Dep 和 Watcher 的依赖关系
  • 数据变化发送通知
Compiler
  • 负责编译模板,解析指令/插值表达式
  • 负责页面的首次渲染过程
  • 当数据变化后重新渲染
Dep
  • 收集依赖,添加订阅者(watcher)
  • 通知所有订阅者
Watcher
  • 自身实例化的时候往dep对象中添加自己
  • 当数据变化dep通知所有的 Watcher 实例更新视图

Windows 取证之EVTX日志 - 合天网安实验室 - 博客园

mikel阅读(350)

来源: Windows 取证之EVTX日志 – 合天网安实验室 – 博客园

0x0、概述

evtx文件是微软从 Windows NT 6.0(Windows Vista 和 Server 2008) 开始采用的一种全新的日志文件格式。在此之前的格式是 evt 。evtxWindows事件查看器创建,包含Windows记录的事件列表,以专有的二进制XML格式保存。

0x1、EVTX文件结构

evtx文件主要由三部分组成:

  • file header (文件头)

  • chunks (数据块)

  • trailing empty values (尾部填充空值)

File Header(文件头):

文件头长度为4KB(4096bytes),其结构如下:

 

偏移 长度(Bytes) 描述
0x00 8 “ElfFile\x00” 标志位/签名
0x08 8 第一个区块编号(存在时间最久的区块编号)
0x10 8 当前区块编号(块的编号从0开始)
0x18 8 下一条事件记录的ID
0x20 4 128 文件头有效部分的大小
0x24 2 1 次要版本
0x26 2 3 主要版本
0x28 2 4096 文件头的大小
0x2A 2 区块的数量
0x2C 76 未知 (空值)
0x78 4 文件标志
0x7C 4 文件头前 120 bytes 的CRC32校验和
0x80 3968 未知 (空值)

 

我们可以使用Hex编辑器打开一个evtx文件查看一下:

image-20210720163636907

Chunk(块):

每个块的大小是 65536 bytes(64KB),主要由三部分组成:

  • chunk header 块头

  • array of event records 事件记录组

  • unused space 未使用的空间

chunk头长度为512bytes,其结构如下:

 

偏移 长度(Bytes) 描述
0x00 8 “ElfChnk\x00” 标志位/签名
0x08 8 基于日志编号的第一条日志记录的ID
0x10 8 基于日志编号的最后一条日志记录的ID
0x18 8 基于文件编号的第一条日志记录的ID
0x20 8 基于文件编号的最后一条日志记录的ID
0x28 4 128 chunk头大小
0x2C 4 最后一条日志记录的偏移量(相对于块头的起始偏移量)
0x30 4 下一条日志记录的偏移量(相对于块头的起始偏移量)
0x34 4 事件记录数据的 CRC32 校验和
0x38 64 Unknown (空值)
0x78 4 Unknown (flags?)
0x7C 4 块头CRC32校验和(块头前120个字节和128至512字节的数据的CRC32校验和)

 

image-20210721123805851

Event record(事件记录):

事件记录的长度非固定长度,其结构如下:

 

偏移 长度(Bytes) 描述
0x00 4 “\x2a\x2a\x00\x00” 标志位/签名
0x04 4 事件记录的长度
0x08 8 记录ID
0x10 8 日志记录的写入时间(FILETIME)
0x18 不确定 基于二进制XML编码的信息
不确定 4 记录长度(副本)

 

image-20210721131110932

image-20210721131135650

由上面的信息,可知evtx日志文件包含一个4KB的文件头加后面一定数量的64KB大小的块,一个块中记录一定数量(大约100条)的事件记录。每个块是独立的,不受其他块影响。不会出现一条事件记录的数据存在于两个块中。每条记录包含一个基于二进制XML编码的信息。每条事件记录包含其创建时间与事件 ID(可以用于确定事件的种类),因此可以反映某个特定的时间发生的特定的操作,取证人员可以根据日志文件来发现犯罪的过程。

evtx日志文件大概的结构如下所示:

image-20210721132346354

在windows事件查看器中查看:

image-20210721140401309

0x2、EVTX文件的存储

Windows事件日志文件保存在%SystemRoot%\System32\Winevt\Logs路径中。

image-20210721141141511

常见日志文件主要有三个,分别是:System.evtx 、Application.evtx 和Security.evtx。分别是系统日志、应用程序日志和安全日志。

  • System.evtx

    记录操作系统自身组件产生的日志事件,比如驱动、系统组件和应用软件的崩溃以及数据丢失错误等等。

  • Application.evtx

    记录应用程序或系统程序运行方面的日志事件,比如数据库程序可以在应用程序日志中记录文件错误,应用的崩溃记录等。

  • Security.evtx

    记录系统的安全审计日志事件,比如登录事件、对象访问、进程追踪、特权调用、帐号管理、策略变更等。Security.evtx也是取证中最常用到的。

默认情况下,当一个evtx文件的记录满了,日志服务会覆盖最开始的记录,从头开始写入新的记录。也就是相当于一个循环记录的缓存文件。

image-20210721142106536

0x3、Evtx日志分析

Windows 用 Event ID来标识事件的不同含义,拿Security日志来说,一些常见的Event ID 如下:

 

事件ID 描述
4608 Windows 启动
4609 Windows 关机
4616 系统时间发生更改
4624 用户成功登录到计算机
4625 登录失败。使用未知用户名或密码错误的已知用户名尝试登录。
4634 用户注销完成
4647 用户启动了注销过程
4648 用户在以其他用户身份登录时,使用显式凭据成功登录到计算机
4703 令牌权限调整
4704 分配了用户权限
4720 已创建用户账户
4725 账户被禁用
4768 请求Kerberos身份验证票证(TGT)
4769 请求Kerberos服务票证
4770 已续订Kerberos服务票证
4779 用户在未注销的情况下断开了终端服务器会话

 

1、通过Windows事件查看器分析日志

通过Windows事件查看器可以查看当前主机的事件日志,也可以打开保存的 evtx文件。

image-20210721160559304

可以通过点击、筛选、查找等多种方式查看事件日志

image-20210721160753865

筛选器提供了丰富的筛选方式:

image-20210721161013112

2、通过工具分析Evtx

Log Parser

Log Parser(是微软公司自己开发的日志分析工具,它功能强大,使用简单,可以分析基于文本的日志文件、XML 文件、CSV(逗号分隔符)文件,以及操作系统的事件日志、注册表、文件系统、Active Directory。它使用类似 SQL 语句一样查询分析这些数据,还可以把分析结果以图表的形式展现出来。

Log Parser下载地址:https://www.microsoft.com/en-us/download/details.aspx?id=24659

使用方法:

logparser -i:输入文件的格式 -o:输出文件的格式 "查询语句 和文件路径"

例子:

查询登录成功的事件:

LogParser.exe -i:EVT -o:DATAGRID  "SELECT *  FROM E:\Security.evtx where EventID=4624"

image-20210721161849128

还有其他的语法,具体可以查看其帮助信息

>LogParser.exe

Microsoft (R) Log Parser Version 2.2.10
Copyright (C) 2004 Microsoft Corporation. All rights reserved.

Usage:   LogParser [-i:<input_format>] [-o:<output_format>] <SQL query> |
                  file:<query_filename>[?param1=value1+...]
                  [<input_format_options>] [<output_format_options>]
                  [-q[:ON|OFF]] [-e:<max_errors>] [-iw[:ON|OFF]]
                  [-stats[:ON|OFF]] [-saveDefaults] [-queryInfo]

        LogParser -c -i:<input_format> -o:<output_format> <from_entity>
                  <into_entity> [<where_clause>] [<input_format_options>]
                  [<output_format_options>] [-multiSite[:ON|OFF]]
                  [-q[:ON|OFF]] [-e:<max_errors>] [-iw[:ON|OFF]]
                  [-stats[:ON|OFF]] [-queryInfo]

-i:<input_format>   :  one of IISW3C, NCSA, IIS, IISODBC, BIN, IISMSID,
                       HTTPERR, URLSCAN, CSV, TSV, W3C, XML, EVT, ETW,
                       NETMON, REG, ADS, TEXTLINE, TEXTWORD, FS, COM (if
                       omitted, will guess from the FROM clause)
-o:<output_format>  :  one of CSV, TSV, XML, DATAGRID, CHART, SYSLOG,
                       NEUROVIEW, NAT, W3C, IIS, SQL, TPL, NULL (if omitted,
                       will guess from the INTO clause)
-q[:ON|OFF]         :  quiet mode; default is OFF
-e:<max_errors>     :  max # of parse errors before aborting; default is -1
                       (ignore all)
-iw[:ON|OFF]        :  ignore warnings; default is OFF
-stats[:ON|OFF]     :  display statistics after executing query; default is
                       ON
-c                  :  use built-in conversion query
-multiSite[:ON|OFF] :  send BIN conversion output to multiple files
                       depending on the SiteID value; default is OFF
-saveDefaults       :  save specified options as default values
-restoreDefaults    :  restore factory defaults
-queryInfo          :  display query processing information (does not
                       execute the query)


Examples:
LogParser "SELECT date, REVERSEDNS(c-ip) AS Client, COUNT(*) FROM file.log
           WHERE sc-status<>200 GROUP BY date, Client" -e:10
LogParser file:myQuery.sql?myInput=C:\temp\ex*.log+myOutput=results.csv
LogParser -c -i:BIN -o:W3C file1.log file2.log "ComputerName IS NOT NULL"

Help:
-h GRAMMAR                  : SQL Language Grammar
-h FUNCTIONS [ <function> ] : Functions Syntax
-h EXAMPLES                 : Example queries and commands
-h -i:<input_format>        : Help on <input_format>
-h -o:<output_format>       : Help on <output_format>
-h -c                       : Conversion help

Log Parser Studio

logparser的GUI版本。

下载地址:https://techcommunity.microsoft.com/t5/exchange-team-blog/log-parser-studio-2-0-is-now-available/ba-p/593266

其界面如下:

image

Event Log Explorer

Event Log Explorer 是一个非常好用的Windows 日志分析工具,下载地址:https://eventlogxp.com/

image-20210721163315625

LogParser Lizard

LogParser Lizard 是一个功能丰富的Windows 日志分析软件,可以通过类似SQL查询语句对日志筛选查询进行分析。

下载地址:https://lizard-labs.com/log_parser_lizard.aspx

image-20210721163556793

image-20210721163647101

Evtx Explorer/EvtxECmd

具有标准化CSV、XML和json输出的事件日志(Evtx)解析器!

下载地址:https://ericzimmerman.github.io/#!index.md

使用方法:

EvtxECmd.exe -f 日志文件 --xml 输出路径

image-20210721165111773

解析的xml文件结构如下:

image-20210721165205013

0x4、Evtx取证实战

题目来源:Cynet应急响应挑战赛

描述:GOT Ltd 的人力资源主管King-Slayer认为他的电脑上有可疑活动。

2020 年 2 月 8 日,15:00 左右,他发现桌面上出现了一个带有 kiwi标志的文件。据他描述,该文件首次出现在他的桌面后不久就突然消失了。那天晚些时候,他开始收到消息告诉他需要重新激活 Windows Defender。他激活了 Windows Defender,几个小时后又收到了同样的消息。

他决定将这件事告诉他在 IT 部门的朋友——ChrisChris立即将此事报告给了 GOT 的网络安全部门。

该公司的 CISO 立即打电话求助我们,GOT有限公司总部设在瑞士,CISO 向我们发送了来自 King-Slayer的 PC 和域控制器的所有事件日志文件。他希望我们查出异常:

提示:

  • 用户帐户 (KingSlayer) 是他电脑上的本地管理员。

  • 域名 -> GOT.Com

  • DC 服务器名称 -> WIN-IL7M7CC6UVU

  • Jaime(King Slayer)的主机名->DESKTOP-HUB666E(172.16.44.135)

提交攻击者使用的域用户帐户(King-Slayer除外)以及他使用此用户帐户访问的主机的IP地址。

我们拿到的文件包括DC服务的日志和主机日志文件:

image-20210721171750067

给出的文件还有一个提示就是PassTheHash ,表明攻击者使用了该技术。

传递哈希是一种黑客技术,它允许攻击者使用用户密码的基础NTLM或LanMan哈希对远程服务器或服务进行身份验证,而不是像通常情况下那样要求使用关联的明文密码。它取代了仅窃取哈希值并使用该哈希值进行身份验证而窃取明文密码的需要。–via 维基百科

通过日志交叉比对和筛选查找,我们确定了在2020-2-9 21:59左右,有异常登录行为

注意:Windows EVTX 的FILETIME 是 UTC时间,注意转化为瑞士当地时间。

image-20210721173442458

image-20210721173517788

我们发现用户Daenerys在2020年2月9日21:59 (当地时间15:59)通过SMB协议登录到WIN-IL7M7CC6UVU(域控制器),而且使用了PSExec.exe 利用Deanerys用户登录了域控服务器。攻击者可能使用了Mimikatz拿到了Daenerys用户的哈希,然后用于横向移动渗透到DC。

斯坦福“草泥马”火了:100美元就能比肩GPT-3.5,手机都能运行的那种

mikel阅读(315)

来源: 斯坦福“草泥马”火了:100美元就能比肩GPT-3.5,手机都能运行的那种

一夜之间,大模型界又炸出个big news!

斯坦福发布Alpaca(羊驼,网友口中的“草泥马”):

只花100美元,人人都可微调Meta家70亿参数的LLaMA大模型,效果竟可比肩1750亿参数的GPT-3.5(text-davinci-003)。

而且还是单卡就能运行的那种,甚至树莓派、手机都能hold住!

还有一个更绝的“骚操作”。

研究所涉及到的数据集,是斯坦福团队花了不到500美元用OpenAI的API来生成的。

所以整个过程下来,就等同于GPT-3.5自己教出了个旗鼓相当的对手AI。

(薅羊毛高手……)

然后团队还说,用大多数云计算平台去微调训练好的模型,成本也不到100美元:

复制一个GPT-3.5效果的AI,很便宜,很容易,还很小。

而且团队还把数据集(秒省500刀)、代码统统都给开源了,这下子人人都能去微调个效果炸裂的对话AI:

项目在GitHub发布才半天时间,便已经狂揽1800+星,火爆程度可见一斑。

Django联合开发者甚至对斯坦福的新研究用“惊天大事”来形容:

不仅如此,斯坦福团队还搞了个demo,在线可玩的那种。

话不多说,我们现在就来看看这个“草泥马”的效果。

比肩davinci-003的草泥马Aplaca

在斯坦福官方的演示中,他们先小试牛刀地提了一个问题:

什么是羊驼?它和美洲驼的区别是什么?

草泥马Aplaca给出的答案较为干练:

羊驼是一种小型骆驼科动物,原产于秘鲁、玻利维亚、厄瓜多尔和智利;它比美洲驼小,羊毛更细,也没有驼峰。

而后又简单的介绍了二者群居生活的不同。

同样的问题若是交给ChatGPT(GPT3.5-turbo),则答案就不会像草泥马Aplaca那般简洁:

对此,团队给出的解释是:

Alpaca的答案通常比ChatGPT短,反映出text-davinci-003的输出较短。

而后团队演示了让草泥马Alpaca写邮件:

写一封e-mail祝贺被斯坦福大学录取的新生,并提到你很高兴能亲自见到他们。

草泥马Alpaca对于这个任务也是信手拈来,直接给出了一个像模像样的邮件模板:

难度再次进阶,团队这次提出了让草泥马Alpaca写论文摘要的需求:

写一篇经过深思熟虑的机器学习论文摘要,证明42是训练神经网络的最优seed。

草泥马Alpaca给出的答案从内容上来看,非常符合大多数论文的摘要形式:试图回答什么问题、用了什么方法、结果如何,以及未来展望。

当然,也有迫不及待的网友亲自下场试验,发现草泥马Alpaca写代码也是不在话下。

不过即便草泥马Alpaca能够hold住大部分问题,但这并不意味着它没有缺陷。

例如团队便演示了一个例子,在回答“坦桑尼亚的首都是哪里”的问题时,草泥马Alpaca给出的答案是“达累斯萨拉姆”。

但实际上早在1975年便被“多多马”取代了。

除此之外,若是亲自体验过草泥马Alpaca就会发现,它……巨慢:

对此,有网友认为可能是使用的人太多的原因。

笔记本、手机、树莓派都能跑

Meta开源的LLaMA大模型,刚发布几周就被大家安排明白了,单卡就能运行。

所以理论上,基于LLaMA微调的Alpaca同样可以轻松在本地部署。

没有显卡也没关系,苹果笔记本甚至树莓派、手机都可以玩。

在苹果笔记本部署LLaMA的方法来自GitHub项目llama.cpp,使用纯C/C++做推理,还专门对ARM芯片做了优化。

作者实测,M1芯片的MacBook Pro上即可运行,另外也支持Windows和Linux系统。

还是这个C++移植版本,有人成功在4GB内存的树莓派4上成功运行了LLaMA的70亿参数版本。

虽然速度非常慢,大约10秒生成一个token(也就是一分钟蹦出4.5个单词)。

更离谱的是仅仅2天之后,有人把LLaMA模型量化压缩(权重转换成更低精度的数据格式)后成功在Pixel6安卓手机上运行(26秒一个token)。

Pixel6使用谷歌自研处理器Google Tensor,跑分成绩在骁龙865+到888之间,也就是说新一点的手机理论上都能胜任。

微调数据集也开源

斯坦福团队微调LLaMA的方法,来自华盛顿大学Yizhong Wang等去年底提出的Self-Instruct。

以175个问题作为种子任务,让AI自己从中组合出新的问题以及生成配套答案实例,人工过滤掉低质量的,再把新任务添加到任务池里。

所有这些任务,之后可以采用InstructGPT的方法让AI学会如何遵循人类指令。

套娃几圈下来,相当于让AI自己指导自己。

斯坦福版Alpaca,就是花了不到500美元使用OpenAI API生成了5.2万个这样的示例搞出来的。

这些数据同样开源了出来,并且比原论文的数据多样性更高。

同时还给出了生成这些数据的代码,也就是说如果有人还嫌不够,可以再去自行扩充微调数据,继续提高模型的表现。

微调代码也会在HuggingFace官方支持LLaMA后放出。

不过Alpaca最终的模型权重需要Meta许可才能发布,并且继承了LLaMA的非商用开源协议,禁止任何商业用途。

并且由于微调数据使用了OpenAI的API,根据使用条款也禁止用来开发与OpenAI形成竞争的模型。

One More Thing

还记得AI绘画的发展历程吗?

2022年上半年还只是话题热度高,8月份Stable Diffusion的开源让成本下降到可用,并由此产生爆炸式的工具创新,让AI绘画真正进入各类工作流程。

语言模型的成本,如今也下降到了个人电子设备可用的程度。

最后还是由Django框架创始人Simon Willison喊出:

大语言模型的Stable Diffusion时刻到了。

ASP.NET Core Web API 接口限流 - 0611163 - 博客园

mikel阅读(270)

来源: ASP.NET Core Web API 接口限流 – 0611163 – 博客园

一. 前言

ASP.NET Core Web API 接口限流、限制接口并发数量,我也不知道自己写的有没有问题,抛砖引玉。

二. 需求

  1. 写了一个接口,参数可以传多个人员,也可以传单个人员,时间范围限制最长一个月。简单来说,当传单个人员时,接口耗时很短,当传多个人员时,一般人员会较多,接口耗时较长,一般耗时几秒。
  2. 当传多个人员时,并发量高时,接口的耗时就很长了,比如100个用户并发请求,耗时可长达几十秒,甚至1分钟。
  3. 所以需求是,当传单个人员时,不限制。当传多个人员时,限制并发数量。如果并发用户数少于限制数,那么所有用户都能成功。如果并发用户数,超出限制数,那么超出的用户请求失败,并提示”当前进行XXX查询的用户太多,请稍后再试”。
  4. 这样也可以减轻被请求的ES集群的压力。

三. 说明

  1. 使用的是.NET6
  2. 我知道有人写好了RateLimit中间件,但我暂时还没有学会怎么使用,能否满足我的需求,所以先自己实现一下。

四. 效果截图

下面是使用jMeter并发测试时,打的接口日志:

五. 代码

1. RateLimitInterface

接口参数的实体类要继承该接口

using JsonA = Newtonsoft.Json;
using JsonB = System.Text.Json.Serialization;

namespace Utils
{
    /// <summary>
    /// 限速接口
    /// </summary>
    public interface RateLimitInterface
    {
        /// <summary>
        /// 是否限速
        /// </summary>
        [JsonA.JsonIgnore]
        [JsonB.JsonIgnore]
        bool IsLimit { get; }
    }
}

2. 接口参数实体类

继承RateLimitInterface接口,并实现IsLimit属性

public class XxxPostData : RateLimitInterface
{
    ...省略

    /// <summary>
    /// 是否限速
    /// </summary>
    [JsonA.JsonIgnore]
    [JsonB.JsonIgnore]
    public bool IsLimit
    {
        get
        {
            if (peoples.Count > 2) //限速条件,自己定义
            {
                return true;
            }
            return false;
        }
    }
}

3. RateLimitAttribute

作用:标签打在接口方法上,并设置并发数量

namespace Utils
{
    /// <summary>
    /// 接口限速
    /// </summary>
    public class RateLimitAttribute : Attribute
    {
        private Semaphore _sem;

        public Semaphore Sem
        {
            get
            {
                return _sem;
            }
        }

        public RateLimitAttribute(int limitCount = 1)
        {
            _sem = new Semaphore(limitCount, limitCount);
        }
    }
}

4. 使用RateLimitAttribute

标签打在接口方法上,并设置并发数量。
服务器好像是24核的,并发限制为8应该没问题。

[HttpPost]
[Route("[action]")]
[RateLimit(8)]
public async Task<List<XxxInfo>> Query([FromBody] XxxPostData data)
{
    ...省略
}

5. 限制接口并发量的拦截器RateLimitFilter

/// <summary>
/// 接口限速
/// </summary>
public class RateLimitFilter : ActionFilterAttribute
{
    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        Type controllerType = context.Controller.GetType();
        object arg = context.ActionArguments.Values.ToList()[0];
        var rateLimit = context.ActionDescriptor.EndpointMetadata.OfType<RateLimitAttribute>().FirstOrDefault();

        bool isLimit = false; //是否限速
        if (rateLimit != null && arg is RateLimitInterface) //接口方法打了RateLimitAttribute标签并且参数实体类实现了RateLimitInterface接口时才限速,否则不限速
        {
            RateLimitInterface model = arg as RateLimitInterface;
            if (model.IsLimit) //满足限速条件
            {
                isLimit = true;
                Semaphore sem = rateLimit.Sem;

                if (sem.WaitOne(0)) //注意:超时时间为0,表示不等待
                {
                    try
                    {
                        await next.Invoke();
                    }
                    catch
                    {
                        throw;
                    }
                    finally
                    {
                        sem.Release();
                    }
                }
                else
                {
                    var routeList = context.RouteData.Values.Values.ToList();
                    routeList.Reverse();
                    var route = string.Join('/', routeList.ConvertAll(a => a.ToString()));
                    var msg = $"当前访问{route}接口的用户数太多,请稍后再试";
                    LogUtil.Info(msg);
                    context.Result = new ObjectResult(new ApiResult
                    {
                        code = (int)HttpStatusCode.ServiceUnavailable,
                        message = "当前查询的用户太多,请稍后再试。"
                    });
                }
            }
        }

        if (!isLimit)
        {
            await next.Invoke();
        }
    }
}

上述代码说明:sem.WaitOne(0)这个超时时间,最好是0,即不等待,否则高并发下会有问题。SemaphoreSlim的异步wait没试过。如果超时时间大于0,意味着,高并发下,会有大量的等待,异步等待也是等待。
SemaphoreSlim短时间是自旋,想象一下一瞬间产生大量自旋会怎么样?所以最好不等待,如果要等待,那代码还得再研究研究,经过测试才能用。

6. 注册拦截器

//拦截器
builder.Services.AddMvc(options =>
{
    ...省略

    options.Filters.Add<RateLimitFilter>();
});

六. 使用jMeter进行压力测试

测试结果:

  1. 被限速的接口,满足限速条件的调用并发量大时,部分用户成功,部分用户提示当前查询的人多请稍后再试。但不影响未满足限速条件的传参调用,也不影响其它未限速接口的调用。
  2. 测试的所有接口、所有查询参数条件的调用,耗时稳定,大量并发时,不会出现接口耗时几十秒甚至1分钟的情况。

七. 同时测试三个接口

测试三个接口,一个是触发限流的A接口,一个是未触发限流的A接口,一个是未被限流的B接口。

jMeter测试设置

触发限流的A接口,并发量设置为200:

未触发限流的A接口以及未被限流的B接口,并发量设置为1:

测试日志截图


截图说明:可以看到被限流接口共1000次调用,只有大约40次调用是成功的,剩下的返回请稍后再试。


截图说明:实际上触发限流的接口,并发量为8,压力依然很大,会拖慢自身以及其它接口,当触发限流的接口请求结束时,其它接口访问速度才正常。

八. 实际情况

  1. 这种接口计算量大,是难以支持高并发的,需要限流。争取客户的理解,仅支持少量用户在同一时间查询。
  2. 实际上只要用户错开几秒访问,接口的耗时就很正常。问题是,如何错开几秒呢?当用户看到”请稍后再试”的提示,关闭提示,重新点击查询,就可以错开了。如果一次两次不行,就多点几次查询。

九. 后续

  1. 修改为使用SemaphoreSlim类,这样可以异步等待
  2. RateLimitAttribute类增加了超时时间属性

代码如下:

1. RateLimitAttribute

/// <summary>
/// 接口限速
/// </summary>
public class RateLimitAttribute : Attribute
{
    private SemaphoreSlim _sem;

    public SemaphoreSlim Sem
    {
        get
        {
            return _sem;
        }
    }

    /// <summary>
    /// 超时时间(单位:毫秒)
    /// </summary>
    private int _timeout;

    /// <summary>
    /// 超时时间(单位:毫秒)
    /// </summary>
    public int Timeout
    {
        get
        {
            return _timeout;
        }
    }

    /// <summary>
    /// 接口限速
    /// </summary>
    /// <param name="limitCount">限制并发数量</param>
    /// <param name="timeout">超时时间(单位:秒)</param>
    public RateLimitAttribute(int limitCount = 1, int timeout = 0)
    {
        _sem = new SemaphoreSlim(limitCount, limitCount);
        _timeout = timeout * 1000;
    }
}

2. RateLimitFilter

/// <summary>
/// 接口限速
/// </summary>
public class RateLimitFilter : ActionFilterAttribute
{
    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        Type controllerType = context.Controller.GetType();
        object arg = context.ActionArguments.Values.ToList()[0];
        var rateLimit = context.ActionDescriptor.EndpointMetadata.OfType<RateLimitAttribute>().FirstOrDefault();

        bool isLimit = false;
        if (rateLimit != null && arg is RateLimitInterface)
        {
            RateLimitInterface model = arg as RateLimitInterface;
            if (model.IsLimit) //满足限速条件
            {
                isLimit = true;
                SemaphoreSlim sem = rateLimit.Sem;

                if (await sem.WaitAsync(rateLimit.Timeout))
                {
                    try
                    {
                        await next.Invoke();
                    }
                    catch
                    {
                        throw;
                    }
                    finally
                    {
                        sem.Release();
                    }
                }
                else
                {
                    var routeList = context.RouteData.Values.Values.ToList();
                    routeList.Reverse();
                    var route = string.Join('/', routeList.ConvertAll(a => a.ToString()));
                    var msg = $"当前访问{route}接口的用户数太多,请稍后再试";
                    LogUtil.Info(msg);
                    context.Result = new ObjectResult(new ApiResult
                    {
                        code = (int)HttpStatusCode.ServiceUnavailable,
                        message = "当前查询的用户太多,请稍后再试。"
                    });
                }
            }
        }

        if (!isLimit)
        {
            await next.Invoke();
        }
    }
}

效果

  1. 假如设置RateLimit(1, 0),即并发1,超时时间0,那么当100个并发请求时,只有1个成功,99个失败。
  2. 假如接口耗时2秒,设置RateLimit(1, 10),即并发1,超时时间10秒,那么当100个并发请求时,会有大约5个成功,95个失败。第1个成功的接口请求耗时大约2秒,后续成功的4个,请求耗时依次增加。
  3. 当设置了并发量和超时时间后,接口平均一秒钟能被请求多少次,取决于接口耗时,耗时短的接口平均每秒能被请求的次数多,耗时长的接口平均每秒能被请求的次数少。

 PHP: ThinkPHP获取客户端IP地址_thinkphp 获取ip_彭世瑜的博客-CSDN博客

mikel阅读(222)

来源: (1条消息) PHP: ThinkPHP获取客户端IP地址_thinkphp 获取ip_彭世瑜的博客-CSDN博客

1、ThinkPHP5通过助手函数即可获取

request()->ip()
1
2、通过自己解析

function get_client_ip($type = 0)
{
$type = $type ? 1 : 0;
static $ip = NULL;

if ($ip !== NULL) {
return $ip[$type];
}

if (isset($_SERVER[‘HTTP_X_REAL_IP’])) {
//nginx 代理模式下,获取客户端真实IP
$ip = $_SERVER[‘HTTP_X_REAL_IP’];
} elseif (isset($_SERVER[‘HTTP_CLIENT_IP’])) {
//客户端的ip
$ip = $_SERVER[‘HTTP_CLIENT_IP’];
} elseif (isset($_SERVER[‘HTTP_X_FORWARDED_FOR’])) {
//浏览当前页面的用户计算机的网关
$arr = explode(‘,’, $_SERVER[‘HTTP_X_FORWARDED_FOR’]);
$pos = array_search(‘unknown’, $arr);
if (false !== $pos) unset($arr[$pos]);
$ip = trim($arr[0]);
} elseif (isset($_SERVER[‘REMOTE_ADDR’])) {
//浏览当前页面的用户计算机的ip地址
$ip = $_SERVER[‘REMOTE_ADDR’];
} else {
$ip = $_SERVER[‘REMOTE_ADDR’];
}
// IP地址合法验证
$long = sprintf(“%u”, ip2long($ip));
$ip = $long ? array($ip, $long) : array(‘0.0.0.0’, 0);
return $ip[$type];
}

参考
获取客户端IP地址
————————————————
版权声明:本文为CSDN博主「彭世瑜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mouday/article/details/121626509

ThinkPHP获取客户端IP地址

mikel阅读(187)

ThinkPHP中有一个自带的函数,用于获取客户端计算机的IP地址:get_client_ip()。

调用的方式很简单:
$client_ip = get_client_ip();

除了ThinkPHP内置get_client_ip函数外,也可使用下面函数获取客户端IP地址。

/**
* 获取客户端的IP地址
* @param $type 表示返回类型 0 返回IP地址, 1 返回IPV4地址数字
*/

function get_client_ip($type = 0) {
$type       =  $type ? 1 : 0;
static $ip  =   NULL;
if ($ip !== NULL) return $ip[$type];
if (isset($_SERVER[‘HTTP_X_FORWARDED_FOR’])) {
$arr    =   explode(‘,’, $_SERVER[‘HTTP_X_FORWARDED_FOR’]);
$pos    =   array_search(‘unknown’,$arr);
if(false !== $pos) unset($arr[$pos]);
$ip     =   trim($arr[0]);
}elseif (isset($_SERVER[‘HTTP_CLIENT_IP’])) {
$ip     =   $_SERVER[‘HTTP_CLIENT_IP’];
}elseif (isset($_SERVER[‘REMOTE_ADDR’])) {
$ip     =   $_SERVER[‘REMOTE_ADDR’];
}
// IP地址合法验证
$long = ip2long($ip);
$ip   = $long ? array($ip, $long) : array(‘0.0.0.0’, 0);
return $ip[$type];
}

thinkphp I()方法获取不到ajax传值 - 蜡笔没有小新 - 博客园

mikel阅读(215)

来源: thinkphp I()方法获取不到ajax传值 – 蜡笔没有小新 – 博客园

把json数据post给PHP,但在PHP里面$_post获取不到,I(‘id’)也获取不到,$_REQUEST也获取不到,可以看下ajax的contentType设置的数据类型是什么,php对对型如text/xml 或者 soap 或者 application/octet-stream 之类的内容无法解析,原来PHP默认只识别application/x-www.form-urlencoded标准的数据类型,我们只要将ajax的contentType设置成application/x-www.form-urlencoded就可以,或者使用file_get_contents(“php://input”)。下面我们在来看TP中的I()方法是如何做的?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
function I($name,$default='',$filter=null) {
    if(strpos($name,'.')) { // 指定参数来源
        //判断参数$name中是否包括.号
        list($method,$name) =   explode('.',$name,2);
        //如果包括.号将.号前后分隔,并且分别赋值给$method以及$name
    }else// 默认为自动判断
        //如果没有.号
        $method =   'param';
    }
    switch(strtolower($method)) {//将$method转换为小写
        //如果$method为get,则$input为$_GET
        case 'get'     :   $input =& $_GET;break;
        //如果$method为get,则$input为$_POST
        case 'post'    :   $input =& $_POST;break;
        //如果为put,则将post的原始数据转参数给$input
        case 'put'     :   parse_str(file_get_contents('php://input'), $input);break;
        //如果是param
        case 'param'   :
            //判断$_SERVER['REQUEST_METHOD']
            switch($_SERVER['REQUEST_METHOD']) {
                //如果为post,则$input的内容为$_POST的内容
                case 'POST':
                    $input  =  $_POST;
                    break;
                //如果为PUT.则input的内容为PUT的内容
                case 'PUT':
                    parse_str(file_get_contents('php://input'), $input);
                    break;
                //默认为$_GET的内容
                default:
                    $input  =  $_GET;
            }
            break;
        //如果$method为request,则$input为$_REQUEST
        case 'request' :   $input =& $_REQUEST;   break;
        //如果$method为session,则$input为$_SESSION
        case 'session' :   $input =& $_SESSION;   break;
        //如果$method为cookie,则$input为$_COOKIE
        case 'cookie'  :   $input =& $_COOKIE;    break;
        //如果$method为server,则$input为$_SERVER
        case 'server'  :   $input =& $_SERVER;    break;
        //如果$method为globals,则$input为$GLOBALS
        case 'globals' :   $input =& $GLOBALS;    break;
        //默认返回空
        default:
            return NULL;
    }
    /**
     * 到此为止,已经根据传入的参数的需要(第一个参数.号前面的),把所有的变量都取到了。下面就是返回变量的内容了。
    **/
    //如果$name为空,也就是I()第一个参数的.号后面为空的时候
    if(empty($name)) { // 获取全部变量
        //获取到的变量$input全部复制给$data
        $data       =   $input;
        //array_walk_recursive — 对数组中的每个成员递归地应用用户函数
        //将$data的键值作为filter_exp函数的第一个参数,键名作为第二个参数
        //如果$data的键值中含有or或者exp这两个字符,自动在后面加一个空格
        array_walk_recursive($data,'filter_exp');
        //判断过滤参数是否有,如果有的话,就直接使用过滤方法,如果没有的话,就使用配置中的过滤方法
        $filters    =   isset($filter)?$filter:C('DEFAULT_FILTER');
        if($filters) {
            $filters    =   explode(',',$filters);
            //将过滤参数中的每个方法都应用到$data中
            foreach($filters as $filter){
                //将$data的每个值使用$filters过滤
                $data   =   array_map_recursive($filter,$data); // 参数过滤
            }
        }
    }elseif(isset($input[$name])) { // 取值操作
        $data       =   $input[$name];
        is_array($data) && array_walk_recursive($data,'filter_exp');
        $filters    =   isset($filter)?$filter:C('DEFAULT_FILTER');
        if($filters) {
            $filters    =   explode(',',$filters);
            foreach($filters as $filter){
                if(function_exists($filter)) {
                    $data   =   is_array($data)?array_map_recursive($filter,$data):$filter($data); // 参数过滤
                }else{
                    $data   =   filter_var($data,is_int($filter)?$filter:filter_id($filter));
                    if(false === $data) {
                        return   isset($default)?$default:NULL;
                    }
                }
            }
        }
    }else// 变量默认值
        $data       =    isset($default)?$default:NULL;
    }
    return $data;
}

从源码中我们可以看到I方法的$method分为get、post、put、param,当ajax的contentType不是标准类型时,在我们接受值时可以写成

1
I('put.id')

就可以正常接收到值了

sql中2中分页 row_number() 和OFFSET, FETCH 分页(2012特有)_飞天海里鱼的博客-CSDN博客

mikel阅读(287)

来源: sql中2中分页 row_number() 和OFFSET, FETCH 分页(2012特有)_飞天海里鱼的博客-CSDN博客

第一个种分页,使用row_number()  over(order by)

比如:

CREATE TABLE [dbo].[Student](
[id] [int] IDENTITY(1,1) NOT NULL,
[name] [varchar](50) NULL

)

 

declare @pageIndex int=3
declare @pageSize int=5

select * from

(

select ROW_NUMBER() over(order by ID) as numbers,ID,name from student

)  c where numbers between (@pageIndex-1)*@pageSize+1 and @pageIndex*@pageSize

  1. ORDER BY order_by_expression
  2. [ COLLATE collation_name ]
  3. [ ASC | DESC ]
  4. [ ,…n ]
  5. [ <offset_fetch> ]
  6. <offset_fetch> ::=
  7. {
  8. OFFSET { integer_constant | offset_row_count_expression } { ROW | ROWS }
  9. [
  10. FETCH { FIRST | NEXT } {integer_constant | fetch_row_count_expression } { ROW | ROWS } ONLY
  11. ]

第二种

select * from student order by ID OFFSET (@pageIndex-1)*@pageSize ROW FETCH next @pageSize rows only

推荐采用第二种

SQL分页查询:offset xxx rows fetch next xxx rows only 方式_陈皮糖chen的博客-CSDN博客

mikel阅读(302)

来源: SQL分页查询:offset xxx rows fetch next xxx rows only 方式_陈皮糖chen的博客-CSDN博客

分页:offset xxx rows fetch next xxx rows only 方式
分页实现的方法又多了一种,在SQL Server 2012版本中,TSQL在Order By子句中新增 Offset-Fetch子句,用于从有序的结果集中,跳过一定数量的数据行,获取指定数量的数据行,从而达到数据行分页的目的。经过测试,从逻辑读取数量和响应的时间消耗来测评,使用Offset-Fetch实现的分页方式,比Row_Number()方式性能要高很多。

Offset-Fetch子句要求结果集是有序的,因此,只能用于order by 子句中,语法如下:

// x1:跳过的行数 x2:显示的行数
ORDER BY 字段 ASC/DESC offset x1 rows fetch next x2 rows only例如:

// LEN(字段名)取长度 @pageIndex:第几页 @pageLine:一页显示的条数
// 假如一页显示3条数据,查询第2页需要显示的数据,则
// ((@pageIndex – 1) * @pageLine)=(2-1)*3 @pageLine=3
ORDER BY LEN(MW.WREA_CODE),MW.WREA_CODE
offset((@pageIndex – 1) * @pageLine) rows fetch next @pageLine rows only

关键字解析:
•Offset子句:用于指定跳过(Skip)的数据行;
•Fetch子句:该子句在Offset子句之后执行,表示在跳过(Sikp)指定数量的数据行之后,返回一定数据量的数据行;
•执行顺序:Offset子句必须在Order By 子句之后执行,Fetch子句必须在Offset子句之后执行;

分页实现的思路:
1.在分页实现中,使用Order By子句,按照指定的columns对结果集进行排序;
2.使用Offset子句跳过前N页:Offset (@PageIndex-1)*@RowsPerPage rows;
3.使用Fetch子句呈现当前Page:Fetch next @RowsPerPage rows only;
————————————————
版权声明:本文为CSDN博主「陈皮糖chen」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_45659376/article/details/107336143

SQL中用OFFSET FETCH NEXT 分页的坑__学而时习之_的博客-CSDN博客

mikel阅读(280)

来源: (1条消息) SQL中用OFFSET FETCH NEXT 分页的坑__学而时习之_的博客-CSDN博客

SQL2012后的版本中支持了OFFSET index FETCH NEXT page_size ROWS ONLY的分页方式,但要分页就必须要有排序,而排序的字段选择的不对,就有可能造成分页结果不正确,比如第1页和第2页的数据有重复。为什么呢?

究其原因,是因为排序字段的数据不唯一,或才有null,比如按价格排序,有同一价格的商品很多,结果就会造成排序错误。所以需要选择唯一值的字段来排序,比如自增的索引、GUID、KEY等,示例代码如下

DECLARE @PageIndex INT
DECLARE @PageSize INT
SET @PageIndex=0
SET @PageSize=10
SELECT * FROM TProduct ORDER BY Product_Key DESC
OFFSET @PageIndex * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY

————————————————
版权声明:本文为CSDN博主「_学而时习之_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xxdddail/article/details/89173470