操作系统中25 个关键术语及其简要定义,涵盖了操作系统中的核心概念。以下是总结:

  1. 进程与线程:进程是运行中的程序实例,拥有独立内存和资源;线程是进程内的最小执行单位,共享进程内存。
  2. 上下文切换与调度:上下文切换是 CPU 从一个进程/线程切换到另一个;调度由操作系统决定进程/线程的 CPU 时间分配。
  3. 内存管理:包括虚拟内存(允许程序使用超出物理内存的内存)、分页(将内存分为固定大小块)、分段(逻辑划分内存为代码、数据、栈等)、交换(在内存满时将数据移到磁盘)。
  4. 内核与用户空间:内核是操作系统核心,控制内存、进程、I/O;用户空间运行用户程序,受限访问,内核有完全权限。
  5. 中断与系统调用:中断是通知 CPU 处理紧急事件的信号;系统调用是程序请求操作系统执行底层操作。
  6. I/O 与文件系统:I/O 缓冲暂时存储数据以提升性能;文件描述符是操作系统对打开文件或套接字的引用;文件系统管理磁盘上的数据存储与访问。
  7. 并发与同步问题:死锁是进程互相等待资源导致无法继续;竞争条件是系统输出依赖于不可控事件时序。
  8. 信号与进程控制:信号用于通知进程事件(如终止或中断);Fork & Exec 在 Unix 中用于创建新进程和运行程序。
  9. 后台进程与内存问题:守护进程/服务是无需用户交互的后台进程;内存泄漏是分配的内存未释放导致系统变慢;僵尸进程是已终止但仍留在进程表中的进程。
  10. 孤儿进程与启动:孤儿进程是父进程退出后被 init(PID 1)收养的进程;引导加载程序是系统启动时加载操作系统的底层程序。
  11. 文件系统挂载:挂载是将文件系统(如 USB 或外部磁盘)附加到主文件系统树的过程。

对应英文:
20250520143256-2025-05-20

Comment and share

设计模式概览

1. Singleton(单例模式)

类别:创建型(Creational)
说明:确保一个类只有一个实例,并提供全局访问点。
使用场景:当需要确保只有一个实例时(例如,数据库连接、配置管理)。
独特性:限制实例化为单一对象。
常见用例:日志记录、线程池、缓存、配置设置。
UML图:
20250407152734-2025-04-07

2. Factory Method(工厂方法模式)

类别:创建型
说明:提供一个接口用于创建对象,但让子类决定创建对象的具体类型。
使用场景:当对象创建逻辑需要集中管理且动态时。
独特性:将对象创建逻辑封装在一个方法中。
常见用例:框架、库、依赖注入。
UML图:
20250407172852-2025-04-07

3. Abstract Factory(抽象工厂模式)

类别:创建型
说明:创建一组相关或依赖对象的接口,而无需指定具体类。
使用场景:当需要动态创建多个相关对象家族时。
独特性:封装多个工厂方法。
常见用例:跨平台UI工具包、插件架构、创建复杂对象(例如文档、UI组件、HTTP请求)。
UML图:
20250407173021-2025-04-07

4. Builder(建造者模式)

类别:创建型
说明:将对象的构造过程与其表示分离,允许分步创建。
使用场景:当对象构造逻辑复杂时。
独特性:允许分步构建对象。
常见用例:游戏开发(例如克隆敌人)、对象池。
UML图:
20250407173147-2025-04-07

5. Prototype(原型模式)

类别:创建型
说明:通过复制原型实例来创建对象。
使用场景:当对象创建成本高且克隆更高效时。
独特性:通过克隆现有对象创建。
常见用例:游戏开发(例如克隆敌人)、对象池。
UML图:
20250407173247-2025-04-07

6. Adapter(适配器模式)

类别:结构型(Structural)
说明:允许不兼容的接口协同工作。
使用场景:当需要整合遗留代码或第三方API时。
独特性:作为不兼容接口之间的桥梁。
常见用例:连接遗留系统、API兼容性。
UML图:
20250407173343-2025-04-07

7. Decorator(装饰者模式)

类别:结构型
说明:动态地为对象添加行为,而无需修改其类。
使用场景:当需要动态扩展功能时。
独特性:包装对象以添加行为。
常见用例:日志记录、缓存、权限、安全包装。
UML图:
20250407173502-2025-04-07

8. Facade(外观模式)

类别:结构型
说明:为子系统中的一组接口提供统一的接口。
使用场景:当需要简化复杂子系统供客户端使用时。
独特性:隐藏复杂性,提供简单接口。
常见用例:API网关、复杂库、应用程序。
UML图:
20250407173619-2025-04-07

9. Proxy(代理模式)

类别:结构型
说明:为另一个对象提供代理或占位符以控制访问。
使用场景:当需要控制对象访问时(例如,延迟初始化、安全、远程访问)。
独特性:作为受控访问的中间人。
常见用例:延迟加载、安全代理、远程代理。
UML图:
20250407173704-2025-04-07

10. Bridge(桥接模式)

类别:结构型
说明:将抽象与实现解耦,使两者可以独立变化。
使用场景:当抽象和实现需要独立扩展时。
独特性:分离抽象与实现。
常见用例:跨平台GUI框架、设备驱动。
UML图:
20250407173746-2025-04-07

11. Composite(组合模式)

类别:结构型
说明:允许以统一方式处理单个对象和对象组合。
使用场景:当处理树形结构时,组件和组合需要一致处理。
独特性:表示部分-整体层次。
常见用例:文件系统、UI元素。
UML图:
20250407173857-2025-04-07

