阿里终面:如何设计一个高性能网关?,java高级工程师面试题及答案解析

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

private Mono forward(ServerWebExchange exchange, String url) {

ServerHttpRequest request = exchange.getRequest();

ServerHttpResponse response = exchange.getResponse();

HttpMethod method = request.getMethod();

WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(url).headers((headers) -> {

headers.addAll(request.getHeaders());

});

WebClient.RequestHeadersSpec<?> reqHeadersSpec;

if (requireHttpBody(method)) {

reqHeadersSpec = requestBodySpec.body(BodyInserters.fromDataBuffers(request.getBody()));

} else {

reqHeadersSpec = requestBodySpec;

}

// nio->callback->nio

return reqHeadersSpec.exchange().timeout(Duration.ofMillis(properties.getTimeOutMillis()))

.onErrorResume(ex -> {

return Mono.defer(() -> {

String errorResultJson = “”;

if (ex instanceof TimeoutException) {

errorResultJson = “{“code”:5001,“message”:“network timeout”}”;

} else {

errorResultJson = “{“code”:5000,“message”:“system error”}”;

}

return ShipResponseUtil.doResponse(exchange, errorResultJson);

}).then(Mono.empty());

}).flatMap(backendResponse -> {

response.setStatusCode(backendResponse.statusCode());

response.getHeaders().putAll(backendResponse.headers().asHttpHeaders());

return response.writeWith(backendResponse.bodyToFlux(DataBuffer.class));

});

}

/**

  • weather the http method need http body

  • @param method

  • @return

*/

private boolean requireHttpBody(HttpMethod method) {

if (method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PATCH)) {

return true;

}

return false;

}

private String buildUrl(ServerWebExchange exchange, ServiceInstance serviceInstance) {

ServerHttpRequest request = exchange.getRequest();

String query = request.getURI().getQuery();

String path = request.getPath().value().replaceFirst(“/” + serviceInstance.getAppName(), “”);

String url = “http://” + serviceInstance.getIp() + “:” + serviceInstance.getPort() + path;

if (!StringUtils.isEmpty(query)) {

url = url + “?” + query;

}

return url;

}

/**

  • choose an ServiceInstance according to route rule config and load balancing algorithm

  • @param appName

  • @param request

  • @return

*/

private ServiceInstance chooseInstance(String appName, ServerHttpRequest request) {

List serviceInstances = ServiceCache.getAllInstances(appName);

if (CollectionUtils.isEmpty(serviceInstances)) {

LOGGER.error(“service instance of {} not find”, appName);

throw new ShipException(ShipExceptionEnum.SERVICE_NOT_FIND);

}

String version = matchAppVersion(appName, request);

if (StringUtils.isEmpty(version)) {

throw new ShipException(“match app version error”);

}

// filter serviceInstances by version

List instances = serviceInstances.stream().filter(i -> i.getVersion().equals(version)).collect(Collectors.toList());

//Select an instance based on the load balancing algorithm

LoadBalance loadBalance = LoadBalanceFactory.getInstance(properties.getLoadBalance(), appName, version);

ServiceInstance serviceInstance = loadBalance.chooseOne(instances);

return serviceInstance;

}

private String matchAppVersion(String appName, ServerHttpRequest request) {

List rules = RouteRuleCache.getRules(appName);

rules.sort(Comparator.comparing(AppRuleDTO::getPriority).reversed());

for (AppRuleDTO rule : rules) {

if (match(rule, request)) {

return rule.getVersion();

}

}

return null;

}

private boolean match(AppRuleDTO rule, ServerHttpRequest request) {

String matchObject = rule.getMatchObject();

String matchKey = rule.getMatchKey();

String matchRule = rule.getMatchRule();

Byte matchMethod = rule.getMatchMethod();

if (MatchObjectEnum.DEFAULT.getCode().equals(matchObject)) {

return true;

} else if (MatchObjectEnum.QUERY.getCode().equals(matchObject)) {

String param = request.getQueryParams().getFirst(matchKey);

if (!StringUtils.isEmpty(param)) {

return StringTools.match(param, matchMethod, matchRule);

}

} else if (MatchObjectEnum.HEADER.getCode().equals(matchObject)) {

HttpHeaders headers = request.getHeaders();

String headerValue = headers.getFirst(matchKey);

if (!StringUtils.isEmpty(headerValue)) {

return StringTools.match(headerValue, matchMethod, matchRule);

}

}

return false;

}

}

3.3 数据同步

========

app数据同步

后台服务(如订单服务)启动时,只将服务名,版本,ip地址和端口号注册到了Nacos,并没有实例的权重和启用的插件信息怎么办?

一般在线的实例权重和插件列表都是在管理界面配置,然后动态生效的,所以需要ship-admin定时更新实例的权重和插件信息到注册中心。

对应代码ship-admin的NacosSyncListener

/**

  • @Author: Ship

  • @Description:

  • @Date: Created in 2020/12/30

*/

@Configuration

public class NacosSyncListener implements ApplicationListener {

private static final Logger LOGGER = LoggerFactory.getLogger(NacosSyncListener.class);

private static ScheduledThreadPoolExecutor scheduledPool = new ScheduledThreadPoolExecutor(1,

new ShipThreadFactory(“nacos-sync”, true).create());

@NacosInjected

private NamingService namingService;

@Value(“${nacos.discovery.server-addr}”)

private String baseUrl;

@Resource

private AppService appService;

@Override

public void onApplicationEvent(ContextRefreshedEvent event) {

if (event.getApplicationContext().getParent() != null) {

return;

}

String url = “http://” + baseUrl + NacosConstants.INSTANCE_UPDATE_PATH;

scheduledPool.scheduleWithFixedDelay(new NacosSyncTask(namingService, url, appService), 0, 30L, TimeUnit.SECONDS);

}

class NacosSyncTask implements Runnable {

private NamingService namingService;

private String url;

private AppService appService;

private Gson gson = new GsonBuilder().create();

public NacosSyncTask(NamingService namingService, String url, AppService appService) {

this.namingService = namingService;

this.url = url;

this.appService = appService;

}

/**

  • Regular update weight,enabled plugins to nacos instance

*/

@Override

public void run() {

try {

// get all app names

ListView services = namingService.getServicesOfServer(1, Integer.MAX_VALUE, NacosConstants.APP_GROUP_NAME);

if (CollectionUtils.isEmpty(services.getData())) {

return;

}

List appNames = services.getData();

List appInfos = appService.getAppInfos(appNames);

for (AppInfoDTO appInfo : appInfos) {

if (CollectionUtils.isEmpty(appInfo.getInstances())) {

continue;

}

for (ServiceInstance instance : appInfo.getInstances()) {

Map<String, Object> queryMap = buildQueryMap(appInfo, instance);

String resp = OkhttpTool.doPut(url, queryMap, “”);

LOGGER.debug(“response :{}”, resp);

}

}

} catch (Exception e) {

LOGGER.error(“nacos sync task error”, e);

}

}

private Map<String, Object> buildQueryMap(AppInfoDTO appInfo, ServiceInstance instance) {

Map<String, Object> map = new HashMap<>();

map.put(“serviceName”, appInfo.getAppName());

map.put(“groupName”, NacosConstants.APP_GROUP_NAME);

map.put(“ip”, instance.getIp());

map.put(“port”, instance.getPort());

map.put(“weight”, instance.getWeight().doubleValue());

NacosMetadata metadata = new NacosMetadata();

metadata.setAppName(appInfo.getAppName());

metadata.setVersion(instance.getVersion());

metadata.setPlugins(String.join(“,”, appInfo.getEnabledPlugins()));

map.put(“metadata”, StringTools.urlEncode(gson.toJson(metadata)));

map.put(“ephemeral”, true);

return map;

}

}

}

ship-server再定时从Nacos拉取app数据更新到本地Map缓存。

/**

  • @Author: Ship

  • @Description: sync data to local cache

  • @Date: Created in 2020/12/25

*/

@Configuration

public class DataSyncTaskListener implements ApplicationListener {

private static ScheduledThreadPoolExecutor scheduledPool = new ScheduledThreadPoolExecutor(1,

new ShipThreadFactory(“service-sync”, true).create());

@NacosInjected

private NamingService namingService;

@Autowired

private ServerConfigProperties properties;

@Override

public void onApplicationEvent(ContextRefreshedEvent event) {

if (event.getApplicationContext().getParent() != null) {

return;

}

scheduledPool.scheduleWithFixedDelay(new DataSyncTask(namingService)

, 0L, properties.getCacheRefreshInterval(), TimeUnit.SECONDS);

WebsocketSyncCacheServer websocketSyncCacheServer = new WebsocketSyncCacheServer(properties.getWebSocketPort());

websocketSyncCacheServer.start();

}

class DataSyncTask implements Runnable {

private NamingService namingService;

public DataSyncTask(NamingService namingService) {

this.namingService = namingService;

}

@Override

public void run() {

try {

// get all app names

ListView services = namingService.getServicesOfServer(1, Integer.MAX_VALUE, NacosConstants.APP_GROUP_NAME);

if (CollectionUtils.isEmpty(services.getData())) {

return;

}

List appNames = services.getData();

// get all instances

for (String appName : appNames) {

List instanceList = namingService.getAllInstances(appName, NacosConstants.APP_GROUP_NAME);

if (CollectionUtils.isEmpty(instanceList)) {

continue;

}

ServiceCache.add(appName, buildServiceInstances(instanceList));

List pluginNames = getEnabledPlugins(instanceList);

PluginCache.add(appName, pluginNames);

}

ServiceCache.removeExpired(appNames);

PluginCache.removeExpired(appNames);

} catch (NacosException e) {

e.printStackTrace();

}

}

private List getEnabledPlugins(List instanceList) {

Instance instance = instanceList.get(0);

Map<String, String> metadata = instance.getMetadata();

// plugins: DynamicRoute,Auth

String plugins = metadata.getOrDefault(“plugins”, ShipPluginEnum.DYNAMIC_ROUTE.getName());

return Arrays.stream(plugins.split(“,”)).collect(Collectors.toList());

}

private List buildServiceInstances(List instanceList) {

List list = new LinkedList<>();

instanceList.forEach(instance -> {

Map<String, String> metadata = instance.getMetadata();

ServiceInstance serviceInstance = new ServiceInstance();

serviceInstance.setAppName(metadata.get(“appName”));

serviceInstance.setIp(instance.getIp());

serviceInstance.setPort(instance.getPort());

serviceInstance.setVersion(metadata.get(“version”));

serviceInstance.setWeight((int) instance.getWeight());

list.add(serviceInstance);

});

return list;

}

}

}

路由规则数据同步

同时,如果用户在管理后台更新了路由规则,ship-admin需要推送规则数据到ship-server,这里参考了soul网关的做法利用websocket在第一次建立连接后进行全量同步,此后路由规则发生变更就只作增量同步。

服务端WebsocketSyncCacheServer:

/**

  • @Author: Ship

  • @Description:

  • @Date: Created in 2020/12/28

*/

public class WebsocketSyncCacheServer extends WebSocketServer {

private final static Logger LOGGER = LoggerFactory.getLogger(WebsocketSyncCacheServer.class);

private Gson gson = new GsonBuilder().create();

private MessageHandler messageHandler;

public WebsocketSyncCacheServer(Integer port) {

super(new InetSocketAddress(port));

this.messageHandler = new MessageHandler();

}

@Override

public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {

LOGGER.info(“server is open”);

}

@Override

public void onClose(WebSocket webSocket, int i, String s, boolean b) {

LOGGER.info(“websocket server close…”);

}

@Override

public void onMessage(WebSocket webSocket, String message) {

LOGGER.info(“websocket server receive message:\n[{}]”, message);

this.messageHandler.handler(message);

}

@Override

public void onError(WebSocket webSocket, Exception e) {

}

@Override

public void onStart() {

LOGGER.info(“websocket server start…”);

}

class MessageHandler {

public void handler(String message) {

RouteRuleOperationDTO operationDTO = gson.fromJson(message, RouteRuleOperationDTO.class);

if (CollectionUtils.isEmpty(operationDTO.getRuleList())) {

return;

}

Map<String, List> map = operationDTO.getRuleList()

.stream().collect(Collectors.groupingBy(AppRuleDTO::getAppName));

if (OperationTypeEnum.INSERT.getCode().equals(operationDTO.getOperationType())

|| OperationTypeEnum.UPDATE.getCode().equals(operationDTO.getOperationType())) {

RouteRuleCache.add(map);

} else if (OperationTypeEnum.DELETE.getCode().equals(operationDTO.getOperationType())) {

RouteRuleCache.remove(map);

}

}

}

}

客户端WebsocketSyncCacheClient:

/**

  • @Author: Ship

  • @Description:

  • @Date: Created in 2020/12/28

*/

@Component

public class WebsocketSyncCacheClient {

private final static Logger LOGGER = LoggerFactory.getLogger(WebsocketSyncCacheClient.class);

private WebSocketClient client;

private RuleService ruleService;

private Gson gson = new GsonBuilder().create();

public WebsocketSyncCacheClient(@Value(“${ship.server-web-socket-url}”) String serverWebSocketUrl,

RuleService ruleService) {

if (StringUtils.isEmpty(serverWebSocketUrl)) {

throw new ShipException(ShipExceptionEnum.CONFIG_ERROR);

}

this.ruleService = ruleService;

ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1,

new ShipThreadFactory(“websocket-connect”, true).create());

try {

client = new WebSocketClient(new URI(serverWebSocketUrl)) {

@Override

public void onOpen(ServerHandshake serverHandshake) {

LOGGER.info(“client is open”);

List list = ruleService.getEnabledRule();

String msg = gson.toJson(new RouteRuleOperationDTO(OperationTypeEnum.INSERT, list));

send(msg);

}

@Override

public void onMessage(String s) {

}

@Override

public void onClose(int i, String s, boolean b) {

}

@Override

public void onError(Exception e) {

LOGGER.error(“websocket client error”, e);

}

};

client.connectBlocking();

//使用调度线程池进行断线重连,30秒进行一次

executor.scheduleAtFixedRate(() -> {

if (client != null && client.isClosed()) {

try {

client.reconnectBlocking();

} catch (InterruptedException e) {

LOGGER.error(“reconnect server fail”, e);

}

}

}, 10, 30, TimeUnit.SECONDS);

} catch (Exception e) {

LOGGER.error(“websocket sync cache exception”, e);

throw new ShipException(e.getMessage());

}

}

public void send(T t) {

while (!client.getReadyState().equals(ReadyState.OPEN)) {

LOGGER.debug(“connecting …please wait”);

}

client.send(gson.toJson(t));

}

}

四、测试

====

4.1动态路由测试

=========

本地启动nacos ,sh startup.sh -m standalone

启动ship-admin

本地启动两个ship-example实例。

实例1配置:

阿里终面:如何设计一个高性能网关?

实例2配置:

阿里终面:如何设计一个高性能网关?

  1. 在数据库添加路由规则配置,该规则表示当http header 中的name=ship时请求路由到gray_1.0版本的节点。

阿里终面:如何设计一个高性能网关?

  1. 启动ship-server,看到以下日志时则可以进行测试了。

2021-01-02 19:57:09.159 INFO 30413 — [SocketWorker-29] cn.sp.sync.WebsocketSyncCacheServer : websocket server receive message:

[{“operationType”:“INSERT”,“ruleList”:[{“id”:1,“appId”:5,“appName”:“order”,“version”:“gray_1.0”,“matchObject”:“HEADER”,“matchKey”:“name”,“matchMethod”:1,“matchRule”:“ship”,“priority”:50}]}]

6.用Postman请求

http://localhost:9000/order/user/add,POST方式,header设置name=ship,可以看到只有实例1有日志显示。

==========add user,version:gray_1.0

4.2性能压测

===========

压测环境:

MacBook Pro 13英寸

处理器 2.3 GHz 四核Intel Core i7

内存 16 GB 3733 MHz LPDDR4X

后端节点个数一个

压测工具:wrk

读者福利

由于篇幅过长,就不展示所有面试题了,感兴趣的小伙伴

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

更多笔记分享

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
] cn.sp.sync.WebsocketSyncCacheServer : websocket server receive message:

[{“operationType”:“INSERT”,“ruleList”:[{“id”:1,“appId”:5,“appName”:“order”,“version”:“gray_1.0”,“matchObject”:“HEADER”,“matchKey”:“name”,“matchMethod”:1,“matchRule”:“ship”,“priority”:50}]}]

6.用Postman请求

http://localhost:9000/order/user/add,POST方式,header设置name=ship,可以看到只有实例1有日志显示。

==========add user,version:gray_1.0

4.2性能压测

===========

压测环境:

MacBook Pro 13英寸

处理器 2.3 GHz 四核Intel Core i7

内存 16 GB 3733 MHz LPDDR4X

后端节点个数一个

压测工具:wrk

读者福利

由于篇幅过长,就不展示所有面试题了,感兴趣的小伙伴

[外链图片转存中…(img-CQ8WBOav-1713169807698)]

[外链图片转存中…(img-jD2718oL-1713169807698)]

[外链图片转存中…(img-xEZmxaao-1713169807699)]

更多笔记分享

[外链图片转存中…(img-BRCBOC81-1713169807699)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-33KqmMwD-1713169807699)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值