前端微服务无界实践

一、前言

随着项目的发展,前端SPA应用的规模不断加大、业务代码耦合、编译慢,导致日常的维护难度日益增加。同时前端技术的发展迅猛,导致功能扩展吃力,重构成本高,稳定性低。因此前端微服务应运而生。

前端微服务优势

1.复杂度可控: 业务模块解耦,避免代码过大,保持较低的复杂度,便于维护与开发效率。

2.独立部署: 模块部署,减少模块影响范围,单个模块发生错误,不影响全局,提升项目稳定性。

3.技术选型灵活: 在同一项目下可以使用市面上所有前端技术栈,也包括未来的前端技术栈。

4.扩展性,提升业务动态扩展的可能,避免资源浪费

微前端服务结构

技术对比和选型:

选型 静态资源预加载 子应用保活 iframe js沙箱 css沙箱 接入成本 地址
EMP × × × http://github.com/efoxTeam/emp
Qiankun × × 中低 http://qiankun.umijs.org/zh/
无界 中低 http://wujie-micro.github.io/doc/
micro-app × × 中低 http://zeroing.jd.com/micro-app/

通过对比多种技术对项目的支持情况和项目接入的成本,我们最终选型无界。

二、wujie简单用法(以主应用使用vue框架为例)

主应用是vue框架可直接使用wujie-vue,react框架可直接使用wujie-react,先安装对应的插件哦

主应用改造:

// 引入无界,根据框架不同版本不同,引入不同的版本
import { setupApp, bus, preloadApp, startApp } from 'wujie-vue2'

// 设置子应用默认参数
setupApp({
    name: '子应用id(唯一值)',
    url: "子应用地址",
    exec: true,
    el: "容器",
    sync: true
})

// 预加载
preloadApp({ name: "唯一id"});

// 启动子应用
startApp({ name: "唯一id"});

子应用改造:

1、跨域

子应用如果支持跨域,则不用修改

原因:存在请求子应用资源跨域

方案:因前端应用基本是前后端分离,使用proxy代理。只需配置在子应用配置允许跨域即可

// 本地配置
server: {
    host: '127.0.0.1', // 本地启动如果主子应用没处在同一个ip下,也存在跨域的问题,需要配置
    headers: {
        'Access-Control-Allow-Credentials': true,
        'Access-Control-Allow-Origin': '*', // 如资源没有携带 cookie,需设置此属性
        'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type',
        'Access-Control-Allow-Methods': '*'
    }
}

// nginx 配置
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Headers 'X-Requested-With,Content-Type';
add_header Access-Control-Allow-Methods "*";

2、运行模式选择

无界有三种运行模式:单例模式、保活模式、重建模式

(1)、保活模式(长存页面)

释义:类似于vue的keep-alive性质(子应用实例和webcomponent不销毁,状态、路由都不丢失,只做热webcomponent的热插拔),子应用不想做生命周期改造,子应用切换又不想有白屏时间,可以采用保活模式。主应用上有多个入口跳转到子应用的不同页面,不能采用保活模式,因为无法改变子应用路由。

配置:只需要在主应用加载子应用的时候,配置参数添加alive:true

效果:预加载+保活模式=页面数据请求和渲染提前完成,实现瞬间打开效果

(2)、单例模式

释义:子应用页面切走,会调用window.__WUJIE_UNMOUNT销毁子应用当前实例。子应用页面如果切换回来,会调用window.__WUJIE_MOUNT渲染子应用新的子应用实例。过程相当于:销毁当前应用实例 => 同步新路由 => 创建新应用实例

配置:只需要在主应用加载子应用的时候,配置参数添加alive:false

改造生命周期

// window.__POWERED_BY_WUJIE__用来判断子应用是否在无界的环境中
if (window.__POWERED_BY_WUJIE__) {
  let instance;
  // 将子应用的实例和路由进线创建和挂载
  window.__WUJIE_MOUNT = () => {
    const router = new VueRouter({ routes });
    instance = new Vue({ router, render: (h) => h(App) }).$mount("#app");
  };
   // 实例销毁
  window.__WUJIE_UNMOUNT = () => {
    instance.$destroy();
  };
} else {
  // 子应用单独启动
  new Vue({ router: new VueRouter({ routes }), render: (h) => h(App) }).$mount("#app");
}
 

(3)、重建模式

释义:每次页面切换销毁子应用webcomponent+js的iframe。

配置:只需要在主应用加载子应用的时候,配置参数添加alive:false

无生命周期改造

备注:非webpack打包的老项目,子应用切换可能出现白屏,应尽可能使用保活模式降低白屏时间