12. Observer(观察者模式)

类别:行为型(Behavioral)
说明:定义对象间一对多的依赖关系,当状态变化时通知所有依赖对象。
使用场景:当多个对象需要对状态变化做出反应时。
独特性:封装事件驱动通信。
常见用例:事件处理、UI框架、发布-订阅系统。
UML图:
20250407173955-2025-04-07

13. Strategy(策略模式)

类别:行为型
说明:定义一组算法家族,使其可互换。
使用场景:当需要在运行时选择行为时。
独特性:封装可互换算法。
常见用例:排序算法、验证策略。
UML图:
20250407174040-2025-04-07

14. Command(命令模式)

类别:行为型
说明:将请求封装为对象,允许参数化客户端。
使用场景:当需要撤销/重做功能或排队请求时。
独特性:将请求封装为对象。
常见用例:事务系统、撤销/重做、远程操作。
UML图:
20250407174120-2025-04-07

15. State(状态模式)

类别:行为型
说明:允许对象在其内部状态改变时改变行为。
使用场景:当对象行为随状态变化时。
独特性:封装状态特定行为。
常见用例:状态机、工作流管理。
UML图:
20250407174208-2025-04-07

16. Mediator(中介者模式)

类别:行为型
说明:定义一个对象,封装对象间的交互。
使用场景:当需要减少对象间的直接依赖时。
独特性:集中通信。
常见用例:聊天室、事件处理、消息系统。
20250407174241-2025-04-07

17. Chain of Responsibility(责任链模式)

类别:行为型
说明:将请求沿处理者链传递,直到一个处理者处理它。
使用场景:当请求处理者需要动态处理时。
独特性:允许动态处理者委托。
常见用例:日志框架、事件传播。
20250407174319-2025-04-07

18. Template Method(模板方法模式)

类别:行为型
说明:定义算法骨架,子类按步骤填充。
使用场景:当需要强制执行一系列步骤但允许灵活性时。
独特性:封装算法结构。
常见用例:框架、算法定制。
UML图:
20250407174354-2025-04-07

工厂模式名称和别名的由来

  1. 简单工厂(Simple Factory)
    名称由来:被称为“简单”是因为它是最基础的工厂模式,逻辑简单,集中在一个类中。
    别名:有时称为“静态工厂”(Static Factory),因为创建方法通常是静态的。
    原因:它不属于GoF的23种模式,但因其简单性和实用性被广泛使用。
  2. 工厂方法(Factory Method)
    名称由来:强调“方法”这一核心概念,工厂方法模式通过定义一个方法(factoryMethod())来创建对象,具体实现由子类决定。
    别名:有时称为“虚拟构造器”(Virtual Constructor),因为它通过多态性“构造”对象。
    原因:通过方法的多态性实现对象的创建,符合面向对象设计原则。
  3. 抽象工厂(Abstract Factory)
    名称由来:被称为“抽象”是因为它定义了一个抽象接口(或抽象类)来创建一组对象,具体工厂实现由子类提供。
    别名:有时称为“工具包”(Kit),因为它提供了一套创建相关对象的工具。
    原因:它抽象了产品家族的创建,适合需要创建一组相关对象的场景。
    总结
    工厂模式对比:
    简单工厂:最简单,但扩展性差,适合小型系统。
    工厂方法:关注单一产品,扩展性好,符合开放-封闭原则。
    抽象工厂:处理产品家族,适合复杂对象创建场景。
    名称由来:工厂模式名称反映了其核心特点(简单性、方法、抽象性),别名则源于其功能或实现方式(如静态工厂、虚拟构造器、工具包)。
    UML图:通过类图展示了每种设计模式的结构和关系,帮助理解其实现方式和适用场景。

总结

创建型模式(Singleton, Factory Method, Abstract Factory, Builder, Prototype):关注对象创建,解决实例化问题,适合需要动态或复杂对象创建的场景。
结构型模式(Adapter, Decorator, Facade, Proxy, Bridge, Composite):关注对象和类的组织,简化结构或扩展功能,适合处理复杂系统或接口兼容性。
行为型模式(Observer, Strategy, Command, State, Mediator, Chain of Responsibility, Template Method):关注对象间的交互和职责分配,适合动态行为或事件驱动系统。

Comment and share

背景

在Spring 使用注入依赖bean的时候,可能既有使用@Autowired 的字段注入,也可能使用构造器注入。如此场景,在写单元测试的时候使用@Mock的Field就是null了。这是由于Mockito初始化@InjectMocks的被
测试对象时的策略会采用合适的构造器进行实例化,着用@Mock的Field在使用时就是null.

示例代码

OldService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
@RequiredArgsConstructor
public class OldService {

@Autowired
private OldService2 oldService;

//add new dependency bean
private final NewService newService;

public void save(SomeObject someObj) {
SomeObject newObject = oldService.handleSome(someObj);
newService.save(newObject);
}
}

OldServiceTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ExtendWith(MockitoExtension.class)
public class OldServiceTest {
@InjectMocks
private OldService oldService;

@Mock
private OldService2 oldService;

@Mock
private NewService newService;

@Test
void should_save_but_oldService_will_NPE(){
//will NPE
when(oldService.handleSome(someObj)).thenReturn(newObject)
}

}

解决方案

自定义CustomInjectionExtension.java 处理字段注入的mock

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.*;


