# React 1. **如何对cookie进行操作** Option1: use JavaScript (不作讨论) Option2: Library: js-cookie 这是一个流行的库,用于处理cookies。它提供了一个简单的API,使得在React中处理cookie变得更加容易。 **Scenario**: User Login – login successful – server return a cookie that demonstrate user session * React前端 ```jsx import React, { useState } from 'react'; function Login() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [message, setMessage] = useState(""); const handleLogin = async () => { try { // 发送登录请求到后端服务器 const response = await fetch("http://localhost:8080/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username, password }), credentials: 'include' // 这个设置确保浏览器会带上cookie信息在请求中 }); const data = await response.json(); if (data.success) { setMessage("Logged in successfully!"); } else { setMessage("Login failed."); } } catch (error) { console.error("Login error:", error); } }; return ( <div> <h1>Login</h1> <input value={username} onChange={e => setUsername(e.target.value)} placeholder="Username" /> <input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="Password" /> <button onClick={handleLogin}>Login</button> {message && <p>{message}</p>} </div> ); } export default Login; ``` Java 后端 ```jsx import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletResponse; @RestController @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true") public class LoginController { @PostMapping("/login") public Map<String, Boolean> loginUser(@RequestBody User user, HttpServletResponse response) { Map<String, Boolean> result = new HashMap<>(); // Mock login logic for simplicity if ("admin".equals(user.getUsername()) && "password123".equals(user.getPassword())) { // Set a simple cookie to represent a user session Cookie sessionCookie = new Cookie("session", "some-session-id"); sessionCookie.setMaxAge(60 * 30); // 30 minutes sessionCookie.setHttpOnly(true); response.addCookie(sessionCookie); result.put("success", true); } else { result.put("success", false); } return result; } } class User { private String username; private String password; // getters and setters... } ``` **流程解释**: 1) 用户在React前端输入用户名和密码 2) 当点击“登录”按钮时,React前端发送一个POST请求到Java后端,携带user输入的用户名和密码 3) Java后端收到请求后,检查用户名和密码。这里为了简化,我们假设只有用户名为“admin”和密码为“pwd123”的用户可以成功登录。 4) 如果验证成功,Java后端创建一个cookie来代表用户的会话,并将其发送回React前端。 5) React前端收到相应后,根据服务器的响应显示成功或失败的消息。 注意: 1) `@CrossOrigin`注释确保我们的后端可以接受来自我们React前端(运行在localhost:3000)的请求。 2) 在React的fetch调用中,我们设置credentials为include,确保每次发出请求时都会带上任何存在的cookie。 3) 在Java后端中,如果登录验证成功,我们创建一个新的cookie并设置其属性。特别要注意的是`setHttpOnly(true)`,这确保了cookie只能由服务器访问,增加了安全性。 2. **React clean cookie** 在前端和后端,删除(或“清除”)cookie的基本思路是将其有效期设置为一个过去的时间,这样浏览器就会自动删除它。 在前端,你可能希望有一个按钮来注销用户。点击这个按钮时,你可以发送一个请求到后端服务器来清除cookie。 在后端,你可以处理注销请求,将cookie的有效期设置为一个过去的时间。 3. **HTTP library (fetch function)** * **Fetch vs js-cookie** - Fetch: Fetch 是一个用于发起网络请求的方法。你可以用它来向服务器请求数据,或者发送数据到服务器。简而言之,它是 Web 开发中数据交换的关键工具。 - js-cookie: js-cookie 是一个用于处理浏览器中的 cookies 的库。你可以用它来创建、读取或删除 cookies。它不涉及网络请求,只是用于操作浏览器存储的小型数据片段(即 cookies)。 * **应用场景**: - Fetch: 当你想从服务器获取信息(例如获取用户列表)或发送信息到服务器(例如提交一个表单)时,你会使用 Fetch。 - js-cookie: 当你需要在用户的浏览器上存储一些短期数据(例如用户登录状态或一些偏好设置)时,你可能会使用 js-cookie。 * **与React的关系**: - Fetch: 虽然 Fetch 可以与任何 JavaScript 项目一起使用,但在 React 应用中,它经常被用于与后端API进行通信。 - js-cookie: 同样,js-cookie 可以在任何前端项目中使用,不仅仅是 React。但在 React 中,它可以方便地帮助你管理和操作 cookies。 Fetch 和 js-cookie 是两个为不同目的设计的工具。Fetch 是用于网络通信的,而 js-cookie 是用于在浏览器中操作 cookies 的。 4. **Hooks** * hooks 是react的一个新特性,它允许你在不编写类组件的情况下使用state和其他react特性。 * 搞不明白了 5. **Redux** * Redux是Redux的官方React UI 绑定层。它允许您的 React 组件从 Redux 存储读取数据,并将操作分派到存储以更新状态。 # Spring Boot ## Jar vs War Spring Boot JAR 提供了一个简单、自承载的部署选项,而 WAR 文件提供了与传统 Java EE 环境的兼容性。 这是因为 Tomcat 和其他 Servlet 容器设计为读取和解压 WAR 文件,然后运行 Web 应用程序。这是 Java Web 开发的传统方式。当你将应用程序部署到外部的 Tomcat 或任何其他 Servlet 容器时,使用 WAR 包是最直接的方法。 ## IoC vs DI vs AOP **IoC** 提供了一个框架,它控制应用程序的流程和对象的生命周期。 **DI** 是实现 IoC 的一种方式,允许动态注入依赖。 **AOP** 允许在不修改核心业务代码的情况下,添加跨越多个对象和方法的公共行为。 Spring Boot 建立在 Spring Framework 上,简化了这些概念的使用,通过自动配置、开箱即用的特性,使开发者可以更轻松地开发高质量的应用程序。 ## Reflection 在计算机编程中,反射是指程序能够在运行时查看和修改其自身结构和行为的能力。具体地说,在面向对象的编程语言中,反射提供了工具和技术,允许一个程序查询并修改其自身的对象、方法、属性和注解的元数据和行为。 例子: ```java public class Book { private String title; public Book(String title) { this.title = title; } public String getTitle() { return title; } } ``` ```java Book myBook = new Book("Harry Potter"); Class<?> bookClass = myBook.getClass(); // 使用(反射 reflection)查看 Book 类的所有方法 for (Method method : bookClass.getMethods()) { System.out.println(method.getName()); } ``` ## 关键的annotation `@RestController = @Controller + @ResponseBody` `@RequestMapping` = `@GetMapping + @PostMapping + @PutMapping + @DeleteMapping + @PatchMapping` 用于映射一个url到对应的处理器(handler),which 就是函数。 `@SpringBootApplication` = `@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan` Via this annotation, we tell the Spring Boot to perform auto configuration and scan all the bean inside our springboot application. `@Configuration / @SpringBootConfiguration` * `@Configuration` 注解在 Spring Boot 中用于定义配置类,这些类是 Java 配置的一部分,用来替代传统的 XML 配置文件。通过在类上标记 `@Configuration`,我们告诉 Spring 容器该类包含了应用程序上下文的 bean 定义。这些 bean 可以通过 @Bean 注解的方法声明。这种方式使得我们能够以程序化的方式来管理依赖注入和服务配置,同时提高了配置的灵活性和可维护性。此外,配置类还支持环境特定配置、其他配置类的导入,以及与外部属性文件的集成。这使得我们能够根据不同的环境(如开发、测试、生产)来定制应用程序的行为。" `@EnableAutoConfiguration` # Core Java ## ConcurrentHashMap * Hash Table 的一个 Implement,存储 key-value * 允许多个线程同时访问,而不需要显式地加锁 (It allows multiple threads to access it simultaneously without the need for explicit locking.) * ConcurrentHashMap 使用哈希算法将键映射到特定的存储桶(buckets)上,这些存储桶包含键值对。每个存储桶可以被视为一个小的哈希表,它们可以独立地加锁以提供并发访问。 **red black tree**:-- 用于解决bucket中数据量过大时候的性能问题,确保操作快速进行。 红黑树是一种自平衡的二叉搜索树,它用于替代 ConcurrentHashMap 存储桶中的链表。 **当某个存储桶中的键值对数量变得很多时,链表的性能可能下降,因此会将链表转化为红黑树以提高性能。** 红黑树具有**对数级别的搜索和插入复杂度**,因此在高负载情况下,它可以提供更好的性能。 **When Red Black Tree?** 是根据特定条件自动触发的。在 Java 8 及更高版本中,ConcurrentHashMap 使用红黑树来替代链表的规则是基于以下条件的: 链表长度:当某个存储桶(bucket)中的链表长度(即键值对的数量)超过一个阈值(默认为 8),并且存储桶的总数量大于一个阈值(默认为 64)时,会考虑将链表替换为红黑树。 操作类型:红黑树的引入通常是为了提高查找操作的性能,因此只有在查找、插入和删除等操作需要进行时才会考虑将链表转换为红黑树。 并发情况:并发情况也会影响是否将链表替换为红黑树,因为在多线程环境中,需要确保数据的一致性和并发安全性。 当以上条件同时满足时,ConcurrentHashMap 会自动将存储桶中的链表转换为红黑树。这种自动应用的机制旨在提高 ConcurrentHashMap 在高负载情况下的性能,因为红黑树通常具有更好的查找性能,特别是在数据量较大的情况下。 # OOD **Java里implement和extend的区别** * extend继承:应用场景:当两个类在概念上存在“是一个(is-a)”关系时,通常使用继承。例如,Dog 是一个 Animal。 * implement: 实现一个接口interface which是抽象的 # Implementing REST Service ![Screenshot 2023-11-18 at 2.39.04 AM](https://hackmd.io/_uploads/Hydm9J8NT.png) # SQL Query Optimization **How to optimize SQL Query**? Reference: https://blog.devart.com/how-to-optimize-sql-query.html 1. 使用EXPLAIN - 在查询语句前添加`EXPLAIN`可以帮助你理解MySQL如何执行你的查询。 具体Scenario: `EXPLAIN SELECT * FROM users WHERE age > 18;` - 查看输出结果。你会得到一个表格,其中的每一行都描述了查询执行的不同阶段或部分。每列都提供了关于查询执行方式的有用信息。 (info about how MySQL access the table.) 主要的`EXPLAIN` output **column**: * **ID**: A unique identifier for the query being executed. * **Select Type**: Tells us the type of select statement is being executed. This can be simple, primary, union, or a few others. * **Table**: The name of the table being accessed. * **Partitions**: Displays the partitions being accessed for the query (beyond the scope of this course). * **Type**: The kind of access MySQL used to retrieve the data. This is one of the most important column values, and we'll discuss it in more detail later. * **Possible Keys**: The possible indexes that MySQL could use. MySQL 可以使用哪些索引来优化查询。 * **Key**: The actual index that MySQL uses. * **Key Length**: Displays the length of the index used by MySQL. * **Ref**: The value being compared to the index. * **Rows**: An estimated number of rows that MySQL needs to examine to return the result. * **Filtered**: The estimated percentage of rows that match the query criteria. **如何解读 EXPLAIN 的结果:** * 如果 `type` 列显示为 `ALL` 或 `index`,这可能意味着查询正在进行全表扫描或全索引扫描,这通常不是最优选择,特别是在大表上。 * 如果 `possible_keys` 列是 `NULL`,这意味着没有合适的索引来帮助查询。 * 在 `Extra` 列中,你希望避免看到如 `"Using filesort"` 或 ``"Using temporary"`` 的条目,因为这些操作可能会降低性能。 * using filesort * 当MySQL需要对结果进行排序,而不能直接用index进行排序的时候,它会使用一个叫filesort的算法 * 对于大型数据集,filesort可能会显著增加查询时间。 * 为避免这种情况,可以尝试优化查询或添加/修改索引,使其对应于查询的 `ORDER BY` 子句。 * Scenario be like: * `SELECT * FROM products ORDER BY date_added DESC LIMIT 10;` * 由于`date_added`没有索引,MySQL可能无法直接使用索引来排序这些产品。结果,它需要完整的表扫描,收集所有的产品,然后用`filesort`来对他们进行排序。这样做在产品数量很少的时候没问题,但是随着产品主粮增长,这个排序过程可能会变得非常慢。 * `ALTER TABLE products ADD INDEX idex_date_added(date_added);` 比如: `EXPLAIN SELECT * FROM users WHERE email = 'john.doe@example.com' OR last_name = 'Doe';` 输出可能会在`possible_keys`中显示`email, last name`, 表示在决策过程中,MySQL考虑了这两个索引。 2. 索引优化 * 查询性能:频繁查询的是index的strong candidate. * 写性能:INDEX can accelerate READ operation, but they will slower the WRITE Operation such as INSERT, UPDATE, DELETE. * 确保 WHERE 子句中用于过滤的字段已被索引。 * 但不是所有WHERE子句中的都要被索引,索引选择取决于查询的频率、数据的分布和其他因素。只有当你觉得某个特定的列被频繁用于WHERE子句,并且索引可以提高性能时,才应该为该列创建索引。例如,性别列(只有“男”和“女”这两个值)可能不适合索引,因为它的基数(唯一值的数量)很低。另一方面,一个包含数百万不同日期的日期列可能是索引的好选择,尤其是当你经常基于日期进行查询时。 * 当你在查询中使用WHERE子句来过滤某些行时,如果过滤条件中的字段(或列)有索引,数据库可以更快地找到匹配的行,因为它不需要扫描整个表。它可以使用索引来快速定位到匹配的行,大大提高查询效率。例如: * `SELECT * FROM users WHERE last_name = 'Smith';` * 在这里,如果last_name列被索引,那么上述查询的性能会更好。 * 但避免过度索引,因为每次插入或更新操作都需要更新索引,这可能会降低写操作的性能。 * 尽管索引可以加速查询,但它们并不是免费的。每当你向表中插入、删除或更新数据时,相关的索引也需要更新。这意味着每个索引都会增加数据修改操作的开销。因此,你不应该盲目地为每一列添加索引,而是要在性能和维护开销之间找到一个平衡点。 * 对于多表连接查询,确保用于 JOIN 的字段已被索引。 * Fuzzy Search 对一个被索引的列执行模糊搜索时,是否能有效利用索引取决于模糊搜索的具体模式 1. 前缀模糊搜索(prefix fuzzy search) LIKE ‘JOHN’那么MySQL能够有效地使用B-tree索引来查找匹配的行。在这种情况下,索引是有助于提高查询效率的。 2. 使用后缀 suffix 或中间模糊搜索:但是,如果你使用像LIKE '%ohn'或LIKE '%oh%'这样的模糊搜索,MySQL就不能有效地使用B-tree索引,因为它不知道搜索的起始点在哪里。在这种情况下,它通常会执行全表扫描,即使该列已被索引。 **TRACE**: 追踪查询执行的每个步骤。 **SHOW PROFILE**: 提供查询的性能详细信息,如执行时间、CPU 使用情况等。 **Performance Schema**: 是一个动态的性能调优工具,可以监控 MySQL 服务器的运行时性能。 **避免 SELECT * ** 只选择你真正需要的列,而不是选择所有列。 使用 LIMIT 如果你只对结果集中的前几条记录感兴趣,使用 LIMIT 来限制返回的行数。 避免使用复杂的子查询 尽量使用 JOIN 替代子查询。 避免在 WHERE 子句中使用函数或计算 这会导致查询无法使用索引并扫描每一行。 优化 LIKE 查询 使用 LIKE 'pattern%' 可以使用索引,而 LIKE '%pattern%' 不能。 MySQL 的分析工具: TRACE: 追踪查询执行的每个步骤。 SHOW PROFILE: 提供查询的性能详细信息,如执行时间、CPU 使用情况等。 Performance Schema: 是一个动态的性能调优工具,可以监控 MySQL 服务器的运行时性能。 Slow Query Log: 跟踪执行时间超过 long_query_time 秒数的查询。 mysqltuner: 是一个脚本,分析你的 MySQL 服务器并提供优化建议。 # Redis 我在实习中使用Redis的场景: 在电商平台上(Phone Card Dealer),产品信息是经常被查询的数据。对于热门商品(Propular Products),其详情页可能会在短时间内被大量用户反复访问。每次都从数据库中获取这些信息不仅会导致延迟,还会增加数据库的负载。因此,可以使用 Redis 对产品信息进行缓存,以提高性能和响应速度。 实现步骤: 1. **当用户请求一个产品详情时:** - 先检查Redis缓存是否包含该产品的信息 - 如果Redis中存在该产品的缓存,直接返回缓存中的数据给用户,可以极大减少响应时间。 - 如果Redis中不存在该产品的数据,那么就从数据库中查询。 2. **从数据库查询产品信息** - 当从数据库中获取产品信息后,将这些数据存储到 Redis 缓存中,并为其设置一个合适的过期时间(例如,10分钟)。这样,未来的请求可以直接从 Redis 中获取数据,而不需要查询数据库。 3. **产品信息更新或者无效化** - 当后台管理系统中产品信息被修改(例如,价格或库存变化)时,确保更新或清除 Redis 中的相关缓存,以确保用户看到的数据是最新的。 Cache Fragmentation; Cache penetration; Cache avalanche; Sliding Expiration; Mutex (Mutual Exclusion Lock) 1- Redis的五种数据类型 * 字符串 String * Hashes * List (双头 LinkedList,可以实现最新消息排队功能) * Set (无序集合,就是HashSet,实战场景:用户给post加tag; 点赞,或点踩,收藏等,可以放到set中实现) * SortedSet 对应的Scenario: 1. 存captcha: * 数据类型:String * Captcha代码通常是一个短字符串,可以直接存储为字符串类型 * 键: captcha:{sessionID或userID} 2. 存热门产品信息 * 哈希 Hash * 原因: 产品信息通常包含多个字段,如产品ID、产品名称、描述、价格等。哈希结构非常适合存储对象及其属性。 * Practice: * Key 键:product: {productID} * field: * name: 产品名称 * description: 产品描述 * price: 产品价格 * ... 其他相关字段 2- spring data redis的基本使用方法, 比如如何get 或者set数据到Redis中 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` ```java @Service public class ProductService { @Autowired private RedisTemplate<String, Product> redisTemplate; @Autowired private ProductRepository productRepository; // 假设您使用的是 Spring Data JPA private static final String PRODUCT_KEY_PREFIX = "product:"; public Product getProductById(Long productId) { // 尝试从 Redis 中获取产品信息 Product product = redisTemplate.opsForValue().get(PRODUCT_KEY_PREFIX + productId); // 如果 Redis 缓存中没有,从数据库中查询 if (product == null) { product = productRepository.findById(productId).orElse(null); if (product != null) { // 将产品信息存储到 Redis 缓存中,并设置过期时间为 10 分钟 redisTemplate.opsForValue().set(PRODUCT_KEY_PREFIX + productId, product, 10, TimeUnit.MINUTES); // redisTemplate.opsForValue().set("key", "value", 10, 过期时间单位); } } return product; } public void updateProduct(Product updatedProduct) { // 更新数据库中的产品信息 productRepository.save(updatedProduct); // 清除 Redis 中的相关缓存 redisTemplate.delete(PRODUCT_KEY_PREFIX + updatedProduct.getId()); } // ... 其他方法 } ``` # Docker # Kafka 了解基本概念, 比如topics是什么 * Apache Kafka is an open-source distributed **event streaming platform** used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications. * Microservice 场景下,Kafka作为MQ or 消息中间件使用,因为各个微服务之间Decouple了,所以Kafka是各个微服务之间的消息媒介。 * 具体的flow是: 1. 事件定义:对于微服务架构中的每种服务,首先确定可能会发生的事件。例如,对于一个用户服务,可能的事件包括:用户创建、用户修改和用户删除; 2. 事件生产Producer: 当在某个服务(如用户服务)中发生了一个特定事件(如用户创建)的时候,该服务会将这个事件发布Kafka。通常序列化成JSON格式发送。 3. 事件消费Consumer:其他服务(如订单服务、通知服务等)可以订阅感兴趣的 Kafka 主题。 4. 错误处理和重试:如果出现问题,就重新入对,或者发送事件到一个 "死信队列" 以供稍后处理。 5. Kafka 保留事件的能力使得它非常适合用作审计日志。这意味着你可以随时跟踪和查看系统中发生的所有事件。 6. 对于更复杂的用例,你可以使用 Kafka Streams 或 KSQL 来处理、过滤或转换流式事件数据。 * Kafka的topics是一个消息流或事件流的名称或标识符。它表示存储在Kafka集群中的一系列相关的消息或事件。它为消息提供持久性存储,这意味着即使生产者产生消息后停止运行,消息仍然可以被消费者消费,因为它们被存储在Kafka的brokers上。 * 分区: 为了支持大规模的并行处理和增加吞吐量,每个topic可以被分割为多个partitions(分区)。消息被写入一个特定的分区,通常基于消息的key的哈希值。每个分区都是有序的,并且可以在多个broker上进行复制以提供数据冗余和容错性。 * 消费者组: 消费者可以组成消费者组来并行地读取一个topic。在消费者组中,每个消息只会被一个消费者实例读取,这允许消费者可以在多台机器上并行处理数据。 * 生产者和消费者: 在Kafka中,生产者负责向topics发送消息,而消费者从topics读取消息。 **Scenario: UserService 和 SubscriptionService:** 1. 发起购买,userService接收到购买请求后,首先验证用户的身份和支付信息。 2. 验证成功后,UserService 发布一个 UserSubscriptionInitiated 事件到 Kafka 主题 user-subscription-events。 3. SubscriptionService 订阅了 user-subscription-events 主题,所以它接收到了 UserSubscriptionInitiated 事件。 4. SubscriptionService 根据事件中的信息为用户创建一个新的订阅记录,并将订阅状态设置为 PENDING。 5. 如果这是一个付费订阅,SubscriptionService 会处理付款,并将状态更新为 CONFIRMED。然后,SubscriptionService 发布一个 SubscriptionConfirmed 事件到 Kafka 主题 subscription-confirmation-events。 6. UserService 再次消费: UserService 订阅了 subscription-confirmation-events 主题,所以它接收到了 SubscriptionConfirmed 事件。UserService 更新用户的状态为已订阅,并可能赋予用户与该订阅相关的特定权限或功能。 7. 通知用户: 一旦用户的订阅状态得到更新,UserService 可以发布一个 UserNotifiedForSubscription 事件到 user-notification-events 主题。 SubscriptionService 订阅此主题,当接收到 UserNotifiedForSubscription 事件时,它可以向用户发送一个通知,例如通过电子邮件或短信,确认他们已成功购买订阅。 **UserService发布event到Kafka** 这里我们使用 `KafkaTemplate` 来发布消息。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; @Service public class UserService { @Autowired private KafkaTemplate<String, String> kafkaTemplate; public void initiateSubscription(String userId, String subscriptionType) { // ...验证用户的身份和支付信息等逻辑... // 构建事件消息 String event = String.format("{ \"userId\": \"%s\", \"subscriptionType\": \"%s\" }", userId, subscriptionType); // 发送到 Kafka kafkaTemplate.send("user-subscription-events", userId, event); } } ``` **SubscriptionService 消费事件并处理:** 使用`@KafkaListener` ```java import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Service; @Service public class SubscriptionService { @KafkaListener(topics = "user-subscription-events", groupId = "subscriptionGroup") public void handleSubscriptionInitiated(String event) { // 反序列化事件,这里简化为直接使用字符串 // 实际应用中,可能会使用如 Jackson 之类的库来反序列化JSON String userId = extractUserIdFromEvent(event); String subscriptionType = extractSubscriptionTypeFromEvent(event); // ...根据事件中的信息为用户创建一个新的订阅记录... // ...处理付款,并将状态更新为 CONFIRMED... } private String extractUserIdFromEvent(String event) { // 示意性代码,实际中应使用JSON库 return event.split("userId")[1].split(":")[1].trim().replace("\"", ""); } private String extractSubscriptionTypeFromEvent(String event) { // 示意性代码,实际中应使用JSON库 return event.split("subscriptionType")[1].split(":")[1].trim().replace("\"", ""); } } ``` 微服务架构下 RESTful API 的Directory Be Like: ```plaintext /my-e-commerce-platform /user-service /src /main /java /com /mysite /userservice /controllers UserController.java /services UserService.java /repositories UserRepository.java /models User.java /config SecurityConfig.java DatabaseConfig.java /resources application.properties /test Dockerfile pom.xml (if it's a Maven project) build.gradle (if it's a Gradle project) /order-service /src /main /java /com /mysite /orderservice /controllers OrderController.java /services OrderService.java /repositories OrderRepository.java /models Order.java /resources application.properties /test Dockerfile pom.xml /product-service ... (similar structure as above) /config-server (optional, for centralized configuration management) /api-gateway (optional, for routing, load balancing, etc.) /discovery-server (optional, for service discovery, e.g., Eureka) ``` From this directory tree we can see: 1. 每个微服务,如`user-service`, `order-service`, `product-service` 都是一个独立的项目,它有自己的源代码、配置文件和构建脚本。 2. 在每个微服务内部,代码通常根据功能(如控制器、服务、存储库和模型)分组到不同的包和目录中。 3. 可以使用想Docker这样的工具容器化每个服务,以简要部署或者扩展。 # Kubernetes ## 基本概念 Kubernetes is a tool to organize and manage containers. Node: node 是个物理概念,代表一台机器 Pod: 每台机器运行one or multiple containers. Pod is the smallest unit in Kubernetes. Control Plane (中心计算机): 集中管理pods,通过专有的api和nodes进行通信。 Service is an abstract object which route the network traffic to the right pod. Deployment is the claim that tell kubernetes how to run and update your application. ![k8s](https://hackmd.io/_uploads/rJJMeoH-a.png) ## Deploy Spring Boot 微服务到AWS 1. 打包应用,构建docker镜像(Dockerfile + codepipeline.yaml 2. 推送Docker image到Amazon Elastic Container Registry (ECR),把image推送到该仓库。 3. Kubernetes Configuration - 如果你还没有 Kubernetes 集群,你可以使用 Google Kubernetes Engine (GKE)、Amazon EKS (Elastic Kubernetes Service) 或 Microsoft AKS 等托管服务,或自行设置一个 Kubernetes 集群。 4. 编写 Kubernetes 配置 - 为了在 Kubernetes 上部署应用,你需要创建一个 Deployment 和 Service。创建一个 `k8s-deployment.yaml` 文件,内容可能如下: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-spring-boot-app spec: replicas: 2 selector: matchLabels: app: my-spring-boot-app template: metadata: labels: app: my-spring-boot-app spec: containers: - name: my-spring-boot-app image: yourusername/my-spring-boot-app:latest ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: my-spring-boot-app spec: selector: app: my-spring-boot-app ports: - protocol: TCP port: 80 targetPort: 8080 type: LoadBalancer ``` 6. 使用`kubectl`部署应用 首先,确保你的 `kubectl` 命令行工具已经配置了正确的 Kubernetes 集群。然后,执行以下命令来部署应用: ```zsh kubectl apply -f k8s-deployment.yaml ``` 7. 访问应用 如果你使用的是 LoadBalancer 服务类型,可以使用以下命令查找分配给你的服务的公共 IP: ```zsh kubectl get svc my-spring-boot-app ``` ## Kubernetes for ME **使用Kafka的微服务架构场景**: 假设你在一个电商平台中拥有多个微服务,包括“订单”服务、“库存”服务和“支付”服务。当用户下单时,“订单”服务需要与“库存”服务通信以确认库存,并与“支付”服务通信以处理付款。 * 订单服务是生产者producer,它发送订单详情到Kafka * “库存”服务和“支付”服务作为消费者从Kafka中获取订单详情并进行处理 **解决方案**: 使用Kubernetes的Service对象。Service为运行在一个或多个Pod中的应用程序提供了一个稳定的IP地址和DNS名称。这样,“订单”服务只需要知道Service的DNS名称,例如payments.default.svc.cluster.local,而无需知道具体的Pod IP。Kubernetes的内部负载均衡会自动将请求路由到正确的Pod。 **步骤1**: 你首先需要确保Kafka集群在Kubernetes中可用,并且有一个Kafka Service供其他服务使用。 **步骤2**: “订单”服务需要知道Kafka Service的地址来发送消息。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; @Service public class OrderService { @Autowired private KafkaTemplate<String, OrderDetail> kafkaTemplate; public void placeOrder(OrderDetail orderDetail) { // ... other business logic ... // Send order details to Kafka topic kafkaTemplate.send("order-topic", orderDetail); } } ``` 在`application.properties`或`application.yml`中,你需要配置Kafka的broker地址,这里你应该使用Kafka Service的DNS名称: ``` spring.kafka.producer.bootstrap-servers=kafka-service.default.svc.cluster.local:9092 ``` **步骤3**: “库存”服务和“支付”服务需要从Kafka中消费这些消息。你可以为这两个服务配置不同的消费者组,这样每个服务都会收到所有的消息。 在我们之前讨论的Kafka的微服务示例中,实际上并没有为了Kubernetes特别编写的代码。我们的主要讨论是关于服务间如何使用Kafka进行通信,而这个过程在Kubernetes内外都是相似的。但是,当我们在Kubernetes中部署应用程序时,有一些考虑和优势需要明确: 服务发现: Kubernetes提供了Service对象,它为运行在Pods中的应用程序提供了一个稳定的IP和DNS名称。所以,当你的微服务需要连接到Kafka时,你只需要知道Kafka Service的DNS名称,例如kafka-service.default.svc.cluster.local,而不是具体的Pod或容器的IP。这为微服务之间的通信提供了一个抽象层,从而使得Pods可以自由地移动和扩展。 配置管理: 使用Kubernetes的ConfigMap和Secret对象,你可以动态地为你的微服务和Kafka提供配置。例如,你可以将Kafka的连接参数或生产者/消费者的配置存储在ConfigMap中,并在Pod启动时动态地注入这些配置。 ## Notions node 是物理概念,一个机器是一个node 一个pod里一般来说运行一个container,但支持好几个container。 replicasets: * replicas: 3 就是有三个pod * authentication service:eg. 10万并发量,一个pod肯定是受不了的。 * eg.100个pods, 哪个pod掉线了,它可以自动重启。 service: * port: 70 * targetPort: 9376 client -- request -- to pod -- 首先通过ingress,这个ingress它负责 pod to internet * ingress * service (内部网络访问权限) 就 host map 到 一个 pod上 a a a a a a a a