三、加载模块(主应用配置)

子应用基础信息管理

// subList.js 数据可在配置页面动态配置
const subList = [
    {
        "name":"subVueApp1",
        "exec":true,// false只会预加载子应用的资源,true时预执行子应用代码
        "alive": true,
        "show":true,// 是否引入
        "url":{
            "pre":"http://xxx1-pre.com",
            "gray":"http://xxx1-gray.com",
            "prod":"http://xxx1.com"
        }
    },
    {
        "name":"subVueApp2",
        "exec":false,// false只会预加载子应用的资源,true时预执行子应用代码
        "alive": false,
        "show":true,// 是否引入
        "url":{
            "pre":"http://xxx2-pre.com",
            "gray":"http://xxx2-gray.com",
            "prod":"http://xxx2.com"
        }
    }
]
export default subList;
// hostMap.js
import subList from './subList'

 const env = process.env.mode || 'pre'

// 子应用map结构
const subMap = {}
const subArr = []

// 转换子应用
export const hostMap = () => {
    subList.forEach(v => {
        const {url, ...other} = v
        const info = {
            ...other,
            url: url[env]
        }
       subMap[v.name] = info
       subArr.push(info)
    })
   return subArr
}

// 获取子应用配置信息
export const getSubMap = name => {
    return subMap[name].show ? subMap[name] : {}
}

子应用注册预加载和启动

// setupApp.js
import WujieVue from 'wujie-vue2';
import {hostMap} from './hostMap';

const { setupApp, preloadApp } = WujieVue 

const setUpApp = Vue => {
    Vue.use(WujieVue)
    hostMap().forEach(v => {
        setupApp(v)
        preloadApp(v.name)
    })
}
export default setUpApp;


// main.js
import Vue from 'vue'
import setUpApp from'@/microConfig/setupApp'
setUpApp(Vue)

配置公共函数

全子应用共享的生命周期函数,可用于执行多个子应用间相同的逻辑操作函数共同处理

// lifecycle.js
const lifecycles = {
  beforeLoad: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeLoad 生命周期`),
  beforeMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeMount 生命周期`),
  afterMount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterMount 生命周期`),
  beforeUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} beforeUnmount 生命周期`),
  afterUnmount: (appWindow) => console.log(`${appWindow.__WUJIE.id} afterUnmount 生命周期`),
  activated: (appWindow) => console.log(`${appWindow.__WUJIE.id} activated 生命周期`),
  deactivated: (appWindow) => console.log(`${appWindow.__WUJIE.id} deactivated 生命周期`),
  loadError: (url, e) => console.log(`${url} 加载失败`, e),
};

export default lifecycles;


// subCommon.js
// 跳转到主应用指定页面
const toJumpMasterApp = (location, query) => {
    
    this.$router.replace(location, query);
    const url = new URL(window.location.href);
    url.search = query
    // 手动的挂载url查询参数
    window.history.replaceState(null, '', url.href);
}
// 跳转到子应用的页面
const toJumpSubApp = (appName, query) => {
   this.$router.push({path: appName}, query)
}
export default {
    toJumpMasterApp,
    toJumpSubApp 
}


// setupApp.js
import lifecycles from './lifecycles';
import subCommon from './subCommon';
const setUpApp = Vue => {
    ....
    hostMap().forEach(v => {
        setupApp({
            ...v,
            ...lifecycles,
            props: subCommon
        })
        preloadApp(v.name)
    })
}

主应用加载子应用页面

// 子应用页面加载
// app1.vue
<template>
    <WujieVue
        :key="update"
        width="100%"
        height="100%"
        :name="name"
        :url="appUrl"
        :sync="subVueApp1Info.sync" 
        :alive="subVueApp1Info.alive" 
        :props="{ data: dataProps ,method:{propsMethod}}"
    ></WujieVue>
</template>

<script>
import wujieVue from "wujie-vue2";
import {getSubMap} from '../../hostMap';
const name = 'subVueApp1'
export default {
    data() {
       return {
          dataProps: [],
          subVueApp1Info: getSubMap(name)
       }
    },
    computed: {
      appUrl() {
        // return getSubMap('subVueApp1').url
        return this.subVueApp1Info.url + this.$route.params.path
      }
    },
     watch: {
        // 如果子应用是保活模式,可以采用通信的方式告知路由变化
        "$route.params.path": {
          handler: function () {
            wujieVue.bus.$emit("vue-router-change", `/${this.$route.params.path}`);
          },
          immediate: true,
        },
      },
    methods: {
        propsMethod() {}
    }
}
</script>

四、子应用配置

