在关于Yarn那些事的博客里,介绍的主要是针对任务提交的一个动态流程说明,而其中牵涉到的一些细节问题,必须通过Resourcemanager的启动和NodeManager的启动,来更好的说明。

而本系列,就详细说说ResourceManager启动过程中,都发生了什么。

我们都知道,Yarn的启动脚本是start-yan.sh,我们就从这个脚本开始,琢磨琢磨。

  1. "$bin"/yarn-daemon.sh --config $YARN_CONF_DIR  start resourcemanager

脚本里这句话,指向了本目录下的yarn-daemon.sh脚本,命令参数指定了resourcemanager,接着看yarn-daemon.sh脚本:

  1. nohup nice -n $YARN_NICENESS "$HADOOP_YARN_HOME"/bin/yarn --config $YARN_CONF_DIR $command "$@" > "$log" 2>&1 < /dev/null &

这句话很关键,交代了我们实际的启动脚本是bin目录下的yarn,我们看下:

  1. elif [ "$COMMAND" = "resourcemanager" ] ; then
  2. CLASSPATH=${CLASSPATH}:$YARN_CONF_DIR/rm-config/log4j.properties
  3. CLASSPATH=${CLASSPATH}:"$HADOOP_YARN_HOME/$YARN_DIR/timelineservice/*"
  4. CLASSPATH=${CLASSPATH}:"$HADOOP_YARN_HOME/$YARN_DIR/timelineservice/lib/*"
  5. CLASS='org.apache.hadoop.yarn.server.resourcemanager.ResourceManager'
  6. YARN_OPTS="$YARN_OPTS $YARN_RESOURCEMANAGER_OPTS"
  7. if [ "$YARN_RESOURCEMANAGER_HEAPSIZE" != "" ]; then
  8. JAVA_HEAP_MAX="-Xmx""$YARN_RESOURCEMANAGER_HEAPSIZE""m"
  9. fi

终于顺利找到了根基,原来根据我们指定的脚本,找到的是ResourceManager这个类来启动的,下面就看看这个类。

先来看下注释:

  1. /**
  2. * The ResourceManager is the main class that is a set of components. "I am the
  3. * ResourceManager. All your resources belong to us..."
  4. *
  5. */
  6. @SuppressWarnings("unchecked")
  7. public class ResourceManager extends CompositeService implements Recoverable

格外霸气,管理整个集群内所有的资源,并且继承了CompositeService类,这是一个服务类,不多介绍了,主要提供了一些服务初始化和启动的方法,供子类使用。

从ResourceManager的成员变量开始看起:

  1. protected ClientToAMTokenSecretManagerInRM clientToAMSecretManager = new ClientToAMTokenSecretManagerInRM();
  2. protected RMContainerTokenSecretManager containerTokenSecretManager;
  3. protected NMTokenSecretManagerInRM nmTokenSecretManager;
  4. protected AMRMTokenSecretManager amRmTokenSecretManager;
  5. private Dispatcher rmDispatcher;
  6. protected ResourceScheduler scheduler;
  7. private ClientRMService clientRM;
  8. protected ApplicationMasterService masterService;
  9. private ApplicationMasterLauncher applicationMasterLauncher;
  10. private AdminService adminService;
  11. private ContainerAllocationExpirer containerAllocationExpirer;
  12. protected NMLivelinessMonitor nmLivelinessMonitor;
  13. protected NodesListManager nodesListManager;
  14. private EventHandler<SchedulerEvent> schedulerDispatcher;
  15. protected RMAppManager rmAppManager;
  16. protected ApplicationACLsManager applicationACLsManager;
  17. protected QueueACLsManager queueACLsManager;
  18. protected RMDelegationTokenSecretManager rmDTSecretManager;
  19. private DelegationTokenRenewer delegationTokenRenewer;
  20. private WebApp webApp;
  21. protected RMContext rmContext;
  22. protected ResourceTrackerService resourceTracker;
  23. private boolean recoveryEnabled;

