在关于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. AsyncDisplayKit技术分析

    转载请注明出处:http://xujim.github.io/ios/2014/12/07/AsyncDisplayKit_inside.html ,谢谢 前言 Facebook前段时间发布了其iOS ...

  2. jquery 操作css 尺寸

    .height() 获取元素集合中的第一个元素的当前计算高度值,或设置每一个匹配元素的高度值. .height() 获取匹配元素集合中的第一个元素的当前计算高度值. 这个方法不接受参数. $(wind ...

  3. ABAP Table Control

    SAP中,Table Control是在Screen中用的最广泛的控件之一了,可以实现对多行数据的编辑.  简单来说,Table Control是一组屏幕元素在Screen上的重复出现,这就是它与普通 ...

  4. html编写头部,mata的含义

    <meta name="viewport" content="width=device-width, initial-scale=1.0"> con ...

  5. Logrotate实现Catalina.out日志每俩小时切割

    一.Logrotate工具介绍 Logrotate是一个日志文件管理工具,它是Linux默认自带的一个日志切割工具.用来把旧文件轮转.压缩.删除,并且创建新的日志文件.我们可以根据日志文件的大小.天数 ...

  6. python——内建模块instance的学习

    python中内建函数isinstance的用法 语法:isinstance(object,type) 作用:来判断一个对象是否是一个已知的类型. 其第一个参数(object)为对象,第二个参数(ty ...

  7. 笔记-python-standard library-16.3 time

    笔记-python-standard library-16.3 time 1.      time 1.1.    开始 time模块中时间表现的格式主要有三种: timestamp时间戳,时间戳表示 ...

  8. spark stream简介

    1.复杂的迭代计算 假如我们计算的需要100步的计算,但是当我执行到第99步的时候,突然数据消失, 根据血统,从头进行恢复,代价很高 sc.setCheckpointDir("共享存储文件系 ...

  9. 7,vim

    vim与程序员 所有的 Unix Like 系统都会内建 vi 文书编辑器,其他的文书编辑器则不一定会存在. 但是目前我们使用比较多的是 vim 编辑器. vim 具有程序编辑的能力,可以主动的以字体 ...

  10. android 事件拦截 (Viewpager不可以左右滑动)

    以前没有做过真正的需求,所以从来没有觉得事件拦截分发处理有什么好懂的. 现在做需求了,真的是什么需求都有,你作为开发都要去研究实现.比如说,只能点不能滑动的viewpager.其实这都可以不用view ...