无界的插件体系主要是方便用户在运行时去修改子应用代码从而避免去改动仓库代码

// plugins.js
const plugins = {
  'subVueApp1': [{
    htmlLoader:code => {
      return code;
    },
    cssAfterLoaders: [
      // 在加载html所有样式之后添加一个外联样式
      { src:'http://xxx/xxx.css' },
      // 在加载html所有样式之后添加一个内联样式
      { content:'img{height: 300px}' }
    ],
    jsAfterLoaders: [
      { src:'http://xxx/xxx.js' },
      // 插入一个内联脚本本
      { content:`
          window.$wujie.bus.$on('routeChange', path => {
          console.log(path, window, self, global, location)
          })`
      },
      // 执行一个回调
      {
        callback(appWindow) {
          console.log(appWindow.__WUJIE.id);
        }
      }
    ]
  }],
  'subVueApp2': [{
    htmlLoader: code=> {
      return code;
    }
  }]
};
export default plugins;
// setupApp.js
import plugins from './plugins';
const setUpApp = Vue => {
    ......
    hostMap().forEach(v => {
        setupApp({
            ...v,
            plugins: plugins[element.name]
        })
        ......
    })
}

五、数据传输和消息通信

数据交互方式

1,通过props进行传

2、通过window进线传达

3,通过事件bus进行传达

props

主应用通过data传参给子应用, 子应用通过methods方法传参给主应用

// 主应用
<WujieVue name="xxx" url="xxx" :props="{ data: xxx, methods: xxx }"></WujieVue>

// 子应用
const props = window.$wujie?.props; // {data: xxx, methods: xxx}

window

利用子应用运行在主应用的iframe

类似iframe的传参和调用

// 主应用获取子应用的全局变量数据
window.document.querySelector("iframe[name=子应用id]").contentWindow.xxx;

//子应用获取主应用的全局变量数据
window.parent.xxx;

eventBus

去中心化的通信方案,方便。类似于组件间的通信

主应用

// 使用 wujie-vue
import WujieVue from"wujie-vue";
const{ bus }= WujieVue;

// 主应用监听事件
bus.$on("事件名字",function(arg1,arg2, ...){});
// 主应用发送事件
bus.$emit("事件名字", arg1, arg2,...);
// 主应用取消事件监听
bus.$off("事件名字",function(arg1,arg2, ...){});

子应用

// 子应用监听事件
window.$wujie?.bus.$on("事件名字",function(arg1,arg2, ...){});
// 子应用发送事件
window.$wujie?.bus.$emit("事件名字", arg1, arg2,...);
// 子应用取消事件监听
window.$wujie?.bus.$off("事件名字",function(arg1,arg2, ...){});

规范主子应用传递规则

规则:子应用名+事件名

主应用向子应用传参

// 主应用传参
bus.$emit('matser', options) // 主应用向所有子应用传参
bus.$emit('vite:getOptions', options) // 主应用向指定子应用传参

//子应用监听主应用事件
window?.$wujie?.bus.$on("master", (options) => {
  console.log(options)
});
//子应用监听主应用特定通知子应用事件
window?.$wujie?.bus.$on("vite:getOptions", (options) => {
  console.log(options)
});

六、路由

以 vue 主应用为例,子应用 A 的 name 为 A, 主应用 A 页面的路径为/pathA,子应用 B 的 name 为 B,主应用 B 页面的路径为/pathB为例

主应用统一props传入跳转函数

jump (location) {
  this.$router.push(location);
}

1、主应用history路由

子应用 B 为非保活应用

1、子应用A 只能跳转到子应用 B 的主应用的默认路由

function handleJump(){
   window.$wujie?.props.jump({ path:"/pathB"});
}

2、子应用A 只能跳转到子应用B 应用的指定路由(非默认路由)

// 子应用A点击跳转处理函数, 子应用B需开启路由同步
function handleJump(){
    window.$wujie?.props.jump({ path:"/pathB", query:{ B:"/test"}});
}

子应用 B 为保活应用

子应用A 只能跳转到子应用 B 的主应用的路由

可写入主应用的插件中,主应用插件根据不同的应用,引入不同方法

// 子应用 A 点击跳转处理函数
function handleJump() {
  window.$wujie?.bus.$emit("routeChange", "/test");
}

// 子应用 B 监听并跳转
window.$wujie?.bus.$on("routeChange", (path) => this.$router.push({ path }));

2、主应用hash路由

子应用 B 为非保活应用

1、子应用A 只能跳转到子应用 B 的主应用的默认路由

同子应用B为非保活应用,子应用A跳转到子应用 B 的主应用的默认路由

