keydb+socket.io 进行实时应用开发
socket.io 是一个很不错的实时应用开发框架,基于socket.io 开发的实时系统不少,而且socket.io 也支持不少语言框架的集成
参考图
玩法说明
通过keydb 的active-Replica 能力,可以实现一个快速的ha 能力,同时对于业务为了方便业务集成,添加了haproxy 作为lb,对于socket.io server 实例使用haproxy 提供的lb地址,对于业务应用可以基于nginx 通过路由规则将链接请求路由到不同的socket.io 单元中(按照业务的分离,对于跨业务的通信需要调整下)
参考集成
- keydb docker-compose
version: '3'
services:
db:
image: eqalpha/keydb
command: keydb-server /etc/keydb/keydb.conf --requirepass dalong --masterauth dalong --active-replica yes --replicaof db2 6379
ports:
- 6379:6379
db2:
image: eqalpha/keydb
command: keydb-server /etc/keydb/keydb.conf --requirepass dalong --masterauth dalong --active-replica yes --replicaof db 6379
ports:
- 6380:6379
haproxy:
image: haproxytech/haproxy-debian:2.7.5
volumes:
- "./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg"
ports:
- "5002:6379"
haproxy 配置
global
chroot /var/lib/haproxy
stats timeout 30s
user haproxy
group haproxy
# The lines below enable multithreading. This should correlate to number of threads available you want to use.
nbthread 4
maxconn 40000
defaults
log global
mode tcp
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend main
bind :6379
maxconn 40000
mode tcp
option tcplog
default_backend app
backend app
balance first
option tcp-check
server keydb3 db:6379 maxconn 20000 check inter 1s
server keydb2 db2:6379 maxconn 20000 check inter 1s
- socket.io 实例服务
集成redis adapter
const app = require("express")();
const http = require("http").createServer(app);
const {Server} = require("socket.io")
const { createClient } = require('redis');
const { createAdapter } = require('@socket.io/redis-adapter');
const io = new Server(http,{
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
app.get("/", (req, res) => res.sendFile(__dirname + "/index.html"));
io.on("connection", function (socket) {
console.log("socket connecte");
io.emit("user connected");
socket.on("message", function (msg) {
io.emit("message", msg);
});
});
const pubClient = createClient({ host: 'localhost', port: 5002,password:'dalong' });
const subClient = pubClient.duplicate();
Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
io.adapter(createAdapter(pubClient, subClient));
http.listen(3000, () => console.log("listening on http://localhost:3000"))
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Socket.io Example</title>
</head>
<body>
<h1>Our Socket.io Chat Application</h1>
<div>
<h2>Messages</h2>
<ul></ul>
</div>
<form action="">
<input type="text" />
<button>Send</button>
</form>
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script>
// select relevant elements
const form = document.querySelector("form");
const input = document.querySelector("input");
messageList = document.querySelector("ul");
// establish socket.io connection
const socket = io();
// handle sending message to server & input reset
function sendMessage(e) {
// prevent form submission refreshing page
e.preventDefault();
// send input value to server as type 'message'
socket.emit("message", input.value);
// reset input value
input.value = "";
}
// add listener to form submission
form.addEventListener("submit", sendMessage);
// add message to our page
function addMessageToHTML(message) {
// create a new li element
const li = document.createElement("li");
// add message to the elements text
li.innerText = message;
// add to list of messages
messageList.append(li);
}
socket.on("message", addMessageToHTML);
function alertUserConnected() {
addMessageToHTML("User connected");
}
socket.on("user connected", alertUserConnected);
</script>
</body>
</html>
- java 集成
基于官方的socket.io-client 客户端
public static void main(String[] args) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
.build();
IO.Options options = new IO.Options();
options.callFactory = okHttpClient;
Socket socket = IO.socket(URI.create("http://localhost:3001"), options);
socket.on("message", new Emitter.Listener() {
@Override
public void call(Object
System.out.println(objects[0].toString());
}
});
socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
@Override
public void call(Object
System.out.println("EVENT_CONNECT");
}
});
socket.on(Socket.EVENT_DISCONNECT, new Emitter.Listener() {
@Override
public void call(Object
System.out.println("EVENT_DISCONNECT");
}
});
socket.on(Socket.EVENT_CONNECT_ERROR, new Emitter.Listener() {
@Override
public void call(Object
System.out.println("EVENT_CONNECT_ERROR");
}
});
socket.connect();
}
说明
以上是一个简单的集成,实际上如果需要基于socket.io 做一个完备的实时系统还有不少事情需要做,权限,认证,安全,规则路由,成员管理,消息存储,离线消息处理
但是利用keydb提供的一些能力以及利用socket.io 周边的扩展,我们还是可以快速的开发一个简单但是可靠的实时应用的
参考资料
https://github.com/socketio/socket.io
https://github.com/socketio/socket.io-redis-adapter
https://socket.io/docs/v4/
https://socketio.github.io/socket.io-client-java/installation.html
https://github.com/rongfengliang/socket.io-keydb-learning