In einem unserer Projekte haben wir IPC (Inter-Process Communication) für Sockets verwendet. Ein ziemlich großes Projekt, ein Handelsbot, in dem es viele Module gab, die miteinander interagierten. Mit zunehmender Komplexität wurde es zu einer Frage der Überwachung der Vorgänge in Microservices. Wir haben uns entschlossen, eine eigene Anwendung zur Verfolgung des Datenflusses zu erstellen, bei der nur zwei Bibliotheken reagieren und wiederherstellen . Ich möchte unseren Ansatz mit Ihnen teilen.
Microservices tauschen JSON-Objekte mit zwei Feldern aus: Name und Daten. Der Name ist die Kennung, für welchen Dienst das Objekt bestimmt ist, und das Datenfeld ist die Nutzlast. Beispiel:
{ name:'ticket_delete', data:{id:1} }
Da der Service ziemlich grob ist und die Protokolle jede Woche geändert werden, sollte die Überwachung so einfach und modular wie möglich sein. Dementsprechend muss in der Anwendung jedes Modul die dafür vorgesehenen Daten anzeigen. Durch Hinzufügen und Löschen von Daten müssen wir daher eine Reihe unabhängiger Module für die Überwachung von Prozessen in Microservices erhalten.
Also fangen wir an. Lassen Sie uns zum Beispiel eine einfache Anwendung und einen Webserver erstellen. Die Anwendung besteht aus drei Modulen. Sie sind im Bild durch gepunktete Linien gekennzeichnet. Schaltflächen für Timer, Statistik und Statistiksteuerung.
Erstellen wir einen einfachen Web Socket-Server.
/** src/ws_server/echo_server.js */
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8888 });
function sendToAll( data) {
let str = JSON.stringify(data);
wss.clients.forEach(function each(client) {
client.send(str);
});
}
//
setInterval(e=>{
let d = new Date();
let H = d.getHours();
let m = ('0'+d.getMinutes()).substr(-2);
let s = ('0'+d.getSeconds()).substr(-2);
let time_str = `${H}:${m}:${s}`;
sendToAll({name:'timer', data:{time_str}});
},1000);
. :
node src/ws_server/echo_server.js
. rollup .
rollup.config.js
import serve from 'rollup-plugin-serve';
import babel from '@rollup/plugin-babel';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import hmr from 'rollup-plugin-hot'
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer'
import replace from '@rollup/plugin-replace';
const browsers = [ "last 2 years", "> 0.1%", "not dead"]
let is_production = process.env.BUILD === 'production';
const replace_cfg = {
'process.env.NODE_ENV': JSON.stringify( is_production ? 'production' : 'development' ),
preventAssignment:false,
}
const babel_cfg = {
babelrc: false,
presets: [
[
"@babel/preset-env",
{
targets: {
browsers: browsers
},
}
],
"@babel/preset-react"
],
exclude: 'node_modules/**',
plugins: [
"@babel/plugin-proposal-class-properties",
["@babel/plugin-transform-runtime", {
"regenerator": true
}],
[ "transform-react-jsx" ]
],
babelHelpers: 'runtime'
}
const cfg = {
input: [
'src/main.js',
],
output: {
dir:'dist',
format: 'iife',
sourcemap: true,
exports: 'named',
},
inlineDynamicImports: true,
plugins: [
replace(replace_cfg),
babel(babel_cfg),
postcss({
plugins: [
autoprefixer({
overrideBrowserslist: browsers
}),
]
}),
commonjs({
sourceMap: true,
}),
nodeResolve({
browser: true,
jsnext: true,
module: false,
}),
serve({
open: false,
host: 'localhost',
port: 3000,
}),
],
} ;
export default cfg;
main.js
.
/** src/main.js */
import React, { createElement, Component, createContext } from 'react';
import ReactDOM from 'react-dom';
import {Connect, Provider} from './store'
import Timer from './Timer/Timer'
const Main = () => (
<Provider>
<h1>ws stats</h1>
<Timer/>
</Provider>
);
const root = document.body.appendChild(document.createElement("DIV"));
ReactDOM.render(<Main />, root);
/** src/store.js */
import React, { createElement, Component, createContext } from 'react';
import createStoreFactory from 'redoor';
import * as actionsWS from './actionsWS'
import * as actionsTimer from './Timer/actionsTimer'
const createStore = createStoreFactory({Component, createContext, createElement});
const { Provider, Connect } = createStore(
[
actionsWS, // websocket actions
actionsTimer, // Timer actions
]
);
export { Provider, Connect };
. .
/** src/actionsWS.js */
export const __module_name = 'actionsWS'
let __emit;
// emit redoor
export const bindStateMethods = (getState, setState, emit) => {
__emit = emit
};
//
let wss = new WebSocket('ws://localhost:8888')
// redoor
wss.onmessage = (msg) => {
let d = JSON.parse(msg.data);
__emit(d.name, d.data);
}
. : . redoor . :
+------+ | emit | --- events --+--------------+----- ... ------+-------------> +------+ | | | v v v +----------+ +----------+ +----------+ | actions1 | | actions2 | ... | actionsN | +----------+ +----------+ +----------+
"" .
. Timer
Timer.js
actionsTimer.js
/** src/Timer/Timer.js */
import React from 'react';
import {Connect} from '../store'
import s from './Timer.module.css'
const Timer = ({timer_str}) => <div className={s.root}>
{timer_str}
</div>
export default Connect(Timer);
, timer_str
actionsTimer.js
. Connect
redoor.
/** src/Timer/actionsTimer.js */
export const __module_name = 'actionsTimer'
let __setState;
//
export const bindStateMethods = (getState, setState) => {
__setState = setState;
};
//
export const initState = {
timer_str:''
}
// "" "timer"
export const listen = (name,data) =>{
name === 'timer' && updateTimer(data);
}
//
function updateTimer(data) {
__setState({timer_str:data.time_str})
}
, "" timer
( listen
) .
redoor:
__module_name
- .
bindStateMethods
- setState
, .
initState
- timer_str
listen
- redoor.
. http://localhost:3000
npx rollup -c rollup.config.js --watch
. . . echo_server.js
/** src/ws_server/echo_server.js */
...
let g_interval = 1;
//
setInterval(e=>{
let stats_array = [];
for(let i=0;i<30;i++) {
stats_array.push((Math.random()*(i*g_interval))|0);
}
let data = {
stats_array
}
sendToAll({name:'stats', data});
},500);
...
. Stats
Stats.js
actionsStats.js
/** src/Stats/Stats.js */
import React from 'react';
import {Connect} from '../store'
import s from './Stats.module.css'
const Bar = ({h})=><div className={s.bar} style={{height:`${h}`px}}>
{h}
</div>
const Stats = ({stats_array})=><div className={s.root}>
<div className={s.bars}>
{stats_array.map((it,v)=><Bar key={v} h={it} />)}
</div>
</div>
export default Connect(Stats);
/** src/Stats/actionsStats.js */
export const __module_name = 'actionsStats'
let __setState = null;
export const bindStateMethods = (getState, setState, emit) => {
__setState = setState;
}
export const initState = {
stats_array:[],
}
export const listen = (name,data) =>{
name === 'stats' && updateStats(data);
}
function updateStats(data) {
__setState({
stats_array:data.stats_array,
})
}
/** src/store.js */
...
import * as actionsStats from './Stats/actionsStats'
const { Provider, Connect } = createStore(
[
actionsWS,
actionsTimer,
actionsStats //<-- Stats
]
);
...
:
Stats
Timer
, , . , ? .
g_interval . .
. interval
.
/** src/Stats/Stats.js */
...
import Buttons from './Buttons' //
...
const Stats = ({cxRun, stats_array})=><div className={s.root}>
<div className={s.bars}>
{stats_array.map((it,v)=><Bar key={v} h={it} />)}
</div>
<Buttons/> {/* */}
</div>
...
/** src/Stats/Buttons.js */
import React from 'react';
import {Connect} from '../store'
import s from './Stats.module.css'
const DATA_INTERVAL_PLUS = {
name:'change_interval',
interval:1
}
const DATA_INTERVAL_MINUS = {
name:'change_interval',
interval:-1
}
const Buttons = ({cxEmit, interval})=><div className={s.root}>
<div className={s.btns}>
<button onClick={e=>cxEmit('ws_send',DATA_INTERVAL_PLUS)}>
plus
</button>
<div className={s.len}>interval:{interval}</div>
<button onClick={e=>cxEmit('ws_send',DATA_INTERVAL_MINUS)}>
minus
</button>
</div>
</div>
export default Connect(Buttons);
:
actionsWS.js
/** src/actionsWS.js */
...
let wss = new WebSocket('ws://localhost:8888')
wss.onmessage = (msg) => {
let d = JSON.parse(msg.data);
__emit(d.name, d.data);
}
// ""
export const listen = (name,data) => {
name === 'ws_send' && sendMsg(data);
}
//
function sendMsg(msg) {
wss.send(JSON.stringify(msg))
}
Buttons.js
(cxEmit
) redoor. ws_send
"" actionsWS.js
. data
- : DATA_INTERVAL_PLUS
DATA_INTERVAL_MINUS
. { name:'change_interval', interval:1 }
/** src/ws_server/echo_server.js */
...
wss.on('connection', function onConnect(ws) {
// "" "change_interval"
// Buttons.js
ws.on('message', function incoming(data) {
let d = JSON.parse(data);
d.name === 'change_interval' && change_interval(d);
});
});
let g_interval = 1;
//
function change_interval(data) {
g_interval += data.interval;
// ,
sendToAll({name:'interval_changed', data:{interval:g_interval}});
}
...
Buttons.js. actionsStats.js "interval_changed
" interval
/** src/Stats/actionsStats.js */
...
export const initState = {
stats_array:[],
interval:1 //
}
export const listen = (name,data) =>{
name === 'stats' && updateStats(data);
// ""
name === 'interval_changed' && updateInterval(data);
}
//
function updateInterval(data) {
__setState({
interval:data.interval,
})
}
function updateStats(data) {
__setState({
stats_array:data.stats_array,
})
}
Wir haben also drei unabhängige Module, bei denen jedes Modul nur sein eigenes Ereignis überwacht und nur dieses anzeigt. Das ist sehr praktisch, wenn die Struktur und die Protokolle in der Prototyping-Phase noch nicht vollständig klar sind. Es muss nur hinzugefügt werden, dass wir, da alle Ereignisse eine End-to-End-Struktur haben, die Vorlage zum Erstellen eines Ereignisses klar einhalten müssen und Folgendes für uns selbst ausgewählt haben : MODULEN AME)_(FUNCTION NAME)_(VAR NAME)
.
Hoffe es war hilfreich. Die Quellcodes des Projekts befinden sich wie üblich auf dem Github.