2、子应用A 只能跳转到子应用B 应用的指定路由(非默认路由)

主应用
jump(location,query){ 
    // 跳转到主应用B页面
    this.$router.push(location); 
    const url=new URL(window.location.href);
    url.search=query
    // 手动的挂载url查询参数
    window.history.replaceState(null,"",url.href);
}

// 子应用 B 开启路由同步能力


// 子应用A
function handleJump() {
  window.$wujie?.props.jump({ path: "/pathB" } , `?B=${window.encodeURIComponent("/test")}`});
}

子应用 B 为保活应用

同子应用B为保活应用,子应用A跳转到子应用 B 路由

// bus.js
// 在 xxx-sub 路由下子应用将激活路由同步给主应用,主应用跳转对应路由高亮菜单栏
  bus.$on('sub-route-change', (name, path) => {
      const mainName = `${name}-sub`;
      const mainPath = `/${name}-sub${path}`;
      const currentName = router.currentRoute.name;
      const currentPath = router.currentRoute.path;
    if (mainName === currentName && mainPath !== currentPath) {
        router.push({ path: mainPath });
      }
  });

七、部署

前端单页面的部署,不管怎么自动化,工具怎么变. 都是把打包好的静态文件,放到服务器的正确位置下。所以支持项目的独立部署和混合部署。

作者:京东物流 张燕燕 刘海鼎

内容来源:京东云开发者社区

本文转载于网络 如有侵权请联系删除

