20220523
20220520
日志处理
一个数学公式编辑器,可以在线编辑
CortexJShttps://cortexjs.io/mathlive/
23种设计模式
The Catalog of Design Patternshttps://refactoring.guru/design-patterns/catalog
我们的最佳实践是在一个 RUN 命令中执行更新、安装和清理操作:
RUN apt-get update && \
apt-get install -y libgirepository1.0-dev libpoppler-glib-dev && \
rm -rf /var/lib/apt/lists/*
20220517
堆排序是一种选择排序,它的最坏,最好,平均时间复杂度为O(nlogn),它也是不稳定排序。
堆排序基本思想
(1)将待排序序列构造成一个大顶堆
(2)此时,整个序列的最大值就是堆顶的根节点。
(3)将其与末尾元素进行交换,此时末尾就为最大值。
(4)然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了。
20220513
时序数据特征:
Comparing with BTree and HashTable:
- skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。 所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点
- 在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现
- 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速
- 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势
- 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的
- 从算法实现难度上来比较,skiplist比平衡树要简单得多
- 跳表和红黑树的插入、删除、查找效率都是O(logN),都可以顺序遍历所有元素(红黑树通过中序遍历)。红黑树更稳定一些,跳表恶化是存在概率的,虽然概率极低。
- 跳表实现简单,但是浪费了很多空间。红黑树实现麻烦,但是没有使用额外的空间。
- 跳表区间查找很方便,redis中的zset实现区间查找就是用的跳表。
插入
查找
浏览器二维码API
20220512
PostgreSQL部署|基于Stream复制的手工主从切换https://mp.weixin.qq.com/s/CSkS1RvZ_MTq_3C5h_h08w
PostgreSQL部署|基于Stream复制的高可用部署https://mp.weixin.qq.com/s/5SJHeZ_YBa-JGNZbnJAX8Q
20220511
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(@CurrentUserName String name) {
return "hello "+name;
}
}
手把手教你在 SpringBoot 中自定义参数解析器。https://mp.weixin.qq.com/s/mbstcEEtwOS9ZRSSjusbfA
20220510
在 OpenGL 中,设置好顶点数据,设置好着色器,调用 drawcall 函数,3D 图形就被绘制出来了。
那么在这背后,GPU 做了什么工作呢?其实,从输入的顶点 3D 信息,到输出每个像素点的颜色信息,中间经过了很多步操作。这些操作按照一定的顺序构成了一条图形流水线(Graphics Pipeline),或者叫渲染管线。每个步骤的输入都依赖于前一步骤输出的结果。其中的步骤包括顶点处理(vertex processing)、图元装配(triangle assembly)、光栅化(rasterization)、片段处理(fragment processing)、测试和混合(testing and blending)几个关键步骤。
图片来源:https://graphicscompendium.com/intro/01-graphics-pipeline
开始: Application: Input-vertices in 3D space 物体在空间中的表示为一堆点和它的连接关系
1 Vertex Processing: Vertex Stream - Vertices positioned in screen space
顶点处理,经过一系列的变换,三维空间的点 最终变换成屏幕上的点
2 Triangle Processing: Tringle Stream - Triangles postioned in screen space
三角网处理:它们之前的连接关系并没有改变,变换后的点仍然会表示三角形,只不过这些三角形投影到屏幕中去了。
3 Rasterization: Fragment Stream - Fragments (one per covered sample)
光栅化,把原本一个连续表示的三角形,三个点连起来,把它给离散化成屏幕空间上的一堆像素,当然不一定是像素,如果用了一些msaa或者其它一些抗锯齿什么的,这些一个一个的基本的微元(片段,对应directX中就称之为像素),我们有一系列的三角形需要把它打散成像素,那么打散成像素,其过程中涉及一些遮挡的处理。
4 Fragment Processing: Shaded Fragments - Shaded fragments
对任何的Fragment做着色,就是去计算它应该长什么样子,比如说Blinn-Phong着色模型
5 FrameBuffer Operations
帧缓存操作
结束: Display: Output - image (pixels)
线程数是有限制的,有限的线程数导致无法实现上万级别的并发连接,过多的线程切换也抢走了 CPU 的时间,从而降低了每秒能够处理的请求数量。
于是为了达到高并发,你可能会选择一个异步框架,用非阻塞 API 把业务逻辑打乱到多个回调函数中,通过多路复用实现高并发。但此时就要求业务代码过度关注并发细节,需要维护很多中间状态,一旦代码逻辑出现错误就会陷入回调地狱。
因此这么做不但 Bug 率会很高,项目的开发速度也上不去,产品及时上线存在风险。如果想兼顾开发效率,又能保证高并发,协程就是最好的选择。它可以在保持异步化运行机制的同时,还能用同步的方式写代码,这既实现了高并发,又缩短了开发周期,是高性能服务未来的发展方向。
这里我们必须要指出,在并发量方面,使用「协程」的方式并不优于「非阻塞+回调」的方式,而我们之所以选择协程是因为它的编程模型更简单,类似于同步,也就是可以让我们使用同步的方式编写异步的代码。「非阻塞+回调」这种方式非常考验编程技巧,一旦出现错误,不好定位问题,容易陷入回调地狱、栈撕裂等困境。
I/O 多路复用机制是指一个线程处理多个 IO 流
为什么要有协程,协程是如何实现的?高并发的解决方案https://mp.weixin.qq.com/s/UF_lkPKPkEVwzTk0ermung
终于懂了:协程思想的起源与发展协程概念比线程概念更早出现https://mp.weixin.qq.com/s/Wy-_J9TlU3aSAJt24ZT5hw
因为这两者根本不是同一种东西。
协程是一边吃饭一边说话。
线程是一边听音乐一边写作业。
协程其本身是串行的。在任何一个时刻,同属于一个组当中的协程,只有一个协程在跑。即便你的cpu是多核的,如果不另外开多个工作线程去跑协程,那么无论你多少个协程,它们都跑在同一个核心上。
而线程,至少在概念上是完全同时的、并行的。在很久以前,当cpu只有一个核心的时候,它们的确最终也只能以类似协程的方式时分复用核心,但是在如今,它们是在多个核心上同时跑的。
所以,除了你所提到的那些之外,最大的开销在于协程是自主让渡执行时间,而且都跑在一个核心上,不需要复杂的调度算法,不需要优先级管理;而线程,其自身不让渡核心时间,还有优先级。操作系统要像指挥交通那样去指挥,所以耗时。
volatile 能够确保一定的有序性,例如:
//这里 x、y 为非 volatile 变量
//而 flag 为 volatile 变量
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
由于 flag 是 volatile 变量,确保了:指令 1、2 一定在语句 3 之前执行,语句 4、5 一定在语句 3 后面执行。但是 volatile 并不会确保语句 1、2 之间不进行指令重排序,语句 4、5 之间不进行指令重排序。
理解指令重排序的例子是 doule check lock(DCL)单例模式,如下所示:
public class Student {
...
private static volatile Student student;
//double check null
public static Student getStudent() {
if (student == null) {
synchronized (Student.class) {
if (student == null) {
student = new Student("hello world",12);
}
}
}
return student;
}
...
}
单例模式通常会要求单例字段被 volatile 修饰,这是因为 new 一个实例实际上分为三步非原子操作来完成:
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间
CPU 会对上述三个操作进行指令重排序:
- 内存分配一定要第一步执行,否则会影响语义,因此 CPU 不会对其进行指令重排;
- 初始化对象以及设置 singleton 指向刚申请的内存空间可能会被指令重排,因为在单线程模型下这两步即使倒过来做,也没有影响。
如果单例字段不使用 volatile 修饰,那么在并发环境下就会遇到这样的问题:线程 1 先给 student 赋值上没有初始化完成的 Student 类实例。线程 2 通过单例方法得到了该实例,在调用相关方法时却发现字段尚未初始化,最终抛出了异常。
使用 volatile 修饰单例字段就能够避免上述并发安全问题,为什么?
这是因为 new 操作对应的第三步实际上是给 volatile 字段赋值,它会要求第二步必须在其之前就已经运行完毕,这就能够避免因为指令重排序而导致的单例提前暴露。
20220509
NIO 相对于 BIO 最大的改进就是使用了多路复用技术,用少量线程处理大量客户端 IO 请求,因为IO 请求等待时间相对较长,一个线程则可以同时处理多个就绪的IO.
20220507
实现泛型的两种最常见方式:
虚拟方法表
在编译器中实现泛型的一种方法是使用 Virtual Method Table。泛型函数被修改成只接受指针作为参数的方式。然后,这些值被分配到堆上,这些值的指针被传递给泛型函数。这样做是因为指针看起来总是一样的,不管它指向的是什么类型。
如果这些值是对象,而泛型函数需要调用这些对象的方法,它就不能再这样做了。该函数只有一个指向对象的指针,不知道它们的方法在哪里。因此,它需要一个可以查询方法的内存地址的表格:Virtual Method Table。这种所谓的动态调度已经被 Go 和 Java 等语言中的接口所使用。
Virtual Method Table 不仅可以用来实现泛型,还可以用来实现其他类型的多态性。然而,推导这些指针和调用虚拟函数要比直接调用函数慢,而且使用 Virtual Method Table 会阻止编译器进行优化。
单态化
一个更简单的方法是单态化(Monomorphization),编译器为每个被调用的数据类型生成一个泛型函数的副本。
最大的优势是,Monomorphization 带来的运行时性能明显好于使用 Virtual Method Table。直接方法调用不仅更有效率,而且还能适用整个编译器的优化链。不过,这样做的代价是编译时长,为所有相关类型生成泛型函数的副本是非常耗时的。
Go 的实现
这两种方法中哪一种最适合 Go?快速编译很重要,但运行时性能也很重要。为了满足这些要求,Go 团队决定在实现泛型时混合两种方法。
Go 使用 Monomorphization,但试图减少需要生成的函数副本的数量。它不是为每个类型创建一个副本,而是为内存中的每个布局生成一个副本:int、float64、Node 和其他所谓的 "值类型" 在内存中看起来都不一样,因此泛型函数将为所有这些类型复制副本。
与值类型相反,指针和接口在内存中总是有相同的布局。编译器将为指针和接口的调用生成一个泛型函数的副本。就像 Virtual Method Table 一样,泛型函数接收指针,因此需要一个表来动态地查找方法地址。在 Go 实现中的字典与虚拟方法表的性能特点相同。
结论
这种混合方法的好处是,你在使用值类型的调用中获得了 Monomorphization 的性能优势,而只在使用指针或接口的调用中付出了 Virtual Method Table 的成本。
React 首次成为头号 UI 框架,但如果我们考虑到 Vue.js 被分成了两个仓库(第二和第三版本),实际上 Vue.js 才是第一名。
最大的变化是 Svelte 的崛起,它超越 Angular 占据第三位。
越来越多的工具或组件将 Svelte 纳入目标框架中(例如我们提到的 Vite)。
Virtual DOM是纯开销
当前,Virtual DOM实现在计算新旧虚拟节点之间的差异时会产生计算成本。
即使使用非常有效的差分算法 (如list-diff2),当虚拟节点树大于虚拟节点的两位数时,差异成本也会变得显著。
树区分算法是出了名的慢。时间复杂度可以从O(n)转O(n ^ 3)取决于虚拟节点树的复杂性。这与DOM操纵相去甚远,后者是O(1)。
Virtual DOM的未来
编译器是新框架”-- 汤姆·戴尔
Ember的创建者汤姆是最早倡导为JavaScript UI库使用编译器开源狂热者之一。
现在,我们知道汤姆的赌注是正确的。JavaScript生态系统见证了Solid、Svelte等“已编译”库的兴起,它们放弃了Virtual DOM。这些库使用编译器预渲染,并在使用时生成代码来跳过不必要的渲染。
另一方面,Virtual DOM落后于这一趋势。当前的虚拟DOM库本质上与“按需” 编译器不兼容。因此,Virtual DOM的渲染速度通常是比现代“No Virtual DOM” UI库慢几个数量级。
如果我们希望Virtual DOM在未来的渲染速度上具有竞争力,那就需要重新设计Virtual DOM以允许编译器增强。
20220506
MySQL 8.0.28版本增强Instant DDL对列重命名的支持。
在Instant Add Column基础上实现列重命名较为简单直接。
快速DDL支持类型
- Instant add column
- 当一条alter语句中同时存在不支持instant的ddl时,则无法使用
- 只能顺序加列
- 不支持压缩表、不支持包含全文索引的表
- 不支持临时表,临时表只能使用copy的方式执行DDL
- 不支持那些在数据词典表空间中创建的表
- 修改索引类型
- 修改ENUM/SET类型的定义
- 存储的大小不变时
- 向后追加成员
- 增加或删除类型为virtual的generated column
- RENAME TABLE操作
MySQL · InnoDB · Instant DDL扩展概述http://mysql.taobao.org/monthly/2022/04/05/
前端文章写的不错
计算机网络实验
Jim Kurose Homepagehttps://gaia.cs.umass.edu/kurose_ross/wireshark.php
20220505
Spring Security 中的权限注解很神奇吗?。https://mp.weixin.qq.com/s/TaPlws-ZLTDUnffuiw-r1Q
build
命令之前提供 GOOS
和 GOARCH
这两个环境变量,来允许你构建针对不同硬件架构和操作系统的可执行文件。
GOOS=linux GOARCH=arm64 go build hello.go
查看底层汇编指令
源代码并不会直接转换为可执行文件,尽管它生成了一种中间汇编格式,然后最终被组装为可执行文件。在 Go 中,这被映射为一种中间汇编格式,而不是底层硬件汇编指令。
要查看这个中间汇编格式,请在使用 build
命令时,提供 -gcflags
选项,后面跟着 -S
。这个命令将会显示使用到的汇编指令:
复制
$ go build -gcflags="-S" hello.go
# command-line-arguments
"".main STEXT size=138 args=0x0 locals=0x58 funcid=0x0
0x0000 00000 (/test/hello.go:5) TEXT "".main(SB), ABIInternal, $88-0
0x0000 00000 (/test/hello.go:5) MOVQ (TLS), CX
0x0009 00009 (/test/hello.go:5) CMPQ SP, 16(CX)
0x000d 00013 (/test/hello.go:5) PCDATA $0, $-2
0x000d 00013 (/test/hello.go:5) JLS 128
<< snip >>
你也可以使用 objdump -s
选项,来查看已经编译好的可执行程序的汇编指令,就像下面这样:
复制
$ ls
hello hello.go
$ go tool objdump -s main.main hello
TEXT main.main(SB) /test/hello.go
hello.go:5 0x4975a0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX
hello.go:5 0x4975a9 483b6110 CMPQ 0x10(CX), SP
hello.go:5 0x4975ad 7671 JBE 0x497620
hello.go:5 0x4975af 4883ec58 SUBQ $0x58, SP
hello.go:6 0x4975d8 4889442448 MOVQ AX, 0x48(SP)
<< snip >>
20220503
《对线面试官》 Java内存模型为什么存在? #12纯净版https://mp.weixin.qq.com/s/DnZElICmvVwt2-V8lmEo0w
MESI协议:是以缓存行(缓存的基本数据单位,在Intel的CPU上一般是64字节)的几个状态来命名的(全名是Modified、Exclusive、 Share or Invalid)。该协议要求在每个缓存行上维护两个状态位,使得每个数据单位可能处于M、E、S和I这四种状态之一,各种状态含义如下:
M:被修改的。处于这一状态的数据,只在本CPU中有缓存数据,而其他CPU中没有。同时其状态相对于内存中的值来说,是已经被修改的,且没有更新到内存中。
E:独占的。处于这一状态的数据,只有在本CPU中有缓存,且其数据没有修改,即与内存中一致。
S:共享的。处于这一状态的数据在多个CPU中都有缓存,且与内存一致。
I:无效的。本CPU中的这份缓存已经无效。
一个处于M状态的缓存行,必须时刻监听所有试图读取该缓存行对应的主存地址的操作,如果监听到,则必须在此操作执行前把其缓存行中的数据写回内存。
一个处于S状态的缓存行,必须时刻监听使该缓存行无效或者独享该缓存行的请求,如果监听到,则必须把其缓存行状态设置为I。
一个处于E状态的缓存行,必须时刻监听其他试图读取该缓存行对应的主存地址的操作,如果监听到,则必须把其缓存行状态设置为S。
当CPU需要读取数据时,如果其缓存行的状态是I的,则需要从内存中读取,并把自己状态变成S,如果不是I,则可以直接读取缓存中的值,但在此之前,必须要等待其他CPU的监听结果,如其他CPU也有该数据的缓存且状态是M,则需要等待其把缓存更新到内存之后,再读取。
当CPU需要写数据时,只有在其缓存行是M或者E的时候才能执行,否则需要发出特殊的RFO指令(Read Or Ownership,这是一种总线事务),通知其他CPU置缓存无效(I),这种情况下性能开销是相对较大的。在写入完成后,修改其缓存状态为M。
所以如果一个变量在某段时间只被一个线程频繁地修改,则使用其内部缓存就完全可以办到,不涉及到总线事务,如果缓存一会被这个CPU独占、一会被那个CPU 独占,这时才会不断产生RFO指令影响到并发性能。这里说的缓存频繁被独占并不是指线程越多越容易触发,而是这里的CPU协调机制,这有点类似于有时多线程并不一定提高效率,原因是线程挂起、调度的开销比执行任务的开销还要大,这里的多CPU也是一样,如果在CPU间调度不合理,也会形成RFO指令的开销比任务开销还要大。当然,这不是编程者需要考虑的事,操作系统会有相应的内存地址的相关判断
先行发生(Happens-Before)是Java内存模型中定义的两项操作之间的偏序关系,比如说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包括修改了内存中共享变量的值、发送了消息、调用了方法等。
20220502
借助 SLF4J 中的 MDC 工具类,把操作人放在日志中,然后在日志中统一打印出来。首先在用户的拦截器中把用户的标识 Put 到 MDC 中。
如何优雅地记录操作日志?https://mp.weixin.qq.com/s/pRnrucbNX70SJyCGSQ8PQQ
20220501
所谓的有栈,无栈并不是说这个协程运行的时候有没有栈,而是说协程之间是否存在调用栈(callbackStack)
无栈协程是什么呢?其实无栈协程的本质就是一个状态机(state machine),它可以理解为在另一个角度去看问题,即同一协程协程的切换本质不过是指令指针寄存器的改变。
从执行时栈的角度来看,其实所有的协程共用的都是一个栈,即系统栈,也就也不必我们自行去给协程分配栈,因为是函数调用,我们当然也不必去显示的保存寄存器的值,而且相比有栈协程把局部变量放在新开的空间上,无栈协程直接使用系统栈使得CPU cache局部性更好,同时也使得无栈协程的中断和函数返回几乎没有区别,这样也可以凸显出无栈协程的高效。
协程的适用在IO密集型的程序。
由于IO操作远远小于CPU的操作,
所以往往需要CPU去等IO操作。同步IO下系统需要切换线程,让操作系统可以再IO过程中执行其他的东西。
这样虽然代码是符合人类的思维习惯但是由于大量的线程切换带来了大量的性能的浪费,尤其是IO密集型的程序。
所以人们发明了异步IO。就是当数据到达的时候触发我的回调。来减少线程切换带来性能损失
20220429
记录锁、间隙锁与 Next-Key Lock。https://mp.weixin.qq.com/s/Zh7GSzXJg_zt2ug3X5TwEQ
20220424
上下文保存与恢复是协程的核心
#include <chrono>
#include <iostream>
#include <thread>
// rbp rbx r12 r13 r14 r15 rip rsp
uint64_t co1_regs[8] = { 0 };
uint64_t co2_regs[8] = { 0 };
uint64_t co2_stack[1024];
void switch_to(void*, void*)
{
__asm volatile("movq %rbp, (%rdi)");
__asm volatile("movq %rbx, 8(%rdi)");
__asm volatile("movq %r12, 16(%rdi)");
__asm volatile("movq %r13, 24(%rdi)");
__asm volatile("movq %r14, 32(%rdi)");
__asm volatile("movq %r15, 40(%rdi)");
__asm volatile("movq 8(%rsp), %rax");
__asm volatile("movq %rax, 48(%rdi)");
__asm volatile("movq %rsp, 56(%rdi)");
__asm volatile("movq 56(%rsi), %rsp");
__asm volatile("movq 48(%rsi), %rax");
__asm volatile("movq %rax, 8(%rsp)");
__asm volatile("movq 40(%rsi), %r15");
__asm volatile("movq 32(%rsi), %r14");
__asm volatile("movq 24(%rsi), %r13");
__asm volatile("movq 16(%rsi), %r12");
__asm volatile("movq 8(%rsi), %rbx");
__asm volatile("movq (%rsi), %rbp");
}
void co1_routine()
{
for (;;)
{
std::cout << "co1 running" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
switch_to(co1_regs, co2_regs);
}
}
void co2_routine()
{
for (;;)
{
std::cout << "co2 running" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
switch_to(co2_regs, co1_regs);
}
}
int main()
{
co2_regs[0] = (uint64_t)&co2_stack[1024];
co2_regs[6] = (uint64_t)&co2_routine;
co2_regs[7] = co2_regs[0];
co1_routine();
}
20220422
20220421
1、性能和一致性不能同时满足,为了性能考虑,通常会采用「最终一致性」的方案;
2、掌握缓存和数据库一致性问题,核心问题有 3 点:缓存利用率、并发、缓存 + 数据库一起成功问题;
3、失败场景下要保证一致性,常见手段就是「重试」,同步重试会影响吞吐量,所以通常会采用异步重试的方案;
4、订阅变更日志的思想,本质是把权威数据源(例如 MySQL)当做 leader 副本,让其它异质系统(例如 Redis / Elasticsearch)成为它的 follower 副本,通过同步变更日志的方式,保证 leader 和 follower 之间保持一致。
如果系统能实现任意单一对象的单一读操作都返回最近写操作的值(A Read returns the most recently value written),则代表了这个系统实现了在技术层面对于单一对象单一操作的的最强的一致性需求
20220420
深入浅析Go中三个点(...)用法
标识数组元素个数
1 |
|
Go命令行中的通配符
描述包文件的通配符。
在这个例子中,会单元测试当前目录和所有子目录的所有包:
1 |
|
这里,...意味着数组的元素个数:
https://www.jb51.net/article/224566.htmhttps://www.jb51.net/article/224566.htm
ZGC 没有分代的概念
ZGC 的内存布局说起。与 Shenandoah 和 G1一样,ZGC 也采用基于 Region 的堆内存布局,但与它们不同的是 , ZGC 的 Region 具 有 动 态 性 (动态创建和销毁 , 以及动态的区域容量大小)。在 x64硬件平台下 , ZGC 的 Region 可以具有大、中、小三类容量(如下图所示):
- 小型 Region (Small Region ):容量固定为 2M, 存放小于 256K 的对象。
- 中型 Region (Medium Region):容量固定为 32M,放置大于等于256K但小于4M的对象。
- 大型 Region (Large Region): 容量不固定,可以动态变化,但必须为2MB 的整数倍,用于放置 4MB或以上的大对象。
go 通过"~"来表示支持类型扩展类型
否则只能接受 constraint 列表中的类型作为调用的参数,包括这些类型的别名。但对于列表中类型的衍生类型就不能接受了
20220419
Redis是怎样通讯的?_redis_ooooooh灰灰_InfoQ写作平台模型Redis 协议模型就是简单的请求-响应模型,和平常的 Http 协议有点类似。客户端发送 Redis 命令,然后服务端处理命令并返回结果给客户端。Redis 官方说这可能是最简单的网络协议模型了https://xie.infoq.cn/article/0496d06df156ad6a9ff365d08MySQL 是怎样通讯的?_Go_ooooooh灰灰_InfoQ写作平台前言我们平常使用数据库的场景一般是程序里面代码直接连接使用,然后进行 CRUD 操作。或者使用有 GUI 界面的数据库软件来手动操作数据库, 这类软件有 DataGrip、Navicat等等...。平https://xie.infoq.cn/article/42e11a05bf40d0ee709a68bb6MySQL 协议明文连接代码实现[Go语言] · GitHubMySQL 协议明文连接代码实现[Go语言]. GitHub Gist: instantly share code, notes, and snippets.https://gist.github.com/greycodee/22f98464fece7792a83433a1fba58e2ahttps://gist.github.com/greycodee/4a102aa9ae689aea1874b1fe06190192https://gist.github.com/greycodee/4a102aa9ae689aea1874b1fe06190192