必修-场景题

server/2024/10/9 15:20:45/

场景题

    • 1. 树遍历
      • 二叉树
      • 三叉树
    • 2. 并发问题
      • 营销模块优惠卷架构设计
        • 前端
        • 后端
          • Nginx
          • Spring Cloud Gateway和限流的依赖:
        • 处理优惠券的缓存逻辑:
          • 处理优惠卷HTTP请求:
          • 实现令牌桶算法
          • 请求限流一秒
        • 用Resilience4j实现降级策略
          • 在 application.yml 或 application.properties 中配置 Resilience4j:
    • 3.匹配算法逻辑题
    • 4. jvm方面
      • 基础概念
      • 垃圾回收算法
      • 常用调优命令会问到的整理
        • 基本示例
    • 5. 网络方面
      • 使用Spring Boot实现WebSocket通信
      • 使用Netty作为通信
      • Http 和 Https
        • 基本概念
        • HTTP请求过程
        • HTTP/1.0 vs HTTP/1.1 vs HTTP/2 vs HTTP/3
        • HTTP网络模型
        • “粘包”和“拆包”的问题

1. 树遍历

遍历树是数据结构中的一个重要部分
二叉树(每个节点最多有两个子节点)
三叉树(每个节点最多有三个子节点)

通常,我们会有以下几种遍历方式:

前序遍历:访问顺序是根节点 -> 左子树 -> 右子树。
中序遍历 :访问顺序是左子树 -> 根节点 -> 右子树。
后序遍历:访问顺序是左子树 -> 右子树 -> 根节点。
层序遍历:按层次依次访问节点。

二叉树

首先,我们定义一个二叉树节点类:

class BinaryTreeNode {int value;BinaryTreeNode left;BinaryTreeNode right;BinaryTreeNode(int value) {this.value = value;left = null;right = null;}
}

接下来,我们实现各种遍历方法:

import java.util.LinkedList;
import java.util.Queue;public class BinaryTreeTraversal {// 前序遍历public void preOrder(BinaryTreeNode node) {if (node != null) {System.out.print(node.value + " ");preOrder(node.left);preOrder(node.right);}}// 中序遍历public void inOrder(BinaryTreeNode node) {if (node != null) {inOrder(node.left);System.out.print(node.value + " ");inOrder(node.right);}}// 后序遍历public void postOrder(BinaryTreeNode node) {if (node != null) {postOrder(node.left);postOrder(node.right);System.out.print(node.value + " ");}}// 层序遍历public void levelOrder(BinaryTreeNode root) {if (root == null) return;Queue<BinaryTreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {BinaryTreeNode node = queue.poll();System.out.print(node.value + " ");if (node.left != null) queue.offer(node.left);if (node.right != null) queue.offer(node.right);}}public static void main(String[] args) {BinaryTreeNode root = new BinaryTreeNode(1);root.left = new BinaryTreeNode(2);root.right = new BinaryTreeNode(3);root.left.left = new BinaryTreeNode(4);root.left.right = new BinaryTreeNode(5);BinaryTreeTraversal traversal = new BinaryTreeTraversal();System.out.println("Pre-order:");traversal.preOrder(root);System.out.println("\nIn-order:");traversal.inOrder(root);System.out.println("\nPost-order:");traversal.postOrder(root);System.out.println("\nLevel-order:");traversal.levelOrder(root);}
}

三叉树

首先,我们定义一个三叉树节点类:

class TernaryTreeNode {int value;TernaryTreeNode left;TernaryTreeNode middle;TernaryTreeNode right;TernaryTreeNode(int value) {this.value = value;left = null;middle = null;right = null;}
}

接下来,我们实现各种遍历方法:

import java.util.LinkedList;
import java.util.Queue;public class TernaryTreeTraversal {// 前序遍历public void preOrder(TernaryTreeNode node) {if (node != null) {System.out.print(node.value + " ");preOrder(node.left);preOrder(node.middle);preOrder(node.right);}}// 后序遍历public void postOrder(TernaryTreeNode node) {if (node != null) {postOrder(node.left);postOrder(node.middle);postOrder(node.right);System.out.print(node.value + " ");}}// 层序遍历public void levelOrder(TernaryTreeNode root) {if (root == null) return;Queue<TernaryTreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {TernaryTreeNode node = queue.poll();System.out.print(node.value + " ");if (node.left != null) queue.offer(node.left);if (node.middle != null) queue.offer(node.middle);if (node.right != null) queue.offer(node.right);}}public static void main(String[] args) {TernaryTreeNode root = new TernaryTreeNode(1);root.left = new TernaryTreeNode(2);root.middle = new TernaryTreeNode(3);root.right = new TernaryTreeNode(4);root.left.left = new TernaryTreeNode(5);root.left.middle = new TernaryTreeNode(6);root.left.right = new TernaryTreeNode(7);TernaryTreeTraversal traversal = new TernaryTreeTraversal();System.out.println("Pre-order:");traversal.preOrder(root);System.out.println("\nPost-order:");traversal.postOrder(root);System.out.println("\nLevel-order:");traversal.levelOrder(root);}
}

