xxl-job源码分析

xxl-job源码分析

xxl-job

系统说明

安装

安装部署参考文档:分布式任务调度平台xxl-job

功能

定时调度、服务解耦、灵活控制跑批时间(停止、开启、重新设定时间、手动触发)

XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用

概念

执行器列表:一个执行器是一个项目

任务:一个任务是一个项目中的 JobHandler

一个xxl-job服务可以有多个执行器(项目),一个项目下可以有多个任务(JobHandler),他们是如何关联的?

页面操作:

  1. 在管理平台可以新增执行器(项目)
  2. 在任务列表可以指定执行器(项目)下新增多个任务(JobHandler)

代码操作:

  1. 项目配置中增加 xxl.job.executor.appname = "执行器名称"
  2. 在实现类中增加 @JobHandler(value="xxl-job-demo") 注解,并继承 IJobHandler

架构图

1072053-20190920094456690-1804945154.png

抛出疑问

  1. 调度中心启动过程?
  2. 执行器启动过程?
  3. 执行器如何注册到调度中心?
  4. 调度中心怎么调用执行器?
  5. 集群调度时如何控制一个任务在该时刻不会重复执行
  6. 集群部署应该注意什么?

系统分析

执行器依赖jar包

com.xuxueli:xxl-job-core:2.1.0

com.xuxueli:xxl-registry-client:1.0.2

com.xuxueli:xxl-rpc-core:1.4.1

调度中心启动过程

