粉丝1550获赞6606

有假发,我自信今天的问题是使用 radas 实现分布式所。所谓分布式所,就是在多个节点都可以访问的位置设置一个标志,任何节点想访问被锁的资源时,首先尝试获取这个标志,如果获取成功,表示获取所成功,如果获取失败,就表示获取所失败。 使用 rays 实现分布式所,就是在准备一个 rays 节点,在 rays 设置一个 p 作为所标志,当 p 存在的时候,就表示资源被锁,如果 p 不存在,就说明资源处于空闲状态。 加锁的过程就是判断 p 是否存在,如果不存在就添加一个。这里要注意的是,判断 p 是否存在和添加 p 的过程必须是原子操作,可以使用 radest 的 settings 方法,资源使用完成再将 p 删除。释放锁,这里同样要注意原子操作,可是如果持有锁的节点 在释放锁之前挡机了,就会导致锁永远无法释放,如何解决呢?解决方案就是在添加踢的时候,要给踢设置一个过期时间,这样的话,即使持有锁的节点挡机了, rades 会自动释放锁。貌似挺完美的, 但是还是有问题,比如我们设置过期时间为二十秒,但是持有锁的节点本身操作时间超过了二十秒,那么就会导致持有锁的节点还没有操作完成之前,锁就被 rades 给释放了。那么我们怎么解决这个问题呢?解决方案就是当某个节点获取到锁之后,立刻启动一个子线城给自己续命。 紫线城的主要任务就是等批的过期时间快到的时候,一般都是还剩三分之一的时间的时候就重新设置批的过期时间为二十秒。当持有锁的节点完成操作之后,显示主动关闭紫线城,然后释放锁。当持有锁的节点档期之后, 续命的紫线城也会停止,所以就自动释放了。这就是使用 radas 实现的分布式所关注假发,每天学习奇怪的知识。

