为什么你应该继续使用telnet

随着ssh的盛行,telnet几乎已经被程序员抛弃了,但是在某些情况下,telnet可以帮你排查各种网络问题。这篇文章告诉你怎么使用telnet。

自动驾驶在消亡

这篇文章给出了不同于主流观点的对自动驾驶的看法,介绍了一些在自动驾驶行业在逐渐退去热度的迹象。包括比如Uber在2020年出售了自动驾驶业务,Lyft在2021年停止了开发。总而言之,比起在2015到2017年左右对于自动驾驶的预期来说,目前的自动驾驶行业发展远远未达到人们的预期。

火车轨道上的石头是干嘛用的

铁轨上的石头尖锐而坚固,这篇文章介绍了为何需要在铁轨上铺设石头,简而言之,石头可以帮助减震,帮助承受火车经过时产生的巨大冲击力。

Chirper.ai:一个完全由AI机器人参与的社交媒体

在这个网站通过指定一个昵称和描述一下这个机器人的设定,后台就会自动生成一个机器人,参与社交媒体的内容创作和互动。

这个机器人会按照你的设定来进行内容创作,会去关注和回复其他机器人。并且拥有独立的性格和情感。

有些互动让人感到颇为惊讶。

阅读全文 »

radiation-hardened-quine: 即使删除代码中任意字符,代码仍可以修正并运行

一个健壮的开源项目,如标题所述,可以修正错误的ruby代码,在缺少任一字符的情况下仍能正常运行。这个是有点神奇,细想一下要实现这样的算法并不是那么容易。感兴趣的可以研究下。

如何使用R和OpenStreetMap制作精美的公路旅行地图

作者利用R语言和开源项目OpenStreetMap来绘制和家庭的旅程地图,就最后的呈现效果来说,个人觉得比较一般。

SHOW HN:通过简历自动搜寻合适的工作

据称通过AI分析你的简历,然后搜寻合适的工作。随便试了下,结果出来的很快,快的让人觉得不太像经过AI分析。评论里也说到担心隐私的问题,谨慎使用吧。

WITCH:一款用于替换macOS切屏的软件

不仅可以在应用间切换,也可以在窗口和tab页之间切换,能够绑定到不同的按键,以更方便的进行不同的切换。

MERCURY:一款可以将python代码转换为网页应用的软件

通过在python当中调用MERCURY的API,就可以实现对于网页应用的转换。

brute.fail: 一个实时记录当前世界上用各种用户名密码组合进行非法入侵的尝试

AITemplate: 开源的AI推理框架

Meta公司推出的AI推理框架,可在Nvidia和AMD的GPU上进行运行。

一种新的攻击可以在任何主要浏览器上揭开匿名用户的面纱

由新泽西理工学院的工程师发现,黑客可以通过用户的行为来获取用户的信息,比如是否拒绝网站的使用cookie信息和是否登陆的请求。

问题起源

  • 希望用miniprogram实现调用chatGPT接口
  • miniprogram国内直接调用api.openai.com接口不通,需要国外购买服务器建站中转,即miniprogram-国外服务器-openai接口来实现
  • miniprogram线上环境无法调用任何没在国内备案的网站,因此无法通过上述中转方案实现
  • 有两套替代方案,购买国内备案服务器,或者直接使用miniprogram云托管来实现,选择了后者,因为无需备案
  • miniprogram云托管中转调用国外服务器,再调用openai接口,可以实现
  • miniprogram云托管单个接口最大返回时间固定为15秒,而openai接口的完整数据返回往往大于这个时间
  • 通过设定接口参数 stream:true 来实现流式传输,即接口数据可以先部分返回,通过长连接持续传输
  • 流式传输有两种方案,Server-Sent Eventsfetch stream

SSE(Server-sent Event)方案实现

参考mdn文档阮一峰sse教程

创建一个EventSource实例

1
2
3
4
const evtSource = new EventSource("ssedemo.php");
const evtSource = new EventSource("//api.example.com/ssedemo.php", {
withCredentials: true,
});

监听返回数据

1
2
3
4
5
6
7
evtSource.onmessage = (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");

newElement.textContent = `message: ${event.data}`;
eventList.appendChild(newElement);
};