很多,具体可以参照每个类的用法,在此不多说,其中牵涉到Application较多的,比如RMAppManager,RMContext等,需要细看,这是废话,每个都值得研究。

看Main方法:

  1. Configuration conf = new YarnConfiguration();
  2. ResourceManager resourceManager = new ResourceManager();
  3. ShutdownHookManager.get().addShutdownHook(new CompositeServiceShutdownHook(resourceManager),
  4. SHUTDOWN_HOOK_PRIORITY);
  5. setHttpPolicy(conf);
  6. resourceManager.init(conf);
  7. resourceManager.start();

直接把目光聚焦在这里,我们重点研究下服务的初始化和启动。

  1. this.rmDispatcher = createDispatcher();
  2. addIfService(this.rmDispatcher);

初始化的第一个关键点,创建调度器,这是ResourceManager异步调度的关键,看看这个方法:很简单:

  1. protected Dispatcher createDispatcher() {
  2. return new AsyncDispatcher();
  3. }

很明显,这是个异步调度器,看看这个类的注释和初始化步骤:

  1. /**
  2. * Dispatches {@link Event}s in a separate thread. Currently only single thread
  3. * does that. Potentially there could be multiple channels for each event type
  4. * class and a thread pool can be used to dispatch the events.
  5. */
  6. @SuppressWarnings("rawtypes")
  7. @Public
  8. @Evolving
  9. public class AsyncDispatcher extends AbstractService implements Dispatcher

这也是一个需要启动的服务,用于事件的调度:

  1. public AsyncDispatcher() {
  2. this(new LinkedBlockingQueue<Event>());
  3. }
  4. public AsyncDispatcher(BlockingQueue<Event> eventQueue) {
  5. super("Dispatcher");
  6. this.eventQueue = eventQueue;
  7. this.eventDispatchers = new HashMap<Class<? extends Enum>, EventHandler>();
  8. }

注意,Dispatcher内部封装了一个阻塞队列,运行过程中会把事件都放在这个池子里,并进行调度处理,同时定义了一个eventDispatchers,后续代码更容易看懂这个map的作用:

我们仔细看看AsyncDispatcher的服务初始化代码:

  1. @Override
  2. protected void serviceInit(Configuration conf) throws Exception {
  3. this.exitOnDispatchException = conf.getBoolean(Dispatcher.DISPATCHER_EXIT_ON_ERROR_KEY,
  4. Dispatcher.DEFAULT_DISPATCHER_EXIT_ON_ERROR);
  5. super.serviceInit(conf);
  6. }

目前来说,AsyncDispatcher的初始化代码先到这儿,我们继续看ResourceManager的服务初始化代码:

  1. this.amRmTokenSecretManager = createAMRMTokenSecretManager(conf);

我们看到内部有个这个成员变量:

  1. /**
  2. * AMRM-tokens are per ApplicationAttempt. If users redistribute their
  3. * tokens, it is their headache, god save them. I mean you are not supposed to
  4. * distribute keys to your vault, right? Anyways, ResourceManager saves each
  5. * token locally in memory till application finishes and to a store for restart,
  6. * so no need to remember master-keys even after rolling them.
  7. */
  8. public class AMRMTokenSecretManager extends
  9. SecretManager<AMRMTokenIdentifier>

看注释,清晰明了,每次提交一个ApplicationAttempt时候,都不用再递交自己的token了,实际上还是一个身份验证工具(自己的理解)。

  1. this.containerAllocationExpirer = new ContainerAllocationExpirer(this.rmDispatcher);
  2. addService(this.containerAllocationExpirer);

看下这两句话ContainerAllocationExpirer,并且加到了serviceList中,用于最后的初始化,我们看看这个是什么作用,注释非常简单,还是留在动态提交ApplicationMaster的时候分析吧,其实主要是用来判断分配的container是否在规定时间内得到启动的:

  1. AMLivelinessMonitor amLivelinessMonitor = createAMLivelinessMonitor();
  2. addService(amLivelinessMonitor);

