前端实时更新数据的几种方式
实时更新数据的几种方式
背景
在我们的日常工作中,我们往往会遇到客户端需要实时获取服务端最新数据的场景,例如聊天系统(WeChat/Telegram),股票行情查看软件(同花顺/富途),feed 推送系统(Twitter/微博)等等。在实现这些需求的时候,我们的技术方案是有很多的,本文将会给大家介绍四种常见的实时获取服务端数据的方案,它们分别是:短轮询(polling),长轮询(long polling),长连接(WebSocket)以及服务器事件推送(Sever-Sent Events, aka SSE)。本篇文章将会介绍每种方案的基本原理,以及分别使用他们来实现同一个需求:动态事件列表,我们用到的技术栈是原生 html + 原生 NodeJS。
需求介绍
先说一下这个动态事件列表的需求:我们的服务器每隔 5 秒会产生一个新的事件,每个事件都有一个 id 字段以及 timestamp 字段,id 和 timestamp 字段都是该事件生成的时间戳,前端会以列表的形式展示目前服务端已产生的所有事件信息,后面当服务器产生新的事件时,前端会获取到最新的事件并添加到页面列表的末尾。
一、轮询
概念解释
我相信大多数程序员或多或少都使用过轮询来获取服务端的资源,简单来说轮询就是客户端不停地调用服务端接口以获得最新的数据。客户端在发起请求后服务端会立即响应,不过因为这时服务端的数据没有更新所以返回了一个空的结果给客户端。客户端在等待了一段时间后(可能是几秒),再次请求服务端的数据,这时由于服务端的数据发生了更新,所以会给客户端返回最新的数据,客户端在拿到数据后等待一下然后继续发送请求,如此反复。
代码实现
下面就让我们用轮询来实现动态事件列表的需求, 首先是 Node 代码:
//服务端代码实现
const http = require('http');
const url = require('url');
// 事件列表
const events = []
// 最新生成的事件时间
let latestTimestamp = 0
// 事件生产者
const EventProducer = () => {
const event = {
id: Date.now(),
timestamp: Date.now()
}
events.push(event)
latestTimestamp = event.timestamp
}
// 每隔5秒生成一个新的事件
setInterval(() => {
EventProducer()
}, 5000)
//创建服务
const server = http.createServer((req, resp) => {
const urlParsed = url.parse(req.url, true)
resp.setHeader('Access-Control-Allow-Origin', '*')
resp.setHeader('Origin', '*')
if (urlParsed.pathname == '/events') {
// 每次客户端都带上它最后拿到的事件时间戳来获取新的事件
const timestamp = parseInt(urlParsed.query['timestamp'])
// 判断客户端是否拿到最新事件
if (timestamp < latestTimestamp) {
// 将所有没发送过给这个客户端的事件一次性发送出去
resp.write(JSON.stringify(events.filter(event => event.timestamp > timestamp)))
}
resp.end()
}
})
server.listen(8080, () => {
console.log('server is listening')
})
上面的代码十分简单,我们实现了一个 eventsAPI,前端每次都会带上上一次的时间戳来请求这个时间点后的最新事件。接着再来看一下前端的实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端实时更新数据实现</title>
</head>
<body>
<div id="wrap"></div>
</body>
<script>
//前端存储事件列表
const events = [];
//最新的数据时间戳
let timestamp = 0;
//获取数据函数
const fetchLatestEvents = async (timestamp) => {
// 获取最新的事件
const body = await window.fetch(`http://localhost:8080/events?timestamp=${timestamp}`);
if (body.ok) {
const json = await body.json()
return json
} else {
console.error('failed to fetch')
}
}
//设置轮询,3秒发一次请求
function App() {
const timer = setInterval(async () => {
const latestEvents = await fetchLatestEvents(timestamp);
if (latestEvents && latestEvents.length) {
timestamp = latestEvents[latestEvents.length - 1].timestamp;
events.push(...latestEvents);
renderEl(events);
}
}, 3000)
}
//把数据渲染到页面上
function renderEl(arr) {
let str = '';
arr.map(v => {
str += `
<div>id:${v.id};-------timestamp:${v.timestamp} <div>
`
})
const el = document.getElementById('wrap');
el.innerHTML = str;
}
//执行函数
App();
</script>
</html>
打开Chrome的Devtools我们发现,前端每隔3s向后端请求一次,请求得相当频繁,并且在后端没有产生新数据的时候,很多请求的返回值是空的,也就是说大多数的网络资源都被浪费了。
轮询的优缺点
从上面的代码我们可以看出,短轮询这个技术方案最大的优点就是实现简单,而它的缺点也很明显:
无用的请求多: 因为客户端不知道服务端什么时候有数据更新,所以它只能不停地询问服务端,如果服务端的数据更新并不频繁的话,这些请求大多都是无用的。无用的请求会导致服务端的带宽占用增加,消耗服务端资源,同时如果客户端是一些移动设备的话,耗电速度也会很快。
数据实时性差: 由于不想消耗太多客户端或者服务端的资源,我们通常在实现轮询时不会拿到上一个请求的结果后立即发送第二个请求,这就导致了即使服务端的数据更新了,我们客户端还是需要一段时间才能拿到最新的数据,这对于一些数据实时性要求高的应用例如IM系统是致命的。
使用场景
一般生产级别的应用都不会使用短轮询这个方案,除非你只是写一些给少数人用的系统。
二、长轮询
看完了上面关于短轮询的介绍,我们知道了轮询有两个重大的缺陷:一个是无用请求过多,另外一个是数据实时性差。为了解决这两个问题,某些聪明的程序员就发明了另外一个方案:长轮询,客户端发起请求后,服务端发现当前没有新的数据,这个时候服务端没有立即返回请求,而是将请求挂起,在等待一段时间后(一般为30s或者是60s),发现还是没有数据更新的话,就返回一个空结果给客户端。客户端在收到服务端的回复后,立即再次向服务端发送新的请求。这次服务端在接收到客户端的请求后,同样等待了一段时间,这次好运的是服务端的数据发生了更新,服务端给客户端返回了最新的数据。客户端在拿到结果后再次发送下一个请求,如此反复。
代码实现
接着就让我们使用长轮询来动态实现事件列表的功能,先看一下后端代码:
//服务端代码
const http = require("http");
const url = require("url");
//事件池
const events = [];
//存储
let timers = new Set();
// 当前挂起的请求
let subscribers = new Set();
//事件处理
const EventProducer = () => {
const event = {
id: Date.now(),
timestamp: Date.now(),
};
events.push(event);
// 通知所有挂起的请求
subscribers.forEach((subscriber) => {
const arr = events.filter(
(event) => event.timestamp > subscriber.timestamp
);
if (arr.length) {
subscriber.resp.write(JSON.stringify(arr));
subscriber.resp.end();
clearTimeout(subscriber.timer);
subscribers.delete(subscriber);
}
});
};
// 5秒生成一个事件
setInterval(() => {
EventProducer();
}, 5000);
const server = http.createServer((req, resp) => {
const urlParsed = url.parse(req.url, true);
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Origin", "*");
if (urlParsed.pathname == "/events") {
const timestamp = parseInt(urlParsed.query["timestamp"]);
const arr = events.filter((event) => event.timestamp > timestamp);
if (arr.length) {
resp.write(JSON.stringify(arr));
resp.end();
} else {
// 30s超时,自动关闭连接
const timer = setTimeout(() => {
resp.end();
clearTimeout(timer);
}, 30000);
const subscriber = {
timestamp,
resp,
timer,
};
// 新建的连接挂起来
subscribers.add(subscriber);
// 客户端主动断开连接
req.on("close", () => {
subscribers.delete(subscriber);
clearTimeout(timer);
});
}
}
});
server.listen(8080, () => {
console.log("server is up");
});
上面的代码中每来一个新的连接我们都会将它挂起来(保存在set里面),然后当有新的事件产生时再将所有该客户端没有获取过的事件返回给它,接着来看一下前端代码的实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端实时更新数据实现</title>
</head>
<body>
<div id="wrap"></div>
</body>
<script>
//前端存储事件列表
const events = [];
//最新的数据时间戳
let timestamp = 0;
//获取数据函数
const fetchLatestEvents = async (timestamp) => {
// 获取最新的事件
const body = await window.fetch(`http://localhost:8080/events?timestamp=${timestamp}`);
if (body.ok) {
const json = await body.json()
return json
} else {
console.error('failed to fetch')
}
}
const App = async () => {
const latestEvents = await fetchLatestEvents(timestamp);
if (latestEvents && latestEvents.length) {
timestamp = latestEvents[latestEvents.length - 1].timestamp;
events.push(...latestEvents);
renderEl(events);
}
}
//把数据渲染到页面上
function renderEl(arr) {
let str = '';
arr.map(v => {
str += `
<div>id:${v.id};-------timestamp:${v.timestamp} <div>
`
})
const el = document.getElementById('wrap');
el.innerHTML = str;
}
function getData() {
//执行函数
App().then(() => {
getData();
}).catch(err => {
console.log(err, 'err');
})
}
getData();
</script>
</html>
值得注意的是,这个时候,我们打开浏览器的调试工具可以发现浏览器每一次发出的请求都不会立马收到回复,而是pending一段时间后(大概是5秒)才会有结果,并且结果里面都是有数据的。
长轮询的优缺点
长轮询很完美地解决了短轮询的问题,首先服务端在没有数据更新的情况下没有给客户端返回数据,所以避免了客户端大量的重复请求。再者客户端在收到服务端的返回后,马上发送下一个请求,这就保证了更好的数据实时性。不过长轮询也不是完美的:
服务端资源大量消耗: 服务端会一直hold住客户端的请求,这部分请求会占用服务器的资源。对于某些语言来说,每一个HTTP连接都是一个独立的线程,过多的HTTP连接会消耗掉服务端的内存资源。
难以处理数据更新频繁的情况: 如果数据更新频繁,会有大量的连接创建和重建过程,这部分消耗是很大的。虽然HTTP的keep-alive字段可以解决一部分问题,不过每次拿到数据后客户端都需要重新subscribe,因此相对于WebSocket和SSE它多了一个发送新请求的阶段,对实时性和性能还是有影响的
应用场景
从网上找的资料来看之前的WebQQ和Web微信都是基于长轮询实现的,现在是不是我就不知道了,有兴趣的读者可以自行验证一下。
三、websocket
概念解释
上面说到长轮询不适用于服务端资源频繁更新的场景,而解决这类问题的一个方案就是WebSocket。用最简单的话来介绍WebSocket就是:客户端和服务器之间建立一个持久的长连接,这个连接是双工的,客户端和服务端都可以实时地给对方发送消息。首先客户端会给服务端发送一个HTTP请求,这个请求的Header会告诉服务端它想基于WebSocket协议通信,如果服务端支持升级协议的话,会给客户端发送一个Switching Protocal的响应,它们之间后面都是基于WebSocket协议来通信了。
代码实现
我们再来看一下如何使用WebSocket来实现动态事件列表的需求,下面是后端代码:
const WebSocket = require('ws');
const events = [];
let latestTimestamp = Date.now();
const clients = new Set();
const EventProducer = () => {
const event = {
id: Date.now(),
timestamp: Date.now()
}
events.push(event)
latestTimestamp = event.timestamp
// 推送给所有连接着的socket
clients.forEach(client => {
client.ws.send(JSON.stringify(events.filter(event => event.timestamp > client.timestamp)))
client.timestamp = latestTimestamp
})
}
// 每5秒生成一个新的事件
setInterval(() => {
EventProducer()
}, 5000)
// 启动socket服务器
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws, req) => {
// 首次连接,推送现存事件
ws.send(JSON.stringify(events))
const client = {
timestamp: latestTimestamp,
ws,
}
clients.add(client)
ws.on('close', _ => {
clients.delete(client)
})
})
上面的代码中客户端连接服务端的时候,服务端会记住客户端的时间戳,当新事件产生的时候会给客户端推送所有的新事件。下面是前端代码实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端实时更新数据实现</title>
</head>
<body>
<div id="wrap"></div>
</body>
<script>
//前端存储事件列表
const events = [];
//最新的数据时间戳
let timestamp = 0;
const ws = new WebSocket(`ws://localhost:8080/ws?timestamp=${timestamp}`);
//监听链接事件
ws.addEventListener('open', () => {
console.log('connect');
})
//报错信息
ws.addEventListener('error', () => {
console.log('error');
})
//关闭事件
ws.addEventListener('close', () => {
console.log('close');
})
//信息发送事件
ws.addEventListener('message', (ev) => {
const latestEvents = JSON.parse(ev.data)
if (latestEvents && latestEvents.length) {
timestamp = latestEvents[latestEvents.length - 1].timestamp;
events.push(...latestEvents);
renderEl(events);
}
})
//把数据渲染到页面上
function renderEl(arr) {
let str = '';
arr.map(v => {
str += `
<div>id:${v.id};-------timestamp:${v.timestamp} <div>
`
})
const el = document.getElementById('wrap');
el.innerHTML = str;
}
</script>
</html>
打开Chrome的网络调试工具点击ws,你会发现客户端和服务端只有一个websocket连接,它们所有的通信都是发生在这个连接上面的:
WebSocket的优缺点
总的来说,我认为WebSocket有下面这些优点:
客户端和服务端建立连接的次数少:理想情况下客户端只需要发送一个HTTP升级协议就可以升级到WebSocket连接,后面所有的消息都是通过这个通道进行通信,无需再次建立连接。
消息实时性高:由于客户端和服务端的连接是一直建立的,所以当数据更新的时候可以马上推送给客户端。
双工通信:服务端和客户端都可以随时给对方发送消息,这对于本文的其它三种方案都是很难做到的。
适用于服务端数据频繁更新的场景:和长轮询不同,服务端可以随时给客户端推送新的信息,而客户端在拿到信息后不需要重新建立连接或者发送请求,因此WebSocket适合于数据频繁更新的场景。
同样WebSocket也不是完美的,它有下面这些问题:
扩容麻烦:基于WebSocket的服务是有状态的。这就意味着在扩容的时候很麻烦,系统设计也会较复杂。
代理限制:某些代理层软件(如Nginx)默认配置的长连接时间是有限制的,可能只有几十秒,这个时候客户端需要自动重连。要想突破这个限制你就需要将从客户端到服务端之间所有的代理层的配置都改掉,在现实中这可能是不可行的。
应用场景
WebSocket的应用场景是一些实时性要求很高的而且需要双工通信的系统例如IM软件等。
四、Server-Sent Events
概念解释
Server-Sent Events简称SSE,是一个基于HTTP协议的服务端向客户端推送数据的技术。下面是一个简单的SSE图示:
在上图中,客户端向服务端发起一个持久化的HTTP连接,服务端接收到请求后,会挂起客户端的请求,有新消息时,再通过这个连接将数据推送给客户端。这里需要指出的是和WebSocket长连接不同,SSE的连接是单向的,也就是说它不允许客户端向服务端发送消息。
代码实现
和上面一样,我们使用SSE来实现一下动态事件列表的需求,先看后端代码:
const http = require('http');
const url = require('url');
const events = [];
const clients = new Set();
let latestTimestamp = Date.now();
const headers = {
// 告诉HTTP连接,它是一个event-stream的请求
'Content-Type': 'text/event-stream',
// 保持HTTP连接不断开
'Connection': 'keep-alive',
'Cache-Control': 'no-cache',
'Access-Control-Allow-Origin': '*',
"Origin": '*'
}
const EventProducer = () => {
const event = {
id: Date.now(),
timestamp: Date.now()
}
events.push(event);
latestTimestamp = event.timestamp;
clients.forEach(client => {
client.resp.write(`id: ${(new Date()).toLocaleTimeString()}\n`)
// 后面的两个\n\n一定要有,可以理解为服务端先客户端推送信息的特殊格式
client.resp.write(`data: ${JSON.stringify(events.filter(event => event.timestamp > client.timestamp))}\n\n`)
client.timestamp = latestTimestamp
})
}
// 每5秒生成一个新的事件
setInterval(() => {
EventProducer()
}, 5000)
const server = http.createServer((req, resp) => {
const urlParsed = url.parse(req.url, true)
if (urlParsed.pathname == '/subscribe') {
resp.writeHead(200, headers)
// 发送现存事件
resp.write(`id: ${(new Date()).toLocaleTimeString()}\n`)
resp.write(`data: ${JSON.stringify(events)}\n\n`)
const client = {
timestamp: latestTimestamp,
resp
}
clients.add(client)
req.on('close', () => {
clients.delete(client)
})
}
})
server.listen(8080, () => {
console.log('server is up')
})
在上面的代码中,每次客户端给服务端发送请求后,服务端先给客户端返回所有的现存事件然后将该请求挂起,在新的事件生成时再给客户端返回所有的新事件。下面是前端代码实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>前端实时更新数据实现</title>
</head>
<body>
<div id="wrap"></div>
</body>
<script>
//前端存储事件列表
const events = [];
//最新的数据时间戳
let timestamp = 0;
const source = new EventSource(`http://localhost:8080/subscribe?timestamp=${timestamp}`)
//监听链接事件
source.onopen = () => {
console.log('connected')
}
//监听信息推送事件
source.onmessage = ev => {
const latestEvents = JSON.parse(ev.data)
if (latestEvents && latestEvents.length) {
timestamp = latestEvents[latestEvents.length - 1].timestamp;
events.push(...latestEvents);
renderEl(events);
}
}
//监听错误事件
source.addEventListener('error', (e) => {
console.error('Error: ', e);
})
//把数据渲染到页面上
function renderEl(arr) {
let str = '';
arr.map(v => {
str += `
<div>id:${v.id};-------timestamp:${v.timestamp} <div>
`
})
const el = document.getElementById('wrap');
el.innerHTML = str;
}
</script>
</html>
打开Chrome的网络调试工具,会发现HTTP请求变成了EventStream类型,而且服务端给客户端所有的事件推送都在这个连接上,而无需建立新的连接。
SSE的优缺点
在我看来,SSE的技术有下面的优点:
连接数少: 客户端和服务端只有一个持久的HTTP连接,因此性能也是很好的。
数据实时性高: 它比长轮询更加实时,因为服务端和客户端的连接是持久的,所以有新消息的话可以直接推送到客户端。
SSE的问题也很明显:
- 单向通信: SSE长连接是单向的,不允许客户端给服务端推送数据。
- 代理层限制: 和WebSocket一样会遇到代理层配置的问题,配置错误的话,客户端需要不断和服务端进行重连。
使用场景
SSE技术适合一些只需要服务端单向推送事件给客户端的场景,例如股票行情推送软件。
总结
在本篇文章中我通过图解和实际代码给大家介绍了四种不同的和服务端保持数据同步的方案,看完本篇文章后,相信你后面再遇到类似的需求时,除了短轮询你会有更多的方案可以选择。同时这里也还是要强调一下:任何一种技术都不是瑞士军刀,都有自己适用和不适用的场景,一定要根据自己的实际情况进行取舍,从而选择最适合的方案,千万不要为了用某个技术而用某个技术!
注:转载自:https://juejin.cn/post/7139684620777291807,作者稍加整理
推荐阅读
-
35 岁实现财务*,腾讯程序员手握2300万提前退休?-1000万房产、1000万腾讯股票、加上300万的现金,一共2300万的财产。有网友算了一笔账,假设1000万的房产用于自住,剩下1300万资产按照平均税后20-50万不等进行计算,大约花上26-60年左右的时间才能赚到这笔钱。也就是说,普通人可能奋斗一辈子,才能赚到这笔钱。在很多人还在为中年危机而惶惶不可终日的时候,有的人的35岁,就已经安全着陆,试问哪个打工人不羡慕?但问题是有这样财富积累必然有像样的实力做靠山。没有人可以不劳而获。 看到这里,肯定有人说,那么对于普通人来说,卷可能真就成了唯一的出路。但是卷也有轻松的卷,“偷懒”的卷法,对于程序员而言,刨除掉一时无法改掉的开会传统占用的大部分时间,如何把有限的时间和精力放在真正重要的架构设计、需求设计上,而不是重复的造*,编码、改bug、手动测试。因此在科技改变生活的今天,学会使用AI工具成为程序员们的必备技能。 以全栈式全自动的软件开发工具飞算SoFlu软件机器人为例,作为全球首款面向微服务架构设计和最佳实践的软件机器人,SoFlu软件机器人改变了原来手工编码的作业模式,通过可视化拖拽方式以及参数配置就能实现等同于编写复杂代码的业务逻辑,在设计业务逻辑时就完成了微服务应用开发,做到“业务即图,图即代码”,实现“软件开发,十倍提效”。 而作为飞算SoFlu软件机器人的一个重要组成部分,FuncGPT(慧函数)支持所有类型函数创建。通过自然语言描述Java函数需求,实时生成高质量、高可读性的Java函数代码。生成代码可直接复制到IDEA,或一键导入Java全自动开发工具函数库。 FuncGPT(慧函数)具有五大核心能力: ● 自然语言:通过自然语言即可生成函数,降低软件开发门槛。 ● 秒级函数开发:全自动秒级开发函数,秒级完成,效率千倍提升。 ● 一站式函数开发应用:函数生成即可直接应用,效率、安全有保障。 ● 质量可靠:生成的函数代码符合业界规范、具备良好的可读性,符合最优解。 ● 灵活生成:可以根据开发人员的具体需求生成并支持调整和修改。 以“将文件流转换为File对象”这个需求为例,以下是FuncGPT(慧函数)秒级生成的代码: / 类名:FileConverter // 函数名:convertStreamToFile // 函数功能:将文件流转换为File对象 // POM依赖包:无 import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class FileConverter { /** * 将文件流转换为File对象 * @param inputStream 文件流 * @param filePath 文件路径 * @param fileName 文件名 * @return 转换后的File对象 * @throws IOException 如果发生I/O异常 */ public static File convertStreamToFile(InputStream inputStream, String filePath, String fileName) throws IOException { File file = new File(filePath + File.separator + fileName); // 创建File对象 try (FileOutputStream outputStream = new FileOutputStream(file)) { // 创建文件输出流 byte buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { // 从文件流读取数据并写入文件 outputStream.write(buffer, 0, bytesRead); } } return file; // 返回转换后的File对象 } } // 函数示例 // 将文件流转换为File对象示例 // 入参:inputStream,文件流 // 入参:filePath,文件路径 // 入参:fileName,文件名 // 出参:file,转换后的File对象 // 调用示例: // InputStream inputStream = new FileInputStream("example.txt"); // String filePath = "C:\\Users\\User\\Documents"; // String fileName = "example.txt"; // File file = FileConverter.convertStreamToFile(inputStream, filePath, fileName); // System.out.println(file.getAbsolutePath); // 输出结果:例如,将文件流转换为File对象后,文件的绝对路径为:C:\Users\User\Documents\example.txt // 则输出结果为:C:\Users\User\Documents\example.txt 通过分析,不难发现以上代码:
-
参数传递的前端和后端交互有几种常见方式
-
实时更新网络数据的三种简单方法
-
反传销网8月30日发布:视频区块链里的骗子,币里的韭菜,杜子建骂人了!金融大V周召说区块链!——“一小帮骗子玩一大帮小白,被割韭菜,小白还轮流被割,割的就是你!” 什么区块链,统统是骗子 作者:周召(知乎金融领域大V,毕业于上海财经大学,目前任职上海某股权投资基金合伙人) 有人问我,区块链现在这么火,到底是不是骗局? 我的回答是: 是骗局。而且我并不是说数字货币是骗局,而是说所有搞区块链的都是骗局。 -01- 区块链是一种鸡肋技术 人类社会任何技术的发明应用,本质都是为了提高社会的生产效率。而所谓区块链技术本质不过是几种早已成熟的技术的大杂烩,冗余且十分低效,除了提高了洗钱和诈骗的效率以外,对人类社会的进步毫无贡献。 真正意义上的区块链得包含三个要素:分布式系统(包括记账和存储),无法篡改的数据结构,以及共识算法,三者互为基础和因果,就像三体世界一样。看上去挺让人不明觉厉的,而经过几年的瞎折腾,稍微懂点区块链的碰了几次壁后都已经渐渐明白区块链其实并没有什么卵用,区块链技术已经名存实亡,沦为了营销工具和传销组织的画皮。 因为符合上述定义的、以比特币为代表的原教旨区块链技术,是反效率的,从经济学角度来说,不但不是一种帕累托改进,甚至还可以说是一种帕累托倒退。 原教旨区块链技术的效率十分低下,因为要遍历所有节点,只能做非常轻量级的数据应用,一旦涉及到大量的数据传输与更新,区块链就瞎了。 一方面整条链交易速度会极慢,另一方面数据库容量极速膨胀,考虑到人手一份的存储机制,区块链其实是对存储资源和能源的一种极大的浪费。 这里还没有加上为了取得所谓的共识和挖矿消耗的巨大的能源,如果说区块链技术是屎,那么这波区块链投机浪潮可谓人类历史上最大规模的搅屎运动。 区块链也验证不了任何东西。 所谓的智能合约,即不智能,也非合约。我看有人还说,如果有了智能合约,就可以跟老板签一份放区块链上,如果明年销售业绩提升30%,就加薪10%,由于区块链不能篡改,不能抵赖,所以老板必须得执行,说得有板有眼,不懂行的愣一看,好像还真是那么回事。 但仔细一想,问题就来了。首先,在区块链上如何证明你真的达到了30%业绩提升?即便真的达到老板耍赖如何执行? 也就是说,如果区块链真这么厉害,要法院和仲裁干什么。 人类社会真正的符合成本效益原则的是代理制度。之前有人说要用区块链改造注册会计师行业,我不知道他准备怎么设计,我猜想他思路大概是这样的,首先肯定搞去中心化,让所有会计师到链上来,然后一个新人要成为注册会计师就要所有会计师同意并记录在链上。 那我就请问了,我每天上班累死累活,为什么还要花时间去验证一个跟我无关的的人的专业能力?最优做法当然是组织一个委员会,让专门的人来负责,这不就是现在注册会师协会干的事儿吗?区块链的逻辑相当于什么事情都要拿出来公投,这个绝对是扯淡的。 当然这么说都有点抬举区块链了,区块链技术本身根本没有判断是非能力,如果这么高级的人工智能,靠一个无脑分布式记账就能实现的话,我们早就进入共产主义社会了。 虽然EOS等数字货币采用了超级节点,通过再中心化的方式提高效率,有点行业协会的意思,是对区块链原教旨主义的一种修正,但是依然无法突破区块链技术最本质的局限性。有人说,私有链和联盟链是区块链技术的未来,也是扯淡,因为区块链技术没有未来。如果有,说明他是包装成区块链的伪区块链技术。 区块链所涉及的所有底层技术,不管是分布式数据库技术,加密技术,还是点对点传输技术等,基本都是早已存在没什么秘密可言的技术。 比特币系统最重要的特性是封闭性和自洽性,他验证不了任何系统自身以外产生的信息的真实性。 所谓系统自身产生的信息,就是数据库数据的变动信息,有价值的基本上有且只有交易信息。所以说比特币最初不过是中本聪一种炫技的产物,来证明自己对几种技术的掌握,你看我多牛逼,设计出了一个像三体一样的系统。因此,数字货币很有可能是区块链从始至终唯一的杀手应用。 比特币和区块链概念从诞生到今天已经快10年了,很多人说区块链技术在爆发的前夜,但这个前夜好像是不是有点过长了啊朋友,跟三体里的长夜有一拼啊。都说区块链技术像是90年代初的互联网,可是90年代初的互联网在十年发展后,已经出现了一大批伟大的公司,阿里巴巴在99年都成立了,区块链怎么除了币还是币呢? 正规的数字货币未来发展的形式无外乎几种,要么就是论坛币形式,或者类似股票的权益凭证等。问题是论坛币和股票之前,本来也都电子化了,区块链来了到底改变了什么呢? 所有想把TOKEN和应用场景结合起来的人最后都很痛苦,最后他们会发现区块链技术就是脱裤子放屁,自己辛苦搞半天,干嘛不自己作为中心关心门来收钱?最后这些人都产生了价值的虚无感,最终精神崩溃,只能发币疯狂收割韭菜,一边嘴里还说着我是个好人之类的奇怪的话。 因此,之前币圈链圈还泾渭分明,互相瞧不起,但这两年链圈逐渐坐不住了,想着是不是趁着泡沫没彻底破灭之前赶快收割一波,不然可能什么都捞不着了。 前段时间和一个名校毕业的链圈朋友瞎聊天,他说他们“致力于用区块链技术解决数字版权保护问题”,我就问他一个问题,你们如何保证你链的版权所有权声明是真实的,万一盗版者抢先一步把数据放在链上怎么办。他说他们的解决方案是连入国家数字版权保护中心的数据库进行验证…… 所以说区块链技术就是个鸡肋,研究到最后都会落入效率与真实性的黑洞,很多人一头扎进链圈后才发现,真正意义上的区块链技术,其实什么都干不了。 -02- 不是蠢就是坏的区块链媒体 空气币和区块链的造富神话,让区块链自媒体也开始迎风乱扭。一群群根本不知道区块链为何物的妖魔鬼怪纷纷进驻区块链自媒体战场,开始大放厥词胡编乱造。 任何东西,但凡只要和区块,链,分,分布式,记账,加密,验证,可追溯等等这些个关键词沾到哪怕一点点,这些所谓的区块链媒体人就会像狗闻到了屎了一样疯狂地把区块链概念往上套。 这让我想起曾经一度也是热闹非凡的物联网,我曾经去看过江苏一家号称要改变世界的“物联网”企业,过去一看是生产路由器的,我黑人问号脸,对方解释说没有路由器万物怎么互联,我觉得他说得好有道理,竟无言以对。 好,下面让我们进入奇葩共赏析时间,来看看区城链媒体经常有哪些危言耸听的奇谈怪论 区块链(分布式记账)的典型应用是*?? 正如前面所说,真正意义上的区块链分布式记账,不光包括“记”这个动作,还包括分布式存储和共识机制等。而*诞生远远早于区块链这个词的出现,勉强算是“分布式编辑”吧,就被很多区块链媒体拿来强行充当区块链技术应用的典范。 其实事实恰恰相反,*恰恰是去中心化失败的典范,现在如果没有精英和专业人士的编辑和维护,*早就没法看了。 区块链会促进社会分工?? 罗振宇好像就说过类似的话,虽然罗振宇说过很多没有逻辑的话,但这句话绝对是最没逻辑思维的。很多区块链自媒体也常常用这句话来忽悠老百姓,说分工代表效率提高社会进步,而区块链“无疑”会促进分工,他们的理由仅仅是分工和分布式记账都共用一个“分”字,就强行把他们扯到一起。 实际情况恰恰相反,区块链是逆分工的,区块链精神是号召所有人积极地参与到他不擅长也不想掺合的事情里面去。 区块链不能像上帝一样许诺他的子民死后上天国,只能给他们许诺你们是六度人脉中的第一级,我可以赚后面五级人的钱,你处于金字塔的顶端。
-
旷视天元开源图像比对工具 MegSpot,助力图像算法研发 - 1.多样化图像比对:可提供叠加比对、拖拽比对等多种比对方式,支持缩放、移动等同步操作,并可生成 GIF 保存比对结果。2. 2.专业呈现:支持像素级图像查看、图像直方图、RGB 查看;支持预览亮度、对比度、饱和度、灰度等指标。3. 视频对比:Cognizant Megapixel 可提供多种图像对比方法,如拖放对比等。 3.视频对比:除了支持视屏的所有图像对比功能外,CCTV MegSpot 还支持同步回放、回放暂停和快进、回放速度设置等功能。 4.跨平台支持:CCTV MegSpot 提供对 Mac、Linux 和 Windows 系统的跨平台支持,借助 Electron 框架,可以低成本完成跨平台应用的开发,并保证各平台体验的一致性。 此外,央视网MegSpot支持跨平台自动更新和数据持久化,确保用户体验的连续性,并支持中、英、日三种语言:MegSpot为大尺寸图像文件的对比提供了本地解决方案。 MegSpot 是一种用于比较大型图像文件的本地解决方案。
-
实时音频和视频技术的发展与应用-1.1 双重音频和视频 从架构上看,双人音视频系统相对简单明了。红点代表房间信令服务,房间信令服务的主要功能是管理房间信息,实现容量协商和上下行链路的质量调节,例如当下行信道发生拥塞时,上行线路的码率和分辨率会降低。 在传输信道层面,我们的策略是优先直连,在跨区域、跨运营商的情况下,我们会选择单中转或双中转信道,在策略上尽量保持直连和中转信道同时存在,当其中一个信道的质量不好时,系统会自动切断到另一个信道的流量。 1.2 多人音视频 多人视频通话的产品形态是整个房间不超过 50 人,大盘平均房间规模约为 4.x 人,房间内部最多满足一个大视频和三个小视频(四屏)。根据这一条件,我们在架构中采用了典型的 SFU 小房间设计。 上图中的红点代表房间信令服务,主要用于房间管理和状态信息同步。房间管理主要包括用户列表的管理,例如哪些用户打开了视频/音频,我看了谁,谁看了我,这些都是基于房间管理的信息,然后房间信令服务会将这些信息同步到媒体传输服务进行数据分发。 房间服务的另一个作用是房间级容量协商和质量控制,例如,房间里的每个人一开始都支持 H.265 编码,当某个时刻进来一个只支持 H.264 编码的用户时,房间里所有的上游主播就必须把 H.265 切成 H.264。还有一种情况是,房间里有一定比例的人下行链路信道质量较差,这会导致上行链路房间质量下降。 在传输层面,我们采用的是单层分布式媒体传输网络,大家都选择中转方式,不区分双人和多人,采用 Full-Mesh 传输机制将所有数据推送过去,比如一个节点上的人并不都看另外两个人的视频,但还是会将视频推送给他们。
-
uniapp 跨页面传递数据的几种方式
-
如何让Java后端收集的数据迅速在网页上显示更新 前端如何实现实时从后端接收数据
-
前端获取接口报400错误时返回的响应数据的方式
-
Web前端(九)-创建SpringBoot项目、web服务器(WebServer、Servlet、Controller)、SpringBoot介绍(处理静态、动态资源请求、客户端发出请求的几种方式、获取客户端传递参数的方式)