西北玄天一片云

Ctl+C, Ctl+V


  • 首页

  • 标签

  • 归档

  • 分类

  • 关于

Redis容量评估

发表于 2020-02-28 | 分类于 系统设计 , 容量预估

存储容量预估系统设计最重要的项之一。精准的容量预估,可以帮助我们选择最合适存储软件。

本文主要来介绍下Redis几种常用的数据结构,以及内存占用大小。

字符串(SDS)

1
2
3
4
5
6
//SDS的定义如下(sds.h/sdshdr):
struct sdshdr {
int len; // 记录buf数组中已使用字节的数量
int free; // 记录buf数组中未使用字节的数量
char buf[]; // 字节数组,用于保存实际字符串
}

字符串长度不能超过512MB。

下图展示了一个SDS实例:

SDS实例中存储了字符串“Redis”, 该实例中free的值为5(表示未使用的字节为5),len的值为5(表示已使用的字节为5),加上字符数组末尾的”\0”,所以SDS实例占用的总字节数为:
(sizeof(int) * 2) + (5 + 5 + 1) = 19,即(len+free)+buf

举例说明

当key长度为 13,value长度为15,key个数为2000,根据上面总结的容量评估模型,容量预估值为 (32 + 16 + 32 + 32) * 2000 + 2048 * 8 = 240384

生产实践

用redis做商品缓存,key为商品id,value为商品信息。key大约占用30个字节,value大约占用1500个字节。
当缓存1百万商品时,容量预估值为(32 + 16 + 64 + 1536) * 1000000+ 1000000(预估) * 8 = 1656000000,约等于1.54G
总结:当value比较大时,占用的内存约等于value的大小*个数

双向链表(List)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//每个链表节点使用一个listNode结构来表示,具体定义如(adlist.h/listNode):
typedef struct listNode {
struct listNode *prev; // 前置节点
struct listNode *next; // 后置节点
void *value; // 节点的值
} listNode;

//redis另外还使用了list结构来管理链表,以方便操作,具体定义如下(adlist.h/list):

typedef struct list {
listNode *head; // 表头节点
listNode *tail; // 表尾结点
void *(*dup)(void *ptr); // 节点值复制函数
void (*free)(void *ptr); // 节点值释放函数
int (*match)(void *ptr, void *key); // 节点值对比函数
unsigned int len; // 链表所包含的节点数量
} list;

有序集合(ZSET)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;

typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;

typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;

zskiplistNode结构占用的总字节数为:24 + 16 * n,n为level数组的大小。

zskiplist结构则用于保存跳跃表节点的相关信息,header和tail分别指向跳跃表的表头和表尾节点,length记录节点总数量,level记录跳跃表中层高最大的那个节点的层数量。zskiplist结构占用的总字节数为32。

字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;

typedef struct dictType {
uint64_t (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key);
void *(*valDup)(void *privdata, const void *obj);
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;

/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table;
unsigned long size;
unsigned long sizemask;
unsigned long used;
} dictht;

typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;

一个Hash存储结构最终会产生以下几个消耗内存的结构:

  • 1个SDS结构,用作key字符串,占9个字节(free4个字节+len4个字节+字符串末尾”\0”1个字节);
  • 1个dictEntry结构,24字节,负责保存当前的哈希对象;
  • 1个redisObject结构,16字节,指向当前key下属的dict结构;
  • 1个dict结构,88字节,负责保存哈希对象的键值对;
  • n个dictEntry结构,24*n字节,负责保存具体的field和value,n等于field个数;
  • n个redisObject结构,16*n字节,用作field对象;
  • n个redisObject结构,16*n字节,用作value对象;
  • n个SDS结构,(field长度+9)*n字节,用作field字符串;
  • n个SDS结构,(value长度+9)*n字节,用作value字符串;

因为hash类型内部有两个dict结构,所以最终会有产生两种rehash,一种rehash基准是field个数,另一种rehash基准是key个数,结合jemalloc内存分配规则,hash类型的容量评估模型为:

总内存消耗 = [key_SDS大小 + redisObject大小 + dictEntry大小 + dict大小 +(redisObject大小 * 2 + field_SDS大小 + val_SDS大小 + dictEntry大小) * field个数 + field_bucket个数 * 指针大小] * key个数 + key_bucket个数 * 指针大小