/**
* JUnit 5 extension that enhances Mockito's dependency injection capabilities.
* This extension locates fields annotated with @InjectMocks and injects @Mock/@Spy objects
* into them, handling null instances and constructor injection automatically.
*/
public class CustomInjectionExtension implements BeforeEachCallback {

@Override
public void beforeEach(ExtensionContext context) throws Exception {
Object testInstance = context.getRequiredTestInstance();

// Find and process the field with @InjectMocks annotation
Field injectMocksField = findInjectMocksField(testInstance);
Object injectMocksInstance = getOrCreateInjectMocksInstance(testInstance, injectMocksField);

// Perform dependency injection
injectDependencies(testInstance, injectMocksInstance, injectMocksField.getType());
}

/**
* Finds the field annotated with @InjectMocks in the test class.
*
* @param testInstance the test class instance
* @return the field annotated with @InjectMocks
* @throws RuntimeException if no field with @InjectMocks is found
*/
private Field findInjectMocksField(Object testInstance) {
return Arrays.stream(testInstance.getClass().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(InjectMocks.class))
.findFirst()
.orElseThrow(() -> new RuntimeException("No @InjectMocks annotation found in test class"));
}

/**
* Gets the existing @InjectMocks instance or creates a new one if null.
*
* @param testInstance the test class instance
* @param injectMocksField the field annotated with @InjectMocks
* @return the instance of the class to inject mocks into
* @throws Exception if instance creation fails
*/
private Object getOrCreateInjectMocksInstance(Object testInstance, Field injectMocksField) throws Exception {
injectMocksField.setAccessible(true);
Object injectMocksInstance = injectMocksField.get(testInstance);

if (injectMocksInstance == null) {
Map<Class<?>, Object> mocksByType = collectMockObjects(testInstance);
injectMocksInstance = createInstanceViaConstructor(injectMocksField.getType(), mocksByType);
injectMocksField.set(testInstance, injectMocksInstance);
}

return injectMocksInstance;
}

/**
* Collects all mock objects from the test class instance.
*
* @param testInstance the test class instance
* @return map of mock objects by their type
* @throws IllegalAccessException if field access fails
*/
private Map<Class<?>, Object> collectMockObjects(Object testInstance) throws IllegalAccessException {
Map<Class<?>, Object> mocksByType = new HashMap<>();

for (Field field : testInstance.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Mock.class) || field.isAnnotationPresent(Spy.class)) {
field.setAccessible(true);
Object mockObject = field.get(testInstance);

// Create mock object if null
if (mockObject == null) {
mockObject = createMockObject(field);
field.set(testInstance, mockObject);
}

mocksByType.put(field.getType(), mockObject);
}
}

return mocksByType;
}

/**
* Creates a mock or spy object based on field annotations.
*
* @param field the field to create a mock for
* @return created mock or spy object
*/
private Object createMockObject(Field field) {
if (field.isAnnotationPresent(Mock.class)) {
return Mockito.mock(field.getType());
} else {
return Mockito.spy(field.getType());
}
}

/**
* Creates an instance of the target class by finding and using an appropriate constructor.
*
* @param targetClass the class to instantiate
* @param availableMocks map of available mock objects by type
* @return the created instance
* @throws RuntimeException if instance creation fails
*/
private Object createInstanceViaConstructor(Class<?> targetClass, Map<Class<?>, Object> availableMocks) {
Constructor<?>[] constructors = targetClass.getDeclaredConstructors();
Arrays.sort(constructors, (c1, c2) -> c2.getParameterCount() - c1.getParameterCount());

for (Constructor<?> constructor : constructors) {
constructor.setAccessible(true);
Object[] constructorArgs = prepareConstructorArguments(constructor, availableMocks);

try {
return constructor.newInstance(constructorArgs);
} catch (Exception e) {
// Continue to next constructor if this one fails
continue;
}
}

throw new RuntimeException(String.format(
"Could not instantiate %s. No suitable constructor found that can be called with available mocks.",
targetClass.getSimpleName()));
}

/**
* Prepares arguments for a constructor by matching parameter types with available mocks.
*
* @param constructor the constructor to prepare arguments for
* @param availableMocks map of available mock objects by type
* @return array of constructor arguments
*/
private Object[] prepareConstructorArguments(Constructor<?> constructor, Map<Class<?>, Object> availableMocks) {
Parameter[] parameters = constructor.getParameters();
Object[] args = new Object[parameters.length];

for (int i = 0; i < parameters.length; i++) {
Class<?> paramType = parameters[i].getType();
args[i] = findOrCreateMockForType(paramType, availableMocks);
}

return args;
}

/**
* Finds an existing mock for the given type or creates a new one.
*
* @param requiredType the type to find or create a mock for
* @param availableMocks map of available mock objects by type
* @return a mock object compatible with the required type
*/
private Object findOrCreateMockForType(Class<?> requiredType, Map<Class<?>, Object> availableMocks) {
// Try exact type match
Object mock = availableMocks.get(requiredType);
if (mock != null) {
return mock;
}

// Try assignable types (interfaces, superclasses)
mock = availableMocks.entrySet().stream()
.filter(entry -> requiredType.isAssignableFrom(entry.getKey()))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);

if (mock != null) {
return mock;
}

// Create new mock if no match found
mock = Mockito.mock(requiredType);
availableMocks.put(requiredType, mock);
return mock;
}

