GithubHelp home page GithubHelp logo

danclay / eris-fleet Goto Github PK

View Code? Open in Web Editor NEW
41.0 2.0 18.0 1.18 MB

Cluster management for Discord bots using the Eris library.

Home Page: https://danclay.github.io/eris-fleet

License: MIT License

TypeScript 88.04% JavaScript 11.96%
eris sharding clustering discord bot nodejs

eris-fleet's Introduction

โš ๏ธ This repo will no longer be updated since eris-fleet has been moved to a monorepo to make adaptation of it for the Oceanic library easier. This monorepo is wumpus-navy. For the latest version see the monorepo. Eris-fleet the package is not deprecated and the npm package will be updated under the same name as before. This repo is deprecated.

The documentation has moved as well: https://danclay.github.io/wumpus-navy/

About eris-fleet

A spin-off of eris-sharder and megane with services and configurable logging.

For detailed documentation check the docs.

eris-fleet currently supports Eris v0.16.x.

Highlighted Features:

  • Clustering across cores
  • Sharding
  • Recalculate shards with minimal downtime
  • Update a bot with minimal downtime using soft restarts
  • Customizable logging
  • Fetch data from across clusters easily
  • Services (non-eris workers)
  • IPC to communicate between clusters, other clusters, and services
  • Detailed stats collection
  • Soft cluster and service restarts where the old worker is killed after the new one is ready
  • Graceful shutdowns
  • Central request handler
  • Central data store
  • Can use a modified version of Eris
  • Concurrency support

A very basic diagram:

Basic diagram

Help

If you still have questions, you can join the support server on Discord: Discord server

Support server on Discord

Installation

Run npm install eris-fleet or with yarn: yarn add eris-fleet.

To use a less refined, but more up-to-date branch, use npm install danclay/eris-fleet#dev or yarn add danclay/eris-fleet#dev. Documentation for the dev branch.

Basics

Some working examples are in test/.

Naming Conventions

Term Description
"fleet" All the components below
"admiral" A single sharding manager
"worker" A worker for node clustering
"cluster" A worker containing Eris shards
"service" A worker that does not contain Eris shards, but can interact with clusters

Get Started

To get started, you will need at least 2 files:

  1. Your file which will create the fleet. This will be called "index.js" for now.
  2. Your file containing your bot code. This will be called "bot.js" for now. This file will extend BaseClusterWorker

In the example below, the variable options is passed to the admiral. Read the docs for what options you can pass.

Here is an example of index.js:

const { isMaster } = require('cluster');
const { Fleet } = require('eris-fleet');
const path = require('path');
const { inspect } = require('util');

require('dotenv').config();

const options = {
    path: path.join(__dirname, "./bot.js"),
    token: process.env.token
}

const Admiral = new Fleet(options);

if (isMaster) {
    // Code to only run for your master process
    Admiral.on('log', m => console.log(m));
    Admiral.on('debug', m => console.debug(m));
    Admiral.on('warn', m => console.warn(m));
    Admiral.on('error', m => console.error(inspect(m)));

    // Logs stats when they arrive
    Admiral.on('stats', m => console.log(m));
}

This creates a new Admiral that will manage bot.js running in other processes. More details

The following is an example of bot.js. Read the IPC docs for what you can access and do with clusters.

const { BaseClusterWorker } = require('eris-fleet');

module.exports = class BotWorker extends BaseClusterWorker {
    constructor(setup) {
        // Do not delete this super.
        super(setup);

        this.bot.on('messageCreate', this.handleMessage.bind(this));

        // Demonstration of the properties the cluster has (Keep reading for info on IPC):
        this.ipc.log(this.workerID); // ID of the worker
        this.ipc.log(this.clusterID); // The ID of the cluster
    }

    async handleMessage(msg) {
        if (msg.content === "!ping" && !msg.author.bot) {
            this.bot.createMessage(msg.channel.id, "Pong!");
        }
    }

	handleCommand(dataSentInCommand) {
		// Optional function to return data from this cluster when requested
		return "hello!"
	}

    shutdown(done) {
        // Optional function to gracefully shutdown things if you need to.
        done(); // Use this function when you are done gracefully shutting down.
    }
}

Make sure your bot file extends BaseClusterWorker! The bot above will respond with "Pong!" when it receives the command "!ping".

Services

