基于Skywalking开发分布式监控(四)一个案例

上一篇我们简单介绍了基于SkyWalking自定义增强的基本架构,即通过把Trace数据导入数据加工模块进行加工,进行持久化,并赋能grafana展示。

现在我们给出一个例子,对于量化交易系统,市场交易订单提交,该订单可以走模拟盘也可以走实盘,可以自动提交,也可以走人工提交,订单提交后,会把交易所给到的订单信息反馈回来。 需要监控的需求很简单:可以按,自动实盘/虚拟盘,人工实盘/虚拟盘订单分类监控,提交和反馈流程,满足指标项:

1 每分钟延时、延时百分位(P50/75/90/95/99 MAX)、每分钟请求数,排名前5的慢请求等监控项(metrics)

2 以及按排名前5的慢请求对应的SPAN进行抓取,分析出最慢的SPAN

那么SW原生监控有啥问题呢?
1 需要根据该流程在不同阶段的特征才能定位该流程,按Trace-Span模型来说,即需要一个Trace链根据不同Span提供的特征才能抓取该Trace,SW并不支持

例如 分辨人工/自动订单实际上是按Trace相关EndpointName来的
人工订单走页面,EntrySpan的 endpointName为POST:/api/trade/order/send
但自动订单由程序发起,EntrySpan的 endpointName为“rpc.OrderTradeService.send

而分辨是否走实盘/虚拟盘,则是在后续Span,按tag systemFlag=1或2,来确认

在这里插入图片描述
而SW的搜索显然是不支持的

  1. 问题2 反馈消息是根据交易所API生成的,不是一个标准通讯架构,只能根据自定义用户增强(customize-enhance),生成的localSpan形成跟踪链,那SW原生Trace查询根本没法按endpoint名字搜索,只能按tag搜索,然后按时间取定位,效率非常低
  2. 还有一个上一篇说了,SW对Trace和Span不提供metric聚合项

那增强计算模块怎么解决上述问题
对问题1: 按人工、自动、虚拟、实盘,形成4个搜索项,然后定时(基本)同时执行,把搜索结果叠加到ES索引中,按订单编号trade_id更新索引项,利用ES的向量特征形加上业务标签,供下游按业务标签定位需要的Trace

对问题2: 按预先设计的Tag值标识反馈消息,然后按Tag搜索,把搜索结果叠加到ES索引中,按订单编号trade_id更新索引项,利用ES的向量特征形加上业务标签,供下游按业务标签定位需要的Trace

对问题3 按业务标签计算各监控项(metrics),并按时间点汇总最慢的5个Trace,查找Span

我们按配置config来说明
关于问题1,我们配置了4个搜索项

