Enplee's blog
0%

木舟远远地越过这片海域
大大的船帆满满地承受着风势
直面前往新世界

Rewrite CANOE


你好 欢迎来到 Enplee's Blog!
Read more »

RPC的概念本质就是本地调用远程主机的方向像是在本地调用一样简单。对于使用RPC框架的开发人员来说,他们最终只需要写出一行Interface::Method(), 就可以实现远程的方法调用。那么就需要框架来实现如何屏蔽掉调用远程方法时候的一系列操作: 构建RpcRequest消息、消息发送,结果接受并按照方法返回。这个过程刚好可以通过代理模式实现。

代理模式

什么是代理模式呢?就像生活中的代理一样,被代理的类只负责核心的业务逻辑,而代理类负责处理核心逻辑之外的工作。所有对被代理的请求全部被都交给代理类全权打理。这样,一是可以增加额外的功能而不侵入核心逻辑的代码,同时这整个过程对完是完全不可见的。对于外部访问来说,他仅仅是调用了统一个接口的方法。

  • 静态代理

静态代理是最容易实现的代理模式。设计思想是代理类与被代理类实现相同的接口,并且代理类的内部组合了被代理类。代理类的内部实现了接口的方法,但是这个方法一定会调用了被代理类的方法,只不过在被代理类方法执行前后,增加额外的处理逻辑。之后发挥多态的特点,所有请求都是走的代理类。代理类的方法被调起的时候,被代理类或者说委托类的方法也会在内部被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface BuyHouse {
void buyHosue();
}
// 被代理类
public class BuyHouseImpl implements BuyHouse {
@Override
public void buyHosue() {
System.out.println("我要买房");
}
}

public class Proxy implements BuyHouse {

BuyHouseImpl buyHouseImpl;

@Override
public void buyHosue() {
System.out.println("-----");
buyHouseImpl.buyHosue();
System.out.println("-----");
}
}
  • JDK动态代理

静态代理的实现方式简单,但是如果我们有很多的业务逻辑类需要做代理,就要为每一个类同时创建一个代理类。如果需求有更改,还要做大量的修改。JDK为我们提供了动态代理的方式,我们只需要实现InvokationHanlder,JDK就会在运行的时候动态的创建出一个代理类。

1
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler invocationHandler)

需要传入委托类的类加载器、实现接口以及InvocationHandler。InvocationHandler是需要手动实现invoke方法。

在invoke(),方法参数是委托类被方法被调用时候的各种参数,如方法参数、方法参数类型等。获得的代理对象在调用委托类方法时候,其实是执行的Invoke方法中的逻辑。

JDK动态代理相对于静态代理打打减少了我们开发任务。但是JDK代理只支持为实现了接口的方法提供代理对象,由于java是单继承模式,无法为没有实现接口的类进行代理。这时候就需要另外一种动态代理方式:CGLib.

  • CGLib动态代理

CGLib动态代理采用字节码动态织入的方式。采用继承的方式,通过字节码技术为委托类生成一个子类并用子类去拦截所有父类的方法调用。CGLib的性能比JDK动态代理要高,但是生成代理对象却比较慢。

RPC中的代理模式应用

选型: 优先使用动态代理,而且根据远程调用的特性,一定是基于接口的。所以使用JDK提供的动态代理方式:

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
@Slf4j
public class RpcClientProxy implements InvocationHandler {

private static final String INTERFACE_NAME = "interfaceName :";
private final RpcServiceConfig rpcServiceConfig;
private final RpcRequestTransposter rpcRequestTransposter;

public RpcClientProxy(RpcServiceConfig rpcServiceConfig, RpcRequestTransposter rpcRequestTransposter) {
this.rpcServiceConfig = rpcServiceConfig;
this.rpcRequestTransposter = rpcRequestTransposter;
}

@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz}, this);
}

