Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

Mapper 顾名思义是作一个映射作用,在 Tomcat 中会根据域名找到 host 组件,再根据 uri 可以找到对应的 context 和 wrapper 组件,但是对于当前这个环境 (Springboot) 会有一点小区别
之前说到
请求会经过 coyote 适配器进行进一步处理,
org.apache.coyote.http11.Http11Processor#service

// Process the request in the adapter
if (getErrorState().isIoAllowed()) {
    try {
        rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
        getAdapter().service(request, response);

然后到 coyoteAdapter 的 service
org.apache.catalina.connector.CoyoteAdapter#service

try {
    // Parse and set Catalina and configuration specific
    // request parameters
    postParseSuccess = postParseRequest(req, request, res, response);
    if (postParseSuccess) {
        //check valves if we support async
        request.setAsyncSupported(
                connector.getService().getContainer().getPipeline().isAsyncSupported());
        // Calling the container
        // ----------> 到这就是调用 pipeline 去处理了,我们要关注上面的 postParseRequest
        connector.getService().getContainer().getPipeline().getFirst().invoke(
                request, response);
    }

主要先看到 postParseRequest
在 postParseRequest 的代码里会调用 Mapper 的 map 方法

while (mapRequired) {
    // This will map the the latest version by default
    connector.getService().getMapper().map(serverName, decodedURI,
            version, request.getMappingData());

    // If there is no context at this point, either this is a 404
    // because no ROOT context has been deployed or the URI was invalid
    // so no context could be mapped.
    if (request.getContext() == null) {
        // Allow processing to continue.

而后面的 context 就是在 map 方法里处理塞进去的
往里看就是 org.apache.catalina.mapper.Mapper#internalMap
第一步找的是 host

// Virtual host mapping
        MappedHost[] hosts = this.hosts;
        MappedHost mappedHost = exactFindIgnoreCase(hosts, host);

而在后续代码里继续设置 context

if (contextVersion == null) {
            // Return the latest version
            // The versions array is known to contain at least one element
            contextVersion = contextVersions[versionCount - 1];
        }
        mappingData.context = contextVersion.object;

然后是 wrapper

// Wrapper mapping
if (!contextVersion.isPaused()) {
    internalMapWrapper(contextVersion, uri, mappingData);
}

在这个方法 org.apache.catalina.mapper.Mapper#internalMapWrapper

// Rule 7 -- Default servlet
if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
    if (contextVersion.defaultWrapper != null) {
        mappingData.wrapper = contextVersion.defaultWrapper.object;
        mappingData.requestPath.setChars
            (path.getBuffer(), path.getStart(), path.getLength());
        mappingData.wrapperPath.setChars
            (path.getBuffer(), path.getStart(), path.getLength());
        mappingData.matchType = MappingMatch.DEFAULT;

这里就设置了 wrapper ,因为我们是在 Springboot 中,所以只有默认的 dispatchServlet
上面主要是在请求处理过程中的查找映射过程,一开始的注册是从 MapperListener 开始的
MapperListener 继承了 LifecycleMbeanBase,也就是有了 Lifecycle 状态变化那一套

@Override
public void startInternal() throws LifecycleException {

    setState(LifecycleState.STARTING);

    Engine engine = service.getContainer();
    if (engine == null) {
        return;
    }

    findDefaultHost();

    addListeners(engine);

    Container[] conHosts = engine.findChildren();
    for (Container conHost : conHosts) {
        Host host = (Host) conHost;
        if (!LifecycleState.NEW.equals(host.getState())) {
            // Registering the host will register the context and wrappers
            registerHost(host);
        }
    }
}

在启动过程中就会去把 engine 的子容器 host 找出来进行注册,就是调用 registerHost 方法

private void registerHost(Host host) {

    String[] aliases = host.findAliases();
    mapper.addHost(host.getName(), aliases, host);

    for (Container container : host.findChildren()) {
        if (container.getState().isAvailable()) {
            registerContext((Context) container);
        }
    }

    // Default host may have changed
    findDefaultHost();

    if(log.isDebugEnabled()) {
        log.debug(sm.getString("mapperListener.registerHost",
                host.getName(), domain, service));
    }
}

在这里面会添加 host 组件,注册 context 等,注册context 里还会处理 wrapper 的添加记录

private void registerContext(Context context) {

    String contextPath = context.getPath();
    if ("/".equals(contextPath)) {
        contextPath = "";
    }
    Host host = (Host)context.getParent();

    WebResourceRoot resources = context.getResources();
    String[] welcomeFiles = context.findWelcomeFiles();
    List<WrapperMappingInfo> wrappers = new ArrayList<>();

    for (Container container : context.findChildren()) {
        prepareWrapperMappingInfo(context, (Wrapper) container, wrappers);

        if(log.isDebugEnabled()) {
            log.debug(sm.getString("mapperListener.registerWrapper",
                    container.getName(), contextPath, service));
        }
    }

    mapper.addContextVersion(host.getName(), host, contextPath,
            context.getWebappVersion(), context, welcomeFiles, resources,
            wrappers);

    if(log.isDebugEnabled()) {
        log.debug(sm.getString("mapperListener.registerContext",
                contextPath, service));
    }
}

就是在 org.apache.catalina.mapper.Mapper#addContextVersion 方法中

public void addContextVersion(String hostName, Host host, String path,
        String version, Context context, String[] welcomeResources,
        WebResourceRoot resources, Collection<WrapperMappingInfo> wrappers) {

    hostName = renameWildcardHost(hostName);

    MappedHost mappedHost  = exactFind(hosts, hostName);
    if (mappedHost == null) {
        addHost(hostName, new String[0], host);
        mappedHost = exactFind(hosts, hostName);
        if (mappedHost == null) {
            log.error(sm.getString("mapper.addContext.noHost", hostName));
            return;
        }
    }
    if (mappedHost.isAlias()) {
        log.error(sm.getString("mapper.addContext.hostIsAlias", hostName));
        return;
    }
    int slashCount = slashCount(path);
    synchronized (mappedHost) {
        ContextVersion newContextVersion = new ContextVersion(version,
                path, slashCount, context, resources, welcomeResources);
        if (wrappers != null) {
            addWrappers(newContextVersion, wrappers);
        }

大致介绍了下 Mapper 的逻辑

前面介绍过 Tomcat 的层次结构,

<Server>
    <Service>
        <Connector />
        <Connector />
        <Engine>
            <Host>
                <Context />
            </Host>
        </Engine>
    </Service>
</Server>

参考这个 xml,而对于这些组件中,有一类有相同的基类,也就是这次要介绍的 ContainerBase,

包括 engine,host,context 还有 wrapper ,都是同样的容器组件,
而他们共同实现的接口就是 Container ,
主要包含了

public static final String ADD_CHILD_EVENT = "addChild";


/**
 * The ContainerEvent event type sent when a valve is added
 * by <code>addValve()</code>, if this Container supports pipelines.
 */
public static final String ADD_VALVE_EVENT = "addValve";


/**
 * The ContainerEvent event type sent when a child container is removed
 * by <code>removeChild()</code>.
 */
public static final String REMOVE_CHILD_EVENT = "removeChild";


/**
 * The ContainerEvent event type sent when a valve is removed
 * by <code>removeValve()</code>, if this Container supports pipelines.
 */
public static final String REMOVE_VALVE_EVENT = "removeValve";

这几个事件类型,

public Pipeline getPipeline();

public Container getParent();

public void addChild(Container child);

public Container[] findChildren();

public int getStartStopThreads();

public void setStartStopThreads(int startStopThreads);

包括 pipeline 和获取 parent,添加 child 跟查找 child,然后设置线程池的线程数等
而对于 ContainerBase
也包含了 Lifecycle 的常规方法

@Override
protected void initInternal() throws LifecycleException {
    reconfigureStartStopExecutor(getStartStopThreads());
    super.initInternal();
}


private void reconfigureStartStopExecutor(int threads) {
    if (threads == 1) {
        // Use a fake executor
        if (!(startStopExecutor instanceof InlineExecutorService)) {
            startStopExecutor = new InlineExecutorService();
        }
    } else {
        // Delegate utility execution to the Service
        Server server = Container.getService(this).getServer();
        server.setUtilityThreads(threads);
        startStopExecutor = server.getUtilityExecutor();
    }
}

初始化方法主要就是之前讲过的线程池
后面就开始 startInternal

@Override
    protected synchronized void startInternal() throws LifecycleException {

        // Start our subordinate components, if any
        logger = null;
        getLogger();
        Cluster cluster = getClusterInternal();
        if (cluster instanceof Lifecycle) {
            ((Lifecycle) cluster).start();
        }
        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).start();
        }

        // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            results.add(startStopExecutor.submit(new StartChild(child)));
        }

        MultiThrowable multiThrowable = null;

        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                if (multiThrowable == null) {
                    multiThrowable = new MultiThrowable();
                }
                multiThrowable.add(e);
            }

        }
        if (multiThrowable != null) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                    multiThrowable.getThrowable());
        }

        // Start the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle) {
            ((Lifecycle) pipeline).start();
        }

        setState(LifecycleState.STARTING);

        // Start our thread
        if (backgroundProcessorDelay > 0) {
            monitorFuture = Container.getService(ContainerBase.this).getServer()
                    .getUtilityExecutor().scheduleWithFixedDelay(
                            new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
        }
    }

这里包括了 cluster 的启动和 realm 的启动,然后将 child 子组件添加到线程池里启动,然后循环 get 结果,接下去就是 pipeline 的启动,设置开始中状态,后面是 ContainerBackgroundProcessor 的启动
然后是停止方法

@Override
    protected synchronized void stopInternal() throws LifecycleException {

        // Stop our thread
        if (monitorFuture != null) {
            monitorFuture.cancel(true);
            monitorFuture = null;
        }
        threadStop();

        setState(LifecycleState.STOPPING);

        // Stop the Valves in our pipeline (including the basic), if any
        if (pipeline instanceof Lifecycle &&
                ((Lifecycle) pipeline).getState().isAvailable()) {
            ((Lifecycle) pipeline).stop();
        }

        // Stop our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (Container child : children) {
            results.add(startStopExecutor.submit(new StopChild(child)));
        }

        boolean fail = false;
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Exception e) {
                log.error(sm.getString("containerBase.threadedStopFailed"), e);
                fail = true;
            }
        }
        if (fail) {
            throw new LifecycleException(
                    sm.getString("containerBase.threadedStopFailed"));
        }

        // Stop our subordinate components, if any
        Realm realm = getRealmInternal();
        if (realm instanceof Lifecycle) {
            ((Lifecycle) realm).stop();
        }
        Cluster cluster = getClusterInternal();
        if (cluster instanceof Lifecycle) {
            ((Lifecycle) cluster).stop();
        }
    }

