2023年4月

数字时代下,算力成为新的核心生产力,传统以
CPU为核心的架构难以满足新场景下快速增长的算力需求,具备软硬加速能力的DPU得以出现并快速发展。天翼云凭借领先的技术和丰富的应用实践自研
紫金
DPU,

打造为云而生的全新一代云计算体系结构,助力算力基础设施升级,赋能海量算力高效释放。

传统数据中心里,所有的数据处理都依赖于
CPU的通用计算能力,近10年来,数据带宽增长超过10倍,然而因为摩尔定律的失效,CPU的主频几乎没有增长,给算力基础设施的发展带来了极大挑战。

云计算要实现虚拟化管理需要依托众多虚拟化和管理组件,这会大量占据服务器主机的
CPU和内存资源,带来巨大的算力消耗,形成高额的算力税。然而即便如此,基于CPU通用计算能力、以纯软件来实现的虚拟化组件,整体性能仍无法满足客户日益增长的数据处理需求。且国产化芯片架构、操作系统种类繁多,复杂的适配工作会耗费大量的人力和时间成本,新型高性能计算架构势在必行。

天翼云作为云服务国家队,积极应对算力挑战,自研紫金
DPU,通过虚拟化组件卸载、多种业务硬件加速以及自研三栈合一高性能网络协议,打造
高性能、低延时、高可靠性并且可大规模部署
的环境,助力算力基础设施升级。

天翼云紫金
DPU采用SOC+FPGA架构,依托FPGA强大的处理性能和灵活的可编程能力,对多种应用进行加速。

对于客户使用的虚拟网络和存储设备,其后端都是通过
DPU硬件来实现。天翼云紫金DPU通过硬件DMA提供超强性能和超低时延;通过硬件模拟提供数以千计的pvf设备和queue资源,
充分满足虚机容器等场景下大量虚拟设备的应用需求。

对于虚拟网络、虚拟存储业务,天翼云实现了
DPU卸载加速。虚拟网络的控制面,包括vnet的agent和OVS的控制面,以及数据面Slowpath都运行在DPU的SOC上;慢速路径处理后生成的fastPath,则全部被卸载到DPU的硬件上。除首包需要上送到软件之外,后续报文都可以
直接通过硬件处理来进行加速。

对于存储业务,控制面的建链也是通过
DPU的SOC来实现的。SOC完成建链后,会将连接信息以及云盘的元数据信息等都更新到FPGA硬件里面,之后的存储IO硬件能够直接处理,无需再通过SOC。除了支持基础的存储IO卸载加速外,紫金DPU还支持部分应用加速,例如EC纠删码计算、数据压缩、存储object hash值计算等操作。通过硬件卸载加速,天翼云的
存储业务可以为客户提供极致的性能。

此外,天翼云还开创性的推出三栈合一的传输层网络协议栈,即
SF-Stack,Super fusion超融合协议栈,在底层提供统一的传输层接口,支持传输协议的动态选择和切换,上层应用只需要关注业务逻辑,无需关注传输层,做到简单易用、超高性能、超高可靠。AZ内数据传输采用RDMA传输协议,跨AZ的传输首选用户态的TCP协议,实现极致性能,同时该协议还保留了
可靠性更高的内核态
TCP,作为故障发生时的最后兜底手段。

最后,依托于
DPU的物理隔离,天翼云紫金DPU将虚拟化组件和DPU一起作为一个整体,实现了不同算力、不同架构、不同系统即插即用,支持市面上大多数不同架构的主流芯片,
真正做到了一云多芯,
助推国产算力基础设施生态发展。

依托全新的紫金架构,天翼云实现了服务器虚拟化接近零损耗,助力算力全面释放,推动整个算力基础设施运营效率大幅提升,为客户带来更优的使用体验。


在虚拟网络转发方面,通过
DPU卸载加速,曾经需要十余个宿主机CPU才能实现的2000万pps提升至4000多万,
提升了超
100%;


在存储方面,通过硬件
RDMA加速,配合天翼云自研拥塞控制算法,IOPS能力从60万提升至200万,
提升了超
200%;

▍在时
延方面,紫金
DPU使网络时延降到了原来的
四分之一。

在数字经济飞速发展的当下,
DPU作为新的蓝海赛道,具有巨大的探索空间及应用价值。天翼云将继续坚持科技创新,挖掘DPU的更多潜力与可能,推动数据中心向高效率、高扩展、高带宽、高灵活性发展,为数字中国建设铸牢云底座。

哈喽大家好,我是咸鱼。今天我们来聊聊什么是
Flask 上下文

咸鱼在刚接触到这个概念的时候脑子里蹦出的第一个词是 CPU 上下文

今天咸鱼希望通过这篇文章,让大家能够对 Flask 上下文设计的初衷以及应用有一个基本的了解

Flask 上下文

我们在使用 Flask 开发 web 程序的时候,通常会面临下面的情况

假设同一时间内有三台客户端(或浏览器)向 web 服务器发送了请求,那么 Flask 应用是如何分辨出这三个请求属于哪台客户端(或浏览器)并返回正确的响应的呢?

聪明的你结合文章题目很快就想到了——Flask 上下文

在 Flask 中,通过本地线程技术来实现上下文隔离
。本地线程是一种轻量级的线程,它可以在同一个进程中创建多个线程,并且每个线程拥有独立的堆栈和寄存器等资源,因此可以并发地执行多个任务

1、接收到请求的时候,Flask 会为每个请求创建一个 Flask 上下文对象,这个
上下文对象包含了一些全局或者请求级别的变量

2、处理请求的时候,Flask 会为这个请求的上下文对象分配或创建一个本地线程,这样就可以在处理请求的各个环节当中通过本地线程来访问这个请求的上下文对象了

3、请求处理完毕之后,Flask 会将请求上下文对象从当前线程中删除并销毁。这样就能保证每次请求都是独立的,避免了线程安全问题

结合上面的例子我们得知,首先 Flask 应用会为这三个请求分配(或创建)到不同的线程中,然后创建与三个请求对应的三个 Flask 上下文对象(应用上下文和请求上下文)并存储到本地线程当中

到这里有小伙伴可能会问,
Flask 应用是如何区分这三个请求上下文的呢?

Flask 使用的上下文是通过
werkzeug.local
模块中的
Local
类实现的。
Local
对象实际上是一个字典,它的键是线程 ID,值是该线程对应的上下文对象

所以说,
Flask 通过线程 ID 来区分不同的请求上下文

两种上下文

现在我们知道了,Flask 会自动为每个请求创建一个专属的独一无二的环境,称为上下文

这个上下文环境包含了一些全局级别和请求级别的变量,可以在请求处理的整个过程当中使用

也就是说,Flask 上下文由两部分组成——应用上下文和请求上下文

应用上下文(application context)

当一个 Flask 应用启动时,会自动创建一个应用上下文对象。这个应用上下文对象表示整个应用的运行环境,用于存储应用全局的变量和配置(应用配置、数据库连接信息等)

对于每个请求来说,应用上下文对象会在当前请求处理之前创建,并且会一直存在到请求处理完毕之后才被销毁。这意味着,应用上下文对象可以在整个请求生命周期内共享数据

请求上下文(request context)

当请求到达 Flask 应用的时候,每一个请求都会有一个专属的请求上下文环境,用于存储请求相关的变量和信息(请求路径、请求方法、请求参数等等)

应用上下文和请求上下文的区别

可以看到,应用上下文是全局的,表示整个 Flask 应用的运行环境,而请求上下文是针对每个请求独立的,表示该请求的运行环境

在应用程序的整个生命周期中,应用上下文只有一个,而且存在于应用的整个生命周期中。而请求上下文会随着请求的到来而动态创建和销毁

对于每个请求来讲,应用上下文是每个请求共享的,请求上下文是每个请求独有的

举个简单的例子
,比如说有一家大型超市(Flask 应用),每当有顾客来超市买东西的时候(相当于请求到达 Flask 应用),店员就会为每个顾客分配一个购物车(本地线程)

这个购物车里面存放了每个顾客的商品、折扣券(请求上下文)等等。购物车里面的东西是每个顾客专属的、独有的(不同请求的请求上下文是独立的)

