Luyu Huang's Tech Blog

用树莓派搭建一个能在外网访问的 NAS

我最近买了一个树莓派, 想用它搭建一个 NAS. 对此我有几点要求: 能在外网访问 启用 HTTPS 内外网使用相同的访问方式, 无缝切换 我准备使用 NextCloud, 因为它支持 WebDAV, 这样可以作为 Joplin 的同步服务器. 我之前在外网 VPS 上搭建过一个 NextCloud, 现在我准备把它迁移到我的树莓派上, 感觉数据还是放在自己身边更可靠些. 我购买的是树莓派 4B, 内存 4GB, 32GB 空白存储卡; 再加上我 2 TB 的西部数据移动硬盘作为数据盘. 这篇文章记录我是怎么做的. 安装操作系统 操作系统的安装比较简单. 我安装的是 Ubuntu Server 20.04. Ubuntu 对树莓派的支持还是非常好的, 官网...

Read more

Jump Consistent Hash 算法

这篇文章我们讨论 Jump Consistent Hash 算法, 一个极简且高效的一致性哈希算法. 哈希与一致性哈希 哈希函数, 或者说散列函数, 能将一个较大定义域中的元素映射到一个较小的有限值域中. 值域中的元素有时也称为桶 (bucket), 值域的大小亦称为桶的数量. MD5 就是一种常用的哈希函数, 它能将任意大小的数据 (较大的定义域) 映射成一个 16 字节的哈希值 (较小的有限值域). 最简单的哈希函数就是取模, 它能将全体整数 (较大的定义域) 映射到一个整数区间 (较小的有限值域) 中. 假设我们的服务器有 3 个工作进程, 同时为用户提供服务. 这些工作进程的功能是相同的, 并且会保存用户的状态数据. 那么如何决定一个用户由哪个工作进程提供服务呢? 最简...

Read more

ZooKeeper 入门教程

ZooKeeper 是一个分布式服务中间件, 乍一看有点像一个 NoSQL 数据库系统. 不过它的主要功能不是存储数据, 而是提供一种共享数据和服务间通信的方式, 使用它我们能够更方便地开发分布式软件. 这篇文章介绍 ZooKeeper 的主要特性, 使用方式和应用场景. 主要特性 我们先来看一下 ZooKeeper 的主要特性, 以及它跟 NoSQL 数据库系统不同的地方. 数据存储 不同于 Redis 的 key-value 结构, ZooKeeper 将所有的数据管理在一个树状结构中. 这个结构很像文件系统, 一个路径标识一个节点, 由若干个用斜杠隔开的名字组成. 根结点路径为 /, 因此路径总是由斜杠开头. 与文件系统不同的是, ZooKeeper 中叶子结点和内...

Read more

并不简单的二分查找

二分查找是一个很经典的入门算法, 我们每个人都学过. 然而它往往没有我们没有想象的那么简单, 它有很多容易出错的细节: 用 < 还是 <= ? 是 right = mid 还是 right = mid - 1 ? 是用 mid = (right + left) / 2 还是 mid = left + (right - left) / 2 ? 如何使用二分查找找出左边界和右边界? 等等等等. 这篇文章我们就来搞清楚这些问题. 基本思路 二分查找的基本思路很简单: 在一个升序排列的数组中找到目标值, 首先检查数组正中间的数, 如果它比目标数大, 那么目标数一定位于数组的前半部分, 否则位于数组的后半部分; 然后在前半部分或者后半部分中执行同样的查找操作, 直到找到目标数. ...

Read more

经典动态规划问题

我最近温习了一下动态规划, 发现有些问题解法的代码十分相似, 但思路却大相径庭, 非常容易混淆. 这里总结一下. 这涉及到几个经典的动态规划问题: 0-1 背包问题, 完全背包问题和爬楼梯问题. 题目源自 LeetCode 416 题, 518 题, 377 题 和 70 题. 我们先来来看问题和它们的解法, 你会发现这些解法十分相似. 接着我们再来逐步分析其中的思路. 不同的问题, 相似的解法 分割等和子集 给定一个只包含正整数的非空数组, 问是否可以将这个数组分割成两个子集, 使得两个子集的元素和相等. 程序输入一个整数数组 nums, 输出一个布尔值. 这个问题可以转换成: 是否可以从数组中选出一些数, 使它们的和等于全部元素之和的一半. 这是一个 0-1 背包问题,...

Read more

自动生成 Lua 热更新代码

游戏服务器使用 Lua 的一个重要原因是 Lua 便于热更. 即使服务器正在运行, 只需让它执行一段代码, 即可重写其中的某些函数, 达到热更新的目的. 例如模块 app 有一个函数 foo local M = {} function M.foo(a, b) return a + b end return M 如果我们要将 foo 热更成将 a 和 b 相乘, 只需要让服务器加载运行如下代码即可: local M = require("app") function M.foo(a, b) return a * b end 不过很多时候, 函数并不是这么单纯. 函数常常会依赖许多上值 (upvalue), 举一个复杂点的例子: local databas...

Read more

由斜杠划分的区域

一月份 Leetcode 的每日一题几乎都是并查集. 不过个人认为与状态转移方程千变万化的动态规划相比, 并查集还是相对比较简单的. 这道题是我觉得最有趣的两道之一 (另一道是打砖块, 以后有时间的话也写一篇它的题解). 题目源自 Leetcode 959 题 在由 1 x 1 方格组成的 N x N 网格 grid 中,每个 1 x 1 方块由 /、\ 或空格构成。这些字符会将方块划分为一些共边的区域。 (请注意,反斜杠字符是转义的,因此 \ 用 "\\" 表示。) 返回区域的数目。 示例 1: 输入: [   " /",   "/ " ] 输出:2 解释:2x2 网格如下: 示例 2: 输入: [   " /...

Read more

Go 设置 socket 端口复用

我们知道, 一般来说, TCP/UDP 的端口只能绑定在一个套接字上. 当我们尝试监听一个已经被其他进程监听的端口时, bind 调用就会失败, errno 置为 98 EADDRINUSE. 也就是所谓的端口占用. int fd1 = socket(AF_INET, SOCK_DGRAM, 0); int fd2 = socket(AF_INET, SOCK_DGRAM, 0); struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons(1234); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); bind(fd1, (struct...

Read more