【换算】
总内存消耗 = [ key_SDS大小 + 16 + 24 + 88 + (16 * 2 + field_SDS大小 + val_SDS大小 + 24) * field个数 + field_bucket个数 * 8] * key个数 + key_bucket个数 * 8
总内存消耗 =[128+ key_SDS大小 +(56 + field_SDS大小 + val_SDS大小 ) * field个数 + field_bucket个数 * 8] * key个数 + key_bucket个数 * 8

集合(SET)

SET的实现其实就是value为null的hash表

参考文献

  1. Redis容量预估
  2. 深入学习Redis(1):Redis内存模型

微服务与SOA架构对比

发表于 2020-02-28 | 分类于 架构 , 微服务

面向服务架构(SOA, Service-oriented architecture)已经存在有些年头了,这是一种用于设计软件的伟大原则。在SOA中,所有组件都是独立自主的,并能为其他组件提供服务。

1
2
3
4
5
6
7
8
9
10
11
下面进一步解释上表所述的不同之处:

开发方面 - 在这两种体系结构中,可以使用不同的编程语言和工具开发服务,从而将技术多样性带入开发团队。开发可以在多个团队中组织,但是在SOA中,每个团队都需要了解常见的通信机制。另一方面,使用微服务,服务可以独立于其他服务运行和部署。因此,频繁部署新版本的微服务或独立扩展服务会更容易。您可以在这里进一步阅读有关微服务的这些好处。

“上下文边界” - SOA鼓励组件的共享,而微服务尝试通过“上下文边界”来最小化共享。上下文边界是指以最小的依赖关系将组件及其数据耦合为单个单元。由于SOA依靠多个服务来完成业务请求,构建在SOA上的系统可能比微服务要慢。

通信 - 在SOA中,ESB可能成为影响整个系统的单一故障点。由于每个服务都通过ESB进行通信,如果其中一个服务变慢,可能会阻塞ESB并请求该服务。另一方面,微服务在容错方面要好得多。例如,如果一个微服务存在内存错误,那么只有该微服务会受到影响。所有其他微服务将继续定期处理请求。

互操作性 - SOA通过消息中间件组件促进了多种异构协议的使用。微服务试图通过减少集成选择的数量来简化架构模式。因此,如果您想要在异构环境中使用不同协议来集成多个系统,则需要考虑SOA。如果您的所有服务都可以通过相同的远程访问协议访问,那么微服务对您来说是一个更好的选择。

大小Size - 最后一点但并非最不重要的一点,SOA和微服务的主要区别在于规模和范围。微服务架构中的前缀“微”是指内部组件的粒度,意味着它们必须比SOA架构的服务往往要小得多。微服务中的服务组件通常有一个单一的目的,他们做得很好。另一方面,在SOA服务中通​​常包含更多的业务功能,并且通常将它们实现为完整的子系统。

典型微服务架构图,当然还要配合服务发现、服务治理、服务监控

参考文献

  1. 微服务架构 vs. SOA架构

长连接与短连接

发表于 2020-02-28 | 分类于 HTTP

短连接、keepalive、长连接,相信有很多人分不清楚这三个概念。包括本人在写下这句话的时候。

短连接

短连接可能是大部分人最长接触到的,也比较容易理解:

  1. 建立连接
  2. 数据交互
  3. 关闭连接

短连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。每个 TCP 连接的建立都需要三次握手,每个 TCP 连接的断开要四次挥手。适用于并发量大,但是每个用户又不需频繁操作的情况。

但是在用户需要频繁操作的业务场景下(如新用户注册,网购提交订单等),频繁的使用短连接则会使性能时延产生叠加。

keepalive

通过Http1.1默认开启Keep Alive(http1.0 1.1 2.0),我们可以让一条Http连接保持不被立即关闭。有些同学这时就疑惑了,是不是长连接就是Keep Alive呢?

其实不是的。长连接我们也叫TCP长连接,它是架设在TCP协议上的,而上面说的Keep Alive是Http协议的内容,连协议都不同,两者自然不是一个东西。

开启了Keep Alive是Http连接,我们也称之为持久连接,和长连接并不同。感兴趣可参考此文:《TCP 进阶》。

长连接

长连接与持久连接本质上非常的相似,持久连接侧重于 HTTP 应用层,特指一次请求结束之后,服务器会在自己设置的 keepalivetimeout 时间到期后才关闭已经建立的连接。长连接则是 client 方与 server 方先建立连接,连接建立后不断开,然后再进行报文发送和接收,直到有一方主动关闭连接为止。

