有多台笔记本电脑,手机,如何统合这些计算资源? 比如我在一台电脑上安装QQ这个软件 另一台电脑上不需要再安装这个软件 也可以使用QQ这个程序也就是说同样的资源是需要在一台电脑上即可 (我尝试过网络存储,但是效果不太好,首先太慢了,其次需要把每台电脑灯配置成服务器) 另外一个要求 对于一个计算任务把它分配给不同电脑上的CPU进行计算 顺便问一下企业是如何实现多个CPU资源充分利用
在求职的道路上,简历是我们展示个人能力、经验和潜力最直观也是最重要的工具之一。1、五大常见错误,千万要小心!(1)一份简历,应聘多样岗位不同岗位对雇员的要求是不一样的。通用的简历,无法突出你与目标岗位的关联性,反而会降低竞争力。要基于每个不同岗位的需求,调整简历内容,突出与该职位相关的技能和经验。 (2)模板滥用,配色缺乏审美高颜值的简历是求职的有力敲门砖,但滥用模板可能会导致配色混乱、排版杂乱,甚至是逻辑混乱。 简历应选择简洁、专业的模板,并保持一致的配色方案。同时,注意选择合适的颜色和排版,以给HR营造专业、清晰的印象。 (3)个人信息,不必过于暴露虽然简历需要提供足够的个人信息,但不要暴露过多不必要的隐私信息,比如:婚姻状况、家庭情况、身份证号等。要注意保护个人隐私,只提供与求职相关的信息,比如:联系方式、教育背景、科研经历、志愿活动和工作经验等。(4)经历杂乱,能力不够聚焦大家可能会有很多个人经历,但呈现在简历中,应该有舍有得、清晰明了、有逻辑地呈现。要避免杂乱地列举经历,基于目标岗位的能力要求,选择恰当的个人经历,并用相应的话术突出在这个经历中你的某几项与目标岗位需求匹配的能力,且需要为每个经历提供简洁的描述,强调你做事的结果和贡献。 (5)篇幅过长,错字病句标点如果简历篇幅过长并且逻辑性不强,可能会导致关键信息被埋没。要突出关键信息,避免在简历中包含冗长的段落和不必要的细节。一般情况,应届毕业生用一整页A4纸,大约写800-1000字即可。 此外,拼写、错字、病句、标点错误,在简历中非常常见,这是非常严重的低级错误,很容易被雇主判断为态度问题。 写完简历之后,最好出声音读一遍,仔细校对。这些小错误,可能会给雇主留下不重视、不专业、不踏实、不仔细、不够关注细节的不好印象。 2、如何能让自己的简历脱颖而出?(1)STAR原则S(Situation)交代事件的背景,为什么会去做这件事?T(Task)自己承担的任务是什么,怎样在事情的背景下明确自己的任务。A(Action)自己都完成了些什么,为了完成任务,做了哪些事,为什么要这么做。R(Result)最终获得了何种成果?行动中收获了什么,有没有完成目标。我们来看一个例子: 大家是不是觉得写到这里就完成了呢?如果希望提高简历通过率,我们还可以继续对内容进行优化,不仅让内容符合STAR原则,还可以让内容更具备条理性。以下是进一步的优化结果: 那么STAR原则只能用在简历经历的描述吗?当然不是!在面试时,面试官可能就某段经历,请应聘者展开讲述。很多同学依然会使用STAR原则来描述,但这样的描述平铺直叙,缺乏吸引力。此时完全可以直奔主题,对个人经历进行先抑后扬的故事化重组。 这个时候STAR原则就可以转为CAR原则(Chagllenge挑战、Action行动、Result成果)。例如:在XX基金公司任职期间,作为技术主要负责人我曾遇到一次重大挑战,当时公司B端微信小程序版本大迭代,不仅需要参与并把控前端开发事务,而且还需要整体对接后端开发人员及产品业务人员(Challenge挑战),于是我们几个部门负责人就一起商量,使用微信小程序开发框架,搭建项目架构,根据设计和产品需求,参与需求分析和功能设计,参与后端开发人员进行接口对接,实现数据的交互和展示(Action行动),最后完善小程序各项功能,提升用户体验,用户满意度比原来提升 12%(Result成果)。我觉得这件事既锻炼了我的团队协作能力,更让我对自己的潜力有十足的信心。(2)利用视觉化的方法A.数据与经历的可视化 在简历中使用数据体现经历,可以更加直观地展示经验和能力。 比如,你运营了一个公众号,就可以写,在校期间,运营什么公众号几个月,发表原创文章多少,原创字数累计多少,浏览量多少,用户新增多少,用户完读率多少,公转私多少,还可以进一步增加同比环比数据。一定要学会挖掘数据化的成果,这不仅可以让HR更快地了解你的成就,还能使你的简历更具备说服力。B. 图像化的处理在设计简历时,合理运用颜色、图标和排版,能够使你的简历更具吸引力。但要注意保持简洁和专业,避免过多的花哨元素。一般是选择一个主色调,并在整个简历中保持一致,以营造统一的视觉效果。整个页面所有色彩元素最多不要超过三种。此外,图标可以用来突出你的技能或特长。 比如,如果你擅长多种编程语言,可以用相应的图标来表示每种语言的熟练程度。这样,HR可以在短时间内快速了解你的技能结构。C. 有效利用模板选择一个干净、简洁但又有个性的模板,能够帮助你更好地展示个人信息。要适度地进行个性化调整,以突出你的独特之处。 注意!不要完全套用模板哦~需要构建自己的简历逻辑,将你的职业优势关键信息放置在显眼位置,并按照你自己的思路排布简历,不要简单地把自己的信息直接安到某个模板里。排列逻辑可以是时间轴倒叙,最近的工作、实习放在最前面,依次往后排,方便雇主尽快清晰的看到你现在的经历与匹配度;可以是按类别,比如按照实习、校园工作、科研经历、志愿活动这样排列;也可以是按能力排列,比如按照专业能力、综合能力这样排列。这样做,HR在浏览你的简历时,可以快速获取到你想要传达的最重要的信息。以上这些你都学会了吗?赶快按照上面的步骤和方法修改你的简历吧!如果你对优化简历仍有疑问,可以寻找万码小助手的帮助~ 祝大家在求职过程中取得成功!
“你对我们公司的行业有什么了解?”“没了解,第一次接触。” “你对我们公司有什么了解?”“没了解,是你们叫我来面试的。” “你对我们的岗位有什么了解?”“没了解,应该和我之前做的差不多吧。”日常招聘过程中,这种精准踩在面试官雷区的情况并不少见,今天就来梳理盘点,那些在面试官雷区拼命蹦跶的情形。 初级踩雷:我对贵司一无所知如前文所示,前来面试不提前了解公司情况,无疑是精准踩在面试官的雷区。有的候选人还精心打扮,在个人的形象、着装方面大做文章,固然会让人眼前一亮,但是基本知识都不提前准备的,真的会很减分。哪怕是没有提前认真准备、了解,至少招聘网站的公司介绍、公司官网、岗位JD看一眼也不至于一问三不知。 中级踩雷:我的情况如简历所示/十分钟的自我介绍大家都知道,面试的第一个问题基本都是自我介绍。自我介绍是方便双方进入面试情景的重要环节,是给候选人缓冲时间,也是给面试官缓冲时间。不是所有面试官都会提前看简历,对新晋面试官而言,需要一点时间了解人选的基本情况;也不是所有面试官都需要看简历,很多资深面试官已经自成体系,能经过前期面试的人员已经满足基本条件,后面轮次的面试官不太关注简历写的内容了。最好的自我介绍是1-3分钟,避免太短双方没有进入状态,又避免太长让面试官听的乏味。 高级踩雷:我有很多offer,但也想看看贵司机会 也许求职者想通过多offer表现自己的优秀,通过放弃offer表现自己的加入意愿,但是这种行为却成功踩在面试官的雷区,与工作机会失之交臂。用人企业可能会对候选人心存顾虑:”他还在职,拿了十几个offer,每个offer面试三轮,是不是上班时间去面试了?还是打着出差的名义去见下家?“”他拿了那么多offer,还没定下来,对自己的认知和定位,是否有问题?一个关键岗位的人选连自己的定位都摸不准,把业务交给他不放心。“”与其把机会给一个不一定会来的人选,不如给确定性强的更合适。“ 究极踩雷:我和XX很熟,他的业绩还不如我有一些候选人,满嘴爬火车,张口就来。这也有几种情况:第一种是刚好撞到了面试官枪口的。比如面试官刚好来自于候选人目前所在的公司或曾经任职的公司,问及公司的一些情况时,候选人侃侃而谈,却风马牛不相及,直接被否掉。第二种是掩耳盗铃式。简历可能有水分或者完全造假,问及当时情况时就说“涉及公司保密信息,我不能透露”,“事情过的有点久了,虽然是我主导的,但我记不大清楚了”。第三种是对面试官的背调能力毫不知情,问及是否可以背调时还镇定自若,却不知道还没走出会议室就已经完成了背调,所谓的和人很熟,业绩很好,全都被调查得一览无遗。 不诚信,品德有问题的候选人,不管再怎么伪装,也只剩下淘汰的结局。 面试是个技术活,不是张张嘴聊聊天就是面试;而面试过程中,也确实会遇到很多候选人踩雷的行为。这些行为的之所以会成为雷区,折射的是面试官根据公司用人标准、评估要素进行的面试评判。希望广大候选人们,不要在面试官的雷区蹦跶。 那么面试很糟糕,就真的没机会了吗?其实不然,敢于主动的人,可能为自己创造翻盘机会。求职时想让自己脱颖而出的一个关键要素,就是主动。给大家分享几个案例:方法一:直接拦人360老板在乌镇的时候,被几位来自河北师范大学的学生拦住塞简历,学生们是信息安全相关专业的,很明显是早有预谋。周老板平时少见非985背景的毕业生简历吧,这非常规路径非常符合互联网公司的精神,更符合CEO的个性。引得周老板当天就在微博上发表了感言。这种专业(能力)针对性强、路径简单粗暴的方式通常有效。方法二:自我广告一位毕业于湘潭大学的湖南小伙今年春节在广州地铁出口处的广告栏投简历,展示了他所获得的一些奖项,右下角附上了他的微信二维码,左边放上了一张被挡住脸的照片。据说这一广告5天1000元,有30多家公司联络了他。 方法三:毛遂自荐在传统媒体多年的王记者,眼看着杂志社要关了,拿了遣散费的他微信了曾经采访过一次的企业老板,自荐自己有人脉,可以介绍客户,想来公司谋个的职位。王记者成为王经理后,果然凭借之前采访累积的人脉资源,在跨行业打下了一片天地,成为公司里的业务冠军,年薪百万,老板还直夸自己“英明决策”。正是他们的积极主动,让他们获得了那些看似不可能的机会。除了求职时主动出击,我们在面试后这么做也能带来事半功倍的效果: 如果没有主动去接触部门领导,就不会知道被拒绝的原因是什么,也就没机会提出解决方案,打消对方的顾虑。在面试官结束发问后,他通常会问求职者“你还有什么想问我的吗?”,这是个主动了解面试情况的好机会。我们可以向面试官请教自己在面试中有哪些不足,刚刚面试中的哪个问题还不太清楚等等。面对你真诚的态度,一般面试官都不会拒绝跟你交流一些想法。这样不仅能够帮助我们更好的评估自己的面试表现,在下次面试中有针对性的进行改善;同时也给了我们再次争取的机会,解释误解、消除顾虑,说不定就能扭转乾坤拿到offer呢。除此之外,如果到了约定的结果通知日还没消息,一定要主动询问,说不定只是HR在走流程,或是忘了通知你面试通过,就算没被录用,知道结果也能帮助我们更好的为下一次面试做准备。 复盘的概念,源自中国围棋,指的是双方棋手在每次博弈结束后,重新在棋盘上把刚才的对局再重复一遍,看看哪些地方下的好,哪些地方下的不好,是否还有别的走法,不同的走法对棋局有什么样的影响。运用在我们的面试中,就是通过还原整个面试场景,仔细分析每一环节,比如我们可以从面试目标、准备工作、对岗位/公司/行业的了解、面试沟通表达情况、简历这几个方面进行逐个复盘。重点关注面试官问了你哪些问题,哪些问题你回答的不好,找出影响结果的因素,进而确定出下一步的改进方向。对于那些表现出色的地方,总结规律和技巧,不断地将其运用在以后的面试中,沉淀经验,提高效率,提升自己在面试中的表现。 《高效能人士的7个习惯》里第一个就是积极主动,主动去争取自己想要的结果,哪怕主动碰壁,反弹回来的也会是一个全新的你。
用户多个请求同时发送,后端已经使用了redis锁对用户的uid进行限制,但是由于redis加锁需要时间,导致在加锁的时候已经有多个请求打过来了,请教一下有什么好的方法去限制吗
一、前言对于一个运行中的应用来说,线上排查问题是一件很头疼的问题。不管是springboot单应用,还是springcloud微服务应用,一旦在生产环境出了问题,大多数人第一反应就是赶紧去看日志查问题。如何查呢?如果是管理不那么严格的项目,允许你登录生产服务器通过命令去查,或者将生产的日志down下来去查。但为了服务器安全,一般来说是不允许研发人员随便接触服务器,会有运维人员去操作日志,这样以来就极大的影响了排查的效率,这时候会有人说,如果有可视化的操作,能可视化检索日志的界面就好了。二、为什么需要ELK一般我们需要进行日志分析场景:直接在日志文件中 grep、awk 就可以获得自己想要的信息。但在规模较大的场景中,此方法效率低下,面临问题包括日志量太大如何归档、文本搜索太慢怎么办、如何多维度查询。需要集中化的日志管理,所有服务器上的日志收集汇总。常见解决思路是建立集中式日志收集系统,将所有节点上的日志统一收集,管理,访问。一般大型系统是一个分布式部署的架构,不同的服务模块部署在不同的服务器上,问题出现时,大部分情况需要根据问题暴露的关键信息,定位到具体的服务器和服务模块,构建一套集中式日志系统,可以提高定位问题的效率。一个完整的集中式日志系统,需要包含以下几个主要特点:收集-能够采集多种来源的;传输-能够稳定的把日志数据传输到中央系统;存储-如何存储日志数据;分析-可以支持 UI 分析;警告-能够提供错误报告,监控机制;基于上述的需求,业界很多公司在不断探索过程中,经过多年实践经验,最终形成了以ELK为主流的一整套解决方案,并且都是开源软件,之间互相配合使用,完美衔接,高效的满足了很多场合的应用,是目前主流的一种日志系统。三、ELK介绍3.1 什么是elkELK其实并不是某一款软件,而是一套完整的解决方案,是三个产品的首字母缩写,即:Elasticsearch;Logstash ;Kibana;这三个软件都是开源软件,通常配合使用,而且又先后归于 Elastic.co 公司名下,故被简称为ELK协议栈,具体来说:Elasticsearch是一个分布式的搜索和分析引擎,可以用于全文检索、结构化检索和分析,并能将这三者结合起来。Elasticsearch 基于 Lucene 开发,现在是使用最广的开源搜索引擎之一。Logstash简单来说就是一根具备实时数据传输能力的管道,负责将数据信息从管道的输入端传输到管道的输出端,与此同时这根管道还可以让你根据自己的需求在中间加上滤网,Logstash提供了很多功能强大的滤网以满足你的各种应用场景。Kibana 是一个开源的分析与可视化平台,设计出来用于和Elasticsearch一起使用的。你可以用kibana搜索、查看、交互存放在Elasticsearch索引里的数据,使用各种不同的图标、表格、地图等,kibana能够很轻易的展示高级数据分析与可视化。3.2 elk工作原理如下是elk实际工作时的原理图,还是很容易理解的Logstash的存在,让数据可视化的展示成为很多需要做日志类数据展示不可或缺的组件,比如数据源可以是静态的日志文件,也可以是mysql,或来自于kafka的topic消息数据等。四、ELK环境搭建下面演示如何搭建elk,网上的参考资料比较丰富,本文采用docker快速搭建起elk的演示环境,参考下面的步骤。4.1 搭建es环境4.1.1 获取es镜像版本可以根据自身的情况选择,我这里使用的是7.6的版本docker pull elasticsearch:7.6.24.1.2 启动es容器使用下面的命令启动es容器,注意这个配置,ES_JAVA_OPTS="-Xms512m -Xmx512m",这个配置参数值根据你的服务器配置决定,一般最好不要低于512m即可;docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name es76 -d elasticsearch:7.6.22.1.3 配置es参数进入到es容器内部,然后找到下面的这个文件然后将下面的配置参数配置进去cluster.name: "docker-cluster" http.cors.enabled: true network.host: 0.0.0.0 http.port: 9200 http.cors.allow-origin: "*"2.1.4 重启es容器并访问配置完成后重启docker容器,重启成功后,开放9200端口,然后浏览器访问,IP:9200,看到如下信息,说明es可以正常使用。4.2 搭建kibana4.2.1 拉取kibana镜像为了减少后面的配置麻烦和一些问题,建议kibana版本与es版本一致docker pull kibana:7.6.24.2.2 启动kibana容器这里的IP地址,如果是云服务器,注意使用内网的IP地址docker run --name kibana -e ELASTICSEARCH_HOSTS=http://es服务IP:9200 -p 5601:5601 -d kibana:7.6.24.2.3 修改配置文件进入到kibana容器中,进入到下面的目录中cd /usr/share/kibana/config vi kibana.yml 将如下的配置信息配置进去(es的IP地址如果是云服务器建议使用内网IP)server.name: kibanaserver.host: "0"elasticsearch.hosts: [ "http://es服务IP:9200" ]xpack.monitoring.ui.container.elasticsearch.enabled: truei18n.locale: zh-CN4.2.4 重启容器并访问上述配置信息配置完成后,重启容器,开放5601端口,浏览器就可以直接访问,IP:5601,看到下面的效果说明kibana可以正常使用了4.3 搭建logstash4.3.1 下载安装包logstash的版本建议不要与es版本差别太多即可wget https://artifacts.elastic.co/downloads/logstash/logstash-7.1.0.tar.gz4.3.2 解压安装包tar -zxvf logstash-7.1.0.tar.gz4.3.3 新增配置logstash文件进入logstash-7.1.0目录下,创建一个目录,用于保存自定义的配置文件,注意提前开发4560端口cd cd logstash-7.1.0/mkdir log-confvi logstash.conf然后添加下面的配置信息input { tcp { mode => "server" host => "0.0.0.0" port => 4560 codec => json } } output { elasticsearch { hosts => "es公网地址:9200" index => "springboot-logstash-%{+YYYY.MM.dd}" }, stdout { codec => rubydebug } }在主目录下,使用下面的命令进行启动./bin/logstash -f ./log-conf/logstash.conf看到下面的输出日志,说明当前logstash服务已经开始工作,准备接收输入日志了五、SpringBoot集成ELK5.1 集成过程参考下面的过程在springboot中快速集成elk,如果是dubbo或者springcloud项目,集成步骤也差不多5.1.1 创建springboot工程项目目录下5.1.2 导入依赖根据需要引入依赖,如果是集成elk,还需要引入下面这个 <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>5.3</version> </dependency>5.1.3 配置logback日志springboot集成elk最关键的就是配置logback日志文件,需要按照一定的格式规范进行配置,才能将运行过程中产生的日志上报到logstash,然后经过转换输送到es,最后展现在kibana中,参考下面的配置信息,以下两种配置方式都可以;方式一:<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <springProperty scope="context" name="springApplicationName" source="spring.application.name" /> <property name="LOG_HOME" value="logs/service.log" /> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!--接收info日志输出到LogStash--> <appender name="LOG_STASH_INFO" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <destination>logstash地址:4560</destination> <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder"> <providers> <timestamp> <timeZone>Asia/Shanghai</timeZone> </timestamp> <!--自定义日志输出格式--> <pattern> <pattern> { "project": "elk", "level": "%level", "service": "${springApplicationName:-}", "pid": "${PID:-}", "thread": "%thread", "class": "%logger", "message": "%message", "stack_trace": "%exception" } </pattern> </pattern> </providers> </encoder> </appender> <root > <appender-ref ref="STDOUT" /> <appender-ref ref="LOG_STASH_INFO" /> </root> </configuration> 方式二:<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--提取配置文件中的服务名--> <springProperty scope="context" name="springApplicationName" source="spring.application.name" /> <property name="LOG_HOME" value="logs/service.log" /> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <destination>logstash地址:4560</destination> <encoder class="net.logstash.logback.encoder.LogstashEncoder" > <!--定义appname的名字是服务名,多服务时,根据这个进行区分日志--> <customFields>{"appname": "${springApplicationName}"}</customFields> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT" /> <appender-ref ref="logstash" /> </root> </configuration> 5.1.4 增加测试接口为了后续方便观察效果,增加两个测试接口,一个模拟正常的调用,另一个模拟异常调用@RestController @RequestMapping("/user") @Slf4j public class UserController { @Autowired private UserService userService; //http://localhost:8088/user/get?userId=001 @GetMapping("/get") public Object getUserInfo(String userId){ log.info("getUserInfo userId:【{}】",userId); Map userInfo = userService.getUserInfo(userId); return userInfo; } //http://localhost:8088/user/error?userId=001 @GetMapping("/error") public Object error(String userId){ log.info("error userId:【{}】",userId); Map userInfo = userService.getUserInfo(userId); int e = 1/0; return userInfo; } }5.2 效果演示5.2.1 启动服务工程启动之后,通过下面的在logstash终端的输出日志信息中,可以发现logstash已经接收到程序中上报过来的日志了,并且内部已经按照预定的格式进行了转换;5.2.2 配置索引模式为了让程序中的日志能够正常展现到es中,由于es是通过接收logstash传输过来的数据,存储到索引中才能通过kibana展现,所以索引的存储格式就很重要,需要提前在kibana上面配置一下索引的展现格式,按照下面的操作步骤配置即可。在kibana中找到下图模式配置入口自定义索引的模式,比如这里选择的就是在上面logstash中配置的名称前缀使用时间戳刷新配置最后进入到索引查看的栏目就可以看到展示的索引中的日志信息了5.2.3 调用接口验证效果依次调用上面的两个测试接口,然后查看kibana中日志的变化调用正常响应的接口接口能够正常响应,由于我们在接口方法中添加了一行输出日志信息,通过上面的搜索框,能够在es的日志信息中搜索出来;调用异常响应接口接口调用异常后,也能通过kibana快速发现异常信息输出通过上面的实验和操作体验,可以感受到在springboot中集成elk之后带来的便利,有了可视化的日志展现,提升问题排查效率的同时,也能更好的统一管理日志,并充分发挥日志的作用。5.3 ELK使用补充上面完整演示了如何在springboot中快速接入ELK进行日志的可视化展示,如果使用的是springloud或dubbo等技术栈,集成步骤类似,这里结合实际经验,补充下面几点以供参考。日志切分与清理展示的日志毕竟是要存储到ES索引中,随着时间的推移,日志文件将会越来越大,索引也将会占用较大的存储空间,如何管理这些源源不断的日志索引呢,给出下面两点建议:原始的日志文件,即logback文件建议按天切分(需要在配置文件中配置策略),这样产生的es索引文件也是按天存储;有了第一步之后,可以通过脚本或者手动的方式定期清理索引;控制日志输出级别不建议使用debug级别的日志级别,这样es中存储的日志索引文件会增长的非常快设置kibana访问密码生产环境中,日志也是非常重要的数据,在很多公司甚至不会对外开放,而是需要通过授权后才能查看,因此如果是在你的生产环境集成ELK,建议设置kibana的访问账户信息。六、写在文末本文详细介绍了如何在springboot中快速接入ELK的过程,ELK可以说在实际项目中具有很好的适用价值,不管是小项目,还是中大型项目,都具备普适参考性,值得深入了解和学习。本篇到此结束,感谢观看。
为什么要做性能优化Java 程序中,调用一个 Native 方法相比调用一个 Java 方法要耗时很多,我们应该减少 JNI 方法的调用,同时一次 JNI 调用尽量完成更多的事情。对于过于耗时的 JNI 调用,应该放到后台线程调用。Native 程序要访问 Java 对象的字段或调用它们的方法时,本机代码必须调用 FindClass()、GetFieldID()、GetStaticFieldID、GetMethodID() 和 GetStaticMethodID() 等方法,返回的 ID 不会在 JVM 进程的生存期内发生变化。但是,获取字段或方法的调用有时会需要在 JVM 中完成大量工作,因为字段和方法可能是从超类中继承而来的,这会让 JVM 向上遍历类层次结构来找到它们。为了提高性能,我们可以把这些 ID 缓存起来,用内存换性能。使用时缓存Java 层:public class TestJavaClass { //...... private void myMethod() { Log.i("JNI", "this is java myMethod"); } //...... } public native void cacheTest(); Natice 层extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_cacheTest(JNIEnv *env, jobject thiz) { jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } static jmethodID java_construct_method_id = NULL; static jmethodID java_method_id = NULL; //实现缓存的目的,下次调用不用再获取 methodid 了 if (java_construct_method_id == NULL) { //构造函数 id java_construct_method_id = env->GetMethodID(clazz, "<init>", "()V"); if (java_construct_method_id == NULL) { return; } } //调用构造函数,创建一个对象 jobject object_test = env->NewObject(clazz, java_construct_method_id); if (object_test == NULL) { return; } //相同的手法,缓存 methodid if (java_method_id == NULL) { java_method_id = env->GetMethodID(clazz, "myMethod", "()V"); if (java_method_id == NULL) { return; } } //调用 myMethod 方法 env->CallVoidMethod(object_test, java_method_id); env->DeleteLocalRef(clazz); env->DeleteLocalRef(object_test); } 手法还是比较简单的,主要是通过一个全局变量保存 methodid,这样只有第一次调用 native 函数时,才会调用 GetMethodID 去获取,后面的调用都使用缓存起来的值了。这样就避免了不必要的调用,提升了性能。静态初始化缓存Java 层: static { System.loadLibrary("myjnidemo"); initIDs(); } public static native void initIDs();C++ 层://定义用于缓存的全局变量 static jmethodID java_construct_method_id2 = NULL; static jmethodID java_method_id2 = NULL; extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_initIDs(JNIEnv *env, jclass clazz) { jclass clazz2 = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } //实现缓存的目的,下次调用不用再获取 methodid 了 if (java_construct_method_id2 == NULL) { //构造函数 id java_construct_method_id2 = env->GetMethodID(clazz2, "<init>", "()V"); if (java_construct_method_id2 == NULL) { return; } } if (java_method_id2 == NULL) { java_method_id2 = env->GetMethodID(clazz2, "myMethod", "()V"); if (java_method_id2 == NULL) { return; } } }手法和使用时缓存是一样的,只是缓存的时机变了。如果是动态注册的 JNI 还可以在 Onload 函数中来执行缓存操作。
JavaVM 是什么?JavaVM 是一个结构体,用于描述 Java 虚拟机。一个 JVM 中只有一个 JavaVM 对象。在 Android 平台上,一个 Java 进程只能有一个 ART 虚拟机,也就是说一个进程只有一个 JavaVM 对象。JavaVM 可以在进程中的各线程间共享接着我来看一下 JavaVM 在代码中是如何被定义的:struct JavaVM_; #ifdef __cplusplus typedef JavaVM_ JavaVM; //c++ 中,是 JavaVM_ #else typedef const struct JNIInvokeInterface_ *JavaVM; //c 中,是 JNIInvokeInterface_ #endif // JavaVM_ 主要是定义了几个成员函数 struct JavaVM_ { const struct JNIInvokeInterface_ *functions; #ifdef __cplusplus jint DestroyJavaVM() { return functions->DestroyJavaVM(this); } jint AttachCurrentThread(void **penv, void *args) { return functions->AttachCurrentThread(this, penv, args); } jint DetachCurrentThread() { return functions->DetachCurrentThread(this); } jint GetEnv(void **penv, jint version) { return functions->GetEnv(this, penv, version); } jint AttachCurrentThreadAsDaemon(void **penv, void *args) { return functions->AttachCurrentThreadAsDaemon(this, penv, args); } #endif }; //JNIInvokeInterface_ 主要定义了几个函数指针 struct JNIInvokeInterface_ { void *reserved0; void *reserved1; void *reserved2; jint (JNICALL *DestroyJavaVM)(JavaVM *vm); jint (JNICALL *AttachCurrentThread)(JavaVM *vm, void **penv, void *args); jint (JNICALL *DetachCurrentThread)(JavaVM *vm); jint (JNICALL *GetEnv)(JavaVM *vm, void **penv, jint version); jint (JNICALL *AttachCurrentThreadAsDaemon)(JavaVM *vm, void **penv, void *args); };如何获得 JavaVM动态注册时,可以在 JNI_OnLoad 的参数中获取到 JavaVM:JavaVM *gJavaVM; jint JNI_OnLoad(JavaVM * vm, void * reserved) { gJavaVM = vm //...... }也可以通过 JNIEnv 的函数获取到 JavaVM:JavaVM *gJavaVM; JNIEXPORT jstring JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject obj) { env->GetJavaVM(&gJavaVM); return (*env)->NewStringUTF(env,"Hello from JNI !"); }
JNI 程序中的异常分为以下几种:Native 程序原生异常,一般通过函数返回值和 linux 信号处理, C++ 中也有 try catch 机制解决异常,不是本文重点JNIEnv 内部函数抛出的异常,一般通过返回值判断,发现异常直接 return, jvm 会给将异常传递给 Java 层Native 回调 Java 层方法,被回调的方法抛出异常,JNI 提供了特定的 API 来处理这类异常1. JNIEnv 内部函数抛出的异常很多 JNIEnv 中的函数都会抛出异常,处理方法大体上是一致的:返回值与特殊值(一般是 NULL)比较,知晓函数是否发生异常如果发生异常立即 returnjvm 会将异常抛给 java 层,我们可以在 java 层通过 try catch 机制捕获异常接着我们来看一个例子:Java 层:public native void exceptionTest(); //调用 try { exceptionTest(); } catch (Exception e) { e.printStackTrace(); } Native 层:extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) { //查找的类不存在,返回 NULL; jclass clazz = env->FindClass("com/yuandaima/myjnidemo/xxx"); if (clazz == NULL) { return; //return 后,jvm 会向 java 层抛出 ClassNotFoundException } } 执行后的 log:java.lang.ClassNotFoundException: Didn't find class "com.yuandaima.myjnidemo.xxx"说明,java 层捕获到了异常2. Native 回调 Java 层方法,被回调的方法抛出异常Native 回调 Java 层方法,被回调的方法抛出异常。这样情况下一般有两种解决办法:Java 层 Try catch 本地方法,这是比较推荐的办法。Native 层处理异常,异常处理如果和 native 层相关,可以采用这种方式2.1 Java 层 Try catch 本地方法Java 层://执行这个方法会抛出异常 private static int exceptionMethod() { return 20 / 0; } //native 方法,在 native 中,会调用到 exceptionMethod() 方法 public native void exceptionTest(); //Java 层调用 try { exceptionTest(); } catch (Exception e) { //这里处理异常 //一般是打 log 和弹 toast 通知用户 e.printStackTrace(); } Native 层:extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) { jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } //调用 java 层会抛出异常的方法 jmethodID static_method_id = env->GetStaticMethodID(clazz, "exceptionMethod", "()I"); if (NULL == static_method_id) { return; } //直接调用,发生 ArithmeticException 异常,传回 Java 层 env->CallStaticIntMethod(clazz, static_method_id); env->DeleteLocalRef(clazz); }2.2 Native 层处理异常有的异常需要在 Native 处理,这里又分为两类:异常在 Native 层就处理完了异常在 Native 层处理了,还需要返回给 Java 层,Java 层继续处理接着我们看下示例:Java 层://执行这个方法会抛出异常 private static int exceptionMethod() { return 20 / 0; } //native 方法,在 native 中,会调用到 exceptionMethod() 方法 public native void exceptionTest(); //Java 层调用 try { exceptionTest(); } catch (Exception e) { //这里处理异常 //一般是打 log 和弹 toast 通知用户 e.printStackTrace(); }Native 层:extern "C" JNIEXPORT void JNICALL Java_com_yuandaima_myjnidemo_MainActivity_exceptionTest(JNIEnv *env, jobject thiz) { jthrowable mThrowable; jclass clazz = env->FindClass("com/yuandaima/myjnidemo/TestJavaClass"); if (clazz == NULL) { return; } jmethodID static_method_id = env->GetStaticMethodID(clazz, "exceptionMethod", "()I"); if (NULL == static_method_id) { return; } env->CallStaticIntMethod(clazz, static_method_id); //检测是否有异常发生 if (env->ExceptionCheck()) { //获取到异常对象 mThrowable = env->ExceptionOccurred(); //这里就可以根据实际情况处理异常了 //....... //打印异常信息堆栈 env->ExceptionDescribe(); //清除异常信息 //如果,异常还需要 Java 层处理,可以不调用 ExceptionClear,让异常传递给 Java 层 env->ExceptionClear(); //如果调用了 ExceptionClear 后,异常还需要 Java 层处理,我们可以抛出一个新的异常给 Java 层 jclass clazz_exception = env->FindClass("java/lang/Exception"); env->ThrowNew(clazz_exception, "JNI抛出的异常!"); env->DeleteLocalRef(clazz_exception); } env->DeleteLocalRef(clazz); env->DeleteLocalRef(mThrowable); }
今天我们来介绍下分布式事务以及分布式事务的解决方案。1 什么是分布式系统?部署在不同结点上的系统通过网络交互来完成协同工作的系统。 比如:充值加积分的业务,用户在充值系统向自己的账户充钱,在积分系统中自己积分相应的增加。充值系统和积分系统是两个不同的系统,一次充值加积分的业务就需要这两个系统协同工作来完成。2 什么是事务?事务是指由一组操作组成的一个工作单元,这个工作单元具有原子性( atomicity )、一致性( consistency )、隔离性(isolation )和持久性( durability )。原子性:执行单元中的操作要么全部执行成功,要么全部失败。如果有一部分成功一部分失败那么成功的操作要全部回滚到执行前的状态。一致性:执行一次事务会使用数据从一个正确的状态转换到另一个正确的状态,执行前后数据都是完整的。 隔离性:在该事务执行的过程中,任何数据的改变只存在于该事务之中,对外界没有影响,事务与事务之间是完全的隔离的。只有事务提交后其它事务才可以查询到最新的数据。持久性:事务完成后对数据的改变会永久性的存储起来,即使发生断电宕机数据依然在。3 什么是本地事务? 本地事务就是用关系数据库来控制事务,关系数据库通常都具有 ACID 特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。4 什么是分布式事务?在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。这里强调的是多个系统通过网络协同完成一个事务的过程,并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是分布式事务,如下图: 另外一种分布式事务的表现是,一个应用程序使用了多个数据源连接了不同的数据库,当一次事务需要操作多个数据源,此时也属于分布式事务,当系统作了数据库拆分后会出现此种情况。 上面两种分布式事务表现形式以第一种据多。5 分布式事务有哪些场景?电商系统中的下单扣库存电商系统中,订单系统和库存系统是两个系统,一次下单的操作由两个系统协同完成金融系统中的银行卡充值在金融系统中通过银行卡向平台充值需要通过银行系统和金融系统协同完成。教育系统中下单选课业务在线教育系统中,用户购买课程,下单支付成功后学生选课成功,此事务由订单系统和选课系统协同完成。SNS系统的消息发送在社交系统中发送站内消息同时发送手机短信,一次消息发送由站内消息系统和手机通信系统协同完成。
介绍分布式事务解决方案,这里有必要先介绍下CAP理论。1 CAP理论如何进行分布式事务控制?CAP 理论是分布式事务处理的理论基础,了解了 CAP 理论有助于我们研究分布式事务的处理方案。CAP 理论是:分布式系统在设计时只能在一致性 (Consistency) 、可用性 (Availability) 、分区容忍性 (Partition Tolerance)中满足两种,无法兼顾三种。通过下图理解 CAP 理论:一致性(Consistency) :服务 A 、 B 、 C 三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。可用性(Availability) :服务 A 、 B 、 C 三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务 A 结点,当服务A 宕机整个系统将无法提供服务,增加服务 B 、 C 是为了保证系统的可用性。分区容忍性(Partition Tolerance) :分区容忍性就是允许系统通过网络协同工作,分区容忍性要解决由于网络分区导致数据的不完整及无法访问等问题。分布式系统不可避免的出现了多个系统通过网络协同工作的场景,结点之间难免会出现网络中断、网延延迟等现象,这种现象一旦出现就导致数据被分散在不同的结点上,这就是网络分区。分布式系统能否兼顾C、A、P? 在保证分区容忍性的前提下一致性和可用性无法兼顾,如果要提高系统的可用性就要增加多个结点,如果要保证数据的一致性就要实现每个结点的数据一致,结点越多可用性越好,但是数据一致性越差。所以,在进行分布式系统设计时,同时满足 “ 一致性 ” 、 “ 可用性 ” 和 “ 分区容忍性 ” 三者是几乎不可能的。CAP 有哪些组合方式?1、CA :放弃分区容忍性,加强一致性和可用性,关系数据库按照 CA 进行设计。2、AP :放弃一致性,加强可用性和分区容忍性,追求最终一致性,很多 NoSQL 数据库按照 AP 进行设计。说明:这里放弃一致性是指放弃强一致性,强一致性就是写入成功立刻要查询出最新数据。追求最终一致性是指允许暂时的数据不一致,只要最终在用户接受的时间内数据 一致即可。3、CP :放弃可用性,加强一致性和分区容忍性,一些强一致性要求的系统按 CP 进行设计,比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。说明:由于网络问题的存在 CP 系统可能会出现待等待超时,如果没有处理超时问题则整理系统会出现阻塞。总结:在分布式系统设计中 AP 的应用较多,即保证分区容忍性和可用性,牺牲数据的强一致性(写操作后立刻读取到最新数据),保证数据最终一致性。比如:订单退款,今日退款成功,明日账户到账,只要在预定的用户可以接受的时间内退款事务走完即可。2 解决方案 2.1 两阶段提交协议(2PC)为解决分布式系统的数据一致性问题出现了两阶段提交协议( 2 Phase Commitment Protocol ),两阶段提交由协调者和参与者组成,共经过两个阶段和三个操作,部分关系数据库如Oracle 、 MySQL 支持两阶段提交协议,本节讲解关系数据库两阶段提交协议。参考:2PC : https://en.wikipedia.org/wiki/Two-phase_commit_protocol2PC 协议流程图:1、第一阶段:准备阶段(prepare)协调者通知参与者准备提交订单,参与者开始投票。协调者完成准备工作向协调者回应 Yes 。2、第二阶段:提交(commit)/回滚(rollback)阶段协调者根据参与者的投票结果发起最终的提交指令。如果有参与者没有准备好则发起回滚指令。一个下单减库存的例子:1 、应用程序连接两个数据源。2 、应用程序通过事务协调器向两个库发起 prepare ,两个数据库收到消息分别执行本地事务(记录日志),但不提交,如果执行成功则回复yes ,否则回复 no 。3 、事务协调器收到回复,只要有一方回复 no 则分别向参与者发起回滚事务,参与者开始回滚事务。4 、事务协调器收到回复,全部回复 yes ,此时向参与者发起提交事务。如果参与者有一方提交事务失败则由事务协调器发起回滚事务。2PC的优点:实现强一致性,部分关系数据库支持(Oracle、MySQL等)。缺点:整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。解决方案有: springboot+ Atomikos or Bitronix3PC 主要是解决协调者与参与者通信阻塞问题而产生的,它比 2PC 传递的消息还要多,性能不高。详细参考 3PC :https://en.wikipedia.org/wiki/Three-phase_commit_protocol 2.2 事务补偿(TCC)TCC 事务补偿是基于 2PC 实现的业务层事务控制方案,它是 Try 、 Confifirm 和 Cancel 三个单词的首字母,含义如下:1、Try 检查及预留业务资源完成提交事务前的检查,并预留好资源。2、Confifirm 确定执行业务操作对 try 阶段预留的资源正式执行。3、Cancel 取消执行业务操作对 try 阶段预留的资源释放。基本流程如下: 下边用一个下单减库存的业务为例来说明:1、Try下单业务由订单服务和库存服务协同完成,在 try 阶段订单服务和库存服务完成检查和预留资源。订单服务检查当前是否满足提交订单的条件(比如:当前存在未完成订单的不允许提交新订单)。库存服务检查当前是否有充足的库存,并锁定资源。2、Confirm订单服务和库存服务成功完成 Try 后开始正式执行资源操作。订单服务向订单写一条订单信息。库存服务减去库存。3、Cancel如果订单服务和库存服务有一方出现失败则全部取消操作。订单服务需要删除新增的订单信息。库存服务将减去的库存再还原。优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。缺点:开发成本高,每个事务操作每个参与者都需要实现try/confifirm/cancel三个接口。注意: TCC 的 try/confifirm/cancel 接口都要实现幂等性,在为在 try 、 confifirm 、 cancel 失败后要不断重试。 什么是幂等性?幂等性是指同一个操作无论请求多少次,其结果都相同。幂等操作实现方式有:1 、操作之前在业务方法进行判断如果执行过了就不再执行。2 、缓存所有请求和处理的结果,已经处理的请求则直接返回结果。3 、在数据库表中加一个状态字段(未处理,已处理),数据操作时判断未处理时再处理。 2.3 消息队列实现最终一致本方案是将分布式事务拆分成多个本地事务来完成,并且由消息队列异步协调完成,如下图:下边以下单减少库存为例来说明:订单服务和库存服务完成检查和预留资源。订单服务在本地事务中完成添加订单表记录和添加“减少库存任务消息”。由定时任务根据消息表的记录发送给MQ通知库存服务执行减库存操作。库存服务执行减少库存,并且记录执行消息状态(为避免重复执行消息,在执行减库存之前查询是否执行过此消息)。库存服务向MQ发送完成减少库存的消息。订单服务接收到完成库存减少的消息后删除原来添加的“减少库存任务消息”。实现最终事务一致要求:预留资源成功理论上要求正式执行成功,如果执行失败会进行重试,要求业务执行方法实现幂等。优点 :由MQ按异步的方式协调完成事务,性能较高。不用实现try/confifirm/cancel接口,开发成本比TCC低。缺点:此方式基于关系数据库本地事务来实现,会出现频繁读写数据库记录,浪费数据库资源,另外对于高并发操作不是最佳方案。