The Small Resource Server is designed to handle resource locking and synchronization efficiently in high-load microservices environments. By centralizing resource access, it prevents race conditions and inconsistent state across distributed services, while remaining lightweight and fast thanks to its Swoole-based concurrency model. This reduces contention, improves throughput, and ensures that critical operations—like payments, reservations, or inventory updates—remain reliable even under heavy parallel traffic.
It also allowing you a good way for long multi processes / muti servers batches synchronization.
Small Resource Server offers a tiny, language‑agnostic HTTP API for just that:
- Acquire a named lock with optional TTL & owner token
- Sharing associated data
The Symfony Client Bundle gives you ergonomic, resilient access from Symfony with
-
HttpClientInterface
integration - DI configuration (
base_uri
, API key, timeouts) - Straightforward
Resource
facade/service APIs
Architecture (high level)
-
Small Resource Server
- Runtime: PHP + Swoole/OpenSwoole HTTP server
- In‑memory map of
resourceName -> lease
with optional pluggable storage (Redis/DB) if enabled - Lease data:
owner
,token
,acquiredAt
,ttl
,expiresAt
- Background janitor to evict expired leases
- Minimal REST endpoints
-
Client Bundle (Symfony)
- Registers a named HTTP client preconfigured with
base_uri
, headers (x-api-key
), and default timeouts - Exposes a
ResourceFactory
/ResourceClient
service toacquire()
,renew()
, andrelease()
- Optional retry/backoff on transient errors and clock skew tolerance
- Registers a named HTTP client preconfigured with
HTTP API (server)
Create resource
POST /resource Headers: x-api-key: <WRITE key> Body (JSON): { "name": "printer", "timeout": 300 // optional server-side semantics } 201 Created Content-Type: application/json { ...resource... }
Get resource data (with optional lock)
GET /resource/{resourceName}/{selector}?lock=1 Headers: x-api-key: <READ or READ+LOCK> x-ticket: <existing ticket | optional> Query: lock=1 (default) → attempt to acquire/keep lock lock=0 → read without locking Responses: - 200 OK + JSON body → resource data is available + Header: x-ticket: <ticket> - 202 Accepted → not available yet (locked by someone else), body: { "unavailable": true } + Header: x-ticket: <ticket> (your ticket to retry with) - 404 Not Found → resource/selector unknown - 401 Unauthorized → missing/invalid x-api-key / missing LOCK when lock=1
Update resource data
PUT /resource/{resourceName}/{selector} Headers: x-api-key: <WRITE key> x-ticket: <ticket from GET> Body: raw JSON payload to store 204 No Content
Unlock resource
POST /resource/{resourceName}/{selector}/unlock Headers: x-api-key: <READ+LOCK> x-ticket: <ticket> 200 OK { "unlocked": true }
Security: Accept an
x-api-key
header; prefer TLS; rate‑limit abusive clients.
📦 Installation
Server (Docker Hub image)
The easiest way to run the server is via Docker:
# Pull the latest image from Docker Hub docker pull smallphp/small-resource-server:latest # Run it (adjust API_KEY and port as needed) docker run -d \ --name resource-server \ --volume "./conf/small-swoole-resource-server.conf:/etc/small-swoole-resource-server.conf" \ -p 8080:9501 \ smallphp/small-resource-server:latest
You can now access the server on http://localhost:8080.
Docker Compose example:
version: '3.8' services: resource-server: image: smallphp/small-resource-server:latest restart: unless-stopped ports: - "8080:9501" volumes: - ./conf/small-swoole-resource-server.conf:/etc/small-swoole-resource-server.conf database: container_name: small-swoole-resource-db image: mysql:8 environment: MYSQL_ROOT_PASSWORD: secret command: ["mysqld", "--mysql-native-password=FORCE"]
And config file example:
MYSQL_HOST=database MYSQL_USER=root MYSQL_PASSWORD=secret RESOURCE_READ=d8e8fca2dc0f896fd7cb4cb0031ba249 RESOURCE_READ_LOCK=ed4779d230afc18ca8df5213ba02b11e RESOURCE_WRITE=eadd7e1bbc06f288417df096bbedfa61
Symfony Client Bundle
Add the bundle to your app:
composer require small/swoole-resource-client-bundle
Register the bundle if Flex is not used:
return [ Small\SwooleResourceClientBundle\SmallSwooleResourceClientBundle::class => ['all' => true], ];
Configure the client:
# config/packages/small_resource_client.yaml small_swoole_resource_client: base_uri: '%env(RESOURCE_SERVER_BASE_URI)%' api_key: '%env(RESOURCE_SERVER_API_KEY)%' timeout: 10
Environment:
###> Small Resource Server ### RESOURCE_SERVER_BASE_URI=http://localhost:8080 RESOURCE_SERVER_API_KEY=eadd7e1bbc06f288417df096bbedfa61 ###< Small Resource Server ###
Quick start from Symfony
$lock = $this->resources->create('shop:42:inventory'); if ($lock->acquire('sync-worker', 30)) { $guard = $lock->autoRenew(every: 20, ttl: 30); try { $this->syncInventory(); } finally { $guard->stop(); $lock->release(); } }
Error handling & edge cases
- 409 Conflict → someone else holds the lock
- 403 Forbidden → token mismatch/expired
- Timeouts → handle with retry/backoff
- Crash safety → expired leases are automatically evicted
Security
- All traffic over HTTPS in production
- Protect with API keys (env var
API_KEY
) - Rotate secrets regularly
sources
On github
- resource server : https://github.com/sebk69/small-resource-server.git
- Symfony client bundle : https://github.com/ebk69/small-swoole-resource-client-bundle.git
On Docker Hub (server)
On Packagist (Symfony client bundle)
Top comments (0)