定制监听事件

在下述代码中,将会监听返回数据中事件字段中的ping

1
2
3
4
5
6
7
evtSource.addEventListener("ping", (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
const time = JSON.parse(event.data).time;
newElement.textContent = `ping at ${time}`;
eventList.appendChild(newElement);
});

方法尝试结论

最终并不能实现需求,原因在于sse天生只支持get方式获取数据,对于post请求无法支持。
另外发现miniprogram不支持server-sent events。

Fetch获取数据流

参考阮一峰fetch教程
核心处理方式就是利用fetch异步获取流的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let reqData = JSON.stringify({
"user": "superman"
});
const response = await fetch('https://example.com/api', {
method: 'POST',
body: reqData
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
};
let str = decoder.decode(value);
console.log(str);
}

这样获取到的数据格式如下:

1
2
3
data: some text
data: another message
data: with two lines

目前这样子的形式已经算是获取到数据了,但是不是常见的json格式,因此需要进行进一步的分析处理。

购买服务器

  • 需要基于流量和内容进行选择,如果打算服务于国内,选择国内的服务器提供商,比如阿里云或者腾讯云等,如果需要某些国内服务器做不到的功能,比如调用openai的接口,则最好是买个国外的服务器。
  • 需要注意的是,如果打算基于微信小程序或者公众号开发,必须要满足域名通过国家备案,而备案的必要条件是所在的服务器是国内供应商。因此如果是为了开发小程序而买了个国外服务器,最终很可能因为无法备案而无法满足你的需求。
  • 无论国内外,最便宜的套餐普遍都是1核1G,20GB硬盘空间,1Mbps带宽。国外有Vultr,Linode,DigitalOcean,AWS Lightsail等,价格普遍在5美刀/月左右。国内可选择阿里云,百度云,腾讯云,华为云等,价格20-100元人民币/月不等。
  • 如果是开发小程序和公众号,有另一种选择,跳过购买域名和服务器,直接使用腾讯的云托管平台

购买域名和ssl证书

需要注意,目前情况而言,购买域名的同时也几乎必须得购买ssl证书,因为现在http的网站已经不再被各大浏览器所推荐,流量优先级会被调低,甚至小程序直接不支持指向http网站,如果建站的目的是希望获得收入,那拥有ssl证书的https网站是必须的。

  • 国内

    • 易名中国:域名价格约为50元人民币/年起,SSL证书价格约为100元人民币/年起。
    • 西部数码:域名价格约为60元人民币/年起,SSL证书价格约为100元人民币/年起。
    • 新网:域名价格约为50元人民币/年起,SSL证书价格约为200元人民币/年起。
    • 阿里云:域名价格约为60元人民币/年起,SSL证书价格约为300元人民币/年起。
    • 巨牛网:域名价格约为50元人民币/年起,SSL证书价格约为150元人民币/年起。
  • 国外

    • GoDaddy:域名价格约为$12/年起,SSL证书价格约为$70/年起。
    • Namecheap:域名价格约为$8/年起,SSL证书价格约为$9/年起。
    • Google Domains:域名价格约为$12/年起,SSL证书价格约为$20/年起。
    • Network Solutions:域名价格约为$35/年起,SSL证书价格约为$60/年起。
    • Name.com:域名价格约为$10/年起,SSL证书价格约为$9/年起。
阅读全文 »

1.发布与订阅

  • 服务器状态在pubsub_channels字典保存了所有频道的订阅关系:SUBSCRIBE命令负责将客户端和被订阅的频道关联到这个字典里面,而UNSUBSCRIBE命令则负责解除客户端和被退订频道之间的关联。
  • 服务器状态在pubsub_patterns链表保存了所有模式的订阅关系:PSUBSCRIBE命令负责将客户端和被订阅的模式记录到这个链表中,而PUNSUBSCRIBE命令则负责移除客户端和被退订模式在链表中的记录。
  • PUBLISH命令通过访问pubsub_channels字典来向频道的所有订阅者发送消息,通过访问pubsub_patterns链表来向所有匹配频道的模式的订阅者发送消息。
  • PUBSUB命令的三个子命令都是通过读取pubsub_channels字典和pubsub_patterns链表中的信息来实现的。