// 1. 加载 XxlJobAdminConfig,adminConfig = this
XxlJobAdminConfig.java// 启动过程代码
@Component
public class XxlJobScheduler implements InitializingBean, DisposableBean {private static final Logger logger = LoggerFactory.getLogger(XxlJobScheduler.class);@Overridepublic void afterPropertiesSet() throws Exception {// init i18ninitI18n();// admin registry monitor run// 2. 启动注册监控器(将注册到register表中的IP加载到group表)/ 30执行一次JobRegistryMonitorHelper.getInstance().start();// admin monitor run// 3. 启动失败日志监控器(失败重试,失败邮件发送)JobFailMonitorHelper.getInstance().start();// admin-server// 4. 初始化RPC服务initRpcProvider();// start-schedule// 5. 启动定时任务调度器(执行任务,缓存任务)JobScheduleHelper.getInstance().start();logger.info(">>>>>>>>> init xxl-job admin success.");}......
}

执行器启动过程

@Override
public void start() throws Exception {// init JobHandler Repository// 将执行 JobHandler 注册到缓存中 jobHandlerRepository(ConcurrentMap)initJobHandlerRepository(applicationContext);// refresh GlueFactory// 刷新GLUEGlueFactory.refreshInstance(1);// super start// 核心启动项super.start();
}public void start() throws Exception {// 初始化日志路径 // private static String logBasePath = "/data/applogs/xxl-job/jobhandler";XxlJobFileAppender.initLogPath(this.logPath);// 初始化注册中心列表 (把注册地址放到 List)this.initAdminBizList(this.adminAddresses, this.accessToken);// 启动日志文件清理线程 (一天清理一次)// 每天清理一次过期日志,配置参数必须大于3才有效JobLogFileCleanThread.getInstance().start((long)this.logRetentionDays);// 开启触发器回调线程TriggerCallbackThread.getInstance().start();// 指定端口this.port = this.port > 0 ? this.port : NetUtil.findAvailablePort(9999);// 指定IPthis.ip = this.ip != null && this.ip.trim().length() > 0 ? this.ip : IpUtil.getIp();// 初始化RPC 将执行器注册到调度中心 30秒一次this.initRpcProvider(this.ip, this.port, this.appName, this.accessToken);
}

执行器注册到调度中心

执行器

// 注册执行器入口
XxlJobExecutor.java->initRpcProvider()->xxlRpcProviderFactory.start();// 开启注册
XxlRpcProviderFactory.java->start();// 执行注册
ExecutorRegistryThread.java->start();
// RPC 注册代码
for (AdminBiz adminBiz: XxlJobExecutor.getAdminBizList()) {try {ReturnT<String> registryResult = adminBiz.registry(registryParam);if (registryResult!=null && ReturnT.SUCCESS_CODE == registryResult.getCode()) {registryResult = ReturnT.SUCCESS;logger.debug(">>>>>>>>>>> xxl-job registry success, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});break;} else {logger.info(">>>>>>>>>>> xxl-job registry fail, registryParam:{}, registryResult:{}", new Object[]{registryParam, registryResult});}} catch (Exception e) {logger.info(">>>>>>>>>>> xxl-job registry error, registryParam:{}", registryParam, e);}}

调度中心

// RPC 注册服务
AdminBizImpl.java->registry();

数据库

1072053-20190920094906422-637038043.png

1072053-20190920094851139-1515543303.png

调度中心调用执行器

/* 调度中心执行步骤 */
// 1. 调用执行器
XxlJobTrigger.java->runExecutor();// 2. 获取执行器
XxlJobScheduler.java->getExecutorBiz();// 3. 调用
ExecutorBizImpl.java->run();/* 执行器执行步骤 */
// 1. 执行器接口
ExecutorBiz.java->run();// 2. 执行器实现
ExecutorBizImpl.java->run();// 3. 把jobInfo 从 jobThreadRepository (ConcurrentMap) 中获取一个新线程,并开启新线程
XxlJobExecutor.java->registJobThread();// 4. 保存到当前线程队列
JobThread.java->pushTriggerQueue();// 5. 执行
JobThread.java->handler.execute(triggerParam.getExecutorParams());

调度中心(Admin)

实现 org.springframework.beans.factory.InitializingBean类,重写 afterPropertiesSet 方法,在初始化bean的时候都会执行该方法

DisposableBean spring停止时执行

结束加载项

  1. 停止定时任务调度器(中断scheduleThread,中断ringThread)
  2. 停止触发线程池(JobTriggerPoolHelper)
  3. 停止注册监控器(registryThread)
  4. 停止失败日志监控器(monitorThread)
  5. 停止RPC服务(stopRpcProvider)

手动执行方式

JobInfoController.java

@RequestMapping("/trigger")
@ResponseBody
//@PermissionLimit(limit = false)
public ReturnT<String> triggerJob(int id, String executorParam) {// force cover job paramif (executorParam == null) {executorParam = "";}JobTriggerPoolHelper.trigger(id, TriggerTypeEnum.MANUAL, -1, null, executorParam);return ReturnT.SUCCESS;
}

定时调度策略

调度策略执行图

1072053-20190920094826086-2070926333.png

调度策略源码

JobScheduleHelper.java->start();

路由策略

第一个

固定选择第一个机器

ExecutorRouteFirst.java->route();
最后一个

固定选择最后一个机器

ExecutorRouteLast.java->route();
轮询

随机选择在线的机器

ExecutorRouteRound.java->route();private static int count(int jobId) {// cache clearif (System.currentTimeMillis() > CACHE_VALID_TIME) {routeCountEachJob.clear();CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;}// count++Integer count = routeCountEachJob.get(jobId);count = (count==null || count>1000000)?(new Random().nextInt(100)):++count;  // 初始化时主动Random一次,缓解首次压力routeCountEachJob.put(jobId, count);return count;
}
随机

随机获取地址列表中的一个

ExecutorRouteRandom.java->route();
一致性HASH

一个job通过hash算法固定使用一台机器,且所有任务均匀散列在不同机器

ExecutorRouteConsistentHash.java->route();public String hashJob(int jobId, List<String> addressList) {// ------A1------A2-------A3------// -----------J1------------------TreeMap<Long, String> addressRing = new TreeMap<Long, String>();for (String address: addressList) {for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {long addressHash = hash("SHARD-" + address + "-NODE-" + i);addressRing.put(addressHash, address);}}long jobHash = hash(String.valueOf(jobId));// 取出键值 >= jobHashSortedMap<Long, String> lastRing = addressRing.tailMap(jobHash);if (!lastRing.isEmpty()) {return lastRing.get(lastRing.firstKey());}return addressRing.firstEntry().getValue();
}
最不经常使用

使用频率最低的机器优先被选举
把地址列表加入到内存中,等下次执行时剔除无效的地址,判断地址列表中执行次数最少的地址取出
频率、次数

ExecutorRouteLFU.java->route();public String route(int jobId, List<String> addressList) {// cache clearif (System.currentTimeMillis() > CACHE_VALID_TIME) {jobLfuMap.clear();CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;}// lfu item initHashMap<String, Integer> lfuItemMap = jobLfuMap.get(jobId);     // Key排序可以用TreeMap+构造入参Compare;Value排序暂时只能通过ArrayList;if (lfuItemMap == null) {lfuItemMap = new HashMap<String, Integer>();jobLfuMap.putIfAbsent(jobId, lfuItemMap);   // 避免重复覆盖}// put newfor (String address: addressList) {if (!lfuItemMap.containsKey(address) || lfuItemMap.get(address) >1000000 ) {// 0-n随机数,包括0不包括nlfuItemMap.put(address, new Random().nextInt(addressList.size()));  // 初始化时主动Random一次,缓解首次压力}}// remove oldList<String> delKeys = new ArrayList<>();for (String existKey: lfuItemMap.keySet()) {if (!addressList.contains(existKey)) {delKeys.add(existKey);}}if (delKeys.size() > 0) {for (String delKey: delKeys) {lfuItemMap.remove(delKey);}}/*********************** 优化 START ***********************/// 优化  remove old部分Iterator<String> iterable = lfuItemMap.keySet().iterator();while (iterable.hasNext()) {String address = iterable.next();if (!addressList.contains(address)) {iterable.remove();}}/*********************** 优化 START ***********************/// load least userd count address// 从小到大排序List<Map.Entry<String, Integer>> lfuItemList = new ArrayList<Map.Entry<String, Integer>>(lfuItemMap.entrySet());Collections.sort(lfuItemList, new Comparator<Map.Entry<String, Integer>>() {@Overridepublic int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {return o1.getValue().compareTo(o2.getValue());}});Map.Entry<String, Integer> addressItem = lfuItemList.get(0);String minAddress = addressItem.getKey();addressItem.setValue(addressItem.getValue() + 1);return addressItem.getKey();
}
最近最久未使用

最久未使用的机器优先被选举
用链表的方式存储地址,第一个地址使用后下次该任务过来使用第二个地址,依次类推(PS:有点类似轮询策略)
与轮询策略的区别:

  1. 轮询策略是第一次随机找一台机器执行,后续执行会将索引加1取余
  2. 轮询策略依赖 addressList 的顺序,如果这个顺序变了,索引到下一次的机器可能不是期望的顺序
  3. LRU算法第一次执行会把所有地址加载进来并缓存,从第一个地址开始执行,即使 addressList 地址顺序变了也不影响
    次数
ExecutorRouteLRU.java->route();public String route(int jobId, List<String> addressList) {// cache clearif (System.currentTimeMillis() > CACHE_VALID_TIME) {jobLRUMap.clear();CACHE_VALID_TIME = System.currentTimeMillis() + 1000*60*60*24;}// init lruLinkedHashMap<String, String> lruItem = jobLRUMap.get(jobId);if (lruItem == null) {/*** LinkedHashMap*      a、accessOrder:ture=访问顺序排序(get/put时排序);false=插入顺序排期;*      b、removeEldestEntry:新增元素时将会调用,返回true时会删除最老元素;可封装LinkedHashMap并重写该方法,比如定义最大容量,超出是返回true即可实现固定长度的LRU算法;*/lruItem = new LinkedHashMap<String, String>(16, 0.75f, true);jobLRUMap.putIfAbsent(jobId, lruItem);}/*********************** 举个例子 START ***********************/// 如果accessOrder为true的话,则会把访问过的元素放在链表后面,放置顺序是访问的顺序 // 如果accessOrder为flase的话,则按插入顺序来遍历LinkedHashMap<String, String> lruItem = new LinkedHashMap<String, String>(16, 0.75f, true);jobLRUMap.putIfAbsent(1, lruItem);lruItem.put("192.168.0.1", "192.168.0.1");lruItem.put("192.168.0.2", "192.168.0.2");lruItem.put("192.168.0.3", "192.168.0.3");String eldestKey = lruItem.entrySet().iterator().next().getKey();String eldestValue = lruItem.get(eldestKey);System.out.println(eldestValue + ": " + lruItem);eldestKey = lruItem.entrySet().iterator().next().getKey();eldestValue = lruItem.get(eldestKey);System.out.println(eldestValue + ": " + lruItem);// 输出结果:192.168.0.1: {192.168.0.2=192.168.0.2, 192.168.0.3=192.168.0.3, 192.168.0.1=192.168.0.1}
192.168.0.2: {192.168.0.3=192.168.0.3, 192.168.0.1=192.168.0.1, 192.168.0.2=192.168.0.2}/*********************** 举个例子 END ***********************/// put newfor (String address: addressList) {if (!lruItem.containsKey(address)) {lruItem.put(address, address);}}// remove oldList<String> delKeys = new ArrayList<>();for (String existKey: lruItem.keySet()) {if (!addressList.contains(existKey)) {delKeys.add(existKey);}}if (delKeys.size() > 0) {for (String delKey: delKeys) {lruItem.remove(delKey);}}// loadString eldestKey = lruItem.entrySet().iterator().next().getKey();String eldestValue = lruItem.get(eldestKey);return eldestValue;
}
故障转移

按照顺序依次进行心跳检测,第一个心跳检测成功的机器选定为目标执行器并发起调度

ExecutorRouteFailover.java->route();
忙碌转移

按照顺序依次进行空闲检测,第一个空闲检测成功的机器选定为目标执行器并发起调度

ExecutorRouteBusyover.java->route();
分片广播

广播触发对应集群中所有机器执行一次任务,同时传递分片参数;可根据分片参数开发分片任务

阻塞处理策略

为了解决执行线程因并发问题、执行效率慢、任务多等原因而做的一种线程处理机制,主要包括 串行、丢弃后续调度、覆盖之前调度,一般常用策略是串行机制

ExecutorBlockStrategyEnum.javaSERIAL_EXECUTION("Serial execution"), // 串行
DISCARD_LATER("Discard Later"), // 丢弃后续调度
COVER_EARLY("Cover Early"); // 覆盖之前调度ExecutorBizImpl.java->run();// executor block strategy
if (jobThread != null) {ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(triggerParam.getExecutorBlockStrategy(), null);if (ExecutorBlockStrategyEnum.DISCARD_LATER == blockStrategy) {// discard when runningif (jobThread.isRunningOrHasQueue()) {return new ReturnT<String>(ReturnT.FAIL_CODE, "block strategy effect:"+ExecutorBlockStrategyEnum.DISCARD_LATER.getTitle());}} else if (ExecutorBlockStrategyEnum.COVER_EARLY == blockStrategy) {// kill running jobThreadif (jobThread.isRunningOrHasQueue()) {removeOldReason = "block strategy effect:" + ExecutorBlockStrategyEnum.COVER_EARLY.getTitle();jobThread = null;}} else {// just queue trigger}
}
单机串行

对当前线程不做任何处理,并在当前线程的队列里增加一个执行任务

丢弃后续调度

如果当前线程阻塞,后续任务不再执行,直接返回失败

覆盖之前调度

创建一个移除原因,新建一个线程去执行后续任务

运行模式

ExecutorBizImpl.java->run();
BEAN

java里的bean对象

GLUE(Java)

利用java的反射机制,通过代码字符串生成实体类

IJobHandler originJobHandler = GlueFactory.getInstance().loadNewInstance(triggerParam.getGlueSource());GroovyClassLoader
GLUE(Shell Python PHP Nodejs PowerShell)

按照文件命名规则创建一个执行脚本文件和一个日志输出文件,通过脚本执行器执行

失败重试次数

任务失败后记录到 xxl_job_log 中,由失败监控线程查询处理失败的任务且失败次数大于0,继续执行

任务超时时间

把超时时间给 triggerParam 触发参数,在调用执行器的任务时超时时间,有点类似HttpClient的超时时间

执行器(Exector)

  1. 注册自己的机器地址

  2. 注册项目中的 JobHandler

  3. 提供被调度中心调用的接口

    public interface ExecutorBiz {/*** 供调度中心检测机器是否存活** beat* @return*/public ReturnT<String> beat();/*** 供调度中心检测机器是否空闲** @param jobId* @return*/public ReturnT<String> idleBeat(int jobId);/*** kill* @param jobId* @return*/public ReturnT<String> kill(int jobId);/*** log* @param logDateTim* @param logId* @param fromLineNum* @return*/public ReturnT<LogResult> log(long logDateTim, long logId, int fromLineNum);/*** 执行触发器* * @param triggerParam* @return*/public ReturnT<String> run(TriggerParam triggerParam);}

总结

1072053-20190920094739862-1959701255.png

学到了什么

  1. 算法(LFU、LRU、轮询等)
  2. JDK动态代理对象(详细研究)
  3. 用到了Netty(详细研究)
  4. FutureTask
  5. GroovyClassLoader
posted on 2019-09-20 09:43 小猴子先生 阅读(...) 评论(...) 编辑 收藏

转载于:https://www.cnblogs.com/guoyinli/p/11555035.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

定制jQuery File Upload为微博式单文件上传

原文链接&#xff1a;http://avnpc.com/pages/single-file-upload-component-by-jquery-file-upload jQuery File Upload是一个非常优秀的上传组件&#xff0c;主要使用了XHR作为上传方式&#xff0c;并且利用了相当多的现代浏览器功能&#xff0c;所以可以实现诸如批量上传、超…

vb趣味编程弹球小游戏_最好玩的微信小游戏集合,总有一款是你没玩过的

大家好&#xff0c;这里是小雅龙生活趣味时间&#xff0c;自从17年微信推出小游戏程序以来&#xff0c;微信小游戏行业可谓是炙手可热&#xff0c;知道2019年不断有许许多多的微信小游戏如雨后春笋般的生根发芽。下面就由我带大家来看看今年最好玩&#xff0c;最受欢迎的微信小…

開發MOSS2007 Masterpage的一些經驗

一直在做MOSS平臺的Masterpage開發,碰到很多的問題,總結了一些經驗,特此記錄: masterpage的所有的ContentPlaceholder詳細解釋見以下網址:http://www.cnblogs.com/WinYoung/archive/2007/06/25/791766.html 1.如果應用masterpage以后IE狀態欄出現""網頁指令碼錯誤訊息…

Golang——垃圾回收GC(2)

1 垃圾回收中的重要概念 1.1 定义 In computer science, garbage collection (GC) is a form of automatic memory management. The garbage collector, or just collector, attempts to reclaim garbage, or memory occupied by objects that are no longer in use by the pro…

java gui框架_推荐!程序员整理的Java资源大全

构建这里搜集了用来构建应用程序的工具。Apache Maven&#xff1a;Maven使用声明进行构建并进行依赖管理&#xff0c;偏向于使用约定而不是配置进行构建。Maven优于Apache Ant。后者采用了一种过程化的方式进行配置&#xff0c;所以维护起来相当困难。Gradle&#xff1a;Gradle…

帆软报表(finereport)控件背景色更改

setTimeout(function() {$(.fr-trigger-btn-up).css({"background-color": "#003399" });}, 100); 转载于:https://www.cnblogs.com/Williamls/p/11571586.html

开心网分析,师从“中国缘”

作者&#xff1a;麦田   一&#xff0c;师从“中国缘” 开心网从08年“爆发”之后&#xff0c;网上出现很多评论文章。几乎100%的评论文章都谈到了开心网“不可思议”的爆发增长速度&#xff0c;比如几个月就进入了alexa前500等等。但是&#xff0c;几乎没有一篇文章提到“开心…

HTML5+CSS3+JQuery1.9 输入框切换和Div失焦模拟

Div失焦原理&#xff1a;判断document点击对象是否在Div容器以内&#xff0c;否则触发事件 需要脚本&#xff1a;jquery-1.9.1.js 下载地址&#xff1a;http://download.csdn.net/detail/dmtnewtons/5807757 <!DOCTYPE> <html> <head> <meta http-equ…

资本冬天已至,开发者却可以着眼未来

云&#xff0c;在国内外都已成为软件开发者的首选服务。纵观历史&#xff0c;在云计算发展的这些年里&#xff0c;不管云上做了多少产品和服务&#xff0c;其实都离不开云最本质的价值体系&#xff1a;自服务、高弹性、按需提供、免运维&#xff0c;这些特性也让云服务天然成为…

mybatis 大于_酸爽!IDEA 中这么玩 MyBatis,让编码速度飞起!

作者&#xff1a;Orsoncnblogs.com/java-class/p/6237564.html1. 搭建 MyBatis Generator 插件环境a. 添加插件依赖 pom.xmlb. 配置文件 generatorConfig.xmlc. 数据库配置文件 jdbc.propertiesd. 配置插件启动项2.项目实战a. 比如在一个项目 我们要删除某个小组下某个用户的信…

Java的三种代理模式完整源码分析

Java的三种代理模式&完整源码分析 Java的三种代理模式&完整源码分析 参考资料&#xff1a; 博客园-Java的三种代理模式 简书-JDK动态代理-超详细源码分析 [博客园-WeakCache缓存的实现机制](https://www.cnblogs.com/liuyun1995/p/8144676.html) 静态代理 静态代理在使…

scatter函数_matplotlib.pyplot常用函数scatter讲解大全(三)

前言这篇文章再来总结一个常用画图函数scatter-散点图。参数常用参数示例import matplotlib.pyplot as plt import numpy as np#导入需要的包 datanp.random.multivariate_normal([0,1],[[1,0],[0,1]],200)#准备数据&#xff0c;二维正态分布plt.rcParams["axes.unicode_m…

如何彻底卸载MySQL

本文摘自&#xff1a;http://www.heiqu.com/show-64764-1.html 内容为&#xff1a; 由于安装MySQL的时候&#xff0c;疏忽没有选择底层编码方式&#xff0c;采用默认的ASCII的编码格式&#xff0c;于是接二连三的中文转换问题随之而来&#xff0c;就想卸载了重新安装MYSQL&…

vue-cli项目模板的一些思考

之前有个想法&#xff0c;就是要利用vue写一套ui。然后当时也没有搞清楚到底怎么写。 几经周转吧&#xff0c;通过付费的方式在gitbook上面找到了答案。 找到答案之后再看我们正在开发的项目&#xff0c;看伙伴写的代码&#xff0c;突然发现完全可以按照写ui组件库的方式调整目…

flex基于svn协同开发

想做一个游戏&#xff0c;正好有人陪我做。于是想到用flex来协同开发。本来是想使用cvs&#xff0c;可是结果捣鼓了半天&#xff0c;也没个结果——估计是最近没怎么看电影&#xff0c;IQ降下来了。于是改用svn。 参考资料&#xff1a;http://www.flashmagazine.com/tutorials/…

cookie与session详解

session与cookie是什么?session与cookie属于一种会话控制技术.常用在身份识别&#xff0c;登录验证&#xff0c;数据传输等.举个例子&#xff0c;就像我们去超市买东西结账的时候&#xff0c;我们要拿出我们的会员卡才会获取优惠.这时候&#xff0c;我们怎么识别这个会员卡真实…

c++万能头文件_初学Python,与C对比

✎背景学了一学年的C的基础&#xff0c;下学年开课Python&#xff0c;现在正在自学中...C也不是不学了&#xff0c;而是之前买了一本《CPrimer》在学校里&#xff0c;就准备先学一下Python&#xff0c;下学期利用自由时间接着学习C。这里分析了一下二者的优缺点&#xff0c;供大…

listen(int fd, int backlog)中的backlog含义

1. listen(int fd, int backlog)中的backlog不能限制连接数量??? http://bbs.chinaunix.net/viewthread.php?tid870564 backlog应该是未完成3次握手连接和已完成3次握手而未被accept的两对列之和.不知道我说的对不? 如果要控制连接数量,是不是要自己编码控制...下面的可以…

本地无法启动MySQL服务,报的错误:1067,进程意外终止---解决

原文链接&#xff1a;http://blog.csdn.net/shenhonglei1234/article/details/5928873 在本地计算机无法启动MYSQL服务错误1067进程意外终止 这种情况一般是my.ini文件配置出错了 首先找到这个文件&#xff1a; 默认安装路径 C:/Program Files/MySQL/MySQL Server 5.1/my.ini …

一篇文章助你理解Python3中字符串编码问题

前几天给大家介绍了unicode编码和utf-8编码的理论知识&#xff0c;以及Python2中字符串编码问题&#xff0c;没来得及上车的小伙伴们可以戳这篇文章&#xff1a;浅谈unicode编码和utf-8编码的关系和一篇文章助你理解Python2中字符串编码问题。下面在Python3环境中进行代码演示&…