radius 常用的五种数据类型都是基于不同的数据结构实现的,本期介绍实现 list 列表的数据结构, quick list 快速列表。实现 visit 有序集合的数据结构, skip list 跳表。 学习数据结构之前,我们先要知道什么是时间复杂度和空间复杂度。其实这两个是衡量算法效率的指标,用来描述时间和空间增长的趋势。通常我们会使用大 o 表示法来表示。 假设 a 大于 b, 那么 c 加加这行代码就会执行一次,它的时间复杂度就是 o e, 这是一个 for 循环。从 e 到 n 要循环 n 次,所以 c 加加会执行 n 次, 它的时间复杂度就是 o n。 那么一个两层循环,外层循环 n 次,内层也循环 n 次。 c 加加需要执行的次数就是 n 的二次方,所以它的时间复杂度就是 o n 的二次方。 o e o n o n 的二次方。三种是最常见的时间复杂度。与之类似的三种空间复杂度, int a 等于一,它声明了一个固定大小的变量,占用空间的大小不会发生变化,因此它的空间复杂度就是 o e a re 是一个长度为 n 的数组,占用空间的大小会随着 n 的大小改变,所以它的空间复杂度就是 o n。 那如果 array 是一个长度为 n 的二位数组,它的空间复杂度就是 o n 的二次方。除了以上 上三个,还有一些其他的复杂度,比如跳表数据结构,它的平均时间复杂度是 olog n, 这是一个初始化值 i 等于二的破循环, i 乘二的方式递增。如果 n 等于二, c 加加会执行一次,就是 log, 以二为底,二的对数等于一, 如果 n 等于四, c 加加会执行。两次就是 log, 腰为底,四的对数等于二。如果 n 等于八, c 加加会执行。三次就是 log, 腰为底,八的对数等于三, 以此类推,执行的次数就是 log, 以二为底 and 对数,所以它的时间复杂度就是 o log n。 理解了时间复杂度 o log n 的概念,我们趁热打铁开始介绍实现 visit 有序集合的 数据结构。跳表,这是一个有序列表,我们在添加、查询和删除的平均时间复杂度都是 o n, 因为 n 的元素我们使用负循环便利,最多需要执行 n 次才能找到这个元素。 而跳表在这个原始链表的基础上构建了多级锁影,目的就是提高查询效率,将它的添加、查询和删除的平均时间复杂度变成 olog n。 每一层锁影都是一个有序链表,包含了前面一层的部分元素,顶层的锁影元素最少,底层的锁影元素最多。 通常每个节点的元素是否能晋升到上一层有一个随机概率, p 决定。通常 p 的值可以设置为是零点五或者零点二五。在这个案例中,每一层的节点都只能一半,晋升到上一层意味着 p 为零点五。跳表会有一个 header 头结点,这个头结点存在于每一层缩影中。所有查询、删除、添加、操作都是从头结点开始。 我们在每一层都是从当前节点向右移动,遇到小于目标值的节点就将指针推进到下一个节点。遇到大于目标值的节点,就回到当前节点,并锁影向下推进一层。 我们来模拟一下跳表的执行原理。假如我们需要查询元素四,从 header 头节点出发,从二级锁引开始,向右移动,查看后记节点四,节点四等于四。查找结束了。整个过程一共执行了一次比较操作。 假如我们需要查询元素五,从黑的头结点出发,由二级缩音开始,向右移动,查看后 节点四,节点四小于五,将指针推进到节点四。从节点四出发,向右移动查看四的后期,节点是八,节点八大于五,于是回到节点四,所以向下推进一层, 来到四的一级锁影,向右移动,查看四的后记,节点为六,节点六大于五,于是回到节点四,所以又向下推进一层,来到四的原始节点,向右移动,查看四的后记,节点为五,节点五等于五。查找结束。 整个过程一共执行了四次比较操作。假如我们需要查询元素七,从 header 头结点出发,由二级缩音开始,向右移动,查看后极节点四,节点四小于五,将指针推进 到节点四。从节点四出发,向右移动,查看四的后记,节点是八,八大于七,于是回到节点四,所以向下推进一层, 来到四的一级缩影,向右移动,查看四的后极,节点为六,六小于七。指针推进到节点六的位置,从节点六出发,向右移动,查看六的后极节点八,八大于七,于是回到节点六。缩影,向下推进一层, 来到六的原始节点,向右移动,查看六的后期节点为七,节点七等于七。查找结束。整个过程一共执行了五次比较操作,添加和删除操作。查找节点的核心流程也是一样的,在这十个节点中,想要操作某一个节点,需要执行的平均次数是 log, 以二为底,十的对数等于三点三,也就是平均时间,复杂度为 o log n。 quick list 是实现 list 列表的数据结构,它将双向列表和 zip list 压缩列表结合。 zip list 是一种简单紧凑、连续存储的数据结构,由一系列特殊编码的连续内存快组成。 前面的三个是 zip list 的头部信息,最后一个 zealand 是整个 zip list 结束的标识,而中间的 entry 就是 zip list 存储的数据节点。 例如一个类似的列表, a b c, 存储为 h, e 等于 a h, 二等于 b h, 三等于 c, 但是它只适合小数据量场景,如果存 存储的数据量超出了 zip less 的最佳的上限,就会导致效率很低。既然一个 zip less 的放不下,那就多整几个 zip less 的来分片存储数据。看一下这边的演示图,每一个 zip less 的都作为一个节点, 再把它们串联起来,形成一个双向列表。 next 是指向列表中的后一个节点, our preview 是指向列表中的前一个节点。这就是一个 quick list 的基本结构,有多个节点组成,每一个节点就是 quick list node。 其中 next 和 preview 是 quick list node 节点的两个属性。 quick list 也是有两个属性, head 是指项链 表的头部节点, tail 指向链表的尾部节点。通过将链表和 zipless 的结合,既实现了链表的灵活操作,又具备了压缩列表的内存优势。那本期的内容就到这边结束了,我们下期再见。