也是类似的,只不过停止顺序和刚才开始的顺序反了一下

因为服务器续费即使是这次双十一还是太贵了,之前在 tx 买的三年 2c4g 3m 的一台服务器,原来价格是三年 800+,现在续费三年要 3700,因为之前还买了其他的服务器,所以感觉再这么贵地续一个有点不划算就考虑迁移了服务器
主要是分为四块内容,先大致记录下,后续会慢慢展开讲
第一部分就是 headscale 的迁移,本身 headscale 的部署其实还好,原来的服务器是因为权限搞得几个文件都有问题,这次就按着第一次已经修改好的文件进行部署就好了,问题不大,可以参考 Headscale初体验以及踩坑记 ,主要的一点是要考虑 derper 的部署,以及客户端认证逻辑
第二部分是 traefik 以及背后的 gitea,WordPress 的迁移,这个的部署方式都是使用 docker-compose 的,已经算比较方便了的,但是要注意两点,第一个是对于这些有状态的需要先停止 docker-compose ,然后在进行数据迁移,第二步数据迁移可能就需要使用到 docker 命令,执行 mysqldump

docker exec -it  mysql_server【docker容器名称/ID】 mysqldump -uroot -p123456【数据库密码】 test_db【数据库名称】 > /opt/sql_bak/test_db.sql【导出表格路径】