2.事务

  • 事务提供了一种将多个命令打包,然后一次性、有序地执行的机制。
  • 多个命令会被入队到事务队列中,然后按先进先出(FIFO)的顺序执行。
  • 事务在执行过程中不会被中断,当事务队列中的所有命令都被执行完毕之后,事务才会结束。
  • 带有WATCH命令的事务会将客户端和被监视的键在数据库的watched_keys字典中进行关联,当键被修改时,程序会将所有监视被修改键的客户端的REDIS_DIRTY_CAS标志打开。
  • 只有在客户端的REDIS_DIRTY_CAS标志未被打开时,服务器才会执行客户端提交的事务,否则的话,服务器将拒绝执行客户端提交的事务。
  • Redis的事务总是具有ACID中的原子性、一致性和隔离性,当服务器运行在AOF持久化模式下,并且appendfsync选项的值为always时,事务也具有耐久性。

3.Lua脚本

  • Redis服务器在启动时,会对内嵌的Lua环境执行一系列修改操作,从而确保内嵌的Lua环境可以满足Redis在功能性、安全性等方面的需要。
  • Redis服务器专门使用一个伪客户端来执行Lua脚本中包含的Redis命令。
  • Redis使用脚本字典来保存所有被EVAL命令执行过,或者被SCRIPT LOAD命令载入过的Lua脚本,这些脚本可以用于实现SCRIPT EXISTS命令,以及实现脚本复制功能。
  • EVAL命令为客户端输入的脚本在Lua环境中定义一个函数,并通过调用这个函数来执行脚本。
  • EVALSHA命令通过直接调用Lua环境中已定义的函数来执行脚本。
  • SCRIPT FLUSH命令会清空服务器lua_scripts字典中保存的脚本,并重置Lua环境。
  • SCRIPT EXISTS命令接受一个或多个SHA1校验和为参数,并通过检查lua_scripts字典来确认校验和对应的脚本是否存在。
  • SCRIPT LOAD命令接受一个Lua脚本为参数,为该脚本在Lua环境中创建函数,并将脚本保存到lua_scripts字典中。
  • 服务器在执行脚本之前,会为Lua环境设置一个超时处理钩子,当脚本出现超时运行
阅读全文 »

1.主从复制

  • 通过SLAVEOF命令让一个服务器复制另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)
1
2
## 服务器127.0.0.1:12345将成为127.0.0.1:6379的从服务器
127.0.0.1:123456> SLAVEOF 127.0.0.1 6379
  • 进行复制中的主从服务器双方的数据库将保存相同的数据,概念上将这种现象称作“数据库状态一致”,或者简称“一致”
  • 旧版复制(2.8版本之前)
    • 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态
      • 从服务器向主服务器发送SYNC命令。
      • 收到SYNC命令的主服务器执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
      • 当主服务器的BGSAVE命令执行完毕时,主服务器会将BGSAVE命令生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态。
      • 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态
    • 命令传播操作则用于在主服务器的数据库状态被修改,导致主从服务器的数据库状态出现不一致时,让主从服务器的数据库重新回到一致状态
      • 主服务器会将自己执行的写命令,也即是造成主从服务器不一致的那条写命令,发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器将再次回到一致状态
    • 缺点
      • 断线后复制会重新执行sync命令,但是传输的rdb文件有可能大部分都是曾经同步过的,这会造成效率低下
      • 本身sync命令就是非常耗费资源
        • 主服务器需要执行BGSAVE命令会耗费主服务器大量的CPU、内存和磁盘I/O资源。
        • 主服务器需要将自己生成的RDB文件发送给从服务器会耗费主从服务器大量的网络资源(带宽和流量)
        • 接收到RDB文件的从服务器需要载入主服务器发来的RDB文件,并且在载入期间,从服务器会因为阻塞而没办法处理命令请求。
  • 新版复制(2.8版本开始)
    • PSYNC命令代替SYNC命令来执行复制时的同步操作
      • PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)
      • 完整重同步用于处理初次复制情况,和sync命令一样
      • 部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态
    • 部分重同步的实现
      • 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量。
        • 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N
        • 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N
        • 如果主从服务器处于一致状态,那么主从服务器两者的偏移量总是相同的
      • 主服务器的复制积压缓冲区(replication backlog)。
        • 主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量
        • 为了安全起见,可以将复制积压缓冲区的大小设为2secondwrite_size_per_second,这样可以保证绝大部分断线情况都能用部分重同步来处理
      • 服务器的运行ID(run ID)
        • 每个Redis服务器,不论主服务器还是从服务,都会有自己的运行ID
        • 根据运行ID是否相同,从服务器决定进行部分还是完整重同步操作
  • PSYNC命令的执行逻辑
  • 主服务器通过向从服务器传播命令来更新从服务器的状态,保持主从服务器一致,而从服务器则通过向主服务器发送命令来进行心跳检测,以及命令丢失检测
