165 lines
5.1 KiB
Python
165 lines
5.1 KiB
Python
# flake8: noqa
|
|
|
|
# __begin_deploy_app_with_uniform_request_router__
|
|
from ray import serve
|
|
from ray.serve.request_router import ReplicaID
|
|
import time
|
|
from collections import defaultdict
|
|
from ray.serve.context import _get_internal_replica_context
|
|
from typing import Any, Dict
|
|
from ray.serve.config import RequestRouterConfig
|
|
|
|
|
|
@serve.deployment(
|
|
request_router_config=RequestRouterConfig(
|
|
request_router_class="custom_request_router:UniformRequestRouter",
|
|
),
|
|
num_replicas=10,
|
|
ray_actor_options={"num_cpus": 0},
|
|
)
|
|
class UniformRequestRouterApp:
|
|
def __init__(self):
|
|
context = _get_internal_replica_context()
|
|
self.replica_id: ReplicaID = context.replica_id
|
|
|
|
async def __call__(self):
|
|
return self.replica_id
|
|
|
|
|
|
handle = serve.run(UniformRequestRouterApp.bind())
|
|
response = handle.remote().result()
|
|
print(f"Response from UniformRequestRouterApp: {response}")
|
|
# Example output:
|
|
# Response from UniformRequestRouterApp:
|
|
# Replica(id='67vc4ts5', deployment='UniformRequestRouterApp', app='default')
|
|
# __end_deploy_app_with_uniform_request_router__
|
|
|
|
|
|
# __begin_deploy_app_with_throughput_aware_request_router__
|
|
def _time_ms() -> int:
|
|
return int(time.time() * 1000)
|
|
|
|
|
|
@serve.deployment(
|
|
request_router_config=RequestRouterConfig(
|
|
request_router_class="custom_request_router:ThroughputAwareRequestRouter",
|
|
request_routing_stats_period_s=1,
|
|
request_routing_stats_timeout_s=1,
|
|
),
|
|
num_replicas=3,
|
|
ray_actor_options={"num_cpus": 0},
|
|
)
|
|
class ThroughputAwareRequestRouterApp:
|
|
def __init__(self):
|
|
self.throughput_buckets: Dict[int, int] = defaultdict(int)
|
|
self.last_throughput_buckets = _time_ms()
|
|
context = _get_internal_replica_context()
|
|
self.replica_id: ReplicaID = context.replica_id
|
|
|
|
def __call__(self):
|
|
self.update_throughput()
|
|
return self.replica_id
|
|
|
|
def update_throughput(self):
|
|
current_timestamp_ms = _time_ms()
|
|
|
|
# Under high concurrency, requests can come in at different times. This
|
|
# early return helps to skip if the current_timestamp_ms is more than a
|
|
# second older than the last throughput bucket.
|
|
if current_timestamp_ms < self.last_throughput_buckets - 1000:
|
|
return
|
|
|
|
# Record the request to the bucket
|
|
self.throughput_buckets[current_timestamp_ms] += 1
|
|
self.last_throughput_buckets = current_timestamp_ms
|
|
|
|
def record_routing_stats(self) -> Dict[str, Any]:
|
|
current_timestamp_ms = _time_ms()
|
|
throughput = 0
|
|
|
|
for t, c in list(self.throughput_buckets.items()):
|
|
if t < current_timestamp_ms - 1000:
|
|
# Remove the bucket if it is older than 1 second
|
|
self.throughput_buckets.pop(t)
|
|
else:
|
|
throughput += c
|
|
|
|
return {
|
|
"throughput": throughput,
|
|
}
|
|
|
|
|
|
handle = serve.run(ThroughputAwareRequestRouterApp.bind())
|
|
response = handle.remote().result()
|
|
print(f"Response from ThroughputAwareRequestRouterApp: {response}")
|
|
# Example output:
|
|
# Response from ThroughputAwareRequestRouterApp:
|
|
# Replica(id='tkywafya', deployment='ThroughputAwareRequestRouterApp', app='default')
|
|
# __end_deploy_app_with_throughput_aware_request_router__
|
|
|
|
|
|
# __begin_deploy_app_with_round_robin_router__
|
|
@serve.deployment(
|
|
request_router_config=RequestRouterConfig(
|
|
request_router_class=(
|
|
"ray.serve.experimental.round_robin_router:RoundRobinRouter"
|
|
),
|
|
),
|
|
num_replicas=4,
|
|
ray_actor_options={"num_cpus": 0},
|
|
)
|
|
class RoundRobinRouterApp:
|
|
def __init__(self):
|
|
context = _get_internal_replica_context()
|
|
self.replica_id: ReplicaID = context.replica_id
|
|
|
|
async def __call__(self):
|
|
return self.replica_id
|
|
|
|
|
|
handle = serve.run(RoundRobinRouterApp.bind())
|
|
response = handle.remote().result()
|
|
print(f"Response from RoundRobinRouterApp: {response}")
|
|
# __end_deploy_app_with_round_robin_router__
|
|
|
|
|
|
# __begin_deploy_app_with_consistent_hash_router__
|
|
import requests
|
|
from starlette.requests import Request
|
|
|
|
@serve.deployment(
|
|
request_router_config=RequestRouterConfig(
|
|
request_router_class=(
|
|
"ray.serve.experimental.consistent_hash_router:ConsistentHashRouter"
|
|
),
|
|
request_router_kwargs={
|
|
"num_virtual_nodes": 100,
|
|
"num_fallback_replicas": 2,
|
|
},
|
|
),
|
|
num_replicas=4,
|
|
ray_actor_options={"num_cpus": 0},
|
|
)
|
|
class ConsistentHashRouterApp:
|
|
def __init__(self):
|
|
context = _get_internal_replica_context()
|
|
self.replica_id: ReplicaID = context.replica_id
|
|
|
|
async def __call__(self, request: Request) -> str:
|
|
return str(self.replica_id)
|
|
|
|
|
|
serve.run(ConsistentHashRouterApp.bind())
|
|
|
|
# Clients pin a session to a replica by sending the same `x-session-id`
|
|
# on every request.
|
|
response = requests.get(
|
|
"http://localhost:8000/",
|
|
headers={"x-session-id": "example-session-id"},
|
|
)
|
|
print(f"Response from ConsistentHashRouterApp: {response.text}")
|
|
# Example output:
|
|
# Response from ConsistentHashRouterApp:
|
|
# Replica(id='...', deployment='ConsistentHashRouterApp', app='default')
|
|
# __end_deploy_app_with_consistent_hash_router__
|