然后再在新的 mysql 容器中先 cp 到容器内,然后在 docker 内用 mysql 命令连接 mysql-server 以后用 source 将 sql 导进来
而像 traefik 本身其实是无状态的,只需要配置文件没有什么机器依赖就好,还有就是证书的更新,最后就是域名解析问题,修改解析就好
第三部分是 rustdesk 的迁移,这个其实就按官方文档来跑两个容器就好,hbbr 跟 hbbs 就好了,一个是 relay,一个是 id 服务器,不过这里有个问题我目前没看到怎么识别是否已经用到了这个,有种部署了但是不知道有没有用的尴尬,有知道的可以指导下
第四部分是 qinglong 的部署,这个也挺简单了,就是有个小问题,如果部署的订阅链接有 github 的就可以去站长的 ping 连接速度看看,自己配个 hosts 就会比较方便,不然经常容易拉不下来,还有就是要注意安全,至少要改强密码

线程池在 Tomcat 中也是非常重要的工具,这里我们简单介绍下 Tomcat 中的线程池,在 container 的启动过程中
org.apache.catalina.core.ContainerBase#initInternal

@Override
    protected void initInternal() throws LifecycleException {
        reconfigureStartStopExecutor(getStartStopThreads());
        super.initInternal();
    }

这里先会获取线程数,

@Override
public int getStartStopThreads() {
    return startStopThreads;
}

默认的 ContainerBase 的设置线程数是 1

private int startStopThreads = 1;

那就会按照 org.apache.catalina.core.ContainerBase#reconfigureStartStopExecutor 来设置线程池类型

private void reconfigureStartStopExecutor(int threads) {
    if (threads == 1) {
        // Use a fake executor
        if (!(startStopExecutor instanceof InlineExecutorService)) {
            startStopExecutor = new InlineExecutorService();
        }
    } else {
        // Delegate utility execution to the Service
        Server server = Container.getService(this).getServer();
        server.setUtilityThreads(threads);
        startStopExecutor = server.getUtilityExecutor();
    }
}

