当前位置:首页 > 嵌入式
  • 从串口驱动到Linux驱动模型

    点击上方「嵌入式大杂烩」,「星标公众号」第一时间查看嵌入式笔记! 原文:https://www.jianshu.com/p/3a9013b9569c 作者:Linkerist 大家好,我是ZhengN,本次给大家分享一篇长文。文章比较长,也比较老,但是也可以从中理清一些概念、知识点。下转原文: 本文通过对Linux下串口驱动的分析。由最上层的C库。到操作系统系统调用层的封装。再到tty子系统的核心。再到一系列线路规程。再到最底层的硬件操作。 对Linux中的tty子系统进行简要的说明。从理论到实践。以便读者能对OS原理有更深入的了解和更具体的掌握。 在具体分析之前。我们必须对串口。驱动。和Linux操作系统有一定的了解。这一阶段我们有三个问题需要解决: 1.什么是Linux操作系统。 2.什么是Linux设备驱动。 3.关于串口的种种。 要了解这些概念。如下我介绍了一点这方面的知识。不过遗憾的是对一些概念有着不可避免的向前引用。 这个过程中我会尽量忽略次要因素。以在本次调研中最主要目的为主线。如果读者您对这些概念已经有很深入的理解。可以直接阅读后面的代码分析: 1、什么是Linux操作系统 ? Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。 它能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。 Linux操作系统诞生于1991 年10 月5 日(这是第一次正式向外公布时间)。Linux存在着许多不同的Linux版本,但它们都使用了Linux内核。 Linux具备惊人的可移植性。可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、视频游戏控制台、台式计算机、大型机和超级计算机。 严格来讲,Linux这个词本身只表示Linux内核,但实际上人们已经习惯了用Linux来形容整个基于Linux内核,并且使用GNU 工程各种工具和数据库的操作系统。 在这几个简要的段落中。有不少新的名词被引入了进来。下面我对几个重要的概念进行描述。 A、关于类UNIX系统 类Unix系统(英文:Unix-like)指各种传统的Unix系统(比如FreeBSD、OpenBSD、SUN公司的Solaris)以及各种与传统Unix类似的系统(例如Minix、Linux、QNX等)。 它们虽然有的是自由软件,有的是商业软件,但都相当程度地继承了原始UNIX的特性,有许多相似处,并且都在一定程度上遵守POSIX规范。 这个在一些经典的操作系统教科书中已经作了说明。我们仅需知道。它和我们熟知的Windows系列操作系统一样。都是一种现代操作系统。对底层的计算机资源进行抽象。对上层用户提供调用接口。完成计算机应该完成的功能。 B、关于可移植性 可移植性指与软件从某一环境转移到另一环境下的难易程度。为获得较高的可移植性,在设计过程中常采用通用的程序设计语言和运行支撑环境。尽量不用与系统的底层相关性强的语言。 可移植性是软件质量之一,良好的可移植性可以提高软件的生命周期。代码的可移植性主题是软件;可移植性是软件产品的一种能力属性,其行为表现为一种程度,而表现出来的程度与环境密切相关。 一个操作系统的可移植性往往表现在它能在运行在不同的体系结构上。感性的理解就是可以支持的设备有很多。比如前文所说的,Linux可以运行在大型服务器上。各种平板电脑上。 前段时间有黑客成功的把Linux移植到一个佳能照相机上。并且在这个照相机上运行了一些主流的软件。可以说。只要有足够可以利用的硬件资源。就可以把Linux移植到这个硬件平台上去。这个资源的最低要求往往很低。这可以与对硬件资源要求很高的Windows有一个鲜明的对比。举个例子就是。当Windows 10的升级提示从你计算机的右下角弹出时。 你可以不假思索的点击‘马上升级’吗?我想大多数人对这个问题的答案是否定的。为什么?因为大多数情况下。升级之后就会变得更卡。延迟更大。一些无用而庞大的软件疯狂的占用你有限的计算机资源。而如果你选择的是Linux。你几乎可以任意的在计算机上安装软件。运行程序(如果你的内存不是太小。且硬盘交换分区足够的话)。 Linux核心已经将有限的硬件资源发挥到了极致。开源软件良好的模块化设计在各个层次上充分利用了程序的局部性原理。(当然这是在损失了一定易用性的前提下的。)。不好意思我扯远了。这些不是本文的重点。。 由于笔者没有土豪到有很多计算机。所以选择了一款比较便宜的ARM9开发板作为开发平台。它的CPU是三星公司生产的S3C2440。核心是ARM920T。 C、关于Linux的基本思想 Linux的基本思想有两点: 第一. 一切都是文件。系统中的所有都归结为一个文件,包括命令、硬件和软件设备、操作系统、进程等等对于操作系统内核而言,都被视为拥有各自特性或类型的文件。至于说Linux是基于Unix的,很大程度上也是因为这两者的基本思想十分相近 第二. 每个软件都有确定的用途。。 D、关于Linux的特点 完全免费 Linux是一款免费的操作系统,用户可以通过网络或其他途径免费获得,并可以任意修改其源代码。这是其他的操作系统所做不到的。 正是由于这一点,来自全世界的无数程序员参与了Linux的修改、编写工作,程序员可以根据自己的兴趣和灵感对其进行改变,这让Linux吸收了无数程序员的精华,不断壮大。 完全兼容POSIX1.0标准 这使得可以在Linux下通过相应的模拟器运行常见的DOS、Windows的程序。这为用户从Windows转到Linux奠定了基础。 许多用户在考虑使用Linux时,就想到以前在Windows下常见的程序是否能正常运行,这一点就消除了他们的疑虑。 多用户、多任务 Linux支持多用户,各个用户对于自己的文件设备有自己特殊的权利,保证了各用户之间互不影响。多任务则是现在电脑最主要的一个特点,Linux可以使多个程序同时并独立地运行。 良好的界面 Linux同时具有字符界面和图形界面。在字符界面用户可以通过键盘输入相应的指令来进行操作。它同时也提供了类似Windows图形界面的X-Window系统,用户可以使用鼠标对其进行操作。在X-Window环境中就和在Windows中相似,可以说是一个Linux版的Windows。 支持多种平台 Linux可以运行在多种硬件平台上,如具有x86、680x0、SPARC、Alpha等处理器的平台。此外Linux还是一种嵌入式操作系统,可以运行在掌上电脑、机顶盒或游戏机上。2001年1月份发布的Linux 2.4版内核已经能够完全支持Intel 64位芯片架构。同时Linux也支持多处理器技术。多个处理器同时工作,使系统性能大大提高。 文件类型 普通文件(regular file):就是一般存取的文件,由ls-al显示出来的属性中,第一个属性为 [-],例如 [-rwxrwxrwx]。另外,依照文件的内容,又大致可以分为: 1、纯文本文件(ASCII):这是Unix系统中最多的一种文件类型,之所以称为纯文本文件,是因为内容可以直接读到的数据,例如数字、字母等等。设置文件几乎都属于这种文件类型。举例来说,使用命令“cat ~/.bashrc”就可以看到该文件的内容(cat是将文件内容读出来)。 2、二进制文件(binary):系统其实仅认识且可以执行二进制文件(binary file)。Linux中的可执行文件(脚本,文本方式的批处理文件不算)就是这种格式的。举例来说,命令cat就是一个二进制文件。 3、数据格式的文件(data):有些程序在运行过程中,会读取某些特定格式的文件,那些特定格式的文件可以称为数据文件(data file)。举例来说,Linux在用户登入时,都会将登录数据记录在 /var/log/wtmp文件内,该文件是一个数据文件,它能通过last命令读出来。但使用cat时,会读出乱码。因为它是属于一种特殊格式的文件。 4、目录文件(directory):就是目录,第一个属性为[d],例如 [drwxrwxrwx]。 连接文件(link):类似Windows下面的快捷方式。第一个属性为 [l],例如 [lrwxrwxrwx]。 5、设备与设备文件(device):与系统外设及存储等相关的一些文件,通常都集中在 /dev目录。通常又分为两种: 块设备文件:就是存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件。第一个属性为 [b]。 字符设备文件:即串行端口的接口设备,例如键盘、鼠标等等。第一个属性为 [c]。 6、套接字(sockets):这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。第一个属性为 [s],最常在 /var/run目录中看到这种文件类型。 7、管道(FIFO,pipe):FIFO也是一种特殊的文件类型,它主要的目的是,解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写。第一个属性为 [p] 文件结构 /:根目录,所有的目录、文件、设备都在/之下,/就是Linux文件系统的组织者,也是最上级的领导者。 /bin:bin 就是二进制(binary)英文缩写。在一般的系统当中,都可以在这个目录下找到linux常用的命令。系统所需要的那些命令位于此目录。 /boot:Linux的内核及引导系统程序所需要的文件目录,比如 vmlinuz initrd.img 文件都位于这个目录中。在一般情况下,GRUB或LILO系统引导管理器也位于这个目录。 /cdrom:这个目录在刚刚安装系统的时候是空的。可以将光驱文件系统挂在这个目录下。例如:mount /dev/cdrom /cdrom /dev:dev 是设备(device)的英文缩写。这个目录对所有的用户都十分重要。因为在这个目录中包含了所有linux系统中使用的外部设备。但是这里并不是放的外部设备的驱动程序。这一点和常用的windows,dos操作系统不一样。它实际上是一个访问这些外部设备的端口。可以非常方便地去访问这些外部设备,和访问一个文件,一个目录没有任何区别。 /etc:etc这个目录是linux系统中最重要的目录之一。在这个目录下存放了系统管理时要用到的各种配置文件和子目录。要用到的网络配置文件,文件系统,x系统配置文件,设备配置信息,设置用户信息等都在这个目录下。 /home:如果建立一个用户,用户名是"xx",那么在/home目录下就有一个对应的/home/xx路径,用来存放用户的主目录。 /lib:lib是库(library)英文缩写。这个目录是用来存放系统动态连接共享库的。几乎所有的应用程序都会用到这个目录下的共享库。因此,千万不要轻易对这个目录进行什么操作,一旦发生问题,系统就不能工作了。 /lost+found:在ext2或ext3文件系统中,当系统意外崩溃或机器意外关机,而产生一些文件碎片放在这里。当系统启动的过程中fsck工具会检查这里,并修复已经损坏的文件系统。有时系统发生问题,有很多的文件被移到这个目录中,可能会用手工的方式来修复,或移到文件到原来的位置上。 /mnt:这个目录一般是用于存放挂载储存设备的挂载目录的,比如有cdrom等目录。可以参看/etc/fstab的定义。 /media:有些linux的发行版使用这个目录来挂载那些usb接口的移动硬盘(包括U盘)、CD/DVD驱动器等等。 /opt:这里主要存放那些可选的程序。 /proc:可以在这个目录下获取系统信息。这些信息是在内存中,由系统自己产生的。 /root:Linux超级权限用户root的家目录。 /sbin:这个目录是用来存放系统管理员的系统管理程序。大多是涉及系统管理的命令的存放,是超级权限用户root的可执行命令存放地,普通用户无权限执行这个目录下的命令,这个目录和/usr/sbin; /usr/X11R6/sbin或/usr/local/sbin目录是相似的,凡是目录sbin中包含的都是root权限才能执行的。 /selinux :对SElinux的一些配置文件目录,SElinux可以让linux更加安全。 /srv 服务启动后,所需访问的数据目录,举个例子来说,www服务启动读取的网页数据就可以放在/srv/www中 /tmp:临时文件目录,用来存放不同程序执行时产生的临时文件。有时用户运行程序的时候,会产生临时文件。/tmp就用来存放临时文件的。/var/tmp目录和这个目录相似。 /usr:这是linux系统中占用硬盘空间最大的目录。用户的很多应用程序和文件都存放在这个目录下。在这个目录下,可以找到那些不适合放在/bin或/etc目录下的额外的工具 /usr/local:这里主要存放那些手动安装的软件,即不是通过“新立得”或apt-get安装的软件。它和/usr目录具有相类似的目录结构。让软件包管理器来管理/usr目录,而把自定义的脚本(scripts)放到/usr/local目录下面、。 /usr/share :系统共用的东西存放地,比如/usr/share/fonts 是字体目录,/usr/share/doc和/usr/share/man帮助文件。 /var:这个目录的内容是经常变动的,看名字就知道,可以理解为vary的缩写,/var下有/var/log 这是用来存放系统日志的目录。/var/ www目录是定义Apache服务器站点存放目录;/var/lib 用来存放一些库文件,比如MySQL的,以及MySQL数据库的的存放地。 如上。相信读者已经对Linux操作系统有了一个概观。对于一些具体命令。笔者决定需要用到的时候再做说明。现在我们来看看第二个概念: 2、什么是Linux设备驱动 设备驱动最通俗的解释就是驱使硬件设备行动。驱动与底层硬件直接打交道,按照硬件设备的具体工作方式,读写设备的寄存器,完成设备的轮询、中断处理、DMA通信,进行物理内存向虚拟内存的映射等,最终让通信设备能收发数据,让显示设备能显示文字和画面,让存储设备能记录文件和数据。 Linux设备驱动是对底层硬件资源的抽象。对上层的操作系统其他服务提供一个良好的接口。让其他服务可以把一个特定的硬件。或是一种机制当做一个文件使用。使用通用的系统调用进行调用。 3、关于串口的种种 众所周知。我们现在的计算机上面有很多接口。如USB。网口。并口等。串口总线是其中的一个。串行接口简称串口,也称串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。 串行接口 (Serial Interface) 是指数据一位一位地顺序传送,其特点是通信线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传送速度较慢。一条信息的各位数据被逐位按顺序传送的通讯方式称为串行通讯。 串行通讯的特点是:数据位的传送,按位顺序进行,最少只需一根传输线即可完成;成本低但传送速度慢。串行通讯的距离可以从几米到几千米;根据信息的传送方向,串行通讯可以进一步分为单工、半双工和全双工三种。 串口通信的两种最基本的方式:同步串行通信方式和异步串行通信方式。 同步串行是指SPI(SerialPeripheral interface)的缩写,顾名思义就是串行外围设备接口。SPI总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息,TRM450是SPI接口。 异步串行是指UART(UniversalAsynchronous Receiver/Transmitter),通用异步接收/发送。UART是一个并行输入成为串行输出的芯片,通常集成在主板上。UART包含TTL电平的串口和RS232电平的串口。 TTL电平是3.3V的,而RS232是负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平,MDS2710、MDS SD4、EL805等是RS232接口,EL806有TTL接口。 串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。 RS-232 也称标准串口,最常用的一种串行通讯接口。它是在1970年由美国电子工业协会(EIA)联合贝尔系统、调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。 它的全名是“数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准”。传统的RS-232-C接口标准有22根线,采用标准25芯D型插头座(DB25),后来使用简化为9芯D型插座(DB9),现在应用中25芯插头座已很少采用。 RS-232采取不平衡传输方式,即所谓单端通讯。由于其发送电平与接收电平的差仅为2V至3V左右,所以其共模抑制能力差,再加上双绞线上的分布电容,其传送距离最大为约15米,最高速率为20kb/s。RS-232是为点对点(即只用一对收、发设备)通讯而设计的,其驱动器负载为3~7kΩ。所以RS-232适合本地设备之间的通信。 RS-422 标准全称是“平衡电压数字接口电路的电气特性”,它定义了接口电路的特性。典型的RS-422是四线接口。实际上还有一根信号地线,共5根线。其DB9连接器引脚定义。由于接收器采用高输入阻抗和发送驱动器比RS232更强的驱动能力,故允许在相同传输线上连接多个接收节点,最多可接10个节点。 即一个主设备(Master),其余为从设备(Slave),从设备之间不能通信,所以RS-422支持点对多的双向通信。接收器输入阻抗为4k,故发端最大负载能力是10×4k+100Ω(终接电阻)。 RS-422四线接口由于采用单独的发送和接收通道,因此不必控制数据方向,各装置之间任何必须的信号交换均可以按软件方式(XON/XOFF握手)或硬件方式(一对单独的双绞线)实现。 RS-422的最大传输距离为1219米,最大传输速率为10Mb/s。其平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能达到最大传输距离。只有在很短的距离下才能获得最高速率传输。一般100米长的双绞线上所能获得的最大传输速率仅为1Mb/s。 RS-485 是从RS-422基础上发展而来的,所以RS-485许多电气规定与RS-422相仿。如都采用平衡传输方式、都需要在传输线上接终接电阻等。RS-485可以采用二线与四线方式,二线制可实现真正的多点双向通信,而采用四线连接时,与RS-422一样只能实现点对多的通信,即只能有一个主(Master)设备,其余为从设备,但它比RS-422有改进,无论四线还是二线连接方式总线上可多接到32个设备。 RS-485与RS-422的不同还在于其共模输出电压是不同的,RS-485是-7V至+12V之间,而RS-422在-7V至+7V之间,RS-485接收器最小输入阻抗为12kΩ、RS-422是4kΩ;由于RS-485满足所有RS-422的规范,所以RS-485的驱动器可以在RS-422网络中应用。 RS-485与RS-422一样,其最大传输距离约为1219米,最大传输速率为10Mb/s。平衡双绞线的长度与传输速率成反比,在100kb/s速率以下,才可能使用规定最长的电缆长度。只有在很短的距离下才能获得最高速率传输。一般100米长双绞线最大传输速率仅为1Mb/s。 笔者采用的RS-232串口通信协议。下面对其通信接线方法做简要说明。目前较为常用的串口有9针串口(DB9)和25针串口(DB25),通信距离较近时(

    时间:2020-10-26 关键词: Linux 编程 嵌入式

  • 2020-1024=996:归并排序!

    今天继续给大家分享排序算法里面的另外一种排序算法:归并排序! 一、归并排序: 1、归并排序操作的核心思想: a、确定分界点:mid=(l+r)/2 b、递归排序左边和右边(排完左右两边的数,就会成为两个有序的序列了) c、归并(把上面的两个有序序列合并成一个有序的序列,用一个简单的词来说,就是合二为一!) 2、举例: 比如上图我们有两组已经排好的序列数字,我们要进行第三步合并,该如何进行呢?思路如下: a、这里先定义一个空的数组res,它主要是为了临时存放合并序列排序好的数字;我们从图中可以看到,第一个序列指针i指向数字1,第二序列指针j指向2,这个时候我们要比较两个数字的大小,小的数字就放到临时数组res里面去,这里我们明显知道数字1小于2,所以把1放到临时数组res里去 b、然后指针i往下移动,如下图所示,再次进行比较,明显发现指针j指向的数字2更小,把它放到res里面去,然后指针j往下移动,指针i不动,后面依次类推 c、如下图所示,两个指针都指向了数字5,如果遇到两个数字一样的话,一般是把第一个序列的数字放到临时数组res里面去,这点稍微要注意一下 d、最后把临时数组里面的是数字放到原来的数组里面去 注意:一个算法稳定,并不能说它的时间效率是稳定的;这里的稳定是说两个序列中有两个数是相同的,如果在排完序之后,他们的位置还是没有发生变化的话,那么这个排序就是稳定的,反之亦然! 3、归并排序的平均时间复杂度的计算推导: 注:图片来源:https://visualgo.net/zh/sorting 从图片的纵性来分析,当拆解到1的时候,这个时候什么数等于n除于它等于1,通过计算,我们知道是logn,然后再从横向分析,我们要最多比较n个数字,所以归并排序的时间复杂度就是:nlogn 二、代码示例: 代码: #include using namespace std;const int N = 1e5 + 10;int n;int q[N], tmp[N];void merge_sort(int q[],int l, int r){ if(l>=r)return;//判断序列中是否为空或者只有一个数字,如果是的话,我们就不用排序了 //确定分界点 int mid = l + r >> 1; //递归处理 merge_sort(q,l,mid); merge_sort(q,mid+1,r); //定义双指针 int k =0,i = l, j= mid+1; //归并处理 while(i 

    时间:2020-10-25 关键词: 排序算法 嵌入式

  • 再谈I2C!结合项目经验说说这项知识

    目录 背景 硬件层 数据传输协议 实际上如何工作? 单个主设备连接多个从机 多个主设备连接多个从机 如何编程? 总结 背景 I2C(Inter-Integrated Circuit),中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,是由飞利浦公司在1980年代初设计的,方便了主板、嵌入式系统或手机与周边设备组件之间的通讯。由于其简单性,它被广泛用于微控制器与传感器阵列,显示器,IoT设备,EEPROM等之间的通信。 I2C最重要的功能包括: 只需要两条总线; 没有严格的波特率要求,例如使用RS232,主设备生成总线时钟; 所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址; I2C是真正的多主设备总线,可提供仲裁和冲突检测; 传输速度; 标准模式:Standard Mode=100  Kbps 快速模式:Fast Mode=400  Kbps 高速模式:High speed mode=3.4 Mbps 超快速模式:Ultra fast mode=5 Mbps 最大主设备数:无限制; 最大从机数:理论上是127; 以上是I2C的一些重要特点,下面会进一步对I2C进行介绍。 硬件层 I2C协议仅需要一个SDA和SCL引脚。SDA是串行数据线的缩写,而SCL是串行时钟线的缩写。这两条数据线需要接上拉电阻。 设备间的连接如下所示: 使用I2C,可以将多个从机(Slave)连接到单个主设备(Master),并且还可以有多个主设备(Master)控制一个或多个从机(Slave)。 假如希望有多个微控制器(MCU)将数据记录到单个存储卡或将文本显示到单个LCD时,这个功能就非常有用。 I2C总线(SDA,SCL)内部都使用漏极开路驱动器(开漏驱动),因此SDA和SCL 可以被拉低为低电平,但是不能被驱动为高电平,所以每条线上都要使用一个上拉电阻,默认情况下将其保持在高电平; 上拉电阻的值取决于许多因素。德州仪器TI 建议 使用以下公式来计算正确的上拉电阻值: 其中  是逻辑低电压; 是逻辑低电流; 是信号的最大上升时间; 是总线(电线)电容; 具体如下所示: 根据上表,这里不难发现需要在做电阻选择需要满足几个条件; 灌电流 最大值为 ; 另外I2C总线规范和用户手册还为低电平输出电压 设置了最大值为0.4V 所以根据上述公式可以计算,对于5V的电源,每个上拉电阻阻值至少1.53kΩ,而对于3.3V的电源,每个电阻阻值至少967Ω。 如果觉得计算电阻值比较麻烦,也可以使用典型值 4.7kΩ。 上述推导过程可以参考 TI的文档《I2C Bus Pullup Resistor Calculation》 https://www.ti.com/lit/an/slva689/slva689.pdf 最终在调试的时候,当我们测量SDA或SCL信号并且逻辑LOW上的电压高于0.4V时,我们就知道可以知道灌电流太高了; 当然,这并不意味着每当灌电流超过3mA时,设备就会立即停止工作。但是,在操作超出其规格的设备时,应始终小心,因为它可能导致通信故障,缩短其使用寿命甚至甚至永久损坏设备。 数据传输协议 主设备和从设备进行数据传输时遵循以下协议格式。数据通过一条SDA数据线在主设备和从设备之间传输0和1的串行数据。串行数据序列的结构可以分为,开始条件,地址位,读写位,应答位,数据位,停止条件,具体如下所示; 开始条件 当主设备决定开始通讯时,需要发送开始信号,需要执行以下动作; 先将SDA线从高压电平切换到低压电平; 然后将 SCL从高电平切换到低电平; 在主设备发送开始条件信号之后,所有从机即使处于睡眠模式也将变为活动状态,并等待接收地址位。 具体如下图所示; 地址位 通常地址位占7位数据,主设备如果需要向从机发送/接收数据,首先要发送对应从机的地址,然后会匹配总线上挂载的从机的地址; I2C还支持10位寻址; 读写位 该位指定数据传输的方向; 如果主设备需要将数据发送到从设备,则该位设置为  0; 如果主设备需要往从设备接收数据,则将其设置为  1 。 ACK / NACK 主机每次发送完数据之后会等待从设备的应答信号ACK; 在第9个时钟信号,如果从设备发送应答信号 ACK,则 SDA会被拉低; 若没有应答信号 NACK,则 SDA会输出为高电平,这过程会引起主设备发生重启或者停止; 数据块 传输的数据总共有8位,由发送方设置,它需要将数据位传输到接收方。 发送之后会紧跟一个ACK / NACK位,如果接收器成功接收到数据,则设置为0。否则,它保持逻辑“ 1”。 重复发送,直到数据完全传输为止。 停止条件 当主设备决定结束通讯时,需要发送开始信号,需要执行以下动作; 先将SDA线从低电压电平切换到高电压电平; 再将SCL线从高电平拉到低电平; 具体如下图所示; 实际上如何工作? 第一步:起始条件 主设备通过将SDA线从高电平切换到低电平,再将SCL线从高电平切换到低电平,来向每个连接的从机发送启动条件 : 第二步:发送从设备地址 主设备向每个从机发送要与之通信的从机的7位或10位地址,以及相应的读/写位; 第三步:接收应答 每个从设备将主设备发送的地址与其自己的地址进行比较。如果地址匹配,则从设备通过将SDA线拉低一位以表示返回一个ACK位; 如果来自主设备的地址与从机自身的地址不匹配,则从设备将SDA线拉高,表示返回一个NACK位; 第四步:收发数据 主设备发送或接收数据到从设备; 第五步:接收应答 在传输完每个数据帧后,接收设备将另一个ACK位返回给发送方,以确认已成功接收到该帧: 第六步:停止通信 为了停止数据传输,主设备将SCL切换为高电平,然后再将SDA切换为高电平,从而向从机发送停止条件; 单个主设备连接多个从机 I2C总线上的主设备使用7位地址对从设备进行寻址,可以使用128( )个从机地址。 请使用4.7K上拉电阻将SDA和SCL线连接到Vcc; 多个主设备连接多个从机 多个主设备可以连接到一个或多个从机; 当两个主设备试图通过SDA线路同时发送或接收数据时,同一系统中的多个主设备就会出现问题。 为了解决这个问题,每个主设备都需要在发送消息之前检测SDA线是低电平还是高电平; 如果SDA线为低电平,则意味着另一个主设备可以控制总线,并且主设备应等待发送消息。 如果SDA线为高电平,则可以安全地发送消息。 要将多个主设备连接到多个从机,请使用下图,其中4.7K上拉电阻将SDA和SCL线连接到Vcc: 如何编程? Talk is cheap. Show me the code. 参考了STM32的HAL库中I2C驱动,主设备发送函数HAL_I2C_Master_Transmit()具体如下: /**  * @brief  Transmits in master mode an amount of data in blocking mode.  * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains  *                the configuration information for the specified I2C.  * @param  DevAddress Target device address: The device 7 bits address value  *         in datasheet must be shifted to the left before calling the interface  * @param  pData Pointer to data buffer  * @param  Size Amount of data to be sent  * @param  Timeout Timeout duration  * @retval HAL status  */HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c,                                           uint16_t DevAddress,                                           uint8_t *pData,                                           uint16_t Size,                                           uint32_t Timeout){  uint32_t tickstart = 0x00U;  /* Init tickstart for timeout management*/  tickstart = HAL_GetTick();  if(hi2c->State == HAL_I2C_STATE_READY){    /* Wait until BUSY flag is reset */    if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK){      return HAL_BUSY;    }    /* Process Locked */    __HAL_LOCK(hi2c);    /* Check if the I2C is already enabled */    if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE){      /* Enable I2C peripheral */      __HAL_I2C_ENABLE(hi2c);    }    /* Disable Pos */    hi2c->Instance->CR1 &= ~I2C_CR1_POS;    hi2c->State     = HAL_I2C_STATE_BUSY_TX;    hi2c->Mode      = HAL_I2C_MODE_MASTER;    hi2c->ErrorCode = HAL_I2C_ERROR_NONE;    /* Prepare transfer parameters */    hi2c->pBuffPtr    = pData;    hi2c->XferCount   = Size;    hi2c->XferOptions = I2C_NO_OPTION_FRAME;    hi2c->XferSize    = hi2c->XferCount;    /* Send Slave Address */    if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK){      if(hi2c->ErrorCode == HAL_I2C_ERROR_AF){        /* Process Unlocked */        __HAL_UNLOCK(hi2c);        return HAL_ERROR;      }else{        /* Process Unlocked */        __HAL_UNLOCK(hi2c);        return HAL_TIMEOUT;      }    }    /* Clear ADDR flag */    __HAL_I2C_CLEAR_ADDRFLAG(hi2c);    while(hi2c->XferSize > 0U){      /* Wait until TXE flag is set */      if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK){        if(hi2c->ErrorCode == HAL_I2C_ERROR_AF){          /* Generate Stop */          hi2c->Instance->CR1 |= I2C_CR1_STOP;          return HAL_ERROR;        }else{          return HAL_TIMEOUT;        }      }      /* Write data to DR */      hi2c->Instance->DR = (*hi2c->pBuffPtr++);      hi2c->XferCount--;      hi2c->XferSize--;      if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET)          && (hi2c->XferSize != 0U)){        /* Write data to DR */        hi2c->Instance->DR = (*hi2c->pBuffPtr++);        hi2c->XferCount--;        hi2c->XferSize--;      }      /* Wait until BTF flag is set */      if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK){                  if(hi2c->ErrorCode == HAL_I2C_ERROR_AF){          /* Generate Stop */          hi2c->Instance->CR1 |= I2C_CR1_STOP;          return HAL_ERROR;        }else{          return HAL_TIMEOUT;        }      }    }    /* Generate Stop */    hi2c->Instance->CR1 |= I2C_CR1_STOP;    hi2c->State = HAL_I2C_STATE_READY;    hi2c->Mode = HAL_I2C_MODE_NONE;        /* Process Unlocked */    __HAL_UNLOCK(hi2c);    return HAL_OK;  }else{    return HAL_BUSY;  }} 总结 本文主要介绍I2C的入门基础知识,从I2C协议的硬件层,协议层进行了简单介绍;作者能力有限,难免存在错误和纰漏,请大佬不吝赐教。 -END- 来源 | 小麦大叔 作者 | 菜刀和小麦 | 整理文章为传播相关技术,版权归原作者所有 | | 如有侵权,请联系删除 | 【1】用C实现:均值计算的两种算法 【2】单片机DSP必备概念:快速教会你傅立叶算法 【3】几种常见的校验算法 【4】C语言编程:九种必会查找算法(附完整代码) 【5】图解机器学习:请不要再说看不懂算法! 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 通讯 嵌入式

  • 《我想进大厂》之JVM夺命连环10问

    这是面试专题系列第五篇JVM篇。这一篇可能稍微比较长,没有耐心的同学建议直接拖到最后。 说说JVM的内存布局? Java虚拟机主要包含几个区域: 堆:堆Java虚拟机中最大的一块内存,是线程共享的内存区域,基本上所有的对象实例数组都是在堆上分配空间。堆区细分为Yound区年轻代和Old区老年代,其中年轻代又分为Eden、S0、S1 3个部分,他们默认的比例是8:1:1的大小。 栈:栈是线程私有的内存区域,每个方法执行的时候都会在栈创建一个栈帧,方法的调用过程就对应着栈的入栈和出栈的过程。每个栈帧的结构又包含局部变量表、操作数栈、动态连接、方法返回地址。 局部变量表用于存储方法参数和局部变量。当第一个方法被调用的时候,他的参数会被传递至从0开始的连续的局部变量表中。 操作数栈用于一些字节码指令从局部变量表中传递至操作数栈,也用来准备方法调用的参数以及接收方法返回结果。 动态连接用于将符号引用表示的方法转换为实际方法的直接引用。 元数据:在Java1.7之前,包含方法区的概念,常量池就存在于方法区(永久代)中,而方法区本身是一个逻辑上的概念,在1.7之后则是把常量池移到了堆内,1.8之后移出了永久代的概念(方法区的概念仍然保留),实现方式则是现在的元数据。它包含类的元信息和运行时常量池。 Class文件就是类和接口的定义信息。 运行时常量池就是类和接口的常量池运行时的表现形式。 本地方法栈:主要用于执行本地native方法的区域 程序计数器:也是线程私有的区域,用于记录当前线程下虚拟机正在执行的字节码的指令地址 知道new一个对象的过程吗? 当虚拟机遇见new关键字时候,实现判断当前类是否已经加载,如果类没有加载,首先执行类的加载机制,加载完成后再为对象分配空间、初始化等。 首先校验当前类是否被加载,如果没有加载,执行类加载机制 加载:就是从字节码加载成二进制流的过程 验证:当然加载完成之后,当然需要校验Class文件是否符合虚拟机规范,跟我们接口请求一样,第一件事情当然是先做个参数校验了 准备:为静态变量、常量赋默认值 解析:把常量池中符号引用(以符号描述引用的目标)替换为直接引用(指向目标的指针或者句柄等)的过程 初始化:执行static代码块(cinit)进行初始化,如果存在父类,先对父类进行初始化 Ps:静态代码块是绝对线程安全的,只能隐式被java虚拟机在类加载过程中初始化调用!(此处该有问题static代码块线程安全吗?) 当类加载完成之后,紧接着就是对象分配内存空间和初始化的过程 首先为对象分配合适大小的内存空间 接着为实例变量赋默认值 设置对象的头信息,对象hash码、GC分代年龄、元数据信息等 执行构造函数(init)初始化 知道双亲委派模型吗? 类加载器自顶向下分为: Bootstrap ClassLoader启动类加载器:默认会去加载JAVA_HOME/lib目录下的jar Extention ClassLoader扩展类加载器:默认去加载JAVA_HOME/lib/ext目录下的jar Application ClassLoader应用程序类加载器:比如我们的web应用,会加载web程序中ClassPath下的类 User ClassLoader用户自定义类加载器:由用户自己定义 当我们在加载类的时候,首先都会向上询问自己的父加载器是否已经加载,如果没有则依次向上询问,如果没有加载,则从上到下依次尝试是否能加载当前类,直到加载成功。 说说有哪些垃圾回收算法? 标记-清除 统一标记出需要回收的对象,标记完成之后统一回收所有被标记的对象,而由于标记的过程需要遍历所有的GC ROOT,清除的过程也要遍历堆中所有的对象,所以标记-清除算法的效率低下,同时也带来了内存碎片的问题。 复制算法 为了解决性能的问题,复制算法应运而生,它将内存分为大小相等的两块区域,每次使用其中的一块,当一块内存使用完之后,将还存活的对象拷贝到另外一块内存区域中,然后把当前内存清空,这样性能和内存碎片的问题得以解决。但是同时带来了另外一个问题,可使用的内存空间缩小了一半! 因此,诞生了我们现在的常见的年轻代+老年代的内存结构:Eden+S0+S1组成,因为根据IBM的研究显示,98%的对象都是朝生夕死,所以实际上存活的对象并不是很多,完全不需要用到一半内存浪费,所以默认的比例是8:1:1。 这样,在使用的时候只使用Eden区和S0S1中的一个,每次都把存活的对象拷贝另外一个未使用的Survivor区,同时清空Eden和使用的Survivor,这样下来内存的浪费就只有10%了。 如果最后未使用的Survivor放不下存活的对象,这些对象就进入Old老年代了。 PS:所以有一些初级点的问题会问你为什么要分为Eden区和2个Survior区?有什么作用?就是为了节省内存和解决内存碎片的问题,这些算法都是为了解决问题而产生的,如果理解原因你就不需要死记硬背了 标记-整理 针对老年代再用复制算法显然不合适,因为进入老年代的对象都存活率比较高了,这时候再频繁的复制对性能影响就比较大,而且也不会再有另外的空间进行兜底。所以针对老年代的特点,通过标记-整理算法,标记出所有的存活对象,让所有存活的对象都向一端移动,然后清理掉边界以外的内存空间。 那么什么是GC ROOT?有哪些GC ROOT? 上面提到的标记的算法,怎么标记一个对象是否存活?简单的通过引用计数法,给对象设置一个引用计数器,每当有一个地方引用他,就给计数器+1,反之则计数器-1,但是这个简单的算法无法解决循环引用的问题。 Java通过可达性分析算法来达到标记存活对象的目的,定义一系列的GC ROOT为起点,从起点开始向下开始搜索,搜索走过的路径称为引用链,当一个对象到GC ROOT没有任何引用链相连的话,则对象可以判定是可以被回收的。 而可以作为GC ROOT的对象包括: 栈中引用的对象 静态变量、常量引用的对象 本地方法栈native方法引用的对象 垃圾回收器了解吗?年轻代和老年代都有哪些垃圾回收器? 年轻代的垃圾收集器包含有Serial、ParNew、Parallell,老年代则包括Serial Old老年代版本、CMS、Parallel Old老年代版本和JDK11中的船新的G1收集器。 Serial:单线程版本收集器,进行垃圾回收的时候会STW(Stop The World),也就是进行垃圾回收的时候其他的工作线程都必须暂停 ParNew:Serial的多线程版本,用于和CMS配合使用 Parallel Scavenge:可以并行收集的多线程垃圾收集器 Serial Old:Serial的老年代版本,也是单线程 Parallel Old:Parallel Scavenge的老年代版本 CMS(Concurrent Mark Sweep):CMS收集器是以获取最短停顿时间为目标的收集器,相对于其他的收集器STW的时间更短暂,可以并行收集是他的特点,同时他基于标记-清除算法,整个GC的过程分为4步。 初始标记:标记GC ROOT能关联到的对象,需要STW 并发标记:从GCRoots的直接关联对象开始遍历整个对象图的过程,不需要STW 重新标记:为了修正并发标记期间,因用户程序继续运作而导致标记产生改变的标记,需要STW 并发清除:清理删除掉标记阶段判断的已经死亡的对象,不需要STW 从整个过程来看,并发标记和并发清除的耗时最长,但是不需要停止用户线程,而初始标记和重新标记的耗时较短,但是需要停止用户线程,总体而言,整个过程造成的停顿时间较短,大部分时候是可以和用户线程一起工作的。 G1(Garbage First):G1收集器是JDK9的默认垃圾收集器,而且不再区分年轻代和老年代进行回收。 G1的原理了解吗? G1作为JDK9之后的服务端默认收集器,且不再区分年轻代和老年代进行垃圾回收,他把内存划分为多个Region,每个Region的大小可以通过-XX:G1HeapRegionSize设置,大小为1~32M,对于大对象的存储则衍生出Humongous的概念,超过Region大小一半的对象会被认为是大对象,而超过整个Region大小的对象被认为是超级大对象,将会被存储在连续的N个Humongous Region中,G1在进行回收的时候会在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间优先回收收益最大的Region。 G1的回收过程分为以下四个步骤: 初始标记:标记GC ROOT能关联到的对象,需要STW 并发标记:从GCRoots的直接关联对象开始遍历整个对象图的过程,扫描完成后还会重新处理并发标记过程中产生变动的对象 最终标记:短暂暂停用户线程,再处理一次,需要STW 筛选回收:更新Region的统计数据,对每个Region的回收价值和成本排序,根据用户设置的停顿时间制定回收计划。再把需要回收的Region中存活对象复制到空的Region,同时清理旧的Region。需要STW 总的来说除了并发标记之外,其他几个过程也还是需要短暂的STW,G1的目标是在停顿和延迟可控的情况下尽可能提高吞吐量。 什么时候会触发YGC和FGC?对象什么时候会进入老年代? 当一个新的对象来申请内存空间的时候,如果Eden区无法满足内存分配需求,则触发YGC,使用中的Survivor区和Eden区存活对象送到未使用的Survivor区,如果YGC之后还是没有足够空间,则直接进入老年代分配,如果老年代也无法分配空间,触发FGC,FGC之后还是放不下则报出OOM异常。 YGC之后,存活的对象将会被复制到未使用的Survivor区,如果S区放不下,则直接晋升至老年代。而对于那些一直在Survivor区来回复制的对象,通过-XX:MaxTenuringThreshold配置交换阈值,默认15次,如果超过次数同样进入老年代。 此外,还有一种动态年龄的判断机制,不需要等到MaxTenuringThreshold就能晋升老年代。如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。 频繁FullGC怎么排查? 这种问题最好的办法就是结合有具体的例子举例分析,如果没有就说一般的分析步骤。发生FGC有可能是内存分配不合理,比如Eden区太小,导致对象频繁进入老年代,这时候通过启动参数配置就能看出来,另外有可能就是存在内存泄露,可以通过以下的步骤进行排查: jstat -gcutil或者查看gc.log日志,查看内存回收情况 S0 S1 分别代表两个Survivor区占比 E代表Eden区占比,图中可以看到使用78% O代表老年代,M代表元空间,YGC发生54次,YGCT代表YGC累计耗时,GCT代表GC累计耗时。 [GC [FGC 开头代表垃圾回收的类型 PSYoungGen: 6130K->6130K(9216K)] 12274K->14330K(19456K), 0.0034895 secs代表YGC前后内存使用情况 Times: user=0.02 sys=0.00, real=0.00 secs,user表示用户态消耗的CPU时间,sys表示内核态消耗的CPU时间,real表示各种墙时钟的等待时间 这两张图只是举例并没有关联关系,比如你从图里面看能到是否进行FGC,FGC的时间花费多长,GC后老年代,年轻代内存是否有减少,得到一些初步的情况来做出判断。 dump出内存文件在具体分析,比如通过jmap命令jmap -dump:format=b,file=dumpfile pid,导出之后再通过 Eclipse Memory Analyzer等工具进行分析,定位到代码,修复 这里还会可能存在一个提问的点,比如CPU飙高,同时FGC怎么办?办法比较类似 找到当前进程的pid,top -p pid -H 查看资源占用,找到线程 printf “%x\n” pid,把线程pid转为16进制,比如0x32d jstack pid|grep -A 10 0x32d查看线程的堆栈日志,还找不到问题继续 dump出内存文件用MAT等工具进行分析,定位到代码,修复 JVM调优有什么经验吗? 要明白一点,所有的调优的目的都是为了用更小的硬件成本达到更高的吞吐,JVM的调优也是一样,通过对垃圾收集器和内存分配的调优达到性能的最佳。 简单的参数含义 首先,需要知道几个主要的参数含义。 -Xms设置初始堆的大小,-Xmx设置最大堆的大小 -XX:NewSize年轻代大小,-XX:MaxNewSize年轻代最大值,-Xmn则是相当于同时配置-XX:NewSize和-XX:MaxNewSize为一样的值 -XX:NewRatio设置年轻代和年老代的比值,如果为3,表示年轻代与老年代比值为1:3,默认值为2 -XX:SurvivorRatio年轻代和两个Survivor的比值,默认8,代表比值为8:1:1 -XX:PretenureSizeThreshold 当创建的对象超过指定大小时,直接把对象分配在老年代。 -XX:MaxTenuringThreshold设定对象在Survivor复制的最大年龄阈值,超过阈值转移到老年代 -XX:MaxDirectMemorySize当Direct ByteBuffer分配的堆外内存到达指定大小后,即触发Full GC 调优 为了打印日志方便排查问题最好开启GC日志,开启GC日志对性能影响微乎其微,但是能帮助我们快速排查定位问题。-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.log 一般设置-Xms=-Xmx,这样可以获得固定大小的堆内存,减少GC的次数和耗时,可以使得堆相对稳定 -XX:+HeapDumpOnOutOfMemoryError让JVM在发生内存溢出的时候自动生成内存快照,方便排查问题 -Xmn设置新生代的大小,太小会增加YGC,太大会减小老年代大小,一般设置为整个堆的1/4到1/3 设置-XX:+DisableExplicitGC禁止系统System.gc(),防止手动误触发FGC造成问题 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下: 长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: C语言 嵌入式

  • 新基建背景下,这些姿势架构师必须懂!

    今天,北极熊全程参与了“UCloud用户大会暨Think in Cloud 2020”,不知不觉TIC大会自2014年创办以来已经连续六届了,本次大会的主题是“构建•创见”,一如既往的保持了高水准。今年1月UCloud在科创板挂牌,一路披荆斩棘,成为中国第一家公有云科创板上市公司。通过本次大会可以感受到,上市之后UCloud中立的初心不改,依然秉持用户为先的理念令人信赖。 本次大会料很多,尤其是技术分论坛,揭秘了许多公有云背后的技术秘密。下面北极熊一一介绍下本次大会技术分论坛的内容,帮助没有时间参会的朋友快速了解下这次大会都有哪些技术干货。 一、物联网,UCloud准备好了! 在《新基建.新物联》议题中,UCloud 物联网产品负责人钱波提到,2020年全球活跃的物联网设备达到100亿,到2025年全球物联网设备数量预计达到220亿,这么多设备必然会给产业互联网带来新的变革。 对于物联网,UCloud早已开始进军布局,比如2019年7月发布了第一版的UIoT Core平台,同时配带了直连设备的SDK;今年的3月份和国内领先的操作系统商RT-Thread 深度合作推出了RT-Thread生态下的UIoT SDK;今年的5月份重磅发布了一个面向非直连设备的UIoT Edge 产品,同时也发布了针对传感器的Modbus驱动;今年7月份和一应科技联合发布了智能社区的解决方案,同时也发布了基于电力能耗能源的DLT645的设备驱动。 提到物联网,必然要分为三个部分云、边、端,云主要是解决设备的广连接和消息的高并发问题;边主要是解决那些不能直接上云的设备的连接和本地计算的问题;而那些不能上云的设备包括大量的传感器、行业的表计甚至是工业的机床,我们称之为端。正是由于UIoT Core实现了设备和云上面应用的一个连接的桥梁,所以我们通常认为云端的功能是IoT产品家族的一个最基本的底座。 在物联网通讯方面,UCloud推出了物联网通信云平台-UIoT Core,通过UIoT SDK,最快可实现10分钟完成设备数据上云。费用方面1.6元/百万消息,1.0元/百万分钟(100万分钟=1.9年),在目前的主流公有云里面,是性价比最高的。 同时,UCloud物联网解决方案已经拥有智慧社区、电力能源、智能硬件、智能制药AIoT合作伙伴和案例,形成了物联网生态。对此,钱波重点分享了多个使用场景下的案例,像一应科技、晟能科技、奕客围棋等均已借助UIoT Core平台实现了云端物联。 二、SDN,解决异构网络互联互通! 在《异构网络之SDN解决方案》议题中,UCloud 虚拟网络平台产品负责人周健首先分享了UCloud SDN网络的架构,其中UXR系统被设计用来解决各网关的流量转发,公有云VPC/混合云网关/物理云网关主要解决的是异构网络的互联互通,UDPN/云联网主要解决异构网络的跨域互联互通,从而实现统一的网络模型、高效互联互通、安全隔离。 UCloud公有云VPC除了提供标准的VPC功能,还支持一系列高级特性,如分布式路由器DVR,让用户的东西向流量不成为瓶颈;支持子网跨可用区,满足用户业务系统的高可用部署需求;支持自定义路由表,满足用户复杂的自定义网络架构。 对于有些用户业务来说,需要部署在不同的Region/AZ,以满足业务级别的高可用。所以,跨域之间的互联互通也就成为用户网络的需求。这就可以通过UDPN和云联网两个产品来满足此类需求。UDPN构建了覆盖国内外近30节点的专线网络,支持即买即用,也支持从1M到数G带宽的动态调整。云联网是UCloud正在开发中的一个产品,它把跨域网络联通及维护的细节对用户屏蔽,用户只需要将需要打通和断开的地域向云联网中添加和删除。 最后,周健还分享了一个第三方智能风险管理服务提供商的案例,该客户面临着在国内分布广、组网困难,海外业务需求建设、运维困难,业务发展迅速无法满足快速扩容的困境。通过UCloud SDN网络实现了全球快速布点,满足了业务高速增长的需求,同时在公有云+混合云模式下,核心机密放在托管云、其他业务部署在公有云,从而最大化资源利用,节约了成本。 三、出海抗DDoS,UCloud构筑坚固防线! 在UCloud高级产品经理冯业浩分享的《UCloud海外DDOS防护》中,详细介绍了DDoS攻击的原理、黑客可能的攻击方法,以及针对不同的攻击流量,UCloud在防DDoS方面提供全球分级应对方案。 本地清洗产品的防护阈值上限为20G,可以抵御小规模的DDoS攻击,好处是一旦购买服务之后,用户账户下所有的EIP都能够享受到相关的DDoS防御的服务,EIP的延时不会有任何的影响。 高防EIP则引入了专门的线路进行高防的清洗,线路的带宽足够将所有的攻击流量和正常流量引入,同时线路还可以保证访问效果和普通的IP地址相比没有明显的差别,高防EIP拥有70G的防护能力,延时等同于普通的EIP。 分布式高防产品采用的思路,是用单独的高防机房进行清洗之后通过公网进行回源。目前的高防机房位于香港,覆盖东南亚的效果很不错,最高延时不超过60毫秒。相对于普通的高防产品,分布式高防产品没有域名的限制,没有端口的限制,也没有QPS的数量的限制,因此非常适合用于大带宽的DDoS防御。 Anycast采用了分治法的思路,UCloud在全球八大入口点宣告了同样的高防地址,这样所有的攻击流量和正常业务流量都会通过这八个入口点分别就近流入,而攻击流量会在这8个入口点分别进行清洗,完成之后公网正常的业务流量,将会通过UCloud的全球骨干网进行回源送到最终的源站。 介绍中还提到多个受到大流量攻击的典型案例,比如某游戏客户曾经受到高达226G的攻击,UCloud高防方案帮助其有效抵御了30多分钟的攻击。 四、直播时代,UCloud打造音视频生产工具! UCloud边缘计算产品负责人裴志伟分享的《URTC 实时音视频,助力企业构建新一代生产工具》中,他讲到,最近一年多,直播和视频会议需求强烈,UCloud的音视频解决方案URTC已支持实时语音通话、多人实时会议、万人互动直播三大场景。 要实现URTC产品的落地支持,首先SDK的兼容性非常重要。目前URTC已经适配了超过4000款主流pc、手机、浏览器等等;其中web场景已经较好的支持了手机默认浏览器(最常见的是短信内嵌url直接发起在线视频办理业务),也支持了微信浏览器(常见的一些是群管理、私域流量以及在线客服等等);而即将迎来爆发的视频物联网常用的linux终端,也已经支持了主流的嵌入式芯片、海思、树莓派等等。 其次是用户体验的改善,在RTC的定义中,实时音视频需要延迟低于400ms。而URTC可以在国内提供100ms、东南亚200ms、中国大陆到欧美地区低于300ms的超低延时用户体验。 用户体验部分还有个核心环节是抵抗公共互联网的不可靠,URTC通过一系列复杂的基于网络实际状况的自适应调优技术,目前提供了视频最高抗40%丢包以及音频最高抗70%丢包的能力。并且在网络条件恶化的情况下,还有智能降低码率、帧率以及最极端的只保留音频等策略,从而最大可能性的保证会议/教学等诸多场景的业务连续性,当然这些策略都是可以根据用户业务特性进行灵活配置的。 除此之外,用户使用URTC SDK时还会有些相关性较高的产品能力需求,结合UCloud公有云平台,URTC将服务端大规模转码、混流、AI处理、加水印、录制存储、旁路推流等有机整体的结合在一起,给用户提供一站式直播、录制音视频服务。同时UFile对象存储的接入也降低了一些用户需要走公网传输数据的成本,在一些限定场景下也能通过弹性和错峰大幅降低成本。 五、归档存储,UCloud做到了低成本、高可靠! 《海量数据云归档最佳实践》议题中,UCloud高级产品经理周恭元介绍到,UCloud最早在2017年就推出了第一代的归档存储产品,2019年针对对象存储产品的不同存储类型做了统一,在对象存储服务中提供了3种不同的存储类型,单价上由热至冷,存储成本分别为标准存储的一半与四分之一。用户可以实现在同一个存储空间里存储数据,借助生命周期策略自动对数据进行降冷处理,从而优化存储成本。 UCloud在今年8月发布了新一代归档存储产品,成为了国内首先采用JBOD磁盘阵列与SMR叠瓦式磁盘介质提供公有云归档存储服务的厂商。在具备分钟级别取回时效、11个9以上数据可靠性的归档存储产品中,突破了传统3分钱每GB每月的定价区间,达到了0.024元每GB每月。 UCloud通过采用西部数据高密度的叠瓦式磁盘存储介质与磁盘阵列方案,单位机架的存储容量相比较传统36盘位的存储服务器提升5.375倍,同时借助磁盘休眠的技术,降低了90%的硬盘能耗。 在降低成本的同时,新一代归档存储的全新架构也带来了更高的可靠性保障,通过双机头的故障快速切换,能在数据取回快速的同时提供极高的可用性保障;通过采用Intel大比例纠删码冗余策略,归档存储提供了同时4块硬盘故障情况下的数据可靠性保障,由于归档存储读取请求较小的缘故,新一代归档存储还引入了定期的一致性校验应对磁盘的静默错误,以保障数据一致性。 归档存储适用的是写大于读的数据存储场景,核心场景汇总为三类,分别为多媒体数据归档、历史数据合规性归档,和大数据、AI分析数据的归档。对于这些场景的存储,UCloud新一代归档存储做到了低成本、高可靠! 六、Cube,免服务器运维的容器服务! 针对议题《UCloud Cube容器技术解析》,UCloud 容器云研发负责人张苗磊介绍了一款Serverless容器实例服务Cube。Cube产品的优势是免服务器运维,用户无需关注基础设施资源的运行,按秒计费,秒级启动,并且有自愈功能,比K8S更轻量,更灵活。K8S比较复杂,用户在关心其应用的同时还要学习K8S知识,Cube理念是将K8S的最小运行单元pod,直接暴露出来,而将其他K8S繁琐的概念统统封装起来。 Cube对外仅暴露pod,用户所需要的镜像运行命令,和其他资源关联的关系都可以通过标准的K8S yaml提交给UCloud API。这样pod就可以直接运行起来了,而用户实际所需要负责的仅仅是pod所需要的资源大小。 Cube背后的pod是如何实现的呢,我们知道原生的docker实现,由于不能很好的做到资源隔离和租户隔离,因此无法在云厂商上直接暴露给用户。因此Cube对docker运行时进行了大量的改造,在标准的虚拟机内实现的容器,QEMU提供了虚拟机的隔离能力,而用户在QEMU虚拟机内会部署一个完整的docker或者container。 Cube对外暴露的是标准的K8S CRI接口,但具体的实现是一个轻量级的虚拟机,而用户实际需要运行的容器是在轻量级的虚拟机内拉起的,这样的带来的好处是Cube融合了虚拟机资源隔离的优点和容器快速启动的优点。 为了完全的比拟docker实现的容器快速启动,Cube在性能上也做了很多优化,比如将QEMU虚拟机换成了Firecracker轻量级虚拟机,能够进一步的降低虚拟化损耗,并且拉起速度能够降低到100毫秒。 当然容器的快速启动,也不仅包括容器启动的时间,还包括了镜像拉取的时间。为了解决这种问题,UCloud实现了镜像缓存的功能,即用户的实际镜像拉取在第一次拉取中会缓存,在镜像缓存中心的镜像加载是直接通过NBD的形式,直接挂载到Cube容器里,这样就可以实现Cube的快速启动而跳过了镜像拉取的时间。对于特别大的镜像,用户也可以选择预先加载的形式,直接加载到镜像缓存中心从而进一步降低了启动时间。 除了这些,张苗磊还针对Cube的网络、存储、监控/日志等方面做了详细的技术解析,并且最后讲解了一个Cube典型的使用场景。上图中我们可以看到所有的计算功能都可以通过Cube容器的实例来提供。而入向流量通过ULB来实现,后接的存储、数据库都可以通过云上原生提供的Mysql或者UFS来实现,这样架构可以很好的提供计算、存储分离,并且能够提供快速横向扩展和弹性资源使用的能力。 总结:因为开放,所以信赖! 除了以上这些,TIC大会的技术分论坛直播还有UCloud 私有云研发负责人彭兴宇的《UCloudStack 2.0技术方案分享》、UCloud 人工智能产品负责人王达侃的《人脸识别测温产品的设计与实践》、UCloud 医疗产品负责人王彬的《UCloud智慧医疗云解决方案发布》以及英特尔全球云计算总监Santiago Durante的《构建智慧云基石, 加速云上创新》四个议题。本文限于篇幅就不一一详细介绍了,感兴趣的大家可以点击“阅读原文”链接查看完整直播视频回放。 最后想说的是,通过今天TIC大会的技术分论坛直播,我有两点体会: 第一,从头到尾,一直能感受到UCloud在技术上开放的气息,因为开放,所以信赖。作为最早专注云计算的厂商之一,UCloud在技术上积累的实力不容小觑。一直能够保持开放中立的初心,让用户用的放心。 第二,UCloud在技术上已经走的更深入,更有自信,今天的分享涉及基础架构到物联网、音视频应用、医疗行业、安全等多个维度、多个层面的技术,并且UCloud的产品或者解决方案已经很成熟,在不同行业均有丰富的案例。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 架构师 嵌入式

  • 架构师说了:不想做背锅侠?生产问题要这样查

    话说这天一大早,那个悲催的中年架构师大刘又被手机微信群给炸醒。部门的运维兄弟在公司微信群里说: 短信的生产环境服务器 CPU 占用率过高,疯狂报警。是不是你们昨天上线看门狗导致的? 大刘迷了巴登的想了想,没错,昨天确实给短信服务装上了看门狗。但是看门狗服务肯定不会有问题(架构师必备的蜜汁自信),而且上线之前各轮测试也都测过了,没见过这个想象啊。 难道是测试妹子没测试到位?难道线上短信应用自身出现了问题? 生产无小事,小事更不能忽视,主要是怕扣绩效奖金。大刘迅速打开电脑,打开 VPN ,远程登上短信生产服务器,开始大刘最拿手的 2W1H 三板斧诊断之旅。 接下来的诊断内容有点烧脑,节奏有点快,请大家坐稳扶好。 1. 病号是谁(WHO)? 大刘拿出控制台诊断仪器,输入 top 命令一探究竟。我勒个去,不看不知道一看吓一跳,PID 为 1878 的病号,CPU 占用居然 200% 多。 问题算是定位到了,但是 PID 为 1878 的病号到底是谁,难道真是昨天上线的看门狗 ? 虽然大刘久经职场,但是排查生产问题时,内心还是比较忐忑,毕竟这是生产环境。 说时迟那时快,只见大刘一个命令输入: ps -ef | grep 1878 定睛一看,原来是放屁瞅别人,短信服务自己在作祟,和看门狗没关系,大刘心里一下子平缓了不少。 锅找到了主儿,其实这个时候大刘完全可以把这个问题甩给短信开发团队,但是大刘最喜欢做的不是甩锅,而是打破砂锅刨到底。 2. 病号哪里出了问题(WHERE)? 为什么 1878 号病人占用 CPU 会这么高呢? 只见黑乎乎的控制台诊断仪器上,大刘熟练的输入: jstack -l 1878 >> 1878号病历.log 这样便得到一份 1878 号病人的病历详情单,一会儿用得上。 到底 1878 号病人的哪个部位出了问题呢? 话没说完,只见大刘又在控制台诊断仪器上,输入一个: top -Hp 1878 白板黑字,把 1878 号病人的器官信息全部列了出来。 看到结果,甚是一惊,PID 代号为 8721 的器官占用 CPU 100% 多。 疑惑油然而生,这个 PID 代号 为 8721 的器官是啥,是头、是眼睛、还是胳膊腿呢?这些器官展示的 PID 列都是昵称,都这么善于伪装,如何揭露它的真面目呢? 还好大刘有高招,借助照妖镜算法,熟练的输入: printf "%x\n" 8721 果真使得代号为 8721 的器官,现了真身,真实身份居然是 2211 的呼吸道,怪不得病号一直气喘吁吁,上气不接下气。 到这一步还无法对症下药啊,还需要进一步确诊 2211 的呼吸道到底出了什么幺蛾子,导致 1878 号病人一直气喘吁吁,上气不接下气? 只见黑乎乎的控制台诊断仪器上,大刘再次飞一般的在输入: grep 2211 -A20 1878号病历.log 诊断结果随之显示在诊断仪器上。 曾经背了很多锅的大刘,看到诊断结果心里乐了一下,一眼就看出是高并发情况下用了 HashMap 的问题(请大家们自行寻找谷歌、百度,就不在此深入展开啦),终于拨开云雾见青天。 3. 如何对症下药( HOW )? 在大刘行云流水没有一丝一毫的拖泥带水般的神操作下,1878 号病人的诊断也就结束了,这个锅就彻底被打破了。 术业有专攻,大刘就可以郑重的告诉短信开发同事具体原因了,捉得病根,开发同事也就可以对症下药啦。 大刘这套行走江湖的诊断问题方式你 get 到了没?大刘自己简单概括为 2W1H 三板斧:病号是谁、病号哪里出了问题、对症下药。 1、病号是谁?(WHO) 第一步:采用 top 命令,找出 CPU 占用最高的病号 PID ; 第二步:通过 ps -ef | grep PID 查看病号对应的真实身份。 2、病号哪里出了问题?(WHERE) 第一步:采用 jstack -l  PID >> PID.log  获取病号的各器官信息的病历单; 第二步:采用 top -Hp PID 拿到占用 CPU 最高的器官昵称 PID ; 第三步:采用 printf "%x\n" PID  根据器官昵称 PID 的拿到器官真实身份 TID ; 第四步:采用 grep TID -A20 pid.log 根据 TID 去病历单中匹配,确定是哪出了问题。 3、捉得病根、便可拿出医药箱,对症下药啦。(HOW) 作为程序猿,工作中难免会遇到不少类似这样的问题。面对问题,你如果像无头苍蝇一样乱撞,撞得头破血流依然不知道缘由,在背锅即将成为现实时,那就不妨试试大刘的 2W1H 三板斧的诊断方式,说不定会帮你快速定位、解决线上问题,毕竟快速的解决生产问题会把损失降到最低。 最后,想对大家说一句: 作为程序猿,一定要有程序猿的态度。避免背锅,拒绝甩锅,打破砂锅,从你我做起。 长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 互联网 嵌入式

  • 这篇MySQL索引和B+Tree讲的太通俗易懂!

    来源:https://blog.csdn.net/b_x_p/article/details/86434387 作者:he_321 正确的创建合适的索引,是提升数据库查询性能的基础。在正式讲解之前,对后面举例中使用的表结构先简单看一下: create table user(    id     bigint  not null comment 'id' primary key,    name   varchar(200) null comment 'name',    age    bigint       null comment 'age',    gender int          null comment 'gender',    key (name)); 索引是什么及工作机制? 索引是为了加速对表中数据行的检索而创建的一种分散存储的数据结构。其工作机制如下图: 上图中,如果现在有一条sql语句 select * from user where id = 40,如果没有索引的条件下,我们要找到这条记录,我们就需要在数据中进行全表扫描,匹配id = 13的数据。 如果有了索引,我们就可以通过索引进行快速查找,如上图中,可以先在索引中通过id = 40进行二分查找,再根据定位到的地址取出对应的行数据。 MySQL数据库为什么要使用B+TREE作为索引的数据结构? 二叉树为什么不可行 对数据的加速检索,首先想到的就是二叉树,二叉树的查找时间复杂度可以达到O(log2(n))。下面看一下二叉树的存储结构: 二叉树搜索相当于一个二分查找。二叉查找能大大提升查询的效率,但是它有一个问题:二叉树以第一个插入的数据作为根节点,如上图中,如果只看右侧,就会发现,就是一个线性链表结构。如果我们现在的数据只包含1, 2, 3, 4,就会出现 以下情况: 如果我们要查询的数据为4,则需要遍历所有的节点才能找到4,即,相当于全表扫描,就是由于存在这种问题,所以二叉查找树不适合用于作为索引的数据结构。 平衡二叉树为什么不可行 为了解决二叉树存在线性链表的问题,会想到用平衡二叉查找树来解决。下面看看平衡二叉树是怎样的: 平衡二叉查找树定义为:节点的子节点高度差不能超过1,如上图中的节点20,左节点高度为1,右节点高度0,差为1,所以上图没有违反定义,它就是一个平衡二叉树。保证二叉树平衡的方式为左旋,右旋等操作,至于如何左旋右旋,可以自行去搜索相关的知识。 如果上图中平衡二叉树保存的是id索引,现在要查找id = 8的数据,过程如下: 把根节点加载进内存,用8和10进行比较,发现8比10小,继续加载10的左子树。 把5加载进内存,用8和5比较,同理,加载5节点的右子树。 此时发现命中,则读取id为8的索引对应的数据。 索引保存数据的方式一般有两种: 数据区保存id 对应行数据的所有数据具体内容。 数据区保存的是真正保存数据的磁盘地址。 到这里,平衡二叉树解决了存在线性链表的问题,数据查询的效率好像也还可以,基本能达到O(log2(n)), 那为什么mysql不选择平衡二叉树作为索引存储结构,他又存在什么样的问题呢? 搜索效率不足。一般来说,在树结构中,数据所处的深度,决定了搜索时的IO次数(MySql中将每个节点大小设置为一页大小,一次IO读取一页 / 一个节点)。如上图中搜索id = 8的数据,需要进行3次IO。当数据量到达几百万的时候,树的高度就会很恐怖。 查询不不稳定。如果查询的数据落在根节点,只需要一次IO,如果是叶子节点或者是支节点,会需要多次IO才可以。 存储的数据内容太少。没有很好利用操作系统和磁盘数据交换特性,也没有利用好磁盘IO的预读能力。因为操作系统和磁盘之间一次数据交换是以页为单位的,一页大小为 4K,即每次IO操作系统会将4K数据加载进内存。但是,在二叉树每个节点的结构只保存一个关键字,一个数据区,两个子节点的引用,并不能够填满4K的内容。幸幸苦苦做了一次的IO操作,却只加载了一个关键字。在树的高度很高,恰好又搜索的关键字位于叶子节点或者支节点的时候,取一个关键字要做很多次的IO。 那有没有一种结构能够解决二叉树的这种问题呢?有,那就是多路平衡查找树。 多路平衡查找树(Balance Tree) B Tree 是一个绝对平衡树,所有的叶子节点在同一高度,如下图所示: 上图为一个2-3树(每个节点存储2个关键字,有3路),多路平衡查找树也就是多叉的意思,从上图中可以看出,每个节点保存的关键字的个数和路数关系为:关键字个数 = 路数 – 1。 假设要从上图中查找id = X的数据,B TREE 搜索过程如下: 取出根磁盘块,加载40和60两个关键字。 如果X等于40,则命中;如果X小于40走P1;如果40 < X < 60走P2;如果X = 60,则命中;如果X > 60走P3。 根据以上规则命中后,接下来加载对应的数据, 数据区中存储的是具体的数据或者是指向数据的指针。 为什么说这种结构能够解决平衡二叉树存在的问题呢? B Tree 能够很好的利用操作系统和磁盘的交互特性, MySQL为了很好的利用磁盘的预读能力,将页大小设置为16K,即将一个节点(磁盘块)的大小设置为16K,一次IO将一个节点(16K)内容加载进内存。这里,假设关键字类型为 int,即4字节,若每个关键字对应的数据区也为4字节,不考虑子节点引用的情况下,则上图中的每个节点大约能够存储(16 * 1000)/ 8 = 2000个关键字,共2001个路数。对于二叉树,三层高度,最多可以保存7个关键字,而对于这种有2001路的B树,三层高度能够搜索的关键字个数远远的大于二叉树。 这里顺便说一下:在B Tree保证树的平衡的过程中,每次关键字的变化,都会导致结构发生很大的变化,这个过程是特别浪费时间的,所以创建索引一定要创建合适的索引,而不是把所有的字段都创建索引,创建冗余索引只会在对数据进行新增,删除,修改时增加性能消耗。 B树确实已经很好的解决了问题,我先这里先继续看一下B+Tree结构,再来讨论BTree和B+Tree的区别。 先看看B+Tree是怎样的,B+Tree是B Tree的一个变种,在B+Tree中,B树的路数和关键字的个数的关系不再成立了,数据检索规则采用的是左闭合区间,路数和关键个数关系为1比1,具体如下图所示: 如果上图中是用ID做的索引,如果是搜索X = 1的数据,搜索规则如下: 取出根磁盘块,加载1,28,66三个关键字。 X  select * from user;+----+--------------+------+--------+| id | name         | age  | gender |+----+--------------+------+--------+| 20 | 君莫笑       |   15 |      1 || 40 | 苏沐橙       |   12 |      0 || 50 | 张楚岚       |   25 |      1 || 60 | 诸葛青       |   27 |      1 || 61 | 若有人兮     |   38 |      0 || 64 | 冯宝宝       |   18 |      0 |+----+--------------+------+--------+ 为什么说离散型越高,选择型越好? 因为离散度越高,通过索引最终确定的范围越小,最终扫面的行数也就越少。 最左匹配原则 对于索引中的关键字进行对比的时候,一定是从左往右以此对比,且不可跳过。之前讲解的id都为int型数据,如果id为字符串的时候,如下图: 当进行匹配的时候,会把字符串转换成ascll码,如abc变成97 98 99,然后从左往右一个字符一个字符进行对比。所以在sql查询中使用like %a 时候索引会失效,因为%表示全匹配,如果已经全匹配就不需要索引,还不如直接全表扫描。 最少空间原则 前面已经说过,当关键字占用的空间越小,则每个节点保存的关键字个数就越多,每次加载进内存的关键字个数就越多,检索效率就越高。创建索引的关键字要尽可能占用空间小。 联合索引 单列索引:节点中的关键字[name] 联合索引:节点中的关键字[name, age] 可以把单列索引看成特殊的联合索引,联合索引的比较也是根据最左匹配原则。 联合索引列的选择原则 经常用的列优先(最左匹配原则) 离散度高的列优先(离散度高原则) 宽度小的列优先(最少空间原则) 实例分析 下面简单举例平时经常会遇到的问题: 如,平时经常使用的查询sql如下: select * from users where name = ?select * from users where name = ? and age = ? 为了加快检索速度,为上面的查询sql创建索引如下: create index idx_name on users(name)create index idx_name_age on users(name, age) 在上面解决方案中,根据最左匹配原则,idx_name为冗余索引, where name = ?同样可以利用索引idx_name_age进行检索。冗余索引会增加维护B+TREE平衡时的性能消耗,并且占用磁盘空间。 覆盖索引 如果查询的列,通过索引项的信息可直接返回,则该索引称之为查询SQL的覆盖索引。覆盖索引可以提高查询的效率。 如上图,如果通过name进行数据检索: select * from users where name = ? 需要需要在name索引中找到name对应的Id,然后通过获取的Id在主键索引中查到对应的行。整个过程需要扫描两次索引,一次name,一次id。 如果我们查询只想查询id的值,就可以改写SQL为: select id from users where name = ? 因为只需要id的值,通过name查询的时候,扫描完name索引,我们就能够获得id的值了,所以就不需要再去扫面id索引,就会直接返回。 当然,如果你同时需要获取age的值: select id,age from users where name = ? 这样就无法使用到覆盖索引了。 知道了覆盖索引,就知道了为什么sql中要求尽量不要使用select *,要写明具体要查询的字段。其中一个原因就是在使用到覆盖索引的情况下,不需要进入到数据区,数据就能直接返回,提升了查询效率。在用不到覆盖索引的情况下,也尽可能的不要使用select *,如果行数据量特别多的情况下,可以减少数据的网络传输量。当然,这都视具体情况而定,通过select返回所有的字段,通用性会更强,一切有利必有弊。 总结 索引列的数据长度满足业务的情况下能少则少。 表中的索引并不是越多越好,冗余或者无用索引会占用磁盘空间并且会影响增删改的效率。 Where 条件中,like 9%, like %9%, like%9,三种方式都用不到索引。后两种方式对于索引是无效的。第一种9%是不确定的,决定于列的离散型,结论上讲可以用到,如果发现离散情况特别差的情况下,查询优化器觉得走索引查询性能更差,还不如全表扫描。 Where条件中IN可以使用索引, NOT IN 无法使用索引。 多用指定查询,只返回自己想要的列,少用select *。 查询条件中使用函数,索引将会失效,这和列的离散性有关,一旦使用到函数,函数具有不确定性。 联合索引中,如果不是按照索引最左列开始查找,无法使用索引。 对联合索引精确匹配最左前列并范围匹配另一列,可以使用到索引。 联合索引中,如果查询有某个列的范围查询,其右边所有的列都无法使用索引。 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下: 长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 数据库 嵌入式

  • SQL查询语句总是先执行SELECT?你们都错了

    来源 | infoq.cn/article/Oke8hgilga3PTZ3gWvbg 很多 SQL 查询都是以 SELECT 开始的。不过,最近我跟别人解释什么是窗口函数,我在网上搜索”是否可以对窗口函数返回的结果进行过滤“这个问题,得出的结论是”窗口函数必须在 WHERE 和 GROUP BY 之后,所以不能”。于是我又想到了另一个问题:SQL 查询的执行顺序是怎样的? 好像这个问题应该很好回答,毕竟自己已经写了上万个 SQL 查询了,有一些还很复杂。但事实是,我仍然很难确切地说出它的顺序是怎样的。 SQL 查询的执行顺序 于是我研究了一下,发现顺序大概是这样的。SELECT 并不是最先执行的,而是在第五个。 这张图回答了以下这些问题 这张图与 SQL 查询的语义有关,让你知道一个查询会返回什么,并回答了以下这些问题: 可以在 GRROUP BY 之后使用 WHERE 吗?(不行,WHERE 是在 GROUP BY 之后!) 可以对窗口函数返回的结果进行过滤吗?(不行,窗口函数是 SELECT 语句里,而 SELECT 是在 WHERE 和 GROUP BY 之后) 可以基于 GROUP BY 里的东西进行 ORDER BY 吗?(可以,ORDER BY 基本上是在最后执行的,所以可以基于任何东西进行 ORDER BY) LIMIT 是在什么时候执行?(在最后!) 但数据库引擎并不一定严格按照这个顺序执行 SQL 查询,因为为了更快地执行查询,它们会做出一些优化,这些问题会在以后的文章中解释。 所以: 如果你想要知道一个查询语句是否合法,或者想要知道一个查询语句会返回什么,可以参考这张图; 在涉及查询性能或者与索引有关的东西时,这张图就不适用了。 混合因素:列别名 有很多 SQL 实现允许你使用这样的语法: SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)FROM tableGROUP BY full_name 从这个语句来看,好像 GROUP BY 是在 SELECT 之后执行的,因为它引用了 SELECT 中的一个别名。但实际上不一定要这样,数据库引擎可以把查询重写成这样: SELECT CONCAT(first_name, ' ', last_name) AS full_name, count(*)FROM tableGROUP BY CONCAT(first_name, ' ', last_name) 这样 GROUP BY 仍然先执行。数据库引擎还会做一系列检查,确保 SELECT 和 GROUP BY 中的东西是有效的,所以会在生成执行计划之前对查询做一次整体检查。 数据库可能不按照这个顺序执行查询(优化) 在实际当中,数据库不一定会按照 JOIN、WHERE、GROUP BY 的顺序来执行查询,因为它们会进行一系列优化,把执行顺序打乱,从而让查询执行得更快,只要不改变查询结果。 这个查询说明了为什么需要以不同的顺序执行查询: SELECT * FROMowners LEFT JOIN cats ON owners.id = cats.ownerWHERE cats.name = 'mr darcy' 如果只需要找出名字叫“mr darcy”的猫,那就没必要对两张表的所有数据执行左连接,在连接之前先进行过滤,这样查询会快得多,而且对于这个查询来说,先执行过滤并不会改变查询结果。 数据库引擎还会做出其他很多优化,按照不同的顺序执行查询,不过我并不是这方面的专家,所以这里就不多说了。 LINQ 的查询以 FROM 开头 LINQ(C#和 VB.NET 中的查询语法)是按照 FROM…WHERE…SELECT 的顺序来的。这里有一个 LINQ 查询例子: var teenAgerStudent = from s in studentList                      where s.Age > 12 && s.Age  1000] # WHEREdf = df.groupby('something', num_yes = ('yes', 'sum')) # GROUP BYdf = df[df.num_yes > 2]       # HAVING, 对 GROUP BY 结果进行过滤df = df[['num_yes', 'something1', 'something']] # SELECT, 选择要显示的列df.sort_values('sometthing', ascending=True)[:30] # ORDER BY 和 LIMITdf[:30] 这样写并不是因为 pandas 规定了这些规则,而是按照 JOIN/WHERE/GROUP BY/HAVING 这样的顺序来写代码会更有意义些。不过我经常会先写 WHERE 来改进性能,而且我想大多数数据库引擎也会这么做。 R 语言里的 dplyr 也允许开发人员使用不同的语法编写 SQL 查询语句,用来查询 Postgre、MySQL 和 SQLite。 原文链接:SQL queries don’t start with SELECThttps://jvns.ca/blog/2019/10/03/sql-queries-don-t-start-with-select/ 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下: 长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: C语言 嵌入式

  • TCP三次握手、四手挥手,这样说你能明白吧!

    TCP协议全称为:Transmission Control Protocol,是一种面向链接、保证数据传输安全、可靠的数据传输协议。为了确保数据的可靠传输,不仅需要对发出的每个字节进行编号确认,还需要验证每一个数据包的有效性。每个TCP数据包是封闭在IP包中的,每个一IP包的后面紧跟着的是TCP头,TCP报文格式如下: 源端口和目的端口字段 TCP源端口(Source Port):源计算机上的应用程序的端口号,占 16 位。 TCP目的端口(Destination Port):目标计算机的应用程序端口号,占 16 位。 序列号字段 CP序列号(Sequence Number):占 32 位。它表示本报文段所发送数据的第一个字节的编号。在 TCP 连接中,所传送的字节流的每一个字节都会按顺序编号。当SYN标记不为1时,这是当前数据分段第一个字母的序列号;如果SYN的值是1时,这个字段的值就是初始序列值(ISN),用于对序列号进行同步。这时,第一个字节的序列号比这个字段的值大1,也就是ISN加1。 确认号字段 TCP 确认号(Acknowledgment Number,ACK Number):占 32 位。它表示接收方期望收到发送方下一个报文段的第一个字节数据的编号。其值是接收计算机即将接收到的下一个序列号,也就是下一个接收到的字节的序列号加1。 数据偏移字段 TCP 首部长度(Header Length):数据偏移是指数据段中的“数据”部分起始处距离 TCP 数据段起始处的字节偏移量,占 4 位。其实这里的“数据偏移”也是在确定 TCP 数据段头部分的长度,告诉接收端的应用程序,数据从何处开始。 保留字段 保留(Reserved):占 4 位。为 TCP 将来的发展预留空间,目前必须全部为 0。 标志位字段 CWR(Congestion Window Reduce):拥塞窗口减少标志,用来表明它接收到了设置 ECE 标志的 TCP 包。并且,发送方收到消息之后,通过减小发送窗口的大小来降低发送速率。 ECE(ECN Echo):用来在 TCP 三次握手时表明一个 TCP 端是具备 ECN 功能的。在数据传输过程中,它也用来表明接收到的 TCP 包的 IP 头部的 ECN 被设置为 11,即网络线路拥堵。 URG(Urgent):表示本报文段中发送的数据是否包含紧急数据。URG=1 时表示有紧急数据。当 URG=1 时,后面的紧急指针字段才有效。 ACK:表示前面的确认号字段是否有效。ACK=1 时表示有效。只有当 ACK=1 时,前面的确认号字段才有效。TCP 规定,连接建立后,ACK 必须为 1。 PSH(Push):告诉对方收到该报文段后是否立即把数据推送给上层。如果值为 1,表示应当立即把数据提交给上层,而不是缓存起来。 RST:表示是否重置连接。如果 RST=1,说明 TCP 连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。 SYN:在建立连接时使用,用来同步序号。当 SYN=1,ACK=0 时,表示这是一个请求建立连接的报文段;当 SYN=1,ACK=1 时,表示对方同意建立连接。SYN=1 时,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中 SYN 才为 1。 FIN:标记数据是否发送完毕。如果 FIN=1,表示数据已经发送完成,可以释放连接。 窗口大小字段 窗口大小(Window Size):占 16 位。它表示从 Ack Number 开始还可以接收多少字节的数据量,也表示当前接收端的接收窗口还有多少剩余空间。该字段可以用于 TCP 的流量控制。 TCP 校验和字段 校验位(TCP Checksum):占 16 位。它用于确认传输的数据是否有损坏。发送端基于数据内容校验生成一个数值,接收端根据接收的数据校验生成一个值。两个值必须相同,才能证明数据是有效的。如果两个值不同,则丢掉这个数据包。Checksum 是根据伪头 + TCP 头 + TCP 数据三部分进行计算的。 紧急指针字段 紧急指针(Urgent Pointer):仅当前面的 URG 控制位为 1 时才有意义。它指出本数据段中为紧急数据的字节数,占 16 位。当所有紧急数据处理完后,TCP 就会告诉应用程序恢复到正常操作。即使当前窗口大小为 0,也是可以发送紧急数据的,因为紧急数据无须缓存。 可选项字段 选项(Option):长度不定,但长度必须是 32bits 的整数倍。 TCP建立连接 TCP建立连接需要三个步骤,也就是大家熟知的三次握手。下图了正常情形下通过三次握手建立连接的过程: A机器发出一个数据包SYN设置为1,表示希望建立连接。这个包中的假设seq为x。 机器A发送`SYN`数据包后,会进入`SYN_SENT`状态 B机器收到A发送的SYN数据后,响应一个数据包将SYN和ACK设置为1,假设这个响应包的序列号为y,同时期望下一次收到的数据库的序列为x+1 B回复响应包后,进入`SYN_RECD`状态 A收到B的响应包后,对响应包做应答将ACK标志设置为1,序列号为x + 1,期望下一次收到的数据包的序列号为y+1 A机器和B机器连接建立成功 TCP三次握手抓包验证 以为验证三次握手是否描述正确,在下使用Wireshark进行抓包验证。首先使用ping命令获取www.baidu.com的ip地址: 正在 Ping www.a.shifen.com [183.232.231.174] 具有 32 字节的数据:来自 183.232.231.174 的回复: 字节=32 时间=16ms TTL=54来自 183.232.231.174 的回复: 字节=32 时间=16ms TTL=54来自 183.232.231.174 的回复: 字节=32 时间=16ms TTL=54183.232.231.172 的 Ping 统计信息:    数据包: 已发送 = 3,已接收 = 3,丢失 = 0 (0% 丢失),往返行程的估计时间(以毫秒为单位):    最短 = 16ms,最长 = 16ms,平均 = 16ms 以上输出显示www.baidu.com的ip地址:183.232.231.174,然后使用Wireshark的过滤器仅显示与www.baidu.com通信的tcp数据包: ip.src_host == "183.232.231.174" or ip.dst_host == "183.232.231.174" and tcp 使用Wireshark抓包分析后,验证TCP正常连接三次握手与上节描述的一致。 为什么是三次握手? 为什么是三次握手?三次握手主要有两个目的:信息对等和防止超时。 信息对等 两台机器通信时都需要确认四个信息: 自己发报文的能力 自己收报文的能力 对方发报文的能力 对方收报文的通知 第一次握手 第一次握手A机器向B机器发送SYN数据包,此时只有B机器能确认自己收报文的能力和对方发报文的能力。 一次握手完成B机器能够确认的信息有: √B机器收报文的能力 √A机器发报文的能力 第二次握手 每二次握手后B响应A机器的SYN数据包,此时A机器就能确认:自己发报文的能力、自己收报文的能力、对方发报文的能力、对方收报文的能力 二次握手完成A机器能够确认的信息有: √A机器发报文的能力 √A机器收报文的能力 √B机器发报文的能力 √B机器收报文的能力 第三次握手 每三次握手后A应答B机器的SYN + ACK数据包,此时B机器就能确认:自己发报文的能力、对方收报文的能力 三次握手完成B机器能够确认的信息有: √B机器发报文的能力 √A机器收报文的能力 至此经过三次握手A、B机器就能做到信息对等,双方都能确认自己和对方的收、发报文的能力,最后方便理解将信息对等制作成一个小表格: 防止超时 三次握手除了保证信息对等也是了防止请求超时导致脏连接。TTL网络报文的生存往往会超过TCP请求超时时间,如果两次握手就能创建连接,传输数据并释放连接后,第一个超时的连接请求才到达B机器,B机器 会以为是 A 创建新连接的请求,然后确认同意创建连接。因为A机器的状态不是SYN_SENT,所以会直接丢弃了B的确认数据,导致 B 机器单方面的创建连接完毕。 如果是三次握手,则 B 机器收到连接请求后,同样会向 A 机器确同意创建连接,但因为 A 不是SYN_SENT状态,所以 A机器 不会回复 B 机器确认创建连接请求,而 B 机器到一段时间后由于长时间没有收到确认信息,最终会导致连接创建失败,因此不会出现脏连接。 TCP断开连接 TCP是全双工通信,双方都能作为数据的发送方和接收方,但TCP会有断开的时候。TCP建立连接需要三次握手而断开连接却要四次,如图所示为TCP断开连接四次挥手过程: A 机器发送关闭数据包将FIN设置为1,假设序列号为u,发完关闭数据包后此时 A 机器处理FIN_WAIT_1状态 B 收到关闭连接请求后,通知应用程序处理完剩下的数据 B 响应 A 的关闭连接请求,将ACK标志设置为1,seq为v,ack为u+1,随后 B 机器处于 CLOSE_WAIT状态 A 收到应答后,处于FIN_WAIT_2状态,继续等待 B 机器的FIN数据包 B 处理好现场后,主动向 A 机器发送数据包,并将FIN和ACK标志设置为1,seq为w,ack为u+1,随后处于LAST_WAIT状态等待 A 机器的应答 A 机器收到FIN数据包后,随后发送ACK数据包,seq为u+1,ack为w+1, 此时 A 机器处理TIME_WAIT状态 B 机器收到ACK响应包后,进行CLOSED状态,连接正常关闭 A 机器在TIME_WAIT状态等待2MSL后,也进入CLOSEED状态,连接关闭 什么是2MSL:MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”, 2MSL即两倍的MSL 四次挥手断开连接可以用更形象的方式来表达: 男生 :我们分手吧。 女生 :好的,我需要去家里把东西收拾完,再发消息给你。(此时男生不能再拥抱女生) 。。。,一个小时后 女生 :我收拾完了,分手吧(此时女生也不能再拥抱男生) 男生:好的(此时双方约定一段时间后,才可以分别找新的对象) TCP四次挥手抓包验证 抓包过程与与三次握手抓包过程一致,这里不描述。直接看访问后抓包的截图: 第一个包是由192.168.1.6这台机器(也就是客户机),发送了一个FIN包,seq为80,ack为2782 第二个包由183.232.231.174(服务器),对192.168.1.6这台机器(也就是客户机)发送了一个ACK包,seq为2782,ack为81 第三个包由183.232.231.174(服务器),对192.168.1.6这台机器(也就是客户机)发送了一个ACK和FIN包,seq为2782,ack为81 第四个包由192.168.1.6,向服务器响应了一个ACK包,seq为81,ack为2783 四次挥手流程与我们描述的一致。 TIME_WAIT 状态  主动要求关闭的机器(机器A)表示收到对方的FIN报文后,并发送出ACK报文后,进行TIME_WAIT状态,等待2MSL后进行CLOSED状态。如果在TIME_WAIT_1 时收到FIN标志和ACK标志报文时,可以直接进入TIME_WAIT状态,而无需进入TIME_WAIT_2状态。 为什么要有 TIME_WAIT 确认被动关闭(机器B)能够顺利进入CLOSED状态 假如A机器发送最后一个ACK后,但由于网络原因ACK包未能到达 B 机器,此时 B机器通常会认为 A机器 没有收到 FIN+ACK报文,会重发一次FIN+ACK报文。如果 A机器 发送最后一个ACK后,自私的关闭连接进入 CLOSED状态,就可能导致 B 无法收到ACK报文,无法正常关闭。 防止失效请求 TIME_WAIT 状态可以防止已失效的请求包与正常连接的请求数据包混淆而发生异常。因为TIME_WAIT 状态无法真正释放句柄资源,在此期间, Socket中使用的本地端口在默认情况下不能再被使用。 CLOSE_WAIT 状态 被动关闭的机器(机器B)在收到对方发送的,FIN报文后,马上回复ACK报文,进入CLOSE_WAIT状态。通知应用程序,处理剩下的数据,释放资源。 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下: 长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 数据传输协议 嵌入式

  • 几款优秀的支持C、C++等多种语言的在线编译器

    关注+星标公众号,不错过精彩内容 作者 | strongerHuang 微信公众号 | strongerHuang 今天10.24程序员节,是一个特殊的日子,2020 - 1024 = 996,你没看错,2020年的1024更加特别(不要问我为什么特别)。 作为程序员,使用编译器是必备技能,但是从入门到放弃,基本上就是在开发环境安装、配置这一步。。。 大家可能体会过,使用编译器不是一件简单的事,下载、安装、各种配置······但最终不能使用,然后就放弃了。 今天就来分享几个支持C、 C++、 C#、 JAVA······等多种编程语言的在线编译器。 它们和本地编译器的区别在于:在线编译器非常的轻量级,不用安装、不用各种复杂配置,基本上直接就能使用。 在线编译器① 地址: https://rextester.com/ (公号不支持外链接,请复制链接到浏览器打开) 这款在线编译器相对还是比较专业,它可以显示编译时间、运行时间、内存占用等,同时它支持多种编程语言、编译器都能选择,比如:C语言可选择gcc、 clang、 vc等。 在线编译器② 地址: https://www.tutorialspoint.com/codingground.htm (公号不支持外链接,请复制链接到浏览器打开) 这是一款比较全面的在线工具,支持前端技术、文档编辑、在线编译等丰富的在线工具。 比如我们选在其中C语言(GCC)在线编译器: 如果代码有错误,在线编译,也会提示: 总的来说,这款在线编译器的功能挺多,也比较强大。 在线编译器③ 地址: https://www.codechef.com/ide (公号不支持外链接,请复制链接到浏览器打开) 这款工具同样支持多语言、多编译器,从网址可以看得出来,是一款轻量级的IDE。 使用也是很方便,直接编辑代码,或者打开源代码文件,就可以点击“RUN”直接编译运行了: 当然,如果代码有错误,也会很直观的提醒在哪一行的错误: 更多在线编译器 在线编译器比较多,我这里就不一一举例说明了,下面直接罗列一些在线编译器地址,感兴趣的可以试试。 地址: https://tech.io/snippet http://rextester.com https://codesandbox.io https://jsfiddle.net https://www.ideone.com https://www.onlinegdb.com (公号不支持外链接,请复制链接到浏览器打开) 最后,这些在线编译器对于一些初学者(不懂各种配置),或者想测试本地没有的编译环境都是非常有用的,大家有时间可以多尝试一下。 ------------ END ------------ 推荐阅读: 什么是Cortex-M内核的MPU 程序猿如何选择开源协议? 线程、进程、多线程、多进程 和 多任务  关注 微信公众号『strongerHuang』,后台回复“1024”查看更多内容,回复“加群”按规则加入技术交流群。 长按前往图中包含的公众号关注 点击“ 阅读原文 ”查看更多分享,欢迎点分享、收藏、点赞、在看。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: C语言 嵌入式

  • 全面解读STM32生态环境,介绍、下载安装、使用教程等

    关注+星标公众号,不错过精彩内容 作者:strongerHuang 微信公众号:strongerHuang STM32能被这么多开发者认可,它的强大生态系统起了关键作用。本文围绕STM32生态各软件工具进行相关描述,其中包含一些技术细节。 一、STM32生态系统总预览 STM32提供了全套开发工具,以及开发所需的软件包,下面看一张图,从全局来了解STM32生态系统工具和软件: 主要分为两大类: 1.软件工具: 即左边部分的STM32CubeMX、 CubeIDE、 CubeProgrammer、 CubeMontor等。 2.嵌入式软件包: STM32CubeMCU Packages 和 扩展软件包CubeExpansions. STM32Cube生态系统几个特点: 免费自由使用; 接口一致,方便移植 可裁剪、可扩展 易使用和理解 节约开发时间和成本 ······ (当然,这是我自己总结的特点,其实还有很多,欢迎补充) 下面进一步详细描述各工具和软件包的内容。 二、STM32CubeMX STM32CubeMX早在2014年就推出了,但早期的STM32CubeMX和HAL相对现在Bug更多,使用也不方便。再加上用户习惯了标准外设库,所以早期推出STM32CubeMX时,用户并不买账。 因此,为了推广STM32CubeMX,以及让更多用户使用STM32CubeMX,ST在14、15年逐渐停止了对各MCU标准外设库的更新和维护,让用户转向了HAL和LL库。 STM32CubeMX是一个图形化的软件配置工具,使用图形向导可以生成STM32初始化代码工程。 STM32CubeMX特性: 1.直观的选择 STM32 微控制器(MCU)和微处理器(MPU)。 2.丰富易用的图形化界面: -自动解决配置冲突 -具有参数约束动态验证的外设和中间件功能模式 -时钟树与配置的动态验证 -功耗预测 3.生成初始化代码工程,包含:EWARM、 MDK-ARM、 TureSTUDIO、 SW4STM32等。 4.针对部分MPU生成Linux设备树。 5.可独立运行在Windows、Linux和macOS上使用,或作为 Eclipse 插件使用。 三、STM32CubeIDE STM32CubeIDE推出的时间是在19年4月左右,但它的发展历程却有很久了。 STM32CubeIDE的发展要从早期 Atollic 的 TrueSTUDIO 说起,早期的 TrueSTUDIO 是 Atollic公司针对嵌入式开发的一套IDE工具(类似Keil、 IAR)。 在2017年12月的时候,ST收购了Atollic公司,然后TrueSTUDIO for STM32就这样诞生了。 直到2019年4月份,ST正式推出了STM32CubeIDE,简单说就是TrueSTUDIO for STM32的“升级版本”,只是这个“升级版本”变化有点大。 下面通过一张图来了解STM32CubeIDE的发展历史: STM32CubeIDE是一个多功能的集成开发工具(IDE),集成了TrueSTUDIO和STM32CubeMX,它是STM32Cube软件生态系统的一部分。 它是一个先进的C/C++开发平台,具有STM32微控制器的IP配置、代码生成、代码编译和调试功能等。 四、STM32CubeProgmmer STM32CubeProgmmer之前也有一段很长的历史,早期的ST芯片下载工具是 STVP(ST Visual Programmer) ,可能2010年之前的工程师比较熟悉这款工具,但后面的逐渐被ST-LINK Utility取代了。 ST-LINK Utility应该现在很多工程师都还在用,但官方也是用STM32CubeProgmmer替代了ST-LINK Utility,同时也停止了对ST-LINK Utility的更新。 STM32CubeProgrammer有很多地方和ST-LINK Utility相似,但新引入了一些功能,比如安全编程(secure programming)。 五、STM32CubeMonitor STM32CubeMonitor是ST今年(2020)推出的一款新的神器,是一款通过实时读取和显示变量来帮助调试和诊断STM32应用程序的工具。 STM32CubeMonitor也有一个发展历程,它的前生其实是STM Studio。它和STM Studio主要的功能就是监控,比如监控变量: 主要特征: •基于图形流的编辑器,不需要编程来构建仪表板 •通过ST-LINK (SWD、JTAG协议)连接到任何STM32设备 •在目标应用程序运行时,在RAM中实时读取和写入变量解析来自应用程序可执行文件的调试信息 •直接获取模式或快照模式 •关注感兴趣的应用行为 •允许将数据记录到文件中并重播以进行详尽的分析 •通过可配置的显示窗口(如曲线和方框)和大量的小部件(如仪表、条形图和图表)提供定制的可视化多探头支持同时监控多个目标 •远程监控,本机支持多格式显示(PC、平板电脑、手机) •直接支持Node-RED开放社区 •支持操作系统:Windows,Linux Ubuntu 和 macOS (以上来自有道词典翻译,可能存在错误) CubeMonitor扩展: 借助Node-RED开放社区,STM32CubeMonitor可提供丰富的扩展功能。 1.STM32CubeMonitor-RF STM32CubeMonRF是STM32CubeMonitor-RF的缩写,它是一款用于监测无线设备的工具,目前主要用于监测STM32WB与蓝牙(BLE)和802.15.4设备的发送/接收性能。 2.STM32CubeMonitor-Pwwer STM32CubeMonPwr是STM32CubeMonitor-Power,是一款使开发人员能够快速分析目标板低功耗性能的工具。 3.STM32CubeMonitor-UCPD STM32CubeMonUCPD是STM32CubeMonitor-UCPD,是一款用于监视和配置USB Type-C和Power Delivery应用程序的工具。 六、STM32CubeMCU Packages STM32Cube生态的嵌入式软件包种类繁多,但分类比较明确,下面通过一张图来看下嵌入式软件包应用结构图: MCU Packages即MCU软件包,也是大家最常用、最熟悉的软件包: 在STM32CubeMX软件包管理器中可以进行查看和(删除、增加)管理: MCU软件包还可以分两类: 底层驱动HAL和LL 中间层Middlewares 其中中间层有ST自家的、也有第三方的,种类比较多,比如:FreeRTOS、 FatFS、 LwIP、  Open Bootloader、 Bluetooth 5 stack、 Zigbee 3 stack、 USB Host & Device stacks等。 通过一张图全面了解MCU软件包: 各包支持的情况: 七、STM32Expansion STM32扩展包,也是STM32Cube生态系统其中的组成部分,STM32的扩展包有ST自家的,也支持第三方的扩展包。 比如: X-CUBE-AI 、 X-CUBE-BLE1、 X-CUBE-NFC4······等很多很多。 下面进行分类一下: 同样,可以在STM32CubeMX软件包管理器中可以进行查看和(删除、增加)管理: 八、总结 本文总结了STM32Cube生态目前(2020.10)所有的软件工具内容,以及描述相关细节内容。 STM32Cube生态的软件工具细节内容其实比较多,并不是所有都需要掌握,初学者可以从STM32CubeMX和MCU软件包(HAL)开始学起,然后一步一步拓展。 最后,大家可以后台回复“STM32Cube生态系统”查看更多相关内容。 推荐阅读: 【专栏】STM32CubeMX系列教程 STM32CubeMonitor介绍、下载、安装和使用教程 STM32CubeIDE下载安装,配置生成代码,在线调试 关注 微信公众号『strongerHuang』,后台回复“1024”查看更多内容,回复“加群”按规则加入技术交流群。 长按前往图中包含的公众号关注 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 软件 嵌入式

  • 模拟看门狗如何实现?

    关注、星标公众号,不错过精彩内容 作者:逸珺 转自:STM32 对于看门狗大家或许不陌生,但对于模拟看门狗有的朋友可能就不甚了解了。本文来聊聊模拟看门狗,旨在梳理相应的概念,理解模拟看门狗原理、与常规看门狗的异同点以及工程应用价值。 啥是看门狗? 一般来讲,单片机的看门狗可简单看成相对独立的两部分,即计时单元和监控单元。计时单元实现计数与重装。在计数过程中,软件可以适时对计数器的初始值进行重装,以防溢出。监控单元监视计时器的溢出事件,若计数器因未被软件适时重装而发生溢出,看门狗通常会执行复位动作,比如复位处理器。  以STM32F4系列单片机独立看门狗IWDG( Independent watchdog)为例,看看其计时电路的功能架构: 我们再结合STM32的复位逻辑模块,来大致看看IWDG的复位控制过程。 当IWDG看门狗模块监测到计数溢出时,IWDG reset信号输出低,经过与逻辑电路触发脉冲发生器,产生一个正向窄脉冲以控制MOSFET在该脉冲宽度期间导通,并经由滤波电路产生系统复位事件信号,进而触发STM32复位。 上面大致介绍了常见的独立看门狗的功能及工作流程。那么STM32芯片的模拟看门狗是又怎么回事呢?跟上面提到的独立看门狗有关系吗?它是怎么工作的? 请继续阅读..... 模拟看门狗 模拟看门狗跟上面提到的监控微处理器是否正常运行的独立看门狗没有关系,它是基于ADC外设应用的一个功能模块。它以类似于独立看门狗的方式进行工作, 只不过它监视的是模拟通道输入信号幅度,当监测到输入异常时会触发模拟看门狗事件。软件上基于模拟狗事件再进行相应的处理。 当我们在对模拟看门狗做初始化配置时,需事先设定看门狗要监视的模拟输入通道的输入阈值,即上下门限值【被监测模拟信号幅度对应的ADC值】。当模拟看门狗监测到输入信号电压超过阈值时,它将产生看门狗事件,并可以触发中断以运行相应处理代码。 如果说没有这个模拟看门狗,而我们又需要对模拟输入信号幅度做实时监测,往往会有两方面的方案。其一,我们通过软件方式,对ADC采样值进行代码轮询。显然,这会大大增加CPU的负荷,而且实时性也难以保证。其二,我们可以考虑额外设计硬件监控电路。比方,将模拟输入外接两个比较器,将模拟信号与外部产生的上下边界比较阈值进行比较(如下图),从而产生中断信号给单片机也能实现类似的实时功能需求。 那么,此时需要更多的外围器件并占用单片机额外的中断请求引脚。 不难看出,对于模拟输入信号幅度的监测,借助于STM32片内模拟看门狗可轻松实现硬件监测,既无须外扩硬件电路也无须软件轮询信号的合法性或安全性。同时,又大大减轻了CPU的负荷,并保证了对输入异常的实时响应。 模拟看门狗在哪里? 既然模拟看门狗是ADC模块的一部分功能单元,我们不妨以看看STM32F4系列的ADC框图。对于如何使用一个外设模块,首先浏览其功能框图无疑是个不错举措。我们可以从ADC模块的功能框图中找到模拟看门狗功能单元之所在。 有啥工程应用价值? 到此,我们知道模看门狗可监控模拟信号是否超界,据此我们可以想到这样一些应用场合: 供电电源超界检测。在产品开发中,常常需要实现较为智能的电源诊断。在一个产品的关键功能链中,电源是否异常往往需要格外关注。通常需要相应的诊断检测电路,以检测设备电源电压是否超出限定值,如超出限定值需做出诊断报警。这在工业产品、汽车电子、医疗器械中都有明确强制要求。 实现控制环路fail-safe模式。比如一个控制系统根据采集到的传感器信号来控制阀门的开度,以实现流量控制。或者一个速度控制系统,如果传感器信号异常,需要马上将设备置于功能安全状态,以避免造成经济损失或者带来人身伤害。还比如一个电机转速控制系统,如果电流传感器值超限,需要马上让电机停机等。 其实,需要做模拟监测的场合还很多,这里不多做列举。 代码怎么写呢? 下面给出大致参考例程,主要演示实现的基本思路和框架,细节根据项目相应需求完善。 顺便提醒,尽量基于 STM32CubeMx 来着手我们的 STM32 软件开发。 static void ADC_Config(void){ ADC_ChannelConfTypeDef sConfig; ADC_AnalogWDGConfTypeDef AnalogWDGConfig;   /* 初始化 */  AdcHandle.Instance = ADCx;   AdcHandle.Init.ClockPrescaler        = ADC_CLOCK_ASYNC_DIV1;          /* 异步时钟模式,时钟不分频 */  AdcHandle.Init.Resolution = ADC_RESOLUTION_12B; /* 12位 */ AdcHandle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 右对齐模式 */ AdcHandle.Init.ScanConvMode = DISABLE; /* Sequencer disabled (ADC conversion on only 1 channel: channel set on rank 1) */ AdcHandle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* EOC标志指示转换结束 */ AdcHandle.Init.LowPowerAutoWait = DISABLE; /* 自动延迟转换功能禁用 */ AdcHandle.Init.ContinuousConvMode = DISABLE; /* 禁用连续模式,单次模式 */ AdcHandle.Init.NbrOfConversion = 1; AdcHandle.Init.DiscontinuousConvMode = DISABLE; AdcHandle.Init.NbrOfDiscConversion = 1; AdcHandle.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T3_TRGO; /* Timer 3 触发 */ AdcHandle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; /* 软件触发 */ AdcHandle.Init.DMAContinuousRequests = ENABLE; /* DMA 循环模式使能 */ AdcHandle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 如溢出DR寄存器将被最后的转换结果覆盖 */ AdcHandle.Init.OversamplingMode = DISABLE; /* 禁用过采样 */ if (HAL_ADC_Init(&AdcHandle) != HAL_OK) { /* 初始化错误处理 */ Error_Handler();   }  /* 如ADC转换超出所设模拟看门狗窗口,则考虑到IT在每次ADC转换后发生,*/ /* 请选择足够长的采样时间和ADC时钟,以免在IRQHandler中产生开销。*/ sConfig.Channel = ADC_CHANNEL_5; /* 通道选择 */ sConfig.Rank = ADC_REGULAR_RANK_1; /* Rank 选择 */ sConfig.SamplingTime = ADC_SAMPLETIME_6CYCLES_5; /* 采样时间 */ sConfig.SingleDiff = ADC_SINGLE_ENDED; /* 单端输入模式 */ sConfig.OffsetNumber = ADC_OFFSET_NONE; /* 无偏移 */ sConfig.Offset = 0; /* 偏移禁用,该值无用 */ if (HAL_ADC_ConfigChannel(&AdcHandle, &sConfig) != HAL_OK) { /* 通道配置错误处理 */ Error_Handler(); }   /* 设置模拟看门狗阈值 */    /* Analog watchdog 1 模拟看门狗配置 */  AnalogWDGConfig.WatchdogNumber = ADC_ANALOGWATCHDOG_1; AnalogWDGConfig.WatchdogMode = ADC_ANALOGWATCHDOG_ALL_REG; AnalogWDGConfig.Channel = ADCx_CHANNELa; AnalogWDGConfig.ITMode = ENABLE; AnalogWDGConfig.HighThreshold = (RANGE_12BITS * 5/8); AnalogWDGConfig.LowThreshold = (RANGE_12BITS * 1/8); if (HAL_ADC_AnalogWDGConfig(&AdcHandle, &AnalogWDGConfig) != HAL_OK) { /* 配置错误处理 */ Error_Handler(); } } void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef* hadc) { /* 看门狗错误处理,这里可以实现fail-safe需求*/ /* 1.比如关闭关键电路 */ /* 2.设置报警标志 */ /* 3.代码尽可能运行短 */ ..... ubAnalogWatchdogStatus = SET; } #define ADCCONVERTEDVALUES_BUFFER_SIZE 256 /* 数组aADCxConvertedValues[]大小 */ /* ADC转换结果 */ static __IO uint16_t aADCxConvertedValues[ADCCONVERTEDVALUES_BUFFER_SIZE]; static void start_adc(void){ /* 启动ADC以DMA模式运行 */ if (HAL_ADC_Start_DMA(&AdcHandle, (uint32_t *)aADCxConvertedValues, ADCCONVERTEDVALUES_BUFFER_SIZE ) != HAL_OK) { /* 启动错误处理 */ Error_Handler(); } }  总结 这里针对STM32芯片模拟看门狗的概念及原理做了较为清晰的介绍,它也算是STM32一个具有特色的功能模块。对于产品开发中需要进行模拟输入幅值监控的场合,使用它会非常便利。 推荐阅读: 什么是Cortex-M内核的MPU 线程、进程、多线程、多进程 和 多任务   ELF相比Hex、Bin文件格式有哪些与众不同? 关注 微信公众号『strongerHuang』,后台回复“1024”查看更多内容,回复“加群”按规则加入技术交流群。 长按前往图中包含的公众号关注 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 模拟看门狗 嵌入式

  • 程序猿如何选择开源协议?

    关注、星标公众号,不错过精彩内容 素材来源:C语言中文网 编排:strongerHuang 有不少人认为开源就是免费,其实这个观点是错误的,今天就来谈谈关于开源的内容。 一、关于开源 开源软件在追求“自由”的同时,不能牺牲程序员的利益,否则将会影响程序员的创造激情,因此世界上现在有 60 多种被开源促进组织(Open Source Initiative)认可的开源许可协议来保证开源工作者的权益。 开源协议规定了你在使用开源软件时的权利和责任,也就是规定了你可以做什么,不可以做什么。 开源协议虽然不一定具备法律效力,但是当涉及软件版权纠纷时,开源协议也是非常重要的证据之一。 对于准备编写一款开源软件的开发人员,也非常建议先了解一下当前最热门的开源许可协议,选择一个合适的开源许可协议来最大限度保护自己的软件权益。 二、常见开源协议 1.GNU GPL(GNU General Public License,GNU通用公共许可证) 只要软件中包含了遵循 GPL 协议的产品或代码,该软件就必须也遵循 GPL 许可协议,也就是必须开源免费,不能闭源收费,因此这个协议并不适合商用软件。 遵循 GPL 协议的开源软件数量极其庞大,包括 Linux 系统在内的大多数的开源软件都是基于这个协议的。 GPL 开源协议的主要特点: 复制自由:允许把软件复制到任何人的电脑中,并且不限制复制的数量。 传播自由:允许软件以各种形式进行传播。 收费传播:允许在各种媒介上出售该软件,但必须提前让买家知道这个软件是可以免费获得的;因此,一般来讲,开源软件都是通过为用户提供有偿服务的形式来盈利的。 修改自由:允许开发人员增加或删除软件的功能,但软件修改后必须依然基于GPL许可协议授权。 2.BSD(Berkeley Software Distribution,伯克利软件发布版)协议 BSD 协议基本上允许用户“为所欲为”,用户可以使用、修改和重新发布遵循该许可的软件,并且可以将软件作为商业软件发布和销售,前提是需要满足下面三个条件: 如果再发布的软件中包含源代码,则源代码必须继续遵循 BSD 许可协议。 如果再发布的软件中只有二进制程序,则需要在相关文档或版权文件中声明原始代码遵循了 BSD 协议。 不允许用原始软件的名字、作者名字或机构名称进行市场推广。 BSD 对商业比较友好,很多公司在选用开源产品的时候都首选 BSD 协议,因为可以完全控制这些第三方的代码,甚至在必要的时候可以修改或者二次开发。 3. Apache 许可证版本(Apache License Version)协议 Apache 和 BSD 类似,都适用于商业软件。Apache 协议在为开发人员提供版权及专利许可的同时,允许用户拥有修改代码及再发布的自由。 现在热门的 Hadoop、Apache HTTP Server、MongoDB 等项目都是基于该许可协议研发的,程序开发人员在开发遵循该协议的软件时,要严格遵守下面的四个条件: 该软件及其衍生品必须继续使用 Apache 许可协议。 如果修改了程序源代码,需要在文档中进行声明。 若软件是基于他人的源代码编写而成的,则需要保留原始代码的协议、商标、专利声明及其他原作者声明的内容信息。 如果再发布的软件中有声明文件,则需在此文件中标注 Apache 许可协议及其他许可协议。 4.MIT(Massachusetts Institute of Technology)协议 目前限制最少的开源许可协议之一(比 BSD 和 Apache 的限制都少),只要程序的开发者在修改后的源代码中保留原作者的许可信息即可,因此普遍被商业软件所使用。 使用 MIT 协议的软件有 PuTTY、X Window System、Ruby on Rails、Lua 5.0 onwards、Mono 等。 5.GUN LGPL(GNU Lesser General Public License,GNU 宽通用公共许可证) LGPL 是 GPL 的一个衍生版本,也被称为 GPL V2,该协议主要是为类库设计的开源协议。 LGPL 允许商业软件通过类库引用(link)的方式使用 LGPL 类库,而不需要开源商业软件的代码。这使得采用 LGPL 协议的开源代码可以被商业软件作为类库引用并发布和销售。 但是如果修改 LGPL 协议的代码或者衍生品,则所有修改的代码,涉及修改部分的额外代码和衍生的代码都必须采用 LGPL 协议。因此LGPL协议的开源代码很适合作为第三方类库被商业软件引用,但不适合希望以 LGPL 协议代码为基础,通过修改和衍生的方式做二次开发的商业软件采用。 三、如何选择开源协议 世界上的开源协议有上百种(有兴趣的读者请猛击这里了解),很少有人能彻底搞清它们之间的区别,即使在最流行的六种开源协议——GPL、BSD、MIT、Mozilla、Apache 和 LGPL——之中做选择,也很复杂。 乌克兰程序员 Paul Bagwell 画了一张分析图,说明应该怎么选择开源协议,大家看了一目了然,真是清爽。 图片来自于阮一峰博客 四、开源等于免费吗? 首先,开源软件和免费软件是两个概念: 开源软件是指公开源代码的软件。 开源软件在发行的时候会附上软件的源代码,并授权允许用户更改、传播或者二次开发。 免费软件就是免费提供给用户使用的软件。 但是在免费的同时,通常也会有一些限制,比如源代码不公开,用户不能随意修改、不能二次发布等。 免费软件的例子比比皆是,QQ、微信、迅雷、酷狗、360 等都是免费软件,你可以随意使用,尽情蹂躏;但是,如果你嫌弃它们复杂,自己删除了一些无用的功能,然后在网上发布了一个精简版本供大家下载,那么你就离法院的传票不远了。 开源软件是不抵触商业的,开源的目的也不是做慈善事业,而是通过更多人的参与,减少软件的缺陷,丰富软件的功能,同时也避免了少数人在软件里留一些不正当的后门。开源软件最终还会反哺商业,让商业公司为用户提供更好的产品。 Android 就是大众最熟知的一款开源操作系统,它除了用在手机上,还用在汽车、平板电脑、电视、智能手表等其它硬件平台,小米、华为、OPPO、三星等都是 Android 的受益者,他们都赚得盆满钵满。 很多著名的开源项目背后都有商业公司支撑,它们的开发者也都有正式的工作,享受和我们一样的社会福利;如果一个成功的开源项目背后没有商业公司,这反而是不健康的,社会需要开源和商业之间的互补来促进技术的革新。 免责声明:本文来源网络,版权归原作者所有。如涉及作品版权问题,请与我联系删除。 推荐阅读: 什么是Cortex-M内核的MPU 线程、进程、多线程、多进程 和 多任务   ELF相比Hex、Bin文件格式有哪些与众不同? 关注 微信公众号『strongerHuang』,后台回复“1024”查看更多内容,回复“加群”按规则加入技术交流群。 长按前往图中包含的公众号关注 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 开源软件 嵌入式

  • STM32的HAL和LL库可以混用使用吗?

    关注+星标公众号,不错过精彩内容 作者:strongerHuang 微信公众号:strongerHuang 因为STM32标准外设库已经停更了,导致很多开发者都转向了HAL,但一些读者可能比较疑惑,有HAL和LL两种库,到底能不能混合使用呢? 一、标准外设库停更了 很多学习STM32的朋友都比较依赖之前的标准外设库(StdPeriph_Lib),我想告诉大家一个事实,那就是标准外设库已经停更很久了。 支持标准外设库的STM32,只有相对较老的系列:F0、 F1、 F2、 F3、 F4、 L1. 我特地看了下,STM32标准外设库最后一次更新时间是2016年11月的F4系列。 标准外设库地址: https://www.st.com/en/embedded-software/stm32-standard-peripheral-libraries.html (公号不支持外链接,请复制链接到浏览器打开) 这后面出来的L0、 L4、 L5、 F7、 H7、 G0、 G4、MP1等都没有标准外设库了。 所以,使用STM32CubeHAL将成为今后的的主流。这里就出来了一个问题:该使用HAL,还是LL开发呢? 二者能共同共同开发吗? 拓展阅读: 关于STM32的四类嵌入式软件库 STM32Cube LL能高效的原因 二、HAL和LL库能混合使用吗? 这个问题的答案是:不建议共同使用。 当然,这个问题还要分情况:相同外设和不同外设之间共同使用HAL和LL库。 1.不同外设混用HAL和LL库 这里说的不同外设混用HAL和LL库,针对的是不同外设。比如:UART使用HAL库,SPI使用LL库。 这种情况下,一般来说:问题不大。 因为官方不管是从资料,还是从STM32CubeMX工具配置都没有反对这种做法。 虽没有明确说不允许这样操作,但实际项目不建议这种方式。 至于原因,相信不用我说,有项目经验的朋友都明白。这样做不利于代码移植,管理等工作。 2.相同外设混用HAL和LL库 这里才是本文重点,这种情况,官方其实也是不建议混合。 如果混用,会存在一系列问题:底层冲突、结构混乱、管理不方便等。 LL库驱动独立,HAL包含驱动包 拿STM32F4的UART传输函数来说,LL库的位于stm32f4xx_ll_usart.h: 而HAL定义于stm32f4xx_hal_uart.c: 从这里可以看得出来,LL只需包含头文件即可,HAL要包含bsp包。 假如使用LL库的工程,想使用HAL库,需添加bsp包到工程。 HAL句柄 对于LL而言,使用HAL库,会多一个句柄,比如UART1: UART_HandleTypeDef huart1; 如果LL库的工程,直接调用HAL接口是不行的,缺少句柄。 中断请求处理 HAL和LL的中断请求IRQ方式其实是不一样的,混用之后很容易出错。 这里简单举例说这些,深入理解底层的朋友应该知道,还有许多地方也是不建议混用。 当然,不是绝对的(不能混用),我想说:万不得已,慎用。 ------------ END ------------ 推荐阅读: 什么是Cortex-M内核的MPU 程序猿如何选择开源协议? 线程、进程、多线程、多进程 和 多任务  关注 微信公众号『strongerHuang』,后台回复“1024”查看更多内容,回复“加群”按规则加入技术交流群。 长按前往图中包含的公众号关注 点击“ 阅读原文 ”查看更多分享,欢迎点分享、收藏、点赞、在看。 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 软件 嵌入式

  • 硬核!15张图解Redis为什么这么快

    作者|莱乌 作为一名服务端工程师,工作中你肯定和 Redis 打过交道。Redis 为什么快,这点想必你也知道,至少为了面试也做过准备。很多人知道 Redis 快仅仅因为它是基于内存实现的,对于其它原因倒是模棱两可。 那么今天就和小莱一起看看: - 思维导图 - 基于内存实现 这点在一开始就提到过了,这里再简单说说。 Redis 是基于内存的数据库,那不可避免的就要与磁盘数据库做对比。对于磁盘数据库来说,是需要将数据读取到内存里的,这个过程会受到磁盘 I/O 的限制。 而对于内存数据库来说,本身数据就存在于内存里,也就没有了这方面的开销。 高效的数据结构 Redis 中有多种数据类型,每种数据类型的底层都由一种或多种数据结构来支持。正是因为有了这些数据结构,Redis 在存储与读取上的速度才不受阻碍。这些数据结构有什么特别的地方,各位看官接着往下看: 1、简单动态字符串 这个名词可能你不熟悉,换成 SDS 肯定就知道了。这是用来处理字符串的。了解 C 语言的都知道,它是有处理字符串方法的。而 Redis 就是 C 语言实现的,那为什么还要重复造轮子?我们从以下几点来看: (1)字符串长度处理 这个图是字符串在 C 语言中的存储方式,想要获取 「Redis」的长度,需要从头开始遍历,直到遇到 '\0' 为止。 Redis 中怎么操作呢?用一个 len 字段记录当前字符串的长度。想要获取长度只需要获取 len 字段即可。你看,差距不言自明。前者遍历的时间复杂度为 O(n),Redis 中 O(1) 就能拿到,速度明显提升。 (2)内存重新分配 C 语言中涉及到修改字符串的时候会重新分配内存。修改地越频繁,内存分配也就越频繁。而内存分配是会消耗性能的,那么性能下降在所难免。 而 Redis 中会涉及到字符串频繁的修改操作,这种内存分配方式显然就不适合了。于是 SDS 实现了两种优化策略: 空间预分配 对 SDS 修改及空间扩充时,除了分配所必须的空间外,还会额外分配未使用的空间。 具体分配规则是这样的:SDS 修改后,len 长度小于 1M,那么将会额外分配与 len 相同长度的未使用空间。如果修改后长度大于 1M,那么将分配1M的使用空间。 惰性空间释放 当然,有空间分配对应的就有空间释放。 SDS 缩短时,并不会回收多余的内存空间,而是使用 free 字段将多出来的空间记录下来。如果后续有变更操作,直接使用 free 中记录的空间,减少了内存的分配。 (3)二进制安全 你已经知道了 Redis 可以存储各种数据类型,那么二进制数据肯定也不例外。但二进制数据并不是规则的字符串格式,可能会包含一些特殊的字符,比如 '\0' 等。 前面我们提到过,C 中字符串遇到 '\0' 会结束,那 '\0' 之后的数据就读取不上了。但在 SDS 中,是根据 len 长度来判断字符串结束的。 看,二进制安全的问题就解决了。 2、双端链表 列表 List 更多是被当作队列或栈来使用的。队列和栈的特性一个先进先出,一个先进后出。双端链表很好的支持了这些特性。 - 双端链表 - (1)前后节点 链表里每个节点都带有两个指针,prev 指向前节点,next 指向后节点。这样在时间复杂度为 O(1) 内就能获取到前后节点。 (2)头尾节点 你可能注意到了,头节点里有 head 和 tail 两个参数,分别指向头节点和尾节点。这样的设计能够对双端节点的处理时间复杂度降至 O(1) ,对于队列和栈来说再适合不过。同时链表迭代时从两端都可以进行。 (3)链表长度 头节点里同时还有一个参数 len,和上边提到的 SDS 里类似,这里是用来记录链表长度的。因此获取链表长度时不用再遍历整个链表,直接拿到 len 值就可以了,这个时间复杂度是 O(1)。 你看,这些特性都降低了 List 使用时的时间开销。 3、压缩列表 双端链表我们已经熟悉了。不知道你有没有注意到一个问题:如果在一个链表节点中存储一个小数据,比如一个字节。那么对应的就要保存头节点,前后指针等额外的数据。 这样就浪费了空间,同时由于反复申请与释放也容易导致内存碎片化。这样内存的使用效率就太低了。 于是,压缩列表上场了! 它是经过特殊编码,专门为了提升内存使用效率设计的。所有的操作都是通过指针与解码出来的偏移量进行的。 并且压缩列表的内存是连续分配的,遍历的速度很快。 4、字典 Redis 作为 K-V 型数据库,所有的键值都是用字典来存储的。 日常学习中使用的字典你应该不会陌生,想查找某个词通过某个字就可以直接定位到,速度非常快。这里所说的字典原理上是一样的,通过某个 key 可以直接获取到对应的value。 字典又称为哈希表,这点没什么可说的。哈希表的特性大家都很清楚,能够在 O(1) 时间复杂度内取出和插入关联的值。 5、跳跃表 作为 Redis 中特有的数据结构-跳跃表,其在链表的基础上增加了多级索引来提升查找效率。 这是跳跃表的简单原理图,每一层都有一条有序的链表,最底层的链表包含了所有的元素。这样跳跃表就可以支持在 O(logN) 的时间复杂度里查找到对应的节点。 下面这张是跳表真实的存储结构,和其它数据结构一样,都在头节点里记录了相应的信息,减少了一些不必要的系统开销。 合理的数据编码 对于每一种数据类型来说,底层的支持可能是多种数据结构,什么时候使用哪种数据结构,这就涉及到了编码转化的问题。 那我们就来看看,不同的数据类型是如何进行编码转化的: String:存储数字的话,采用int类型的编码,如果是非数字的话,采用 raw 编码; List:字符串长度及元素个数小于一定范围使用 ziplist 编码,任意条件不满足,则转化为 linkedlist 编码; Hash:hash 对象保存的键值对内的键和值字符串长度小于一定值及键值对; Set:保存元素为整数及元素个数小于一定范围使用 intset 编码,任意条件不满足,则使用 hashtable 编码; Zset:zset 对象中保存的元素个数小于及成员长度小于一定值使用 ziplist 编码,任意条件不满足,则使用 skiplist 编码。 合适的线程模型 Redis 快的原因还有一个是因为使用了合适的线程模型: 1、I/O多路复用模型 I/O :网络 I/O 多路:多个 TCP 连接 复用:共用一个线程或进程 生产环境中的使用,通常是多个客户端连接 Redis,然后各自发送命令至 Redis 服务器,最后服务端处理这些请求返回结果。 应对大量的请求,Redis 中使用 I/O 多路复用程序同时监听多个套接字,并将这些事件推送到一个队列里,然后逐个被执行。最终将结果返回给客户端。 2、避免上下文切换 你一定听说过,Redis 是单线程的。那么单线程的 Redis 为什么会快呢? 因为多线程在执行过程中需要进行 CPU 的上下文切换,这个操作比较耗时。Redis 又是基于内存实现的,对于内存来说,没有上下文切换效率就是最高的。多次读写都在一个CPU 上,对于内存来说就是最佳方案。 3、单线程模型 顺便提一下,为什么 Redis 是单线程的。 Redis 中使用了 Reactor 单线程模型,你可能对它并不熟悉。没关系,只需要大概了解一下即可。 这张图里,接收到用户的请求后,全部推送到一个队列里,然后交给文件事件分派器,而它是单线程的工作方式。Redis 又是基于它工作的,所以说 Redis 是单线程的。 总结 基于内存实现 数据都存储在内存里,减少了一些不必要的 I/O 操作,操作速率很快。 高效的数据结构 底层多种数据结构支持不同的数据类型,支持 Redis 存储不同的数据; 不同数据结构的设计,使得数据存储时间复杂度降到最低。     合理的数据编码 根据字符串的长度及元素的个数适配不同的编码格式。 合适的线程模型 I/O 多路复用模型同时监听客户端连接; 单线程在执行过程中不需要进行上下文切换,减少了耗时。 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: redis 嵌入式

  • 如何无侵入管理所有的微服务接口?

    本文是《微服务治理实践》系列篇的第四篇文章,主要分享 Spring Cloud 微服务框架下的服务契约。 在详细讲述服务契约之前,先给大家讲一个场景。 前言 随着微服务架构越来越流行,越来越多的公司使用微服务框架进行开发。甚至不止是公司,连笔者的研究生导师都要对实验室的 Spring Boot 工程项目转型使用微服务框架了。随着时间的推移,服务量逐渐上升,小学妹吃不消跑来问我问题: 一姐,我来交接你之前写的项目啦,你什么时间方便我想问你一些问题。这么多微服务接口,感觉不知道从哪里去看会比较好呢。 我想了想自己刚入门时候写的垃圾代码,还没有注释,无语凝噎。 好。我平时工作日在实习,周末给你讲哈。 于是到周末,花了整整一个晚上的时间,终于给零基础学妹从众多接口的含义,到参数列表的解析,最后到讲解百度应该搜什么关键词,全方位视频指导。学妹十分感动: 一姐你太贴心了555,跟别人协作项目的时候,经常能讲上几句就不错了,然后我还是什么都不明白,改完接口也不及时告诉我。还是你最好了,后面还有什么不懂的我再来问你哦。 从以上场景,我们可以总结出使用微服务框架后,会带来的几点进度协同问题: 1. 不及时提供接口API 尤其体现在项目交接上,该问题对人员变动比较频繁的组织,如高校项目的准毕业生和新生交接、企业项目的外包人员交接,问题会显得更加突出。开发人员经常过于关注微服务的内部实现,相对较少设计API接口。 程序员最讨厌的两件事:1. 写注释 2. 别人不写注释 是不是经常想着写完代码再写注释,但真正把代码写完以后,注释/接口描述一拖再拖最后就没有了? 2. 不及时变更接口 即使有了 API 文档,但由于文档的离线管理,微服务接口变更以后,文档却没有及时变更,影响协作人员的开发进度。 综上我们看到,我们不但希望所有的微服务接口都可以很方便的添加规范的接口描述,而且也能随着接口的变更及时更新文档。因此,我们需要服务契约来帮助我们解决这些问题。 为什么我们需要服务契约 首先我们来看服务契约的定义: 服务契约指基于 OpenAPI 规范的微服务接口描述,是微服务系统运行和治理的基础。 有人可能会问了,既然想要规范的描述接口,我有很多其他的方式啊,为什么我要用服务契约? 1. 我用 Javadoc 来描述接口然后生成文档不可以吗? 可以,但刚刚我们也提到了“程序员最讨厌的两件事”,要求所有的开发人员都去主动的按照规范写注释,把所有的接口、参数列表的类型、描述等信息全都写清楚,是一件比较费时费力的事情。我们希望有一个能够减少开发人员负担的方法。 2. 现在不是有很多专业的 API 管理工具吗,我直接用专业的 API 管理工具去维护也是可以的吧。 API 管理工具我们也是有考虑的,但是有如下的问题: 很多工具依然缺少自动化的API生成; 不是专注于解决微服务领域的问题,随着服务量迅速上升,管理起来依旧比较困难。 3. 那微服务框架本身也会有提供相关的接口管理功能吧,Dubbo可以用Dubbo Admin,Spring Cloud可以用Spring Boot Admin,它们不香吗? 这里篇幅有限,我们不再去详细讲述开源工具我们怎么去一步步使用,详情见表格: 从表格可以看到,EDAS 3.0 微服务治理的服务契约,支持版本更广泛了,配置难度更低了,代码侵入性没有了,直接用 EDAS 3.0 的 Agent 方案,它不是更香了? EDAS 3.0 服务契约实践 下面我们来体验一下,EDAS 3.0 上如何查看 Spring Cloud 的微服务契约。 创建应用 根据你的需要,选择集群类型和应用运行环境,创建 Provider 和 Consumer 应用。 服务查询控制台 登录 EDAS 3.0 控制台,在页面左上角选择地域; 左侧导航栏选择:微服务治理 -> Spring Cloud / Dubbo / HSF -> 服务查询; 服务查询页面单击某个服务的详情。 查看服务契约 服务详情页面包括基本信息、服务调用关系、接口元数据、元数据等信息。在“接口元数据”一栏,便可查看服务的API信息。当用户使用Swagger注解时,会在“描述”列显示相应信息。 服务契约实现细节 在设计服务契约功能的时候,我们不但解决了开源框架中配置难度大,且部分方案具有代码侵入性的问题,而且针对如下阶段的难点都做了相应的方案,相信这些地方也是微服务框架的使用者会关心的: 数据获取 获取的同时是否还需要其他配置? 如何获取所需的方法名及描述、参数列表及描述、返回类型等信息? 会不会影响服务的性能? 信息能不能全面的拿到? 能不能同步接口的变更? 数据解析 能不能看到参数类型/返回值类型的详细结构? 解析参数结构的时候会不会影响启动时间? 泛型、枚举是否支持? 循环引用如何解决? 下面我们来详细介绍一下这几点都是如何解决的。 数据获取 为了减少用户的配置和使用难度,我们采用了 Agent 方案,用户无需任何额外的代码和配置,就可以使用我们的微服务治理功能。 Java Agent是一种字节码增强技术,运行时插入我们的代码,便可稳定的享受到所有的增强功能。 而且通过测试可得,只要在 SpringMVC 的映射处理阶段,选取合适的拦截点,就可以获取到所有的方法映射信息,包括方法名、参数列表、返回值类型、注解信息。由于该点在应用启动过程中只发生一次,因此不会有性能的影响。 我们获取的注解主要是针对 Swagger 注解。作为 OpenAPI 规范的主要指定者, Swagger 虽并非是唯一支持 OpenAPI 的工具,但也基本属于一种事实标准。注解解析的内容在表格的描述部分进行展示: Swagger2的注解解析(如@ApiOperation,@ApiParam,@ApiImplicitParam),解析value值在“描述”列显示; OpenAPI3的注解解析(如@Operation,@Parameter),解析description值在“描述”列显示。 当接口发生变更时,只要将新版本的应用部署上去,显示的服务契约信息就会是最新的,无需担心接口描述信息不能同步的问题。 数据解析 如果参数列表/返回值的类型是一个复杂类型,一般情况我们只看到一个类型名。那么有没有办法可以看到这个复杂类型的具体构成呢? 聪明的你可能就会想到,通过反射来递归遍历该类所有的 Field ,不就都解决了?思路确实如此,但实际要考虑的情况会更复杂一些。 以该复杂类型 CartItem 为例,它可能不但会包含基本类型,还可能会涉及到泛型、枚举,以及存在循环引用的情况。 因此在解析该类型之前,我们需要先判断一下该类型是否存在泛型、枚举的情况,如果是,需要额外解析并存储泛型列表及枚举列表。 而循环引用问题,我们只需借助一个 typeCache 即可解决。如下图,A和B构成了一个循环引用。 如果我们不采取任何措施,递归遍历将永远没有出口。但是,如果我们在遍历A的所有类型之前,先判断一下 typeCache 里是否存在 TypeA 。对 TypeB 也以此类推: 那么当遍历 ObjB 中所包含类型时,如果遇到了 TypeA ,同样也会先判断 typeCache 中是否存在。如存在,就无需再递归遍历 ObjA 中所有的类型了,而是直接记录一个 A 的引用。因此,循环引用问题也就得以解决。 最终的解析信息,可以在服务测试功能中得以体现。未来我们可能会支持直接在服务查询中的服务契约页,通过一个入口显示复杂类型的具体解析结构。 由此我们看到,在服务契约的获取及解析阶段,涉及到的可能影响用户体验的问题都得到了一定的解决。 作者信息: 刘旖明,花名眉生,北京邮电大学计算机学院在读研究生,暑期作为阿里云云原生部门实习开发工程师,主要进行阿里云微服务产品的相关研发,目前关注微服务、云原生等技术方向。 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: C语言 嵌入式

  • ElasticSearch 索引 VS MySQL 索引

    前言 这段时间在维护产品的搜索功能,每次在管理台看到 elasticsearch 这么高效的查询效率我都很好奇他是如何做到的。 这甚至比在我本地使用 MySQL 通过主键的查询速度还快。 为此我搜索了相关资料: 这类问题网上很多答案,大概意思呢如下: ES 是基于 Lucene 的全文检索引擎,它会对数据进行分词后保存索引,擅长管理大量的索引数据,相对于 MySQL 来说不擅长经常更新数据及关联查询。 说的不是很透彻,没有解析相关的原理;不过既然反复提到了索引,那我们就从索引的角度来对比下两者的差异。 MySQL 索引 先从 MySQL 说起,索引这个词想必大家也是烂熟于心,通常存在于一些查询的场景,是典型的空间换时间的案例。 以下内容以 Innodb 引擎为例。 常见的数据结构 假设由我们自己来设计 MySQL 的索引,大概会有哪些选择呢? 散列表 首先我们应当想到的是散列表,这是一个非常常见且高效的查询、写入的数据结构,对应到 Java 中就是 HashMap 这个数据结构应该不需要过多介绍了,它的写入效率很高O(1),比如我们要查询 id=3 的数据时,需要将 3 进行哈希运算,然后再这个数组中找到对应的位置即可。 但如果我们想查询 1≤id≤6 这样的区间数据时,散列表就不能很好的满足了,由于它是无序的,所以得将所有数据遍历一遍才能知道哪些数据属于这个区间。 有序数组 有序数组的查询效率也很高,当我们要查询 id=4 的数据时,只需要通过二分查找也能高效定位到数据O(logn)。 同时由于数据也是有序的,所以自然也能支持区间查询;这么看来有序数组适合用做索引咯? 自然是不行,它有另一个重大问题;假设我们插入了 id=2.5 的数据,就得同时将后续的所有数据都移动一位,这个写入效率就会变得非常低。 平衡二叉树 既然有序数组的写入效率不高,那我们就来看看写入效率高的,很容易就能想到二叉树;这里我们以平衡二叉树为例: 由于平衡二叉树的特性: 左节点小于父节点、右节点大于父节点。 所以假设我们要查询 id=11 的数据,只需要查询 10—>12—>11 便能最终找到数据,时间复杂度为O(logn),同理写入数据时也为O(logn)。 但依然不能很好的支持区间范围查找,假设我们要查询5≤id≤20 的数据时,需要先查询10节点的左子树再查询10节点的右子树最终才能查询到所有数据。 导致这样的查询效率并不高。 跳表 跳表可能不像上边提到的散列表、有序数组、二叉树那样日常见的比较多,但其实 Redis 中的 sort set 就采用了跳表实现。 这里我们简单介绍下跳表实现的数据结构有何优势。 我们都知道即便是对一个有序链表进行查询效率也不高,由于它不能使用数组下标进行二分查找,所以时间复杂度是o(n) 但我们也可以巧妙的优化链表来变相的实现二分查找,如下图: 我们可以为最底层的数据提取出一级索引、二级索引,根据数据量的不同,我们可以提取出 N 级索引。 当我们查询时便可以利用这里的索引变相的实现了二分查找。 假设现在要查询 id=13 的数据,只需要遍历 1—>7—>10—>13 四个节点便可以查询到数据,当数越多时,效率提升会更明显。 同时区间查询也是支持,和刚才的查询单个节点类似,只需要查询到起始节点,然后依次往后遍历(链表有序)到目标节点便能将整个范围的数据查询出来。 同时由于我们在索引上不会存储真正的数据,只是存放一个指针,相对于最底层存放数据的链表来说占用的空间便可以忽略不计了。 平衡二叉树的优化 但其实 MySQL 中的 Innodb 并没有采用跳表,而是使用的一个叫做 B+ 树的数据结构。 这个数据结构不像是二叉树那样大学老师当做基础数据结构经常讲到,由于这类数据结构都是在实际工程中根据需求场景在基础数据结构中演化而来。 比如这里的 B+ 树就可以认为是由平衡二叉树演化而来。 刚才我们提到二叉树的区间查询效率不高,针对这一点便可进行优化: 在原有二叉树的基础上优化后:所有的非叶子都不存放数据,只是作为叶子节点的索引,数据全部都存放在叶子节点。 这样所有叶子节点的数据都是有序存放的,便能很好的支持区间查询。 只需要先通过查询到起始节点的位置,然后在叶子节点中依次往后遍历即可。 当数据量巨大时,很明显索引文件是不能存放于内存中,虽然速度很快但消耗的资源也不小;所以 MySQL 会将索引文件直接存放于磁盘中。 这点和后文提到 elasticsearch 的索引略有不同。 由于索引存放于磁盘中,所以我们要尽可能的减少与磁盘的 IO(磁盘 IO 的效率与内存不在一个数量级) 通过上图可以看出,我们要查询一条数据至少得进行 4 次IO,很明显这个 IO 次数是与树的高度密切相关的,树的高度越低 IO 次数就会越少,同时性能也会越好。 那怎样才能降低树的高度呢? 我们可以尝试把二叉树变为三叉树,这样树的高度就会下降很多,这样查询数据时的 IO 次数自然也会降低,同时查询效率也会提高许多。 这其实就是 B+ 树的由来。 使用索引的一些建议 其实通过上图对 B+树的理解,也能优化日常工作的一些小细节;比如为什么需要最好是有序递增的? 假设我们写入的主键数据是无序的,那么有可能后写入数据的 id 小于之前写入的,这样在维护 B+树 索引时便有可能需要移动已经写好数据。 如果是按照递增写入数据时则不会有这个考虑,每次只需要依次写入即可。 所以我们才会要求数据库主键尽量是趋势递增的,不考虑分表的情况时最合理的就是自增主键。 整体来看思路和跳表类似,只是针对使用场景做了相关的调整(比如数据全部存储于叶子节点)。 ES 索引 MySQL 聊完了,现在来看看 Elasticsearch 是如何来使用索引的。 正排索引 在 ES 中采用的是一种名叫倒排索引的数据结构;在正式讲倒排索引之前先来聊聊和他相反的正排索引。 以上图为例,我们可以通过 doc_id 查询到具体对象的方式称为使用正排索引,其实也能理解为一种散列表。 本质是通过 key 来查找 value。 比如通过 doc_id=4 便能很快查询到 name=jetty wang,age=20 这条数据。 倒排索引 那如果反过来我想查询 name 中包含了 li 的数据有哪些?这样如何高效查询呢? 仅仅通过上文提到的正排索引显然起不到什么作用,只能依次将所有数据遍历后判断名称中是否包含 li ;这样效率十分低下。 但如果我们重新构建一个索引结构: 当要查询 name 中包含 li 的数据时,只需要通过这个索引结构查询到 Posting List 中所包含的数据,再通过映射的方式查询到最终的数据。 这个索引结构其实就是倒排索引。 Term Dictionary 但如何高效的在这个索引结构中查询到 li 呢,结合我们之前的经验,只要我们将 Term 有序排列,便可以使用二叉树搜索树的数据结构在o(logn) 下查询到数据。 将一个文本拆分成一个一个独立Term 的过程其实就是我们常说的分词。 而将所有 Term 合并在一起就是一个 Term Dictionary,也可以叫做单词词典。 英文的分词相对简单,只需要通过空格、标点符号将文本分隔便能拆词,中文则相对复杂,但也有许多开源工具做支持(由于不是本文重点,对分词感兴趣的可以自行搜索)。 当我们的文本量巨大时,分词后的 Term 也会很多,这样一个倒排索引的数据结构如果存放于内存那肯定是不够存的,但如果像 MySQL 那样存放于磁盘,效率也没那么高。 Term Index 所以我们可以选择一个折中的方法,既然无法将整个 Term Dictionary 放入内存中,那我们可以为Term Dictionary 创建一个索引然后放入内存中。 这样便可以高效的查询Term Dictionary ,最后再通过Term Dictionary 查询到 Posting List。 相对于 MySQL 中的 B+树来说也会减少了几次磁盘IO。 这个 Term Index 我们可以使用这样的 Trie树 也就是我们常说的字典树 来存放。 更多关于字典树的内容请查看这里。 如果我们是以 j 开头的 Term 进行搜索,首先第一步就是通过在内存中的 Term Index 查询出以 j 打头的 Term 在 Term Dictionary 字典文件中的哪个位置(这个位置可以是一个文件指针,可能是一个区间范围)。 紧接着在将这个位置区间中的所有 Term 取出,由于已经排好序,便可通过二分查找快速定位到具体位置;这样便可查询出 Posting List。 最终通过 Posting List 中的位置信息便可在原始文件中将目标数据检索出来。 更多优化 当然 ElasticSearch 还做了许多针对性的优化,当我们对两个字段进行检索时,就可以利用 bitmap 进行优化。 比如现在需要查询 name=li and age=18 的数据,这时我们需要通过这两个字段将各自的结果 Posting List 取出。 最简单的方法是分别遍历两个集合,取出重复的数据,但这个明显效率低下。 这时我们便可使用 bitmap 的方式进行存储(还节省存储空间),同时利用先天的位与 计算便可得出结果。 [1, 3, 5]       ⇒ 10101 [1, 2, 4, 5] ⇒ 11011 这样两个二进制数组求与便可得出结果: 10001 ⇒ [1, 5] 最终反解出 Posting List 为[1, 5],这样的效率自然是要高上许多。 同样的查询需求在 MySQL 中并没有特殊优化,只是先将数据量小的数据筛选出来之后再筛选第二个字段,效率自然也就没有 ES 高。 当然在最新版的 ES 中也会对 Posting List 进行压缩,具体压缩规则可以查看官方文档,这里就不具体介绍了。 总结 最后我们来总结一下: 通过以上内容可以看出再复杂的产品最终都是基础数据结构组成,只是会对不同应用场景针对性的优化,所以打好数据结构与算法的基础后再看某个新的技术或中间件时才能快速上手,甚至自己就能知道优化方向。 最后画个饼,后续我会尝试按照 ES 倒排索引的思路做一个单机版的搜索引擎,只有自己写一遍才能加深理解。 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: C语言 嵌入式

  • 通用的底层埋点都是怎么做的?

    想要在程序里监控数据库的操作耗时,想要在底层框架中自动传递链路跟踪信息,这些需求经常会碰到,特别是在构建基础框架的时候。 核心目标只有一个,那就是在底层封装好,不用上层使用人员关心。今天跟大家聊聊常用的底层扩展埋点方式是怎么处理的。 框架自带扩展点 如果你使用的框架在设计的时候,就预留了扩展点就很方便了。比如 Mybatis 的拦截器,我们可以在拦截器中对 Sql 进行监控,改写。 比如阿里的 Sentinel 框架,可以通过 SPI 来扩展 Slot,调整编排顺序,新增自定义的 Slot 来实现限流告警等。 开源框架的质量参差不齐,有在早期设计比较好的,留足了各种扩展点,方便使用者。也有一些没有考虑那么全面,导致你在使用的时候需要进行扩展,发现找不到扩展点,对于框架本身没有提供扩展点的场景,请接着看下面。 修改源码 如果框架没有扩展点,最直接的方式就是修改开源框架的源码来扩展自己想要的功能,通常的做法就是克隆源码到自己的私有仓库中,然后修改,测试,重新打包使用。 像我们之前用了 XXL-JOB 做任务调度,也是修改了某些代码,在界面上扩展了监控通知的配置信息,默认是只支持邮箱,可以扩展出手机,钉钉等。 修改源码不好的点在于需要跟原框架的版本进行对齐,如果不对齐,随便改都没事。不对齐意味着修复了某些 bug 和新增了某些功能,就无法使用了。要对齐,就需要不断的将新版本的代码合并到你自己的分支上。 还有很多公司,就是基于开源的版本,构建了公司内部自己的版本,后续直接就是跟着内部的使用需求去扩展和维护了,不会跟社区的版本进行同步,如果你们有专门的团队去做这件事情,也是一种方式。 同名文件覆盖 改源码的方式需要经常同步新版本的代码,有的时候往往只想修改某一个类而已,比如对底层的某些操作进行埋点监控,如果框架本身没有提供扩展点的话只能改源码来实现。 其实还有个投机取巧的方式,就是在项目中创建一个跟你要修改的一模一样的类,包名+类目都一样,方法名也一样,方法的实现你可以改。这样就能覆盖 jar 包中的类了,还是跟类加载顺序有关系,先加载你自己定义的。 这样的方式好处在于不用经常去同步新版本的代码,如果你用的框架版本升级了,只要包名和类名不变,你这个覆盖的只是那个类而已,新增的功能和修复的 bug 都不会有影响。 切面拦截 切面在做很多统一处理的时候非常有用,同样在做底层埋点的场景也适用。 比如我们要对项目中 Mongodb 的所有操作都进行埋点监控,可以修改 MongoDB 的驱动源码,可以创建同名文件进行覆盖,方式有很多种,找到一个合适,又能实现需求的最重要。 以 Spring 中操作 Mongodb 来说明,在 Spring Data Mongodb 中会 MongoTemplate 来操作 Mongodb。最简单的方式就是直接对 MongoTemplate 类进行埋点,这样所有的操作都可以监控起来。 用切面直接切到 MongoTemplate 的所有方法上,然后进行埋点,就很简单了。 @Aspectpublic class MongoTemplateAspect { @Pointcut("execution(* org.springframework.data.mongodb.core.MongoTemplate.*(..))") public void pointcut() {} @Around("pointcut()") public Object around(final ProceedingJoinPoint pjp) throws Throwable { String callMethod = pjp.getSignature().getDeclaringType().getSimpleName() + "." + pjp.getSignature().getName(); Map data = new HashMap(); data.put("params", JsonUtils.toJson(pjp.getArgs())); return CatTransactionManager.newTransaction(() -> { try { return pjp.proceed(); } catch (Throwable throwable) { throw new RuntimeException(throwable); } }, "Mongo", callMethod, data); }} 又比如,你还想监控 Redis 相关的,Redis 用的也是跟 Spring 整合的框架,那么也有 RedisTemplate 这个类,同样也可以用切面来实现。 基于 Template 类来埋点,相对比较上层,如果还想在底层一点进行监控,也就是 Connection 这层,Template 里面的操作都是基于 Connection 来实现的。 同样我们可以用切面来替换 Connection 相关的实现,比如可以用切面切到获取 Connection 的方法,然后替换 Connection 的对象为具备埋点监控的对象。 @Aspectpublic class RedisAspect { @Pointcut("target(org.springframework.data.redis.connection.RedisConnectionFactory)") public void connectionFactory() {} @Pointcut("execution(org.springframework.data.redis.connection.RedisConnection *.getConnection(..))") public void getConnection() {} @Pointcut("execution(org.springframework.data.redis.connection.RedisClusterConnection *.getClusterConnection(..))") public void getClusterConnection() {} @Around("getConnection() && connectionFactory()") public Object aroundGetConnection(final ProceedingJoinPoint pjp) throws Throwable { RedisConnection connection = (RedisConnection) pjp.proceed(); return new CatMonitorRedisConnection(connection); } @Around("getClusterConnection() && connectionFactory()") public Object aroundGetClusterConnection(final ProceedingJoinPoint pjp) throws Throwable { RedisClusterConnection clusterConnection = (RedisClusterConnection) pjp.proceed(); return new CatMonitorRedisClusterConnection(clusterConnection); }} CatMonitorRedisConnection 中对原生的 RedisConnection 做了增强,也不会影响原有的 RedisConnection 的功能。 public class CatMonitorRedisConnection implements RedisConnection { private final RedisConnection connection; private CatMonitorHelper catMonitorHelper; public CatMonitorRedisConnection(RedisConnection connection) { this.connection = connection; this.catMonitorHelper = new CatMonitorHelper(); } @Override public byte[] get(byte[] key) { return catMonitorHelper.execute(RedisCommand.GET, key, () -> connection.get(key)); }} Java Agent Java Agent 可以在运行期将已经加载的类的字节码进行变更,可以加入我们需要进行监控的代码逻辑。无需对原有代码进行改造,零侵入性。 在非常多优秀的开源框架中都看到了 Java Agent 的应用,像 APM 框架 SkyWalking,异步传递上下文 transmittable-thread-local 等。 Java Agent 相对其他的方式来说,还是有一定的门槛,毕竟不是日常开发中经常会用到的技术点。如果想了解这种扩展方式,可以看看一些已经用了的开源框架的源码,就知道大概怎么使用了。下面贴一段 transmittable-thread-local 中对线程池进行扩展的代码吧,主要就是利用了 javassist 操作字节码。 try {final CtMethod afterExecute = clazz.getDeclaredMethod("afterExecute", new CtClass[]{runnableClass, throwableClass});// unwrap runnable if IsAutoWrapperString code = "$1 = com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.unwrapIfIsAutoWrapper($1);";logger.info("insert code before method " + signatureOfMethod(afterExecute) + " of class " + afterExecute.getDeclaringClass().getName() + ": " + code);afterExecute.insertBefore(code);modified = true;} catch (NotFoundException e) {// clazz does not override afterExecute method, do nothing.} 关于作者:尹吉欢,简单的技术爱好者,《Spring Cloud 微服务-全栈技术与案例解析》, 《Spring Cloud 微服务 入门 实战与进阶》作者。 特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:长按订阅更多精彩▼如有收获,点个在看,诚挚感谢 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 编程 嵌入式

  • 求求你,不要再使用!=null判空了!

    △Hollis, 一个对Coding有着独特追求的人△ 这是Hollis的第 307 篇原创分享 作者 l  上帝爱吃苹果 来源 l Hollis(ID:hollischuang) 对于Java程序员来说,null是令人头痛的东西。时常会受到空指针异常(NPE)的骚扰。连Java的发明者都承认这是他的一项巨大失误。 那么,有什么办法可以避免在代码中写大量的判空语句呢? 有人说可以使用 JDK8提供的 Optional 来避免判空,但是用起来还是有些麻烦。 作者在日常工作中,封装了一个工具,可以可以链式调用对象成员而无需判空,相比原有的if null逻辑 和 JDK8提供的 Optional 更加优雅易用,在工程实践中大大提高了编码效率,也让代码更加的精准和优雅。 不优雅的判空调用 我想从事Java开发的小伙伴肯定有遇到过下面这种让人难受的判空逻辑:现在有一个User类,School 是它的成员变量 /*** @author Axin* @since 2020-09-20* @summary 一个User类定义 * (Ps:Data 是lombok组件提供的注解,简化了get set等等的约定代码)*/@Datapublic class User {    private String name;    private String gender;    private School school;    @Data    public static class School {        private String scName;        private String adress;    }} 现在想要获得School的成员变量 adress , 一般的处理方式: public static void main(String[] args) {    User axin = new User();    User.School school = new User.School();    axin.setName("hello");    if (Objects.nonNull(axin) && Objects.nonNull(axin.getSchool())) {        User.School userSc = axin.getSchool();        System.out.println(userSc.getAdress());    }} 获取adress时要对School进行判空,虽然有些麻烦,到也能用,通过 JDK8 提供的 Optional 工具也是可以,但还是有些麻烦。 而下文的 OptionalBean 提供一种可以链式不断地调用成员变量而无需判空的方法,直接链式调用到你想要获取的目标变量,而无需担心空指针的问题。 链式调用成员变量 如果用了本文设计的工具 OptionalBean ,那么上述的调用可以简化成这样: public static void main(String[] args) {    User axin = new User();    User.School school = new User.School();    axin.setName("hello");    // 1. 基本调用    String value1 = OptionalBean.ofNullable(axin)            .getBean(User::getSchool)            .getBean(User.School::getAdress).get();    System.out.println(value1);} 执行结果: 其中User的school变量为空,可以看到代码并没有空指针,而是返回了null。这个工具怎么实现的呢? OptionalBean 工具 /*** @author Axin* @since 2020-09-10* @summary 链式调用 bean 中 value 的方法*/public final class OptionalBean&lt;T> {    private static final OptionalBean<?> EMPTY = new OptionalBean<>();    private final T value;    private OptionalBean() {        this.value = null;    }    /**     * 空值会抛出空指针     * @param value     */    private OptionalBean(T value) {        this.value = Objects.requireNonNull(value);    }    /**     * 包装一个不能为空的 bean     * @param value     * @param <T>     * @return     */    public static <T> OptionalBean<T> of(T value) {        return new OptionalBean<>(value);    }    /**     * 包装一个可能为空的 bean     * @param value     * @param <T>     * @return     */    public static <T> OptionalBean<T> ofNullable(T value) {        return value == null ? empty() : of(value);    }    /**     * 取出具体的值     * @param fn     * @param <R>     * @return     */    public T get() {        return Objects.isNull(value) ? null : value;    }    /**     * 取出一个可能为空的对象     * @param fn     * @param <R>     * @return     */    public <R> OptionalBean<R> getBean(Function<? super T, ? extends R> fn) {        return Objects.isNull(value) ? OptionalBean.empty() : OptionalBean.ofNullable(fn.apply(value));    }    /**     * 如果目标值为空 获取一个默认值     * @param other     * @return     */    public T orElse(T other) {        return value != null ? value : other;    }    /**     * 如果目标值为空 通过lambda表达式获取一个值     * @param other     * @return     */    public T orElseGet(Supplier<? extends T> other) {        return value != null ? value : other.get();    }    /**     * 如果目标值为空 抛出一个异常     * @param exceptionSupplier     * @param <X>     * @return     * @throws X     */    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {        if (value != null) {            return value;        } else {            throw exceptionSupplier.get();        }    }    public boolean isPresent() {        return value != null;    }    public void ifPresent(Consumer<? super T> consumer) {        if (value != null)            consumer.accept(value);    }    @Override    public int hashCode() {        return Objects.hashCode(value);    }    /**     * 空值常量     * @param <T>     * @return     */    public static<T> OptionalBean<T> empty() {        @SuppressWarnings("unchecked")        OptionalBean<T> none = (OptionalBean<T>) EMPTY;        return none;    }} 工具设计主要参考了 Optional 的实现,再加上对链式调用的扩展就是上述的OptionalBean。 getBean 其实是当变量为空时返回了一个 包装空值的 OptionalBean 对象,同时泛型的使用让工具更加易用。 使用手册 可以看到代码中也提供了和 Optional 一样的扩展方法,如 ifPresent()、orElse()等等: public static void main(String[] args) {    User axin = new User();    User.School school = new User.School();    axin.setName("hello");    // 1. 基本调用    String value1 = OptionalBean.ofNullable(axin)            .getBean(User::getSchool)            .getBean(User.School::getAdress).get();    System.out.println(value1);    // 2. 扩展的 isPresent方法 用法与 Optional 一样    boolean present = OptionalBean.ofNullable(axin)            .getBean(User::getSchool)            .getBean(User.School::getAdress).isPresent();    System.out.println(present);    // 3. 扩展的 ifPresent 方法    OptionalBean.ofNullable(axin)            .getBean(User::getSchool)            .getBean(User.School::getAdress)            .ifPresent(adress -> System.out.println(String.format("地址存在:%s", adress)));    // 4. 扩展的 orElse    String value2 = OptionalBean.ofNullable(axin)            .getBean(User::getSchool)            .getBean(User.School::getAdress).orElse("家里蹲");    System.out.println(value2);    // 5. 扩展的 orElseThrow    try {        String value3 = OptionalBean.ofNullable(axin)                .getBean(User::getSchool)                .getBean(User.School::getAdress).orElseThrow(() -> new RuntimeException("空指针了"));    } catch (Exception e) {        System.out.println(e.getMessage());    }} run一下: 总结 设计了一种可以链式调用对象成员而无需判空的工具让代码更加的精准和优雅,如果本文设计的工具满足了刚好解决你的困扰,那就在项目中使用吧! 如果您有更的设计或者文中有错误,还请留言一起讨论,互相进步! 点个[在看],是对小灰最大的支持! 免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

    时间:2020-10-25 关键词: 编程 嵌入式

  • C语言常用的6种转换工具函数

    1、字符串转十六进制 代码实现: void StrToHex(char *pbDest, char *pbSrc, int nLen){  char h1,h2;  char s1,s2;  int i;    for (i=0; i 9)            s1 -= 7;        s2 = toupper(h2) - 0x30;        if (s2 > 9)            s2 -= 7;        pbDest[i] = s1*16 + s2;    }} 2、十六进制转字符串 代码实现: void HexToStr(char *pszDest, char *pbSrc, int nLen){    char    ddl, ddh;    for (int i = 0; i  57) ddh = ddh + 7;        if (ddl > 57) ddl = ddl + 7;        pszDest[i * 2] = ddh;        pszDest[i * 2 + 1] = ddl;    }    pszDest[nLen * 2] = '\0';} 或者 u16 Hex2StringArray (u8 *pSrc,  u16 SrcLen, u8 *pObj){    u16 i=0;    for(i=0;    i= '9' || *str  10)        *pbDest = 0;    tmp = 1;    *pbDest = 0;    for (i=nLen-1; i>=0; i--)    {        *pbDest += tmp*(*(pbSrc+i)-'0');        tmp = tmp*10;    }} 效果:字符串:”123” 转为 123 第三种:包含转为浮点数: //m^n函数//返回值:m^n次方.u32 NMEA_Pow(u8 m,u8 n){    u32 result=1;        while(n--)result*=m;        return result;}//str转换为数字,以','或者'*'结束//buf:数字存储区//dx:小数点位数,返回给调用函数//返回值:转换后的数值int NMEA_Str2num(u8 *buf,u8*dx){    u8 *p=buf;    u32 ires=0,fres=0;    u8 ilen=0,flen=0,i;    u8 mask=0;    int res;    while(1) //得到整数和小数的长度    {        if(*p=='-'){mask|=0X02;p++;}//是负数        if(*p==','||(*p=='*'))break;//遇到结束了        if(*p=='.'){mask|=0X01;p++;}//遇到小数点了        else if(*p>'9'||(*p> 16) & 0xFF);    buf[2] = ((u32Value >> 8) & 0xFF);    buf[3] = (u32Value & 0xFF);} 效果:整型 50 转字符数组 {‘\0’,’\0’,’\0’,’2’} u8数组转u32 void U8ArrayToU32(uint8_t *buf, uint32_t *u32Value){    *u32Value = (buf[0] 

    时间:2020-10-24 关键词: C语言 嵌入式

首页  上一页  1 2 3 4 5 6 7 8 9 10 下一页 尾页
发布文章

技术子站

更多

项目外包