2024年11月

微软憋大招:SQL Server + Copilot = 地表最强AI数据库!

微软布局代码AI霸主地位

微软在人工智能领域的布局引人注目,尤其在代码生成领域,微软通过Copilot展现出了强大的竞争力。Copilot是基于人工智能的大模型代码助手工具,能够帮助开发者快速生成代码,大幅提升生产力。微软凭借其在AI技术领域的领先地位,正在不断推动Copilot的发展,并且早已没有任何对手。

从github被收购说起

微软在2018年以75亿美元的价格收购了全球最大的代码托管平台GitHub,这一步棋可以说是微软AI布局的重要战略举措。GitHub上拥有超过3000万开发者,管理着超过9600万个代码仓库。这些代码仓库不仅是开发者们合作和分享代码的平台,更是海量数据的根据地。对于微软来说,这些数据正是训练Copilot所需的核心资源。

通过GitHub,微软能够接触到全球开发者的工作成果和代码实践,进而为Copilot提供更为丰富和精准的训练资源。随着Copilot不断优化和升级,微软已经逐渐将其转变为每个开发者的"智能助手"。不仅如此,微软还致力于让开发者能够定制属于自己的Copilot智能体(Agent),从而满足不同开发场景的个性化需求。

普通PC和手机本地大模型普及

随着硬件技术的提升,普通PC就能运行千亿参数的模型,而几台机器的集群甚至可以处理万亿级参数。微软在代码AI领域的布局,将极大地推动这一趋势的实现,代码生成工具将发生质的飞跃,进一步巩固微软在AI领域的霸主地位。

GitHub,这个曾被戏称为“世界上最大的同性交友网站”,如今早已脱胎换骨,成为了微软在代码AI领域的“数据引擎”。未来,或许每个开发者都会有一个属于自己的Copilot,在日常开发中如影随形,微软的AI生态也将在全球开发者的支持下愈发强大。

微软的终极布局

预计在下一个SQL Server版本中, Copilot 和 SQL Server 将会深度集成,到时候一系列 Text2SQL、RAG以及向量数据库等的 AI 基础功能会投喂给用户。微软Copilot 辅助SQL Server数据库管理员和开发者完成数据操作,自动生成优化的 SQL 语句,预测和处理数据库运行中的潜在问题,比如数据库自动故障自愈。微软正通过 SQL Server 和 Copilot 的深度结合,打造一个集成 AI 和数据库能力的全新生态系统,以应对 Oracle数据库 这个老对手推出的 AI 数据库
Oracle23AI

另外,Oracle 23c (c代表 Cloud)在2024年5月2日 改名为Oracle 23ai,意味着Oracle从

转为
AI
的战略,这个方向盘着实打的有点大,可以说,现在数据库AI大战一触即发。

Copilot 的引入也进一步降低了企业使用数据库的门槛,特别是PC集成了大模型之后,PC上运行的SQL Server可以帮助用户通过自然语言与数据库进行交互。 甚至在不了解 SQL 语法的情况下完成数据查询、分析等任务。 结合 Copilot,未来的 SQL Server 将不仅服务于数据专家,还能成为低代码/无代码用户的数据分析工具,赋能更多企业和开发者。

实际上,微软在SQL Server2016开始就已经提供了机器学习服务( Machine Learning Services),这个机器学习服务可以让你在数据库上使用Python、R、Java等语言
直接训练
机器学习
模型
!以后在每个大版本中都在完善机器学习服务这个功能,也就是说SQL Server在2016这个版本就已经有这种
遥遥领先

功能
了!

内置的编程语言如下

Python执行架构如下

安装界面截图如下

目前
SQL Server 2025
的CTP版本迟迟未出,估计微软是在憋大招,有跳票的可能性。

目前微软已向 OpenAI 投资近
140 亿
美元,此外,目前OpenAI的国内服务只有Azure公有云提供,除了Azure公有云并没有其他渠道。

按照这个发展趋势,未来微软也有可能把OpenAI 的核心技术
GPT v5.0
移植进去
SQL Server 2005
,目前就看微软怎么把
大模型
塞进SQL Server,所以
微软
会在
AI数据库上遥遥领先
吗,元芳,你怎么看?

本文版权归作者所有,未经作者同意不得转载。

为什么要有 Buffer Pool?

虽然说 MySQL 的数据是存储在磁盘里的,但是也不能每次都从磁盘里面读取数据,这样性能是极差的。

要想提升查询性能,那就加个缓存。所以,当数据从磁盘中取出后,缓存内存中,下次查询同样的数据的时候,直接从内存中读取。

为此,Innodb 存储引擎设计了一个缓冲池(Buffer Pool),来提高数据库的读写性能。

  • 当读取数据时,如果数据存在于 Buffer Pool 中,客户端就会直接读取 Buffer Pool 中的数据,否则再去磁盘中读取。
  • 当修改数据时,首先是修改 Buffer Pool 中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘。

Buffer Pool里有什么

InnoDB 会把存储的数据划分为若干个页,以页作为磁盘和内存交互的基本单位,一个页的默认大小为 16KB。因此,Buffer Pool 同样需要按页来划分。

Buffer Pool里面包含很多个缓存页,同时每个缓存页还有一个描述数据,也可以叫做是控制数据,也可以叫做描述数据,或者缓存页的元数据。控制块数据,控制数据包括「缓存页的表空间、页号、缓存页地址、链表节点」等等,控制块数据就是为了更好的管理Buffer Pool中的缓存页的。

控制块也是占有内存空间的,它是放在 Buffer Pool 的最前面,接着才是缓存页,如下图:

Buffer Pool 除了缓存「索引页」和「数据页」,还包括了 undo 页,插入缓存、自适应哈希索引、锁信息等等。

数据库启动的时候,是如何初始化Buffer Pool的

数据库只要一启动,就会按照设置的Buffer Pool大小,稍微再加大一点,去找操作系统申请一块内存区域,作为Buffer Pool的内存区域。

当内存区域申请完毕之后,数据库就会按照默认的缓存页的16KB的大小以及对应的800个字节左右的描述数据的大小,在Buffer Pool中划分出来一个一个的缓存页和一个一个的他们对应的描述数据。

只不过这个时候,Buffer Pool中的一个一个的
缓存页都是空的
,里面什么都没有,要等数据库运行起来之后,当对数据执行增删改查的操作的时候,才会把数据对应的页从磁盘文件里读取出来,放入Buffer Pool中的缓存页中。

管理Buffer Pool

管理空闲页-free链表

如何知道哪些缓存页是空的

当数据库运行起来之后,系统肯定会不停的执行增删改查的操作,此时就需要不停的从磁盘上读取一个一个的数据页放入Buffer Pool中的对应的缓存页里去,把数据缓存起来,那么以后就可以在内存里对这个数据执行增删改查了。