当顾客消费完毕之后,购物车里面的东西就会清空,店员就会回收这些购物车,等待分配给下一个顾客使用(请求处理完毕之后将请求上下文对象从当前线程中删除并销毁,并后续为新到的请求分配线程)

而这个超市里面的电梯、货架以及商品(应用上下文)都是每个顾客共享的(应用上下文是每个请求共享的)

当超市关门的时候,电梯、货架以及商品就不能够对外使用了(应用上下文存在于应用的生命周期中)

Flask 上下文激活

Flask 上下文可以分为自动激活和手动激活两种方式

自动激活

自动激活是指 Flask 在处理请求时自动激活应用上下文和请求上下文,无需手动干预,这样可以避免在代码中频繁手动创建和销毁上下文对象,使得代码更加简洁

下面这些情况中,Flask 会自动激活应用上下文:

  • 使用 flask run 命令启动程序时

  • 使用旧命令
    app.run()
    启动程序时

  • 执行使用
    @app.cli.command()
    装饰器注册 flask 命令时

  • 使用 flask shell 命令启动 python shell 时

当请求进入时,Flask 会自动激活请求上下文

手动激活

在一些特殊情况下需要手动创建或激活上下文对象

例如在使用 Flask 提供的测试客户端进行单元测试时,需要手动创建应用上下文和请求上下文,并在测试完成后手动销毁,以保证测试环境的隔离性

  • 手动激活应用上下文

在 Flask 中,手动激活应用上下文可以通过使用
app.app_context()
上下文管理器来实现

  • 手动激活请求上下文

总的来说,无论使用哪种方式,
激活 Flask 上下文的目的都是为了能够在处理请求的过程中访问请求上下文,并且在请求处理完毕后,正确地销毁请求上下文


感谢阅读,
喜欢作者就动动小手
[
一键三连]
,这是我写作最大的动力

前言:腾讯地图分为两个版本,版本1是以
Tmap为标志,链接为
https://map.qq.com/api/gljs?v=1.exp

1
为主, 版本2是以
qq.map
为标志,链接为
https://map.qq.com/api/gljs?v=2.exp