/**
* Injects mock dependencies into the target instance.
*
* @param testInstance the test class instance
* @param targetInstance the instance to inject mocks into
* @param targetClass the class of the target instance
* @throws Exception if injection fails
*/
private void injectDependencies(Object testInstance, Object targetInstance, Class<?> targetClass) throws Exception {
Map<Class<?>, Object> mocksByType = collectMockObjects(testInstance);

for (Map.Entry<Class<?>, Object> entry : mocksByType.entrySet()) {
Class<?> mockType = entry.getKey();
Object mockObject = entry.getValue();

injectMockIntoCompatibleFields(targetInstance, mockType, mockObject);
}
}

/**
* Injects a mock object into all compatible fields of the target instance.
*
* @param targetInstance the instance to inject mocks into
* @param mockType the type of the mock object
* @param mockObject the mock object to inject
* @throws IllegalAccessException if field access fails
*/
private void injectMockIntoCompatibleFields(Object targetInstance, Class<?> mockType, Object mockObject)
throws IllegalAccessException {
Class<?> targetClass = targetInstance.getClass();

for (Field field : getAllFields(targetClass)) {
if (isTypeCompatible(field.getType(), mockType)) {
field.setAccessible(true);
if (field.get(targetInstance) == null) {
field.set(targetInstance, mockObject);
}
}
}
}

/**
* Checks if two types are compatible for injection.
*
* @param fieldType the type of the field
* @param mockType the type of the mock
* @return true if the types are compatible, false otherwise
*/
private boolean isTypeCompatible(Class<?> fieldType, Class<?> mockType) {
return fieldType.isAssignableFrom(mockType) || mockType.isAssignableFrom(fieldType);
}

/**
* Gets all fields from a class and its superclasses.
*
* @param clazz the class to get fields from
* @return array of all fields
*/
public Field[] getAllFields(Class<?> clazz) {
List<Field> allFields = new ArrayList<>();
Class<?> currentClass = clazz;

while (currentClass != null) {
allFields.addAll(Arrays.asList(currentClass.getDeclaredFields()));
currentClass = currentClass.getSuperclass();
}

return allFields.toArray(new Field[0]);
}
}

OldServiceTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ExtendWith({MockitoExtension.class, CustomInjectionExtension.class})
public class OldServiceTest {
@InjectMocks
private OldService oldService;

@Mock
private OldService2 oldService;

@Mock
private NewService newService;

@Test
void should_save_but_oldService_will_NPE(){
//will Fixed
when(oldService.handleSome(someObj)).thenReturn(newObject)
}

}

Comment and share

corePoolSize: the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
maximumPoolSize: the maximum number of threads to allow in the pool keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unit : the time unit for the keepAliveTime argument
workQueue: the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.
handler: the handler to use when execution is blocked because the thread bounds and queue capacities are reache

  1. corePoolSize(核心线程数)

线程池中会始终保持的最小线程数
即使这些线程处于空闲状态,也不会被销毁(除非设置了allowCoreThreadTimeOut为true)
这些线程响应速度最快,因为不需要创建新线程

  1. maximumPoolSize(最大线程数)

线程池允许创建的最大线程数
当工作队列满了,且当前线程数小于maximumPoolSize时,会创建新线程
超过这个数量的任务会触发拒绝策略

  1. keepAliveTime(空闲线程存活时间)

非核心线程空闲超过这个时间后会被销毁
如果allowCoreThreadTimeOut为true,核心线程也会受这个参数影响
用于减少资源浪费

  1. unit(时间单位)

keepAliveTime的时间单位
可以是SECONDS、MINUTES等TimeUnit枚举值

  1. workQueue(工作队列)

用于存放待执行的任务
常用队列类型:

ArrayBlockingQueue:有界队列
LinkedBlockingQueue:无界队列
SynchronousQueue:无缓冲的等待队列
PriorityBlockingQueue:优先级队列

  1. handler(拒绝策略)

当队列满且线程数达到maximumPoolSize时的处理策略
默认策略类型:

AbortPolicy:抛出异常(默认)
CallerRunsPolicy:在调用者线程执行
DiscardPolicy:直接丢弃
DiscardOldestPolicy:丢弃最早的任务

Process UML

planUML描述如下:

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
57
58
59
60
61
62
63
64
65
66
67
68
@startuml
title ThreadPoolExecutor执行流程

skinparam backgroundColor #FFFFFF
skinparam handwritten false

start

:提交新任务;

if (当前线程数 < 核心线程数?) then (是)
:创建新的核心线程;
:执行任务;
else (否)
if (工作队列未满?) then (是)
:将任务放入工作队列;
note right
队列类型:
- ArrayBlockingQueue (有界)
- LinkedBlockingQueue (无界)
- SynchronousQueue (同步)
- PriorityBlockingQueue (优先级)
end note
else (否)
if (当前线程数 < 最大线程数?) then (是)
:创建新的临时线程;
note right
临时线程在空闲超过
keepAliveTime后销毁
end note
:执行任务;
else (否)
:执行拒绝策略;
note right
拒绝策略:
- AbortPolicy (默认)
- CallerRunsPolicy
- DiscardPolicy
- DiscardOldestPolicy
end note
endif
endif
endif

fork
:核心线程池;
note right: 保持运行
fork again
:临时线程池;
note right: 空闲超时销毁
fork again
:等待队列;
note right: 任务缓存
end fork

stop