1
2
3
4
5
长连接的适用场景也非常的广泛:
监控系统:后台硬件热插拔、LED、温度、电压发生变化等;
IM 应用:收发消息的操作;
即时报价系统:例如股市行情 push 等;
推送服务:各种 App 内置的 push 提醒服务;

参考文献

  1. Android 架构之长连接技术
  2. 《TCP 进阶》

分布式锁的实现

发表于 2020-02-28 | 分类于 系统设计 , 分布式

在分布式环境中,经常遇到多台机器上的多个进程对同一数据的操作,如果这些进程不做互斥处理的话,往往会出现不符合预期的错误,比如商品超卖、红包超发、账号多扣款等。

多台机器上的进程的互斥,可以通过锁来达成。接下来我们介绍几种分布式锁的实现方式。

方式一、Redis setnx

这是比较常见的一种简单实现方式

我们先来看一下Redis setnx的文档介绍:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SETNX key value

将 key 的值设为 value ,当且仅当 key 不存在。

若给定的 key 已经存在,则 SETNX 不做任何动作。

SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。

可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
设置成功,返回 1 。
设置失败,返回 0 。

带过期时间的分布式锁实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
class DistributeLock {
private $redis;

public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}

/**
* @param $key string 锁的key
* @param $expire int 锁定的时长,单位秒
* @return bool true:锁定成功;false:锁定失败
*/
function lock($key, $expire) {
try {
if ($this->redis->setnx($key, 1) {
$this->redis->expire($key, $expire);
return true;
}
} catch (Exception $e) {
$this->redis->del($key);
}
return false;
}

function unlock($key) {
$this->redis->del($key);
}
}

看似没问题,想一下这种情况,setnx成功之后,expire操作执行失败了,进程crash,则该key设置的锁永远得不到释放。

这种实现方式的问题在于,setnx与expire操作不是原子操作,存在单点故障时锁无法释放的问题。

方式二、RedLock

这是redis之父antirez提出的分布式锁实现方式。

Redlock获取锁的过程。

1
2
3
4
5
1. 获取开始时间
2. 去各个节点获取锁
3. 再次获取时间。
4. 计算获取锁的时间,检查获取锁的时间是否小于获取锁的时间。
5. 持有锁。

antirez版redlock-rb

PHP版redlock-php

Redlock获取锁的过程。

向各个节点发送del命令,删除锁。

存在的问题

依赖于分布式机器时钟的同步。

这个问题曾引起分布式专家martin与Redis之父Antirez之间的论战。参见参考文档。

Martin 对 RedLock的指控:

1
2
3
1. 分布式的锁具有一个自动释放的功能。锁的互斥性,只在过期时间之内有效,锁过期释放以后就会造成多个Client 持有锁。

2. RedLock 整个系统是建立在,一个在实际系统无法保证的系统模型上的。在这个例子中就是系统假设时间是同步且可信的。

Martin对锁的改进,增加了token,实际是利用了CAS的乐观锁实现:

最后,martin推荐Zookeeper实现分布式锁。

Antirez的回应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
首先在实际的系统中,从两个方面来看:

1. 系统暂停,网络延迟。
2. 系统的时间发生阶跃。
对于第一个问题。上文已经提到了,RedLock做了一些微小的工作,但是没办法完全避免。其他带有自动释放的分布式锁也没有办法。

第二个问题,Martin认为系统时间的阶跃主要来自两个方面:

1. 人为修改。
2. 从NTP服务收到了一个跳跃时时钟更新。

对于人为修改,能说啥呢?人要搞破坏没办法避免。

NTP受到一个阶跃时钟更新,对于这个问题,需要通过运维来保证。需要将阶跃的时间更新到服务器的时候,应当采取小步快跑的方式。多次修改,每次更新时间尽量小。

方式三、基于ZooKeeper实现

原理分析:

1
2
3
4
5
1. 锁即Zookeeper上的一个节点
2. 客户端A发起一个加锁请求,先会在你要加锁的node下搞一个临时顺序节点。
3. 如果创建的几点序号是1,则获得锁
4. 序号不是1,则监听上一个序号释放锁。
5. 释放锁即删除临时节点。

初始状态:

获取锁的流程

参考文献

  1. SETNX
  2. SETNX key value
  3. PHP版redlock-php
  4. Redis RedLock 完美的分布式锁么?
  5. Is Redlock Safe?
  6. Martin 和 antirez关于RedLock的论战
  7. 七张图彻底讲清楚ZooKeeper分布式锁的实现原理【石杉的架构笔记】

回溯算法

发表于 2020-02-27 | 分类于 算法 , 回溯算法

题目一

全排列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
private $list_path = [];
/**
* @param Integer[] $nums
* @return Integer[][]
*/
function permute($nums) {
if(count($nums) == 0) {
return [];
}
$this->backtrace($nums, 0);
return $this->list_path;
}

function backtrace($nums, $start) {
if ($start == count($nums)) {
$this->list_path[] = $nums;
}
for($i=$start;$i<count($nums);$i++) {
$this->swap($nums[$start], $nums[$i]);
$this->backtrace($nums, $start+1);
$this->swap($nums[$start], $nums[$i]);
}
}
function swap(&$i, &$j) {
$t = $i;
$i = $j;
$j = $t;
}
}

##题目二
组合总数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Solution {
public $path = [];
public $sums = [];
/**
* @param Integer[] $candidates
* @param Integer $target
* @return Integer[][]
*/
function combinationSum($candidates, $target) {
$length = count($candidates);
if ($length == 0) {
return [];
}
sort($candidates);
$this->dfs($candidates, 0, $target);
return $this->sums;
}

function dfs($candidates, $start, $target) {
if ($target == 0) {
$this->sums[] = $this->path;
}
for($i = $start;$i<count($candidates) && ($target-$candidates[$i]>=0);$i++) {
$this->path[] = $candidates[$i];
$this->dfs($candidates, $i, $target-$candidates[$i]);
array_pop($this->path);
}
}
}

题目三

电话号码字母组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {

const ALPHA = [
2 => 'abc',
3 => 'def',
4 => 'ghi',
5 => 'jkl',
6 => 'mno',
7 => 'pqrs',
8 => 'tuv',
9 => 'wxyz',
];
private $output = [];
/**
* @param String $digits
* @return String[]
*/
function letterCombinations($digits) {
if (strlen($digits) == 0) {
return [];
}
$len = strlen($digits);
$this->combine("", $digits);
return $this->output;
}

function combine($combine, $digits) {
if (strlen($digits) == 0) {
array_push($this->output, $combine);
return;
}

for($i=0;$i<strlen(self::ALPHA[$digits[0]]);$i++) {
$letter = self::ALPHA[$digits[0]][$i];
$this->combine($combine.$letter, substr($digits, 1));
}
}

}

双指针

发表于 2020-02-27 | 分类于 算法 , 双指针

题目一

删除排序数组中的重复项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {

/**
* @param Integer[] $nums
* @return Integer
*/
function removeDuplicates(&$nums) {
$length = count($nums);
$i = 0;
for($j = 1;$j<$length;$j++) {
if ($nums[$j] != $nums[$i]) {
$i++;
$nums[$i] = $nums[$j];
}
}
return $i + 1;
}
}

题目二

删除链表的倒数第N个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Definition for a singly-linked list.
* class ListNode {
* public $val = 0;
* public $next = null;
* function __construct($val) { $this->val = $val; }
* }
*/
class Solution {

/**
* @param ListNode $head
* @param Integer $n
* @return ListNode
*/
function removeNthFromEnd($head, $n) {
$slow = $fast = $head;
while ($n>0) {
$n--;
$fast = $fast->next;
}
while($fast != null && $fast->next != null) {
$fast = $fast->next;
$slow = $slow->next;
}
if ($slow == $head && $fast == null) {
return $head->next;
}
$slow->next = $slow->next->next;
return $head;
}
}

题目三

三数之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {

/**
* @param Integer[] $nums
* @return Integer[][]
*/
function threeSum($nums) {
if (count($nums) < 3) {
return [];
}
$res = [];
sort($nums);
$length = count($nums);

for ($i = 0; $i < $length-2;$i++) {
$start = $i+1;
$end = $length-1;
if ($i>0&&$nums[$i]==$nums[$i-1]) {
continue;
}
while ($start < $end) {
if (($nums[$i] + $nums[$start] + $nums[$end]) == 0) {
$res[] = [$nums[$i], $nums[$start], $nums[$end]];
while($start<$end && $nums[$start] == $nums[$start+1]) {
$start++;
}
while($start<$end && $nums[$end] == $nums[$end-1]) {
$end--;
}
$start++;
$end--;
} elseif (($nums[$i] + $nums[$start] + $nums[$end]) > 0) {
$end--;
} else {
$start++;
}
}
}
return $res;
}

}

