先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
正文
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配置:
- 在数据库添加路由规则配置,该规则表示当http header 中的name=ship时请求路由到gray_1.0版本的节点。
- 启动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
读者福利
由于篇幅过长,就不展示所有面试题了,感兴趣的小伙伴
更多笔记分享
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!