legend right
参数说明:
====
corePoolSize: 核心线程数
maximumPoolSize: 最大线程数
keepAliveTime: 空闲线程存活时间
workQueue: 工作队列
handler: 拒绝策略
endlegend

@enduml

Comment and share

Macos m2上使用OrbStack docker 安装mongo5.0+

背景

macOS(m2) 从 desktop-docker 切换到OrbStack,使用mongo6.0镜像利用如下docker-compose.yml启动的时候会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'
services:
mongo:
image: mongo:6.0
container_name: mongo
ports:
- "27017:27017"
volumes:
- ~/orbDocker/mongo/configdb:/data/configdb
- ~/orbDocker/mongo/db:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: root

docker-compose up

1
2
3
4
5
6
7
8
9
10
11
12
13
[+] Running 0/0
⠋ Network mongo_default Creating 0.1s
[+] Running 2/2d orphan containers ([mongo-mongo-express-1]) for this project. If you removed or renamed this service in your compose file, you can run this c ✔ Network mongo_default Created 0.1s
✔ Container mongo Created 0.1s
Attaching to mongo
mongo |
mongo | WARNING: MongoDB 5.0+ requires a CPU with AVX support, and your current system does not appear to have that!
mongo | see https://jira.mongodb.org/browse/SERVER-54407
mongo | see also https://www.mongodb.com/community/forums/t/mongodb-5-0-cpu-intel-g4650-compatibility/116610/2
mongo | see also https://github.com/docker-library/mongo/issues/485#issuecomment-891991814
mongo |
mongo | /usr/local/bin/docker-entrypoint.sh: line 416: 28 Illegal instruction "${mongodHackedArgs[@]}" --fork
mongo exited with code 132

修复给docker-compose.yml配置platform: linux/arm64

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3'
services:
mongo:
image: mongo:6.0
platform: linux/arm64
container_name: mongo
ports:
- "27017:27017"
volumes:
- ~/orbDocker/mongo/configdb:/data/configdb
- ~/orbDocker/mongo/db:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: root

Comment and share

Postgres技巧

Lock

查找空闲且可能被锁定的进程。

这个查询查看pg_stat_activity视图,查找那些活跃但是等待事件或等待事件类型不为空的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SELECT
pid,
datname,
usename,
application_name,
client_addr,
client_port,
to_char (now (), 'YYYY-MM-DD HH24:MI:SS') as now,
to_char (now () - xact_start, 'DD HH24:MI:SS MS') as xact_time,
to_char (now () - query_start, 'DD HH24:MI:SS MS') as query_time,
state,
to_char (now () - state_change, 'DD HH24:MI:SS MS') as state_time,
wait_event,
wait_event_type,
left (query, 40)
FROM
pg_stat_activity
WHERE
state != 'idle'
and pid != pg_backend_pid ()
ORDER BY
query_time desc;

找到具有等待事件的进程,向持有初始锁的PID汇总

This query looks at at the pg_stat_activity and pg_locks view showing the pid, state, wait_event, and lock mode, as well as blocking pids.
这个查询查看了 pg_stat_activity 和 pg_locks 视图,显示了 pid、state、wait_event 和 lock mode,以及阻塞的 pid。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
WITH sos AS (
SELECT array_cat(array_agg(pid),
array_agg((pg_blocking_pids(pid))[array_length(pg_blocking_pids(pid),1)])) pids
FROM pg_locks
WHERE NOT granted
)
SELECT a.pid, a.usename, a.datname, a.state,
a.wait_event_type || ': ' || a.wait_event AS wait_event,
current_timestamp-a.state_change time_in_state,
current_timestamp-a.xact_start time_in_xact,
l.relation::regclass relname,
l.locktype, l.mode, l.page, l.tuple,
pg_blocking_pids(l.pid) blocking_pids,
(pg_blocking_pids(l.pid))[array_length(pg_blocking_pids(l.pid),1)] last_session,
coalesce((pg_blocking_pids(l.pid))[1]||'.'||coalesce(case when locktype='transactionid' then 1 else array_length(pg_blocking_pids(l.pid),1)+1 end,0),a.pid||'.0') lock_depth,
a.query
FROM pg_stat_activity a
JOIN sos s on (a.pid = any(s.pids))
LEFT OUTER JOIN pg_locks l on (a.pid = l.pid and not l.granted)
ORDER BY lock_depth;

Set a lock timeout

It can be a good idea to set a lock_timeout within a session so that it will cancel the transaction and relinquish any locks it was holding after a certain period of time.
在会话中设置一个锁定超时可能是个好主意,这样在一段时间后它会取消事务并释放任何它持有的锁。

1
ALTER SYSTEM SET lock_timeout = '10s';

Logging

设置log_min_duration_statement来记录慢查询。

1
ALTER database postgres SET log_min_duration_statement = '250ms';

控制记录哪些类型的语句

1
ALTER DATABASE postgres SET log_statement = 'all';

等待锁时记录

1
ALTER DATABASE postgres SET log_lock_waits = 'on';

PERFORMANCE

使用语句超时来控制运行超时的查询。

Setting a statement timeout prevents queries from running longer than the specified time. You can set a statement timeout on the database, user, or session level. We recommend you set a global timeout on Postgres and then override that one specific users or sessions that need a longer allowed time to run.
设置语句超时时间可以防止查询运行时间超过指定的时间。您可以在数据库、用户或会话级别上设置语句超时时间。我们建议您在Postgres上设置一个全局超时时间,然后针对需要更长运行时间的特定用户或会话进行覆盖设置。