@SuppressWarnings("unchecked")
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//封装RpcReques
log.info("invoke method [{}]", method.getName());
RpcRequest rpcRequest = RpcRequest.builder().methordName(method.getName())
.args(args)
.argsType(method.getParameterTypes())
.requestId(UUID.randomUUID().toString())
.group(rpcServiceConfig.getGroup())
.version(rpcServiceConfig.getVersion())
.serviceName(method.getDeclaringClass().getName()).build();
// rpcTransport::sendRpcRequest
RpcResponce<Object> rpcResponce = null;
CompletableFuture<RpcResponce<Object>> future = (CompletableFuture<RpcResponce<Object>>) rpcRequestTransposter.sendRpcRequest(rpcRequest);
log.info("get future,wait process coming......");
// future::get
rpcResponce = future.get();
// check
this.check(rpcRequest,rpcResponce);
//return
return rpcResponce.getData();
}

public void check(RpcRequest rpcRequest, RpcResponce<Object> rpcResponce) {
// judge null:invoke fail
if(rpcResponce == null) {
throw new RpcException(RpcErrorMessageEnum.SERVICE_INVOKATION_FAILURE,INTERFACE_NAME+rpcRequest.getServiceName());
}
// requestId:request resp not match
if(!rpcRequest.getRequestId().equals(rpcResponce.getRequstId())){
throw new RpcException(RpcErrorMessageEnum.REQUEST_NOT_MATCH_REQUEST,INTERFACE_NAME+rpcRequest.getServiceName());
}
// getCode: service invoke fail
if(rpcResponce.getCode() == null || !rpcResponce.getCode().equals(RpcResponseCodeEnum.SUCCESS.getCode())) {
throw new RpcException(RpcErrorMessageEnum.SERVICE_INVOKATION_FAILURE,INTERFACE_NAME+rpcRequest.getServiceName());
}
}
}

在心跳机制中提到,RPC框架中是维护了一个长连接的,多个请求包会复用这个全双工的长连接。这种情况下就会先拆包与粘包的问题。下面将对拆包和粘包问题进行详细的介绍。

拆包与粘包

  • 什么是拆包和粘包、产生原因

TCP协议是面向字节流的,对于应用层的数据,TCP协议没有应用层级别的数据包概念,他只会根据缓冲区、窗口大小等,将这个二进制流分割成若干个包进行可靠传输。那么带来的后果就是,如果应用层不能预先指定好相应的协议,在read的时候,就可能一次read到多个包粘连在一块(粘包),也可能读取到不完整的包(拆包)。

Read more »

RPC协议是应用层的通讯协议,在client与server建立起连接的时候,一般client会进行多次的远程调用,我们希望能保持这个连接,而如何保持这个连接,就需要心跳机制。

什么是心跳机制

顾名思义, 所谓 心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性.

心跳机制确定了连接双发的存活,保证了连接的有效性。

如何实现心跳

  • 网络的传输层TCP协议实现了Keepalive机制
  • 在应用层自定义RPC自己的心跳机制

基于TCP层面的心跳机制,虽然可以复用,但是存在以下的缺点:

  • 不是TCP的标准协议,默认是关闭的
  • TCP的keepalive依赖于操作系统的实现,默认是两个小时,修改需要修改系统配置,不够灵活。

所以了解的大多数RPC协议都实现了自定义的心跳机制,LeezyRPC是基于Netty实现的网络传输,Netty框架同时提供了开启基于TCP的keepalive心跳机制和自定义的应用层心跳机制。

Read more »

上一篇指北,大致介绍了设计背景和思路,然后科普了一下正则表达式的基本常识,在这一篇中,将详细介绍整个URL提取的详细实现。包括获取Scheme和TLDs,Schemes、Domain、IP+Port….等URL模式的regex表达式拼接。

1
[协议类型]://[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]

资源准备

IANA全称为Internet Assigned Numbers Authority, 负责分配和维护用于驱动互联网的技术标准(“协议”)的唯一代码和编号系统。

IANA的官网上可以获取到官方的Schemes和TLDs的文件。

1
2
Schemes: "https://www.iana.org/assignments/uri-schemes/uri-schemes-1.csv"
TLDs: "https://data.iana.org/TLD/tlds-alpha-by-domain.txt"

