1.1 - 基本的rpc调用

在这里插入图片描述

1.2 - 引入netty框架

在这里插入图片描述

Client

客户端有服务端的功能接口,因此通过此接口创建代理对象,此对象内部会有netty的客户端,当用户调用代理对象的方法时,netty的客户端会向服务端发送请求并返回数据

Server

在服务端启动后,会把自身放入一个map中,在收到请求后且是RpcRequest时,会触发NettyRPCServerHandler处理器,根据RpcRequest中的interfaceName信息,找到服务端的实例,再调用相关的方法,最后返回结果

1.3 - 引入zookeeper作为注册中心

在这里插入图片描述

2.1 - 自定义编码器

解码器和序列化器

BUG

fastJson: 接口的方法的参数类型要用包装类,负责会出现类型不匹配的bug

1
MyDecoder解码失败java.lang.ClassCastException: class java.lang.Integer cannot be cast to class com.alibaba.fastjson.JSONObject (java.lang.Integer is in module java.base of loader 'bootstrap'; com.alibaba.fastjson.JSONObject is in unnamed module of loader 'app')

2.2 - 在客户端建立本地服务缓存并实现动态更新

在这里插入图片描述

3.1 - 负载均衡

在这里插入图片描述

负载均衡算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//虚假节点的数量 一个真实节点对应5个虚拟节点
private static final int VIRTUAL_NUM = 5;

//虚拟节点的分配
private SortedMap<Integer, String> shards = new TreeMap<>();

//真实节点列表
private List<String> realNodes = new ArrayList<>();

//模拟初始服务器
private String[] servers = null;

private void init(List<String> serviceList) {
for (String server : serviceList) {
realNodes.add(server);
System.out.println("真实节点[" + server + "] 被添加");
for (int i = 0; i < VIRTUAL_NUM; i++) {
String virtualNode = server + "&&VN" + i;
int hash = getHash(virtualNode);
shards.put(hash, virtualNode);
System.out.println("虚拟节点[" + virtualNode + "] hash:" + hash + ",被添加");
}
}
}

public String getServer(String node, List<String> serviceList) {
this.init(serviceList);
int hash = getHash(node);
Integer key = null;
SortedMap<Integer, String> subMap = shards.tailMap(hash);
if (subMap.isEmpty()) {
key = shards.lastKey();
} else {
key = subMap.firstKey();
}
String virtualNode = shards.get(key);
return virtualNode.substring(0, virtualNode.indexOf("&&"));
}

/**
* @param: str
* @return: int
* @description: FNV1_32_HASH算法
*/
private static int getHash(String str) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < str.length(); i++) {
hash = (hash ^ str.charAt(i)) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
if (hash < 0) {
hash = Math.abs(hash);
}
return hash;
}

public String balance(List<String> addressList) {
String random = UUID.randomUUID().toString();
return getServer(random, addressList);
}

3.2 - 超时重试&白名单

使用 guavaRetry 实现超时重连

zk 上新增节点 MyRPC/CanRetry 代表可以超时重试的服务

Client

在这里插入图片描述

Server

在这里插入图片描述

4.1 - 服务-限流

在这里插入图片描述

4.2 - 服务-熔断

在这里插入图片描述

6.1 - 日志链路追踪

使用 Slf4jMDC 日志记录时提供额外的上下文信息,
客户端在进行方法调用前会将 traceID 保存在 MDC 种,之后在正式发送请求前,会将 traceID 相关内容存入保存到Channel属性,因为在 channel 发送数据时, netty 内部会新起一个 channel 线程用于发送数据,如果不把 traceID 相关内容存入保存到Channel属性,那么在编码过程种会无法获取 traceID 相关内容,那么服务端就会无法实现日志链路追踪
在这里插入图片描述

BUG

反序列化出现错误, 无法返回Resonse.fail()

错误代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public Object deserialize(byte[] bytes, int messageType) {
Object obj = null;
// 传输的消息分为request与response
switch (messageType){
case 0:
RpcRequest request = JSON.parseObject(bytes, RpcRequest.class);
Object[] objects = new Object[request.getParams().length];
// 把json字串转化成对应的对象, fastjson可以读出基本数据类型,不用转化
// 对转换后的request中的params属性逐个进行类型判断
for(int i = 0; i < objects.length; i++){
Class<?> paramsType = request.getParamType()[i];
//判断每个对象类型是否和paramsTypes中的一致
if (!paramsType.isAssignableFrom(request.getParams()[i].getClass())){
//如果不一致,就行进行类型转换
objects[i] = JSONObject.toJavaObject((JSONObject) request.getParams()[i], request.getParamType()[i]);
}else{
//如果一致就直接赋给objects[i]
objects[i] = request.getParams()[i];
}
}
request.setParams(objects);
obj = request;
break;
case 1:
RpcResponse response = JSON.parseObject(bytes, RpcResponse.class);
Class<?> dataType = response.getDataType();
//判断转化后的response对象中的data的类型是否正确
if(! dataType.isAssignableFrom(response.getData().getClass())){
response.setData(JSONObject.toJavaObject((JSONObject) response.getData(),dataType));
}
obj = response;
break;
default:
System.out.println("暂时不支持此种消息");
throw new RuntimeException();
}
return obj;
}

正确代码

    public Object deserialize(byte[] bytes, int messageType) {
        Object obj = null;
        // 传输的消息分为request与response
        switch (messageType){
            case 0:
                RpcRequest request = JSON.parseObject(bytes, RpcRequest.class);
                Object[] objects = new Object[request.getParams().length];
                // 把json字串转化成对应的对象, fastjson可以读出基本数据类型,不用转化
                // 对转换后的request中的params属性逐个进行类型判断
                for(int i = 0; i < objects.length; i++){
                    Class<?> paramsType = request.getParamType()[i];
                    //判断每个对象类型是否和paramsTypes中的一致
                    if (!paramsType.isAssignableFrom(request.getParams()[i].getClass())){
                        //如果不一致,就行进行类型转换
                        objects[i] = JSONObject.toJavaObject((JSONObject) request.getParams()[i],request.getParamType()[i]);
                    }else{
                        //如果一致就直接赋给objects[i]
                        objects[i] = request.getParams()[i];
                    }
                }
                request.setParams(objects);
                obj = request;
                break;
            case 1:
                RpcResponse response = JSON.parseObject(bytes, RpcResponse.class);
                // 如果类型为空,说明返回错误
                if(response.getDataType()==null){
                    obj = RpcResponse.fail();
                    break;
                }
                Class<?> dataType = response.getDataType();
                //判断转化后的response对象中的data的类型是否正确
                if(!dataType.isAssignableFrom(response.getData().getClass())){
                    response.setData(JSONObject.toJavaObject((JSONObject) response.getData(),dataType));
                }
                obj = response;
                break;
            default:
                System.out.println("暂时不支持此种消息");
                throw new RuntimeException();
        }
        return obj;
    }

);
                }
                obj = response;
                break;
            default:
                System.out.println("暂时不支持此种消息");
                throw new RuntimeException();
        }
        return obj;
    }