二分查找

发表于 2020-02-27 | 分类于 算法 , 二分查找

搞定这三道题,你就搞定了二分查找。

题目一

给定有序(升序)数组nums,和目标值target,查找target在数组中的任一位置(从0开始),找不到返回-1

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Solution {

function search($nums, $target) {
if ($nums == null || count($nums) == 0) {
return -1;
}
$length = count($nums);
$start = 0;
$end = $length-1;
while ($start <= $end) {
$mid = $start + round(($end-$start)/2);
if ($nums[$mid] == $target) {
return $mid;
} elseif ($nums[$mid] > $target) {
$end = $mid - 1;
} else {
$start = $mid + 1;
}
}
return -1;
}
}

题目二

给定有序数组array,和目标值target,查找target在数组中第一个出现的位置(从0开始),找不到返回-1
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
class Solution {

function search($nums, $target) {
if ($nums == null || count($nums) == 0) {
return -1;
}
$length = count($nums);
$start = 0;
$end = $length-1;
while ($start <= $end) {
$mid = $start + round(($end-$start)/2);
if ($mid == 0 && $nums[$mid] == $target) {
return $mid;
} elseif ($nums[$mid] == $target && $nums[$mid-1] != $target) {
return $mid;
} elseif ($nums[$mid] >= $target) {
$end = $mid - 1;
} else {
$start = $mid + 1;
}
}
return -1;
}
}

题目三

搜索旋转排序数组

1
2
3
4
5
6
7
8
9
10
11
12
13
假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
class Solution {
/**
* @param Integer[] $nums
* @param Integer $target
* @return Integer
*/
function search($nums, $target) {
if ($nums == null || count($nums) == 0) {
return -1;
}
$length = count($nums);
$start = 0;
$end = $length-1;
while ($start <= $end) {
$mid = $start + round(($end-$start)/2);
if ($nums[$end] == $target) {
return $end;
}
if ($nums[$start] == $target) {
return $start;
}
if ($nums[$mid] == $target) {
return $mid;
}
if ($nums[$mid] > $nums[$end]) {
if ($nums[$mid] > $target) {
if ($target > $nums[$end]) {
$end = $mid - 1;
} else {
$start = $mid + 1;
}
} else {
$start = $mid + 1;
}
} else {
if ($nums[$mid] > $target) {
$end = $mid - 1;
} else {
if ($target < $nums[$end]) {
$start = $mid + 1;
} else {
$end = $mid - 1;
}
}
}
}
return -1;
}
}

红黑树与B+树的对比

发表于 2020-02-27 | 分类于 数据结构

红黑树和B+树是两种比较重要的高级数据结构。接下来我们来对二者做一个对比分析。

红黑树

1
2
3
4
5
6
7
红黑树的五个性质:
一般的,红黑树(一棵自平衡的排序二叉树),满足以下性质,即只有满足以下性质的树,我们才称之为红黑树:
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。

B+树

1
2
3
4
5
6
7
一个m阶的B+树具有如下几个特征:

1.有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。

2.所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。

3.所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

B树

1
2
3
4
5
6
7
8
9
10
11
一个m阶的B树具有如下几个特征:

1.根结点至少有两个子女。

2.每个中间节点都包含k-1个元素和k个孩子,其中 m/2 <= k <= m

3.每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m

4.所有的叶子结点都位于同一层。

5.每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。

对比分析

数据结构 查询性能 插入性能 删除性能 优点 缺点 适用场景
红黑树 O(logN) O(logN) O(logN) 增删查性能高,相对平衡二叉树简单 树的深度相对B树高 用在内部排序,即全放在内存中的,如:STC的map;hashtable冲突到一定程度链表改为红黑树;epoll监听句柄
平衡二叉树 O(logN) O(logN) O(logN) 增删查性能高 维护平衡的旋转逻辑复杂 暂无
B+树 与树高有关,不超过节点数 同查询 同查询 完美支持磁盘预读 性能相比红黑树差 数据库索引
B-树 与树高有关,不超过节点数 同查询 同查询 支持磁盘预读 节点存储数据,相比B+树占用空间大 数据库索引