将资源get下来,并利用go的template模板库生成go文件,文件内容是string[]的常量。Scheme.go生成代码如下:

Read more »

项目背景

在字节实习的时候遇到了一个需求,简单来说就是支持用户上传音乐链接,并对链接做风险检查。但是考虑到用户可能上传的是文本中蕴含着链接,常用分享的朋友可能经常会见到这种格式的连接分享:

1
9微xR52XUVGuBR嘻 https://m.tb.cn/h.4HiWOMs?sm=c079e2  【淘饱店】Xraypad Aqua Control+ 纯黑纯白鼠标垫AC+粗面多尺寸

当时的状况是,公司内部有团队提供了连接风险检测的接口(实际是结合google的API), 但是只支持纯连接风险检测,不支持以上这种分享格式。同时,前端的同学也希望后端能够帮助提取连接。于是,当时就参考了组内之前使用python提取网易云链接的思路和一些开源的库,使用Go中的regex正则框架实现了一个文本中提取链接的工具库。

Read more »

题目一:哪种连续子字符串更长

方法一:遍历

  • 时间复杂度 O(n)
  • 空间复杂度 O(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public boolean checkZeroOnes(String s) {
int maxOne = 0, maxZero = 0;
int idx = 0;
while (idx<s.length()) {
if(s.charAt(idx)=='1'){
int temp = 0;
while (idx<s.length() && s.charAt(idx) == '1'){
idx++;
temp++;
}
maxOne = Math.max(maxOne,temp);
}else {
int temp = 0;
while (idx<s.length() && s.charAt(idx) == '0'){
idx++;
temp++;
}
maxZero = Math.max(maxZero,temp);
}
}
return maxOne>maxZero;
}
Read more »

题目一:找出所有子集的异或总和再求和

方法一:枚举子集

数据范围有限,直接枚举子集计算异或和。

  • 时间复杂度 O(mn)
  • 空间复杂度 O(1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int subsetXORSum(int[] nums) {
int len = nums.length;
int res = 0;
for(int i=0;i<(1<<len);i++){
res += getXOR(nums,i);
}
return res;
}
public int getXOR(int[] nums,int i){
int sum = 0;
if(i==0) return 0;
for(int k=0;k<31;k++){
if((i&k)!=0) sum^= nums[k];
}
return sum;
}
Read more »

本周关键词:状态压缩DP,树状数组,归并排序,拓扑排序。

题目一:完成所有工作的最短时间 hrad

方法一:状压DP

n个工作分配给k个工人,求让每个工人的最大工作时长最小。观察数据范围:k,n (1~12),考虑状态压缩。枚举出n种工作的所有分配状态。

设dp[i][j]表示i个工人分配了j状态工作的最大时长,j用来表示状态。那么Dij= min(max(dp[i][k],max(j-k))) k表示所有j的子集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int minimumTimeRequired(int[] jobs, int k) {
int n = jobs.lenth;
int m = 1<<n;

int[][] dp = new int[n][m];
int[] sum = new int[m];

for(int i=1;i<m;i++){
int j = Integer.numberOfTrailingZeros(i),x = i^(1<<j);
sum[i] = sum[x] + jobs[j];
dp[0][i] = sum[i];
}

for(int i=1;i<k;i++){
for(int j=1;j<m;j--){
int maxn = Integer.MAX_VALUE;
for(int k=j;k!=0;k=(k-1)&j) {
maxn = Math.min(maxn,Math.Max(sum[k],dp[i-1][j-k]));
}
}
}
return dp[k-1][m-1];
}
Read more »

题目一:人口最多的年份

1
2
https://leetcode-cn.com/problems/maximum-population-year/
## 参考 https://leetcode-cn.com/problems/living-people-lcci/ 存活人数

方法一:暴力

观察数据范围,直接暴力法。

1
2
3
4
5
6
7
8
9
10
11
class Solution(object):
def maximumPopulation(self, logs):
res,year = 0,1950
for i in range(1950,2051):
temp = 0
for per in logs:
if i >= per[0] and i< per[1]: temp +=1
if temp > res:
res = temp
year = i
return year
Read more »