但是此时在从磁盘上读取数据页放入Buffer Pool中的缓存页的时候,必然涉及到一个问题,那就是哪些缓存页是空闲的?

因为默认情况下磁盘上的数据页和缓存页是一 一对应起来的,都是16KB,一个数据页对应一个缓存页。数据页只能加载到空闲的缓存页里,所以MySql必须要知道Buffer Pool中哪些缓存页是空闲的状态?

MySQL数据库会为Buffer Pool设计了一个
free链表
,是一个
双向链表
数据结构,这个free链表里,每个节点就是一个空闲的缓存页的描述数据块的地址,也就是说,只要你一个缓存页是空闲的,那么它的描述数据块就会被放入这个free链表中。

刚开始数据库启动的时候,所有的缓存页都是空闲的,因为此时可能是一个空的数据库,一条数据都没有,所以此时所有缓存页的描述数据块,都会被放入这个free链表中。

这个free链表里面就是各个缓存页的控制块,只要缓存页是空闲的,那么他们对应的控制块就会加入到这个free链表中,每个节点都会双向链接自己的前后节点,组成一个双向链表。

除此之外,这个free链表有一个基础节点,它会引用链表的头节点和尾节点,里面还存储了链表中当前有多少个节点,也就是
链表中有多少个控制块的节点
,也就是有多少个空闲的缓存页。

磁盘上的页如何读取到Buffer Pool的缓存页中去?

  • 首先,需要从free链表里获取一个控制块,然后就可以获取到这个控制块对应的空闲缓存页;
  • 接着就可以把磁盘上的数据页读取到对应的缓存页里去,同时把相关的一些数据写入控制块里去,比如这个数据页所属的表空间之类的信息
  • 最后把那个控制块从free链表里去除就可以了。

MySQL怎么知道某个数据页已经被缓存了

  • 在执行增删改查的时候,肯定是先看看这个数据页有没有被缓存,如果没被缓存就走上面的逻辑,从free链表中找到一个空闲的缓存页,从磁盘上读取数据页写入缓存页,写入控制数据,从free链表中移除这个控制块。
  • 但是如果数据页
    已经被缓存
    了,那么就会直接使用了。所以其实数据库还会有一个哈希表数据结构,他会用表空间号+ 数据页号,作为一个key,然后缓存页的地址作为value。当你要使用一个数据页的时候,通过“表空间号+数据页号”作为key去这个哈希表里查一下,如果没有就读取数据页,如果已经有了,就说明数据页已经被缓存了

MySQL引入了一个数据页缓存哈希表的结构,也就是说,每次你读取一个数据页到缓存之后,都会在这个哈希表中写入一个key-value对,key就是表空间号+数据页号,value就是缓存页的地址,那么下次如果你再使用这个数据页,就可以从哈希表里直接读取出来它已经被放入一个缓存页了。

管理脏页-flush链表

为什么会有脏页

如果你要更新的数据页都会在Buffer Pool的缓存页里,供你在内存中直接执行增删改的操作。mysql此时一旦更新了缓存页中的数据,那么缓存页里的数据和磁盘上的数据页里的数据,就不一致了,那么就说这个缓存页是脏页。

脏页怎么刷回磁盘

为了能快速知道哪些缓存页是脏的,于是就设计出 Flush 链表,它跟 Free 链表类似的,链表的节点也是控制块,区别在于 Flush 链表的元素都是脏页。

有了 Flush 链表后,后台线程就可以遍历 Flush 链表,将脏页写入到磁盘。

提高缓存命中率-LRU链表

Buffer Pool 的大小是有限的,对于一些频繁访问的数据希望可以一直留在 Buffer Pool 中,而一些很少访问的数据希望可以在某些时机可以淘汰掉,从而保证 Buffer Pool 不会因为满了而导致无法再缓存新的数据,同时还能保证常用数据留在 Buffer Pool 中。

缓存命中率是什么?

假设现在有两个缓存页,一个缓存页的数据,经常会被修改和查询,比如在100次请求中,有30次都是在查询和修改这个缓存页里的数据。那么此时我们可以说这种情况下,缓存命中率很高,为什么呢?因为100次请求中,30次都可以操作缓存,不需要从磁盘加载数据,这个缓存命中率就比较高了。

另外一个缓存页里的数据,就是刚从磁盘加载到缓存页之后,被修改和查询过1次,之后100次请求中没有一次是修改和查询这个缓存页的数据的,那么此时我们就说缓存命中率有点低,因为大部分请求可能还需要走磁盘查询数据,他们要操作的数据不在缓存中。

所以针对上述两个缓存页,当缓存页都满了的时候,第一个缓存页命中率很高,因此肯定是选择将第二个缓存页刷入磁盘中,从而释放缓存页。

因此就引入LRU链表来判断哪些缓存页是不常用的。Least Recently Used,最近最少使用。整体思想就是,链表头部的节点是最近使用的,而链表末尾的节点是最久没被使用的。那么,当空间不够了,就淘汰最久没被使用的节点,从而腾出空间。

简单版的LRU链表

  • 当访问的页在 Buffer Pool 里,就直接把该页对应的 LRU 链表节点移动到链表的头部。
  • 当访问的页不在 Buffer Pool 里,除了要把页放入到 LRU 链表的头部,还要淘汰 LRU 链表末尾的节点。

比如下图,假设 LRU 链表长度为 5,LRU 链表从左到右有 1,2,3,4,5 的页。

如果访问了 3 号的页,因为 3 号页在 Buffer Pool 里,所以把 3 号页移动到头部即可。

而如果接下来,访问了 8 号页,因为 8 号页不在 Buffer Pool 里,所以需要先淘汰末尾的 5 号页,然后再将 8 号页加入到头部。

简单版的LRU链表存在两个问题

  • 预读失效
  • Buffer Pool 污染;

什么是预读失效?

MySQL 的预读机制:程序是有空间局部性的,靠近当前被访问数据的数据,在未来很大概率会被访问到。所以,MySQL 在加载数据页时,会提前把它相邻的数据页一并加载进来,目的是为了减少磁盘 IO。

但是可能这些被提前加载进来的数据页,并没有被访问,相当于这个预读是白做了,这个就是预读失效。

如果使用简单的 LRU 算法,就会把预读页放到 LRU 链表头部,而当 Buffer Pool空间不够的时候,还需要把末尾的页淘汰掉。

如果这些预读页如果一直不会被访问到,就会出现一个很奇怪的问题,不会被访问的预读页却占用了 LRU 链表前排的位置,而末尾淘汰的页,可能是频繁访问的页,这样就大大降低了缓存命中率。

如何解决

