Designing a Transparent RPC Framework for Distributed Data Access and Its Application in Redis Cluster
The article explains how to abstract remote data location logic using an RPC framework built on Spring and Dubbo, demonstrates proxy injection and service registration, and shows how similar principles are applied to Redis Cluster to reduce request redirection and improve scalability.
HTTP is a stateless protocol, so session information is carried via cookies; similarly, servers can be stateless or stateful depending on whether they store data.
In a typical scenario a client request may land on a front‑end server (e.g., 174.56.102.101) while the actual data resides on another node (e.g., 174.56.102.102 or 174.56.102.103), requiring the front‑end to forward the request.
Writing explicit forwarding logic in every business function leads to massive code duplication, so the goal is to let developers call ordinary functions while the framework determines the real data location.
Using an RPC framework such as Dubbo, remote service calls can look like local method invocations.
Consumer side design : define an annotation @RPCReference to mark fields that should be injected with a proxy. Example:
@Service
@Slf4j
public class ComputeService {
@RPCReference
private IResourceService iResourceService;
// ...
public Object method(param){
// normal call
iResourceService.m1();
}
}The proxy is created by ConsumerProxyFactory which implements InvocationHandler:
@Slf4j
public class ConsumerProxyFactory implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> clazz = proxy.getClass().getInterfaces()[0];
if (method.getName().contains("toString")) {
return Boolean.TRUE;
}
// decide local or remote based on node
if (当前节点) {
return invodeMethod(clazz, method, args);
}
// remote RPC flow
RPCRequest req = buildRpcReq(clazz, method, args);
Map<String, String> headerMap = buildHeaderMap(requestString);
String responseString = HttpClientUtil.postRequest(url, req, headerMap);
return JSONObject.parseObject(responseString, method.getGenericReturnType());
}
// ... other helper methods ...
}A BeanPostProcessor scans beans for fields annotated with @RPCReference and injects the proxy via reflection:
@Component
@Slf4j
public class RPCReferenceBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
do {
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
if (!hasAnnotation(field.getAnnotations(), RPCReference.class.getName())) {
continue;
}
setField(bean, field);
}
} while ((beanClass = beanClass.getSuperclass()) != null);
return bean;
}
private void setField(Object bean, Field field) {
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
if (field.getType().isInterface()) {
Class<?> interfaceClass = field.getType();
Object object = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new ConsumerProxyFactory());
field.set(bean, object);
} else {
throw new RPCException("10000", field.getType().getName() + "-Referenc only suiteable for interface");
}
} catch (Exception e) {
// handle
}
}
// ... annotation helper ...
}Service side design : implement the business interface (e.g., IResourceService) and annotate the implementation with @RPCService so that Spring can discover it.
@Slf4j
@RPCService
@Service
public class ResourceService implements IResourceService {
public ResourceDTO query() {
// business logic
return resourceDTO;
}
}A loader scans all beans annotated with @RPCService and stores them in a concurrent map for later lookup:
@Slf4j
@Configuration
public class RpcServiceLoader {
private static Map<Class<?>, Object> providers = new ConcurrentHashMap<>();
public static Object getProviders(Class<?> clazz) {
return providers.get(clazz);
}
@Bean
@Autowired
RpcServiceFactory getProviderProxyFactory(ProviderConfig providerConfig, ApplicationContext ct) {
RpcServiceFactory rpcServiceFactory = new RpcServiceFactory();
Map<String, Object> map = ct.getBeansWithAnnotation(RPCService.class);
for (Object bean : map.values()) {
Class<?> interFaceClazz = AopUtils.getTargetClass(bean).getInterfaces()[0];
providers.put(interFaceClazz, bean);
}
return rpcServiceFactory;
}
}The RpcServiceFactory receives the serialized request, deserializes it, locates the target bean, and invokes the method via reflection:
public class RpcServiceFactory {
public Object handleHttpContent(String reqStr) throws Throwable {
RPCRequest req = RPCSerializer.INSTANCE.requestParse(reqStr);
Class<?> clazz = req.getClazz();
String methodName = req.getMethodName();
Object[] args = req.getArguments();
String[] parameterTypeNames = req.getParameterTypeNames();
Class<?>[] parameterTypes = Arrays.stream(parameterTypeNames).map(this::classForName).toArray(Class[]::new);
Method method = clazz.getMethod(methodName, parameterTypes);
Object bean = RpcServiceLoader.getProviders(clazz);
// convert args (handle List types)
// invoke method and return result
if (Void.TYPE.equals(method.getReturnType())) {
method.invoke(bean, args);
return Void.TYPE.getName();
}
return method.invoke(bean, args);
}
// ... helper methods ...
}The same principle is applied to Redis Cluster: keys are mapped to one of 16384 hash slots using CRC16; each master holds a subset of slots. When a client writes, it may contact any master, which computes the slot and either serves the request locally or returns a MOVED redirection.
JedisCluster implements a local slot‑to‑node cache, updates it upon receiving MOVED responses, and retries up to five times, reducing network round‑trips compared with naïve redirection handling.
Optimizations in newer Jedis versions avoid excessive slot updates and ping storms during node failures, improving stability of large‑scale Redis deployments.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