参考资料

  1. 红黑树的定义与运用场景
  2. 平衡二叉树、B树、B+树、B*树、LSM树简介

PHP中数组的实现

发表于 2020-02-27 | 分类于 PHP , PHP数组

PHPer们应该都知道,PHP中无所不能的数组是通过Hashtable+双向链表的形式实现的。

那hash和链表到底是怎么一种组织方式呢?下面我们开始细细研究。

hashtable实现及组织方式

php5版本

php7版本

Zend/zend_types.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar _unused,
zend_uchar nIteratorsCount,
zend_uchar _unused2)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};

/*
* HashTable Data Layout
* =====================
*
* +=============================+
* | HT_HASH(ht, ht->nTableMask) |
* | ... |
* | HT_HASH(ht, -1) |
* +-----------------------------+
* ht->arData ---> | Bucket[0] |
* | ... |
* | Bucket[ht->nTableSize-1] |
* +=============================+
*/
阅读全文 »

php如何实现弱类型

发表于 2020-02-26 | 分类于 PHP , PHP变量

PHPer都知道,PHP是一门弱类型语言。所谓弱类型是指,变量在声明时不需要指明存储的数据类型。

1
2
3
4
5
6
<?php   
$var = 1; //int
$var = "laruence"; //string
$var = 1.0002; //float
$var = array(); // array
$var = new Exception('error'); //object;

那么PHP是如何实现这种弱类型变量的呢?

PHP变量的定义

注:zval在PHP7中有所优化

在PHP中,所有的变量都是用一个结构-zval来保存的, 在Zend/zend.h中我们可以看到zval的定义:

1
2
3
4
5
6
typedef struct _zval_struct {   
zvalue_value value;
zend_uint refcount;
zend_uchar type;
zend_uchar is_ref;
} zval;

问题一、Zend引擎是如何用C实现这种弱类型的呢?

其中zvalue_value是真正保存数据的关键部分,现在到了揭晓谜底的时候了,PHP是如何在ZE的基础上实现弱类型的呢? 因为zvalue_value是个联合体(union)。

1
2
3
4
5
6
7
8
9
10
typedef union _zvalue_value {   
lval;
dval;
truct {
*val;
len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;

问题二、Zend引擎是如何判别、存储PHP中的多种数据类型的呢?

_zval_struct.type中存储着一个变量的真正类型,根据type来选择如何获取zvalue_value的值。

1
2
3
4
5
6
7
8
9
10
11
(Zend/zend.h)
#define IS_NULL 0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY 9

来看一个简单的例子:

1
2
3
<?php
$a = 1; //此时zval.type = IS_LONG,那么zval.value就去取lval.
$a = array(); //此时zval.type = IS_ARRAY,那么zval.value就去取ht

参考文献

  1. PHP源码分析-弱类型变量实现
  2. 深入理解PHP7内核之zval
  3. 笔记
123
John Doe

John Doe

23 日志
22 分类
57 标签

Tag Cloud

  • 4991
  • 5021
  • 5041
  • B+树1
  • Binlog1
  • Binlog与Redolog一致性1
  • COW1
  • Cache1
  • HTTP1
  • Hexo1
  • InnoDB1
  • MySQL3
  • MySQL查询1
  • Mysql1
  • Next1
  • Redis数据结构1
  • Redo log1
  • SOA架构1
  • TCP1
  • UDP1
  • WAL1
  • copy on write1
  • fork1
  • http1.11
  • http1.21
  • http协议1
  • keepalive1
  • php1
  • php源码1
  • redis1
  • 一致性1
  • 七层协议栈1
  • 二分查找1
  • 分布式锁1
  • 分库1
  • 分表1
  • 分表扩容1
  • 博客搭建1
  • 双指针1
  • 回溯算法1
  • 容量预估1
  • 弱类型1
  • 微服务架构1
  • 持久化1
  • 数组源码1
  • 架构1
  • 查询过程1
  • 短连接1
  • 系统设计1
  • 红黑树1
  • 网络协议1
  • 负载均衡1
  • 长连接1
  • 面试算法2
  • 高可用1
© 2020 John Doe
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4
本站访问数 本站总访问量