2. 并发问题

我以设计一个抢优惠券并发场景的解决方案,来举例,那么需要确保系统的高并发处理能力、安全性和可靠性。下面是我想到的解决方案:

营销模块优惠卷架构设计

前端

用户请求:使用Vue.js、React等前端框架进行用户交互设计,确保用户体验。
请求限流:前端可以进行简单的限流,比如每个用户每秒钟只能发起一个请求,用限流器进行限流防抖操作

function throttle(func, wait) {let lastTime = 0;return function(...args) {const now = Date.now();if (now - lastTime >= wait) {lastTime = now;func.apply(this, args);}};
}
后端
  1. 进网管网关层
    使用Nginx、Spring Cloud Gateway等,进行负载均衡、限流和日志记录
Nginx
确保Nginx编译时带有 ngx_http_limit_req_module 模块,这是Nginx默认包含的模块。
配置限流:http {limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;server {listen 80;server_name example.com;location /some/api/method {limit_req zone=one burst=5 nodelay;proxy_pass http://backend;}}
}

在这个配置中,limit_req_zone 定义了一个共享内存区域 one,用于保存限流信息。rate=1r/s 表示允许每秒一次请求。burst=5 允许突发流量可以达到5个请求。nodelay 禁用延迟队列。

Spring Cloud Gateway和限流的依赖:
    <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis-reactive</artifactId></dependency>

配置路由并启用限流:

在 application.yml 中配置限流。

java">    spring:cloud:gateway:routes:- id: some_apiuri: http://backend-servicepredicates:- Path=/some/api/methodfilters:- name: RequestRateLimiterargs:redis-rate-limiter.replenishRate: 1redis-rate-limiter.burstCapacity: 5

确保你有 Redis 依赖和配置: Spring Cloud Gateway 使用 Redis 进行限流,这意味着你需要配置 Redis 相关的依赖和配置

  1. 服务层
    应用服务:采用微服务架构,将抢优惠券逻辑放在专门的Coupon Service中。
    使用 Redis/Memcached:用于缓存优惠券信息,以及用户抢券请求,防止数据库压力过大
处理优惠券的缓存逻辑:
java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
*** Author: 徐寿春* Date:    2024/7/25 17:40* <p>* 名称  处理优惠券的缓存逻辑*/
@Service
public class CouponService {private static final String COUPON_KEY_PREFIX = "coupon:";private static final String USER_REQUEST_KEY_PREFIX = "user_request:";@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 缓存优惠券信息public void cacheCouponInfo(String couponId, String couponInfo, long timeout, TimeUnit unit) {ValueOperations<String, Object> ops = redisTemplate.opsForValue();ops.set(COUPON_KEY_PREFIX + couponId, couponInfo, timeout, unit);}// 获取缓存的优惠券信息public String getCouponInfo(String couponId) {ValueOperations<String, Object> ops = redisTemplate.opsForValue();return (String) ops.get(COUPON_KEY_PREFIX + couponId);}// 记录用户抢券请求public boolean recordUserRequest(String userId, String couponId, long timeout, TimeUnit unit) {ValueOperations<String, Object> ops = redisTemplate.opsForValue();String key = USER_REQUEST_KEY_PREFIX + userId + ":" + couponId;if (redisTemplate.hasKey(key)) {return false; // 用户已经抢过该券} else {ops.set(key, "requested", timeout, unit);return true;}}
}
处理优惠卷HTTP请求:
java">import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
*** Author: 徐寿春* Date:    2024/7/25 17:40* <p>* 名称  处理优惠券逻辑*/
@RestController
@RequestMapping("/coupons")
public class CouponController {@Autowiredprivate CouponService couponService;@GetMapping("/{couponId}")public String getCouponInfo(@PathVariable String couponId) {String couponInfo = couponService.getCouponInfo(couponId);if (couponInfo == null) {// 从数据库中获取优惠券信息,并缓存起来couponInfo = "Coupon info from DB"; // 这里应该从数据库获取couponService.cacheCouponInfo(couponId, couponInfo, 1, TimeUnit.HOURS);}return couponInfo;}@PostMapping("/{couponId}/request")public String requestCoupon(@PathVariable String couponId, @RequestParam String userId) {boolean success = couponService.recordUserRequest(userId, couponId, 1, TimeUnit.DAYS);if (success) {return "Coupon requested successfully";} else {return "Coupon already requested";}}
}

二, Token Bucket:采用令牌桶算法进行流量控制,限制每秒钟的请求数。

实现令牌桶算法
java">import org.springframework.stereotype.Component;import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
*** Author: 徐寿春* Date:    2024/7/25 17:40* <p>* 名称  延时线程池生成token*/
@Component
public class TokenBucket {private final int MAX_TOKENS = 10; // 最大令牌数private int tokens;private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);public TokenBucket() {this.tokens = MAX_TOKENS;startTokenGenerator();}// 定期生成令牌private void startTokenGenerator() {scheduler.scheduleAtFixedRate(() -> {synchronized (this) {if (tokens < MAX_TOKENS) {tokens++;}System.out.println("Tokens available: " + tokens);}}, 0, 1, TimeUnit.SECONDS); // 每秒钟生成一个令牌}// 请求令牌public synchronized boolean requestToken() {if (tokens > 0) {tokens--;return true;}return false;}
}
请求限流一秒
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;*** Author: 徐寿春* Date:    2024/7/25 17:40* <p>* 名称  限流请求token算法令牌接口*/
@RestController
public class RateLimitController {@Autowiredprivate TokenBucket tokenBucket;@GetMapping("/limited-endpoint")public String limitedEndpoint() {if (tokenBucket.requestToken()) {return "Request processed successfully.";} else {return "Too many requests. Please try again later.";}}
}
  1. 数据库层
    关系型数据库:MySQL,用于存储持久化的优惠券数据。
    NoSQL数据库:使用Redis的持久化功能或者MongoDB用于用户临时抢券信息存储。
    MySQL:用于存储需要持久化的、结构化的优惠券数据(长期保存)。
    Redis:用于存储高并发、需要快速读写的用户临时抢券信息(短期保存,高性能)。

  2. 并发控制策略
    a. 乐观锁
    在数据库层面使用乐观锁机制避免超发优惠券。例如,在优惠券数量减少的过程中,进行版本号比较,确保操作的原子性,前提是一个库一张表
    b. 分布式锁
    使用Redis分布式锁(或者ZooKeeper等)确保优惠券扣减的原子性,可避免并发超发,但要考虑延时问题
    c. 请求去重
    使用独立的请求ID对每个用户的请求进行去重,防止重复请求,常用的就是id加上ip加上机器放bitmap
    d. 延迟队列
    对于高并发的场景,可以采用Kafka/RabbitMQ等消息队列,将请求进行排队处理,避免瞬时高并发冲击数据库,关于如何利用消息队列延时队列处理有对应的文章我集成框架 - RabbitMQ

  3. 流程设计
    用户请求:用户发送抢优惠券请求。
    网关层限流:网关层进行初步限流和鉴权。
    缓存层验证:查询Redis缓存中的优惠券是否仍有剩余。
    如果有,进入下一步。
    如果没有,直接返回抢光提示。
    分布式锁:在Redis中获取分布式锁,确保同一时间只有一个请求进行优惠券扣减操作。
    数据库操作:
    开启事务。
    查询当前优惠券库存。
    扣减库存,更新数据。
    提交事务。
    释放锁:释放Redis分布式锁。
    更新缓存:同步更新Redis中的优惠券库存信息。
    响应用户:返回成功领取的响应。

我能想到就这么多,剩下的自己补充

  1. 错误处理与降级策略
    超时处理:设置合理的请求超时时间,超时后提示用户重试,关于系统请求重试机制我写一下吧,以http为例
java">*** Author: 徐寿春* Date:    2024/7/25 17:58* <p>* 名称  重试demo*/
public class RetryHttpRequest {// 定义重试次数和间隔时间private static final int MAX_RETRIES = 3;private static final int RETRY_INTERVAL_MS = 1000; // 1秒钟public static void main(String[] args) {String urlString = "http://example.com";for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {try {String response = sendHttpRequest(urlString);System.out.println("请求成功: " + response);// 成功时跳出循环break;} catch (SocketTimeoutException e) {System.out.println("请求超时,尝试重试 " + attempt);if (attempt == MAX_RETRIES) {System.out.println("达到最大重试次数,停止重试");} else {try {Thread.sleep(RETRY_INTERVAL_MS);} catch (InterruptedException ie) {Thread.currentThread().interrupt();System.out.println("线程被中断");}}} catch (IOException e) {System.out.println("请求失败: " + e.getMessage());// 其他IOException错误直接停止重试break;}}}public static String sendHttpRequest(String urlString) throws IOException {URL url = new URL(urlString);HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(5000); // 设置连接超时时间connection.setReadTimeout(5000); // 设置读取超时时间int responseCode = connection.getResponseCode();if (responseCode == HttpURLConnection.HTTP_OK) { // 200表示成功BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));String inputLine;StringBuilder response = new StringBuilder();while ((inputLine = in.readLine()) != null) {response.append(inputLine);}in.close();return response.toString();} else {throw new IOException("HTTP 请求失败,响应码: " + responseCode);}}
}

降级策略:当系统压力大或出现问题时,可降级处理,例如直接返回优惠券已抢光,或者进入排队模式,Hystrix太重了我平时用Resilience4j

用Resilience4j实现降级策略
<dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-circuitbreaker</artifactId><version>1.7.1</version>
</dependency>
java">import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;import java.time.Duration;
import java.util.function.Supplier;*** Author: 徐寿春* Date:    2024/7/25 18:28* <p>* 名称  Resilience4j demo 降级*/
public class Resilience4jExample {public static void main(String[] args) {// 创建配置CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom().failureRateThreshold(50).waitDurationInOpenState(Duration.ofMillis(1000)).slidingWindowSize(2).build();// 创建CircuitBreakerCircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.of(circuitBreakerConfig);CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("couponService");// 定义业务逻辑Supplier<String> getCouponSupplier = CircuitBreaker.decorateSupplier(circuitBreaker, () -> {if (System.currentTimeMillis() % 2 == 0) {throw new RuntimeException("System is busy");}return "Coupon acquired!";});// 定义降级逻辑Supplier<String> fallbackSupplier = () -> "Coupons are sold out, please try again later.";// 执行并处理降级String result = CircuitBreaker.decorateSupplier(circuitBreaker, getCouponSupplier).recover(fallbackSupplier).get();System.out.println(result);}
}

Resilience4j 这块我写一下吧,回头做个和Hystrix的比较

   <dependency><groupId>io.github.resilience4j</groupId><artifactId>resilience4j-spring-boot2</artifactId><version>1.7.0</version></dependency>
在 application.yml 或 application.properties 中配置 Resilience4j:
   resilience4j:circuitbreaker:configs:default:registerHealthIndicator: trueslidingWindowSize: 100permittedNumberOfCallsInHalfOpenState: 10minimumNumberOfCalls: 10waitDurationInOpenState: 10sfailureRateThreshold: 50eventConsumerBufferSize: 10instances:backendA:baseConfig: default----
registerHealthIndicator:
解释: 当设置为 true 时,Resilience4j 会注册一个健康指示器(Health Indicator),使其可用于监控框架,例如 Spring Boot Actuator。slidingWindowSize:
解释: 滑动窗口的大小。表示断路器的滑动窗口会包含多少次调用。100表明滑动窗口包含100次调用。permittedNumberOfCallsInHalfOpenState:
解释: 半开启状态时允许的最大调用次数。当断路器从开启状态转换为半开启状态时,允许通过的最大调用次数。10表示允许10次调用。minimumNumberOfCalls:
解释: 在评价断路器状态之前,必须记录的最少调用次数。如果设为 10,那么在至少有10次调用后,才会根据配置的其他条件再来决定断路器的状态。waitDurationInOpenState:
解释: 断路器在开启状态保持的时间长度。10s 表示在断路器转换为开放状态后,10秒钟后将进入半开启状态。failureRateThreshold:
解释: 失败率的阈值。定义了错误调用率的最大百分比。如果设为 50,那么只有当失败调用比例超过50%时,断路器才会打开。eventConsumerBufferSize:
解释: 事件消费者的缓冲区大小,表示事件处理队列的最大容量。设为 10的话,表示最多可以处理10个事件。如果满了,新事件将覆盖最老的事件。

控制层

java">   @RestController@RequestMapping("/api")public class BackendController {private final BackendService backendService;@Autowiredpublic BackendController(BackendService backendService) {this.backendService = backendService;}@GetMapping("/doSomething")public ResponseEntity<String> doSomething() {String result = backendService.doSomething();return new ResponseEntity<>(result, HttpStatus.OK);}}

服务实现

java">   @Servicepublic class BackendServiceImpl implements BackendService {@Override@CircuitBreaker(name = "backendA", fallbackMethod = "fallback")public String doSomething() {// 这里模拟一个可能会失败的调用if (new Random().nextBoolean()) {throw new RuntimeException("Service failed");}return "Service is successful";}// 断路器打开时的回退方法public String fallback(Throwable t) {return "Service is down, please try again later";}}
 resilience4j:retry:instances:backendA:maxAttempts: 3waitDuration: 500msretryExceptions:- java.io.IOException- java.util.concurrent.TimeoutExceptionretry: 这部分配置是关于重试机制的。backendA: 这是特定于某个服务或组件(在这里是 backendA)的配置。这里列出的所有配置都只适用于 backendA。maxAttempts: 3: 这是最大重试次数。当对 backendA 进行操作失败时,最多会重试两次(加上第一次尝试,总共三次)。也就是说,最多会允许三次失败尝试。waitDuration: 500ms: 重试之间的等待时间是 500 毫秒。如果一次尝试 backendA 失败,那么在最小 500 毫秒后,库会再次尝试。retryExceptions: 这是一个需要触发重试的异常列表。如果遇到列出的异常,重试机制就会生效。- java.io.IOException: 遇到 java.io.IOException 异常时,会触发重试。- java.util.concurrent.TimeoutException: 遇到 java.util.concurrent.TimeoutException 异常时,也会触发重试。

修改服务实现:

java">   @Servicepublic class BackendServiceImpl implements BackendService {@Override@Retry(name = "backendA", fallbackMethod = "fallback")public String doSomething() {// 这里模拟一个可能会失败的调用if (new Random().nextBoolean()) {throw new RuntimeException("Service failed");}return "Service is successful";}public String fallback(Throwable t) {return "Service is down, please try again later";}}
  1. 监控与报警:使用Prometheus、Grafana等进行系统监控,设置报警阈值,及时发现并处理问题,这部分回头补吧

3.匹配算法逻辑题

有一个字符串构成结构为 北京 杭州 杭州 北京 要求写一个匹配逻辑

举例: pattern = "abba" str = "北京 杭州 杭州 北京 " 返回 true
举例: pattern = "aabb" str = "北京 杭州 杭州 北京 " 返回 false
举例: pattern = "baab" str = "北京 杭州 杭州 北京 " 返回 false

逻辑如下

java">    public static boolean matchPattern(String str, String pattern) {String[] words = pattern.split(" ");if (str.length() != words.length) {return false; // 如果字符串长度和单词数组长度不一致}Map<Object, Integer> mapping = new HashMap<>();for (int i = 0; i < str.length(); i++) {//得到 achar c = str.charAt(i);//得到杭州String word = words[i];// 同时检查字符和单词,确保他们的索引一致if (!mapping.put(c, i).equals(mapping.put(word, i))) {return false; // 如果索引不一致,返回 false}}return true;  // 如果循环中没有冲突,返回 true}
}

4. jvm方面

基础概念

Java 垃圾回收器(Garbage 垃圾桶 Collector 控制,简称 GC)以下是我整理的常见的垃圾回收器及其在不同 JDK 版本中的支持情况:

  1. Serial GC 串行垃圾回收器
    常用于小型应用或单线程环境中 JDK 1.2 及以上

  2. Parallel GC 并行垃圾回收器
    专注于高吞吐量和短暂停顿时间,是大多数服务器应用程序的默认选择,JDK 1.4 及以上

  3. CMS 侧重于减少垃圾回收的停顿时间,并发执行垃圾回收操作。适用于低延迟应用。
    虽然在 JDK 9 中被标记为过时(Deprecated),并在 JDK 14 中被移除。JDK 1.4 - JDK 13

  4. G1 设计用于大内存、多处理器的环境,能够更好地控制停顿时间。分区垃圾回收,优先回收最耗时的区域。
    设计用于取代 CMS GC,并在 JDK 9 中成为默认垃圾回收器。JDK 7 及以上

  5. ZGC 具有非常低的停顿时间,并且可以处理非常大的堆内存(多达数 TB 的内存)。
    专注于极低的延迟,适用于需要大量内存的应用程序,JDK 11 及以上

  6. Shenandoah GC 是 Red Hat 开发的一种低停顿垃圾回收器,像 ZGC 一样,目标是实现几乎恒定的停顿时间。
    另一种低延迟垃圾回收器,与 ZGC 类似,JDK 12 及以上

  7. Epsilon GC
    一个实验性的 “无操作” 垃圾回收器,不进行任何垃圾回收,适合做性能测试和调试用途,JDK 11 及以上

默认 GC 改变

JDK 8:Parallel GC 为默认垃圾回收器。
JDK 9:G1 GC 成为默认垃圾回收器。

垃圾回收算法

  • 标记-清除算法

Description: 分两个阶段,首先是标记出所有存活的对象,然后清除未被标记的对象。标记阶段可能会导致应用停顿。

  • 标记-压缩算法

Description: 与标记-清除类似,但在清除阶段会将存活的对象压缩到堆的一端,防止产生碎片。

  • 复制算法

Description: 将活动对象复制到新区域(通常是“新生代”内存区),原区域被清空。适用于新生代的垃圾回收,减少碎片。

  • 分代收集算法

Description: 将堆分为新生代和老年代,不同代使用不同的回收算法。新生代使用复制算法,老年代使用标记-清除或标记-压缩。

  • 卡表算法

Description: 通过记录对象的引用更新来优化并发垃圾回收器的性能。根部引用发

  • 并发标记-扫描算法

Description: 标记阶段和清除阶段都是并发进行的,以减少垃圾回收对应用的影响。

常用调优命令会问到的整理

  1. 内存配置
    -Xms:设置 JVM 初始堆内存大小。
    -Xmx:设置 JVM 最大堆内存大小。
    -Xmn:设置 JVM 年轻代(young generation)内存大小。
    -XX:PermSize=:设置永久代(permanent generation)初始大小(Java 8 以前)。
    -XX:MaxPermSize=:设置永久代最大大小(Java 8 以前)。
    -XX:MetaspaceSize=:设置元空间初始大小(Java 8 及以后)。
    -XX:MaxMetaspaceSize=:设置元空间最大大小(Java 8 及以后)

  2. 垃圾回收器(GC)选项
    -XX:+UseSerialGC:使用串行垃圾回收器。
    -XX:+UseParallelGC:使用并行垃圾回收器(适用于多核 CPU)。
    -XX:+UseConcMarkSweepGC:使用并发标记清除(CMS)垃圾回收器。
    -XX:+UseG1GC:使用 G1 垃圾回收器(Java 7 更新 4及以后推荐)。

  3. GC 调优参数
    -XX:NewRatio=:设置年轻代和老年代的比值。
    -XX:SurvivorRatio=:设置 Eden 区和 Survivor 区的比值。
    -XX:MaxTenuringThreshold=:设置对象晋升到老年代的年龄阈值。
    -XX:+UseGCLogFileRotation:启用 GC 日志文件轮转。
    -XX:NumberOfGCLogFiles=:设置 GC 日志文件轮转的数量。
    -XX:GCLogFileSize=:设置每个 GC 日志文件的大小。

  4. GC 日志
    -XX:+PrintGC:简单的 GC 统计信息。
    -XX:+PrintGCDetails:详细的 GC 统计信息。
    -XX:+PrintGCTimeStamps:在 GC 日志中打印时间戳。
    -XX:+PrintGCDateStamps:在 GC 日志中打印日期。
    -Xloggc::将 GC 日志输出到文件。

  5. 性能调优参数
    -XX:+AggressiveOpts:启用一些性能优化选项。
    -XX:+UseStringDeduplication:运行字符串去重(在使用 G1 GC 时)。

  6. 其他有用的选项
    -Djava.awt.headless=true:在没有显示器的环境中运行 Java 应用(例如在服务器上)。
    -XX:+HeapDumpOnOutOfMemoryError:在发生内存溢出时生成堆转储文件。
    -XX:HeapDumpPath=:指定堆转储文件的路径。
    -XX:+ExitOnOutOfMemoryError:在内存溢出时结束 JVM。

基本示例
java">java -Xms512m -Xmx2048m -XX:+UseG1GC -XX:+PrintGCDetails -Xloggc:/path/to/gc.log -jar myapp.jar

以上命令设置了初始堆内存为 512MB,最大堆内存为 2048MB,使用 G1 GC,并输出详细的 GC 日志到 /path/to/gc.log。

5. 网络方面

使用Spring Boot实现WebSocket通信

  1. 添加依赖
    首先,你需要在pom.xml文件中添加Spring WebSocket的依赖。
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
</dependencies>
  1. WebSocket配置类
java">import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
/*** Author: 徐寿春* Date:    2024/7/30 11:48* <p>* 名称*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 使用简单的WebSocket处理器registry.addHandler(new MyWebSocketHandler(), "/ws").setAllowedOrigins("*");}
}
  1. WebSocket处理器
java">import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;public class MyWebSocketHandler extends TextWebSocketHandler {@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String payload = message.getPayload();System.out.println("Received: " + payload);// 处理消息并进行响应session.sendMessage(new TextMessage("Hello, " + payload));}
}
  1. 前端JavaScript连接WebSocket 手写一个测试html
<!DOCTYPE html>
<html>
<head><title>xscWebSocket Test</title>
</head>
<body><script type="text/javascript">javascript">const socket = new WebSocket('ws://localhost:8080/ws');socket.onopen = function(event) {console.log('WebSocket is open now.');socket.send('Hello Server');};socket.onmessage = function(event) {console.log('Received Message: ' + event.data);};socket.onclose = function(event) {console.log('WebSocket is closed now.');};socket.onerror = function(error) {console.log('WebSocket Error: ' + error);};</script>
</body>
</html>

启动Spring Boot应用。WebSocket端点将会在/ws路径上提供服务,WebSocketConfig类用于配置WebSocket端点和转发器,MyWebSocketHandler类用于处理WebSocket消息,前端的JavaScript用于连接和与WebSocket服务器通信,可以在MyWebSocketHandler中添加更多处理逻辑,例如处理二进制消息、实现心跳检测、处理不同类型的消息等。此外,你也可以结合Spring Security来保护你的WebSocket连接。

使用Netty作为通信

在pom.xml文件中添加Netty的依赖:

<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.65.Final</version>
</dependency>

编写Netty服务器

java">package com.example.netty;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/*** Author: 徐寿春* Date:    2024/7/30 11:48* <p>* 名称*/
@Component
public class NettyServer {private final int port = 8080; // 可以选择任何开放端口private EventLoopGroup bossGroup;private EventLoopGroup workerGroup;@PostConstructpublic void start() throws Exception {bossGroup = new NioEventLoopGroup(1);workerGroup = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new SimpleChannelHandler());}}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);ChannelFuture channelFuture = serverBootstrap.bind(port).sync();System.out.println("Netty server started on port: " + port);channelFuture.channel().closeFuture().sync();} finally {shutdown();}}@PreDestroypublic void shutdown() throws InterruptedException {if (bossGroup != null) {bossGroup.shutdownGracefully().sync();}if (workerGroup != null) {workerGroup.shutdownGracefully().sync();}}
}

SimpleChannelHandler.java

java">package com.example.netty;import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/*** Author: 徐寿春* Date:    2024/7/30 11:48* <p>* 名称*/
public class SimpleChannelHandler extends SimpleChannelInboundHandler<String> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("Received message: " + msg);ctx.writeAndFlush("Message received: " + msg);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}
}

NettyApplication.java

java">package com.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/*** Author: 徐寿春* Date:    2024/7/30 11:48* <p>* 名称*/
@SpringBootApplication
public class NettyApplication {public static void main(String[] args) {SpringApplication.run(NettyApplication.class, args);}
}

运行NettyApplication主类,启动Spring Boot应用,设置端口80,Netty服务器会在8080绑定。
使用任何TCP客户端工具连接到localhost的8080端口,发送消息 可以额看到服务器端的响应

java">1. 通过 bind 方法绑定不同的端口 8080
2. closeFuture 用于监听每个绑定端口的关闭事件
3. EventLoopGroup 用于处理 Netty 的事件循环。
4. 接受数据打印: System.out.println("Received message: " + msg);'

Http 和 Https

基本概念

HTTP(HyperText Transfer Protocol, 超文本传输协议)
HTTPS(HTTP Secure)是在HTTP之上添加了SSL/TLS加密层,以保证数据传输的安全性


HTTP请求过程

HTTP是一个无状态协议,即每个请求都是独立的,无需保留之前请求的状态。典型的HTTP请求过程如下:

  1. 客户端与服务器建立TCP连接
  2. 客户端发送HTTP请求到服务器。
  3. 服务器处理请求并返回HTTP响应。
  4. 连接关闭(HTTP/1.0)或保持(HTTP/1.1)

HTTP/1.0 vs HTTP/1.1 vs HTTP/2 vs HTTP/3
  1. HTTP/1.0: 每个请求/响应都需要一个新的TCP连接。
  2. HTTP/1.1: 支持持久连接和分块传输编码,允许复用TCP连接。
  3. HTTP/2: 引入了二进制分帧、多路复用、头部压缩和服务器推送等特性,大幅提高了传输性能。
  4. HTTP/3: 基于QUIC传输协议,进一步改善性能和安全性。
HTTP网络模型
  1. OSI七层模型(物理层、表示层和应用层,网络层、传输层、会话层 、数据链路层)

  2. HTTP网络模型实际上基于更底层的TCP/IP五层模型从上到下分别是:(应用层,传输层,网络层,数据链路层,物理层)

  • 应用层(HTTP协议所在层)处理与应用相关的逻辑操作,例如网页的请求和响应,HTTP协议用于指定要传输的请求和响应的格式。
  • 传输层(TCP协议所在层)负责数据的可靠传输(通过TCP协议),建立连接(通过TCP的三次握手)并保证数据传输的完整性。
  • 网络层(IP协议所在层) 负责数据包的路由选择和传输,IP协议定义每个设备的地址并管理数据包从源到目的地的传输。
“粘包”和“拆包”的问题

粘包是说在TCP传输中,接收方在一次接收操作中读取到多个独立的HTTP请求或响应。白话就是说,多个消息在传输时被粘在了一起了,导致接收方一次读取时得到的数据包含了多个完整的或不完整的消息,大致长这样

HTTP/1.1: 支持持久连接和分块传输编码,允许复用TCP连接。

消息1: HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello World
消息2: HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello

TCP在传输时,可能将这两个消息粘在一起

HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello WorldHTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nHello

那么http要拆一下,拆包是指在TCP传输中,一个完整的HTTP请求或响应被分成了多个部分,接收方需要进行多次接收操作才能拼凑成一个完整的消息,大概长这样

部分1: HTTP/1.1 200 OK\r\nContent-Length: 11\r\n\r\nHello
部分2:  World

原因是因为TCP是字节流协议,没有消息的边界概念,而且不同的网络情况会导致数据发送和接收的时间也不同步。比方你买个沙发,沙发腿和其他组装部件可能不是一个快递包,有些配件比较快,其他的走物流比较慢,不可能同时到达。所以你自己拿到东西自己组合。但是我会给你发设计图,你可以判断是否缺少配件

在处理HTTP协议时,http通过某些方法来检测消息的边界,从而避免粘包和拆包问题。常见的解决办法包括:

  1. 基于固定长度的分隔符:HTTP协议使用了特定的分隔符,比如头部与主体之间使用\r\n\r\n作为分隔符。可以通过解析这些分隔符来确定消息的边界。
  2. Content-Length 头部:HTTP使用Content-Length头部来表示消息主体的长度,接收方可以根据这个头部来确定什么时候完整的消息接收完毕。
  3. Chunked Transfer Encoding:对于长连接或者流式数据传输时,使用HTTP的分块传输编码。每个块有独立的大小和边界标记。

白话文就是,标记好,长度的比较长度,3-1,3-2,3-3,大小的标记大小,100m-25m 100m-50m 100m-25m 或者通过特定的xxx公司xxxx有后续,xxxx公司xxx2无后续,这样去判断。


http://www.ppmy.cn/server/93201.html

相关文章

一条命令安装mysql,php

一条命令安装mysql&#xff0c;php&#xff0c;wget http://soft.vpser.net/lnmp/lnmp1.5.tar.gz -cO lnmp1.5.tar.gz && tar zxf lnmp1.5.tar.gz && cd lnmp1.5 && ./install.sh lnmp

hive中分区与分桶的区别

过去&#xff0c;在学习hive的过程中学习过分桶与分区。但是&#xff0c;却未曾将分区与分桶做详细比较。今天&#xff0c;回顾skew join时涉及到了分桶这一概念&#xff0c;一时间无法区分出分区与分桶的区别。查阅资料&#xff0c;特地记录下来。 一、Hive分区 1.分区一般是…

前端拥抱AI:LangChain.js 入门遇山开路之PromptTemplate

PromptTemplate是什么 PromptTemplate是一个可重复使用的模板&#xff0c;用于生成引导模型生成特定输出的文本。与Prompt的区别: PromptTemplate相对于普通Prompt的优势&#xff0c;即其灵活性和可定制性。 简单了解PromptTemplate后&#xff0c;咱们就来聊聊LangChain里的P…

Elasticsearch(ES) 集群脑裂

脑裂问题(split-brain problem)是指一个分布式系统中&#xff0c;当网络分裂&#xff08;network partition&#xff09;发生时&#xff0c;导致系统内部的两个或多个节点相互独立地认为自己仍然与其他节点连接&#xff0c;每个节点组都试图执行操作&#xff0c;这可能会导致数…

Android笔试面试题AI答之Activity(6)

文章目录 1.描述一下Android Actvity中Window的创建过程 &#xff1f;1. Activity的启动2. onCreate()方法调用3. 窗口&#xff08;Window&#xff09;的创建4. 窗口的显示5. 用户的交互总结 2.简述Android Window的添加过程 &#xff1f;1. 准备工作2. 创建WindowManager.Layo…

一下午连续故障两次,谁把我们接口堵死了?!

唉。。。 大家好&#xff0c;我是程序员鱼皮。又来跟着鱼皮学习线上事故的处理经验了喔&#xff01; 事故现场 周一下午&#xff0c;我们的 编程导航网站 连续出现了两次故障&#xff0c;每次持续半小时左右&#xff0c;现象是用户无法正常加载网站&#xff0c;一直转圈圈。 …

Godot入门 05收集物品

创建新场景&#xff0c;添加Area2D节点&#xff0c;AnimatedSprite2D节点 &#xff0c;CollisionShape2D节点 添加硬币 按F键居中&#xff0c;放大视图。设置动画速度设为10FPS&#xff0c;加载后自动播放&#xff0c;动画循环 碰撞形状设为圆形&#xff0c;修改Area2D节点为Co…

塔子哥的编程乐趣-腾讯2023笔试(codefun2000)

题目链接 塔子哥的编程乐趣-腾讯2023笔试(codefun2000) 题目内容 塔子哥是一位资深的程序员,他最近在研究一种特殊的数组操作。他有一个由正整数组成的数组,数组的长度是偶数。塔子哥可以对数组中的任意一个数字执行以下两种操作之一: 将该数字乘以 2;将该数字除以 2 并向下取…