You can create services for your bot. Services are workers which do not interact directly with Eris. Services are useful for processing tasks, a central location to get the latest version of languages for your bot, custom statistics, and more! Read the IPC docs for what you can access and do with services. Note that services always start before the clusters. Clusters will only start after all the services have started. More details

To add a service, add the following to the options you pass to the fleet:

const options = {
    // Your other options...
    services: [{name: "myService", path: path.join(__dirname, "./service.js")}]
}

Add a new array element for each service you want to register. Make sure each service has a unique name or else the fleet will crash.

Here is an example of service.js:

const { BaseServiceWorker } = require('eris-fleet');

module.exports = class ServiceWorker extends BaseServiceWorker {
    constructor(setup) {
        // Do not delete this super.
        super(setup);

        // Run this function when your service is ready for use. This MUST be run for the worker spawning to continue.
        this.serviceReady();

        // Demonstration of the properties the service has (Keep reading for info on IPC):
    	this.ipc.log(this.workerID); // ID of the worker
    	this.ipc.log(this.serviceName); // The name of the service

    }
    // This is the function which will handle commands
    async handleCommand(dataSentInCommand) {
        // Return a response if you want to respond
        return dataSentInCommand.smileyFace;
    }

    shutdown(done) {
        // Optional function to gracefully shutdown things if you need to.
        done(); // Use this function when you are done gracefully shutting down.
    }
}

Make sure your service file extends BaseServiceWorker! This service will simply return a value within an object sent to it within the command message called "smileyFace". Services can be used for much more than this though. To send a command to this service, you could use this:

const reply = await this.ipc.command("myService", {smileyFace: ":)"}, true);
this.bot.createMessage(msg.channel.id, reply);

This command is being sent using the IPC. In this command, the first argument is the name of the service to send the command to, the second argument is the message to send it (in this case a simple object), and the third argument is whether you want a response (this will default to false unless you specify "true"). If you want a response, you must await the command or use .then().

Handling service errors

If you encounter an error while starting your service, run this.serviceStartingError('error here') instead of this.serviceReady(). Using this will report an error and restart the worker. Note that services always start before the clusters, so if your service keeps having starting errors your bot will be stuck in a loop. This issue may be fixed in the future from some sort of maxRestarts option, but this is currently not a functionality.

If you encounter an error when processing a command within your service, you can do the following to reject the promise:

// handleCommand function within the ServiceWorker class
async handleCommand(dataSentInCommand) {
    // Rejects the promise
    return {err: "Uh oh.. an error!"};
}

When sending the command, you can do the following to deal with the error:

this.ipc.command("myService", {smileyFace: ":)"}, true).then((reply) => {
    // A successful response
    this.bot.createMessage(msg.channel.id, reply);
}).catch((e) => {
    // Do whatever you want with the error
    console.error(e);
});

In-depth

Below is more in-depth documentation.

Admiral

Admiral options

Visit the docs for a complete list of options.

Admiral events

Visit the docs for a complete list of events.

Central Request Handler

The central request handler forwards Eris requests to the master process where the request is sent to a single Eris request handler instance. This helps to prevent 429 errors from occurring when you have x number of clusters keeping track of ratelimiting separately. When a response is received, it is sent back to the cluster's Eris client.

Large Bots

If you are using a "very large bot," Discord's special gateway settings apply. Ensure your shard count is a multiple of the number set by Discord or set options.shards and options.guildsPerShard to "auto". You may also be able to use concurrency (see below).

Concurrency

Eris-fleet supports concurrency by starting clusters at the same time based on your bot's max_concurrency value. The clusters are started together in groups. The max_concurrency value can be overridden with options.maxConcurrencyOverride

Formats

Visit the docs to view the Typescript interfaces.

Choose what to log

You can choose what to log by using the whatToLog property in the options object. You can choose either a whitelist or a blacklist of what to log. You can select what to log by using an array. To possible array elements are shown on the docs. Here is an example of choosing what to log:

const options = {
    // Your other options
    whatToLog: {
        // This will only log when the admiral starts, when clusters are ready, and when services are ready.
        whitelist: ['admiral_start', 'cluster_ready', 'service_ready']
    }
};

Change whitelist to blacklist if you want to use a blacklist. Change the array as you wish. Errors and warnings will always be sent.

IPC

Clusters and services can use IPC to interact with other clusters, the Admiral, and services. Visit the IPC docs to view available methods.

Stats

Stats are given in this format.

Using a specific version of Eris or a modified version of Eris

You can use an extended Eris client by passing it to the Options. (see the options.customClient section).

