细节讲解并实操下: 去中心化社交协议 ---- Nostr
作者:林冠宏 / 指尖下的幽灵。转载者,请: 务必标明出处。
GitHub : https://github.com/af913337456/
出版的书籍:
要了解 Nostr,得从头开始
目录
- 服务的演变
- Nostr 的服务形态
- Relay 的局限性
- Nostr 与区块链的关系
- Nostr 协议内容选集
- 账户部分
- 数据操作部分
- Client 发布一个事件
- tags 参数使用例子
- Client 发订阅请求
- Client 取消订阅
- Client 发授权请求
- Relay 返回的命令
- 实操及观察
- 部署 Relay 服务
- 使用 Client 与 Relay 交互
- 查看数据
- kind 附录
- 鸣谢
其实 Nostr 的整体思想并不算新颖,提出的时间也是好几年前了。最近这年由于 web3 应用概念
的火爆导致它被挖出来并广为人知。
三年前的基于 Golang 的 client ,noscl
服务的演变 [可跳过
]
我们固有的 C/S 服务思维
是 client 发请求,server 相应结果。
当简简单单的时候,就是如图下面的情况:
特征:低处理量,低可用,数据高内聚
当用户量越来越多,产品做大了的情况,server 就是一个集群,里面有各种小 server。
这种时候的集群服务所有权是集中的,集中于一个组织或多个组织。不够去中心化。
特征:高处理量,高可用,高耦合,数据可分布式存储,仍然中心化
再后面,区块链出现了,此时的服务要走去中心化的线路,服务不再叫做服务,而是节点
,说法虽然不一样了,但本质还是提供数据处理、存储、整理返回
给 client 的功能。如下图:
这是一种更加复杂的 C/S 通讯模式:
- Client 可以与任一个 Node 通讯;
- 区块链节点 Node 分布在各地,谁都部署一个 Node Server 然后加入到节点网络;
- Node 节点相互 P2P 通讯;
- 整个拓扑比例:N:Y:Z
特征:区块链特征
Nostr 的服务形态 [核心]
如果你看了上面 服务的演变
小节,可以思考下,服务的架构的大方向还可以怎么变?我们把区块链节点的的服务图做一些修改变成下面这图:
去掉 Node 之间的 P2P 通讯。变成:
- Client 可以与 单个 或 多个 Relay 通讯;
- Client 各自独立,不能相互通讯;
- Relay 有多个,且分布在不同地方;
- Relay 之间没任何通讯,各自独立;
这就是 Nostr 协议所描述的服务框架。在 Nostr 中,没有了 server 的称呼,变成了 Relay。就像区块链一样,把 server 变成了 node。
产生了一个核心问题:
数据如何同步?比如 Client-A 如何看到 Client-B 发的信息/消息?
对于这个问题,如果 Relay 之间有 P2P 通讯,那么就像区块链节点那样,数据相互同步便解决了。但 Nostr 所描述的做法却是这样的:
- Client-A 给 Relay-A 发数据,Relay-A 存储在本地;
- Client-B 给 Relay-B 发数据,Relay-B 存储在本地;
- 因为要交流,否则就是玩
单机
,于是乎,A 或 B 两个用户,就会:- 在某些中心化社交平台暴露自己的 Relay 的访问 Url;
- 用某其他软件私聊互发 Relay 的 Url;
- 在 3 的基础上,Client-A 或 Client-B 就可以在 Nostr 协议所实现的 Client 端软件上加载到对方 Relay 存储的数据,也就达到了看见/交流的目的。
Relay 的局限性
用户要使用 Nostr 应用,得满足2个条件:
- 下载 Nostr 客户端软件,这个已有,网上可搜;
- 订阅 Relay,这个可以:
- 自己买个服务器,找份 Relay 代码,编译个可执行文件,启动服务,然后连接进来,就可以发数据到它存起来。然后再去找其他朋友的 Relay 链接;
- 去网上找公开的 Relay 链接,订阅进去,从而可以发数据,看数据;
为了解决这些问题,现在 Nostr 衍生出的产品中就有公共 Relay
,比如:snort.social,这些 Relay 提供注册功能
,所谓的注册就是在他的网站生成密钥对
。然后如果用户没钱、没技术自己搭建 Relay 服务器,就可以订阅它的 Relay。
这样的话,所有订阅者都可以在网站页面展示出来,让后来的人看到,等于直接看到其他用户,在这些用户中找好友来进行第一次的谈话。
但是如果没有自己的 Relay 的话,数据其实变相存储在别人的服务器。
技术实现方面,这里要注意一个点:
某一 Client 所有订阅了的 Relay,在 Client 发数据的时候,需要给所有订阅了的 Relay 都发。并非强制,但协议要求。
Nostr 与区块链的关系
Nostr 与区块链关系不大
。
- Nostr 不是某公链的 DApp;
- Nostr 没涉及到智能合约;
- Nostr 没涉及链上请求;
- Nostr 在用户账户部分用了和 BTC 一样的公私钥生成算法;
- Nostr 和其他区块链的 DApp 拥有一样的 web3 概念
Nostr 协议内容选集
Nostr 协议就像 Http 协议一样,制定了 C/S 的通讯形式。可以使用任何编程语言去实现。下面我将选择几个有代表性
的部分来讲解下,最好是去看协议文档。
官方文档:nostr-protocol
账户部分
Nostr 的客户账号,不需要依赖 Relay,可以在 Client 本地直接生成。就是 BTC
的钱包。
- 私钥充当了密码;
- 公钥充当了账号
比如这段代码就是生成个 Nostr 客户端账户,和 BTC 的钱包生成
一样:
// 完整的,见文章头部 git 项目
// 从私钥获取公钥
func getPubKey(privateKey string) string {
keyb, _ := hex.DecodeString(privateKey)
_, pubkey := btcec.PrivKeyFromBytes(keyb)
return hex.EncodeToString(schnorr.SerializePubKey(pubkey))
}
func keyGen(opts docopt.Opts) {
seedWords, _ := nip06.GenerateSeedWords() // 助记词
seed := nip06.SeedFromWords(seedWords)
sk, _ := nip06.PrivateKeyFromSeed(seed) // 私钥
fmt.Println("seed:", seedWords)
fmt.Println("private key:", sk)
fmt.Println("pubkey:", getPubKey(sk)) // 公钥
}
上面代码运行结果:
seed: arrow suspect reunion hire project damp protect comic leopard market repair diet delay direct bid mountain rigid sister moral speed cloud dawn rain vanish
private key: 3e6d9287d017b5ca1a1219b9d403d172f5ee2df74e112e7d890b070939d1fdb4
pubkey: cb6fd58aa73d01f4e7f803ae41f80caabe2d68288f19a231a6e57571db6a1eb4
数据操作部分
- client 和 relay 采用 websocket 的协议传输数据;
- 数据格式是 Json;
- 标准格式是:
[命令,参数,参数...]
Client 发布一个事件
发布命令:["EVENT", <event JSON>]
要注意,事件具体要完成什么动作,完全看里面的参数 kind
,见下面的kind 附录
小节,可见 kind = 1
的时候,代表发送的是短文,等于发文字帖子。
["EVENT",
{
"id": "21e1b711fa6a9741ab7d134d2ea5a2e6ac6c75751386b411c46438118a4c0dd4",
"pubkey": "86ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3a3566e01eccf9a05ffa4",
"created_at": 1679112916,
"kind": 1, // 发文字帖子
"tags": [
["e",<event_id>,<other_1>,<other_2>],
["p",<pubkey>],
....
],
"content": "9988cc",
"sig": "883af7863a63d55c207e272707894944ee763810cd0393d344f85dc9a0bc624c4cc1b28ca483008729b56602f4acaad22e084c2b44db02ecc11e238880b8fd62"
}
]
id
就是当前请求的 id,生成方式是对 event 整体内容进行sha256.Sum256
计算得到,这些都可以在文档中得知;pubkey
就是发送者 sender;sig
就是整个 event 的签名,防止内容被篡改,在 Relay 端,会对接收到的 event 验签;kind
直接理解为 event 的类型,告诉 Relay 要达到什么目的;tags
纯粹的标签数组,为了携带辅助参数来实现功能而设立,下面我列举两个场景来说明这个参数的灵活使用,要注意,这个参数要实现什么功能,是没固定说法的,思维在这里要灵活
。
tags 参数使用例子
- 场景:
发布内容引用到其他内容的时候
。可以在 tags 中的 e 标签数组内添加其他 event 的 id; - 场景:
删除自己所发布的 event 的时候
。可以在 tags 的 e 标签中添加想要被删除的 eventId; - 场景:
发私信
。私信的kind 是 4
,此时 content 是加密的,只有接收方能解密,此时 tags 的 p 标签中,就是要接收私信人的 pubkey
Client 发订阅请求
订阅操作的完成可以达成两个目的:
- 只要 websocket 不关闭,只要 Relay 有新的 event 接收到,就会推送到 Client;
- 订阅开始的时候,Relay 会对当前的订阅请求返回一次目标 event 数据;
订阅命令:["REQ", <subscription_id>, <filters JSON>]
[ "REQ",
"b326655084f5f1", // 一次性随机生成的请求 id
{
"ids": ["ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3"],
"authors": ["86ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3a3566e01eccf9a05ffa4"],
"kinds": [4], // 标明只加载私信的
"#e": ["9c0c22f940bc5e8bc397206a3a3566e01eccf"], // 同时引用了这个推文
"#p": ["86ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3a3566e01eccf9a05ffa4"],
"since": 1679112916,
"until": 1679112996,
"limit": 7
}
]
可以看到,REQ 订阅操作的参数和 EVENT 的大同小异,一样具有很强的灵活性。查询的时候所有参数取且操作
。
ids
,指定基于 event 的变化,不指定就是全量订阅;authors
,要订阅这个作者发的,不指定就没这个限制;
Client 取消订阅
对应到订阅,自然有取消订阅,取消后,Relay 不再推送 event 数据。
取消订阅命令:["CLOSE", <subscription_id>]
此 subscription_id
就是订阅时候的那个 id。
Client 发授权请求
授权请求在 Nostr 中是个更加高度自定义的动作,根据协议描述,这个请求用于拓展 Relay 的一些主观功能。
首先我们知道,Relay 在整个 Nostr 网络中是会存在很多的,那么就不排除有一些 Relay 代表了一些组织,它们可能只向特定的用户开放,比如说你要接入就要付费
或者收到邀请码
等前提。
Relay 如何实现这些限制呢?答案就是使用 AUTH。
授权命令:["AUTH", <signed-event-json>]
["AUTH", {
"id": "...",
"pubkey": "...",
"created_at": 1669695536,
"kind": 22242, // 固定,必须是 22242
"tags": [
["relay", "wss://relay.example.com/"], // 目标 relay url,会做校验
["challenge", "challengestringhere"] // relay 返回再放进来
],
"content": "",
"sig": "..."
}]
下面我将使用时序图
来说明 Client 和 Relay 是如何进行授权动作的。
- Client 在和 Relay 建立链接的时候,Relay 如果实现了 AUTH 功能,就需要给 Client 返回个
challenge code
; - Client 拿到
challenge code
后得知此 Relay 是要授权接入的,否则一些功能无法使用; - Client 打包 AUTH 请求命令,把发出;
- Relay 验证
challenge code
和其他信息,并存储相关数据;
整个 AUTH 的流程是比较简单的。最核心的信息是 challenge code
,完全可以理解为验证码
,具体怎么去实现这一部分,完全可以由 Relay 自定义
。我上面举的例子是现在 demo 源码的做法,现实中是可以但不限于
下面的拓展:
- 用户在 A 网站购买
challenge code
,再去输入; challenge code
只能用一次,且 10 分钟有效。
challenge code 的验证代码如下,所关联的参数一目了然:
// ValidateAuthEvent checks whether event is a valid NIP-42 event for given challenge and relayURL.
// The result of the validation is encoded in the ok bool.
func ValidateAuthEvent(event *nostr.Event, challenge string, relayURL string) (pubkey string, ok bool) {
if event.Kind != 22242 {return "", false}
if event.Tags.GetFirst([]string{"challenge", challenge}) == nil {
return "", false
}
expected, err := parseUrl(relayURL)
if err != nil {return "", false}
found, err := parseUrl(event.Tags.GetFirst([]string{"relay", ""}).Value())
if err != nil {return "", false}
if expected.Scheme != found.Scheme ||
expected.Host != found.Host ||
expected.Path != found.Path {
return "", false
}
now := time.Now()
if event.CreatedAt.After(now.Add(10*time.Minute)) || event.CreatedAt.Before(now.Add(-10*time.Minute)) {
return "", false
}
if ok, _ := event.CheckSignature(); !ok {return "", false}
return event.PubKey, true
}
Relay 返回的命令
- 订阅之后,给 Client 推送数据。
["EVENT", <subscription_id>, <event JSON>]
,id 就是订阅时发过来的 id; - 给 Client 的请求报错或提示。
["NOTICE", <message>]
; - 告知 Client 当前 Relay 所有存储的 event 都已经返回。
["EOSE", <subscription_id>]
; - 告知 Client,此 Relay 需要授权,并返回 challenge_code。
["AUTH", <challenge-string>]
- 告知 Client 某事件的结果,成功或失败。
["OK", <event_id>, <true|false>, <message>]
NOTICE
和 OK
的区别是:
- NOTICE 多用于请求并未完成,比如参数结构、缺少类的错误
- OK 也会用于返回错误,但此时请求已经完成,出错在最终结果。比如验签、授权。
例子:
// 验证算法:https://github.com/nostr-protocol/nips/blob/master/42.md
// 主要就是验证 client 的 challenge 是否是 relay 返回的。这个 relay 的实现是随机搞的 challenge
// 拓展的做法就是可以根据更丰富的算法生成 challenge 再返回。比如与 pubkey、时间加密后返回一个
// 特定的,在多少天内有效的,只能这个 pubkey 访问的 challenge 码
if pubkey, ok := nip42.ValidateAuthEvent(&evt, ws.challenge, auther.ServiceURL()); ok {
ws.authed = pubkey
ws.WriteJSON([]interface{}{"OK", evt.ID, true, "authentication success"})
} else {
ws.WriteJSON([]interface{}{"OK", evt.ID, false, "error: failed to authenticate"})
}
实操及观察
这里我将基于 Golang 实现的 Relay 和 Client 来做一下 Nostr 的简单交互演示。具体的项目见文头的项目。
部署 Relay 服务
Relay 项目地址: https://github.com/fiatjaf/relayer
进入到 basic 目录,将 main.go 文件的内容改成下面的:
package main
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/fiatjaf/relayer"
"github.com/fiatjaf/relayer/storage/postgresql"
"github.com/fiatjaf/relayer/storage/sqlite3"
"github.com/kelseyhightower/envconfig"
"github.com/nbd-wtf/go-nostr"
)
type SqliteRelay struct {
SQLiteDatabase string `envconfig:"SQLITE_DATABASE"`
storage *sqlite3.SQLite3Backend
}
func (r *SqliteRelay) Name() string {
return "SQLiteRelay"
}
func (r *SqliteRelay) Storage() relayer.Storage {
return r.storage
}
func (r *SqliteRelay) OnInitialized(*relayer.Server) {}
func (r *SqliteRelay) Init() error {
return nil
}
func (r *SqliteRelay) AcceptEvent(evt *nostr.Event) bool {
jsonb, _ := json.Marshal(evt)
if len(jsonb) > 10000 {
return false
}
return true
}
func main() {
runSQLiteRelay()
}
func runSQLiteRelay() {
r := SqliteRelay{}
if err := envconfig.Process("", &r); err != nil {
log.Fatalf("failed to read from env: %v", err)
return
}
r.storage = &sqlite3.SQLite3Backend{DatabaseURL: "jdbc:sqlite:identifier.sqlite"}
if err := relayer.StartConf(relayer.Settings{
Host: "127.0.0.1",
Port: "8888",
}, &r); err != nil {
log.Fatalf("server terminated: %v", err)
}
}
上面代码启动后就会启动本地的 Relay 服务,RelayUrl:http://127.0.0.1:8888
,它使用 sqlite3 数据库来存储数据,sqlite3 是库里面自己支持了的。如果不使用这个,需要自己实现其他数据库的版本,根据接口函数来实现即可,难度并不大。
使用 Client 与 Relay 交互
Client 可以使用下面简单的例子,直接进行测试通讯。完成发送 event 和 req 订阅命令的功能。
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/nbd-wtf/go-nostr"
)
func main() {
ctx := context.Background()
pubkey := "86ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3a3566e01eccf9a05ffa4"
privateKey := "8d137174f49cce2590d3a30f89a9dd9865b319ef3a94e24b37cfa106ff85259c"
// 加载并订阅
// sendREQ(ctx, pubkey, privateKey)
// 发文字贴文
sendEVENT(ctx, pubkey, privateKey, "hello world")
}
func newRelay(ctx context.Context) *nostr.Relay {
relay, err := nostr.RelayConnect(ctx, "http://127.0.0.1:8888")
if err != nil {
panic(fmt.Errorf("failed to connect to relay: %w", err))
}
return relay
}
func sendREQ(ctx context.Context, pubkey, privateKey string) {
filter := nostr.Filter{
IDs: nil,
Kinds: []int{nostr.KindTextNote}, // 只订阅短文
Authors: []string{pubkey}, // 只订阅这个作者发的
Tags: nil,
Since: nil,
Until: nil,
Limit: 100,
}
req := newRelay(ctx).PrepareSubscription()
req.Sub(ctx, nostr.Filters{filter})
// 在这里接受所有的返回并打印
for event := range req.Events { // Events 会在 unsub 函数内被关闭
bys, _ := event.MarshalJSON()
fmt.Println("receive event:-------", string(bys))
}
}
func sendEVENT(ctx context.Context, pubkey, privateKey, content string) {
helloTxtEvent := nostr.Event{
ID: "", // signEventAndCalculateID 中赋值
PubKey: pubkey,
CreatedAt: time.Now(),
Kind: nostr.KindTextNote, // 1 短文
Tags: nil, // 我们没其他的功能,这里 tag 留空
Content: content,
Sig: "", // signEventAndCalculateID 中赋值
}
if err := signEventAndCalculateID(&helloTxtEvent, privateKey); err != nil {
panic(fmt.Errorf("signEventAndCalculateID err: %w", err))
}
if sendStatus, err := newRelay(ctx).Publish(ctx, helloTxtEvent); err != nil {
panic(fmt.Errorf("publish err: %w", err))
} else {
bys, _ := json.Marshal(helloTxtEvent)
fmt.Println(fmt.Sprintf("send event status [%s] event data: %s", sendStatus, string(bys)))
}
}
func signEventAndCalculateID(evt *nostr.Event, privateKey string) error {
h := sha256.Sum256(evt.Serialize())
s, err := hex.DecodeString(privateKey)
if err != nil {
return fmt.Errorf("Sign called with invalid private key '%s': %w", privateKey, err)
}
sk, _ := btcec.PrivKeyFromBytes(s)
sig, err := schnorr.Sign(sk, h[:])
if err != nil {
return err
}
evt.ID = hex.EncodeToString(h[:]) // id 赋值
evt.Sig = hex.EncodeToString(sig.Serialize()) // 生成签名信息
return nil
}
查看数据
执行上面 Client 代码 main 函数中的 sendEVENT
可以在控制台看到发送成功:
send event status [success] event data: {"id":"d80f68baee57f2cbb231f44df27ddbf22728b9649083386a44f01cdf159b398a","pubkey":"86ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3a3566e01eccf9a05ffa4","created_at":1679144385,"kind":1,"tags":[],"content":"hello world","sig":"fdb4570c73cfded2c8a29b8ca926a5a400b81246b676f5090651949806f3741c40d2df8542c70f6dbdbff4ada3123cb285fcdcb0fdf499b2bd41a97ed5f4f3bb"}
再执行 sendREQ
函数,进行订阅可以前面发送过的数据:
receive event:------- {"id":"d80f68baee57f2cbb231f44df27ddbf22728b9649083386a44f01cdf159b398a","pubkey":"86ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3a3566e01eccf9a05ffa4","created_at":1679144385,"kind":1,"tags":[],"content":"hello world","sig":"fdb4570c73cfded2c8a29b8ca926a5a400b81246b676f5090651949806f3741c40d2df8542c70f6dbdbff4ada3123cb285fcdcb0fdf499b2bd41a97ed5f4f3bb"}
receive event:------- {"id":"c0965330b8f703cdb24916f8c74e54264ecdfebe098e79c7bec0e5586bd1ec9a","pubkey":"86ab84ff172fa8104bf9c0c22f940bc5e8bc397206a3a3566e01eccf9a05ffa4","created_at":1679143318,"kind":1,"tags":[],"content":"hello world","sig":"3d4636b45fff22a984b9cd5b5e56fec4fcc065455a2c049441f67512d1c7a6d78608dfd8a53a0fd6540ec63c3cde1d0553c8471811ac7068042116ede258754c"}
除此之外,还可以直接在 sqlite3 中查看表格看到数据。
至此,我们尝试了整个基于 Nostr 协议的完整应用。
kind 附录
kind | description | NIP |
---|---|---|
0 | Metadata | 1 |
1 | Short Text Note | 1 |
2 | Recommend Relay | 1 |
3 | Contacts | 2 |
4 | Encrypted Direct Messages | 4 |
5 | Event Deletion | 9 |
7 | Reaction | 25 |
8 | Badge Award | 58 |
40 | Channel Creation | 28 |
41 | Channel Metadata | 28 |
42 | Channel Message | 28 |
43 | Channel Hide Message | 28 |
44 | Channel Mute User | 28 |
1984 | Reporting | 56 |
9734 | Zap Request | 57 |
9735 | Zap | 57 |
10000 | Mute List | 51 |
10001 | Pin List | 51 |
10002 | Relay List Metadata | 65 |
22242 | Client Authentication | 42 |
24133 | Nostr Connect | 46 |
30000 | Categorized People List | 51 |
30001 | Categorized Bookmark List | 51 |
30008 | Profile Badges | 58 |
30009 | Badge Definition | 58 |
30023 | Long-form Content | 23 |
30078 | Application-specific Data | 78 |
1000-9999 | Regular Events | 16 |
10000-19999 | Replaceable Events | 16 |
20000-29999 | Ephemeral Events | 16 |
30000-39999 | Parameterized Replaceable Events | 33 |
鸣谢
本文一些内容由下面小程xu搜索提供帮助。
细节讲解并实操下: 去中心化社交协议 ---- Nostr的更多相关文章
- 基于NOSTR协议的“公有制”版本的Twitter,去中心化社交软件Damus用后感,一个极端走向另一个极端
最近,一个幽灵,Web3的幽灵,在网络游荡,它叫Damus,这玩意诠释了什么叫做病毒式营销,滑稽的是,一个Web3产品却在Web2的产品链上疯狂传销,各方大佬纷纷为其背书,到底发生了什么?Damus的 ...
- 去中心化存储项目终极指南 | Filecoin, Storj 和 PPIO 项目技术对比(下)
在上篇文章中,我们主要从价值定位.技术层次架构.服务质量.去中心化程度,和经济激励机制五个方面分析了三个项目的不同.在这一篇文章中,我们将着重从区块链的架构设计.数据传输技术设计和数据存储技术设计三方 ...
- 小众Tox——大众的“去中心化”聊天软件
★Tox是什么 一个反窥探的开源项目:一种基于DHT(BitTorrent)技术的即时通讯协议:一个为安全而生的加密通讯系统 .美国棱镜计划曝光后,一个名为 irungentoo 的牛人于17天后的2 ...
- 比特股-去中心化交易所, STEEM - 去中心化社区, EOS - 下一代智能合约
libsnark 是实现了 zkSNARK 模式的 C++ 库.zkSNARK 是一个证明/验证计算完整性的加密方法,也即零知识验证的算法, https://github.com/scipr-lab/ ...
- IM 去中心化概念模型与架构设计
今天打算写写关于 IM 去中心化涉及的架构模型变化和设计思路,去中心化的概念就是说用户的访问不是集中在一个数据中心,这里的去中心是针对数据中心而言的. 站在这个角度而言,实际上并非所有的业务都能做去中 ...
- 一个轻client,多语言支持,去中心化,自己主动负载,可扩展的实时数据写服务的实现方案讨论
背景 背景是设计一个实时数据接入的模块,负责接收client的实时数据写入(如日志流,点击流),数据支持直接下沉到HBase上(兴许提供HBase上的查询),或先持久化到Kafka里.方便兴许进行一些 ...
- 一步步教你开发、部署第一个去中心化应用(Dapp) - 宠物商店
今天我们来编写一个完整的去中心化(区块链)应用(Dapps), 本文可以和编写智能合约结合起来看. 写在前面 阅读本文前,你应该对以太坊.智能合约有所了解,如果你还不了解,建议你先看以太坊是什么除此之 ...
- ImCash:币安下架BSV之辩:规则、中立与去中心化
一种看法是:一个引用价格数据和执行交易的加密货币交易所,其业务决策往往是在链外发生的,不受制于严格的.类似于准宪法的链上规则的约束,加密货币交易所可以拒绝任何人喜欢的价格和交易,而且这样做并不会损害底 ...
- 理解去中心化 稳定币 DAI
本文转载于深入浅出区块链, 原文链接 随着摩根大通推出JPM Coin 稳定币,可以预见稳定币将成为区块链落地的一大助推器. 坦白来讲,对于一个程序员的我来讲(不懂一点专业经济和金融),理解DAI的机 ...
- 去中心化存储项目终极指南 | Filecoin, Storj 和 PPIO 项目异同
Filecoin,Storj 以及 PPIO 这三个存储公链的设计思路是不一样的,没有优劣之分,写这篇文章也并不是为了争论各项目的好坏对错.去中心化存储是一个长期商业赛道,不同团队在同一个赛道上往不同 ...
随机推荐
- 删除没有刀路的刀具.txt
1 UF_initialize(); 2 std::vector<tag_t>tool_tag; 3 std::vector<tag_t>del_tag; 4 tag_t ...
- 北大2022编译原理实践(C/C++)-sysy 编译器构建
这是今年新推出的实践方案,由往年的sysy->IR1->IR2->RISC V变成了sysy->Koopa->RISC V,通过增量的方式让整个实践过程更容易上手 所以先 ...
- 使用LitJson输出格式化json文件到本地
百度上搜了半天,竟然没有C#使用LitJson格式化输出的例子,全都是Newtonsoft.Json的,最后在litjson的官网找到了方法. 给大家分享一下: https://litjson.net ...
- 开始 go
为什么开始想转 go ? 毕业三年多,一直从事的是 Java , 大学学的也是 Java ,本来想一直干下去的,可是似乎 Java 水涨船高,面试要求也越来越高. 曾经一起毕业的同事自学 go ,已经 ...
- MySql 错误:建表时出错1067 - Invalid default value for 'id'
问题描述: 建表时报错无效的默认值: CREATE TABLE `product`( `id` INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT ...
- curl 查看响应时间
curl -o /dev/null -s -w "time_namelookup:%{time_namelookup}\ntime_connect: %{time_connect}\ntim ...
- PR2022(Premiere Pro 2022)Mac/win最新中文版
Adobe Premiere Pro 2022 Mac/win是用于专业级别的视频编辑软件,一个基于时间轴的视频处理工具,具有许多用于生成高端视频的不同功能.Adobe Premiere Pro最重要 ...
- 07 HBase操作
1.理解HBase表模型及四维坐标:行键.列族.列限定符和时间戳. 2.启动HDFS,启动HBase,进入HBaseShell命令行. 3.列出HBase中所有的表信息list 4.创建表create ...
- html:表格
HTML:表格1.表格三标签:(1)<table> 表格(2)<tr> 行(3)<td> 单元格 2.表格的基本语法和结构<table> <t ...
- MySql5.7基础配置
MySql5.7基础配置 [client] #设置mysql客户端的字符集 default-character-set=utf8 [mysqld] #设置mysql端口为3306 port = 330 ...