1
ALTER DATABASE mydatabase SET statement_timeout = '60s';

使用pg_stat_statements[需要安装]来查找使用最多资源的查询和进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 安装pg_stat_statements
-- Create the extension

CREATE EXTENSION pg_stat_statements;

-- Change in config

alter system set shared_preload_libraries='pg_stat_statements';
Restart

-- systemctl restart postgresql
-- Verify changes applied or not.

select * from pg_file_Settings where name='shared_preload_libraries';

SELECT
total_exec_time,
mean_exec_time as avg_ms,
calls,
query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

监视Postgres中的连接

This query will provide the number of connection based on type.
此查询将根据类型提供连接数量。

1
2
3
4
SELECT count(*),
state
FROM pg_stat_activity
GROUP BY state;

查询特定表的大小

1
2
3
4
5
SELECT pg_relation_size('table_name');

-- For prettier formatting you can wrap with:

SELECT pg_size_pretty(pg_relation_size('table_name'));

查询所有表的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT relname AS relation,
pg_size_pretty (
pg_total_relation_size (C .oid)
) AS total_size
FROM pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C .relnamespace)
WHERE nspname NOT IN (
'pg_catalog',
'information_schema'
)
AND C .relkind <> 'i'
AND nspname !~ '^pg_toast'
ORDER BY pg_total_relation_size (C .oid) DESC

检查未使用的索引。

Will return the unused indexes in descending order of size. Keep in mind you want to also check replicas before dropping indexes.

1
2
3
4
5
6
7
8
9
10
11
12
SELECT schemaname || '.' || relname AS table,
indexrelname AS index,
pg_size_pretty(pg_relation_size(i.indexrelid)) AS "index size",
idx_scan as "index scans"
FROM pg_stat_user_indexes ui
JOIN pg_index i ON ui.indexrelid = i.indexrelid
WHERE NOT indisunique
AND idx_scan < 50
AND pg_relation_size(relid) > 5 * 8192
ORDER BY
pg_relation_size(i.indexrelid) / nullif(idx_scan, 0) DESC NULLS FIRST,
pg_relation_size(i.indexrelid) DESC;

获取表格的近似计数

Will return the approximate count for a table based on PostgreSQL internal statistics. Useful for large tables where performing a SELECT count(*) is costly on performance.

1
2
3
SELECT reltuples::numeric as count
FROM pg_class
WHERE relname='table_name';

Non-blocking 创建索引

Adding CONCURRENTLY during index creation, while not permitted in a transaction, will not hold a lock on the table while creating your index.

1
CREATE INDEX CONCURRENTLY foobar ON foo (bar);

PSQL

在PSQL中自动记录查询时间

将自动打印出在psql中运行查询所花费的时间。需要注意的是这是往返时间,而不仅仅是查询执行时间。

1
\timing

在 psql 中自动格式化查询结果

将根据您的终端窗口自动重新组织查询输出,以便更易读。

1
\x auto

编辑您选择的编辑器中的psql查询

将自动在您的默认 $EDITOR 中打开您上次运行的查询。当您保存并关闭时,将执行该查询。

1
\e

为nulls设置一个值

将null渲染为您指定的任何字符。对于更容易解析null和空文本非常方便。

1
\pset null 👻

将您的每个数据库的查询历史保存在本地

将为每个DBNAME自动保存一个历史文件。

1
\set HISTFILE ~/.psql_history- :DBNAME

显示由内部psql命令发出的查询

在命令行中为 psql 添加“-E”(或–echo-hidden)选项。此选项将显示内部 psql 命令生成的查询(例如“\dt mytable”)。这是了解系统目录的更酷的方法,或者可以重用由 psql 发出的查询在您自己的工具中。

1
psql -E

获取数据,只需数据。

在命令行中向psql添加”-qtA”选项。这些选项将使psql以安静模式(“-q”)运行,仅返回元组(“-t”)以不对齐的方式(“-A”)。结合”-c”选项发送单个查询,如果您只想从Postgres获取数据,这对于您的脚本可能很有用。每行返回一行。

1
psql -qtA

以HTML表格的形式获取结果

在命令行中为psql添加”-qtH”选项。这些选项将使psql以安静模式运行(”-q”),仅返回元组(”-t”)以HTML表格形式(”-H”)。结合”-c”选项发送单个查询,可以快速将查询结果嵌入到HTML页面中。

1
psql -qtH

搜索以前的查询使用 Ctrl + R

按下Ctrl + R将启动搜索会话,然后您可以开始键入查询或命令的一部分,以找到并再次运行它。如果您使用注释标记特定查询,这可以帮助稍后进行搜索。

1
(reverse-i-search)

清除plsql屏幕

将清除当前 psql 会话中的屏幕

1
\! clear

持续使用watch命令运行查询

每2秒将自动运行上次的查询并显示输出。您也可以在watch后指定要运行的查询。

1
\watch

在交互模式下,当出现错误时回滚到上一个语句。

当您在交互模式遇到错误时,系统会自动回滚到前一条命令之前的状态,使您可以像预期的那样继续工作。

1
\set ON_ERROR_ROLLBACK interactive

直接从psql导出CSV

当使用查询时提供--csv值,该命令将运行特定查询并将CSV返回到标准输出。

1
psql <connection-string> --csv -c 'select * from test;'

在psql中运行来自文件的查询

在 psql 中执行指定的文件。

1
\i filename

在 psql 中提供清晰的边框