此时线程池类型是 null 也就会走到上一个分支中,new 一个 InlineExecutorService 出来
前面的这些其实是在 StandardEngine 初始化过程中,也就是 initInternal 时候进行的
然后具体的使用是在 startInternal 开始方法中,

// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
    results.add(startStopExecutor.submit(new StartChild(child)));
}

会获取当前 StandardEngine 的子组件提交给线程池进行启动
而这个 StartChild 也比较简单

private static class StartChild implements Callable<Void> {

    private Container child;

    public StartChild(Container child) {
        this.child = child;
    }

    @Override
    public Void call() throws LifecycleException {
        child.start();
        return null;
    }
}

就是调用子组件的 start 方法,
在 org.apache.tomcat.util.threads.InlineExecutorService 的父类,java.util.concurrent.AbstractExecutorService 中先会把 Callable 包装成 FutureTask

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

然后进行执行,

@Override
public void execute(Runnable command) {
    synchronized (lock) {
        if (shutdown) {
            throw new RejectedExecutionException();
        }
        taskRunning = true;
    }
    command.run();
    synchronized (lock) {
        taskRunning = false;
        if (shutdown) {
            terminated = true;
            lock.notifyAll();
        }
    }
}

这个就是我们 startStopExecutor 的主要作用,帮我们启动子组件
对于 Container 来说启动的就是 host,而其实 host 也是继承了 ContainerBase 的,后面可以再继续介绍下


org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer 的时候会调用

this.webServer = factory.getWebServer(getSelfInitializer());

就会调用到之前说到的

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
		return this::selfInitialize;
	}

	private void selfInitialize(ServletContext servletContext) throws ServletException {
		prepareWebApplicationContext(servletContext);
		registerApplicationScope(servletContext);
		WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
		for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}
	}

而这里有个
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#getServletContextInitializerBeans
获取初始化所需要的 bean

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
	return new ServletContextInitializerBeans(getBeanFactory());
}

里面就是 new 了这个 org.springframework.boot.web.servlet.ServletContextInitializerBeans#ServletContextInitializerBeans

public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
			Class<? extends ServletContextInitializer>... initializerTypes) {
		this.initializers = new LinkedMultiValueMap<>();
		this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
				: Collections.singletonList(ServletContextInitializer.class);
        // 这里是添加 ServletContextInitializer
		addServletContextInitializerBeans(beanFactory);
        // filter 是在这里
		addAdaptableBeans(beanFactory);
		List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
				.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
				.collect(Collectors.toList());
		this.sortedList = Collections.unmodifiableList(sortedInitializers);
		logMappings(this.initializers);
	}

具体来看看 org.springframework.boot.web.servlet.ServletContextInitializerBeans#addAdaptableBeans

protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
	MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
       // 这里把 Servlet 作为 RegistrationBean
	addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
       // 这里把 Filter 作为 RegistrationBean
	addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
	for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
		addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
				new ServletListenerRegistrationBeanAdapter());
	}
}

里面是这两段逻辑,先从 beanfactory 里获取到该类型的 bean,然后包装成 RegistrationBean,添加到 initializers

protected <T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
		RegistrationBeanAdapter<T> adapter) {
	addAsRegistrationBean(beanFactory, type, type, adapter);
}

private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type,
		Class<B> beanType, RegistrationBeanAdapter<T> adapter) {
	List<Map.Entry<String, B>> entries = getOrderedBeansOfType(beanFactory, beanType, this.seen);
	for (Entry<String, B> entry : entries) {
		String beanName = entry.getKey();
		B bean = entry.getValue();
		if (this.seen.add(bean)) {
			// One that we haven't already seen
			RegistrationBean registration = adapter.createRegistrationBean(beanName, bean, entries.size());
			int order = getOrder(bean);
			registration.setOrder(order);
			this.initializers.add(type, registration);
			if (logger.isTraceEnabled()) {
				logger.trace("Created " + type.getSimpleName() + " initializer for bean '" + beanName + "'; order="
						+ order + ", resource=" + getResourceDescription(beanName, beanFactory));
			}
		}
	}
}

然后再回到第一段,其中的循环里面,因为 ServletContextInitializerBeans 继承了 AbstractCollection 并且实现了 iterator 接口

for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
			beans.onStartup(servletContext);
		}

在 for 循环中会把 Initializer 排序后的 sortedList 返回 iterator,也就是前面把 filter 包装成的 RegistrationBean

public Iterator<ServletContextInitializer> iterator() {
		return this.sortedList.iterator();
	}

然后调用了 onStartup 方法,

public final void onStartup(ServletContext servletContext) throws ServletException {
	String description = getDescription();
	if (!isEnabled()) {
		logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
		return;
	}
	register(description, servletContext);
}

就会去执行注册逻辑

0%