看这个AMLiveLinessMonitor,顾名思义,是用来检查ApplicationLivenessMonitor是否存活的,其继承了这个类:

  1. /**
  2. * A simple liveliness monitor with which clients can register, trust the
  3. * component to monitor liveliness, get a call-back on expiry and then finally
  4. * unregister.
  5. */
  6. @Public
  7. @Evolving
  8. public abstract class AbstractLivelinessMonitor<O> extends AbstractService

同时,ContainerAllocationMonitor也继承了这个类,就是用于让客户端监控的:

  1. AMLivelinessMonitor amFinishingMonitor = createAMLivelinessMonitor();
  2. addService(amFinishingMonitor);

下面初始化了一个一样的AMLiveLinessMonitor,但是变量名不同,同样是起监控作用的:

接下来看RMStateStore的初始化:

  1. boolean isRecoveryEnabled = conf.getBoolean(YarnConfiguration.RECOVERY_ENABLED,
  2. YarnConfiguration.DEFAULT_RM_RECOVERY_ENABLED);
  3. RMStateStore rmStore = null;
  4. if (isRecoveryEnabled) {
  5. recoveryEnabled = true;
  6. rmStore = RMStateStoreFactory.getStore(conf);
  7. } else {
  8. recoveryEnabled = false;
  9. rmStore = new NullRMStateStore();
  10. }

对于大部分成员变量的初始化不予多说,先看下RMStateStore的初始化,在我们默认配置下:isRecoveryEnabled为false,所以创建了一个空的RMStateStore,即NullRMStateStore,对于ResourceManager的状态进行存储:

  1. rmStore.init(conf);
  2. rmStore.setRMDispatcher(rmDispatcher);

这里面注意下,rmStore内部的dispatcher与RM的dispatcher不是同一个,代码如下:

  1. private Dispatcher rmDispatcher;
  2. AsyncDispatcher dispatcher;

RMStateStore内部有两个调度器,rmDispatcher是RM的调度器,而dispatcher则是其内部用来调度事件的调度器,对于RMStateStore的init代码有些绕,仔细看下:

  1. @Override
  2. public void init(Configuration conf) {
  3. if (conf == null) {
  4. throw new ServiceStateException("Cannot initialize service " + getName() + ": null configuration");
  5. }
  6. if (isInState(STATE.INITED)) {
  7. return;
  8. }
  9. synchronized (stateChangeLock) {
  10. if (enterState(STATE.INITED) != STATE.INITED) {
  11. setConfig(conf);
  12. try {
  13. serviceInit(config);
  14. if (isInState(STATE.INITED)) {
  15. // if the service ended up here during init,
  16. // notify the listeners
  17. notifyListeners();
  18. }
  19. } catch (Exception e) {
  20. noteFailure(e);
  21. ServiceOperations.stopQuietly(LOG, this);
  22. throw ServiceStateException.convert(e);
  23. }
  24. }
  25. }
  26. }

其实际调用的是AbstractService的init方法,其中调用到了serviceInit方法,而这个方法,则是RMStateStore的方法:

  1. public synchronized void serviceInit(Configuration conf) throws Exception {
  2. // create async handler
  3. dispatcher = new AsyncDispatcher();
  4. dispatcher.init(conf);
  5. dispatcher.register(RMStateStoreEventType.class, new ForwardingEventHandler());
  6. initInternal(conf);
  7. }

很清楚看到了内部封装了一个自己的dispatcher,用于调度RMStateStoreEventType类型的事件:

下面接着看:

  1. this.rmContext = new RMContextImpl(this.rmDispatcher, rmStore, this.containerAllocationExpirer,
  2. amLivelinessMonitor, amFinishingMonitor, delegationTokenRenewer, this.amRmTokenSecretManager,
  3. this.containerTokenSecretManager, this.nmTokenSecretManager, this.clientToAMSecretManager);