首先不能害怕预读失效就把预读机制去了,空间局部性原理在大部分场景下是成立且有效的

而要避免预读失效带来影响,最好就是
让预读的页停留在 Buffer Pool 里的时间要尽可能的短,让真正被访问的页才移动到 LRU 链表的头部,从而保证真正被读取的热数据留在 Buffer Pool 里的时间尽可能长

Mysql将LRU链表划分成了两个区域:old 区域 和 young 区域。
young 区域在 LRU 链表的前半部分,old 区域则是在后半部分

划分这两个区域后,
预读的页就只需要加入到 old 区域的头部
,当页被真正访问的时候,才将页插入 young 区域的头部。如果预读的页一直没有被访问,就会从 old 区域移除,这样
就不会影响 young 区域中的热点数据

什么是 Buffer Pool 污染?

即使有了以上划分young区的old区的链表也会存在这个问题。

当某一个 SQL 语句扫描了大量的数据时,因为被读取了,这些数据就都会放在young区的头部,那么由于 Buffer Pool 空间有限,就有可能会将 Buffer Pool 里的所有页都替换出去,导致LRU的young区域的大量热数据被淘汰,等这些热数据又被再次访问的时候,由于缓存未命中,就会产生大量的磁盘 IO,MySQL 性能就会急剧下降,这个过程被称为 Buffer Pool 污染。

如何解决

MySQL 将进入到 young 区域条件增加了一个
停留在 old 区域的时间判断

Mysql在对某个处在 old 区域的缓存页进行第一次访问时,就在它对应的控制块中记录下来这个访问时间:

  • 如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么该缓存页就不会被从 old 区域移动到 young 区域的头部;
  • 如果后续的访问时间与第一次访问的时间不在某个时间间隔内,那么该缓存页移动到 young 区域的头部;

MYsql缓存能否替代Redis

  1. Redis缓存支持的场景更多。
    • 实际工作中缓存的结果不单单是Mysql Select语句返回的结果,有可能是在此基础上又加工的结果;而Mysql缓存的是Select语句的结果
    • Redis可以提供更丰富的数据类型的访问,如List、Set、Map、ZSet
  2. Redis缓存命中率要远高于Mysql缓存。
    • Mysql选择要缓存的语句的方式不是根据访问频率,主要是根据select语句里边是否包含动态变化的值,没有动态变化值的则缓存,比如用了now函数就不会缓存。Redis是由客户端自主根据访问频率高进行缓存。
    • Redis丰富的数据结构使得缓存复用率更高,比如缓存的是List,可以随意访问List中的部分元素,比如分页需求
    • Mysql缓存的失效粒度很粗,只要表有更新,涉及该表的所有缓存(不管更新是否会影响缓存)都失效,这样使得缓存的利用率会很低,只是适用更新很少的表
    • 当存在主从结点,并且会从多个结点读取数据时,各个结点的缓存不会同步3. 性能:Redis的查询性能要远高于Mysql缓存,最主要的原因是Redis是全部放在内存的,但是因为mysql缓存的命中率问题使得Mysql无法全部放到内存中。Redis性能好也还有一些其他原因
    • Redis的存储结构有利于读写性能Redis是IO多路复用,可以支持更大的吞吐,Mysql的数据特征使得做成IO多路复用绝大多数情况下也没有意义
    • 数据更新时会同时将该表的所有缓存失效,会使得数据更新的速度变慢。

面试题专栏

Java面试题专栏
已上线,欢迎访问。

  • 如果你不知道简历怎么写,简历项目不知道怎么包装;
  • 如果简历中有些内容你不知道该不该写上去;
  • 如果有些综合性问题你不知道怎么答;

那么可以私信我,我会尽我所能帮助你。

上周的热门开源项目,Star 数增长犹如坐上了火箭,一飞冲天。短短一周就飙升了 6k Star 的多格式文档解析和导出神器 Docling,支持库和命令行的使用方式。全新的可视化爬虫平台 Maxun,则在刚开源时便轻松斩获了 4k Star。而本地优先的个人理财工具 Actual,支持 Docker 自托管,让用户可以将数据掌握在自己手里。如果你在寻找机器学习的 Python 库,可以去 best-of-ml-python 看一看,它涵盖了 34 个分类,共计 920 个优秀的机器学习 Python 库。

最后,免费的 Windows 应用卸载利器(Bulk-Crap-Uninstaller)和 B 站视频空降助手(BilibiliSponsorBlock),凭借着简单实用、开箱即用的特点,迅速赢得了广大用户的青睐。

  • 本文目录
    • 1. 热门开源项目
      • 1.1 多格式文档解析和导出工具:Docling
      • 1.2 本地优先的个人理财工具:Actual
      • 1.3 简单干净的 Hugo 主题:hugo-PaperMod
      • 1.4 开源的无代码网页数据提取平台:Maxun
      • 1.5 顶级的机器学习 Python 库列表:best-of-ml-python
    • 2. HelloGitHub 热评
      • 2.1 B 站视频空降助手:BilibiliSponsorBlock
      • 2.2 免费的 Windows 应用卸载神器:Bulk-Crap-Uninstaller
    • 3. 结尾

1. 热门开源项目

1.1 多格式文档解析和导出工具:Docling

主语言:Python

Star:7.9k

周增长:6k

这是一个由 IBM 开源的 Python 工具,专门用于将各类文档转化为适合生成式 AI 使用的工具。它能够将 PDF、DOCX、PPTX、图片、HTML、Markdown 等多种流行文档格式,导出为 Markdown 和 JSON 格式,支持多种 OCR 引擎(PDF)、统一的文档对象(DoclingDocument),轻松集成检索增强生成(RAG)和问答应用,适用于需要将文档作为生成式 AI 模型输入的场景。

from docling.document_converter import DocumentConverter

source = "url"  # document per local path or URL
converter = DocumentConverter()
result = converter.convert(source)
print(result.document.export_to_markdown())  # output: "## Docling Technical Report[...]"

GitHub 地址→
github.com/DS4SD/docling

1.2 本地优先的个人理财工具:Actual

主语言:TypeScript

Star:15k

周增长:600

这是一款完全免费开源、本地优先的个人理财工具。它采用 Node.js 编写,拥有简洁的界面和直观的现金流报告,支持 Docker 自建、导入交易数据和多设备同步,以及可选的端到端加密功能,注重保护用户隐私和数据安全。

GitHub 地址→
github.com/actualbudget/actual

1.3 简单干净的 Hugo 主题:hugo-PaperMod

主语言:HTML

Star:10k