"tasks" : [{  #查找按EndpointName=rpc.OrderTradeService.send查找自动订单,并且在ES索引中增加业务标签 businessTag:: Auto"name": "task.QueryTraces",       "para" : {"serviceName" : "TradeService","endpointName" : "rpc.OrderTradeService.send","businessTag" : { "key": "businessTag", "value": "Auto"},"tags" : {},"traces_index" :  "traces-"    #索引名,xx-后面跟着日期},"switch" : "on",      #搜索项有效"interval" : "60"      #每隔60秒执行一次},{ #查找按EndpointName=POST:/api/trade/order/send查找人工订单,并且在ES索引中增加业务标签 businessTag:: manual"name" : "task.QueryTraces","para" : {"serviceName" : "TradeService","endpointName" : "POST:/api/trade/order/send","businessTag" : { "key": "businessTag", "value": "manual"},"tags" : {},"traces_index" :  "traces-"},"switch" : "on","interval" : "60"},{  #查找按tag: systemFlag=1 查找人工订单,并且在ES索引中增加业务标签 systemFlag:: 1 (实盘)"name" : "task.QueryTraces","para" : {"serviceName" : "TradeService","endpointName" : "","businessTag" : { "key": "systemFlag", "value": "sim"},"tags" : { "key": "systemFlag", "value": "1"},"traces_index" :  "traces-"},"switch" : "on","interval" : "60"},{   #查找按tag: systemFlag=2 查找人工订单,并且在ES索引中增加业务标签 systemFlag:: 2 (实盘)"name" : "task.QueryTraces","para" : {"serviceName" : "TradeService","endpointName" : "","businessTag" : { "key": "systemFlag", "value": "RealTime"},"tags" : { "key": "systemFlag", "value": "2"},"traces_index" :  "traces-"},"switch" : "on","interval" : "60"},

task.QueryTraces是查询程序,按每分钟1次的节奏,按Graphql接口查询,需要用到的接口,按ServiceName按SW内置查询searchService接口查ServiceId , 按SW内置查询searchEndpoint接口查EndpointId
然后根据ServiceId , EndpointId调用,或者ServiceId和预置Tag,按SW内置查询接口queryBasicTraces查询相关Traces,注意点如下:
1 查询窗口要注意,也就是要防止Trace形成前执行查询语句,建议做成滑动窗口,可以调节窗口的大小,或者隔几秒多试几次(比如10秒执行3次)
2 要注意应用多页查询,queryBasicTraces有页数限制,一次最多1000条,要查全需要比较完整多页查询结构
查询完更新ES索引之后
在这里插入图片描述
很容易根据业务标签,获取我们所需的Traces

同理对问题2,我们引入配置文件,实际上我们利用FIX报文msgtype=8 报文的特征来标识反馈消息,然后按ordStatus,表示是否是成交或者订单有效的报文,即按tags msgType=8, ordStatus=2/0 查询相关Traces

{"name" : "task.QueryTraces","para" : {"serviceName" : "APIService","endpointName" : "","businessTag" : { "key": "OrdStatus", "value": "deal"},"tags" : [{ "key": "msgType", "value": "8"},{"key": "ordStatus","value": "2"}],"traces_index" :  "traces-"},"switch" : "on","interval" : "60"},{"name" : "task.TracesQueryInfo","para" : {"serviceName" : "APIService","endpointName" : "","businessTag" : { "key": "OrdStatus", "value": "effect"},"tags" : [{ "key": "msgType", "value": "8"},{"key": "ordStatus","value": "0"}],"traces_index" :  "traces-"},"switch" : "on","interval" : "60"},

对于问题3,我们配置两种计算模块: 一是 task.Caculator用于计算各类Metrics,与SW无关,二是 task.SpanInfo计算 ES索引库中 按大于95%分位数延时的慢Traces,逐条查找全部Span

{ # 按业务标签查人工实盘的订单traces(businessTag=manual,systemFlag=RealTime),计算监控项"name": "task.Caculator","para" : {"businessTags" :[{ "key": "businessTag", "value": "manual"},{"key": "systemFlag","value": "RealTime"}],"traces_index" :  "traces-",    # 源索引"stat_index" : "traces_index-"   #监控项索引},"switch" : "on","interval" : "60","delay" : 10      # 比源索引执行慢10秒},{  # 按业务标签查自动虚拟盘的订单traces(businessTag=auto,systemFlag=sim),计算监控项"name": "task.Caculator","para" : {"businessTags" :[{ "key": "businessTag", "value": "Auto"},{"key": "systemFlag","value": "sim"}],"traces_index" :  "traces-","stat_index" : "traces_index-"},"switch" : "on","interval" : "60","delay" : 10},{ # 按业务标签查自动实盘的订单traces(businessTag=auto,systemFlag=Realtime),计算监控项"name": "task.Caculator","para" : {"businessTags" :[{ "key": "businessTag", "value": "Auto"},{"key": "systemFlag","value": "RealTime"}],"traces_index" :  "traces-","stat_index" : "traces_index-"},"switch" : "on","interval" : "60","delay" : 10},{ # 按业务标签查反馈提交有效订单(OrdStatus=effect,systemFlag=Realtime),计算监控项"name": "task.Caculator","para" : {"businessTags" : { "key": "OrdStatus", "value": "effect"},"traces_index" :  "traces-","stat_index" : "traces_index-"},"switch" : "on","interval" : "60","delay" : 10},{ # 计算 ES索引库中 按大于95%分位数延时的慢Traces,逐条查找全部Span"name": "task.SpanInfo","para" : {"percentile" : 0.95,"traces_index" :  "traces-","span_index" : "traces_index-"},"switch" : "on","interval" : "60","delay" : 10}

我们看一下订单提交计算结果索引
在这里插入图片描述

以及慢Trace相关Span的索引
在这里插入图片描述
关于task.QueryTraces,task.Caculator,task.SpanInfo,主要代码如下
task.QueryTraces

public class QueryTraces extends AbstractTraceQuery implements TaskService,Runnable{private static final Lock lock = new ReentrantLock();  //对不同任务的竞争性资源加锁ObjectMapper objectMapper = new ObjectMapper();String serviceName,serviceId,endpointName,endpointId,traces_index;ArrayNode businessTags;JsonNode businessTag,tags;DatasourceService datasource;TargetdbService targetdb;@Overridepublic void run() {logger.info("QueryInfo begin...");if("".equals(serviceId)){//防止获取不到serviceIdserviceId=this.datasource.queryServiceId(serviceName);if("".equals(serviceId)){//第二次获取不成功就终止线程logger.error("query serviceId fail");return;}}if(endpointName.equals("")){//检查tags是否为空,为空就终止线程if(tags.isNull() || tags.isMissingNode()) {logger.error("endpointName & tags is both empty");return;}} else{if("".equals(endpointId)){//防止获取不到endpointIdendpointId=this.datasource.queryEndPointId(endpointName,serviceName);if("".equals(endpointId)){//第二次获取不成功就终止线程logger.error("query endpointId fail");return;}}}targetdb.createForm(traces_index);String endTime=getTimeEndPoint(1,40);String startTime=getTimeEndPoint(3,41);int retry=3;  //重试次数int lastArraylistSize=0;ArrayNode traceList= JsonNodeFactory.instance.arrayNode();logger.info("QueryInfo startTime:: {}  endTime:: {}",startTime,endTime);try{while(retry>0){//查询SW的traces数据,注意有可能需要分页查询traceList=getMultiPageResult(datasource,serviceId,endpointId,startTime,endTime,tags);logger.info("traceList:: {} retry:: {}",traceList.toString(),retry);if(traceList.size()>lastArraylistSize){//如果查到结果,打业务标签,并按TraceId调批量更新目标库lastArraylistSize=traceList.size();Map<String, List<Map<String,Object>>> traceMap = genTraceMap(businessTags, traceList); //结果集合targetdb.updateDate(traces_index,traceMap);//打时间戳logger.info("TracesQuery update is done. {}",System.currentTimeMillis());}try {// 暂停执行5秒钟Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}retry--;}}catch (Exception e) {e.printStackTrace();return;}}@Overridepublic void init(JsonNode paraData, DatasourceService datasourceService, TargetdbService targetdbService) {......}
}

task.Caculator

public class Caculator extends AbstractTraceQuery implements TaskService,Runnable {private final static Logger logger = LoggerFactory.getLogger(TracesQueryInfo.class);private static final Lock lock = new ReentrantLock();  //对不同任务的竞争性资源加锁String traces_index, stat_index;ArrayNode businessTags;JsonNode businessTag;DatasourceService datasource;TargetdbService targetdb;private Map<String,Object> traceProcess(Map<String,Object> sourceMap){//处理traces查询结果AtomicInteger durationSum= new AtomicInteger();AtomicInteger count= new AtomicInteger();AtomicInteger maxDuration=new AtomicInteger();double durationAvg,p50,p75,p90,p95,p99;ArrayList<Integer> durationArray = new ArrayList<>();;  //延时集合,用于计算分位数sourceMap.entrySet().stream().forEach((Map.Entry<String,Object> entry) -> {count.getAndIncrement();String traceId = entry.getKey();System.out.println("traceId::" + traceId);Integer duration = (int) Double.parseDouble(entry.getValue().toString());durationSum.addAndGet(duration);if (duration > maxDuration.get()) {maxDuration.getAndSet(duration);}durationArray.add(duration);});durationAvg=(durationSum.get())/(count.get());p50=percentile(durationArray.toArray(new Integer[durationArray.size()]),0.5);p75=percentile(durationArray.toArray(new Integer[durationArray.size()]),0.75);p90=percentile(durationArray.toArray(new Integer[durationArray.size()]),0.90);p95=percentile(durationArray.toArray(new Integer[durationArray.size()]),0.95);p99=percentile(durationArray.toArray(new Integer[durationArray.size()]),0.99);Map<String,Object> resultMap = new HashMap<>();resultMap.put("max_resp",maxDuration.get());resultMap.put("mean_resp",durationAvg);resultMap.put("count",count.get());resultMap.put("p50",p50);resultMap.put("p75",p75);resultMap.put("p90",p90);resultMap.put("p95",p95);resultMap.put("p99",p99);return resultMap;}@Overridepublic void run() {if(targetdb.isExisted(traces_index)){logger.info("TracesStatInfo begin...");String endTime =getTimeUtcEndPoint(1,30);String startTime=getTimeUtcEndPoint(2,31);logger.info("startTime:: {}  endTime:: {}",startTime,endTime);try{// 在es trace表中,按bussinesTagList 查找local_time_stamp在当前时间范围内的记录logger.info("statQuery queryDate begins ... {}",System.currentTimeMillis());Map<String, Object> dataMap=targetdb.queryData(traces_index,businessTags,startTime,endTime,"duration");Map<String, Object> resMap = new HashMap<>();if(null!=dataMap) {//Map<String, Object> resMap = new HashMap<>();logger.info("TracesStatInfo resultMap:: {} ", dataMap.toString());resMap = traceProcess(dataMap);// targetdb.createForm(stat_index);//targetdb.insertDate(stat_index, seqNo, resMap);}else{//找不到置0logger.info("StatInfo resultMap is null ");resMap.put("max_resp", 0);resMap.put("mean_resp", 0);resMap.put("count", 0);resMap.put("p50", 0);resMap.put("p75", 0);resMap.put("p90", 0);resMap.put("p95", 0);resMap.put("p99", 0);}//打业务标签和时间戳resMap = getMapWithTags(businessTags, resMap);String seqNo = generateSeqNo(); //生成序号// 加锁lock.lock();targetdb.createForm(stat_index);targetdb.insertDate(stat_index, seqNo, resMap)}catch(Exception e){e.printStackTrace();return;}finally {// 释放锁lock.unlock();}}else{logger.info("trace_index {} is not existed",traces_index);}}@Overridepublic void init(JsonNode paraData, DatasourceService datasourceService, TargetdbService targetdbService) {.....}
}

task.SpanInfo

public class SpanInfo extends AbstractTraceQuery implements TaskService,Runnable{private final static Logger logger = LoggerFactory.getLogger(SpanQueryInfo.class);private static final Lock lock = new ReentrantLock();  //对不同任务的竞争性资源加锁String traces_index, span_index;DatasourceService datasource;TargetdbService targetdb;double percentile;private Map<String,Object> findTraces(Map<String,Object> sourceMap,double percentile){ArrayList<Integer> durationArray = new ArrayList<>();;  //延时集合,用于计算分位数Map<String,Object> resultMap = new HashMap<>(); //结果集合//计算percentile分位sourceMap.entrySet().stream().forEach((Map.Entry<String,Object> entry) ->{Integer duration = (int) Double.parseDouble(entry.getValue().toString());durationArray.add(duration);});double percentileData = percentile(durationArray.toArray(new Integer[0]), percentile);logger.info("percentileData:: {}",percentileData);//查找超过percentile的traceIdsourceMap.entrySet().stream().forEach((Map.Entry<String,Object> entry) ->{double duration = (double) Double.parseDouble(entry.getValue().toString());if(duration>=percentileData){String traceId=entry.getKey().toString();resultMap.put(traceId,duration);}});return resultMap;}@Overridepublic void run() {logger.info("SpanInfo begin...");//建表targetdb.createForm(span_index);try{logger.info("SpanInfo try begin...");//找到当前trace_index索引中所有高出95%的值的traceId集合Map<String, Object> dataMap=targetdb.queryAllData(traces_index,"duration");if(null!=dataMap) {logger.info("SpanInfo resultMap:: {} ", dataMap.toString());//查找高于percentile分位数的值Map<String, Object> resMap = findTraces(dataMap, percentile);logger.info("spanInfo foundedMap:: {} ", resMap.toString());//遍历查询结果,如果span_index中不存在,则查询span后插入span_indexresMap.entrySet().stream().forEach((Map.Entry<String, Object> entry) -> {String traceId = entry.getKey();if (targetdb.isNotInTheIndex(span_index, "traceId", traceId)) {//按traceId查询spanArrayNode spanList = datasource.getTraceSpans(traceId);Map<String, List<Map<String, Object>>> spansMap = genSpanMap(traceId, spanList); //组成SpanList//插入span_indextargetdb.updateDate(span_index, spansMap);}});}else{logger.info("SpanInfo resultMap is null ");}}catch(Exception e){e.printStackTrace();return;}}@Overridepublic void init(JsonNode paraData, DatasourceService datasourceService, TargetdbService targetdbService) {....}
}

完成索引持久化后,就可以以grafana访问ES库形成展示,这部分不展开,看一下效果
在这里插入图片描述
在这里插入图片描述

姑且算抛砖引玉吧,希望各位大佬也分享一下方案

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

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

相关文章

关于springboot一个接口请求后,主动取消后,后端是否还在跑

1、最近在思考一个问题&#xff0c;如果一个springboot的请求的接口比较耗时&#xff0c;中途中断该请求后&#xff0c;则后端服务是否会终止该线程的处理&#xff0c;于是写了一个demo RequestMapping(value "/test", method RequestMethod.GET)public BasicResul…

云消息队列 Confluent 版正式上线!

作者&#xff1a;阿里云消息队列 前言 在 2023 年杭州云栖大会上&#xff0c;Confluent 成为阿里云技术合作伙伴&#xff0c;在此基础上&#xff0c;双方展开了深度合作&#xff0c;并在今天&#xff08;3月1日&#xff09;正式上线“云消息队列 Confluent 版”。 通过将 Co…

android基础学习

从上面的描述就可以知道&#xff0c;每一个Activity组件都有一个对应的ViewRoot对象、View对象以及WindowManager.LayoutParams对象。这三个对象的对应关系是由WindowManagerImpl类来维护的。具体来说&#xff0c;就是由WindowManagerImpl类的成员变量mRoots、mViews和mParams所…

【Apache Camel】基础知识

【Apache Camel】基础知识 Apache Camel是什么Apache Camel基本概念和术语CamelContextEndpointsRoutesRouteBuilderComponentsMessageExchangeProcessorsDomain Specific Language&#xff08;DSL&#xff09; Apache Camel 应用执行步骤Apache Camel 示意图参考 Apache Camel…

学习Java的第一天

一、Java简介 Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发&#xff0c;并在 1995 年正式推出。 后来 Sun 公司被 Oracle &#xff08;甲骨文&#xff09;公司收购&#xff0c;Jav…

【AAAI2023】基于神经跨度的持续命名实体识别模型

论文标题&#xff1a;A Neural Span-Based Continual Named Entity Recognition Model 论文链接&#xff1a;https://arxiv.org/abs/2302.12200 代码&#xff1a;https://github.com/Qznan/SpanKL inproceedings{zhang2023spankl,title{A Neural Span-Based Continual Named En…

ElevenLabs用AI为Sora文生视频模型配音 ,景联文科技提供高质量真人音频数据集助力生成逼真音效

随着Open AI公司推出的Sora文生视频模型惊艳亮相互联网&#xff0c;AI语音克隆创企ElevenLabs又为Sora的演示视频生成了配音&#xff0c;所有的音效均由AI创造&#xff0c;与视频内容完美融合。 ElevenLabs的语音克隆技术能够从一分钟的音频样本中创建逼真的声音。为了实现这一…

RPC——远程过程调用

一、RPC介绍 1.1 概述 RPC&#xff08;Remote Procedure Call Protocol&#xff09; 远程过程调用协议。RPC是一种通过网络从远程计算机程序上请求服务&#xff0c;不需要了解底层网络技术的协议。RPC主要作用就是不同的服务间方法调用就像本地调用一样便捷。 1.2 RPC框架 …

QT----在编译器里能够连接云端数据库,使用windeployqt打包后运行程序,链接不上云端mysql数据库

问题描述 在编译器里能够连接云端数据库&#xff0c;使用windeployqt打包后运行程序&#xff0c;链接不上云端mysql数据库&#xff0c;困扰了好几天 打包发布手机上的app还是无法连接 问题解决 打包的时候没有将这个文件放入&#xff0c;我们复制放到exe的目录即可

redis原理深入解析之看完这篇还需要努力

数据结构 动态字符串SDS struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /*已保存的字节数 不含结束标识 header*/uint8_t alloc; /*申请总的字节数&#xff0c;不含结束标识 header*/unsigned char flags;/*不同sds头类型&#xff0c;控制sds头大小 header*/…

【AI视野·今日Robot 机器人论文速览 第八十二期】Tue, 5 Mar 2024

AI视野今日CS.Robotics 机器人学论文速览 Tue, 5 Mar 2024 Totally 63 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;双臂机器人拧瓶盖, (from 伯克利) website: https://toruowo.github.io/bimanual-twist &#x1f4da;水下抓取器, (from …

Dynamo初学尝试梳理(五)-代码块上

“学而时习之&#xff0c;不亦说乎”&#xff0c;今天接着来&#xff0c;稍微提高点难度&#xff08;高手直接忽略就行&#xff09;。 代码块&#xff08;Code Block&#xff09;&#xff0c;是 dynamo 中可以直接输入 DesignScript 的节点。可以通过双击鼠标左键&#xff0c;快…

程序员书单推荐:从入门到精通的必读之作

在程序员的职业生涯中&#xff0c;阅读技术书籍是不断学习和提升自我的重要途径。本文将为你推荐一系列从入门到精通的程序员书单&#xff0c;帮助你系统地掌握编程知识、提高技能水平&#xff0c;并在职业生涯中取得更大的进步。 一、入门篇 《Head First C语言》&#xff1…

基于SpringBoot+Vue+ElementUI+Mybatis前后端分离管理系统超详细教程(一)

Vue.js 是一个流行的前端框架&#xff0c;用于构建用户界面和单页应用程序。Vue 2 是其第二个主要版本&#xff0c;它提供了数据绑定、组件化、虚拟DOM等核心特性。要搭建一个 Vue 2 的工程化项目&#xff0c;可以遵循以下步骤&#xff1a; 一、前端环境搭建 &#xff08;一&a…

Maven入门(作用,安装配置,Idea基础maven,Maven依赖,Maven构建项目)【详解】

目录 一. Maven的作用 1.依赖管理 2.统一项目结构 3.项目构建 二.Maven安装配置 1. Maven的仓库类型 2 加载jar的顺序 3. Maven安装配置 4.安装Maven 5.配置仓库 三.idea集成maven 1.给当前project集成maven 2.给新建project集成maven 3.创建maven项目 4.pom…

二维码门楼牌管理系统应用场景:地方社区管理的新利器

文章目录 前言一、地方社区管理部门的门牌信息利用二、与社区管理部门的联动效应三、结论 前言 随着信息技术的不断发展&#xff0c;二维码门楼牌管理系统逐渐成为地方社区管理的新宠。该系统通过集成二维码技术与门楼牌信息&#xff0c;为社区管理带来了前所未有的便利与高效…

Github 2024-03-07Go开源项目日报 Top10

根据Github Trendings的统计,今日(2024-03-07统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Go项目10TypeScript项目1Harbor - 开源的云原生注册表项目 创建周期:2908 天开发语言:Go协议类型:Apache License 2.0Star数量:21549 个For…

uniapp 解决请求出现 /sockjs-node/info?t=问题

1. uniapp请求出现 /sockjs-node/info?t问题 1.1. 问题 uniapp项目老是出现 http://192.168.2.106:8080/sockjs-node/info?t1709704280949 1.1. sockjs-node介绍 sockjs-node 是一个JavaScript库&#xff0c;提供跨浏览器JavaScript的API&#xff0c;创建了一个低延迟、全…

selinux规则

selinux状态 相关命令 进程要和文件的安全上下文相匹配&#xff0c;进程才能打开文件 查找这个命令从哪个安装包来的用 yum provides 命令 进程httpd 必须与ls -Z的文件类型一致&#xff0c;要不然在强制模式下面&#xff0c;打开不了 在终端2用此命令&#xff0c;把文件类型改…

【有趣】带照明灯的自行车“铃”

这个自行车“铃”发出的不是令行人刺耳讨厌的金属铃声&#xff0c;而是礼貌友好的“请让路&#xff0c;谢谢&#xff01;”声&#xff0c;新颖而有趣&#xff1b;照明灯则为夜间骑车带来方便&#xff0c;既保安全而又实用。整个装置成本不足10元&#xff0c;制作和安装使用也都…