在psql中,当你查询结果输出时,会给结果加上边框。

1
\pset border 2

Refer

https://www.crunchydata.com/postgres-tips

Comment and share

作为一名Javaer领到电脑的第一件事就是需要配置Java环境,以及切换不同的JDK版本来进行日常开发,Node开发有nvm,python开发有pyenv, Java也提供了响应的工具sdkman(Linux, Unix, MacOS)。同时sdkman也可以管理Scala、Gradle、Maven等等软件不同的版本。

安装 SDKMan 要开始使用 SDKMan,

首先需要在你的系统上安装它。SDKMan 支持 Windows、macOS 和 Linux 等操作系统。

在 macOS 和 Linux 上安装

在终端中执行以下命令来安装 SDKMan:

1
2
3
curl -s "https://get.sdkman.io" | bash 
# 或者
curl -s "https://get.sdkman.io" | ZSH

执行

1
2
3
4
source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk version
# 如下,如果显示版本则代表成功
sdkman 5.18.2

在 Windows 上安装

在 Windows 上,你可以使用 Git Bash 或 Cygwin 等工具来安装和使用 SDKMan。首先,下载并安装 Git Bash 或 Cygwin,并确保它们在系统的 PATH 环境变量中。然后,在 Git Bash 或 Cygwin 终端中执行以下命令来安装 SDKMan:

1
curl -s "https://get.sdkman.io" | bash

使用 SDKMan

一旦你安装好了 SDKMan,就可以开始使用它来管理你的开发工具了。

安装工具

使用 SDKMan 安装一个开发工具非常简单。比如,要安装最新版本的 Java,只需要在终端中执行以下命令:

1
2
sdk lis java #显示出java所有版本
sdk install java 17.0.3-oracle

切换版本

1
sdk use java 17.0.3-oracle

升级工具

SDKMan 也提供了升级已安装工具的功能。要升级某个已安装的工具,只需要执行以下命令:

1
sdk upgrade <tool>

其中 <tool> 是你想要升级的工具名称,比如 javascala等。

列出可用版本

如果你想查看某个工具的所有可用版本,可以使用以下命令:

1
sdk list <tool>

这将列出该工具所有可用的版本号供你选择。

删除工具

如果你想删除某个已安装的工具,可以使用以下命令:

1
sdk uninstall <tool> <version>

其中 <tool> 是工具的名称,<version> 是要删除的版本号。

结语

SDKMan 是一个非常实用的开发工具管理工具,可以帮助开发人员轻松安装、升级和切换不同版本的开发工具。它简化了我们在不同项目中使用不同工具版本的过程,让开发变得更加便捷和高效。