2
为主, 可以在导入了在线链接的页面上使用
window.T
或者
window.q
找到其地图,这是由于在导入了地图链接后,地图api将其挂载在了
window
上。 以版本1
Tmap`为例:

所以写腾讯地图时要特别注意自己用的腾讯地图的版本,版本一与版本二两者之间有很大区别,在看
api
时也要注意后缀。

地图api地址:

v1:
https://wemap.qq.com/Vis/JavascriptAPI/APIDoc/map
v2:
https://lbs.qq.com/webApi/javascriptV2/jsGuide/jsOverview
同样是v2的:
https://lbs.qq.com/javascript_v2/doc/map.html

腾讯地图放置在弹窗组件上的表现

笔者在最开始使用的是
v1
版本,但是
Modal
组件会显示不正常的地图,后来尝试使用
v2
版本,结果发现
Modal
组件内根本无法回显地图,无奈之下只好选择了
v1
版本,通过
Modal
组件本身的一些配置,实现了需求,另外,一些博客上的关于使用腾讯地图在弹窗上的实现,其弹窗都是自己实现的,代码不全,因而参考意义并不大。

贴贴,请留意
Modal
自身的一些配置:

<Modal centered zIndex={1001} onCancel={() => closeMapModal && closeMapModal()} forceRender={true} getContainer={document.body} open={MapModalOpen} title='选择位置' footer={false} width={1220}>
 <div>
  <div id='container' ref={mapRef} style={{ width: '100%', height: '500px', position: 'relative' }} />
 <div>
</Modal>

然后就来到了坑2:

window上已挂载地图,但是提示找不到地图

这是由于
Modal
组件的缘故。
Modal
组件本身是可以有多次销毁和显示的,因而,假设要生成
map
实例,要在生成实例外使用settiemout(fn)包裹(读者也可以使用
Promise

Vue
开发者可以使用
$nextTick
,总之,不能使用
同步
的方式在
Modal
组件上生成
map
实例)。

依然贴贴:

const initMap = useCallback(()=>{
// react中没有nextTick,所以用了`setTimeout`
if(!isOpen)return 
if(!mapRef.current)return 
// 初始默认点
let tarLat = 39.984120
let tarLng = 116.307484
let initLatlng = new TMap.LatLng(tarLat, tarLng)
let myOptions = {
  zoom: 12,
  center: initLatlng,
  offset: { // 中心点偏移
    x: 0,
    y: 100,
  }
}
setTimeout(()=>{
   // 参数1:容器实例,配置项
  map = new TMap.Map(mapRef.current, myOptions)
})
},[isOpen])
关键字搜索

v1
中要想实现关键字搜索功能,可以使用
Suggestion
类,想要使用
Suggestion
类需要额外导入
service
此附加库(注意,这个链接是替换动作,不是添加动作):

<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=YOUR_KEY"></script>

// 外部必须要有setTimeout包裹,防止意外报错
setTimeout(()=>{
// 如果不确认,可以在`new TMap`上查看是否有service这个对象
  let suggest = new TMap.service.Suggestion({
    // 新建一个关键字输入提示类
    pageSize: 20, // 返回结果每页条目数
    region: '', // 限制城市范围
    regionFix: false, // 搜索无结果时是否固定在当前城市
  })
    // keyword:关键词   location :用户经纬度
   suggest.getSuggestions({ keyword: keyword, location: map.getCenter()}).then(res=>{
      if (res.status === 0 && Array.isArray(res?.data) && res?.data.length > 0) {
         // 这里就拿到了数据
      }
  }) 
})

// res.data 子项示例,这是我们接下来操作的基础:
{
    "id": "8672089425561259711",  // 这里的id要记住,打点时要用到
    "title": "山东大学(洪家楼校区)",
    "address": "山东省济南市历城区洪家楼5号",
    "category": "教育学校:大学",
    "type": 0,
    "_distance": 2429,
    "location": {
        "lat": 36.687334,
        "lng": 117.068511,
        "height": 0
    },
    "adcode": 370112,
    "province": "山东省",
    "city": "济南市",
    "district": "历城区"
}

打点与批量打点功能

打点与批量打点,本质上是没什么区别的。都是通过实例化
MultiMarker
类来实现。为了避免频繁生成
marker
实例而造成性能损耗,我们可以在地图实例生成后先初始化
marker
实例,然后再在每次需要打点前先将上一次的
marker
数据清空再生成本次的
marker

因而,单个打点与批量打点的区别仅仅在于
内部的
geometries
的长度

刚刚讲到初始化时要先实例化marker对象,其实 我们也应该将marker在最初时就定义,这个动作有点类似在vue的data中先赋初值

贴贴:

let marker
function MyMap(){
  const initMap = useCallBack(()=>{
   setTimeout(()=>{
    if(!isOpen) return
    // 此处为初始化地图 略
    // do  init map 
    // 此处为初始化地图 略
            // 初始化marker
        marker = new TMap.MultiMarker({
          map: map,
          styles: {
            // `default`字段指的是marker的样式名,也可以写多个,也可以写别的
            default: new TMap.MarkerStyle({
              // 点标注的相关样式
              width: 34, // 宽度
              height: 46, // 高度
              anchor: { x: 17, y: 23 }, // 标注点图片的锚点位置
              src: pointImg,     // 标注点图片url或base64地址,不填则默认
              color: '#ccc', // 标注点文本颜色
              size: 16, // 标注点文本文字大小
              direction: 'center', // 标注点文本文字相对于标注点图片的方位
              offset: { x: 0, y: -4 }, // 标注点文本文字基于direction方位的偏移属性
              strokeColor: '#fff', // 标注点文本描边颜色
              strokeWidth: 2, // 标注点文本描边宽度
            }),
          },
          geometries: [] // 这里就是地图上点的数量
        })
  }) 
  },[isOpen])
}

不管是单个打点还是批量打点,笔者这边的需求是打新的点前要先将上一次的点全部清空,因而,我们要先学习一下如何删除点

// 添加点之前,先删除点
// 此处拿得到marker实例是由于在init时就将new TMap.MultiMarker赋给了marker
// marker.getGeometries : 获取当前marker对象有多少个点
// marker.remove([]) //remove方法用于删除marker,根据内部的id,注意remove方法的参数是数组格式
if (marker.getGeometries().length > 0) {
    // 全部删除,也可以使用remove([id])进行单个删除
    marker.remove(marker.getGeometries().map(v => v.id))
}

如果是以同一个marker对象下打点倒不用写太多,甚至还想封装一个函数...

  /**
   * markerMatter : marker物料数据,基于isBat    , 
   * list : 获取到的总数据 
   * isBat是否批量,true则markerMatter为数组态,false则为对象态
  */
const setMarker = (markerMatter, list, isBat = false)=>{
  if (marker.getGeometries().length > 0) {
      marker.remove(marker.getGeometries().map(v => v.id))
  }
// 根据是否批量的标识isBat来判断并生成geoList待传数组,geoList本质上就是marker中的geometries,本质上就是替换动作
  const geoList = isBat ? list.map((item, i) => {
  return ({
    id: item.id,
    styleId: 'marker',
    position: new TMap.LatLng(item.location.lat, item.location.lng),
    content: i + 1 + '',  // 需求:批量打点时,要拿到搜索的index项并标在marker上,但是单个的不需要。
    properties: {
      title: item.title
    }
  })
  }) : [{
  "id": markerMatter.id, // 非常重要,不填则删不掉marker
  "styleId": 'marker',
  "position": new TMap.LatLng(markerMatter.location.lat, markerMatter.location.lng),
  content: undefined, // 单个不需要填充索引内容
  properties: {
    title: markerMatter.title
  }
  }]
  // !!!关键代码
  marker.add(geoList)
}
地图上的弹窗与自定义弹窗与实现自定义弹窗点击事件

弹窗与marker总是成对存在。
之前的需求是不同的marker上显示不同的
windowinfo
,需要多个
windowinfo
存在,后来需求有变,就成了点击哪个搜索项或者点击哪个
marker
,对应的弹窗
windwoinfo
就回显出来,本质上渲染的还是同一个
windowinfo
对象,所以我们依然可以使用同一个
windowinfo
来实现此功能,同
marker

// 首先依然要先外部定义infoWindow
let infoWindow
function MyMap(){
const initMap = () =>{
  // 初始化地图与实例化marker不再赘述
  // 初始化地图与实例化marker不再赘述
    //  初始化info
    info = new TMap.InfoWindow({
      map: map,
      enableCustom: true,
      offset: { x: 0, y: -22 },// 设置偏移防止盖住锚点
      position: new TMap.LatLng(tarLat, tarLng)  // 初始化
    })
    // 先调用一次关闭方法,防止出现多个
    info.close()
}
}

基于需求:整个地图动作过程中,仅仅会出现一个infowindow,因而也就只需要一个就够了
基于需求:弹窗上要添加按钮,传递事件
需求1是很简单的,但是需求2是很麻烦的,笔者翻遍了国内外各大知名不知名网站也没找到解决办法,原因在于,info.setContent方法(setContent:用于设置窗体内容)仅接受字符串格式,可以使用模板字符串,但是一般不能传递事件。

甚至又想封装个函数

想了想还是要先把一些坑点说一下,读者可以根据笔者的思路来做,也可以另辟蹊径

  // 设置窗体信息
// currentContent:当前选中的marker或者搜索项,格式就是刚刚说的搜索项的格式,不表;
// list: 就是总列表
  const setWindowInfo = (currentContent, list) => {
    // 首先先执行一次关闭方法,这是为了防止可能出现的弹窗异常。
    info.close()
    // 这里是设置窗体的内容,重点是要看下那个btn事件,它不能接受Antd的Button组件,但原生button的样式又实在太丑,所以笔者直接将官网上Button组件的基础样式抄了下来,至于hover的样式。。暂时不知道怎么实现
    // 
    info.setContent(`
    <div style="position:relative">
    <div style="text-align:left">${currentContent.title}</div>
    <div style="text-align:left">${currentContent.address}</div>
    <span><label>经度:</label> <span>${currentContent.location.lat}</span></span>
    <span><label>纬度:</label><span>${currentContent.location.lng}</span></span>
    <button  style= "color: #fff;  background-color: #1677ff; box-shadow: 0 2px 0 rgba(5, 145, 255, 0.1);cursor:pointer;height: 32px;
    padding: 4px 15px;    font-size: 14px;border:none;
    border-radius: 6px;"  data-id=${currentContent.id}  id='btn'>添加</button>
  </div>
    `)
    // 设置定位
    info.setPosition(new TMap.LatLng(currentContent.location.lat,         
    currentContent.location.lng))
    // 设置好内容和位置后,我们将它打开    
    info.open()
    /*
      好的,到这里,我们要仔细讲一下原理了,原理是通过HTML5的自定义属性,也就是data-xx的方式去给她设置唯一值,通过这个唯一值我们可以得知用户究竟在哪个marker上点击了这个位置,换句话说,只要我们在点击时确定了这个唯一值,button的功能也就实现了
    **/
     // 原生dom方式拿到这个dom
    let useLocationDom = document.getElementById('btn')
    // 绑定事件
    useLocationDom.addEventListener('click', function (e) {
      //  `dom.getAttribute`获取绑定到data-xx的上的自定义属性值
      const checkedOpt = list.find(v => v.id === useLocationDom.getAttribute('data-id'))
       // 业务逻辑,不表  
       //  你可以在这里做任何你想做的...
      //  业务逻辑,不表 
      message.success(`添加成功`)
      // 此处用于功能完成后的一些样式的优化
      useLocationDom.disabled = true
      useLocationDom.style.cursor = 'not-allowed'
      useLocationDom.style.color = 'rgba(0, 0, 0, 0.25)'
      useLocationDom.style.backgroundColor = 'rgba(0, 0, 0, 0.04)'

    })
  }

至于在搜索项上点击使对应的点回显弹窗,太过简单,不表。

直接将选中项item跟总列表塞到这个 setWindowInfo 里就好了啊喂!

大概写了这些,总的来说地图还是蛮简单,看着api一把嗦就完事了。

以上。

前情提要

当你看到此篇文章时,nacos目前最新稳定版本为:
2.2.2 (Apr 11, 2023)
Latest

版本不在于有多新,在于与当前场景环境匹配正常,稳中求快。万一你用了比较新的版本,遇到问题怎么办?有问题,那就找李元芳吧!开个玩笑。你可以去 nacos 的github仓库issues提问、追问选择解决方案,促进开源社区的和谐发展。

第一篇基础篇,可在博客园搜索:MySQL数据库与Nacos搭建监控服务。

本篇是个人nacos系列文章第二篇springboot项目集成微服务组件nacos。
image

整体思路,个人nacos系列博文一共分为三篇

  • 基础篇:《MySQL数据库与Nacos搭建监控服务》,Nacos与MySQL基本介绍。
  • 开发篇:《开发篇:springboot与微服务组件nacos》,从代码开始构建,集成微服务组件。
  • 运维篇:《运维篇:nacos prometheus grafana》,服务监控篇,主要以Linux发行版为主。

骚话环节

一入编程深似海,从此节操是路人。脱发已是常态,致富还需绝顶。

那天我听说程序员之间还相互鄙视,其中不能忍的终极鄙视:有女朋友的程序员鄙视没有女朋友的程序员。这不能忍,打不过怎么办,问就是加入吧。

由于是测试环境,使用版本比较新,学当然要学新知识吸收精华,羽化成仙,做那万人敬仰韩天尊。啊,不好意思,扯远了。开个玩笑,我们依旧
使用稳定版本
。此次在Linux发行版操作系统中全程使用root用户进行测试避免权限问题带来的干扰,如果使用普通用户请自行测试提权赋予所有者或者所属组权限。

骚话不多说,
直接进入今天的主题springboot项目集成微服务组件nacos

springboot与微服务组件nacos

必备环境,前置条件尽量保持一致

  • JDK版本: JDK17
  • 开发工具和项目构建工具:STS4 & Maven3.6
  • Springboot版本:Springboot2.7.x
  • 服务监控三件套:Nacos2.x、Prometheus2.3.x、Grafana9.3.x
  • 开发测试环境:Linux(centos-stream-9)云服务器或者VMware搭建环境

你可以了解到的知识:从项目开发构建到线上测试发布,不过,需要具备一点点Java或者其它编程语言基础知识。

tips
:做实验时请检查是否关安装防火墙管理工具,关闭防火墙服务或者开启相应端口,或者放通云服务器安全组。

企业中生产环境,唯稳,稳中求快

可以看到,我再次使用了这张流程图,在第三篇会总结使用过程。
image

Nacos服务快速启动

关于nacos2.2.0相关配置说明以及数据源说明,遇到问题总结,可以参考如下文章:

https://blog.cnwangk.top/2023/03/30/MySQL数据库与Nacos搭建监控服务/

使用hexo搭建静态博客网站,感兴趣可以自己搭建一个,利用github pages和cloudflare pages等进行同步。之所以给出上面的链接,因为一篇优质的教程,会持续更新迭代。当然,在个人公众号里面同样可以搜索到相关教程。

根据个人或者团队开发环境,可以选择框架开发环境:spring、springboot以及springcloud集成nacos

值得注意的地方

服务器部署nacos服务支持多种场景:

  1. Nacos原生形式部署。
  2. Nacos Docker形式部署。
  3. Nacos Kubernetes形式部署。

至于用哪种方式,根据实际业务场景分析,选择符合个人或者公司业务场景的最佳方式。

Nacos支持三种部署模式

  • 单机模式:用于测试和单机试用。
  • 集群模式:用于生产环境,确保高可用。
  • 多集群模式:用于多数据中心场景。

高可用环境准备

  • 建议支撑环境:部署JDK,需要 1.8 及其以上版本
  • 建议硬件配置:2核 CPU / 4G 内存 及其以上
  • 建议软件配置:生产环境 3 个节点 及其以上

Nacos 依赖 Java 环境来运行。如果您是从代码开始构建并运行Nacos,还需要为此配置 Maven环境,请确保是在以下版本环境中安装使用:

1、预备环境准备

  • 64 bit OS,支持 Linux/Unix/Mac/Windows,
    推荐选用 Linux/Unix/Mac

  • 64 bit JDK 1.8+ 。

  • Maven 3.2.x+ 。

2、下载源码或者安装包

你可以通过
源码

发行包
两种方式来获取 Nacos。

个人推荐
:下载发行包。关于版本,使用稳定版本(
通常有GA标识
),个人习惯使用官方推荐的上一个小版本。有特殊需求可以下载源码包,修改源码重新编译。

从 Github 获取源码方式,使用
git clone
命令,
值得注意的是你需要部署Git环境

git clone https://github.com/alibaba/nacos.git
cd nacos/
mvn -Prelease-nacos -Dmaven.test.skip=true clean install -U  
ls -al distribution/target/
// change the $version to your actual path
cd distribution/target/nacos-server-$version/nacos/bin

nacos发行包下载地址

下载编译后压缩包方式,您可以从 最新稳定版本 下载 nacos-server-$version.zip 包。

unzip nacos-server-$version.zip 或者 tar -zxvf nacos-server-$version.tar.gz
cd nacos/bin
  • $version指具体nacos版本号,比如具体版本:nacos-server-2.1.1。

  • Windows平台建议下载以 .zip结尾的压缩包:nacos-server-2.1.1.zip。

  • Linux平台建议下载以 .tar.gz 结尾的压缩包:nacos-server-2.1.1.tar.gz。

3、修改配置文件

注意
:修改
conf
目录下的
application.properties
文件。设置其中的
nacos.core.auth.plugin.nacos.token.secret.key
值,详情可查看鉴权-自定义密钥:

https://nacos.io/zh-cn/docs/v2/plugin/auth-plugin.html

注意,文档中的默认值
SecretKey012345678901234567890123456789012345678901234567890123456789

VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg=
为公开默认值,可用于临时测试,实际使用时请
务必
更换为自定义的其他有效值。

4、启动服务器

Linux/Unix/Mac:执行startup.sh脚本
启动命令(standalone代表着单机模式运行,非集群模式):

sh startup.sh -m standalone

如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:

bash startup.sh -m standalone

Windows平台:执行startup.cmd脚本
启动命令(standalone代表着单机模式运行,非集群模式):

startup.cmd -m standalone

5、关闭服务器

Linux/Unix/Mac:执行shutdown.sh脚本

sh shutdown.sh

Windows平台:执行shutdown.cmd脚本

shutdown.cmd

可以在Windows中使用terminal或者cmd命令行运行shutdown.cmd命令,也可以双击shutdown.cmd运行文件。

以下为个人实战总结,仅供参考

个人开发以及测试环境

  • Spring Tool Suite4
  • JDK17
  • Maven3.6
  • Springboot2.7.6
  • VMware16 & Linux(Centos-9-Stream)

关于IDE的选择

有人喜欢使用 IntelliJ IDEA ,有人喜欢用 Vim,有人喜欢用VSCode,还有人就偏爱 eclipse 。开发工具 IDE 的选择,不一定非要和我保持一致。个人开发者可以根据自己的喜好选择,怎么顺手怎么来,主打一个用的舒心。如果是团队开发,最优质的方案是与团队保持一致。

可能是入坑最开始接触的 IDE 是eclipse,习惯了。个人小项目偶尔会用VSCode,大型项目更趋向于 eclipse 或者 IntelliJ IDEA。

STS4 开发工具

支持OS版本,彼时最新版本是4.17.1

  • Linux X86_64、Linux ARM_64
  • MACOS X86_64、MACOS ARM_64
  • WINDOWS X86_64

官网:
https://spring.io/tools

下载地址:
https://download.springsource.com/release/STS4/4.16.1.RELEASE/dist/e4.25/spring-tool-suite-4-4.16.1.RELEASE-e4.25.0-win32.win32.x86_64.self-extracting.jar

image

Maven 环境配置

STS4开发工具引入Maven配置

依次找到顶部菜单栏:Window---> Preferences---> Maven--->User Settings--->Global Settings & User Settings--->Apply

配置完记得点击Apply或者Apply and Close

image

Maven Repo配置阿里云镜像源

本地maven环境配置conf\settings.xml(使用阿里云镜像地址),
maven版本
:apache-maven-3.6.3

配置本地repo仓库存储目录

<localRepository>D:\Maven\repo</localRepository>	

配置mirrors

  <mirrors>
    <mirror>
          <id>aliyunmaven</id>
          <name>aliyun maven</name>
          <!-- 老版本url -->
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          <!-- 新版本url -->
          <!--<url>https://maven.aliyun.com/repository/public/</url>-->
          <mirrorOf>central</mirrorOf>        
    </mirror>
  </mirrors>

阿里云Maven中央仓库为
阿里云云效
提供的公共代理仓库,帮助研发人员提高研发生产效率,使用阿里云Maven中央仓库作为下载源,速度更快更稳定。

仓库名称 阿里云仓库地址 阿里云仓库地址(老版) 源地址
central https://maven.aliyun.com/repository/central https://maven.aliyun.com/nexus/content/repositories/central https://repo1.maven.org/maven2/
jcenter https://maven.aliyun.com/repository/public https://maven.aliyun.com/nexus/content/repositories/jcenter http://jcenter.bintray.com/
public https://maven.aliyun.com/repository/public https://maven.aliyun.com/nexus/content/groups/public central仓和jcenter仓的聚合仓
google https://maven.aliyun.com/repository/google https://maven.aliyun.com/nexus/content/repositories/google https://maven.google.com/
gradle-plugin https://maven.aliyun.com/repository/gradle-plugin https://maven.aliyun.com/nexus/content/repositories/gradle-plugin https://plugins.gradle.org/m2/
spring https://maven.aliyun.com/repository/spring https://maven.aliyun.com/nexus/content/repositories/spring http://repo.spring.io/libs-milestone/
spring-plugin https://maven.aliyun.com/repository/spring-plugin https://maven.aliyun.com/nexus/content/repositories/spring-plugin http://repo.spring.io/plugins-release/
grails-core https://maven.aliyun.com/repository/grails-core https://maven.aliyun.com/nexus/content/repositories/grails-core https://repo.grails.org/grails/core
apache snapshots https://maven.aliyun.com/repository/apache-snapshots https://maven.aliyun.com/nexus/content/repositories/apache-snapshots https://repository.apache.org/snapshots/

在线搜索jar包依赖

https://developer.aliyun.com/mvn/search

Springboot集成nacos服务

主要介绍Springboot项目以微服务形式集成nacos,如果使用springmvc或者是普通springboot项目集成nacos服务,可以参考官方文档。
关于版本问题
,我将官方部分(个人感觉初次使用可能用得上,并非全部)文档引入到本次教程。

tips
:sts个性化注解设置,Window---> Preferences--->Java--->Code Style--->Code Templates--->Comments:Types method

在集成nacos服务之前,一步一步来,从构建第一个springboot项目开始。

springboot项目构建

可能你有疑问,springboot版本如何选择,下图支持最后维护时间可供参考:

image

目前Springboot官网最新稳定版本是Springboot3.0.5,实际工作中个人使用2.7.x版本,未来主流可能是3.X版本。目前主流依旧是Springboot2.x,更倾向于2.6.x或者2.7.x作为开发构建版本,当然也是以spring官网显示维护时间作为参考。

项目构建方式有多种,总体上分官网脚手架构建和开发工具构建,此处以官网和STS4为示例。不必纠结,怎么顺手怎么来。

如果官网访问速度缓慢,你还可以通过阿里云脚手架网站构建

https://start.aliyun.com/

springboot项目之官网构建

访问:
https://start.spring.io/

第一步

  1. Project:选择Maven作为项目构建方式
  2. Language:选择Java作为开发语言
  3. Spring Boot:选择springboot2.7.6稳定版本作为构建版本
  4. Dependencies:选择pom依赖的jar包

image

第二步

  1. Project Metadata:项目元数据,Group组名、Artifact工程名、Name项目名称、Description项目描述、Package name包名。
  2. Packaging:选择Jar作为默认打包方式。
  3. Java:选择JDK17作为Java项目默认构建版本。
  4. GENERATE:生成构建项目demo并下载。
  5. EXPLORE:展示出构建项目结构清单以及文件具体内容。

以下展示Project Metadata截图以及EXPLORE截图。

image

EXPLORE:展示项目层次结构

image

springboot项目之STS4工具构建

依次选择File-->new-->Spring Starter Project,或者使用快捷键ALT+SHIFT+N。

配置Project步骤一

具体含义参考上面官网构建时说明。

image

配置Project步骤二

选择springboot版本,以及配置所需要的pom.xml依赖。

如下图所示,
Spring Boot Version
个人选择的是springboot2.7.6稳定版本作为演示,目前最新稳定版本可选为springboot3.0.5。
Frequently Used
表示官方建议使用到的一些工具,讲几个个人使用过的。

Lombok用于简化实体类(bean、entity、repository)get、set方法以及log日志打印,个人开发很实用,团队中慎重使用。

MariaDB Driver是MariaDB数据库驱动,可以看做是MySQL替代产品。Spring Data JPA是对ORM持久化框架Hibernate近一步封装,简化了SQL操作。Spring Data MongoDB是nosql数据库中的一种,其它有Redis,主要用于做缓存使用。Spring Data Redis是nosql数据库中的一种,前面刚好介绍到了。

搜索框Type to search dependencies
,可以进行检索需要的依赖,也可以展开下面小箭头选择依赖。比如展开下图上的SQL选项,有多种数据库驱动依赖可供选择使用。

image

springboot集成微服务nacos

正式搭建之前,注意项目环境:使用Spring如何集成nacos?使用springboot如何集成nacos?使用springcloud微服务组件如何集成nacos?此处发出了三连问,也许你在使用时也会遇到。不同的环境,可能得到的结果不一样。

**下面将演示 springboot 项目集成微服务组件 nacos 过程 **。

1、启动示例

本地正常启动场景,使用127.0.0.1或者localhost,默认端口:8080。特殊情况在配置文件指定了固定IP地址。例如在application.properties 或者 application.yml 指定IP和端口,两种配置方式保留一种即可。

示例:application.properties

server.port=8081
server.address=192.168.1.666666

示例:application.yml

server:
  port: 8081
  address: 192.168.1.666666

springboot启动过程:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.7.6)

2022-10-26 20:13:06.902  INFO 16620 --- [           main] com.test.demo.Application                : Starting Application using Java 17.0.2 on kart with PID 16620 (...)
2022-10-26 20:13:08.091  INFO 16620 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
...
2022-10-26 20:13:10.260  INFO 16620 --- [           main] c.a.c.n.registry.NacosServiceRegistry    : nacos registry, DEFAULT_GROUP springboot-test 192.168.245.1:8080 register finished
2022-10-26 20:13:10.579  INFO 16620 --- [           main] com.test.demo.Application                : Started Application in 4.45 seconds (JVM running for 5.251)

2、配置pom.xml

springbot版本简要说明
:springboot GA(General Availability 表示稳定版本),各分支最新稳定版本2.3.12、2.4.13、2.5.14、2.6.14、2.7.10、3.0.5

	<!-- springboot GA(最新稳定版本):2.3.12、2.4.13、2.5.14、2.6.14、2.7.10、3.0.5 -->
	<!-- springboot pom.xml parent父类 -->
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.7.6</version>
		<relativePath/> 
	</parent>

统一版本管理
:JDK版本:17,spring-cloud-dependencies版本采用2021.0.4,spring-cloud-alibaba-dependencies版本采用:2021.0.4.0,和我保持一致,可以采用springboot2.7.x进行测试使用。目前,如果使用阿里云脚手架构建,springboot版本推荐不高于2.6.13。在nacos系列博文第一篇《MySQL数据库与Nacos搭建监控服务》中有写到如何构建,这里不在赘述。

	<properties>
		<java.version>17</java.version>
        <spring-cloud-dependencies.version>2021.0.4</spring-cloud-dependencies.version>
		<spring-cloud-alibaba-dependencies.version>2021.0.4.0</spring-cloud-alibaba-dependencies.version>
	</properties>	

pom依赖管理:dependency

主要注意版本对应:

  • 微服务:spring cloud依赖
  • 微服务:spring cloud alibaba依赖
  • 微服务:nacos config依赖、nacos discovery依赖、bootstrap依赖
<!-- 总包管理 -->
	<dependencyManagement>
		<dependencies>
			<!-- spring cloud依赖 -->
			<dependency>
			    <groupId>org.springframework.cloud</groupId>
			    <artifactId>spring-cloud-dependencies</artifactId>
			    <version>${spring-cloud-dependencies.version}</version>
			    <type>pom</type>
			    <scope>import</scope>
			</dependency>
			<!-- spring cloud alibaba依赖 -->
			<dependency>
			    <groupId>com.alibaba.cloud</groupId>
			    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
			    <version>${spring-cloud-alibaba-dependencies.version}</version>
			    <type>pom</type>
			    <scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>	
<!-- 引入相关依赖 -->	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- 微服务 nacos config依赖 -->
		<dependency>
   			<groupId>com.alibaba.cloud</groupId>
    		<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
		</dependency>
		<!-- 微服务 nacos discovery依赖 -->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
    		<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
		</dependency>
		<!-- 微服务 bootstrap依赖  -->
		<dependency>
		    <groupId>org.springframework.cloud</groupId>
		    <artifactId>spring-cloud-starter-bootstrap</artifactId>
		</dependency>
	</dependencies>	

3、配置application.properties & application.yml & bootstrap.properties

如果使用开发环境多配置文件设置,可以使用:dev、prod、test进行区分,使用参数 spring.profiles.active=dev 进行指定为开发环境。

# api port
server.port=8080
# 开发环境多配置文件设置:dev、prod、test
spring.profiles.active=dev
# 服务名称
spring.application.name=springboot-test
###################################nacos配置#######################################
# nacos 打开监控 配合普罗米修斯进行监控 官方提供了MySQL初始化sql文件 在conf目录下:nacos-mysql.sql
# 暴露metrics数据
management.endpoints.web.exposure.include=*
# nacos 配置注册远程服务地址{config.server-addr和server-addr}
#spring.cloud.nacos.config.server-addr=192.168.245.132:8848
# nacos 配置注册与发现{discovery.server-addr}
spring.cloud.nacos.discovery.server-addr=192.168.245.132:8848
#spring.cloud.nacos.discovery.namespace=public
spring.cloud.nacos.config.file-extension=properties
###################################nacos配置#######################################

bootstrap.properties

#bootstrap.properties基础配置
#服务名称
spring.application.name=springboot-test
#暴露config配置服务地址(动态更新)
spring.cloud.nacos.config.server-addr=192.168.245.132:8848
#配置config文件扩展名(properties & yml)
spring.cloud.nacos.config.file-extension=properties

4、springboot入口:配置Application.java

  • @SpringBootApplication:sringboot启动必备注解
  • @EnableDiscoveryClient:用于nacos发现客户端注解
@SpringBootApplication	//sringboot启动必备注解
@EnableDiscoveryClient  //用于nacos发现客户端注解
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

TestController类引入测试代码:使用 Spring Cloud 原生注解,开启自动更新。

单一配置场景,通过@Value注解,配置动态配置获取测试。

多属性配置场景,可以通过@Autowired 注解注入自定义配置类。

@RestController
@RefreshScope // Spring Cloud 原生注解 开启自动更新
@RequestMapping(value = "/t")
public class TestController {
	/** 使用动态配置获取测试 --BEGIN-- **/
	@Value("${alibaba.config.discovery}")
	private String discovery;
	
    @Value("${alibaba.config.name}")
	private String name;

	@GetMapping("/getConfig")
	public String getConfig() {
		log.info("getConfig>>>>>>>>>>>");
		return "getConfig>>>>>>>>>>>>" + "发现:" + discovery + ">>>服务名称:" + name;
	}
	
	@Value("${custom.config.find}")
	private String find;
	
	@Value("${custom.config.say}")
	private String say;
	
	@GetMapping("/meet")
	public String meet() {
		log.info("meet>>>>>>>>>>>");
		return "meet>>>>>>>>>>>>" + "偶遇老湿:" + find + ">>>你好:" + say;
	}
	/** 使用动态配置获取测试 --END-- **/
	}
}

可以根据业务情况而定,将多个配置封装到一个类里面,如下所示,新建CustomConf类:

/**
 * desc:对象配置类
 * @Value 
 * @Component @ConfigurationProperties
 */
@Component
@ConfigurationProperties(prefix = "custom")
public class CustomConf {
	private Integer one;
	private Integer two;
	private Integer three;
	private String description;

    // 此处省略掉了 get set 方法,实际需要补上
}

使用到注解:

  • @Value :通常情况,使用注解取值。
  • @Component :加入注解,便于被扫描到。
  • @ConfigurationProperties:引入配置,通过prefix指定配置前缀。

在项目中注入:

@Autowired
private CustomConf custom;

@RequestMapping("/custom")
   public String custom() {
       return "[custom] " + custom;
   }

nacos 控制台:新建配置
:springboot-test.properties

nacos 控制台:配置详情
:配置的比较随意,参考看看就行

运行服务

  • nohup:代表脱离终端运行
  • &:代表放入后台
  • -Dspring.profiles.active=prod:指定为生产环境
[root@Centos9-Stream test]# nohup java -jar -Dspring.profiles.active=prod /opt/workspace/test/springboot-nacos-cloud-0.0.1-SNAPSHOT.jar  > /opt/
workspace/test/springboot-nacos.log 2>&1 &
[1] 4628

测试接口

http://192.168.245.132:8082/t/getConfig

你也可以使用curl命令请求:

curl -X GET http://192.168.245.132:8082/t/getConfig

得到输出结果

getConfig>>>>>>>>>>>>发现:配置>>>服务名称:nacos服务

查看日志验证:

tail -n 5 springboot-nacos.log

验证成功,getConfig日志打印出来了。

至此,springboot集成微服务组件nacos联动完成,详细说明可以参考上一篇文章,文初有说明,这里不在赘述。

实际使用过程中,难免会遇到各种问题,此时不用慌,有详细官方文档可供参考。如下引入了nacos部分文档,解决搭建过程中遇到的问题,提供参考

nacos版本问题

如果你使用nacos 1.x升级到nacos 2.x版本,建议参考官方兼容性说明与升级文档

nacos2.0.0版本兼容性说明

兼容性说明:
https://nacos.io/zh-cn/docs/v2/upgrading/2.0.0-compatibility.html

目前官网推荐使用nacos2.1.1稳定版
,推荐的上一个稳定版本是2.0.3。

Nacos2.0版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成。

端口 与主端口的偏移量 描述
9848 1000 客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求
9849 1001 服务端gRPC请求服务端端口,用于服务间同步等

使用VIP/nginx请求时,需要配置成TCP转发,不能配置http2转发,否则连接会被nginx断开。

客户端拥有相同的计算逻辑,用户如同1.X的使用方式,配置主端口(默认8848),通过相同的偏移量,计算对应gRPC端口(默认9848)。

因此
如果客户端和服务端之前存在端口转发或防火墙时,需要对端口转发配置和防火墙配置做相应的调整

nacos升级文档

升级文档:
https://nacos.io/zh-cn/docs/v2/upgrading/2.0.0-upgrading.html

nacos版本与spring cloud对应关系

由于 Spring Boot 3.0,Spring Boot 2.7~2.4 和 2.4 以下版本之间变化较大,目前企业级客户老项目相关 Spring Boot 版本仍停留在 Spring Boot 2.4 以下,为了同时满足存量用户和新用户不同需求,社区以 Spring Boot 3.0 和 2.4 分别为分界线,同时维护 2022.x、2021.x、2.2.x 三个分支迭代。 为了规避相关构建过程中的依赖冲突问题,我们建议可以通过
云原生应用脚手架
进行项目创建。

2022.x 分支

适配 Spring Boot 3.0,Spring Cloud 2022.x 版本及以上的 Spring Cloud Alibaba 版本按从新到旧排列如下表(最新版本用*标记): (注意,该分支 Spring Cloud Alibaba 版本命名方式进行了调整,未来将对应 Spring Cloud 版本,前三位为 Spring Cloud 版本,最后一位为扩展版本,比如适配 Spring Cloud 2022.0.0 版本对应的 Spring Cloud Alibaba 第一个版本为:2022.0.0.0,第个二版本为:2022.0.0.1,依此类推)

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2022.0.0.0-RC* Spring Cloud 2022.0.0 3.0.0

2021.x 分支

适配 Spring Boot 2.4,Spring Cloud 2021.x 版本及以上的 Spring Cloud Alibaba 版本按从新到旧排列如下表(最新版本用*标记): (注意,该分支 Spring Cloud Alibaba 版本命名方式进行了调整,未来将对应 Spring Cloud 版本,前三位为 Spring Cloud 版本,最后一位为扩展版本,比如适配 Spring Cloud 2021.0.1 版本对应的 Spring Cloud Alibaba 第一个版本为:2021.0.1.0,第个二版本为:2021.0.1.1,依此类推)

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2021.0.5.0* Spring Cloud 2021.0.5 2.6.13
2021.0.4.0* Spring Cloud 2021.0.4 2.6.11
2021.0.1.0 Spring Cloud 2021.0.1 2.6.3
2021.1 Spring Cloud 2020.0.1 2.4.2

2.2.x 分支

适配 Spring Boot 为 2.4,Spring Cloud Hoxton 版本及以下的 Spring Cloud Alibaba 版本按从新到旧排列如下表(最新版本用*标记):

Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version
2.2.10-RC1* Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.9.RELEASE* Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.8.RELEASE Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.7.RELEASE Spring Cloud Hoxton.SR12 2.3.12.RELEASE
2.2.6.RELEASE Spring Cloud Hoxton.SR9 2.3.2.RELEASE
2.2.1.RELEASE Spring Cloud Hoxton.SR3 2.2.5.RELEASE
2.2.0.RELEASE Spring Cloud Hoxton.RELEASE 2.2.X.RELEASE
2.1.4.RELEASE Spring Cloud Greenwich.SR6 2.1.13.RELEASE
2.1.2.RELEASE Spring Cloud Greenwich 2.1.X.RELEASE
2.0.4.RELEASE(停止维护,建议升级) Spring Cloud Finchley 2.0.X.RELEASE
1.5.1.RELEASE(停止维护,建议升级) Spring Cloud Edgware 1.5.X.RELEASE

组件版本关系

版本说明

https://github.com/alibaba/spring-cloud-alibaba/wiki/版本说明

每个 Spring Cloud Alibaba 版本及其自身所适配的各组件对应版本如下表所示(注意,Spring Cloud Dubbo 从 2021.0.1.0 起已被移除出主干,不再随主干演进):

Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Version Dubbo Version Seata Version
2021.0.5.0 1.8.6 2.2.0 4.9.4 ~ 1.6.1
2.2.10-RC1 1.8.6 2.2.0 4.9.4 ~ 1.6.1
2022.0.0.0-RC1 1.8.6 2.2.1-RC 4.9.4 ~ 1.6.1
2.2.9.RELEASE 1.8.5 2.1.0 4.9.4 ~ 1.5.2
2021.0.4.0 1.8.5 2.0.4 4.9.4 ~ 1.5.2
2.2.8.RELEASE 1.8.4 2.1.0 4.9.3 ~ 1.5.1
2021.0.1.0 1.8.3 1.4.2 4.9.2 ~ 1.4.2
2.2.7.RELEASE 1.8.1 2.0.3 4.6.1 2.7.13 1.3.0
2.2.6.RELEASE 1.8.1 1.4.2 4.4.0 2.7.8 1.3.0
2021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE 1.8.0 1.4.1 4.4.0 2.7.8 1.3.0
2.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE 1.8.0 1.3.3 4.4.0 2.7.8 1.3.0
2.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE 1.7.1 1.2.1 4.4.0 2.7.6 1.2.0
2.2.0.RELEASE 1.7.1 1.1.4 4.4.0 2.7.4.1 1.0.0
2.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE 1.7.0 1.1.4 4.4.0 2.7.3 0.9.0
2.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE 1.6.3 1.1.1 4.4.0 2.7.3 0.7.1

排查错误

启动时报错
Connection is unregistered.

Client not connected,current status:STARTING
.

原因是
客户端gRPC无法和服务端创建连接
,请先使用
telnet ${nacos.server.address}:${nacos.server.grpc.port}
进行测试,查看网络是否畅通,服务端端口是否已经正确监听。

Nacos2.0增加了9848,9849端口来进行GRPC通信,我需要在application.properties中额外配置吗?

不需要,这两个端口在Nacos2.0内部是通过8848+1000以及8848+1001这种偏移量方式计算出来的,不需要用户额外在配置文件中配置。但如果使用的是docker或存在端口转发方式启动,需要把这两个端口进行配置。

启动nacos2.0时希望用nginx 代理,9848这个端口怎样处理,要通过nginx暴露出来么?以及docker是否需要映射?

如果存在防火墙或者nginx端口转发问题,需要进行相应的端口暴露配置。如在nginx中,在已经暴露8848(x)的基础上,需要额外暴露9848(x+1000)。

解决版本冲突问题时遇到端口未开放

2022-12-07 20:08:13.792  INFO 17152 --- [t.remote.worker] com.alibaba.nacos.common.remote.client   : [236c02fe-157b-475e-9540-b11bf110f49e_config-0] Fail to connect server, after trying 2 times, last try server is {serverIp = '192.168.245.132', server main port = 8858}, error = unknown
2022-12-07 20:08:16.213 ERROR 17152 --- [           main] c.a.n.c.remote.client.grpc.GrpcClient    : Server check fail, please check server 192.168.245.132 ,port 9858 is available , error ={}

分析问题
:检查192.168.245.132服务器的端口9858是否可用。如果参考了上面文档,你会发现我默认端口为8858,nacos2.x使用gRPC通信方式,+1000偏移量检测9858是否可用,由于防火墙或端口转发等原因,需要开发相应端口。

开放相应端口

firewall-cmd --zone=public --add-port=9858/tcp --permanent
firewall-cmd --zone=public --add-port=9859/tcp --permanent

重载firewall-cmd服务

firewall-cmd --reload

查看开放的端口

[root@Centos9-Stream nacos-2.0.4]# firewall-cmd --list-all
public (active)
...
ports: 8848/tcp 9001-9010/tcp 8081/tcp 3000/tcp 8082/tcp 8083/tcp 8858/tcp 9858/tcp
...

启动多个nacos server服务导致nacos-server.jar占用 PID 问题

定位问题:查看nacos.log日志文件

2022-12-07 20:19:02,601 INFO Starting Nacos v2.0.3 on Centos9-Stream with PID 34970 (/usr/local/nacos/target/nacos-server.jar started by root in /usr/local/nacos-2.0.4)

参考资料

切记切记,以上总结,仅供参考!别人提供的是思考方向,具体实践还需亲自测试印证。

有不足的地方,还望各位大佬轻喷。

END----

静下心来,才发现原来不会的还有很多。

一分耕耘,一分收获。

多总结,你会发现,自己的知识宝库越来越丰富。

前言

本文旨在为读者解析Spring源码中的关键类,以便读者在深入阅读源码时,能够了解关键类的作用和用途。在阅读Spring源码时,经常会遇到一些不熟悉的概念,了解关键类的作用可以帮助读者更好地理解这些概念。

BeanDefinition

BeanDefinition是Spring框架中的一个重要概念,它定义了一个Bean的基本属性和行为,比如:

  1. BeanClassName,当前的bean名字
  2. Scope,是否单例,具体枚举:#SCOPE_SINGLETON、#SCOPE_PROTOTYPE
  3. LazyInit,是否懒加载,默认不是
  4. DependsOn,是否依赖其他bean,如果依赖,则会先创建依赖bean
  5. InitMethodName,初始化方法名称
  6. DestroyMethodName,销毁类方法名称
  7. ......还有更多,但是这几个大体已经差不多了

BeanDefinition的作用非常重要,它可以帮助Spring容器更好地管理Bean的生命周期和依赖关系。在Spring框架中,我们经常会通过注解方式来定义Bean:

  1. < bean/>
  2. @Bean
  3. @Component(@Controller、@Service)

这些都是被称为申明式定义Bean。就是使用Spring提供好的封装。

除了注解方式,我们还可以通过编程方式来定义Bean,这时就需要直接使用BeanDefinition来创建BeanDefinition对象,并设置对应的属性,然后将其注册到Spring容器中,比如

    // 创建一个Spring容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition.setBeanClass(UserService.class);
        //当然还可以设置其他上面我说的其他属性:懒加载什么的
        applicationContext.registerBeanDefinition("userService", beanDefinition);

        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.test();

无论是通过注解方式还是编程方式来定义Bean,最终都是需要使用BeanDefinition来描述Bean的基本属性和行为,然后将其放入Spring容器中进行管理。

BeanDefinitionReader

BeanDefinitionReader是Spring框架中的一个重要组件,主要用于读取和操作BeanDefinition对象。虽然我们在使用Spring框架时很少直接使用BeanDefinitionReader,但在Spring源码中却扮演着非常重要的角色,相当于Spring源码的基础设施。

BeanDefinitionReader的核心方法包括以下几个:

  1. BeanDefinitionRegistry,用来注册bean定义,相当于一个工厂
  2. BeanNameGenerator,用来生成bean名字的生成器
  3. loadBeanDefinitions,从资源中加载bean

XmlBeanDefinitionReader

XmlBeanDefinitionReader是BeanDefinitionReader的子类,可以用于从XML文件中读取BeanDefinition并注册到Spring容器中。使用XmlBeanDefinitionReader的步骤如下:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(context);
//加载xml中配置的所有<bean>
int i = xmlBeanDefinitionReader.loadBeanDefinitions("spring.xml");
System.out.println(context.getBean("userService"))

AnnotatedBeanDefinitionReader

细心的朋友,应该可以发现AnnotatedBeanDefinitionReader是一个单独的类,不是BeanDefinitionReader的子类,但它的方法与BeanDefinitionReader基本相同,官方说是方便的适配器,用于编程注册bean类,他可以解析@Conditional,@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description相关注解,具体操作如下:

// 创建一个Spring容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

//        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
//        beanDefinition.setBeanClass(UserService.class);
//        applicationContext.registerBeanDefinition("userService", beanDefinition);

        new AnnotatedBeanDefinitionReader(applicationContext).registerBean(UserService.class);

        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.test();

同样的,他也可以让我们注册的bean走完创建的整个生命周期过程。

ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner也是一个用于注册BeanDefinition的工具类,与BeanDefinition接口没有直接关系。ClassPathBeanDefinitionScanner可以扫描指定包路径下带有特定注解的类,并将其解析成BeanDefinition,注册到Spring容器中。主要是他有个scan方法对我们定义的basepackage包路径进行解析扫描所有带有@component、@ManagedBean(JSR-250标准)、@Named(JSR-330标准)
使用ClassPathBeanDefinitionScanner的步骤如下:

// 创建一个Spring容器  
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);  
  
new ClassPathBeanDefinitionScanner(applicationContext).scan("com.xiaoyu");  
UserService userService = (UserService) applicationContext.getBean("userService");  
userService.test();

BeanFactory

BeanFactory是Spring框架中的一个重要接口,他就是Spring用于管理Bean对象的创建和管理,看他的几个主要方法就知道了:

  1. getBean,可以根据name、type等获取bean对象
  2. containsBean,是否bean工厂中有某个对象
  3. isSingleton,判断是否是单例
  4. isTypeMatch,判断改name是否匹配类型
  5. getType,根据bean名字获取类型
  6. getAliases。获取别名数组
    看着主要几个接口实现,基本都是围绕bean所做的,然后根据接口再看他的实现类就方便许多了,

DefaultListableBeanFactory

如果看过源码的朋友肯定对这个实现类不陌生,如果对这个实现类陌生的朋友,那请记住这个重要的实现类,它实现了很多接口、且继承了多层父类,所以他的功能也是相当之多。我们来看看他的主要方法:

  1. containsBeanDefinition,查看是否包含某个bean定义,因为该类维护了一个Map<String, BeanDefinition> beanDefinitionMap属性。
  2. determineAutowireCandidate,决定注入哪个bean,@Primary-->优先级最高--->name
  3. doResolveDependency,解析依赖,进行注入
  4. registerBeanDefinition,注册bean定义到beanDefinitionMap属性
  5. preInstantiateSingletons,进行创建bean实例

具体使用操作也是基本类似的

AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();  
beanDefinition.setBeanClass(UserService.class);  
defaultListableBeanFactory.registerBeanDefinition("userService",beanDefinition);  
  
UserService userService1 = (UserService) defaultListableBeanFactory.getBean("userService");  
userService1.test();

从他的结构图也能看出来:

image

AbstractBeanFactory

该类是抽象bean,介绍他主要目的就是getbean时,走的主要逻辑就是该类实现的dogetbean方法(请记住这个重要的方法),所以确实需要关注下,主要方法如下:

  1. doGetBean,获取bean的主要逻辑,没有则创建
  2. getMergedBeanDefinition,bean定义的合并逻辑,主要是将父类beanfactory中的属性被子类覆盖

AbstractAutowireCapableBeanFactory

继承自刚才提到的AbstractBeanFactory,主要方法如下:

  1. autowireByName,按照name注入
  2. autowireByType,根据类型
  3. createBean,创建bean流程,实例化前可以使用BeanPostProcessors后置处理器
  4. createBeanInstance,正在创建bean,这边使用到了之前入门讲过的推断构造器实现实例化
  5. doCreateBean,创建bean,循环依赖、属性填充、初始化
  6. initializeBean,初始化bean,包括初始化前、初始化、初始化后
  7. instantiateUsingFactoryMethod,利用factorymethod初始化bean
  8. invokeAwareMethods,初始化bean时的回调函数Aware接口
  9. populateBean,初始化之前,属性赋值

可以从他的关键方法看出,主要作用就是初始化bean的全过程,也是很重要的类

HierarchicalBeanFactory

这里说下HierarchicalBeanFactory类,他只是一个接口类,但是如果想要使用beanfactory的层次结构,例如获取父beanfactory,那就必须实现HierarchicalBeanFactory类,比如前面说的bean定义的合并逻辑,就需要获取父beanfactory,从而实现父子bean定义的覆盖合并

ApplicationContext

ApplicationContext是个接口,实际上也是一个BeanFactory,不过比BeanFactory
更加强大,它本身并没有太多方法,但是它继承了很多接口,因为接口之间是可以多继承的。

image

关于他的父接口,这里不做多说明,详情的话请看下子文章(后续更新)。

AnnotationConfigApplicationContext

一看这个类,大家都知道了,我们用的实例全是用这个类去启动我们的spring的,我们看看他的主要方法:

  1. AnnotationConfigApplicationContext,构造器,会初始化DefaultListableBeanFactory、AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner;然后开始调用refresh()方法。
  2. register,会使用编程式定义将bean注入spring容器。就如我们的APPConfig
  3. scan,走ClassPathBeanDefinitionScanner的scan,扫描包路径,将声明式的bean注入进spring容器
  4. setBeanNameGenerator,bean名称生成器

ClassPathXmlApplicationContext

主要就是去解析xml配置的bean定义将其注入到spring容器中,功能其实跟AnnotationConfigApplicationContext类似,但是却没有AnnotationConfigApplicationContext强大,比如不能注册BeanDefinition。

BeanPostProcessor

BeanPostProcess表示Bena的后置处理器,可以有多个BeanPostProcessor,我们自己也可以去定义一个BeanPostProcessor;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if ("userService".equals(beanName)) {
            System.out.println("userService");
            return new User();
        }
        System.out.println("MyBeanPostProcessor.postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("MyBeanPostProcessor.postProcessAfterInitialization");
        return bean;
    }

我们可以通过实现bean的后置处理器,来对某一个bean或者所有bean的进行干预,博主只是随便写了一个,没有什么太大意义。

BeanFactoryPostProcessor

BeanFactoryPostProcessor表示Bean工厂的后置处理器,其实和BeanPostProcessor类似,BeanPostProcessor是干涉Bean的创建过程,BeanFactoryPostProcessor是干涉BeanFactory的创建过程,我们也可以自定义:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("加工beanFactory");
    }
}

FactoryBean

FactoryBean和BeanFactory不是一个东西,大家不要混淆两个概念,BeanFactory是管理我们注入的bean等,而FactoryBean本身也会被Spring管理,一旦Spring知道我们的bean实现了FactoryBean,那么会自动调用getObject方法获取我们自己创建的bean,这个bean完完全全交给我们自己创建了,我们可以这样定义一个FactoryBean:


@Component
public class MyFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        UserService service = new UserService();
        return service;
    }

    @Override
    public Class<?> getObjectType() {
        return UserService.class;
    }

}

但是需要注意的是,这些注入UserService时,是不会有属性依赖注入的,毕竟他没有走bean的生命创建周期。细心的朋友会发现,这根我在配置类中写@bean形式的类有啥区别,现象来讲,他俩都可以被创建出来,但是值得一提的是,FactoryBean创建出来的bean是没走spring定义的bean生命周期的。

MetadataReader、ClassMetadata、AnnotationMetadata

Spring启动时需要扫描指定包路径下的所有类文件来获取需要注入或管理的Bean信息。然而,并非所有类都是需要的,这时可以使用ASM技术来解析类文件的元数据信息,包括类上的注解信息和类的基本信息。ASM技术可以在运行时动态生成和修改Java字节码,从而高效地解析类文件的元数据信息,避免了大量的IO操作和类加载,提高了应用的性能。以下是一个简单的实例:

        SimpleMetadataReaderFactory simpleMetadataReaderFactory = new SimpleMetadataReaderFactory();
        MetadataReader metadataReader = simpleMetadataReaderFactory.getMetadataReader("com.xiaoyu.service.UserService");
        System.out.println(metadataReader.getClassMetadata().getClassName());
        metadataReader.getAnnotationMetadata().getAnnotationTypes().forEach(System.out::println);

结语

通过本文的解析,我们大致了解了Spring框架中的一些关键组件及其用途,这有助于我们在深入理解Spring源码过程中建立起一个整体框架。Spring源码量很大,要真正理解透彻还需要投入大量时间进行细致学习和总结。但如果先对一些关键组件有一个大致的认识,有助于我们进行针对性学习,避免迷失在繁杂的细节中。希望本文能够对读者有一定的帮助,更希望读者在学习Spring源码的过程中,不断总结和提高,并在一定阶段有所突破。祝读者顺利!
公众号