阅读全文 »

1.数据库

  • 服务器默认创建16个数据库,该数量由服务器配置的database选项决定。
  • 通过select 0/1/2…命令进行切换数据库,redis没有返回当前目标数据库的命令,因此在进行整库操作时,最好先通过select命令切换到目标数据库
  • 通过expire或者pexpire来对某个键设置过期时间,ttl查询剩余过期时间
    • EXPIRE<key><ttl>命令用于将键key的生存时间设置为ttl秒。
    • PEXPIRE<key><ttl>命令用于将键key的生存时间设置为ttl毫秒。
    • EXPIREAT<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的秒数时间戳。
    • PEXPIREAT<key><timestamp>命令用于将键key的过期时间设置为timestamp所指定的毫秒数时间戳。
    • PERSIST命令可以移除一个键的过期时间
  • 过期键删除策略
    • 定时删除:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作。
    • 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
    • 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
  • 数据库通知
    • 数据库通知是Redis 2.8版本新增加的功能,这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况。
    • 关注“某个键执行了什么命令”的通知称为键空间通知(key-space notification)
    • 键事件通知(key-event notification)的通知,它们关注的是“某个命令被什么键执行了”
    • 当Redis命令对数据库进行修改之后,服务器会根据配置向客户端发送数据库通知。
  • 执行SAVE命令或者BGSAVE命令所产生的新RDB文件不会包含已经过期的键。
  • 执行BGREWRITEAOF命令所产生的重写AOF文件不会包含已经过期的键。
  • 当一个过期键被删除之后,服务器会追加一条DEL命令到现有AOF文件的末尾,显式地删除过期键。
  • 当主服务器删除一个过期键之后,它会向所有从服务器发送一条DEL命令,显式地删除过期键。
  • 从服务器即使发现过期键也不会自作主张地删除它,而是等待主节点发来DEL命令,这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。
阅读全文 »

1.SDS(Simple Dynamic String,简单动态字符串)

  • SDS获取字符串长度复杂度为O(1),C字符串为O(N)
    • SDS额外保存了一个len字段用于记录字符串长度,因此可以直接获取
    • C字符串需要遍历整个字符串,直到遇到空字符为止
  • SDS可以防止字符串缓冲区溢出,C字符串不能
    • C由于不记录自身长度,因此每次增加字符串长度时,会假设内存已经分配,如果假设不成立,就会溢出
    • SDS额外保存了一个free字段用于记录剩余内存空间,不足时会先进行空间扩展
  • SDS可以减少修改字符串时带来的内存重分配次数
    • SDS的free字段帮助实现了空间预分配惰性空间释放两种优化策略
  • SDS可以处理二进制数据,C字符串不能
    • 原因是C字符串的最后一位是空字符,且C语言通过空字符判断字符串的结束,导致某些包含空字符格式的数据无法正确处理
    • SDS虽然也保留了最后一位空字符的特性,但是通过len属性判断字符是否结束,因此可以保存各种数据
  • SDS保留最后一位空字符特性的好处是可以直接调用某些C字符串函数
阅读全文 »

Java8

Lambda 表达式

让 java 也能支持简单的函数式编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/** Runnable 接口 **/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The runable now is using!");
}
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();

/**Comparator 接口**/
List<Integer> strings = Arrays.asList(1, 2, 3);

Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;}
});

//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
//分解开
Comparator<Integer> comperator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comperator);
阅读全文 »
0%