这是一个快速、简洁、响应式的 Hugo 主题。它基于 hugo-paper 开发,并在此基础上增加了更多功能和自定义选项,支持多语言、自动切换明暗主题、SEO 友好、社交媒体分享按钮、封面图片、导航栏等功能。此外,它还提供了常规、主页信息和个人资料三种模式,可用于快速构建不同风格的个人博客。

GitHub 地址→
github.com/adityatelange/hugo-PaperMod

1.4 开源的无代码网页数据提取平台:Maxun

主语言:TypeScript

Star:4k

周增长:3k

这是一款全新的无代码网页数据提取平台,无需编程即可轻松抓取网站的数据,支持列表/文本抓取、截图、自定义代理、自动处理分页和滚动等功能。作为一个新的开源项目,它的功能还在不停迭代,计划推比如适应网站布局变化和登录后数据提取等新功能。

GitHub 地址→
github.com/getmaxun/maxun

1.5 顶级的机器学习 Python 库列表:best-of-ml-python

主语言:Other

Star:17k

周增长:1.2k

该项目提供了一个高质量的机器学习 Python 库列表,包含超过 900 个开源项目,并按照项目质量评分进行排名,每周更新一次。所有开源项目被分成了 30 多个分类,包括机器学习框架、数据可视化、自然语言处理、OCR、模型序部署等,便于不同应用领域的开发者快速找到所需的机器学习工具和资源。

GitHub 地址→
github.com/ml-tooling/best-of-ml-python

2. HelloGitHub 热评

在此章节中,我们将为大家介绍本周 HelloGitHub 网站上的热门开源项目,我们不仅希望您能从中收获开源神器和编程知识,更渴望“听”到您的声音。欢迎您与我们分享使用这些
开源项目的亲身体验和评价
,用最真实反馈为开源项目的作者注入动力。

2.1 B 站视频空降助手:BilibiliSponsorBlock

主语言:TypeScript

这是一款能够自动跳过 B 站视频中恰饭片段和开场、结尾动画的浏览器插件,所有标注数据均由网友贡献,支持 Chrome、Edge 和 FireFox 浏览器。

项目详情→
hellogithub.com/repository/298fa9ba909c49428c1dc7f8c401bbbd

2.2 免费的 Windows 应用卸载神器:Bulk-Crap-Uninstaller

主语言:C#

这是一个用 C# 开发的 Windows 软件卸载工具,能够快速删除大量不需要的应用程序。它完全免费、开箱即用,支持批量和强制卸载、清理残留文件、检测隐藏或受保护的已注册应用等功能。虽然面向 IT 专业人员设计,但其简单的默认设置,让任何人都能轻松上手。

项目详情→
hellogithub.com/repository/e5745984014e47f1a33648c0425256a0

3. 结尾

以上就是本期「GitHub 热点速览」的全部内容,希望你能够在这里找到自己感兴趣的开源项目,如果你有其他好玩、有趣的 GitHub 开源项目想要分享,欢迎来
HelloGitHub
与我们交流和讨论。

往期回顾

教程名称:使用 C# 入门深度学习

作者:痴者工良

地址:

https://torch.whuanle.cn

1.2 Pytorch 基础

本文内容介绍 Pytorcn 的基础 API,主要是数组的创建方式和运算方式,由于相关内容跟 Numpy 比较相似,并且 Numpy 类型可以转 torch.Tensor,因此对 Numpy 感兴趣的读者可以参考笔者的其它文章:

  • Python 之 Numpy 框架入门

https://www.whuanle.cn/archives/21461

https://www.cnblogs.com/whuanle/p/17855578.html


提示:学习本文时,如果对线性代数有足够的了解,则学习效果更佳,没有线性代数基础也没关系,后面会学习到。本文会同时使用 Python 和 C# 编写示例,方便各位读者对照差异,在后续的章节学习中,基本只会使用 C# 编写示例。


基础使用

由于神经网络中的数值很多以向量或数组等形式存在,不像日常编程中的数值类型那么简单,因此打印数值信息是我们学习了解或调试程序的一种手段,下面我们来观察程序是怎么打印 Pytorch 中复杂数据类型的。


打印

下面使用 Pytorch 创建一个从 0..9 的数组,接着打印数组。

Python:

import torch
x = torch.arange(10)
print(x)
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])


C# 版本不使用
Console.WriteLine()
,而是使用官方提供的库。

using TorchSharp;

var x = torch.arange(10);
x.print(style:TensorStringStyle.Default);
x.print(style:TensorStringStyle.Numpy);
x.print(style:TensorStringStyle.Metadata);
x.print(style:TensorStringStyle.Julia);
x.print(style:TensorStringStyle.CSharp);
[10], type = Int64, device = cpu 0 1 2 3 4 5 6 7 8 9
[0, 1, 2, ... 7, 8, 9]
[10], type = Int64, device = cpu
[10], type = Int64, device = cpu 0 1 2 3 4 5 6 7 8 9
[10], type = Int64, device = cpu, value = long [] {0L, 1L, 2L, ... 7L, 8L, 9L}


Python 打印的结果比较容易理解,C# 默认打印的方式比较难看,所以一般来说,可视化都使用
TensorStringStyle.Numpy
枚举。

C# 打印数值时,参数有个
string? fltFormat = "g5"
,表示精确度的意思,即打印的小数位数。

在 Maomi.Torch 包中提供了一些扩展方法,读者可以使用
x.print_numpy()
扩展直接打印对应风格的信息。


对于后面的章节来说,默认都引入 Python 的 torch 包名称、C# 的 TorchSharp 命名空间,后续代码示例可能会省略引入代码,读者自行引入。


基本数据类型

Pytorch 的数据类型跟我们编程语言中的基本类型不太一样,读者要注意区别。

具体详细的官方文档参考链接:

https://pytorch.org/docs/stable/tensor_attributes.html

https://pytorch.ac.cn/docs/stable/tensor_attributes.html


Pytorch 创建的数据类型以
torch.Tensor
表示,
torch.Tensor
是用来处理机器学习模型中的各种数据的基础结构,包括标量、向量、矩阵以及更高维度的张量。
如果笔者没理解错的话,在 Pytorch 中创建的 Tensor 对象就叫张量。开发者可以通过各种形式的数据在 Pytorch 创建 Tensor

Pytorch 创建的数据类型,都使用 Tensor 对象表示。

对于这句话的理解,建议看完本文再回头看看。


PyTorch 有十二种不同的数据类型,列表如下:

数据类型 dtype
32 位浮点数 torch.float32

torch.float
64 位浮点数 torch.float64

torch.double
64 位复数 torch.complex64

torch.cfloat
128 位复数 torch.complex128

torch.cdouble
16 位浮点数 torch.float16

torch.half
16 位浮点数 torch.bfloat16
8 位整数(无符号) torch.uint8
8 位整数(有符号) torch.int8
16 位整数(有符号) torch.int16

