## 以管理员身份运行bash
mysqld --remove mysql
mysqld --install mysql5 --defaults-file=D:\\service-experiment\\mysql-5.6.33-win32\\my.ini
启动失败,原因:ini配置文件中添加了slowlog 的配置,但是没有创建对应的日志路径,导致启动报错
[ERROR] Could not open D:\service-experiment\mysql-5.6.33-win32\data\log\mysql_slow_query.log for logging (error 2). Turning logging off for the whole duration of the MySQL server process. To turn it on again: fix the cause, shutdown the MySQL server and restart it.
启动失败,原因:彻底删除了data文件夹,导致mysql内置的数据库找不到,无法启动
[ERROR] Fatal error: Can’t open and lock privilege tables: Table ‘mysql.user’ doesn’t exist
为什么404的错误页显示 Whitelabel-page ? 根据浏览器的网络请求分析,肯定是服务内部跳转处理的。 根据返回Whitelabel 错误页的信息,肯定是spring-boot 处理的错误页 如何跳转的?spring-boot 如何替web容器托管了错误页处理功能?
SpringBoot默认提供/Error映射,它以合理的方式处理所有错误,并在servlet容器中注册为“全局”错误页。对于机器客户端,它将生成一个JSON响应,其中包含错误、HTTP状态和异常消息的详细信息。对于浏览器客户端,有一个“Whitelabel”错误视图,它以HTML格式呈现相同的数据(要自定义它,只需添加一个解析为“Error”的视图)。要完全替换默认行为,您可以实现ErrorController并注册该类型的bean定义,或者简单地添加一个类型为ErrorAttributes的bean来使用现有机制,但替换内容。
对应:Developing Web Applications ➡Spring MVC Framework➡Error Handing
ErrorPageFilter 滤请求并转发请求到 error
BasicErrorController 默认error请求处理
ErrorMvcAutoConfiguration 默认错误注册配置、错误页跳转配置,例如:注册默认ErrorPage,注册 BasicErrorController
ResourceHttpRequestHandler 默认的404错误,就是统一由该Handler 产生
WhitelabelErrorViewConfiguration 我们看到的 spring-boot 默认错误页
ErrorPageFilter 实现web错误页跳转是通过来,非嵌入式应用程序(即已部署的WAR文件)提供。作用是过滤请求并转发到错误页,而不是让容器(例如:Tomcat)处理它们。
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 包装response,支持错误分支判断,包装设计模式
ErrorWrapperResponse wrapped = new ErrorWrapperResponse(response);
try {
// 正常业务执行
chain.doFilter(request, wrapped);
// 如果请求response error(例如:404, 500),该Filter 进行判断转发到错误页
if (wrapped.hasErrorToSend()) {
// 服务内部定向跳转,这样,就请求到 BasicErrorController
// request.getRequestDispatcher(errorPath).forward(request, response);
handleErrorStatus(request, response, wrapped.getStatus(), wrapped.getMessage());
response.flushBuffer();
}
// ...
} catch (Throwable ex) {
// ...
}
}
ResourceHttpRequestHandler 优先判断404问题
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// For very general mappings (e.g. "/") we need to check 404 first
Resource resource = getResource(request);
if (resource == null) {
logger.trace("No matching resource found - returning 404");
// response.hasErrorToSend = true;
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// ...
}
在默认路径下,创建自定义的错误页文件即可
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftl
+- <other templates>
在使用 tk.mybatis组件的过程中,调用 insertList(),报错如题。
表面意思是:没有找到 SpecialProvider 默认构造方法,SpecialProvider 实例化失败。
实际上是:insertList 的 ProviderSqlSource
没有替换 的 DynamicSqlSource
,mybatis 直接使用ProviderSqlSource 中的 SpecialProvider 进行解析使用,导致的异常。
tk.mybatis 组件提供的 tk.mybatis.mapper.common.Mapper
是用于通用的DAO 方法的增强,不用重复的写增删改查的代码,后续简称 Mapper。
tk.mybatis 利用mybatis ProviderSqlSource 的特性,通过将模板进行渲染,生成对应mybatis dynamic xml,实现基础增删改查方法的增强。
如上述的问题,MappedStatement 的 ProviderSqlSource 本来应该是在初始化过 程中渲染和替换为 DynamicSqlSource, 由于某种原因,没有执行。最终导致调用insertList(), 直接使用ProviderSqlSource 的SpecialProvider 解析sql,报错。
通过源代码分析,MapperAutoConfiguration
提供自定义的机制,可以手动配置特定的接口,主动进行渲染和替换。
如下配置,声明特殊的接口,让tk.mybatis 完成ProviderSqlSource 到 DynamicSqlSource 的替换。完成增强方法的初始化。
mapper:
mappers: [tk.mybatis.mapper.common.special.InsertListMapper, tk.mybatis.mapper.common.Mapper]
涉及到版本
mybatis.version:3.4.4 mapper.version:3.4.2 mybatis-spring.version:1.3.1 mybatis-spring-boot.version:1.3.0 spring-boot.version:1.5.4.RELEASE
Lettuce is a fully non-blocking Redis client built with netty providing Reactive, Asynchronous and Synchronous Data Access .
RedisURI [host=’localhost’, port=6379] 连接的封装,用于解析database password 等基础参数,还有集群,Sentinel 配置解析。
DefaultEndpoint 通信 channel 的封装。除了提供 channel.write,还具备重连和重试的能力。
RedisClient 负责构造和初始化Netty,thread-safe,reuse this instance as much as possible。
ClientResources 管理(初始化和持有) EventExecutorGroup, Timer, EventBus 等资源。不需要重复创建。
/**
* lettuce 与 redis-server 通信提供编解码(RESP 协议),监控的实现
* @see io.lettuce.core.ConnectionBuilder#buildHandlers()
*/
protected List<ChannelHandler> buildHandlers() {
List<ChannelHandler> handlers = new ArrayList<>();
handlers.add(new ChannelGroupListener(channelGroup));
// Encodes RedisMessage into bytes following
handlers.add(new CommandEncoder());
// writing redis commands and reading responses from the server. core!!!
handlers.add(new CommandHandler());
// 通过 eventBus 把连接状态相关的事件广播出去。
handlers.add(new ConnectionEventTrigger(connectionEvents, connection, clientResources.eventBus()));
// monitoring the channel and reconnecting when the connection is lost.
if (clientOptions.isAutoReconnect()) {
handlers.add(new ConnectionWatchdog());
}
return handlers;
}
Redis clients use a protocol called RESP (REdis Serialization Protocol) to communicate with the Redis server.
RESP can serialize different data types like integers, strings, and arrays.
RESP uses prefixed-length to transfer bulk data.
In RESP, different parts of the protocol are always terminated with “\r\n” (CRLF).
官方文档 详细介绍协议的定义和每种数据类型的格式。
协议规则非常简单,并且容易解析和阅读。
Simple Strings 用于系统内的回复等,可以理解为常量。
Bulk Strings 用于命令参数,使用binary-safe string,确保输入数据不会因为字符集,操作系统等问题,造成数据读写问题。
Simple String 表示“OK”为 "+OK\r\n"
Bulk Strings 表示“hello”为 "$5\r\nhello\r\n"
= 数据类型字符 + 长度 + 间隔符 + 数据 + 间隔符。
Jedis is a Java client for Redis designed for performance and ease of use.
根据协议的规范,可以用任何语言编解码通信数据。如下就是Jedis 对协议规则的实现。
/**
* set("name", "foo") Example
*
* 支持的数据类型: Simple Strings, Errors, Integers, Bulk Strings and Arrays.
* 数据类型的表示:Simple Strings(+), Errors, Integers(:), Bulk Strings($) and Arrays(*)
* 不同的块之间一定要用CRLF间隔(\r\n)
* @see redis.clients.jedis.Protocol#sendCommand
*/
private static void sendCommand(final RedisOutputStream os, final byte[] command, final byte[]... args) {
try {
os.write(ASTERISK_BYTE);// 数组类型,"*" 代表Arrays
os.writeIntCrLf(args.length + 1);// 数组长度:参数(n) + 命令(1), writeInt,writeCrLf
os.write(DOLLAR_BYTE);// String,"$" 代表Bulk Strings
os.writeIntCrLf(command.length);// 长度
os.write(command);// 操作命令 SET/[83, 69, 84]
os.writeCrLf();// 间隔符号
// 操作参数, args[0]="name".bytes(), args[1]="foo".bytes()
for (final byte[] arg : args) {
os.write(DOLLAR_BYTE);
os.writeIntCrLf(arg.length);
os.write(arg);
os.writeCrLf();
}
} catch (IOException e) {
throw new JedisConnectionException(e);
}
}
A client connects to a Redis server by creating a TCP connection to the port 6379.
Jedis 是直接使用socket 来完成通信。
public void connect() {
// 每次使用前,检查 socket 是否可用
if (!isConnected()) {
try {
// socket 编程Example
socket = new Socket();
socket.connect(new InetSocketAddress(host, port), connectionTimeout);
socket.setSoTimeout(soTimeout);
if (ssl) {
// 启用ssl 模式
if (null == sslSocketFactory) {
sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
}
socket = (SSLSocket) sslSocketFactory.createSocket(socket, host, port, true);
}
// RedisOutputStream 提供buffer 和方便的协议写入方法
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
} catch (IOException ex) {
broken = true;
throw new JedisConnectionException(ex);
}
}
}