如果你还没有尝试过 SDKMan,我强烈推荐你去官方网站(https://sdkman.io/)了解更多信息,并在你的开发环境中安装和使用它。相信它会成为你的好帮手!

ChatGpt真强大这篇文章是它写的。

Comment and share

番茄架构是遵循Common Sense Manifesto的软件架构的一种方法

Common Sense Manifesto

  • 选择适合你的软件的架构而不是流行的的架构: 在盲目追随流行人物的建议之前,要考虑软件最佳实践。
  • 不要过度设计: 努力保持简单,而不是过度工程化,试图猜测未来十年的需求。
  • 进行研究与开发,选择一项技术并拥抱它,而不是为了可替代性而创建抽象层。
  • 确保你的解决方案作为整体工作正常,而不仅仅是单个组件。

架构图

tomato-architecture.png

实施指南(架构核心:关注点分离):

  1. 按功能进行打包

    将代码按照功能分成不同的包是一种常见的模式,通常会根据技术层次(如 controllers, services, repositories等)进行划分。如果你正在构建一个专注于特定模块或业务能力的微服务,那么这种方法可能是可行的。

如果你正在构建一个单体应用或者模块化单体应用,强烈建议首先按照功能而非技术层进行划分。

详细信息请阅读链接: https://phauer.com/2020/package-by-feature/

  1. “应用核心”独立于交付机制(Web, Scheduler Jobs, CLI)

    应用核心应该公开可以从主方法调用的API。为了实现这一点,“应用核心”不应该依赖于其调用上下文。这意味着“应用核心”不应依赖于任何HTTP/Web层的库。同样,如果你的应用核心被用于定时任务或命令行接口,任何调度逻辑或命令行执行逻辑都不应泄露到应用核心中。

  2. 将业务逻辑执行与输入源(Web Controllers, Message Listeners, Scheduled Jobs等)分离

    输入源,如Web Controllers, Message Listeners, Scheduled Jobs等,应该是很薄的一层,在提取请求数据后将实际的业务逻辑执行委托给“应用核心”。

比如:

坏味道:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
class CustomerController {
private final CustomerService customerService;

@PostMapping("/api/customers")
void createCustomer(@RequestBody Customer customer) {
if(customerService.existsByEmail(customer.getEmail())) {
throw new EmailAlreadyInUseException(customer.getEmail());
}
customer.setCreateAt(Instant.now());
customerService.save(customer);
}
}

纠正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
class CustomerController {
private final CustomerService customerService;

@PostMapping("/api/customers")
void createCustomer(@RequestBody Customer customer) {
customerService.save(customer);
}
}

@Service
@Transactional
class CustomerService {
private final CustomerRepository customerRepository;

void save(Customer customer) {
if(customerRepository.existsByEmail(customer.getEmail())) {
throw new EmailAlreadyInUseException(customer.getEmail());
}
customer.setCreateAt(Instant.now());
customerRepository.save(customer);
}
}

采用这种方法,无论你是通过REST API调用还是通过CLI创建一个客户,所有的业务逻辑都会集中在应用核心中。

坏味道:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
class OrderProcessingJob {
private final OrderService orderService;

@Scheduled(cron="0 * * * * *")
void run() {
List<Order> orders = orderService.findPendingOrders();
for(Order order : orders) {
this.processOrder(order);
}
}

private void processOrder(Order order) {
...
...
}
}

纠正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
class OrderProcessingJob {
private final OrderService orderService;

@Scheduled(cron="0 * * * * *")
void run() {
List<Order> orders = orderService.findPendingOrders();
orderService.processOrders(orders);
}
}

@Service
@Transactional
class OrderService {

public void processOrders(List<Order> orders) {
...
...
}
}

采用这种方法,你可以将订单处理逻辑与调度程序解耦,可以在没有通过调度程序触发的情况下独立进行测试。

  1. 不要让“外部服务集成”对“应用核心”产生太大影响

    从应用核心,我们可能需要与数据库、消息代理或第三方Web服务等进行通信。必须注意的是,业务逻辑执行器不应过度依赖于外部服务集成。

    例如,假设你正在使用Spring Data JPA进行持久化,而你想从CustomerService中使用分页获取客户。

    坏味道:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Service
    @Transactional
    class CustomerService {
    private final CustomerRepository customerRepository;

    PagedResult<Customer> getCustomers(Integer pageNo) {
    Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.of("name"));
    Page<Customer> cusomersPage = customerRepository.findAll(pageable);
    return convertToPagedResult(cusomersPage);
    }
    }

    纠正:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Service
    @Transactional
    class CustomerService {
    private final CustomerRepository customerRepository;

    PagedResult<Customer> getCustomers(Integer pageNo) {
    return customerRepository.findAll(pageNo);
    }
    }

    @Repository
    class JpaCustomerRepository {

    PagedResult<Customer> findAll(Integer pageNo) {
    Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.of("name"));
    return ...;
    }
    }

    这种方式下,任何持久化库的修改都只会影响到持久化层。

  2. 将领域(domain)逻辑处理放在领域对象中

    如果一个方法仅仅影响领域对象中的状态或者是根据领域对象状态计算某些内容的方法,则该方法应该是属于领域对象。

    坏味道:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Cart {
    List<LineItem> items;
    }

    @Service
    @Transactional
    class CartService {

    CartDTO getCart(UUID cartId) {
    Cart cart = cartRepository.getCart(cartId);
    BigDecimal cartTotal = this.calculateCartTotal(cart);
    ...
    }

    private BigDecimal calculateCartTotal(Cart cart) {
    ...
    }
    }

    纠正:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Cart {
    List<LineItem> items;

    public BigDecimal getTotal() {
    ...
    }
    }

    @Service
    @Transactional
    class CartService {

    CartDTO getCart(UUID cartId) {
    Cart cart = cartRepository.getCart(cartId);
    BigDecimal cartTotal = cart.getTotal();
    ...
    }
    }
  3. 不要创建没有必要的接口

    不要创建接口并希望有一天我们可以为此接口添加另一个实现。如果那一天真的到来,那么利用我们现在拥有的强大的 IDE,只需敲击几下键盘即可提取界面。如果创建接口的原因是为了使用 Mock 实现进行测试,我们有像 Mockito 这样的模拟库,它能够在不实现接口的情况下模拟类。

    因此,除非有充分的理由,否则不要创建接口。

  4. 拥抱框架强大的功能和灵活性

    通常,创建库和框架是为了满足大多数应用程序所需的常见要求。因此,您应该选择一个库/框架来更快地构建应用程序时。

    与其利用所选框架提供的功能和灵活性,不如在所选框架之上创建间接或抽象,并希望有一天您可以将框架切换到其他框架,这通常是一个非常糟糕的主意。

    例如,Spring框架为处理数据库事务、缓存、方法级安全性等提供了声明性支持。引入我们自己的类似注释并通过将实际处理委托给框架来重新实现相同的功能支持是不必要的。

    相反,最好直接使用框架的注释,或者根据需要使用附加语义来组合注释。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Transactional
    public @interface UseCase {
    @AliasFor(
    annotation = Transactional.class
    )
    Propagation propagation() default Propagation.REQUIRED;
    }
  5. 不仅单元测试,也需要集成测试。

    我们绝对应该编写单元测试来测试单元(业务逻辑),如果需要,可以通过模拟外部依赖项。但更重要的是验证整个功能是否正常工作。

    即使我们的单元测试以毫秒为单位运行,我们是否可以放心地投入生产?当然不是。我们应该通过测试实际的外部依赖项(例如数据库或消息代理)来验证整个功能是否正常工作。这让我们更有信心。

    我想知道“我们应该拥有完全独立于外部依赖项的核心域”哲学的整个想法来自于使用真实依赖项进行测试非常具有挑战性或根本不可能的时代。

    幸运的是,我们现在拥有更好的技术(例如:testcontainer 运行测试时动态启动依赖的中间件容器技术)来测试真正的依赖关系。使用真正的依赖项进行测试可能会花费稍多的时间,但与好处相比,这是一个可以忽略不计的成本。

学习于原文:https://github.com/wenPKtalk/tomato-architecture

Comment and share

Author's picture

Topsion

Fullstack Developer


Coder


Xi'an China