torch.short
32 位整数(有符号) torch.int32

torch.int
64 位整数(有符号) torch.int64

torch.long
布尔值 torch.bool


下面示范在创建一个数值全为 1 的数组时,设置数组的类型。

Python:

float_tensor = torch.ones(1, dtype=torch.float)
double_tensor = torch.ones(1, dtype=torch.double)
complex_float_tensor = torch.ones(1, dtype=torch.complex64)
complex_double_tensor = torch.ones(1, dtype=torch.complex128)
int_tensor = torch.ones(1, dtype=torch.int)
long_tensor = torch.ones(1, dtype=torch.long)
uint_tensor = torch.ones(1, dtype=torch.uint8)


C#:

var float_tensor = torch.ones(1, dtype: torch.float32);
var double_tensor = torch.ones(1, dtype: torch.float64);
var complex_float_tensor = torch.ones(1, dtype: torch.complex64);
var complex_double_tensor = torch.ones(1, dtype: torch.complex128);
var int_tensor = torch.ones(1, dtype: torch.int32); ;
var long_tensor = torch.ones(1, dtype: torch.int64);
var uint_tensor = torch.ones(1, dtype: torch.uint8);


在 C# 中, torch.ScalarType 枚举表示 Pytorch 的数据类型,所以可以有以下两种方式指定数据类型。

例如:

var arr = torch.zeros(3,3,3, torch.ScalarType.Float32);
arr.print_numpy();

或:

var arr = torch.zeros(3,3,3, torch.float32);
arr.print_numpy();


CPU 或 GPU 运算

我们知道,AI 模型可以在 CPU 下运行,也可以在 GPU 下运行,Pytorch 的数据也可以这样做,在创建数据类型时就设置绑定的设备,在运算使用会使用对应的设备进行运算。

一般使用
cpu
表示 CPU,使用
cuda

cuda:{显卡序号}
表示 GPU。


下面编写代码判断 Pytorch 正在使用 GPU 还是 CPU 运行。


Python:

print(torch.get_default_device())


C#:

 Console.WriteLine(torch.get_default_device())


如果当前设备支持 GPU,则使用 GPU 启动 Pytorch,否则使用 CPU 启动 Pytorch。可以通过
torch.device('cuda')

torch.device('cuda:0')
指定使用 GPU 、指定使用哪个 GPU。


Python:

if torch.cuda.is_available():
    print("当前设备支持 GPU")
    device = torch.device('cuda')
    # 使用 GPU 启动
    torch.set_default_device(device)
    current_device = torch.cuda.current_device()
    print(f"绑定的 GPU 为:{current_device}")
else:
    # 不支持 GPU,使用 CPU 启动
    device = torch.device('cpu')
    torch.set_default_device(device)

default_device = torch.get_default_device()
print(f"当前正在使用 {default_device}")


C#:

if (torch.cuda.is_available())
{
    Console.WriteLine("当前设备支持 GPU");
    var device = torch.device("cuda",index:0);
    // 使用 GPU 启动
    torch.set_default_device(device);
}
else
{
    var device = torch.device("cpu");
    // 使用 CPU 启动
    torch.set_default_device(device);
    Console.WriteLine("当前正在使用 CPU");
}

var default_device = torch.get_default_device();
Console.WriteLine($"当前正在使用 {default_device}");

C# 没有
torch.cuda.current_device()
这个方法,建议默认设置使用哪块 GPU,即设置 index 参数。


另外可以通过使用
torch.cuda.device_count()
获取设备有多少个显卡,这里不再赘述。


Pytorch 还支持针对单独的数据类型设置使用 CPU 还是 GPU,还可以让两者混合运算,这里不再赘述。


Tensor 类型

在 Pytorch 中,可以将标量、数组等类型转换为 Tensor 类型,Tensor 表示的数据结构就叫张量。

x = torch.tensor(3.0);


基本数组

Pytorch 使用
asarray()
函数将 obj 值转换为数组,其定义如下:

torch.asarray(obj, *, dtype=None, device=None, copy=None, requires_grad=False) → Tensor

官方 API 文档:
https://pytorch.org/docs/stable/generated/torch.asarray.html#torch-asarray


obj
可以是以下之一:

  • a tensor(张量)
  • a NumPy array or a NumPy scalar(NumPy 数组或 NumPy 标量)
  • a DLPack capsule
  • an object that implements Python’s buffer protocol
  • a scalar(标量)
  • a sequence of scalars(标量序列)

笔者不会的或者本文用不到的,就不翻译了。


比如说,传入一个平常的数组类型,转换成 Pytorch 中的数组类型。


Python:

arr = torch.asarray([1,2,3,4,5,6], dtype=torch.float)
print(arr)


C#:

var arr = torch.from_array(new float[] { 1, 2, 3, 4, 5 });
arr.print(style: TensorStringStyle.Numpy);


请注意,两种语言的版本差异有些大。

前面提到过,可以给单独的数据类型设置使用 CPU 还是 GPU。

device = torch.device("cuda",index=0)
arr = torch.asarray(obj=[1,2,3,4,5,6], dtype=torch.float, device=device)
print(arr)


将数据类型转换为使用 CPU 设备:

device = torch.device("cuda",index=0)
arr = torch.asarray(obj=[1,2,3,4,5,6], dtype=torch.float, device=device)
arr = arr.cpu()
print(arr)


但是将数据在 GPU、CPU 之间转换,会消耗一定的性能。


生成数组

torch.zeros

用于创建一个元素全为 0 的数组,可以指定数组大小,其定义如下:

torch.zeros(*size, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor


Python:

arr = torch.zeros(10, dtype=torch.float)
print(arr)


C#:

var arr = torch.zeros(10);
arr.print(style: TensorStringStyle.Numpy);


另外,可以指定生成的数组维度,例如下面指定生成
2*3
的多维数组。

var arr = torch.zeros(2,3, torch.float32);
arr.print(style: TensorStringStyle.Numpy); 

代码为 C# 语言。


打印:

[[0, 0, 0] [0, 0, 0]]


我们还可以生成多种维度的数组,例如下面生成一个
3*3*3
的数组:

var arr = torch.zeros(3,3,3, torch.float32);
arr.print(style: TensorStringStyle.Numpy); 


为了方便理解,下面将打印结果做了格式化处理。

[
[[0, 0, 0]  [0, 0, 0]  [0, 0, 0]]
[[0, 0, 0]  [0, 0, 0]  [0, 0, 0]]
[[0, 0, 0]  [0, 0, 0]  [0, 0, 0]]
]


torch.ones

创建一个全由 1 填充的数组,使用方法跟 torch.zeros 完全一致,因此这里不再赘述。


torch.empty

创建一个未初始化的数组,使用方法跟 torch.zeros 完全一致,因此这里不再赘述。

由于其没有初始化内存,因此内存区域会残留数据,元素的值不确定。


复制函数

此外,上面三个函数还有对应的原型复制函数:

torch.ones_like(input, *, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format) → Tensor
torch.zeros_like(input, *, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format) → Tensor
torch.empty_like(input, *, dtype=None, layout=None, device=None, requires_grad=False, memory_format=torch.preserve_format) → Tensor


它们的作用是根据数组类型,拷贝一个相同的结构,然后填充对应值。

如下示例,复制数组相同的结构,但是填充的值为 1。

var arr = torch.ones_like(torch.zeros(3, 3, 3));
arr.print(style: TensorStringStyle.Numpy);

该代码语言为 C#。


[
[[1, 1, 1]  [1, 1, 1]  [1, 1, 1]]
[[1, 1, 1]  [1, 1, 1]  [1, 1, 1]]
[[1, 1, 1]  [1, 1, 1]  [1, 1, 1]]
]


torch.rand

torch.rand 会生成一个张量,数组会填充来自
[0,1)
区间上的均匀分布的随机数。

函数定义如下:

torch.rand(*size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False, pin_memory=False) → Tensor


例如生成
2*3
大小的,范围在
[0,1)
区间的随机数,使用 C# 编写代码:

var arr = torch.rand(2,3);
arr.print(style: TensorStringStyle.Numpy);
[[0.60446, 0.058962, 0.65601] [0.58197, 0.76914, 0.16542]]


由于 C# 绘制图形的库不像 Python matplotlib 简单易用,因此读者可以引用 Maomi.Torch.ScottPlot、Maomi.ScottPlot.Winforms 两个包,可以快速转换 Pytorch 类型和生成绘制窗口 。下面示范使用编写代码绘制均匀分布的随机数,方便使用 Python matplotlib 和 Maomi.ScottPlot.Winforms 框架显示图形。

Python:

import torch
import matplotlib.pyplot as plt

arr = torch.rand(100, dtype=torch.float)

print(arr)

x = arr.numpy()
y = x
plt.scatter(x,y)
plt.show()


C#:

using Maomi.Torch;
using Maomi.Plot;
using TorchSharp;

var x = torch.rand(100);

x.print(style: TensorStringStyle.Numpy);

ScottPlot.Plot myPlot = new();
myPlot.Add.Scatter(x, x);
var form = myPlot.Show(400, 300);


由图可知,生成的随机数是均匀散布在
[0,1)
区间内。


torch.randn

生成具有给定形状的标准正态分布(平均值为0,方差为1)的随机样本。随机样本取值范围是[0,1)。

定义如下:

torch.randn(*size, *, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False, pin_memory=False) → Tensor

官方文档:
https://pytorch.ac.cn/docs/stable/generated/torch.randn.html#torch.rand


由于 C# 不好绘图,这里使用 Python 编写示例:

import torch
import matplotlib.pyplot as plt

arr = torch.randn(100, dtype=torch.float)

print(arr)

x = arr.numpy()
y = x
plt.hist(x, bins=30, alpha=0.5, color='b', edgecolor='black')

plt.show()

x 坐标轴是数值,y 坐标轴是出现次数。

image-20241103125947540


torch.randint

在某个区间内生成随机数。

定义如下:

torch.randint(low=0, high, size, \*, generator=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor


比如在 0-100 范围内生成 10 个元素,安装
5*2
结构组成,使用 C# 代码编写。

var arr = torch.randint(low: 0, high: 100, size: new int[] { 5, 2 });
arr.print(style: TensorStringStyle.Numpy);
[[17, 46] [89, 52] [10, 89] [80, 91] [52, 91]]


如果要生成某个区间的浮点数,则可以使用
torch.rand
,但是因为
torch.rand
生成范围是
[0,1)
,因此需要自行乘以倍数。例如要生成
[0,100)
的随机数。

var arr = torch.rand(size: 100, dtype: torch.ScalarType.Float32) * 100;
arr.print(style: TensorStringStyle.Numpy);  


torch.arange

指定区间以及步长,均匀提取元素生成数组。

定义如下:

torch.arange(start=0, end, step=1, *, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) → Tensor


比如说,需要生成
[0,1,2,3,4,5,6,7,8,9]
这样的数组,可以使用:

var arr = torch.arange(start: 0, stop: 10, step: 1);
arr.print(style: TensorStringStyle.Numpy);


如果将步长改成 0.5。

var arr = torch.arange(start: 0, stop: 10, step: 0.5);
[0.0000, 0.5000, 1.0000, 1.5000, 2.0000, 2.5000, 3.0000, 3.5000, 4.0000,
        4.5000, 5.0000, 5.5000, 6.0000, 6.5000, 7.0000, 7.5000, 8.0000, 8.5000,
        9.0000, 9.5000]


数组操作和计算


在 Pytorch 中,往往使用 dim(dimension) 参数表示轴,轴就是张量的层数。

有以下数组:

[[ 1, 2, 3 ], { 4, 5, 6 ]]

如果把
a = [1,2,3]

b = [4,5,6]
,则:

[a,b]


那么当我们要获取
a
时,
dim(a) = 0

dim(b) = 1

var arr = torch.from_array(new[,] { { 1, 2, 3 }, { 4, 5, 6 } });

arr.print(style: TensorStringStyle.Numpy);

// 打印维度
arr.shape.print();

var a = arr[0];
a.print();
var b = arr[1];
b.print();
[[1, 2, 3] [4, 5, 6]]
[2, 3]
[3], type = Int32, device = cpu 1 2 3
[3], type = Int32, device = cpu 4 5 6


这里我们分两步理解,由于该数组是
2*3
数组,可以使用
.shape.print()
打印出来。

由于第一层有两个元素,因此可以使用
Tensor[i]
获取第一层的第 i 个元素,其中
i<2

同理,由于 a、b 的下一层都有 3 个元素,因此第二层
n<3


例如要将数组的
3

6
两个元素取出来。

用 C# 可以这样写,但是打印的时候不能选 TensorStringStyle.Numpy ,否则打印不出来。

var arr = torch.from_array(new[,] { { 1, 2, 3 }, { 4, 5, 6 } });

var a = arr[0, 2];
a.print(style: TensorStringStyle.CSharp);
var b = arr[1, 2];
b.print(style: TensorStringStyle.CSharp);


同理,如果数组有三层,可以这样获取
3

6
两个元素

var arr = torch.from_array(new[, ,] { { { 1, 2, 3 } }, { { 4, 5, 6 } } });

var a = arr[0, 0, 2];
a.print(style: TensorStringStyle.CSharp);
var b = arr[1, 0, 2];
b.print(style: TensorStringStyle.CSharp);


如果要取出一部分元素,TorchCsharp 可以使用
a[i..j]
的语法截取,示例如下。

var arr = torch.from_array(new int[] { 1, 2, 3 });
arr = arr[0..2];
arr.print(style: TensorStringStyle.Numpy);
[1, 2]


数组排序

Pytorch 中有一些排序函数:

sort
:沿给定维度按值升序对
input
张量的元素进行排序。

argsort
:它是沿指定轴的间接排序,本文不讲解。

msort
:按值对
input
张量沿其第一维以升序排序。
torch.msort(t)
等效于
torch.sort(t, dim=0)


sort
可以降序或升序,参数说明如下:

torch.sort(input, dim=-1, descending=False, stable=False, *, out=None)
  • input
    (张量) – 输入张量。
  • dim
    (int,可选) – 要排序的维度
  • descending
    (bool,可选) – 控制排序顺序(升序或降序)
  • stable
    (boo,可选) – 使排序例程稳定,从而保证等效元素的顺序得以保留。


示例:

var arr = torch.arange(start: 0, stop: 10, step: 1);

// 或者使用 torch.sort(arr, descending: true)
(torch.Tensor Values, torch.Tensor Indices) a1 = arr.sort(descending: true);

a1.Values.print(style: TensorStringStyle.Numpy);
[9, 8, 7, ... 2, 1, 0]


Values 是排序后的结果,Indices 是排序的规则。


如果数组结构比较复杂,默认不设置参数时,只有最内层数组进行排序。如下代码所示,有两层数组。

var arr = torch.from_array(new[,] { { 4, 6, 5 }, { 8, 9, 7 }, { 3, 2, 1 } });

(torch.Tensor Values, torch.Tensor Indices) a1 = arr.sort();

a1.Values.print(style: TensorStringStyle.Numpy);
a1.Indices.print(style: TensorStringStyle.Numpy);
[[4, 5, 6] [7, 8, 9] [1, 2, 3]]
[[0, 2, 1] [2, 0, 1] [2, 1, 0]]

Indices 会记录当前元素在以前的排序位置。


当设置
arr.sort(dim: 0);
时,按照第一层排序。

[[3, 2, 1] [4, 6, 5] [8, 9, 7]]
[[2, 2, 2] [0, 0, 0] [1, 1, 1]]


当设置
arr.sort(dim: 1);
时,只有里面一层排序。

[[4, 5, 6] [7, 8, 9] [1, 2, 3]]
[[0, 2, 1] [2, 0, 1] [2, 1, 0]]


当一个张量的维度比较大时,我们可以这样逐层排序。

var arr = torch.from_array(new[, ,] { { { 4, 6, 5 }, { 8, 9, 7 }, { 3, 2, 1 } } });

var dimCount = arr.shape.Length;
for (int i = dimCount - 1; i >= 0; i--)
{
    (torch.Tensor Values, torch.Tensor Indices) a1 = arr.sort(dim: i);
    arr = a1.Values;
    arr.print(style: TensorStringStyle.Numpy);
}
[[[1, 2, 3]  [4, 5, 6]  [7, 8, 9]]]


C# 多维数组没有 Python 那么方便,会要求每一层的元素个数必须一致。

例如下面的数组声明是对的:

var array = new int[, , ]
{
    {
        { 10, 12, 11},{ 14, 15, 11 }
    },
    {
        { 4, 6, 5 }, { 8, 9, 7 }
    }
};


如果层数元素个数不一致会报错:

image-20241103203955447


另外要注意,C# 有多维数组和交错数组,下面是交错数组的声明方式,TorchSharp 并不支持。多维数组是数组,交错数组是数组的数组,或数组的数组的数组,要注意区分。

var array = new int[][][]
{
    new int[][]
    {
        new int[] { 1, 2, 3 },
        new int[] { 4, 5, 6 },
        new int[] { 7, 8, 9 }
    },
    new int[][]
    {
        new int[] { 10, 12, 11 },
        new int[] { 14, 15, 13 }
    }
};

image-20241103204339232


数组运算符

在 PyTorch 中,张量支持许多运算符,下面列举部分加以说明:


算术运算符

  • +
    :加法,如
    a + b
  • -
    :减法,如
    a - b
  • *
    :元素级乘法,如
    a * b
  • /
    :元素级除法,如
    a / b
  • //
    :元素级整除,如
    a // b
    ,TorchCsharp 不支持。
  • %
    :取模运算,如
    a % b
  • **
    :幂运算,如
    a ** b
    ,TorchCsharp 不支持,使用
    .pow(x)
    代替。


逻辑运算符

  • ==
    :元素级相等比较,如
    a == b
  • !=
    :元素级不等于比较,如
    a != b
  • >
    :元素级大于比较,如
    a > b
  • <
    :元素级小于比较,如
    a < b
  • >=
    :元素级大于等于比较,如
    a >= b
  • <=
    :元素级小于等于比较,如
    a <= b


位运算符

  • &
    :按位与运算,如
    a & b
  • |
    :按位或运算,如
    a | b
  • ^
    :按位异或运算,如
    a ^ b
  • ~
    :按位取反运算,如
    ~a
  • <<
    :按位左移,如
    a << b
  • >>
    :按位右移,如
    a >> b


索引和切片

  • [i]
    :索引运算符,如
    a[i]
  • [i:j]
    :切片运算符,如
    a[i:j]
    ,TorchCsharp 使用
    a[i..j]
    语法。
  • [i, j]
    :多维索引运算符,如
    a[i, j]


例如张量每个元素的值
*10

var arr = torch.from_array(new int[] { 1, 2, 3 });
arr = arr * 10;
arr.print(style: TensorStringStyle.Numpy);
[10, 20, 30]


此外,还有 Pytorch 还很多函数,后面的章节中会逐渐学习。


一、概述

木舟平台分为微服务平台和物联网平台, 上面几篇都是介绍如何通过网络组件接入设备,那么此篇文章就细致介绍下在木舟平台下如何构建微服务。

木舟 (Kayak) 是什么?

木舟(Kayak)是基于.NET6.0软件环境下的surging微服务引擎进行开发的, 平台包含了微服务和物联网平台。支持异步和响应式编程开发,功能包含了物模型,设备,产品,网络组件的统一管理和微服务平台下的注册中心,服务路由,模块,中间服务等管理。还有多协议适配(TCP,MQTT,UDP,CoAP,HTTP,Grpc,websocket,rtmp,httpflv,webservice,等),通过灵活多样的配置适配能够接入不同厂家不同协议等设备。并且通过设备告警,消息通知,数据可视化等功能。能够让你能快速建立起微服务物联网平台系统。

二、构建服务

创建服务接口,继承IServiceKey,添加特性[ServiceBundle("api/{Service}/{Method}")] 配置routepath,代码如下:

   [ServiceBundle("api/{Service}/{Method}")]public interfaceITestApiService:IServiceKey
{
public Task<string> SayHello(stringname);
}

创建服务实例,继承ProxyServiceBase, ITestApiService, ISingleInstance,如果只是业务处理只需继承ProxyServiceBase,继承ISingleInstance表示注入的生命周期 为单例模式,添加特性ModuleName标识一个服务多个实例,可以在调用的时候传入ServiceKey

    [ModuleName("Test")]public classTestService : ProxyServiceBase, ITestApiService, ISingleInstance
{
public Task<string> SayHello(stringname)
{
return Task.FromResult($"{name} say:hello world");
}
}

二、身份鉴权

webapi调用必然会牵涉到身份鉴权,用户登录问题,而surging 已经集成了一套jwt验证机制

然后在Stage配置节上配置ApiGetWay

   "ApiGetWay": {"AccessTokenExpireTimeSpan": "240","AuthorizationRoutePath": "api/sysuser/authentication",//身份鉴权服务的routepath
     "AuthorizationServiceKey": null,"TokenEndpointPath": "api/oauth2/token",//映射调用的routepath
     "CacheMode": "MemoryCache" //MemoryCache or  gateway.Redis save token
   }

然后在接口方法上加上  [Authorization(AuthType = AuthorizationType.JWT)] 特性,服务调用就要进行身份鉴权

    public interfaceIModuleService : IServiceKey
{
[Authorization(AuthType
=AuthorizationType.JWT)]
Task
<ApiResult<bool>>Add(ModuleModel model);

[Authorization(AuthType
=AuthorizationType.JWT)]
Task
<ApiResult<bool>>Modify(ModuleModel model);

[Authorization(AuthType
=AuthorizationType.JWT)]
Task
<ApiResult<Page<ModuleModel>>>GetPageAsync(ModuleQuery query);

}

三、缓存拦截

surging 可以支持拦截缓存,可以通过
ServiceCacheIntercept特性进行配置,获取缓存可以通过
CachingMethod.Get
, 删除缓存可以通过
CachingMethod.Remove
,可以支持MemoryCache,Redis, 可以支持一,二级缓存,

启用EnableStageCache表示网关调用也可以走缓存拦截(注:不支持模型参数)

 [ServiceBundle("api/{Service}/{Method}")]public interfaceIProductService : IServiceKey
{
[Authorization(AuthType
=AuthorizationType.JWT)]
[ServiceCacheIntercept(CachingMethod.Remove,
"GetProducts", CacheSectionType = "ddlCache", Mode = CacheTargetType.MemoryCache, EnableStageCache = true)]
Task
<ApiResult<bool>>Add(ProductModel model);

[Authorization(AuthType
=AuthorizationType.JWT)]
Task
<ApiResult<ProductModel>> GetProduct(intid);

[Authorization(AuthType
=AuthorizationType.JWT)]
Task
<ApiResult<Page<ProductModel>>>GetPageAsync(ProductQuery query);

[Authorization(AuthType
=AuthorizationType.JWT)]
Task
<ApiResult<List<ProductModel>>>GetProductByCondition(ProductQuery query);

[Authorization(AuthType
=AuthorizationType.JWT)]
[ServiceCacheIntercept(CachingMethod.Remove,
"GetProducts", CacheSectionType = "ddlCache", Mode = CacheTargetType.MemoryCache, EnableStageCache = true)]
[ServiceLogIntercept]
Task
<ApiResult<bool>> DeleteById(List<int>ids);

[Authorization(AuthType
=AuthorizationType.JWT)]
[ServiceCacheIntercept(CachingMethod.Remove,
"GetProducts", CacheSectionType = "ddlCache", Mode = CacheTargetType.MemoryCache, EnableStageCache = true)]
Task
<ApiResult<bool>>Modify(ProductModel model);


[Authorization(AuthType
=AuthorizationType.JWT)]
Task
<ApiResult<bool>>Validate(ProductModel model);

[Authorization(AuthType
=AuthorizationType.JWT)]
[ServiceCacheIntercept(CachingMethod.Remove,
"GetProducts", CacheSectionType = "ddlCache", Mode = CacheTargetType.MemoryCache, EnableStageCache = true)]
Task
<ApiResult<bool>> Stop(List<int>ids);

[Authorization(AuthType
=AuthorizationType.JWT)]
[ServiceCacheIntercept(CachingMethod.Remove,
"GetProducts", CacheSectionType = "ddlCache", Mode = CacheTargetType.MemoryCache, EnableStageCache = true)]
Task
<ApiResult<bool>> Open(List<int>ids);

[Authorization(AuthType
=AuthorizationType.JWT)]
[ServiceCacheIntercept(CachingMethod.Get, Key
= "GetProducts", CacheSectionType = "ddlCache", EnableL2Cache = false, Mode = CacheTargetType.MemoryCache, Time = 480, EnableStageCache = true)]
Task
<ApiResult<List<ProductModel>>>GetProducts();


}

参数如果是非模型集合类型的参数,缓存key 会取第一个参数值,如果是模型参数就需要添加CacheKey特性,代码如下:

    public classPropertyThresholdQuery
{
[CacheKey(
1)]public string PropertyCode { get; set; }
[CacheKey(
2)]public string ProductCode { get; set; }
[CacheKey(
3)]public string DeviceCode { get; set; }
}

四、服务管理

1.平台是支持服务路由管理,此项功能除了可以查看元数据,服务节点,服务规则外,还可以在权重轮询负载算法情况下,改变权重可以让更多的访问调用到此服务节点上,还有可以优雅的移除服务节点

选择权重轮询负载分流算法,代码如下:

    [ServiceBundle("api/{Service}/{Method}")]public interfaceITestApiService:IServiceKey
{
//[Authorization(AuthType = AuthorizationType.JWT)] [Command(ShuntStrategy =AddressSelectorMode.RoundRobin)]public Task<string> SayHello(stringname);
}

以下是编辑权重

2. 热部署中间服务

3. 黑白名单,添加IP地址或者IP段就能限制相关IP访问

就比如访问api/testapi,结果如下:

4.支持swagger API文档

五、分布式链路追踪

支持skywalking 分布式链路追踪

六 、构建发布

1. 微服务发布:

发布微服务的时候,需要引用的是微服务,不要引用stage, 如下图

2. 网关发布, 引用服务接口和聚合服务(中间服务)模块,还有stage 模块

七、总结

以上是木舟平台如何构建服务,平台定于11月20日发布1.0社区版本。也请大家到时候关注捧场。