相关文章

  • C++临界锁CCriticalSection在线程中的使用

    大家好,又见面了,我是你们的朋友全栈君。#define_AFXDLL #include<afxmt.h> #include<iostream> usingnamespacestd; CCriticalSectioncritical; inttick=0; DWORDWINAPIFunc1(LPVOIDlpParam); DWORD__stdcallFunc1(LPVOIDlpParam) { critical.Lock(); tick+=10; cout<<"Func1TICKNOWIS:"<<tick<<endl; critical.Unlock(); return0; } DWORDWINAPIFunc2(LPVOIDlpParam); DWORD__stdcallFunc2(LPVOIDlpParam) { critical.Lock(); tick+=10; cout<<"Func2TICKNOWIS:"<<tick<<endl; c

  • 如何服务器与vps的选择 vps服务器的价格是多少

    从事互联网工作的小伙伴应该都知道,网站的运行绝对少不了服务器。对于服务器的选择有很多中,有vps,也有空间,这些都是要根据网站的实际情况进行选择,如果网站的流量一天就有几千个IP,那购买好用的vps就可以,如果太多,vps就会承受不起。下面就大家讲讲如何服务器与vps的选择?从事互联网工作的小伙伴应该都知道,网站的运行绝对少不了服务器。对于服务器的选择有很多中,有vps,也有空间,这些都是要根据网站的实际情况进行选择,如果网站的流量一天就有几千个IP,那购买好用的vps就可以,如果太多,vps就会承受不起。下面就大家讲讲如何服务器与vps的选择?如何服务器与vps如何服务器与vps的选择如何服务器与vps的选择?在此建议大家初期建立网站,可以选择购买vps,因为这会比服务器的价格上便宜很多,小白也可以购买用来练手。其次就是在购买上的选择,要先考虑vps的安全性、容量、速度。好用且安全的服务器能有效阻挡网站的病毒入侵;容量能确保网站的内容能得到很好的放置;稳定且快的访问速度能更好的帮助网站提升排名。小白购买选择服务器的时候,要优先考虑以上三点。vps服务器的价格是多少说到vps服务器的价

  • 国标GB28181协议视频智能分析人脸/车牌识别平台EasyCVR录像计划页面优化

    视频协议融合平台EasyCVR有录像计划的机制,用户可以根据自己的需求对通道设置录像,此处设置的过程和EasyNVR、EasyGBS等平台大体相同。打击可以了解一下操作方法:EasyNVR边缘计算网关录像计划功能操作介绍。EasyCVR录像计划页面偶尔出现了样式层叠错乱的情况,该情况导致底部分页无法正常使用。经过排查发现,只有当浏览器缩放为80%的比例,页面显示才是正常的。打开控制台发现内容高度,完全撑开主体高度,然后底部信息为固定定位,导致底部信息覆盖主体内容,表现为样式重叠。因此我们需要找到项目中program文件下的index.vue文件,并添加如下代码:.table-box{ max-height:320px; overflow:auto; }复制预修改后页面预览恢复正常。EasyCVR视频智能分析平台目前较为突出的优势就是能够基于智能分析做到人脸识别及车牌识别,如果大家还想了解更多,欢迎联系我们获取测试版本测试,除EasyCVR外,还有EasyGBS、EasyNVR等完善稳定的视频平台提供测试,欢迎了解详情。

  • Go框架比较:goframe、beego、iris和gin

    由于工作需要,这些年来也接触了不少的开发框架,Golang的开发框架比较多,不过基本都是Web"框架"为主。这里稍微打了个引号,因为大部分"框架"从设计和功能定位上来讲,充其量都只能算是一个组件,需要项目使用的话得自己四处再去找找其他的组件,或者自己造轮子。如果用于Web开发,这些"框架"的Web开发能力均已完备,无太大差别,且均是自标准库net/http.Server的二次封装。由于框架众多,这里笔者只选择了几个曾做过技术选型评估、较为熟悉,且目前比较流行和典型的Golang"框架",从适用于业务项目开发框架的角度,做一个简单的横向比较,以便大家在项目框架选型时做个参考。 评估指标由于微信对表格支持得不太好,因此这里通过截图的形式分享给大家。原本表格中带有相关的链接也发不出来,想了解详情的朋友请点击文章末尾的“阅读原文”。Golang框架选型比较:goframe,beego,iris和gin评估指标1 Golang框架选型比较:goframe,beego,iris和gin评估指标2 横向比较以下部分对比参

  • IntelliJ IDEA 超实用技巧分享,不能再全了!

    前言高效率配置日常使用必备快捷键(★★)编码效率相关(★★)代码调试源码阅读相关(★★★)插件方面前言 工欲善其事 必先利其器最近受部门的邀请,给入职新人统一培训IDEA,发现有很多新人虽然日常开发使用的是IDEA,但是还是很多好用的技巧没有用到,只是用到一些基本的功能,蛮浪费IDEA这个优秀的IDE。同时,在这次分享之后,本人自己也学习到了一些新的使用技巧,所以借着这次机会,一起分享出来。希望可以帮到一些人。高效率配置1.代码提示不区分大小写Settings->Editor->General->CodeCompletion 复制(低版本将Casesensitivecompletion设置为None就可以了)2.自动导包功能及相关优化功能Settings->Editor->General->AutoImport 复制3.CTRL+滑动滚轮调整窗口显示大小Settings->Editor->General->Changefontsize(Zoom)withCtrl+Mousewheel 复制选择之后,就可以通过CTRL+滑动滚轮的方式

  • 关于@ResponseBody 默认输出的误区

    背景@ResponseBody默认情况返回的数据格式是什么?所谓默认情况后台接口不指定producesMediaType@Controller publicclassDemoController{ @ResponseBody @GetMapping(value="/demo") publicDemoVOdemo(){ returnnewDemoVO("lengleng","123456"); } }复制使用百度搜索@ResponseBody排名第一的答案,@ResponseBody的作用其实是将java对象转为json格式的数据。正确答案我们先来公布正确的答案。@ResponseBody的输出格式,默认情况取决于客户端的Accept请求头。源码剖析RequestResponseBodyMethodProcessorpublicclassRequestResponseBodyMethodProcessor{ //处理ResponseBody标注的方法 @Override publicbooleansupportsReturnTy

  • 简洁的 React 状态管理库 - Stamen

    本文作者:IMWebforsigner原文出处:IMWeb社区未经同意,禁止转载 说到React状态管理,必提的肯定是Redux与MobX,2018年快过去了,它们依然是最火热的状态管理工具,也有一些基于Redux的,如dva、rematch等,也有新的,如mobx-state-tree,这里不对各个解决方案作评价。但还是想吐槽:什么provider,connections,actions,reducers,effects,dispatch,put,call,payload,@observable,@computed,@observer,@inject...一堆模板代码、各种概念、什么哲学原则...还有各种多如牛毛的Api。我只是想早点码完页面下班,早点下班健身、陪妹子...所以,我想要这样的一个状态管理库:轻量个人做移动端开发比较多简洁没模板代码,尽量少的Api符合直觉没复杂的概念,给个action改state就好清晰更易写出可维护和可读性好的代码高效更高的开发效率,这很重要Typescriptstate和action高度支持智能提示我是个实用主义者,开发效率、代码可维护性和可读性、

  • 网络安全重大事件判定指南

    网络安全重大事件判定指南:1、关键信息基础设施整体中断30分钟或主要功能故障2小时以上;2、关键信息基础设施核心设备已被渗透控制或恶意程序在设施内部大范围传播或设施内部数据批量泄露;3、影响单个地市级行政区30%以上人口的工作、生活;4、影响10万人以上用水、用电、用气、用油、取暖或交通出行;5、泄露5万人以上个人敏感信息;6、造成5000万元以上直接经济损失;7、党政机关门户网站、重点新闻网站、大型网络平台被攻击篡改,导致反动言论或谣言等违法有害信息大范围传播。以下情况之一,可认定为是“大范围传播”:(1)在主页上出现并持续1小时以上或在其他页面出现并持续10小时以上; (2)通过社交平台转发1万次以上; (3)浏览人数超过10万人; (4)省级以上网信部门、公安部门认定为是“大范围传播”的。8、地市级以上党政机关门户网站或重点新闻网站受到攻击,导致6小时以上不能访问;9、发生国家秘密泄露或大量地理、人口、资源等国家基础数据泄露;10、恶意程序、垃圾邮件大范围传播,感染IP地址超过10万个。以上相关判定标准仅供参考。通过以上判定标准我们可以相对量化的去判定哪些属于网络安全重大事件。同

  • 新奇篇 之 Mac 配置 React Native 0.56

    前言最近,好忙。 忙碌的背后,是当年欠下的技术债找上门了。幸好,慢慢的进入了状态,加油~!据说后期的项目会涉及到ReactNative,今天在等待导入依赖的期间,简单配置了一波,特此做个记录。ReactNative开搞~开搞之前,我们简单了解一下什么是ReactNatice?1ReactNative简述ReactNative是Facebook在React.jsConf2015大会上推出的基于JavaScript的开源框架。ReactNative结合了Web应用和Native应用的优势,可以使用JavaScript来开发iOS和Android原生应用。在JavaScript中用React抽象操作系统原生的UI组件,代替DOM元素来渲染等。且ReactNative有句很牛掰的标语:Learnonce,writeanywhere简单来说,跨平台,你会这个,LZ预估就能直接一份代码搞Android和iOS,其次嘛,Facebook已经在多项产品中使用了ReactNative,Enmmm,暂时没啥可担心的。2了解下有关ReactNative优/劣势学习一个东西,首先要明确目标,其次得了解它的优势

  • HDU 1035 Robot Motion(dfs)

        题意就是输入n*m的地图,然后输入p,表示这个机器人从(1,p)这个点为起点,然后至于机器人怎么走应该不用解释了吧,判断的终点就是走出地图,这里我们可以稍稍的做个预处理,地图从1开始输入,那么结束条件就是到达0,n+1,m+1就行了。把字母换成数字存起来,然后每走过一个点都用走的步数标记,当如果走到了标记过的点就说明存在一个环,然后此时的步数就是第一次走到这个点所用的步数,因为用step记录了总步数,所以减一下就是环的长度了。AC代码:#include<iostream> #include<cstdio> #include<cstring> usingnamespacestd; constintMAXN=1005; intMAP[MAXN][MAXN]; intvis[MAXN][MAXN]; intn,m,p,flag,ans,temp; charch; voiddfs(intx,inty,intstep){ if(flag)return; intX,Y; if(MAP[x][y]==1){ X=x-1; Y=y; } elseif(MA

  • 必会:关于SparkStreaming checkpoint那些事儿

    sparkStreaming的checkpoint是一个利器,帮助在driver端非代码逻辑错误导致的driver应用失败重启,比如网络,jvm等,当然也仅限于支持自动重启的集群管理器,比如yarn。由于checkpoint信息包含序列化的Scala/Java/Python对象,尝试使用新的修改类反序列化这些对象可能会导致错误。本文主要讲解checkpoint使用的一些注意事项。系统学习spark,深入spark源码,大数据相关问题,spark源码视频及优质文章。请点击阅读原文,加入浪尖知识星球。checkpoint简介流应用程序必须7*24小时运行,因此必须能够适应与应用程序逻辑无关的故障(例如,系统故障,JVM崩溃等)。为了实现这一点,SparkStreaming需要将足够的信息checkpoint到容错存储系统,以便它可以从故障中恢复。checkpoint有两种类型的数据: 1.元数据checkpoint将定义流式计算的信息保存到容错存储(如HDFS)。这用于从运行流应用程序的driver节点的故障中恢复(稍后详细讨论)。元数据包括:配置-用于创建流应用程序的配置。DStream

  • ThreadLocal与Spring 事务管理

    编写线程安全代码的关键是管理程序中的共享可变状态,除了通过synchronized加锁机制防止多个线程同时访问同一段数据外,还有一种方法就是通过ThreadLocal消除数据的共享,ThreadLocal会为各自线程创建相应的变量副本(线程局部变量),每个副本都由各自线程管理,这样就避免了对共享资源的访问冲突,也减少了同步时的性能消耗。我们来看一段示例:classSequenceimplementsRunnable{ privatefinalinttid; publicSequence(inttid){ this.tid=tid; } publicvoidrun(){ while(!Thread.currentThread().isInterrupted()&&VarHolder.get()<6){ VarHolder.increment(); System.out.println(this); //提示调度器,让相同优先级的线程获得运行的机会,方便重现竞争条件的情景 Thread.yield(); } } publicStringtoString(){ retu

  • 如何实现系统的可扩展性和高可用性

    概述可扩展性,高可用性和性能可扩展性,高可用性,性能和关键任务这些术语对不同组织或组织内的不同部门来说意味着不同的事情。它们经常被互换,造成混乱,导致管理不善的预期或延迟的实现或不现实的指标。本文为您提供了定义这些术语的工具,以便您的团队能够完全了解性能目标来实现目标关键系统。可扩展性可扩展性是系统或应用程序的属性,用于处理大量的工作或更易轻松扩展,用于响应对网络,任务处理,数据库访问或文件系统资源需求的增加水平可扩展性当系统通过添加具有相同功能的新节点扩展时,系统可以水平扩展,从而在所有节点之间重新分配负载。SOA系统和Web服务器通过向负载均衡网络添加更多服务器来扩展,以便传入的请求可以在所有这些网络之间分配。集群是描述扩展系统的常用术语。图1:集群垂直可扩展性当系统通过向节点添加处理器,主存,存储或网络接口进行扩展时,系统可以垂直或向上扩展,以满足每个系统更多的请求。托管服务公司通过增加处理器数量或主存来扩展,以在同一硬件中托管更多的虚拟服务器。图2:虚拟化高可用性可用性描述了系统在一段时间内提供有用资源的情况。高可用性保证在正常运行时间和停机时间之间的时间窗口内有绝对的功能连续

  • WordPress网站Gravatar头像完美替代方案:Cravatar

    全球通用头像Gravatar在国内一直无法正常加载,严重影响WordPress网站的用户体验,国内WordPress爱好者推出了国内Gravatar头像的完美替代方案Cravatar。WordPress网站Gravatar头像完美替代方案:CravatarCravatar是Gravatar在中国的完美替代方案,保持与Gravatar100%兼容,可以自由的上传和分享头像。Cravatar头像创建地址进入Cravatar网站,使用自己的常用邮箱账号注册,登录点击立即创建头像。Cravatar头像服务如何集成到WordPress网站呢?将以下代码添加到当前主题函数模板functions.php中:if(!function_exists('get_cravatar_url')){/***替换Gravatar头像为Cravatar头像**Cravatar是Gravatar在中国的完美替代方案,你可以在https://cravatar.cn更新你的头像*/functionget_cravatar_url($url){$sources=array('www.grava

  • 如何使用网络库实现应用级消息收发

    网络客户端ISocketClient和网络会话ISocketSession都继承了ISocketRemoteISocketRemote表示远程通信,核心就是收发数据。下面是ISocketRemote接口的主要实现 ///<summary>远程通信Socket,仅具有收发功能</summary> publicinterfaceISocketRemote:ISocket { #region属性 ///<summary>远程地址</summary> NetUriRemote{get;set;} ///<summary>通信开始时间</summary> DateTimeStartTime{get;} ///<summary>最后一次通信时间,主要表示会话活跃时间,包括收发</summary> DateTimeLastTime{get;} ///<summary>缓冲区大小</summary> Int32BufferSize{get;set;} #endregion

  • LeetCode5:Longest Palindromic Substring

    题目:GivenastringS,findthelongestpalindromicsubstringinS.YoumayassumethatthemaximumlengthofSis1000,andthereexistsoneuniquelongestpalindromicsubstring.解题思路:主要有三种:第一种:Manacher算法,也是最快的,时间复杂度为O(n)第二种:DP算法,时间复杂度为O(n*n)第三种:中心法,时间复杂度为O(n*n)实现代码:#include<iostream> #include<vector> usingnamespacestd; /** GivenastringS,findthelongestpalindromicsubstringinS. YoumayassumethatthemaximumlengthofSis1000, andthereexistsoneuniquelongestpalindromicsubstring. */ classSolution{ public: //Manacher算法(O(n

  • A2dp sink 初始化流程源码分析

    A2dpsink的初始化流程和A2dp的初始化流程,基本一样,这里做简单分析.这里分析的android的版本是AndroidO. 我们先从service的启动说起吧. 下面是启动的时候的log: D/BluetoothAdapterService(2029):setProfileServiceState()-Startingservicecom.android.bluetooth.a2dpsink.A2dpSinkService复制 01-0108:00:22.042D/A2dpSinkService(2029):Receivedstartrequest.Startingprofile... 01-0108:00:22.045D/A2dpSinkService(2029):start() 01-0108:00:22.054I/BluetoothA2dpSinkServiceJni(2029):classInitNative:succeeds复制 我们看看A2dpSinkService.java的start函数的实现: protectedbooleanstart(){ if(D

  • ASP.NET MVC Bootstrap极速开发框架

    前言 每次新开发项目都要从头开始设计?有木有一个通用的快速开发框架?并且得是ASP.NETMVC AndBootstrap?数据库不要手工创建?框架对未来业务支持的扩展性好?这么简单的功能还需要一天搭建基础环境?能不能只关心我所需要的业务? 有这样的一个项目,基于ASP.NETMVC、EntityFramework、Memcached、Bootstrap的快速项目开发框架,只需3秒钟即可创建一个带有简单用户管理的项目。 一键安装 懒人一键安装包下载地址,双击“install.bat”批处理,即可将模板项目加入到VS项目模板列表。 极速创建 只需单击一个“确定”按钮即可创建一个带有简单用户管理、登陆功能的后台程序,如下图所示:  运行效果 登陆界面 管理后台主界面 用户管理 自动创建的数据库 导出自己的项目模板 导出项目模板示例截图: 源码亮点 服务接口层,彻底拒绝BLL的垃圾冗余代码 服务抽象基类 publicabstractclassServiceContext:IDisposable { ///<summary> ///数据库操作上

  • AD altium designer修改 封装库后报错

    STG:docfile已被损坏。at3437EE11.ADVPCB.DLL,BaseAddress:33980000. 这个错误是封装库摔坏导致,实际上可能是操作过快导致软件问题,可以将库复制出来,重启电脑单独打开库文件,另存为同样名称的库,替换原来的库,再打开工程就OK了 写那些自己遇到的问题,然后解决的方式,不断吸取经验,坚持直到看见成功.

  • 【9】进大厂必须掌握的面试题-DevOps面试

    Q1。DevOps和Agile之间的根本区别是什么? 下表中列出了两者之间的差异。 特征 DevOps--开发运维 Agile--敏捷 敏捷 开发和运营中的敏捷性 只有发展才能敏捷 流程/实践 涉及CI,CD,CT等流程。 涉及诸如敏捷Scrum,敏捷看板等实践。 时效与质量 时效与质量同等重要 及时是重中之重 发布周期/开发周期 释放周期短,可立即获得反馈 释放周期更短 反馈来源 反馈来自自我(监视工具) 反馈来自客户 工作范围 敏捷性与自动化需求 仅敏捷 Q2。DevOps有什么需求? 这个答案应该从解释总体市场趋势开始。公司没有发布大量功能,而是尝试查看是否可以通过一系列发布系列将小的功能传输给客户。这具有许多优点,例如来自客户的快速反馈,更好的软件质量等,从而导致很高的客户满意度。为此,公司必须: 增加部署频率 降低新版本的失败率 缩短了两次版本之间的交付时间 新版本崩溃时平均恢复时间更快 DevOps满足所有这些要求,并有助于实现无缝的软件交付。您可以举一些像Etsy,ali,Google和Amazon这样的公司的例子,这些公司采用

  • SpinalWorkshop实验笔记(三)

    概述 本文涉及Stream、WavePlayer、UDP、Mandelbrot四个实验。实验地址 最后的这四个实验中的三个都和Stream类息息相关。Stream类最关键的是要掌握它的两个特性:需要握手和实时变化。 需要握手指的是Stream的传输数据需要其valid信号和ready信号均为真,而这两个信号分别由master和slave端控制,也就是由发送端和接收端控制。对于发送端,只有这两个信号同为真了,才能认为载荷被接收端接收了;而对于接收端,只有这两个信号同为真了,才能认为载荷有效可以获取。 实时变化指这两个信号都是wire,不是寄存器,所以不受时钟的控制。这是硬件的一个很重要的逻辑,不能把握手看成像tcp网络协议一样有一个谁先谁后的问题,而是同时的,发送端和接收端都在等待两个信号同为真,一旦条件满足,两边同时做各自的操作:接收端取走载荷,发送端更新状态。 Flow类相当于Stream类的一个简化,把“需要握手”这个特性去掉了,只保留发送端控制的valid信号。 内容 Stream 这个实验比较简单,主要是介绍用流载荷读取内存和两个流的同步可以通过API完成。 mem.wri

相关推荐

推荐阅读