Eris-fleet is able to use packages such as eris-additions if you desire. To do so, modify your bot file to match the following template:

// Example using eris-additions
const { Fleet } = require("eris-fleet");
const Eris = require("eris-additions")(require("eris"));

const options = {
    // other options
    customClient: Eris
}
const Admiral = new Fleet(options);

Using ES Modules

Instead of using the file path, you can use ES Modules by passing your BotWorker class to options.BotWorker and your ServiceWorker class to ServiceWorker in the options.services array. See test/ for examples.

eris-fleet's People

Contributors

brainicism avatar danclay avatar dependabot-preview[bot] avatar dependabot[bot] avatar theessem avatar tom-beijner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

eris-fleet's Issues

incorrectly typing id parameters as numbers

Describe the bug
context:

public async fetchUser(id: number): Promise<any> {

public async fetchGuild(id: number): Promise<any> {

public async fetchChannel(id: number): Promise<any> {

public async fetchMember(guildID: number, memberID: number): Promise<any> {

possibly more; unsure

JavaScript doesn't handle discord snowflake IDs properly as numbers; see
image

These types should be changed to strings (or bigints though thats just silly because they would need to be converted into a string instantly)

This library should also perhaps be tested? testing would've seen that this is an issue

Version N/A

To Reproduce N/A

workerID of undefined

It seems like letting the bot run I get the error of "TypeError: Cannot read property 'workerID' of undefined' occasionally appear. I don't have a consistent way of testing this other than letting the bot run.
Reference
Note: The bot was running on Ubuntu

Incorrect amount of users on clusters

Describe the bug
The cluster is showing 1 when the shard itself is not in a guild

Version
v0.2.4

To Reproduce
Console log the clusters users amount

Expected behavior
Should display 0

Custom logging

Decent library so far but I'd like to be able to disable built-in logging so I can receive logging events on the main file (ie. index.js) and pipe them into my custom logging functionality.

So instead of something like this:
image
Or any sort of general logging like shard ready, it'd send a JSON object to an event (via IPC or just by using an event handler on the main Fleet) that can then be handled appropriately.

EDIT: Re clarifying that I'm suggesting a JSON object to be passed instead of just the errors that come right now.

Shard disconnect error with undefined

Describe the bug
Shard disconnect with an error which is undefined, which later the shard resumes.

Version
v0.2.4

To Reproduce
I don't have a way to reproduce it as it seems it just happens while running.

Expected behavior
Show the actual error

Screenshots
Reference

Additional context
The issue occurred on a Ubuntu machine I have not tested if this occurs on my Windows machine.

Workers stuck and never connect

Workers sometimes launch after the connect message is sent.
Workers are launching after the connect message is sent from the queue being executed. This causes the log to get stuck at "shards spread."

Shutdown stops after failure inside shutdown function

Describe the bug
A clear and concise description of what the bug is.

Version
0.3.3

To Reproduce
Shutdown a worker with some non-functional code inside the the shutdown function

Expected behavior
The kill timeout to kick in and kill the worker forcefully

Add more IPC shardstats properties

I would like to see users and guilds to be added to the shardstats function.

Edit: I mean the properties of the shards in the getStats function.

How do I have the events emitted by Fleet in their own files?

Hi, got a question.

My event loader works for Discord events however I'm not sure how to access the events emitted by Fleet since const Admiral = new Fleet(...) is in index.js and cannot be accessed elsewhere.

I want them in separate files since I use classes which means I can access bot, ipc etc and can use webhooks for Discord logging. It also enables me to post stats received from the stats event to my own API.

Solved by danclay on Discord.

shard undefined

Describe the bug
when errors are logged, the log that is showen is 2|Counting | Cluster 6, Shard undefined | [DEPRECATED] Member#permission is deprecated. Use Member#permissions instead
Version
0.3.5

To Reproduce
log an eris-fleet error
Expected behavior
my expected behavior is that the shard that the error ocurrd on will show as in this log
2|Counting | Cluster 6, Shard 6 | [DEPRECATED] Member#permission is deprecated. Use Member#permissions instead

shutdowns giving "TypeError: Cannot redefine property: _token"

When using shutdownCluster or totalShutdown an error occurs:
TypeError: Cannot redefine property: _token

This error is caused by a bug in eris. The bug is fixed in the dev branch of eris. Use abalabahaha/eris#dev. Once eris's dev branch is merged into the master branch eris-fleet's eris dependency will be updated.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.