') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - Praveenduddu/CacheMesh: Using P2P Networking For In Memory Caching between Microservices · GitHub
Skip to content

Latest commit

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheMesh

Build StatusMaven CentralLicense

Using P2P Networking For A Decentralized In-Memory Cache Between Microservices

CacheMesh is a decentralized, distributed in-memory caching system designed specifically for microservices environments. It leverages a peer-to-peer (P2P) network, allowing services to form a caching cluster without a centralized server. This eliminates single points of failure and simplifies deployment.


💡 What is CacheMesh?

In a typical microservices architecture, services often rely on a centralized caching layer like Redis or Memcached. While powerful, this introduces a separate component to manage, scale, and maintain.

CacheMesh takes a different approach. It embeds a lightweight caching node directly within your application. These nodes discover each other and form a distributed hash table (DHT) across the network. When you write data to the cache on one service, it's distributed to the appropriate peer based on the key. When you read data, your service automatically fetches it from the correct peer if it's not available locally.

This creates a shared, high-speed, in-memory cache that scales horizontally with your services.

✨ Features

  • Decentralized: No central server or coordinator. No single point of failure.
  • Peer-to-Peer Discovery: Nodes automatically discover each other using a gossip protocol.
  • Consistent Hashing: Keys are distributed across the cluster using a consistent hashing algorithm, minimizing rebalancing when nodes join or leave.
  • Embeddable: Integrates directly into your Go application as a library.
  • Spring Boot Native: First-class integration with Spring Boot for auto-configuration and easy management.
  • Lightweight: Designed to have a small memory and CPU footprint.
  • Scalable: Scales horizontally as you add more service instances.
  • Simple API: A familiar Get, Set, Delete API for caching.

🏗️ Architecture

Each instance of your microservice embeds a CacheMesh node.

  1. Bootstrap: On startup, a node connects to one or more initial "bootstrap" peers to join the cluster.
  2. Gossip: Once connected, it uses a gossip protocol to learn about other nodes in the network and maintain an up-to-date membership list.
  3. Consistent Hashing: A consistent hash ring is used to map cache keys to specific nodes. This ensures that keys are evenly distributed and that adding/removing nodes has minimal impact on key location.
  4. Data Flow:
    • When you call Set(key, value), the local node hashes the key to determine which node in the cluster is responsible for it. It then sends a request to that node to store the data.
    • When you call Get(key), the local node hashes the key and requests the data from the responsible peer.
 +------------------+ +------------------+
| | | |
| Microservice A |<--------->| Microservice B |
| [CacheMesh Node] | Gossip | [CacheMesh Node] |
| | Protocol | |
+------------------+ +------------------+
^ ^
| |
v v
+------------------+ +------------------+
| | | |
| Microservice C |<--------->| Microservice D |
| [CacheMesh Node] | | [CacheMesh Node] |
| | | |
+------------------+ +------------------+
Consistent Hashing Ring determines data ownership

🚀 Getting Started

Prerequisites

  • Go 1.18 or later

Installation

go get github.com/your-repo/cachemesh

Quick Start

Here is a simple example of how to run two CacheMesh nodes that connect to each other.

main.go

package main
import (
"fmt""log""time""github.com/your-repo/cachemesh"
)
funcmain() {
// Get node port from command-line argument, e.g., ":3000"// Get bootstrap node from command-line argument, e.g., "localhost:3000"// For simplicity, we'll hardcode them here.// Node 1 configurationopts1:= cachemesh.Options{
ListenAddr: ":3000",
BootstrapNodes: []string{}, // First node has no one to connect to initially
}
// Node 2 configurationopts2:= cachemesh.Options{
ListenAddr: ":4000",
BootstrapNodes: []string{":3000"}, // Second node connects to the first
}
// In a real app, you'd run these in separate processes.// We use goroutines here to simulate it.node1, err:=cachemesh.New(opts1)
iferr!=nil {
log.Fatalf("failed to create node 1: %v", err)
}
gonode1.Start()
node2, err:=cachemesh.New(opts2)
iferr!=nil {
log.Fatalf("failed to create node 2: %v", err)
}
gonode2.Start()
// Let the nodes discover each othertime.Sleep(2*time.Second)
// Set a key from Node 2key:= []byte("hello")
value:= []byte("world")
ttl:=10*time.Secondiferr:=node2.Set(key, value, ttl); err!=nil {
log.Fatalf("failed to set key: %v", err)
}
fmt.Println("Set 'hello' -> 'world' from Node 2")
// Get the key from Node 1retrievedValue, err:=node1.Get(key)
iferr!=nil {
log.Fatalf("failed to get key: %v", err)
}
fmt.Printf("Got '%s' -> '%s' from Node 1\n", key, retrievedValue)
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a pull request.

  1. Fork the repository.
  2. Create your feature branch (git checkout -b feature/my-new-feature).
  3. Commit your changes (git commit -am 'Add some feature').
  4. Push to the branch (git push origin feature/my-new-feature).
  5. Create a new Pull Request.

📜 License

Copyright (c) 2024 Your Name or Company

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

About

Using P2P Networking For In Memory Caching between Microservices

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors