冷迁移Resize

1、迁移是指将虚拟机从一个计算节点迁移到另外一个节点上。冷迁移过程中虚拟机是关机或是处于不可用的状态,而热迁移则需要保证虚拟机时刻运行。

2、Resize则是指根据需求调整虚拟机的计算能力和资源。Resize和冷迁移的工作流程相同,区别只是Resize时必须保持新的Flavor配置大于旧的配置,而冷迁移则要求两者相同。

3、resize和冷迁移的本质区别:

同一个接口,根据传参不同,有flavor就执行升配,无flavor就走的是冷迁移。

  1. 迁移一般都是从一个节点迁移到另一个节点上,升配的时候一般默认也是如此,但可以配置resize_to_same_host,升配的时候,不迁移到别的节点。

1、 入口API:nova/api/openstack/compute/migrate_server.py

class MigrateServerController(wsgi.Controller):................def _migrate(self, req, id, body):"""允许管理员将服务器迁移到新主机。"""context = req.environ['nova.context']instance = common.get_instance(self.compute_api, context, id,expected_attrs=['flavor', 'services'])context.can(ms_policies.POLICY_ROOT % 'migrate',target={'project_id': instance.project_id})host_name = Noneif (api_version_request.is_supported(req, min_version='2.56') andbody['migrate'] is not None):host_name = body['migrate'].get('host')try:self.compute_api.resize(req.environ['nova.context'], instance,host_name=host_name)................

instance = common.get_instance(self.compute_api, context, id,expected_attrs=['flavor', 'services'])依据instance id 获取instance id 并对instance的存在性做了校验

实际调用函数self.compute_api.resize(req.environ['nova.context'], instance,host_name=host_name)。compute_api.resize定义在 nova/compute/api.py

2、nova/compute/api.py

class API:"""用于与计算管理器交互的API."""@check_instance_lock
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED])
@check_instance_host(check_is_up=True)
def resize(self, context, instance, flavor_id=None, clean_shutdown=True,host_name=None, auto_disk_config=None):"""调整(即,迁移)一个正在运行的实例的大小。如果flavor_id为None,则认为该进程是迁移,保持原flavor_id。如果flavor_id不是None,实例应该迁移到新的主机并调整大小为新的flavor_id。在调整大小的情况下,host_name总是None。Host_name仅在冷迁移情况下可以设置。"""allow_cross_cell_resize = self._allow_cross_cell_resize(context, instance)
if host_name is not None:node = self._validate_host_for_cold_migrate(context, instance, host_name, allow_cross_cell_resize)# 校验磁盘信息
self._check_auto_disk_config(instance, auto_disk_config=auto_disk_config)
# 获取当前flavor 并校验flavor
current_flavor = instance.get_flavor()
’’’注意(aarents):确保image_base_image_ref存在,因为在finish_resize/cross_cell_resize期间需要它。实例升级从一个较老的新星释放可能没有这个属性,因为a rebuild bug bug /1893618。‘’’
instance.system_metadata.update({'image_base_image_ref': instance.image_ref}
)
volume_backed = None# 如果没有提供flavor_id,则只迁移实例。
if not flavor_id:LOG.debug("flavor_id is None. Assuming migration.",instance=instance)# 保证迁移前后虚拟机 Flavor 不会发生改变new_flavor = current_flavor
else:................
filter_properties = {'ignore_hosts': []}“””通过配置项 allow_resize_to_same_host 来决定是否会 resize 到同一个计算节点实际上,当 Migrate 到同一个计算节点时,nova-compute 会触发 UnableToMigrateToSelf 异常,再继续 Retry Scheduler,直至调度到合适的计算节点或异常退出,前提是 nova-scheduler 启用了 RetryFilter“””
if not self._allow_resize_to_same_host(same_flavor, instance):filter_properties['ignore_hosts'].append(instance.host)
# 获取request_spec,该参数为新的调度参数,将ignore host加入调度
request_spec = objects.RequestSpec.get_by_instance_uuid(context, instance.uuid)
request_spec.ignore_hosts = filter_properties['ignore_hosts']................# 更新虚拟机状态为RESIZE_PREP, 虚拟机设置为该状态开始,将会锁定,不允许进行其他任务instance.task_state = task_states.RESIZE_PREP
instance.progress = 0
instance.auto_disk_config = auto_disk_config or False
instance.save(expected_task_state=[None])# 发送迁移状态if not flavor_id:self._record_action_start(context, instance, instance_actions.MIGRATE)
else:self._record_action_start(context, instance,instance_actions.RESIZE)
scheduler_hint = {'filter_properties': filter_properties}................# resize_instance 任务迁移
self.compute_task_api.resize_instance(context, instance,scheduler_hint=scheduler_hint,flavor=new_flavor,clean_shutdown=clean_shutdown,request_spec=request_spec,do_cast=True)

该步骤主要是进行参数校验,并获取request_spec 调度参数。 调用nova/conductor/api的resize_instance函数

3、nova/conductor/api

def resize_instance(self, context, instance, scheduler_hint, flavor,reservations=None, clean_shutdown=True,request_spec=None, host_list=None, do_cast=False):self.conductor_compute_rpcapi.migrate_server(context, instance, scheduler_hint, live=False, rebuild=False,flavor=flavor, block_migration=None, disk_over_commit=None,reservations=reservations, clean_shutdown=clean_shutdown,request_spec=request_spec, host_list=host_list,do_cast=do_cast)

调用的conductor 层的 migrate_server, 通过rpc远程调用方式进行。

4、nova/conductor/rcpapi.py

def migrate_server(self, context, instance, scheduler_hint, live, rebuild,flavor, block_migration, disk_over_commit,reservations=None, clean_shutdown=True, request_spec=None,host_list=None, do_cast=False):cctxt = self.client.prepare(version=version,call_monitor_timeout=CONF.rpc_response_timeout,timeout=CONF.long_rpc_timeout)
if do_cast:return cctxt.cast(context, 'migrate_server', **kw)
return cctxt.call(context, 'migrate_server', **kw)

实际调用的conductor的migrate_server该过程实际通过Manager进行处理。

5、nova/conductor/manager.py

@targets_cell
@wrap_instance_event(prefix='conductor')
def migrate_server(self, context, instance, scheduler_hint, live, rebuild,flavor, block_migration, disk_over_commit, reservations=None,clean_shutdown=True, request_spec=None, host_list=None):if instance and not isinstance(instance, nova_object.NovaObject):# NOTE(danms): Until v2 of the RPC API, we need to tolerate# old-world instance objects hereattrs = ['metadata', 'system_metadata', 'info_cache','security_groups']instance = objects.Instance._from_db_object(context, objects.Instance(), instance,expected_attrs=attrs)# NOTE: Remove this when we drop support for v1 of the RPC APIif flavor and not isinstance(flavor, objects.Flavor):# Code downstream may expect extra_specs to be populated since it# is receiving an object, so lookup the flavor to ensure this.flavor = objects.Flavor.get_by_id(context, flavor['id'])if live and not rebuild and not flavor:self._live_migrate(context, instance, scheduler_hint,block_migration, disk_over_commit, request_spec)elif not live and not rebuild and flavor:instance_uuid = instance.uuidwith compute_utils.EventReporter(context, 'cold_migrate',self.host, instance_uuid):self._cold_migrate(context, instance, flavor,scheduler_hint['filter_properties'],clean_shutdown, request_spec,host_list)else:raise NotImplementedError()

migrate_server 是一个公用的函数, 冷迁移, resiez, 以及热迁移都经过该函数。这里冷迁移实际使用_cold_migrate 进行处理。

def _cold_migrate(self, context, instance, flavor, filter_properties,clean_shutdown, request_spec, host_list):#  更新request_specrequest_spec = self._get_request_spec_for_cold_migrate(context, instance, flavor, filter_properties, request_spec)# 创建冷迁移任务 并执行
task = self._build_cold_migrate_task(context, instance, flavor,request_spec, clean_shutdown, host_list)
try:task.execute()
except exception.NoValidHost as ex:..................# 保存request_spec 调度参数if request_spec.obj_what_changed():request_spec.save()

该过程实际就是做异常处理,并保存请求参数。task.execute() 实际处理由_execute进行。定义在nova/conductor/tasks/migrate.py

6、nova/conductor/task/migrate.py

def _execute(self):# 将forced host 重置掉,以免影响调度self.request_spec.reset_forced_destinations()legacy_props = self.request_spec.to_legacy_filter_properties_dict()
scheduler_utils.setup_instance_group(self.context, self.request_spec)if not ('requested_destination' in self.request_spec and self.request_spec.requested_destination and 'host' in self.request_spec.requested_destination):
scheduler_utils.populate_retry(legacy_props,self.instance.uuid)
port_res_req, req_lvl_params = (self.network_api.get_requested_resource_for_instance(
self.context, self.instance.uuid))# 重新调度生成request_specself.request_spec.requested_resources = port_res_req
self.request_spec.request_level_params = req_lvl_params
self._set_requested_destination_cell(legacy_props).................if self.host_list is None:# 调度, 获取可用宿主机,并选用一台selection = self._schedule().................else:# 这是一个重新调度,将使用提供的备用主机,在host_list中作为目的主机。selection = self._reschedule()scheduler_utils.populate_filter_properties(legacy_props, selection)
(host, node) = (selection.service_host, selection.nodename)# rpc 调用目的节点prep_resizeself.compute_rpcapi.prep_resize(self.context, self.instance, self.request_spec.image,self.flavor, host, migration,request_spec=self.request_spec, filter_properties=legacy_props,node=node, clean_shutdown=self.clean_shutdown,host_list=self.host_list)

该步骤主要进行调度, 选取目标宿主机, 发送任务, 进行迁移。Nova-compute发起RPC请求后,Nova-compute的manager响应对应的方法

7、nova/compute/manager.py

def prep_resize(self, context, image, instance, flavor,request_spec, filter_properties, node,clean_shutdown, migration, host_list):# 校验参数if node is None:node = self._get_nodename(instance, refresh=True)instance_state = instance.vm_state
with self._error_out_instance_on_exception(context, instance, instance_state=instance_state),\errors_out_migration_ctxt(migration):# 通知状态self._send_prep_resize_notifications(context, instance, fields.NotificationPhase.START,flavor)try:scheduler_hints = self._get_scheduler_hints(filter_properties,request_spec)try:self._validate_instance_group_policy(context, instance,scheduler_hints)
except exception.RescheduledException as e:raise exception.InstanceFaultRollback(inner_exception=e)
self._prep_resize(context, image, instance,flavor, filter_properties,node, migration, request_spec,clean_shutdown)..............finally:self._send_prep_resize_notifications(context, instance, fields.NotificationPhase.END,flavor)

由_prep_resize 进行处理

def _prep_resize(self, context, image, instance, flavor, filter_properties, node,migration, request_spec, clean_shutdown=True,
):if not filter_properties:filter_properties = {}
if not instance.host:self._set_instance_obj_error_state(instance)msg = _('Instance has no source host')raise exception.MigrationError(reason=msg)“””判断是否为同一台主机,并检查 supports_migrate_to_same_host 是否支持迁移到同一台主机上“””
same_host = instance.host == self.host# 如果flavor id匹配,它就是迁移;否则调整
if same_host and flavor.id == instance['instance_type_id']:if not self.driver.capabilities.get('supports_migrate_to_same_host', False):raise exception.InstanceFaultRollback(inner_exception=exception.UnableToMigrateToSelf(instance_id=instance.uuid, host=self.host))# 更新instance 信息 并保存instance.new_flavor = flavor
vm_state = instance.vm_state
LOG.debug('Stashing vm_state: %s', vm_state, instance=instance)
instance.system_metadata['old_vm_state'] = vm_state
instance.save()............limits = filter_properties.get('limits', {})
allocs = self.reportclient.get_allocations_for_consumer(context, instance.uuid)# 开始迁移
with self.rt.resize_claim(context, instance, flavor, node, migration, allocs,image_meta=image, limits=limits,
) as claim:LOG.info('Migrating', instance=instance)# RPC转换到源主机以启动实际的大小调整/迁移。self.compute_rpcapi.resize_instance(context, instance, claim.migration, image,flavor, request_spec, clean_shutdown)

通过RPC回到源主机上由源主机的nova-compute服务完成迁移。

8、nova/compure/rpcapi.py

def resize_instance(self, ctxt, instance, migration, image, flavor,request_spec, clean_shutdown=True):version = '6.0'msg_args = {'instance': instance, 'migration': migration,'image': image,'flavor': flavor,'clean_shutdown': clean_shutdown,'request_spec': request_spec,}client = self.router.client(ctxt)if not client.can_send_version(version):version = self._ver(ctxt, '5.2')del msg_args['flavor']msg_args['instance_type'] = flavorif not client.can_send_version(version):msg_args.pop('request_spec')version = '5.0'cctxt = client.prepare(server=_compute_host(None, instance),version=version)cctxt.cast(ctxt, 'resize_instance', **msg_args)

通过源码分析,得到 compute_rpcapi.resize_instance 其实是调用instance原来主机上的过程,
进行正式的迁移。

9、nova/compute/manager.py

def resize_instance(self, context, instance, image,migration, flavor, clean_shutdown,request_spec):"""启动一个正在运行的实例迁移到另一个主机。这是从目标主机的' ' prep_resize ' '例程启动的并在源主机上运行。"""try:self._resize_instance(context, instance, image, migration,flavor, clean_shutdown, request_spec)except Exception:with excutils.save_and_reraise_exception():self._revert_allocation(context, instance, migration)

主要调用_resize_instance函数。

def _resize_instance(self, context, instance, image, migration, flavor,clean_shutdown, request_spec,
):instance_state = instance.vm_state
with self._error_out_instance_on_exception(context, instance, instance_state=instance_state), \errors_out_migration_ctxt(migration):# 获取instance 网络信息 该部分现在由NEUTRON 处理network_info = self.network_api.get_instance_nw_info(context, instance)# 更新状态migration.status = 'migrating'migration.save()instance.task_state = task_states.RESIZE_MIGRATINGinstance.save(expected_task_state=task_states.RESIZE_PREP)bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(context, instance.uuid)# 通知instance状态self._send_resize_instance_notifications(context, instance, bdms, network_info,fields.NotificationPhase.START)# 获取虚拟机的块设备信息block_device_info = self._get_instance_block_device_info(context, instance, bdms=bdms)timeout, retry_interval = self._get_power_off_values(instance, clean_shutdown)# 关闭并进行C盘迁移disk_info = self.driver.migrate_disk_and_power_off(context, instance, migration.dest_host,flavor, network_info,block_device_info,timeout, retry_interval)# 中断磁盘映射关系self._terminate_volume_connections(context, instance, bdms)
# 迁移虚拟机网络self.network_api.migrate_instance_start(context,instance,migration)# 保存虚拟机状态migration.status = 'post-migrating'migration.save()# 迁移完成instance.host = migration.dest_computeinstance.node = migration.dest_nodeinstance.old_flavor = instance.flavorinstance.task_state = task_states.RESIZE_MIGRATEDinstance.save(expected_task_state=task_states.RESIZE_MIGRATING)# 完成迁移, 完成迁移的收尾工作, 实例创建, 启动等self.compute_rpcapi.finish_resize(context, instance,migration, image, disk_info, migration.dest_compute,request_spec)
self._send_resize_instance_notifications(context, instance, bdms, network_info,fields.NotificationPhase.END)
self.instance_events.clear_events_for_instance(instance)

关键步骤:

1、关闭并进行磁盘迁移

2、网络迁移

3、迁移收尾工作, 创建实例, 启动实例等

核心步骤:

  1. 磁盘迁移:
disk_info = self.driver.migrate_disk_and_power_off(context, instance, migration.dest_host,flavor, network_info,block_device_info,timeout, retry_interval)

driver.migrate_disk_and_power_off过程·定义在nova/virt/libvirt/driver.py

def migrate_disk_and_power_off(self, context, instance, dest,flavor, network_info,block_device_info=None,timeout=0, retry_interval=0):LOG.debug("Starting migrate_disk_and_power_off",instance=instance)
ephemerals = driver.block_device_info_get_ephemerals(block_device_info)eph_size = (block_device.get_bdm_ephemeral_disk_size(ephemerals) orinstance.flavor.ephemeral_gb)# 磁盘参数校验root_down = flavor.root_gb < instance.flavor.root_gb
ephemeral_down = flavor.ephemeral_gb < eph_size
booted_from_volume = self._is_booted_from_volume(block_device_info)
if (root_down and not booted_from_volume) or ephemeral_down:reason = _("Unable to resize disk down.")raise exception.InstanceFaultRollback(exception.ResizeError(reason=reason)).................# 是否为共享存储, 如果不是则在目标宿主机上创建对应的目录if not shared_instance_path:try:self._remotefs.create_dir(dest, inst_base)except processutils.ProcessExecutionError as e:reason = _("not able to execute ssh command: %s") % eraise exception.InstanceFaultRollback(exception.ResizeError(reason=reason))# 关闭虚拟机
self.power_off(instance, timeout, retry_interval)
self.unplug_vifs(instance, network_info)# 获取磁盘映射关系
block_device_mapping = driver.block_device_info_get_mapping(block_device_info)
for vol in block_device_mapping:connection_info = vol['connection_info']self._disconnect_volume(context, connection_info, instance)
disk_info = self._get_instance_disk_info(instance, block_device_info)# 进行目录操作, 磁盘设置等
try:self._cleanup_failed_instance_base(inst_base_resize)
os.rename(inst_base, inst_base_resize)if shared_instance_path:dest = Nonefileutils.ensure_tree(inst_base)
on_execute = lambda process: \self.job_tracker.add_job(instance, process.pid)
on_completion = lambda process: \self.job_tracker.remove_job(instance, process.pid)...............# 将磁盘复制到目标节点
compression = info['type'] not in NO_COMPRESSION_TYPES
libvirt_utils.copy_image(from_path, img_path, host=dest,on_execute=on_execute,on_completion=on_completion,compression=compression)# 迁移disk.info 磁盘信息, 例如注入信息等src_disk_info_path = os.path.join(inst_base_resize, 'disk.info')
if os.path.exists(src_disk_info_path):dst_disk_info_path = os.path.join(inst_base, 'disk.info')libvirt_utils.copy_image(src_disk_info_path,dst_disk_info_path,host=dest, on_execute=on_execute,on_completion=on_completion)libvirt_utils.save_and_migrate_vtpm_dir(instance.uuid, inst_base_resize, inst_base, dest,on_execute, on_completion)
except Exception:with excutils.save_and_reraise_exception():self._cleanup_remote_migration(dest, inst_base,inst_base_resize,shared_instance_path)
return jsonutils.dumps(disk_info)
  1. 磁盘迁移完成后, 进行网络迁移。
  2. 网络迁移完成后,finish_resize。通过RPC到目标主机上完成虚拟机的Resize

10、nova/compute/rcpapi.py

def finish_resize(self, ctxt, instance, migration, image, disk_info, host,request_spec):.............cctxt = client.prepare(server=host, version=version)
cctxt.cast(ctxt, 'finish_resize', **msg_args)
  1. nova/compute/manager.py
def finish_resize(self, context, disk_info, image, instance,migration, request_spec):“””完成迁移过程。设置新传输的磁盘,并在其新的主机上打开实例。“””try:self._finish_resize_helper(context, disk_info, image, instance,migration, request_spec)except Exception:with excutils.save_and_reraise_exception():LOG.info('Deleting allocations for old flavor on source node ''%s after finish_resize failure. You may be able to ''recover the instance by hard rebooting it.',migration.source_compute, instance=instance)
self._delete_allocation_after_move(context, instance, migration)

通过调用._finish_resize_helper函数调用_finish_resize 。

def _finish_resize(self, context, instance, migration, disk_info,image_meta, bdms, request_spec):resize_instance = Falseold_instance_type_id = migration.old_instance_type_idnew_instance_type_id = migration.new_instance_type_idold_flavor = instance.flavorold_vm_state = instance.system_metadata.get('old_vm_state',vm_states.ACTIVE)if old_instance_type_id != new_instance_type_id:new_flavor = instance.new_flavor  # this is set in _prep_resizeself._set_instance_info(instance, new_flavor)for key in ('root_gb', 'swap', 'ephemeral_gb'):if old_flavor[key] != new_flavor[key]:resize_instance = Truebreakinstance.apply_migration_context()# 创建设置网络, 并完成网络迁移self.network_api.setup_networks_on_host(context, instance,migration.dest_compute)
provider_mappings = self._get_request_group_mapping(request_spec)self.network_api.migrate_instance_finish(context, instance, migration, provider_mappings)network_info = self.network_api.get_instance_nw_info(context, instance)instance.task_state = task_states.RESIZE_FINISHinstance.save(expected_task_state=task_states.RESIZE_MIGRATED)self._send_finish_resize_notifications(context, instance, bdms, network_info,fields.NotificationPhase.START)self._update_volume_attachments(context, instance, bdms)#  获取磁盘信息block_device_info = self._get_instance_block_device_info(context, instance, refresh_conn_info=True, bdms=bdms)power_on = old_vm_state != vm_states.STOPPEDallocations = self.reportclient.get_allocs_for_consumer(context, instance.uuid)['allocations']try:# 完成迁移self.driver.finish_migration(context, migration, instance,disk_info,  network_info,image_meta, resize_instance,allocations, block_device_info, power_on)except Exception:with excutils.save_and_reraise_exception():if old_instance_type_id != new_instance_type_id:self._set_instance_info(instance, old_flavor)self._complete_volume_attachments(context, bdms)migration.status = 'finished'migration.save()# 更新状态instance.vm_state = vm_states.RESIZEDinstance.task_state = Noneinstance.launched_at = timeutils.utcnow()instance.save(expected_task_state=task_states.RESIZE_FINISH)return network_info

finish_migration 定义在 virt/libvirt/driver.py

12、nova/virt/libvirt/driver.py

def finish_migration(self,context: nova_context.RequestContext,migration: 'objects.Migration',instance: 'objects.Instance',disk_info: str,network_info: network_model.NetworkInfo,image_meta: 'objects.ImageMeta',resize_instance: bool,allocations: ty.Dict[str, ty.Any],block_device_info: ty.Optional[ty.Dict[str, ty.Any]] = None,power_on: bool = True,
) -> None:# 在目标主机上完成迁移过程# 获取磁盘信息,并创建磁盘镜像block_disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,instance,image_meta,block_device_info)self._create_image(context, instance, block_disk_info['mapping'],block_device_info=block_device_info,ignore_bdi_for_swap=True,fallback_from_host=migration.source_compute)self._ensure_console_log_for_instance(instance)
gen_confdrive = functools.partial(self._create_configdrive, context, instance,InjectionInfo(admin_pass=None, network_info=network_info,files=None))for info in jsonutils.loads(disk_info):path = info['path']disk_name = os.path.basename(path)if (disk_name != 'disk.config' andinfo['type'] == 'raw' and CONF.use_cow_images):self._disk_raw_to_qcow2(info['path'])mdevs = self._allocate_mdevs(allocations)self._finish_migration_vtpm(context, instance)# 依据instance 信息生成 xml 文件
xml = self._get_guest_xml(context, instance, network_info,block_disk_info, image_meta,block_device_info=block_device_info,mdevs=mdevs)# 创建虚拟机guest = self._create_guest_with_network(context, xml, instance, network_info, block_device_info,power_on=power_on, vifs_already_plugged=True,post_xml_callback=gen_confdrive)
if power_on:timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_running,instance)timer.start(interval=0.5).wait()guest.sync_guest_time()
LOG.debug("finish_migration finished successfully.", instance=instance)