这是重头戏,我们必须看看这个拥有如此多成员变量的RMContextImpl到底是什么:

  1. /**
  2. * Context of the ResourceManager.
  3. */
  4. public interface RMContext {

这是RMContextImpl父类的注释,是ResourceManager的上下文,就相当于管家了,基本大权在握,是ResourceManager的心腹。

接下来是这儿:

  1. this.nodesListManager = new NodesListManager(this.rmContext);

其实就相当于告诉了RM,这里到底有多少个子节点可供使用,而且给新建的NodesListManager内部也安插了RM的心腹,即RMContextImpl。

  1. this.rmDispatcher.register(NodesListManagerEventType.class, this.nodesListManager);

看到这儿,我们又得回去看AsyncDispatcher中的一个register方法:

  1. @SuppressWarnings("unchecked")
  2. @Override
  3. public void register(Class<? extends Enum> eventType, EventHandler handler) {
  4. /* check to see if we have a listener registered */
  5. EventHandler<Event> registeredHandler = (EventHandler<Event>) eventDispatchers.get(eventType);
  6. LOG.info("Registering " + eventType + " for " + handler.getClass());
  7. if (registeredHandler == null) {
  8. eventDispatchers.put(eventType, handler);
  9. } else if (!(registeredHandler instanceof MultiListenerHandler)) {
  10. /* for multiple listeners of an event add the multiple listener handler */
  11. MultiListenerHandler multiHandler = new MultiListenerHandler();
  12. multiHandler.addHandler(registeredHandler);
  13. multiHandler.addHandler(handler);
  14. eventDispatchers.put(eventType, multiHandler);
  15. } else {
  16. /* already a multilistener, just add to it */
  17. MultiListenerHandler multiHandler = (MultiListenerHandler) registeredHandler;
  18. multiHandler.addHandler(handler);
  19. }
  20. }

仔细看来,其实就相当于eventDispatcher的充实,把各类事件即相应的处理,都送给eventdispatcher,方便后续出现类似事件,eventdispatcher能够迅速找到对应的对象来进行处理。

这里,就相当于把对应于NodeManager出现的事情,都交给了NodeListManager,这就是你的工作了。

  1. public enum NodesListManagerEventType {
  2. NODE_USABLE, NODE_UNUSABLE
  3. }

如果出现了节点可用和不可用的事情,你就得迅速予以处理了。

  1. // Initialize the scheduler
  2. this.scheduler = createScheduler();
  3. this.schedulerDispatcher = createSchedulerEventDispatcher();
  4. addIfService(this.schedulerDispatcher);
  5. this.rmDispatcher.register(SchedulerEventType.class, this.schedulerDispatcher);

接下来,创建了一个调度器,这一段得仔细看看了,因为yarn中的调度器非常重要,我们作业的初始化,都离不开它:

  1. protected ResourceScheduler createScheduler() {
  2. String schedulerClassName = conf.get(YarnConfiguration.RM_SCHEDULER, YarnConfiguration.DEFAULT_RM_SCHEDULER);
  3. LOG.info("Using Scheduler: " + schedulerClassName);
  4. try {
  5. Class<?> schedulerClazz = Class.forName(schedulerClassName);
  6. if (ResourceScheduler.class.isAssignableFrom(schedulerClazz)) {
  7. return (ResourceScheduler) ReflectionUtils.newInstance(schedulerClazz, this.conf);
  8. } else {
  9. throw new YarnRuntimeException("Class: " + schedulerClassName + " not instance of "
  10. + ResourceScheduler.class.getCanonicalName());
  11. }
  12. } catch (ClassNotFoundException e) {
  13. throw new YarnRuntimeException("Could not instantiate Scheduler: " + schedulerClassName, e);
  14. }
  15. }

我这里的代码是hadoop 2.2.0,在默认配置下,配置的调度器是:

  1. org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler

而接着,我们给调度器自己定义了一个调度器,换句话说,对于调度器接受的事件类型,其会继续调度给其他的handler去处理

最后,把这个调度器也注册给了RM内部的调度器:

  1. protected EventHandler<SchedulerEvent> createSchedulerEventDispatcher() {
  2. return new SchedulerEventDispatcher(this.scheduler);
  3. }
  1. this.rmDispatcher.register(SchedulerEventType.class, this.schedulerDispatcher);

接着,我们的异步调度器又加入了三个成分,负责对Application相关事件,ApplicationAttempt事件,RMNode事件进行调度:

  1. // Register event handler for RmAppEvents
  2. this.rmDispatcher.register(RMAppEventType.class, new ApplicationEventDispatcher(this.rmContext));
  3. // Register event handler for RmAppAttemptEvents
  4. this.rmDispatcher.register(RMAppAttemptEventType.class, new ApplicationAttemptEventDispatcher(this.rmContext));
  5. // Register event handler for RmNodes
  6. this.rmDispatcher.register(RMNodeEventType.class, new NodeEventDispatcher(this.rmContext));

接着,我们看下这个:

  1. this.resourceTracker = createResourceTrackerService();
  2. addService(resourceTracker);

从官方文档中,我们知道RM实现了对于系统全部资源的管控,而这个管控是通过RPC来实现的,NodeManager调用ResourceTracker内的方法来提交自己的资源,而RM端有相应的处理,返回命令,让NM予以执行,而ResourceTrackerSerive就是在此处初始化的:

  1. @Override
  2. protected void serviceInit(Configuration conf) throws Exception {
  3. resourceTrackerAddress = conf.getSocketAddr(YarnConfiguration.RM_RESOURCE_TRACKER_ADDRESS,
  4. YarnConfiguration.DEFAULT_RM_RESOURCE_TRACKER_ADDRESS,
  5. YarnConfiguration.DEFAULT_RM_RESOURCE_TRACKER_PORT);
  6. RackResolver.init(conf);
  7. nextHeartBeatInterval = conf.getLong(YarnConfiguration.RM_NM_HEARTBEAT_INTERVAL_MS,
  8. YarnConfiguration.DEFAULT_RM_NM_HEARTBEAT_INTERVAL_MS);
  9. if (nextHeartBeatInterval <= 0) {
  10. throw new YarnRuntimeException("Invalid Configuration. " + YarnConfiguration.RM_NM_HEARTBEAT_INTERVAL_MS
  11. + " should be larger than 0.");
  12. }
  13. minAllocMb = conf.getInt(YarnConfiguration.RM_SCHEDULER_MINIMUM_ALLOCATION_MB,
  14. YarnConfiguration.DEFAULT_RM_SCHEDULER_MINIMUM_ALLOCATION_MB);
  15. minAllocVcores = conf.getInt(YarnConfiguration.RM_SCHEDULER_MINIMUM_ALLOCATION_VCORES,
  16. YarnConfiguration.DEFAULT_RM_SCHEDULER_MINIMUM_ALLOCATION_VCORES);
  17. super.serviceInit(conf);
  18. }

可以看到,这里牵涉到了很多的默认配置的地址,所以,看代码是必要的,我们想要把配置文件全部搞通,其实认真搞通代码,就轻而易举了。

  1. masterService = createApplicationMasterService();
  2. addService(masterService);

看看这一段,新建了一个ApplicationMasterService,用于对所有提交的ApplicationMaster进行管理:其中的serviceInit方法不复杂,重点在其serviceStart方法中:

下面,我们注意看下这段代码:

  1. this.rmAppManager = createRMAppManager();
  2. // Register event handler for RMAppManagerEvents
  3. this.rmDispatcher.register(RMAppManagerEventType.class, this.rmAppManager);
  4. this.rmDTSecretManager = createRMDelegationTokenSecretManager(this.rmContext);
  5. rmContext.setRMDelegationTokenSecretManager(this.rmDTSecretManager);
  6. clientRM = createClientRMService();
  7. rmContext.setClientRMService(clientRM);
  8. addService(clientRM);
  9. adminService = createAdminService(clientRM, masterService, resourceTracker);
  10. addService(adminService);
  11. this.applicationMasterLauncher = createAMLauncher();
  12. this.rmDispatcher.register(AMLauncherEventType.class, this.applicationMasterLauncher);

这里有些东西需要注意,新建了一个ClientRMService,负责客户端与RM的所有交互,对于客户端的每个请求,我们都可以在ClientRMService下面找到相应的代码。

下面接着看,AMLauncher,这个很重要,负责ApplicationMaster的启动,必须重点分析下。

  1. this.applicationMasterLauncher = createAMLauncher();
  2. this.rmDispatcher.register(AMLauncherEventType.class, this.applicationMasterLauncher);

基于RM的管家,建立了一个AMLauncher:

  1. protected ApplicationMasterLauncher createAMLauncher() {
  2. return new ApplicationMasterLauncher(this.rmContext);
  3. }

分析下其中的serviceInit方法:

  1. @Override
  2. protected void serviceStart() throws Exception {
  3. launcherHandlingThread.start();
  4. super.serviceStart();
  5. }

看看其中的launchHandlingThread:

  1. private class LauncherThread extends Thread {
  2. public LauncherThread(www.vboyule.cn) {
  3. super("ApplicationMaster Launcher");
  4. }
  5. @Override
  6. public void run() {
  7. while (!this.isInterrupted()) {
  8. Runnable toLaunch;
  9. try {
  10. toLaunch = masterEvents.take();
  11. launcherPool.execute(toLaunch);
  12. } catch (InterruptedException e) {
  13. LOG.warn(this.getClass().getName() + " interrupted. Returning.");
  14. return;
  15. }
  16. }
  17. }
  18. }

其内部封装了一个线程池,对于调度给自身的事件不断进行处理:

在serviceInit方法的最后,调用了父类的serviceInit方法,我们看下其父类CompositeService的serviceInit方法:

  1. protected void serviceInit(Configuration conf) throws Exception {
  2. List<Service> services = getServices();
  3. if (LOG.isDebugEnabled()) {
  4. LOG.debug(getName(www.feifanyule.cn) + ": initing services, size=" + services.size());
  5. }
  6. for (Service service : services) {
  7. service.init(conf);
  8. }
  9. super.serviceInit(conf);
  10. }
  1. public List<Service> www.cnzhaotai.com/ getServices() {
  2. synchronized (serviceList) {
  3. return Collections.unmodifiableList(serviceList);
  4. }
  5. }

在看源码的时候,发现RM的serviceInit方法中,所有服务都有一个操作:

  1. protected void addService(Service service) {
  2. if (LOG.www.ysgj1688.com isDebugEnabled(www.vboyl130.cn)) {
  3. LOG.www.cnzhaotai.com/ debug("Adding service " + service.getName());
  4. }
  5. synchronized (serviceList) {
  6. serviceList.add(service);
  7. }
  8. }

调用了父类中的addService方法,把所有服务添加到了serviceList中,在这里统一予以初始化,不得不说设计很精妙,在我们分析源码的时候,必须注意看下service相关的类;最顶层的父类是Serivce类,这是个接口,定义了服务的基本操作和生命周期,其唯一的实现类,是个抽象类,为AbstractService,一般来说,并不复杂的服务继承并实现AbstractService即可,复杂的服务如RM就会继承Compositeservice(AbstractService的子类)

关于Yarn源码那些事-前传之ResourceManager篇(一)初始化的更多相关文章

  1. Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)

    我们知道,如果想要在Yarn上运行MapReduce作业,仅需实现一个ApplicationMaster组件即可,而MRAppMaster正是MapReduce在Yarn上ApplicationMas ...

  2. Yarn源码分析之如何确定作业运行方式Uber or Non-Uber?

    在MRAppMaster中,当MapReduce作业初始化时,它会通过作业状态机JobImpl中InitTransition的transition()方法,进行MapReduce作业初始化相关操作,而 ...

  3. Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(二)

    本文继<Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)>,接着讲述MapReduce作业在MRAppMaster上处理总流程,继上篇讲到作业初始化之后的作 ...

  4. 【commons-pool2源码】写前思考

    写作的初衷 工作4年多, 一直没有系统的阅读过优秀的开源代码, 所以从今年开始做一些尝试, 阅读源码并且试着将自己的理解以文章的形式输出, 从而达到以下目的: 通过阅读源码提升自身的技术水准, 通过写 ...

  5. [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇

    [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 目录 [源码解析] TensorFlow 分布式 DistributedStrategy 之基础篇 1. ...

  6. 老李推荐:第8章1节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-运行环境初始化

    老李推荐:第8章1节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-运行环境初始化   首先大家应该清楚的一点是,MonkeyRunner的运行是牵涉到主机端和目 ...

  7. Centos 7源码编译安装 php7.1 之生产篇

    Centos 7源码编译安装 php7.1 之生产篇 Published 2017年4月30日 by Node Cloud 介绍: 久闻php7的速度以及性能那可是比php5系列的任何一版本都要快,具 ...

  8. Hadoop Yarn源码 - day1

    Hadoop 2.6.0下面的关于Yarn工程,如下所示,主要有以下七个module: hadoop-yarn-api:和外部平台交互的接口 hadoop-yarn-applications hado ...

  9. THINKPHP源码学习--------文件上传类

    TP图片上传类的理解 在做自己项目上传图片的时候一直都有用到TP的上传图片类,所以要进入源码探索一下. 文件目录:./THinkPHP/Library/Think/Upload.class.php n ...

随机推荐

  1. SQL基础语句汇总

    连接数据库 1 mysql -h10.20.66.32 -uroot -p123456 -h后面是mysqlServer所在地址,-u后面是用户名,-p后面是密码 查看数据库 1 show datab ...

  2. springboot整合mybatis笔记

    1首先创建一个springboot项目 创建项目的文件结构以及jdk的版本 选择项目所需要的依赖 之后点击finish,完成创建 2以下是文件结构 看一下啊pom.xml; <?xml vers ...

  3. Servlet学习笔记04——什么是重定向,servlet生命周期?

    1.重定向 (1)什么是重定向? 服务器通知浏览器访问一个新的地址. 注: 服务器可以通过发送一个302状态码及一个 Location消息头(该消息头的值是一个地址,一般 称之为重定向地址)给浏览器, ...

  4. zeppelin ERROR总结

    ERROR [2017-03-23 20:01:50,799] ({qtp331657670-221} NotebookServer.java[onMessage]:221) - Can't hand ...

  5. centos 7 编译安装mysql 详细过程

    一.配置防火墙,开启80端口.3306端口 CentOS 7.0默认使用的是firewall作为防火墙,这里改为iptables防火墙. 1.关闭firewall: systemctl stop fi ...

  6. Linux下vim操作的一些使用技巧

    以下均为个人在编程时对vim编辑器的一些心得,大神请指点,新手可以看过来 1.多文本编辑 vim -On/-on filename_1 … filename_n 如上所示,在要编辑的文件名前加上“-O ...

  7. Android 中运行时权限获取联系人信息 Demo

    代码比较简单... AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <m ...

  8. 基于itchat定制聊天机器人

    #coding=utf8import requestsimport itchat #key自己到图灵注册一个 KEY = '************************************** ...

  9. HDFS HA 的 core-site.xml

    <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <!-- Licens ...

  10. Android 用Chrome浏览器打开url 自定义样式

    1.效果预览 1.1.真实效果就是从某一个APP,打开一个url,跳转到谷歌浏览器,返回之后,又回到之前的APP      1.2.说明一下条件 1.手机上必须要安装谷歌浏览器 2.手机上的默认浏览器 ...