13、_create_guest_with_network创建虚拟机

_create_guest_and_network 在目标宿主机创建虚拟机, 如果原始状态为active则启动
该步骤在虚拟机创建中分析。

Nova组件源码分析之冷迁移与Resize相关推荐

  1. Openstack liberty 云主机迁移源码分析之在线迁移3

    这是在线迁移 源码分析的第三篇,Openstack liberty 云主机迁移源码分析之在线迁移2中分析了prepare准备阶段nova-compute的处理过程,本文将会分析execute执行阶段的 ...

  2. Ui学习笔记---EasyUI的EasyLoader组件源码分析

    Ui学习笔记---EasyUI的EasyLoader组件源码分析 技术qq交流群:JavaDream:251572072   1.问题1:为什么只使用了dialog却加载了那么多的js   http: ...

  3. element-ui button组件 radio组件源码分析整理笔记(一)

    Button组件 button.vue <template><buttonclass="el-button"@click="handleClick&qu ...

  4. element-ui input组件源码分析整理笔记(六)

    input 输入框组件 源码: <template><div :class="[type === 'textarea' ? 'el-textarea' : 'el-inpu ...

  5. alibaba sentinel限流组件 源码分析

    如何使用? maven引入: <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>s ...

  6. Element UI table组件源码分析

    本文章从如下图所示的最基本的table入手,分析table组件源代码.本人已经对table组件原来的源码进行削减,源码点击这里下载.本文只对重要的代码片段进行讲解,推荐下载代码把项目运行起来,跟着文章 ...

  7. 1月24日学习内容整理:Django的admin组件源码分析及流程

    一.单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用 ...

  8. linux内核源码剖析 博客,【Linux内存源码分析】页面迁移

    页面迁移其实是伙伴管理算法中的一部分,鉴于其特殊性,特地另行分析.它是2007年的时候,2.6.24内核版本开发时,新增碎片减少策略(the fragmentation reduction strat ...

  9. 转载:WeUI Picker组件--源码分析

    原文链接:WeUI Picker组件 源代码分析  https://www.cnblogs.com/haha1212/p/8393243.html 前言:由于最近做的一个移动端项目需要用到类似WeUI ...

最新文章

  1. Python3 中实现MATLAB中的点乘 即两列表对应元素相乘
  2. springmvc二十三:日期格式化
  3. wav音量和分贝转换关系_Permute 3 for mac(音视频格式转换器) 最新版
  4. java gc log调优_Java 开启 gc 日志
  5. MySQL数据库datetime类型不能为空值的问题
  6. Syncthing vs BitTorrent Sync
  7. 武魂金刚少林加点、装备坐骑及江湖技能选择
  8. TestNG在接口测试中的应用
  9. Panabit安装snmp插件
  10. 151202storyboard中, 设置子控件和父控件的高宽比
  11. python手机版下载-手机python下载
  12. 剪映专业版 for Mac(视频剪辑工具)
  13. Mobileye超详细研报:高逼格ADAS的内在修养
  14. ECharts 地图绘制
  15. python-----列表生成式和列表生成器表达
  16. Linux脚本输出99乘法表,利用shell脚本各种循环语句输出九九乘法表
  17. Apache Ranger 1.1.0 用户指南
  18. 代码review原则
  19. 暴力破解及端口扫描详解
  20. python安装教程——最新详细版

热门文章

  1. (转)Kangle配置文件
  2. Python scrapy学习之爬取2k唯美壁纸详细过程笔记及讲解
  3. 【Java、Redis】通过中心经纬度与半径获取范围内的结果集(类似附近的人)
  4. send 命令 linux,linux的send命令
  5. 即将步入研究生,有什么建议?
  6. 【翻译】EAST: An Efficient and Accurate Scene Text Detector
  7. Python-Flask开发微电影网站(一)
  8. Python-Flask开发微电影网站(三)
  9. u盘格式化后数据能恢复吗?
  10. 汇编语言机器语言c语言区别,高级语言和汇编语言_机器语言的区别